Exemple #1
0
class Accounting():
    """
    This class is responsible for providing methods to manage resource usage by
    user.
    """

    def __init__(self):
        self.basedb = BaseDB()

    def register(self, user, msg, category):
        """
        Insert accounting information on database.
        """
        LOG.info('accounting.msg: %s' % msg)
        tnow = datetime.now().strftime("%I:%M%p on %B %d, %Y")
        log = {
                'msg': msg, 
                'timestamp': tnow,
                'category': category,
                }
        res = self.basedb.insert(ACCOUNTING_COLLECTION, 
                           ACCOUNTING_KEY, 
                           user, 
                           ACCOUNTING_ITEM, 
                           log)
        return res
        

    def get(self, user):
        """
        Get accounting information from database.
        """
        result = list(self.basedb.get(
            ACCOUNTING_COLLECTION, 
            ACCOUNTING_KEY,
            user))
        for item in result:
            del item['_id']
        return result
Exemple #2
0
class AuthenticationManager:
    """
    Provides an interface to access and manipulate user data collections.
    It is responsible for implementing authentication business logic. 
    All users belongs to some specific application, which is identified by
    the utilization of `app_id` parameter.
    """
    def __init__(self):
        self.basedb = BaseDB()
        self.emails = Emails()
        self.emailToken = EmailToken()
        self.sendEmail = SendEmail()
        self.token = Token()

    def _format_user_dict(self, user):
        return {APP_KEY: user[APP_KEY], USER_ITEM: user[USER_ITEM][0]}

    def _is_admin_unique(self, app_id, username):
        """
        Verify if the admin username on app data is unique.

        Args:
            app_id (int): the app key;
            username (str): the username being tested.

        Returns:
            boolean: False if the admin username is already present, True
                otherwise.
        """
        users = list(self.basedb.get(USER_COLLECTION, APP_KEY, app_id))
        for elem in users:
            for elem_item in elem[USER_ITEM]:
                if elem_item['admin']['username'] == username:
                    return False
        return True

    def _is_user_unique(self, app_id, username):
        """
        Verify if the username on a app data is unique.

        Args:
            app_id (int): the app key;
            username (str): the username being tested.

        Returns:
            boolean: False if the user username is already present, True
                otherwise.
        """
        users = list(self.basedb.get(USER_COLLECTION, APP_KEY, app_id))
        for user in users:
            for elem in user[USER_ITEM]:
                if elem['username'] == username:
                    return False
        return True

    def _hash(self, data):
        """
        Compute the hash of a string using SHA-512.

        Args:
            password (str): the password to be hashed.

        Returns:
            str: the digest of the hashed password in hexadecimal digits.
        """
        return hashlib.sha512(data.encode()).hexdigest()

    def _hashpwd(self, data):
        salt = bcrypt.gensalt()
        result = bcrypt.hashpw(data.encode('utf-8'), salt)
        return result.decode('utf-8')

    def _validatepwd(self, data, pwd):
        return data == bcrypt.hashpw(pwd.encode('utf-8'), data)

    def get_all_users(self):
        """
        Get all users.

        Returns:
            dict: all users data.
        """
        users = []
        for user in self.basedb.get_all(USER_COLLECTION):
            users.append(self._format_user_dict(user))
        return users

    def delete_user(self, app_id, user_info):
        """
        Delete a user entry on users collection in database.

        Args:
            app_id (int): the app id;
            username (dict): the user information.

        Returns:
            object: The inserted object or None on failure;
            str: 'admin' if the cause of failure was repeated admin 
            authentication, 'users' for a non unique username, 'id' if the
            app_id already exists, 'username' for duplicated username on the 
            auth_info.
        """

        username = user_info['username']
        token = user_info['token']
        usr = self.token.verify_token(app_id, token)
        if usr != 'invalid token' and usr == username:
            # REMOVE OTHER COLS?
            return self.basedb.remove_list_item(
                USER_COLLECTION, APP_KEY, app_id, USER_ITEM,
                {'username': user_info['username']})
        else:
            return 0

    def insert_user(self, app_id, user_info):
        """
        Insert a new user entry on users collection in database.

        Args:
            app_id (int): the app id;
            user_info (dict): the user dict, should contain users and admin's 
            information and credentials.

        
        Returns:
            object: the inserted object or None if error;
            str: 'admin' for repeated admin authentication, or 'users' for 
            repeated username.
        """
        if not self.validate_user(user_info):
            return None, 'invalid user information'
        if not self.validate_pwd(user_info):
            return None, 'invalid password'
        if not self.emails.is_email_unique(user_info['email']):
            return None, 'invalid email'

        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        if user_info['username'] == 'admin':
            return None, 'admin'
        auth = copy.deepcopy(user_info)
        auth['password'] = self._hashpwd(auth['password'])
        if not self._is_user_unique(app_id, auth['username']):
            return None, 'users'

        LOG.info('auth: %s' % auth)
        result_email = self.emailToken.send_email_token(
            auth['username'], auth['email'])

        return self.basedb.insert(USER_COLLECTION, APP_KEY, app_id, USER_ITEM,
                                  auth), ''

    def validate_pwd(self, user_info):
        """
        Validate password against the set of policies:

            - At least 8 characters long;
            - Should be different than username, first and last name, and 
            email;
            - It should include at least 3 of the 4 available types: uppercase 
            letters, lowercase letters, numbers, and symbols.

        Args:
           user_info (dict): user information. 

        Returns:
            bool: True if valid and False otherwise.
        """
        pwd = user_info['password']
        if len(pwd) < 8:
            return False
        if user_info['username'] == pwd\
                or user_info['fname'] == pwd\
                or user_info['lname'] == pwd\
                or user_info['email'] == pwd:
            return False
        count = 0
        for c in pwd:
            if 'a' <= c <= 'z':
                count = count + 1
                break
        for c in pwd:
            if 'A' <= c <= 'Z':
                count = count + 1
                break
        for c in pwd:
            if '0' <= c <= '9':
                count = count + 1
                break
        symbol = "~`!@#$%^&*()_-+={}[]:>;',</?*-+"
        for c in pwd:
            if c in symbol:
                count = count + 1
                break
        if count < 3:
            return False
        return self.verify_pwd_blacklist(pwd)

    def verify_pwd_blacklist(self, pwd):
        with open('aaa_manager/password_blacklist.txt', 'r') as f:
            blacklist = f.readlines()
            for p in blacklist:
                if pwd == p:
                    return False
            return True

    def remove_app(self, app_id):
        """
        Remove an item from users collection in database.

        Args:
            app_id (int): application id.

        Returns:
            object: result of remove operation. 
        """
        return self.basedb.remove(USER_COLLECTION, APP_KEY, app_id)

    def access_app(self, app_id, username, password, auth_type=Auth.USERS):
        """Retrieves a user based on a user username/password credential.

        Args:
            auth_type (Auth): the authentification to be searched for;
            app_id (int): application id
            username (str): the inserted username;
            password (str): the inserted password.

        Returns:
            dict: the user corresponding to the authentication match, 
            or None
            otherwise.
        """
        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        for user in users:
            if auth_type == Auth.USERS:
                for user_info in user[USER_ITEM]:
                    if user_info['username'] == username:
                        if self.emailToken.verify_email(
                                username, user_info['email']) == False:
                            return None, "Please confirm your account. Check your inbox or spam folders."
                        hashpwd = user_info['password']
                        if self._validatepwd(hashpwd.encode('utf-8'),
                                             password):
                            del user_info['password']
                            return user_info, ""
        return None, ""

    def get_user(self, app_id, username):
        """Returns user information based on username field. 

        Args:
            username (str): username;

        Returns:
            (dict) user information or None otherwise.
        """
        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        for user in users:
            for user_info in user[USER_ITEM]:
                if 'username' in user_info\
                        and user_info['username'] == username:
                    return user_info
        return None

    def update_user_stayin(self, user, stayin):
        """
        Update stayin field into user data.
        """
        userelem = copy.deepcopy(user)
        res = self.basedb.get(USER_COLLECTION, APP_KEY, 2)
        for item in list(res):
            for elem in item['auth']:
                if elem['username'] == user['username']:
                    userelem['password'] = elem['password']
        resdel = self.delete_user(2, userelem)
        if resdel > 0:
            userelem['stayin'] = True if stayin == "true" else False
            resinsert = self.basedb.insert(USER_COLLECTION, APP_KEY, 2,
                                           USER_ITEM, userelem)
            if resinsert is not None:
                user['stayin'] = userelem['stayin']
                return 1
        return 0

    def update_user(self, app_id, user_new):
        """Updates user information with `user_new` json content. 
        
        Args: 
            app_id (int): application id
            username (str): the inserted username;
            user_new (dict): user information.

        Return: 
            (dict): user information if exists or None otherwise. 
        """
        try:
            if self.token.verify_token(app_id,
                                       user_new['token']) != 'invalid token':
                userelem = {}
                res = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
                for item in list(res):
                    for elem in item['auth']:
                        if elem['username'] == user_new['username']:
                            userelem = elem
                resdel = self.delete_user(app_id, user_new)
                del user_new['token']
                if resdel > 0:
                    userelem['fname'] = user_new['fname']
                    userelem['lname'] = user_new['lname']
                    userelem['stayin'] = user_new['stayin']
                    resinsert = self.basedb.insert(USER_COLLECTION, APP_KEY,
                                                   app_id, USER_ITEM, userelem)
                    if resinsert is not None:
                        return 1
        except Exception as err:
            LOG.error('Error while updating user information')
            raise Exception('Error while updating user information') from err
        return 0

    def change_password(self, app_id, user_new, check=True):
        """Updates user information with `user_new` json content. 
        
        Args: 
            app_id (int): application id;
            username (str): the inserted username;
            user_new (dict): user information.

        Return: 
            (dict): user information if exists or None otherwise. 
        """
        try:
            if not check or self.token.verify_token(
                    app_id, user_new['token']) != 'invalid token':
                userelem = {}
                res = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
                oldpassword = ""
                for item in list(res):
                    for elem in item['auth']:
                        if elem['username'] == user_new['username']:
                            userelem = elem
                            oldpassword = elem['password']
                oldpwd = user_new['oldpwd'] if 'oldpwd' in user_new else ''
                if not check or self._validatepwd(oldpassword.encode('utf-8'),
                                                  oldpwd):
                    resdel = self.basedb.remove_list_item(
                        USER_COLLECTION, APP_KEY, app_id, USER_ITEM,
                        {'username': user_new['username']})
                    if 'token' in user_new:
                        del user_new['token']
                    if resdel > 0:
                        newpassword = self._hashpwd(user_new['newpwd'])
                        userelem['password'] = newpassword
                        resinsert = self.basedb.insert(USER_COLLECTION,
                                                       APP_KEY, app_id,
                                                       USER_ITEM, userelem)
                        if resinsert is not None:
                            return 1
        except Exception as err:
            LOG.error('Error while changing password.')
            raise Exception('Error while changing password.') from err
        return 0

    def validate_user(self, user):
        """Validates user information schema.
        
        Args: 
            user (dict): the user json.

        Returns:
            bool: True if valid or False otherwise.
        """
        SCHEMA = {
            "type": "object",
            "properties": {
                "username": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                },
                "fname": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                },
                "lname": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                },
                "email": {
                    "type": "string",
                    "pattern": "[^@]+@[^@]+\.[^@]+",
                },
                "stayin": {
                    "type": "boolean"
                }
            },
            "required": ["username", "fname", "lname", "email", "stayin"]
        }
        try:
            validate(user, SCHEMA)
        except ValidationError as err:
            LOG.error('Invalid user information')
            raise Exception('Invalid user information') from err
        return True

    def gen_password(self, app_id, username, email):
        """
        Generate password.
        """
        if self.emailToken.verify_email(username, email):
            pwd = genpwd()
            user_new = {'username': username, 'newpwd': pwd}
            res = self.change_password(app_id, user_new, False)
            if res == 1:
                SUBJECT = 'EUBRA-BigSea: forgot password'
                TEXT = """
                A new password was automatically generated. Use it to login and change it. 

                New password: """ + pwd
                self.sendEmail.send_email(email, SUBJECT, TEXT)
                return res
        return 0
