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
2017-06-07 11:04:27 -04:00
import random
2017-03-08 13:53:09 -05:00
import base64
2017-03-13 09:36:21 -04:00
import hashlib
2017-06-07 11:04:27 -04:00
import json
2017-03-13 09:36:21 -04:00
from decimal import Decimal
from datetime import date , time , datetime , timedelta
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 ) )
2017-05-13 05:46:43 -04:00
country = db . Column ( db . String ( 64 ) , default = ' BG ' )
2017-03-08 13:53:09 -05:00
phone = db . Column ( db . String ( 64 ) )
2017-05-13 05:46:43 -04:00
org_account = db . Column ( db . Boolean , default = False )
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 ) )
2017-05-24 10:37:52 -04:00
group = db . Column ( db . String ( 24 ) , default = ' User ' )
2017-05-13 05:46:43 -04:00
language = db . Column ( db . String ( 2 ) , default = ' BG ' )
2017-06-10 23:26:10 -04:00
wallet = db . Column ( db . Float )
2017-03-13 09:36:21 -04:00
currency = db . Column ( db . String ( 3 ) , default = ' BGN ' )
2017-03-08 13:53:09 -05:00
inv_deployments = db . relationship ( ' Deployment ' , backref = ' owner ' , lazy = ' dynamic ' )
2017-06-01 18:27:17 -04:00
inv_services = db . relationship ( ' Service ' , backref = ' owner ' , lazy = ' dynamic ' )
2017-03-08 13:53:09 -05:00
inv_domains = db . relationship ( ' Domain ' , backref = ' owner ' , lazy = ' dynamic ' )
2017-06-04 10:10:38 -04:00
inv_addresses = db . relationship ( ' Address ' , backref = ' owner ' , lazy = ' dynamic ' )
2017-03-08 13:53:09 -05:00
2017-06-10 23:26:10 -04:00
inv_transactions = db . relationship ( ' Transaction ' , backref = ' owner ' , lazy = ' dynamic ' )
2017-03-08 13:53:09 -05:00
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 ) :
2017-06-06 09:49:32 -04:00
if self . can ( Permission . ADMINISTER ) :
return True
else :
return False
2017-03-08 13:53:09 -05:00
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
class Service ( db . Model ) :
__tablename__ = ' services '
pid = db . Column ( db . Integer , primary_key = True ) #PK
2017-06-01 18:27:17 -04:00
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
2017-06-04 10:10:38 -04:00
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
2017-06-09 10:11:03 -04:00
date_last_charge = db . Column ( db . DateTime )
period = db . Column ( db . Integer )
2017-06-04 10:10:38 -04:00
enabled = db . Column ( db . Boolean )
2017-06-09 10:11:03 -04:00
category = db . Column ( db . String ( 64 ) )
2017-03-13 09:36:21 -04:00
description = db . Column ( db . Unicode ( 128 ) )
2017-06-09 10:11:03 -04:00
price = db . Column ( db . Float )
2017-03-08 13:53:09 -05:00
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
date_created = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
2017-06-09 10:11:03 -04:00
date_last_charge = db . Column ( db . DateTime )
2017-03-08 13:53:09 -05:00
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 )
2017-06-07 11:04:27 -04:00
machine_addresses = db . relationship ( ' Address ' , backref = ' deployments ' , lazy = ' dynamic ' )
2017-03-08 13:53:09 -05:00
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
2017-05-13 05:46:43 -04:00
if managed_user . wallet - total > 0 :
managed_user . wallet - = total
print ( ' {} > Charging deployment # {} with {} . Wallet now is: {} ' . format ( managed_user . email , deploy . machine_id , total , managed_user . walet ) )
2017-03-13 09:36:21 -04:00
else :
2017-05-13 05:46:43 -04:00
print ( ' {} > Deployment # {} cannot be charged with {} . Not enough money in the wallet ( {} ). Notifying admin... ' . format ( managed_user . email , deploy . machine_id , total , managed_user . wallet ) )
#TODO: Send emails here.
2017-05-07 21:33:42 -04:00
db . session . commit ( )
2017-03-08 13:53:09 -05:00
2017-06-01 18:27:17 -04:00
#NAMESPACE
class Region ( db . Model ) :
__tablename__ = ' regions '
2017-03-08 13:53:09 -05:00
pid = db . Column ( db . Integer , primary_key = True )
2017-06-04 10:10:38 -04:00
enabled = db . Column ( db . Boolean )
2017-06-01 18:27:17 -04:00
name = db . Column ( db . String ( 64 ) )
description = db . Column ( db . String ( 128 ) )
extraprice = db . Column ( db . Float )
2017-03-08 13:53:09 -05:00
2017-06-01 18:27:17 -04:00
class Address ( db . Model ) :
__tablename__ = ' address '
pid = db . Column ( db . Integer , primary_key = True )
2017-06-04 10:10:38 -04:00
enabled = db . Column ( db . Boolean )
2017-06-01 18:27:17 -04:00
date_assigned = db . Column ( db . DateTime , index = True , default = datetime . utcnow )
user_id = db . Column ( db . Integer , db . ForeignKey ( ' users.pid ' ) ) #FK
region_id = db . Column ( db . Integer , db . ForeignKey ( ' regions.pid ' ) ) #FK
2017-06-07 11:04:27 -04:00
deploy_id = db . Column ( db . Integer , db . ForeignKey ( ' deployments.pid ' ) ) #FK
2017-06-04 10:10:38 -04:00
ip = db . Column ( db . String ( 64 ) )
2017-06-09 10:11:03 -04:00
mac = db . Column ( db . String ( 128 ) )
2017-06-01 18:27:17 -04:00
rdns = db . Column ( db . String ( 256 ) )
2017-06-07 11:04:27 -04:00
reserved = db . Column ( db . Boolean , default = False ) #this ip SHOULD NOT be listed as available to assign even if its not currently owned by anyone
def __init__ ( self ) :
2017-06-09 10:11:03 -04:00
if self . mac is None :
self . mac = self . genmac ( )
2017-06-07 11:04:27 -04:00
def genmac ( self ) :
2017-06-09 10:11:03 -04:00
addr = Address . query . all ( )
allmacs = [ ]
for addr in alladdr :
allmacs . expand ( str ( addr . mac ) )
while True :
mac = [ random . randint ( 0 , 255 ) for x in range ( 0 , 6 ) ]
mac [ 0 ] = ( mac [ 0 ] & 0xfc ) | 0x02
mac = ' ' . join ( [ ' {0:02x} ' . format ( x ) for x in mac ] )
if mac in allmacs :
current_app . logger . warning ( ' mac address {} is in the pool. regenerating... ' . format ( mac ) )
continue
else :
return mac
2017-03-08 13:53:09 -05:00
class Domain ( db . Model ) :
__tablename__ = ' domains '
pid = db . Column ( db . Integer , primary_key = True )
2017-06-04 10:10:38 -04:00
enabled = db . Column ( db . Boolean )
2017-03-08 13:53:09 -05:00
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 )
fqdn = db . Column ( db . String , unique = True )
auto_update = db . Column ( db . Boolean )
2017-05-13 05:46:43 -04:00
#UINVOICE
class Transaction ( db . Model ) :
__tablename__ = ' transaction '
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-06-10 23:26:10 -04:00
description = db . Column ( db . String )
2017-05-13 05:46:43 -04:00
value = db . Column ( db . Float )
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 = db . backref ( ' items ' , order_by = item_number , cascade = " delete " ) )
def total ( self ) :
return self . item_quantity * self . item_price