import secrets import string import bcrypt from flask import current_app from .base import db from ..models.role import Role from ..models.domain import Domain from ..models.account import Account class ApiKey(db.Model): __tablename__ = "apikey" id = db.Column(db.Integer, primary_key=True) key = db.Column(db.String(255), unique=True, nullable=False) description = db.Column(db.String(255)) role_id = db.Column(db.Integer, db.ForeignKey('role.id')) role = db.relationship('Role', back_populates="apikeys", lazy=True) domains = db.relationship("Domain", secondary="domain_apikey", back_populates="apikeys") accounts = db.relationship("Account", secondary="apikey_account", back_populates="apikeys") def __init__(self, key=None, desc=None, role_name=None, domains=[], accounts=[]): self.id = None self.description = desc self.role_name = role_name self.domains[:] = domains self.accounts[:] = accounts if not key: rand_key = ''.join( secrets.choice(string.ascii_letters + string.digits) for _ in range(15)) self.plain_key = rand_key self.key = self.get_hashed_password(rand_key).decode('utf-8') current_app.logger.debug("Hashed key: {0}".format(self.key)) else: self.key = key def create(self): try: self.role = Role.query.filter(Role.name == self.role_name).first() db.session.add(self) db.session.commit() except Exception as e: current_app.logger.error('Can not update api key table. Error: {0}'.format(e)) db.session.rollback() raise e def delete(self): try: db.session.delete(self) db.session.commit() except Exception as e: msg_str = 'Can not delete api key template. Error: {0}' current_app.logger.error(msg_str.format(e)) db.session.rollback() raise e def update(self, role_name=None, description=None, domains=None, accounts=None): try: if role_name: role = Role.query.filter(Role.name == role_name).first() self.role_id = role.id if description: self.description = description if domains is not None: domain_object_list = Domain.query \ .filter(Domain.name.in_(domains)) \ .all() self.domains[:] = domain_object_list if accounts is not None: account_object_list = Account.query \ .filter(Account.name.in_(accounts)) \ .all() self.accounts[:] = account_object_list db.session.commit() except Exception as e: msg_str = 'Update of apikey failed. Error: {0}' current_app.logger.error(msg_str.format(e)) db.session.rollback raise e def get_hashed_password(self, plain_text_password=None): # Hash a password for the first time # (Using bcrypt, the salt is saved into the hash itself) if plain_text_password is None: return plain_text_password if plain_text_password: pw = plain_text_password else: pw = self.plain_text_password # The salt value is currently re-used here intentionally because # the implementation relies on just the API key's value itself # for database lookup: ApiKey.is_validate() would have no way of # discerning whether any given key is valid if bcrypt.gensalt() # was used. As far as is known, this is fine as long as the # value of new API keys is randomly generated in a # cryptographically secure fashion, as this then makes # expendable as an exception the otherwise vital protection of # proper salting as provided by bcrypt.gensalt(). return bcrypt.hashpw(pw.encode('utf-8'), current_app.config.get('SALT').encode('utf-8')) def check_password(self, hashed_password): # Check hashed password. Using bcrypt, # the salt is saved into the hash itself if self.plain_text_password: return bcrypt.checkpw(self.plain_text_password.encode('utf-8'), hashed_password.encode('utf-8')) return False def is_validate(self, method, src_ip=''): """ Validate user credential """ if method == 'LOCAL': passw_hash = self.get_hashed_password(self.plain_text_password) apikey = ApiKey.query \ .filter(ApiKey.key == passw_hash.decode('utf-8')) \ .first() if not apikey: raise Exception("Unauthorized") return apikey def associate_account(self, account): return True def dissociate_account(self, account): return True def get_accounts(self): return True