"""User Logic. The logic related to the user is a little more complex, because our local microservice database might not have all the required information to perform the request – so whenever a user seems to be a missing, or if a password is invalid, we need to backfill the data by going back to ALW or OA. """ import json import uuid import requests import rsa from sukimu import response from sukimu.operations import Equal from auth import config from auth.logic import passwords from auth.logic import token from auth.models import group from auth.models import user def login(username, password, client_id): """Login a user. Args: username (str): the user's username. password (str): the user's password. client_id (int): the client id. Returns: Response: the result of the login. """ errors = dict() if not username: errors.update(login='The login is required') if not password: errors.update(password='The password is required') if errors: return response.create_error_response(errors=errors) current_user = get_user(username) password = password.encode('utf8') if current_user.success: current_password = current_user.password if passwords.check(password, current_password): return token.create_code_token( current_user.login, current_user.id, client_id) return legacy_login(username, password, client_id) def legacy_login(username, password, client_id): """Legacy login. The legacy login performs a request to ALW and OA to see if the user credentials are valid. If they are, we get back a copy of the user info from OA and ALW which are saved into our db. Args: username (str): the user's username. password (bytes): the user's password. client_id (int): the client id. Returns: Response: the response of the request. """ secret = config.SECRET_WORKSTATION url = config.URL_WORKSTATION + '/account/getuser' # encrypt password using ALW public key pubkey = rsa.PublicKey.load_pkcs1(config.PUBKEY_WORKSTATION.encode('utf8')) encrypted_password = rsa.encrypt(password, pubkey) params = { 'login': username, 'password': encrypted_password, 'secret': secret } resp = requests.post(url, params=params) if resp.status_code != 200: return response.Response( resp.status_code, '', dict(login='Incorrect login, please try again.')) data = json.loads(resp.content.decode('utf8')) user = update_user_password( 'alw:{}'.format(data.get('id')), password) if user.status == 400: user = create_user( username, password, data.get('email'), # There is a collision in the user ids on OA and ALW. Adding a # prefix to avoid the collision. user_id='alw:{}'.format(data.get('id')), groups=[group.ALW]) if user.success: return token.create_code_token( user.login, user.id, client_id) return user def get_user(identifier): """Get a user. Args: identifier (str): could be the login or email of the user. Returns: Response: the result of the fetch. """ if not identifier: return response.create_error_response(errors={ 'identifier': 'The username or email cannot be empty.', }) current_user = user.User.fetch_one(login=Equal(identifier)) if not current_user.success: current_user = user.User.fetch(email=Equal(identifier)) if not current_user.success: return current_user if len(current_user.message) > 1: return response.create_error_response(errors={ 'email': ( 'This email is attached to more than one account. Please ' 'use the account username instead') }) current_user.message = current_user.message[0] return current_user def create_user(login, password, email, user_id=None, clean=True, groups=None): """Create a user. If a user is validated from ALW or OA, we need to backfill the db with the right information. This happens everytime a connect is marked as failed. Args: login (str): user's username. password (str): th user's password. email (str): the user's email. user_id (str): the user's id. clean (bool): clean sensitive information from the user. groups (list): optional list of groups to add the user to. Returns: Response: the result of the create. """ user_id = user_id or str(uuid.uuid1().int) if password: if isinstance(password, str): password = password.encode('utf8') password = passwords.encrypt(password) current_user = user.User.create( id=user_id, login=login, password=password, email=email, groups=groups) if not current_user.success: return current_user # Remove the password if clean: current_user.message.pop('password') return current_user def update_user_password(user_id, password, clean=True): """Update a user. Update user password Args: user_id (str): user's user id password (str): new user password clean (bool): clean sensitive information from the user. Returns: Response: the result of the update. """ if isinstance(password, str): password = password.encode('utf8') password = passwords.encrypt(password) current_user = user.User.update(dict(id=user_id), password=password) if not current_user.success: return current_user # Remove the password if clean: current_user.message.pop('password') return current_user