Exemple #3
0
class EmailToken:

    def __init__(self):
        self.token = Token()
        self.basedb = BaseDB()
        self.sendEmail = SendEmail()
        pass

    def insert_email_token(self, username, email, token, valid):
        result = self.basedb.insert('EmailToken', 'email', email, 'data', 
                {
                    'token': token, 
                    'username': username, 
                    'validated': datetime.datetime.now(),
                    'valid': valid
                    })
        return result

    def email_confirmation(self, username, email, token):
        """
        Verifies if given token is valid.

        Args: 
            username (str): username;
            email (str): user email;
            token (str): email token encoded in base64.

        Returns:
            bool: True if valid and False otherwise.
        """
        result = False
        data = self.basedb.get('EmailToken', 'email', email)
        for item in data:
            LOG.info('item: %s' % item)
            for elem in item['data']:
                if elem['token'] == token:
                    new_elem = copy.deepcopy(elem) 
                    new_elem['valid'] = True
                    res = self.basedb.remove_list_item(
                            'EmailToken',
                            'email',
                            email,
                            'data',
                            elem)
                    res = self.basedb.insert(
                            'EmailToken',
                            'email',
                            email,
                            'data',
                            new_elem)
                    result = True
        return result

    def send_email_token(self, username, email):
        """
        Send email with new generated token.
        """
        token = self.token.generate_token(username+email)
        self.insert_email_token(username, email, token, False)
        self.sendEmail.send_email_with_token(username, email, token)
        return token


    def verify_email(self, username, email):
        """
        Verifies if given token is valid.
        TODO: must grant that only the last one is valid.

        Args: 
            username (str): username;
            email (str): user email;
            token (str): email token encoded in base64.

        Returns:
            bool: True if valid and False otherwise.
        """
        result = list(self.basedb.get('EmailToken', 'email', email))
        for item in result:
            if 'data' in item:
                data = item['data']
                for elem in data:
                    if elem['valid'] == True and elem['username'] == username: 
                        return True
        return False
