2017-03-08 13:53:09 -05:00
|
|
|
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
|
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
|
|
|
|
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 '<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)
|
|
|
|
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 '<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))
|
|
|
|
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
|
|
|
|
|