2017-03-08 13:53:09 -05:00
from werkzeug . security import generate_password_hash , check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app , request , url_for
from flask_login import UserMixin , AnonymousUserMixin
2017-03-08 20:31:16 -05:00
from app . exceptions import ValidationError
2017-03-08 13:53:09 -05:00
from . import db , lm
import os
import base64
2017-03-13 09:36:21 -04:00
import hashlib
from decimal import Decimal
from datetime import date , time , datetime , timedelta
2017-03-08 13:53:09 -05:00
import json
2017-03-13 09:36:21 -04:00
from sortedcontainers import SortedDict
2017-03-08 13:53:09 -05:00
import requests
import onetimepass
class Permission :
DEPLOY = 0x01
ADMINISTER = 0x80
class Role ( db . Model ) :
__tablename__ = ' roles '
pid = db . Column ( db . Integer , primary_key = True )
name = db . Column ( db . String ( 64 ) , unique = True )
default = db . Column ( db . Boolean , default = False , index = True )
permissions = db . Column ( db . Integer )
users = db . relationship ( ' User ' , backref = ' role ' , lazy = ' dynamic ' )
@staticmethod
def insert_roles ( ) :
roles = {
' User ' : ( Permission . DEPLOY , True ) ,
' Administrator ' : ( 0xff , False )
}
for r in roles :
role = Role . query . filter_by ( name = r ) . first ( )
if role is None :
role = Role ( name = r )
role . permissions = roles [ r ] [ 0 ]
role . default = roles [ r ] [ 1 ]
db . session . add ( role )
db . session . commit ( )
def __repr__ ( self ) :
return ' <Role %r > ' % self . name
class User ( db . Model , UserMixin ) :
__tablename__ = ' users '
pid = db . Column ( db . Integer , primary_key = True )
email = db . Column ( db . String ( 64 ) , unique = True , index = True )
role_id = db . Column ( db . Integer , db . ForeignKey ( ' roles.pid ' ) ) #FK
password_hash = db . Column ( db . String ( 128 ) )
confirmed = db . Column ( db . Boolean , default = False )
2017-03-13 09:36:21 -04:00
active = db . Column ( db . Boolean , default = True )
2017-03-08 13:53:09 -05:00
member_since = db . Column ( db . DateTime ( ) , default = datetime . utcnow )
last_seen = db . Column ( db . DateTime ( ) , default = datetime . utcnow )
last_ip = db . Column ( db . String ( 128 ) )
twofactor = db . Column ( db . Boolean , default = False ) #optional 2factor auth
otp_secret = db . Column ( db . String ( 16 ) )
avatar_hash = db . Column ( db . String ( 32 ) )
name = db . Column ( db . Unicode ( 256 ) )
address = db . Column ( db . Unicode ( 256 ) )
city = db . Column ( db . Unicode ( 64 ) )
postcode = db . Column ( db . String ( 10 ) )
country = db . Column ( db . String ( 64 ) )
phone = db . Column ( db . String ( 64 ) )
2017-03-13 09:36:21 -04:00
org_companyname = db . Column ( db . Unicode ( 64 ) )
org_regaddress = db . Column ( db . Unicode ( 128 ) )
2017-03-08 13:53:09 -05:00
org_responsible = db . Column ( db . Unicode ( 128 ) )
org_bulstat = db . Column ( db . String ( 16 ) )
2017-03-13 09:36:21 -04:00
org_vat = db . Column ( db . Boolean , default = False )
org_vatnum = db . Column ( db . String ( 16 ) )
wallet = db . Column ( db . Float )
credit = db . Column ( db . Float )
creditlimit = db . Column ( db . Float , default = 20.0 )
currency = db . Column ( db . String ( 3 ) , default = ' BGN ' )
language = db . Column ( db . String ( 2 ) , default = ' BG ' )
2017-03-08 13:53:09 -05:00
inv_deployments = db . relationship ( ' Deployment ' , backref = ' owner ' , lazy = ' dynamic ' )
inv_contracts = db . relationship ( ' Contract ' , backref = ' owner ' , lazy = ' dynamic ' )
inv_domains = db . relationship ( ' Domain ' , backref = ' owner ' , lazy = ' dynamic ' )
def __init__ ( self , * * kwargs ) :
super ( User , self ) . __init__ ( * * kwargs )
if self . role is None :
if self . email == current_app . config [ ' ADMIN_EMAIL ' ] :
#if email match config admin name create admin user
self . role = Role . query . filter_by ( permissions = 0xff ) . first ( )
if self . role is None :
#if role is stil not set, create default user role
self . role = Role . query . filter_by ( default = True ) . first ( )
if self . email is not None and self . avatar_hash is None :
self . avatar_hash = hashlib . md5 ( self . email . encode ( ' utf-8 ' ) ) . hexdigest ( )
if self . otp_secret is None :
# generate a random secret
self . otp_secret = base64 . b32encode ( os . urandom ( 10 ) ) . decode ( ' utf-8 ' )
@property
def password ( self ) :
raise AttributeError ( ' password is not a readable attribute ' )
@password.setter
def password ( self , password ) :
self . password_hash = generate_password_hash ( password )
def verify_password ( self , password ) :
return check_password_hash ( self . password_hash , password )
def get_totp_uri ( self ) :
return ' otpauth://totp/DataPanel: {0} ?secret= {1} &issuer=datapanel ' . format ( self . email , self . otp_secret )
def verify_totp ( self , token ) :
return onetimepass . valid_totp ( token , self . otp_secret )
def generate_confirmation_token ( self , expiration = 86400 ) :
s = Serializer ( current_app . config [ ' SECRET_KEY ' ] , expiration )
return s . dumps ( { ' confirm ' : self . pid } )
def confirm ( self , token ) :
s = Serializer ( current_app . config [ ' SECRET_KEY ' ] )
try :
data = s . loads ( token )
except :
return False
if data . get ( ' confirm ' ) != self . pid :
return False
self . confirmed = True
db . session . add ( self )
db . session . commit ( )
return True
def generate_reset_token ( self , expiration = 86400 ) :
s = Serializer ( current_app . config [ ' SECRET_KEY ' ] , expiration )
return s . dumps ( { ' reset ' : self . pid } )
def reset_password ( self , token , new_password ) :
s = Serializer ( current_app . config [ ' SECRET_KEY ' ] )
try :
data = s . loads ( token )
except :
return False
if data . get ( ' reset ' ) != self . pid :
return False
self . password = new_password
db . session . add ( self )
db . session . commit ( )
return True
def can ( self , permissions ) :
return self . role is not None and ( self . role . permissions & permissions ) == permissions
def is_administrator ( self ) :
return self . can ( Permission . ADMINISTER )
def ping ( self ) :
self . last_seen = datetime . utcnow ( )
db . session . add ( self )
db . session . commit ( )
def gravatar ( self , size = 100 , default = ' identicon ' , rating = ' g ' ) :
#this check is disabled because it didnt work for me but forcing https to gravatar is okay.
#if request.is_secure:
# url = 'https://secure.gravatar.com/avatar'
#else:
# url = 'http://www.gravatar.com/avatar'
url = ' https://secure.gravatar.com/avatar '
hash = self . avatar_hash or hashlib . md5 ( self . email . encode ( ' utf-8 ' ) ) . hexdigest ( )
return ' {url} / {hash} ?s= {size} &d= {default} &r= {rating} ' . format ( url = url , hash = hash , size = size , default = default , rating = rating )
def is_authenticated ( self ) :
return self . is_authenticated
def get_id ( self ) :
return str ( self . pid )
def __repr__ ( self ) :
return ' <User %r > ' % self . email
class AnonymousUser ( AnonymousUserMixin ) :
def can ( self , permissions ) :
return False
def is_administrator ( self ) :
return False
lm . anonymous_user = AnonymousUser
@lm.user_loader
def load_user ( user_id ) :
return User . query . get ( int ( user_id ) )
def contact_proxmaster ( data , method , cubeid = 0 ) :
url = current_app . config [ ' PROXMASTER_URL ' ]
data [ ' apikey ' ] = current_app . config [ ' APIKEY ' ]
data_json = json . dumps ( data )
#print('--> {}'.format(data))
if method == ' vmcreate ' :
url = ' {} / {} ' . format ( url , method )
else :
url = ' {} / {} / {} ' . format ( url , method , cubeid )
db_result = requests . post ( url , data = data_json , headers = { " content-type " : " application/json " } , timeout = 30 )
try :
proxjson = db_result . json ( )
#print('proxmaster query {}'.format(str(proxjson)))
return proxjson
except :
return None
#TEMPLATE CLASSES
class Product ( db . Model ) :
__tablename__ = ' products '
pid = db . Column ( db . Integer , primary_key = True ) #PK
group = db . Column ( db . Integer )
name = db . Column ( db . String ( 64 ) )
image = db . Column ( db . String ( 128 ) )
description = db . Column ( db . String ( 128 ) )
cpu = db . Column ( db . Integer ) #default cpu
mem = db . Column ( db . Integer ) #default mem
hdd = db . Column ( db . Integer ) #default hdd
recipe = db . Column ( db . String ( 128 ) ) #defaut template name
enabled = db . Column ( db . Boolean )
@staticmethod
def insert_products ( ) :
products = current_app . config [ ' PRODUCTS ' ]
for p in products :
product = Product . query . filter_by ( pid = p ) . first ( )
if product is None :
product = Product ( name = p )
#insert default values
product . group = products [ p ] [ 0 ]
product . name = products [ p ] [ 1 ]
product . image = products [ p ] [ 2 ]
product . description = products [ p ] [ 3 ]
product . cpu = products [ p ] [ 4 ]
product . mem = products [ p ] [ 5 ]
product . hdd = products [ p ] [ 6 ]
product . recipe = products [ p ] [ 7 ]
product . enabled = products [ p ] [ 8 ]
db . session . add ( product )
db . session . commit ( )
@staticmethod
def get_products ( ) :
result = Product . query . all ( )
products = { }
for product in result :
if product . enabled == True :
products [ int ( product . pid ) ] = { ' group ' : product . group ,
' img ' : ' /static/images/ ' + product . image ,
' name ' : product . name ,
' description ' : product . description ,
' cpu ' : product . cpu ,
' mem ' : product . mem ,
' hdd ' : product . hdd ,
' recipe ' : product . recipe ,
}
return products
class Service ( db . Model ) :
__tablename__ = ' services '
pid = db . Column ( db . Integer , primary_key = True ) #PK
name = db . Column ( db . String ( 64 ) )
image = db . Column ( db . String ( 128 ) )
2017-03-13 09:36:21 -04:00
description = db . Column ( db . Unicode ( 128 ) )
2017-03-08 13:53:09 -05:00
unitprice = db . Column ( db . Float )
enabled = db . Column ( db . Boolean )
@staticmethod
def insert_services ( ) :
services = current_app . config [ ' SERVICES ' ]
for s in services :
service = Service . query . filter_by ( pid = p ) . first ( )
if service is None :
service = Service ( name = s )
#insert default values
service . name = products [ p ] [ 1 ]
service . image = products [ p ] [ 2 ]
service . description = products [ p ] [ 3 ]
service . unitprice = products [ p ] [ 4 ]
service . enabled = products [ p ] [ 5 ]
db . session . add ( service )
db . session . commit ( )
@staticmethod
def get_services ( ) :
result = Service . query . all ( )
services = { }
for service in result :
if service . enabled == True :
services [ int ( service . pid ) ] = { ' img ' : ' /static/images/ ' + service . image ,
' name ' : service . name ,
' description ' : service . description ,
' unitprice ' : service . unitprice
}
return services
2017-03-13 09:36:21 -04:00
#TRANSACTIONS
class Transaction ( db . Model ) :
__tablename__ = ' transaction '
2017-03-08 13:53:09 -05:00
pid = db . Column ( db . Integer , primary_key = True )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
currency = db . Column ( db . String , default = ' BGN ' )
2017-03-13 09:36:21 -04:00
#PROFORMA INVOICE CLASS
class Invoice ( db . Model ) :
__tablename__ = ' invoice '
pid = db . Column ( db . Integer , primary_key = True )
invoice_number = db . Column ( db . Integer , unique = True )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
currency = db . Column ( db . String , default = ' BGN ' )
tax = db . Column ( db . Float ) #VAT
description = db . Column ( db . Unicode ( 255 ) )
def sub_total ( self ) :
items = self . items
sub_total = 0
for item in items :
sub_total + = item . total ( )
return sub_total
def tax_amount ( self ) :
if not self . tax :
return 0
return self . sub_total ( ) * self . tax / 100.0
def grand_total ( self ) :
amount_str = str ( self . sub_total ( ) + self . tax_amount ( ) )
amount = Decimal ( amount_str )
rounder = Decimal ( " 0.05 " ) # precision for rounding
return amount - amount . remainder_near ( rounder )
class InvoiceItem ( db . Model ) :
__tablename__ = ' invoice_item '
pid = db . Column ( db . Integer , primary_key = True )
item_number = db . Column ( db . Integer )
invoice_id = db . Column ( db . Integer , db . ForeignKey ( ' invoice.pid ' ) ) #FK
item_title = db . Column ( db . Unicode ( 255 ) )
item_quantity = db . Column ( db . Float )
item_price = db . Column ( db . Float )
amount = db . Column ( db . Float )
invoice = db . relationship ( Invoice , backref = backref ( ' items ' , order_by = item_number , cascade = " delete " ) )
def total ( self ) :
return self . item_quantity * self . item_price
2017-03-08 13:53:09 -05:00
#INVENTORY CLASSES
class Deployment ( db . Model ) :
__tablename__ = ' deployments '
pid = db . Column ( db . Integer , primary_key = True )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
product_id = db . Column ( db . Integer , db . ForeignKey ( ' products.pid ' ) ) #FK
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
date_expire = db . Column ( db . DateTime )
enabled = db . Column ( db . Boolean )
machine_id = db . Column ( db . BigInteger ) #cubeid
2017-03-13 09:36:21 -04:00
machine_alias = db . Column ( db . String ) #dns name
2017-03-08 13:53:09 -05:00
machine_cpu = db . Column ( db . Integer )
machine_mem = db . Column ( db . Integer )
machine_hdd = db . Column ( db . Integer )
def charge ( ) :
result = Deployment . query . all ( )
for deploy in result :
2017-03-13 09:36:21 -04:00
if deploy . enabled == True :
managed_user = User . query . get ( deploy . user_id )
db . session . add ( managed_user )
current_product = Product . query . get ( int ( deploy . product_id ) )
2017-03-08 13:53:09 -05:00
cpu_cost = deploy . machine_cpu * current_app . config [ ' CPU_RATIO ' ]
mem_cost = ( deploy . machine_mem / 1024 ) * current_app . config [ ' MEM_RATIO ' ]
hdd_cost = deploy . machine_hdd * current_app . config [ ' HDD_RATIO ' ]
total = cpu_cost + mem_cost + hdd_cost
2017-03-13 09:36:21 -04:00
if managed_user . wallet - managed_user . credit > managed_user . creditlimit :
print ( ' {} > Deployment # {} costs {} today. Credit is now {} ' . format ( managed_user . email , deploy . machine_id , total , managed_user . credit ) )
managed_user . credit + = total
else :
print ( ' {} > Deployment # {} costs {} today. Credit now is {} and its lower than the credit limit {} . ' . format ( managed_user . email , deploy . machine_id , total , managed_user . credit , managed_user . creditlimit )
print ( ' ' )
if datetime . utcnow . date ( ) ==
db . session . commit ( )
if deploy .
2017-03-08 13:53:09 -05:00
class Contract ( db . Model ) :
__tablename__ = ' contracts '
pid = db . Column ( db . Integer , primary_key = True )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
service_id = db . Column ( db . Integer , db . ForeignKey ( ' services.pid ' ) ) #FK
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
date_expire = db . Column ( db . DateTime )
enabled = db . Column ( db . Boolean )
2017-03-13 09:36:21 -04:00
description = db . Column ( db . Unicode )
2017-03-08 13:53:09 -05:00
units = db . Column ( db . Integer )
discount = db . Column ( db . Integer ) #percent
credit = db . Column ( db . Float )
def charge ( ) :
result = Contract . query . all ( )
for contract in result :
managed_user = User . query . get ( contract . user_id )
db . session . add ( contract )
if datetime . utcnow . date ( ) > ( contract . date_expire - timedelta ( days = 10 ) ) :
if contract . enabled == True :
print ( ' {} > Contract {} will expire in 10 days at {} . Creating new order... ' . format ( managed_user . email , contract . pid , contract . date_expire ) )
current_service = Service . query . get ( int ( contract . product_id ) )
order = Order ( user_id = managed_user . id , units = contract . units , unitvalue = ( current_service . unitprice * contract . units ) , description = current_service . name )
db . session . add ( order )
contract . data_expire = datetime . utcnow . date ( ) + timedelta ( days = 30 )
db . session . commit ( )
return True
class Domain ( db . Model ) :
__tablename__ = ' domains '
pid = db . Column ( db . Integer , primary_key = True )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
date_expire = db . Column ( db . DateTime )
enabled = db . Column ( db . Boolean )
fqdn = db . Column ( db . String , unique = True )
auto_update = db . Column ( db . Boolean )
credit = db . Column ( db . Float )
def charge ( ) :
result = Domain . query . all ( )
for domain in result :
managed_user = User . query . get ( domain . user_id )
db . session . add ( domain )
if datetime . utcnow . date ( ) > ( domain . date_expire - timedelta ( days = 60 ) ) :
if domain . enabled == True :
print ( ' {} > Domain {} will expire in 60 days at {} . Creating new order... ' . format ( managed_user . email , domain . fqdn , domain . date_expire ) )
order = Order ( user_id = managed_user . id , unitvalue = 25 , description = domain . fqdn )
db . session . add ( order )
db . session . commit ( )
return True