Exemple #4
0
class Infra:
    def __init__(self):
        self.basedb = BaseDB()

    def create(self, username, infra_info):
        """
        Create an infra associated to username on database. 

        Args:
            username (str): username;
            infra_info (dict): infra information.

        Returns:
            database response
        """
        result = read(username)
        if result is not None:
            if self.validate_infra(infra_info):
                return self.basedb.insert(INFRA_COLLECTION, INFRA_KEY,
                                          username, INFRA_ITEM, infra_info)
        else:
            return None, "alerady has infra data"

        return None

    def read(self, username):
        """
        Read infra information for username. 

        Args: 
            username (str): username;
            
        """
        result = self.basedb.get(INFRA_COLLECTION, INFRA_KEY, username)
        res = list(result)
        for item in res:
            del item['_id']
        return res

    def delete(self, username):
        """
        Delete infra information for that username.
        """
        result = self.basedb.remove(INFRA_COLLECTION, INFRA_KEY, username)
        return result

    def validate_infra(self, infra_info):
        SCHEMA = {
            'type': 'object',
            'properties': {
                'principal': {
                    'type': 'string',
                    'minLength': 1,
                    'maxLength': 50
                },
                'secret': {
                    'type': 'string',
                    'minLength': 1,
                    'maxLength': 50
                },
            },
            'required': ['principal', 'secret']
        }
        try:
            validate(infra_info, SCHEMA)
        except ValidationError as err:
            LOG.error('Invalid infra')
            raise Exception('Invalid infra') from err
        return True
