Ejemplo n.º 1
0
class UserService(BaseService):
    """
    UserService, using when create new user, edit user info, or delete user, or enable/disable mfa flow
    """
    def __init__(self):
        super().__init__(User())
        self.authen_setting = AuthenSetting()

    def create_new_user_srp(self, id, email, password_verifier, salt,
                            iv_parameter, display_name, auth_source):
        # create new normal user with these parameter
        try:
            self.model = User(id=id,
                              email=email,
                              password_verifier=password_verifier,
                              salt=salt,
                              iv_parameter=iv_parameter,
                              display_name=display_name,
                              auth_source=auth_source)
            self.model.add()
        except Exception as e:
            logger.error(e)
            raise Exception(Message.REGISTER_USER_FAILED)

    def create_user_social(self, id, email, display_name, auth_source):
        # create new social user with these parameter
        try:
            self.model = User(id=id,
                              email=email,
                              display_name=display_name,
                              auth_source=auth_source)
            self.model.add()
            return self
        except Exception as e:
            logger.info(e)
            return None

    def forgot_user(self, user_info, new_user_id, password_verifier, salt,
                    iv_parameter):
        # delete user, then recreate user with new user_id and security information
        try:
            self.model = User(id=new_user_id,
                              email=user_info.email,
                              display_name=user_info.display_name,
                              auth_source=user_info.auth_source,
                              password_verifier=password_verifier,
                              salt=salt,
                              iv_parameter=iv_parameter,
                              first_name=user_info.first_name,
                              last_name=user_info.last_name,
                              status=user_info.status,
                              avatar=user_info.avatar,
                              phone_number=user_info.phone_number,
                              created_at=user_info.created_at)
            self.model.add()
            self.delete_user(user_info.id)
        except Exception as e:
            logger.error(e)
            raise Exception(Message.REGISTER_USER_FAILED)

    def get_google_user(self, email, auth_source):
        # get user with email and auth_source info
        user_info = self.model.get_google_user(email, auth_source)
        return user_info

    def change_password(self, request, old_pass, new_pass, user_id):
        # change password in keycloak
        try:
            user_info = self.model.get(user_id)
            response = KeyCloakUtils.set_user_password(user_id, new_pass)

            return user_info
        except Exception as e:
            logger.info(e)
            raise Exception(Message.CHANGE_PASSWORD_FAILED)

    def get_mfa_state(self, user_id):
        # get mfa state of user_id
        user_info = self.model.get(user_id)
        if user_info is None:
            raise Exception(Message.AUTH_USER_NOT_FOUND)
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting is None:
            user_authen_setting = AuthenSetting(id=user_id).add()
        return user_authen_setting.mfa_enable

    def init_mfa_state_enabling(self, user_id):
        # start enable mfa for user_id
        # if otp in frozen state, raising exception
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting is None:
            user_authen_setting = AuthenSetting(id=user_id).add()
        if user_authen_setting.mfa_enable:
            success = False
            next_step = ''
        else:
            if user_authen_setting.otp_frozen_time > datetime.datetime.now():
                raise Exception(Message.FROZEN_STATE_OTP_SERVICE)

            next_step = 'mfa_validate_password'
            user_authen_setting.require_action = next_step
            user_authen_setting.update()
            success = True
        return success, next_step

    def init_mfa_state_disabling(self, user_id):
        # diable mfa for user_id
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting is None:
            user_authen_setting = AuthenSetting(id=user_id).add()
        if user_authen_setting.mfa_enable:
            user_authen_setting.mfa_enable = False
            user_authen_setting.update()
            success = True
        else:
            success = False
        next_step = ''
        return success, next_step

    def mfa_validate_password_flow(self, user_id):
        # check if user_id is in mfa_validate_password action
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting.require_action != 'mfa_validate_password':
            raise Exception(Message.AUTHEN_SETTING_FLOW_NOT_FOUND)
        return True

    def mfa_request_otp(self, user_id, phone_number):
        # start request otp service for user_id with phone_number
        user_authen_setting = self.authen_setting.get(user_id)
        n_times = user_authen_setting.otp_request_counter + 1
        if n_times > OTPServer.valid_resend_time:
            # reset counter and put otp service into frozen state
            user_authen_setting.otp_tried_time = 0
            user_authen_setting.otp_request_counter = 0
            user_authen_setting.otp_frozen_time = OTPServer.cal_frozen_time()
            user_authen_setting.update()
            raise Exception(Message.FROZEN_STATE_OTP_SERVICE)
        if user_authen_setting.otp_frozen_time > datetime.datetime.now():
            raise Exception(Message.FROZEN_STATE_OTP_SERVICE)
        try:
            next_step = 'mfa_validate_otp'
            user_authen_setting.require_action = next_step
            hash_otp = OTPServer.get_otp(phone_number)
            user_authen_setting.otp = hash_otp
            user_authen_setting.token_valid_time = OTPServer.get_valid_time()
            user_authen_setting.otp_request_counter = n_times
            user_authen_setting.update()
            success = True
            return success, next_step
        except Exception as e:
            logger.error(e)
            raise Exception(Message.OTP_SERVER_NOT_RESPONDING)

    def validate_otp(self, user_id, otp):
        # validate otp service for user_id
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting is None:
            raise Exception(Message.AUTH_USER_NOT_FOUND)
        if user_authen_setting.require_action != 'mfa_validate_otp':
            raise Exception(Message.AUTHEN_SETTING_FLOW_NOT_FOUND)
        if user_authen_setting.otp_tried_time >= OTPServer.valid_trying_time:
            user_authen_setting.otp = None
            user_authen_setting.update()
            raise Exception(Message.EXCEED_MAXIMUM_TRIED_TIMES_OTP)
        if datetime.datetime.now() > user_authen_setting.token_valid_time:
            user_authen_setting.otp = None
            user_authen_setting.update()
            raise Exception(Message.EXPIRED_OTP)
        if OTPServer.check_otp(otp, user_authen_setting.otp) is True:
            user_authen_setting.mfa_enable = True
            user_authen_setting.otp = None
            user_authen_setting.require_action = ""
            user_authen_setting.token_valid_time = datetime.datetime.now()
            user_authen_setting.otp_request_counter = 0
            user_authen_setting.otp_tried_time = 0
            user_authen_setting.update()
            success = True
            next_step = ''
        else:
            user_authen_setting.otp_tried_time += 1
            user_authen_setting.update()
            raise Exception(Message.WRONG_OTP)
        return success, next_step

    def re_init_otp(self, user_id):
        # retry request otp for user_id
        user_info = self.model.get(user_id)
        if user_info is None:
            raise Exception(Message.AUTH_USER_NOT_FOUND)
        user_authen_setting = self.authen_setting.get(user_id)
        if user_authen_setting.require_action != 'mfa_validate_otp':
            raise Exception(Message.AUTHEN_SETTING_FLOW_NOT_FOUND)
        n_times = user_authen_setting.otp_request_counter + 1
        if n_times > OTPServer.valid_resend_time:
            # reset counter and put otp service into frozen state
            user_authen_setting.otp_tried_time = 0
            user_authen_setting.otp_request_counter = 0
            user_authen_setting.otp_frozen_time = OTPServer.cal_frozen_time()
            user_authen_setting.update()
            raise Exception(Message.FROZEN_STATE_OTP_SERVICE)
        if user_authen_setting.otp_frozen_time > datetime.datetime.now():
            raise Exception(Message.FROZEN_STATE_OTP_SERVICE)
        try:
            hash_otp = OTPServer.get_otp(user_info.phone_number)
            user_authen_setting.otp = hash_otp
            user_authen_setting.otp_tried_time = 0
            user_authen_setting.token_valid_time = OTPServer.get_valid_time()
            user_authen_setting.otp_request_counter = n_times
            user_authen_setting.update()
            success = True
            next_step = 'mfa_validate_otp'
            return success, next_step
        except Exception as e:
            logger.error(e)
            raise Exception(Message.OTP_SERVER_NOT_RESPONDING)

    def update_hash_pass(self,
                         user_id,
                         hash_password,
                         salt='',
                         iv_parameter=''):
        # update hash_password and relate security information for user_id
        user_info = self.model.get(user_id)
        user_info.password_verifier = hash_password
        if salt:
            user_info.salt = salt
        if iv_parameter:
            user_info.iv_parameter = iv_parameter
        user_info.update()
        return (user_info.salt, user_info.iv_parameter)

    def update_hash_pin(self, user_id, hash_pincode, salt='', iv_parameter=''):
        # update hash_pincode and relate security information for user_id
        user_info = self.model.get(user_id)
        user_info.password_verifier = hash_pincode
        if salt:
            user_info.salt = salt
        if iv_parameter:
            user_info.iv_parameter = iv_parameter
        user_info.update()
        return user_info.salt, user_info.iv_parameter

    def get_profile(self, user_id):
        # get profile of user_id
        try:
            user_info = self.model.get(user_id)
            if user_info is not None:
                obj_res = user_pb2.UserProfileResponse(
                    id=user_info.id, display_name=user_info.display_name)
                if user_info.email:
                    obj_res.email = user_info.email
                if user_info.phone_number:
                    obj_res.phone_number = user_info.phone_number
                if user_info.avatar:
                    obj_res.avatar = user_info.avatar
                return obj_res
            else:
                return None
        except Exception as e:
            logger.info(e)
            raise Exception(Message.GET_PROFILE_FAILED)

    def update_profile(self, user_id, display_name, phone_number, avatar,
                       clear_phone_number):
        # update profile for user_id, with force clear phone number flag for clearing stored phone number in db
        try:
            profile = self.model.get(user_id)
            if display_name:
                profile.display_name = display_name
            if clear_phone_number:
                user_authen_setting = self.authen_setting.get(user_id)
                if user_authen_setting is None:
                    user_authen_setting = AuthenSetting(id=user_id).add()
                user_authen_setting.enable_mfa = False  # change phone_number automatically turn off enable_mfa
                user_authen_setting.update()
                profile.phone_number = ""
            elif phone_number:
                user_authen_setting = self.authen_setting.get(user_id)
                if user_authen_setting is None:
                    user_authen_setting = AuthenSetting(id=user_id).add()
                user_authen_setting.enable_mfa = False  # change phone_number automatically turn off enable_mfa
                user_authen_setting.update()
                profile.phone_number = phone_number
            if avatar:
                profile.avatar = avatar
            return profile.update()

        except Exception as e:
            logger.info(e)
            raise Exception(Message.UPDATE_PROFILE_FAILED)

    def get_user_info(self, client_id, workspace_domain):
        # get information of client_id with additional infor about workspace_domain
        try:
            user_info = self.model.get(client_id)
            if user_info is not None:
                return user_pb2.UserInfoResponse(
                    id=user_info.id,
                    display_name=user_info.display_name,
                    workspace_domain=workspace_domain)
            else:
                raise Exception(Message.GET_USER_INFO_FAILED)
        except Exception as e:
            logger.info(e)
            raise Exception(Message.GET_USER_INFO_FAILED)

    def get_user_by_auth_source(self, email, auth_source):
        # get fully stored information about user by email and auth_source
        user_info = self.model.get_user_by_auth_source(email, auth_source)
        return user_info

    def get_user_by_id(self, client_id):
        # get fully stored information about user by user_id
        user_info = self.model.get(client_id)
        return user_info

    def search_user(self, keyword, client_id):
        # searching user with keyword differ to client_id
        try:
            lst_user = self.model.search(keyword, client_id)
            lst_obj_res = []
            for obj in lst_user:
                obj_res = user_pb2.UserInfoResponse(
                    id=obj.id,
                    display_name=obj.display_name,
                )
                lst_obj_res.append(obj_res)

            response = user_pb2.SearchUserResponse(lst_user=lst_obj_res)
            return response
        except Exception as e:
            logger.info(e)
            raise Exception(Message.SEARCH_USER_FAILED)

    def get_users(self, client_id, workspace_domain):
        # get other users for client_id within workspace_domain
        try:
            lst_user = self.model.get_users(client_id)
            lst_obj_res = []
            for obj in lst_user:
                obj_res = user_pb2.UserInfoResponse(
                    id=obj.id,
                    display_name=obj.display_name,
                    workspace_domain=workspace_domain,
                )
                lst_obj_res.append(obj_res)

            response = user_pb2.GetUsersResponse(lst_user=lst_obj_res)
            return response
        except Exception as e:
            logger.info(e)
            raise Exception(Message.GET_USER_INFO_FAILED)

    def update_last_login(self, user_id):
        # update last time login for user_id
        try:
            user_info = self.model.get(user_id)
            user_info.last_login_at = datetime.datetime.now()
            user_info.update()
        except Exception as e:
            logger.info(e)

    def set_user_status(self, client_id, status):
        # set status for client_id
        try:
            user_info = self.model.get(client_id)
            if status == "":
                status = None
            user_info.status = status

            user_info.update()
            client_record = client_records_list_in_memory.get(
                str(client_id), None)
            client_record["user_status"] = status
        except Exception as e:
            logger.error(e)
            raise Exception(Message.UPDATE_USER_STATUS_FAILED)

    def update_client_record(self, client_id):
        # update client record in temp variable
        try:
            client_record = client_records_list_in_memory.get(
                str(client_id), None)
            if client_record is None:
                client_records_list_in_memory.update({
                    str(client_id): {
                        "last_active": datetime.datetime.now(),
                        "prev_active": None,
                        "user_status": None,
                    }
                })
            else:
                client_record["prev_active"] = client_record["last_active"]
                client_record["last_active"] = datetime.datetime.now()
        except Exception as e:
            logger.error(e)
            raise Exception(Message.PING_PONG_SERVER_FAILED)

    def categorize_workspace_domains(self, list_clients):
        # create and return categorize_workspace_domains, with client is devided into each workspace
        workspace_domains_dictionary = {}

        for client in list_clients:
            if str(client.workspace_domain
                   ) in workspace_domains_dictionary.keys():
                workspace_domains_dictionary[str(
                    client.workspace_domain)].append(client)
            else:
                workspace_domains_dictionary.update(
                    {str(client.workspace_domain): [client]})
        return workspace_domains_dictionary

    def get_list_clients_status(self, list_clients, should_get_profile):
        # get list of clients status, with returning additional basic infor of user is should_get_profile is True
        logger.info("get_list_clients_status")
        try:
            owner_workspace_domain = get_owner_workspace_domain()
            list_clients_status = []

            workspace_domains_dictionary = self.categorize_workspace_domains(
                list_clients)

            for workspace_domain in workspace_domains_dictionary.keys():
                list_client = workspace_domains_dictionary[workspace_domain]
                if workspace_domain == owner_workspace_domain:
                    for client in list_client:
                        user_status = self.get_owner_workspace_client_status(
                            client.client_id)

                        tmp_client_response = user_pb2.MemberInfoRes(
                            client_id=client.client_id,
                            workspace_domain=workspace_domain,
                            status=user_status,
                        )
                        if should_get_profile:
                            user_info = self.model.get(client.client_id)
                            if user_info is not None:
                                if user_info.display_name:
                                    tmp_client_response.display_name = user_info.display_name
                                if user_info.phone_number:
                                    tmp_client_response.phone_number = user_info.phone_number
                                if user_info.avatar:
                                    tmp_client_response.avatar = user_info.avatar
                            logger.info("user info {}: {}".format(
                                client.client_id, tmp_client_response))
                        list_clients_status.append(tmp_client_response)
                else:
                    logger.info("workspace request other server {}".format(
                        workspace_domain))
                    other_clients_response = self.get_other_workspace_clients_status(
                        workspace_domain, list_client, should_get_profile)
                    list_clients_status.extend(other_clients_response)

            response = user_pb2.GetClientsStatusResponse(
                lst_client=list_clients_status)
            return response
        except Exception as e:
            logger.error(e)
            raise Exception(Message.GET_USER_STATUS_FAILED)

    def get_owner_workspace_client_status(self, client_id):
        # get client record of client_id in this server
        client_record = client_records_list_in_memory.get(str(client_id), None)

        if client_record is not None:
            leave_time_amount = datetime.datetime.now(
            ) - client_record["last_active"]

            if leave_time_amount.seconds > get_system_config().get(
                    "maximum_offline_time_limit"):
                user_status = "Offline"
            else:
                if client_record["user_status"] is not None:
                    user_status = client_record["user_status"]
                else:
                    user_status = "Online"
        else:
            user_status = "Undefined"
        return user_status

    def get_other_workspace_clients_status(self,
                                           workspace_domain,
                                           list_client,
                                           should_get_profile=False):
        # get client record of client_id in other server
        server_error_resp = []

        client = ClientUser(workspace_domain)
        client_resp = client.get_clients_status(list_client,
                                                should_get_profile)

        if client_resp is None:
            logger.info("CALL WORKSPACE ERROR", workspace_domain)
            for client in list_client:
                tmp_client_response = user_pb2.MemberInfoRes(
                    client_id=client.client_id,
                    workspace_domain=client.workspace_domain,
                    status="Undefined",
                )
                server_error_resp.append(tmp_client_response)
            return server_error_resp
        return client_resp.lst_client

    def base64_enconding_text_to_string(self, text):
        # encode utf-8 string to ascii string
        text_bytes = text.encode("ascii")
        encoded_text_bytes = base64.b64encode(text_bytes)
        return encoded_text_bytes.decode('ascii')

    def upload_avatar(self, client_id, file_name, file_content, file_type,
                      file_hash):
        # upload avatar for client_id
        m = hashlib.new('md5', file_content).hexdigest()
        if m != file_hash:
            raise Exception(Message.UPLOAD_FILE_DATA_LOSS)
        # start upload to s3 and resize if needed
        tmp_file_name, file_ext = os.path.splitext(file_name)
        avatar_file_name = self.base64_enconding_text_to_string(
            client_id) + file_ext

        avatar_url = UploadFileService().upload_to_s3(avatar_file_name,
                                                      file_content, file_type)
        obj_res = user_pb2.UploadAvatarResponse(file_url=avatar_url)
        return obj_res

    def delete_user(self, user_id):
        # delete user, note that this function must be called only by admin
        user_info = self.model.get(user_id)
        user_info.delete()
        return True