import hashlib 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 from proxadmin.exceptions import ValidationError from . import db, lm import os import base64 import json import requests import onetimepass from datetime import date, datetime, time, timedelta from sortedcontainers import SortedDict 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 '' % 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) 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)) org_responsible = db.Column(db.Unicode(128)) org_bulstat = db.Column(db.String(16)) 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') currency = db.Column(db.String(3), default='BGN') 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 '' % 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)) description = db.Column(db.String(128)) 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 class Order(db.Model): __tablename__ = 'orders' 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) units = db.Column(db.Integer, default=1) unitvalue = db.Column(db.Float) currency = db.Column(db.String, default='BGN') paid = db.Column(db.Boolean, default=False) #class Invoice(db.Model) #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 machine_alias = db.Column(db.String) #dns name for easy managing machine_cpu = db.Column(db.Integer) machine_mem = db.Column(db.Integer) machine_hdd = db.Column(db.Integer) credit = db.Column(db.Float) def charge(): result = Deployment.query.all() for deploy in result: managed_user = User.query.get(deploy.user_id) db.session.add(deploy) if datetime.utcnow.date() > (deploy.date_expire - timedelta(days=10)): if deploy.credit > 0: print('{} Deployment {} will expire in 10 days at {}. Creating new order...'.format(managed_user.email, deploy.machine_alias, deploy.date_expire)) current_product = Product.query.get(int(deploy.product_id)) order = Order(user_id=managed_user.pid, unitvalue=deploy.credit, description='Cloud server {} - {}'.format(current_product.name, deploy.machine_alias)) db.session.add(order) deploy.credit = 0 deploy.data_expire = datetime.utcnow.date() + timedelta(days=30) else: 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 if deploy.enabled == True: print('{}> Charging deployment #{} with {} ({} monthly)'.format(managed_user.email, deploy.pid, total)) deploy.credit += total db.session.commit() return True 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) description = db.Column(db.String) 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