class Authorisation:
    """
    Authorisation class is responsible for managing resource usage rules.
    """
    def __init__(self):
        self.basedb = BaseDB()
        self.accounting = Accounting()

    def verify(self, username, resource_name, resource_category):
        """
        Returns True if username is allowed to access resource.
        """
        resources = list(
            self.basedb.get(AUTHORISATION_COLLECTION, AUTHORISATION_KEY,
                            username))
        for item in resources:
            for elem in item['resource_rule']:
                LOG.info('elem: %s' % elem)
                if elem['resource_name'] == resource_name and\
                    elem['resource_category'] == resource_category:
                    return True
        return False

    def update_resource_item(self, username, resource_name, resource_category):
        """
        Add 1 to used field.
        """
        resources = self.basedb.get(AUTHORISATION_COLLECTION,
                                    AUTHORISATION_KEY, username)
        for item in resources:
            for elem in item['resource_rule']:
                LOG.info('elem: %s' % elem)
                if elem['resource_name'] == resource_name and\
                        elem['resource_category'] == resource_category:
                    old_item = copy.deepcopy(item)
                    elem['used'] = elem['used'] + 1
                    res = self.basedb.update(AUTHORISATION_COLLECTION,
                                             AUTHORISATION_KEY, username,
                                             AUTHORISATION_ITEM, old_item,
                                             item)
        return res

    def use_resource(self, username, resource_name, resource_category):
        """
        This method is called in order to user a determined resource. Thus, it
        is responsible for triggering the accounting mechanism and updating the
        database to increment the number of times that resource was used. 
        """
        if self.verify(username, resource_name, resource_category):
            # add 1 to used field
            self.update_resource_item(username, resource_name,
                                      resource_category)
            # account it
            msg = "Resource " + resource_name + " used by: " + username + "."
            LOG.info('msg: %s' % msg)
            category = INFO
            self.accounting.register(username, msg, category)
            return {'msg': msg}
        return None

    def validate_rule(self, rule):
        """
        Validates authorisation object.
        """

        SCHEMA = {
            'type': 'object',
            'properties': {
                'resource_name': {
                    'type': 'string',
                    'minLength': 1,
                    'maxLength': 50
                },
                'resource_category': {
                    'type': 'string',
                    'minLength': 1,
                    'maxLength': 50
                },
                'max_used': {
                    'type': 'number'
                },
                'used': {
                    'type': 'number'
                },
            },
            'required': ['resource_category', 'resource_name', 'max_used']
        }
        try:
            validate(rule, SCHEMA)
        except ValidationError as err:
            LOG.error('Invalid rule')
            raise Exception('Invalid rule') from err
        return True

    def create(self, username, resource_category, resource_name, max_used):
        """
        Create an authorisation rule on database. 

        Args:
            username (str): username;
            resource_name (str): name that identifies the resource being used;
            rule (dict): rule object.

        Returns:
            database response
        """
        rule = {
            'resource_category': resource_category,
            'resource_name': resource_name,
            'max_used': int(max_used),
            'used': 0
        }
        if self.validate_rule(rule):
            result = self.basedb.insert(AUTHORISATION_COLLECTION,
                                        AUTHORISATION_KEY, username,
                                        AUTHORISATION_ITEM, rule)
            if result is not None:
                LOG.info('Rule: ' + json.dumps(rule) +
                         'successfully created for user: '******'.')
                return result
        return None

    def read(self, username, resource_name, resource_category):
        """
        Read rule information from user.
        """
        resources = self.basedb.get(AUTHORISATION_COLLECTION,
                                    AUTHORISATION_KEY, username)
        for item in resources:
            for elem in item[AUTHORISATION_ITEM]:
                if elem['resource_name'] == resource_name and\
                        elem['resource_category'] == resource_category:
                    return elem
        return None

    def update(self, username, resource_name, resource_category, max_allowed):
        """
        Update rule information. 
        """
        resources = self.basedb.get(AUTHORISATION_COLLECTION,
                                    AUTHORISATION_KEY, username)
        for item in resources:
            for elem in item[AUTHORISATION_ITEM]:
                if elem['resource_name'] == resource_name and\
                        elem['resource_category'] == resource_category:
                    new_elem = copy.deepcopy(elem)
                    new_elem['max_allowed'] = max_allowed
                    result = self.basedb.update(AUTHORISATION_COLLECTION,
                                                AUTHORISATION_KEY, username,
                                                AUTHORISATION_ITEM, elem,
                                                new_elem)
                    return result
        return None

    def delete(self, username, resource_name, resource_category):
        """
        Delete rule information. 
        """
        resources = self.basedb.get(AUTHORISATION_COLLECTION,
                                    AUTHORISATION_KEY, username)
        for item in resources:
            for elem in item[AUTHORISATION_ITEM]:
                if elem['resource_name'] == resource_name and\
                        elem['resource_category'] == resource_category:
                    result = self.basedb.remove_list_item(
                        AUTHORISATION_COLLECTION, AUTHORISATION_KEY, username,
                        AUTHORISATION_ITEM, elem)
                    return result
        return None
Exemple #6
0
class Token:
    def __init__(self):
        self.basedb = BaseDB()

    def get_token(self, user):
        """Gets token from database.

        Args:
            app_id (int): application id;
            user (dict): user information;

        Returns:
            token if user exists or None otherwise.
        """
        result = list(self.basedb.get_all('Token'))
        for item in result:
            if 'data' in item:
                for data in item['data']:
                    if 'user' in data and data['user'] == user:
                        return item['token']
        return None

    def generate_token(self, user):
        """
        Generate a token that can be used to authenticate user to 
        access app. 

        Args:
            user (dict): user information.

        Returns: 
            str: base64 representation of token.
        """
        #return self._hash(json.dumps(user)+datetime.datetime.now().
        #        strftime("%Y-%m-%d %H:%M:%S"))
        result = bcrypt.hashpw((SECRET + json.dumps(user)).encode('utf-8'),
                               bcrypt.gensalt())
        return base64.b64encode(result).decode('utf-8')

    def remove_token(self, token):
        """
        Remove token from DB.

        Args:
            token (str): base64 token

        Returns: 
            obj: mongodb result
        """
        return self.basedb.remove('Token', 'token', token)

    def insert_token(self, app_id, user, token):
        """Insert token into DB.

        Args:
            app_id (int): application id;
            user (dict): user information;
            token (str): hexidecimal token.

        Returns: 
            obj: mongodb result
        """
        return self.basedb.insert('Token', 'token', token, 'data', {
            'app_id': app_id,
            'user': user,
            'created': datetime.datetime.now()
        })

    def verify_token(self, app_id, token):
        """Verify token validity.

        Args:
            app_id (int): application id;
            token (str): base64 token.

        Returns:
            str: username corresponding to token if valid, 
            'invalid token' otherwise
        """
        result = self.read_user_info(app_id, token)
        if result != 'invalid token':
            return result['username']
        else:
            return 'invalid token'

    def read_user_info(self, app_id, token):
        """Read user information.

        Args:
            app_id (int): application id;
            token (str): base64 token.

        Returns:
            str: username corresponding to token if valid, 
            'invalid token' otherwise
        """
        result = list(self.basedb.get('Token', 'token', token))
        for item in result:
            if 'data' in item:
                for data in item['data']:
                    if 'app_id' in data and data['app_id'] == app_id\
                            and 'created' in data and\
                            'user' in data and 'stayin' in data['user']:
                        if (data['user']['stayin'] == True\
                                and (datetime.datetime.now() - datetime.timedelta(minutes=TOKEN_EXPIRATION_STAYIN) < data['created']))\
                                or (data['user']['stayin'] == False\
                                and (datetime.datetime.now() - datetime.timedelta(minutes=TOKEN_EXPIRATION) < data['created'])):
                            LOG.info(
                                '#### %s' %
                                (datetime.datetime.now() -
                                 datetime.timedelta(minutes=TOKEN_EXPIRATION) <
                                 data['created']))
                            LOG.info(
                                '#### %s' %
                                (datetime.datetime.now() - datetime.timedelta(
                                    minutes=TOKEN_EXPIRATION_STAYIN) <
                                 data['created']))
                            LOG.info('#### created: %s' % data['created'])
                            LOG.info('#### stayin: %s' %
                                     data['user']['stayin'])
                            return data['user']
                    if 'stayin' not in data['user']:
                        if (datetime.datetime.now() -
                                datetime.timedelta(minutes=TOKEN_EXPIRATION) <
                                data['created']):
                            return data['user']
        return 'invalid token'
class Favorites:
    def __init__(self):
        self.basedb = BaseDB()

    def create(self, app_id, username, item_id, item_type, city_id, country_id,
               favorite_id, data, token):
        """
        Create an favorite associated to username on database. 

        Args:
            username (str): username;
            item_id: item id (primary key),
            item_type: item_type (distinguish among applications),
            city_id: city_id (external),
            country_id: country_id (external),
            favorite_id: favorite_id (external),
            data: data (external),
            token (str): token.

        Returns:
            database response
        """
        item = {
            'item_id': item_id,
            'item_type': item_type,
            'city_id': city_id,
            'country_id': country_id,
            'favorite_id': favorite_id,
            'data': data,
            'token': token,
        }
        if self.validate_favorite(item):
            return self.basedb.insert(FAVORITE_COLLECTION, FAVORITE_KEY,
                                      username, FAVORITE_ITEM, item)
        return None

    def read(self, app_id, username, city_id, country_id, token):
        """
        Read favorite information for username. 

        Args: 
            username (str): username;
            city_id (dict): city_id (external);
            ccountry_id (dict): country_id (external);
            token (str): token.
            
        """
        result = self.basedb.get(FAVORITE_COLLECTION, FAVORITE_KEY, username)
        for item in result:
            for elem in item['favorites']:
                if elem['city_id'] == city_id and\
                        elem['country_id'] == country_id:
                    return elem
        return None

    def read_all(self, app_id, username):
        """
        Read favorites information for username. 

        Args: 
            username (str): username;
            city_id (dict): city_id (external);
            ccountry_id (dict): country_id (external);
            token (str): token.
            
        """
        result = self.basedb.get(FAVORITE_COLLECTION, FAVORITE_KEY, username)
        res = list(result)
        for item in res:
            del item['_id']
        return res

    def update(self, username, item_id):
        pass

    def delete(self, app_id, username, item_id, token):
        """
        Delete favorite from database.

        Args:
            username (str): username;
            item_id (str): item primary key (external);
            token (str): token.

        """
        result = self.basedb.get(FAVORITE_COLLECTION, FAVORITE_KEY, username)
        for item in result:
            for elem in item['favorites']:
                if elem['item_id'] == item_id:
                    r = self.basedb.remove_list_item(FAVORITE_COLLECTION,
                                                     FAVORITE_KEY, username,
                                                     FAVORITE_ITEM, elem)
                    return r
        return None

    def validate_favorite(self, favorite_info):
        SCHEMA = {
            'type':
            'object',
            'properties': {
                'item_id': {
                    'type': 'string'
                },
                'item_type': {
                    'type': 'string'
                },
                'city_id': {
                    'type': 'number'
                },
                'country_id': {
                    'type': 'number'
                },
                'favorite_id': {
                    'type': 'string',
                    'maxLength': 4000
                },
                'data': {
                    'type': 'string',
                    'maxLength': 100000
                },
            },
            'required': [
                'item_id', 'item_type', 'city_id', 'country_id', 'favorite_id',
                'data'
            ]
        }
        try:
            validate(favorite_info, SCHEMA)
        except ValidationError as err:
            LOG.error('Invalid favorite')
            raise Exception('Invalid favorite') from err
        return True
class AuthenticationManager:
    """
    Provides an interface to access and manipulate user data collections.
    It is responsible for implementing authentication business logic. 
    All users belongs to some specific application, which is identified by
    the utilization of `app_id` parameter.
    """
    def __init__(self, host=_DEFAULT_DB_HOST, port=_DEFAULT_DB_PORT):
        self.host = host
        self.port = port
        self.basedb = BaseDB(host, port)

    def _format_user_dict(self, user):
        return {APP_KEY: user[APP_KEY], USER_ITEM: user[USER_ITEM][0]}

    def _is_admin_unique(self, app_id, username):
        """
        Verifies if the admin username on app data is unique.

        Args:
            app_id (int): the app key
            username (str): the username being tested

        Returns:
            boolean: False if the admin username is already present, True
                otherwise
        """
        users = list(self.basedb.get(USER_COLLECTION, APP_KEY, app_id))
        for elem in users:
            for elem_item in elem[USER_ITEM]:
                if elem_item['admin']['username'] == username:
                    return False
        return True

    def _is_user_unique(self, app_id, username):
        """Verifies if the username on a app data is unique

        Args:
            app_id (int): the app key
            username (str): the username being tested

        Returns:
            boolean: False if the user username is already present, True
                otherwise
        """
        users = list(self.basedb.get(USER_COLLECTION, APP_KEY, app_id))
        for user in users:
            for elem in user[USER_ITEM]:
                if elem['username'] == username:
                    return False
        return True

    def _hash(self, data):
        """Hashes a string using SHA-512.

        Args:
            password (str): the password to be hashed

        Returns:
            str: the digest of the hashed password in hexadecimal digits
        """
        return hashlib.sha512(data.encode()).hexdigest()

    def get_all_users(self):
        """Get all users

        Returns:
            dict: all users data
        """
        users = []
        for user in self.basedb.get_all(USER_COLLECTION):
            users.append(self._format_user_dict(user))
        return users

    def delete_user(self, app_id, username):
        """
        Deletes a new user entry on users collection in DB

        Args:
            app_id (int): the app id
            username (dict): the username

        Returns:
            object: The inserted object or None on failure
            str: 'admin' if the cause of failure was repeated admin authentication, 'users' for a non unique username, 'id' if the app_id already exists, 'username' for duplicated username on the auth_info
        """
        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        auth = copy.deepcopy(user_info)
        return self.basedb.remove_item(USER_COLLECTION, APP_KEY, app_id,
                                       USER_ITEM, auth), ''

    def insert_user(self, app_id, user_info):
        """
        Inserts a new user entry on users collection in DB

        Args:
            app_id (int): the app id
            auth_info (dict): the user dict, should contain the users and the admin's data, and a username/password pair

        
        Returns:
            object: The inserted object or None on failure
            str: 'admin' if the cause of failure was repeated admin authentication, 'users' for a non unique username, 'id' if the app_id already exists, 'username' for duplicated username on the auth_info
        """
        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        if user_info['username'] == 'admin':
            return None, 'admin'
        auth = copy.deepcopy(user_info)
        auth['password'] = self._hash(auth['password'])
        LOG.info('#### users: %s' % list(users))
        LOG.info('#### auth: %s' % auth)
        if not self._is_user_unique(app_id, auth['username']):
            return None, 'users'

        return self.basedb.insert(USER_COLLECTION, APP_KEY, app_id, USER_ITEM,
                                  auth), ''

    def remove_app(self, app_id):
        """Removes a user entry on users collection in DB

        Args:
            app_id (int): the user key

        Returns:
            The kdb remove operation result
        """
        return self.basedb.remove(USER_COLLECTION, APP_KEY, app_id)

    def generate_token(self, user):
        """Generates a token that can be used to authenticate user to access
        app. 

        Args:
            user (dict): user information

        Returns: 
            str: hexadecimal representation of token
        """
        return self._hash(
            json.dumps(user) +
            datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

    def remove_token(self, token):
        """Remove token from DB.

        Args:
            token (str): hexidecimal token

        Returns: 
            obj: mongodb result
        """
        return self.basedb.remove('Token', 'token', token)

    def insert_token(self, app_id, user, token):
        """Insert token into DB.

        Args:
            app_id (int): application id
            user (dict): user information
            token (str): hexidecimal token

        Returns: 
            obj: mongodb result
        """
        self.basedb.update('Token', 'token', token, 'data', {
            'app_id': app_id,
            'user': user,
            'status': 'valid'
        }, {
            'app_id': app_id,
            'user': user,
            'status': 'invalid'
        })
        return self.basedb.insert('Token', 'token', token, 'data', {
            'app_id': app_id,
            'user': user,
            'status': 'valid'
        })

    def verify_token(self, app_id, token):
        """Verify token validity.

        Args:
            app_id (int): application id
            token (str): hexidecimal token

        Returns:
            str: username corresponding to token if valid, 
            'invalid token' otherwise
        """
        result = list(self.basedb.get('Token', 'token', token))
        for item in result:
            if 'data' in item:
                for data in item['data']:
                    if 'app_id' in data and data['app_id'] == app_id\
                            and 'status' in data and data['status'] == 'valid':
                        return data['user']['username']
        return 'invalid token'

    def get_token(self, app_id, user):
        """Get token from database

        """
        result = list(self.basedb.get_all('Token'))
        for item in result:
            if 'data' in item:
                for data in item['data']:
                    if 'app_id' in data and data['app_id'] == app_id and\
                        'user' in data and data['user'] == user:
                        return item['token']
        return None

    def access_app(self, app_id, username, password, auth_type=Auth.USERS):
        """Retrieves a user based on a user username/password pair

        Args:
            auth_type (Auth): the authentification to be searched for
            username (str): the inserted username
            password (str): the inserted password

        Returns:
            dict: the user corresponding to the authentication pair match,
                or None if any
        """
        users = self.basedb.get(USER_COLLECTION, APP_KEY, app_id)
        for user in users:
            if auth_type == Auth.USERS:
                for user_info in user[USER_ITEM]:
                    if user_info['username'] == username and user_info['password'] == \
                        password:
                        del user_info['password']
                        return user_info
        return None