Exemple #1
0
    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)
Exemple #2
0
 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
Exemple #3
0
 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
Exemple #4
0
    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
Exemple #5
0
 def __init__(self):
     super().__init__()
     self.user_db = User()
     self.authen_setting = AuthenSetting()
Exemple #6
0
class AuthService:
    """
    Authen Service for authenticate action. This service involved in any granted authentication, include register new user,
    login, logout, forgot password, generate pre_access_token or start mfa service.
    """
    def __init__(self):
        super().__init__()
        self.user_db = User()
        self.authen_setting = AuthenSetting()

    def token(self, user_name, password):
        # basic authen service for login with user_name and password
        try:
            token = KeyCloakUtils.token(user_name, password)
            if token:
                return token
        except Exception as e:
            logger.info(e)
            response_code = e.response_code
            check_error = json.loads(e.args[0]).get("error")
            if check_error == "invalid_grant" and response_code == 400:
                raise Exception(Message.USER_NOT_VERIFY_EMAIL)
            raise Exception(Message.AUTH_USER_NOT_FOUND)

    def logout(self, refresh_token):
        # basic authen service for logout using refresh_token
        try:
            KeyCloakUtils.logout(refresh_token)
        except Exception as e:
            logger.info(e)
            # raise Exception(Message.UNAUTHENTICATED)

    def remove_token(self, client_id, device_id):

        try:
            NotifyPushService().delete_token(client_id=client_id,
                                             device_id=device_id)
        except Exception as e:
            logger.info(e)
            raise Exception(Message.UNAUTHENTICATED)

    def forgot_user(self, email, password_verifier, display_name):
        try:
            # delete user, then re create new user with activate status is True
            old_user_id = self.get_user_by_email(email)["id"]
            self.delete_user(old_user_id)
            new_user_id = KeyCloakUtils.create_user(email, email,
                                                    password_verifier, "",
                                                    display_name)
            if new_user_id:
                KeyCloakUtils.active_user(new_user_id)
                return new_user_id
        except Exception as e:
            logger.info(e)
            raise Exception(Message.REGISTER_USER_FAILED)

    def register_srp_user(self, email, password_verifier, display_name):
        # register new user with using email as user name
        try:
            user_id = KeyCloakUtils.create_user(email, email,
                                                password_verifier, "",
                                                display_name)
            if user_id:
                a = KeyCloakUtils.send_verify_email(user_id)
                return user_id
        except Exception as e:
            logger.info(e)
            raise Exception(Message.REGISTER_USER_FAILED)

    def delete_user(self, userid):
        # delete user in keycloak server by its userid
        try:
            KeyCloakUtils.delete_user(user_id=userid)
        except Exception as e:
            logger.info(e)
            raise Exception(Message.UNAUTHENTICATED)

    def get_user_by_email(self, email):
        # get user in keycloak server by its email
        try:
            return KeyCloakUtils.get_user_by_email(email)
        except Exception as e:
            logger.info(e)
            raise Exception(Message.USER_NOT_FOUND)

    def send_forgot_password(self, email):
        # send an email to user with app link for reset password
        try:
            user = self.get_user_by_email(email=email)
            if not user:
                raise Exception(Message.USER_NOT_FOUND)
        except Exception as e:
            logger.info(e)
            raise Exception(Message.USER_NOT_FOUND)
        else:
            pre_access_token = self.hash_pre_access_token(
                user['id'], "forgot_password")
            server_domain = get_owner_workspace_domain()
            user_info = User().get(user['id'])
            if user_info.auth_source == 'account':
                MailerServer.send_reset_password_mail(email, email,
                                                      pre_access_token,
                                                      server_domain)
                return user['id']
            else:
                raise Exception(Message.EMAIL_ALREADY_USED_FOR_SOCIAL_SIGNIN)

    # login google
    def google_login(self, google_id_token):
        # google login by using google id token, return user_name, user_id and boolean variable indicate if this user is new user
        try:
            verify_id_token_url = "https://oauth2.googleapis.com/tokeninfo?id_token=" + google_id_token
            req = requests.get(url=verify_id_token_url)
            if req.status_code != 200:
                raise Exception(Message.GOOGLE_AUTH_ID_TOKEN_INVALID)
            google_token_info = req.json()

            logger.info("Google login token spec:")
            logger.info(google_token_info)

            # check google_token_info["aud"] matching with google app id
            google_app_id = get_system_config()["google_app_id"]
            if google_token_info["aud"] != google_app_id[
                    "ios"] and google_token_info["aud"] != google_app_id[
                        "android"]:
                raise Exception(Message.GOOGLE_AUTH_FAILED)

            google_email = google_token_info["email"]
            # check account exits
            user = self.get_user_by_email(email=google_email)
            #active_user
            if user:
                if not user["emailVerified"]:
                    KeyCloakUtils.active_user(user["id"])
                user_info = UserService().get_user_by_id(user["id"])
                return google_email, user[
                    "id"], user_info.password_verifier is None or user_info.password_verifier == ""
            else:
                # create new user
                new_user_id = KeyCloakUtils.create_user_without_password(
                    google_email, google_email, "", google_token_info["name"])
                new_user = UserService().create_user_social(
                    id=new_user_id,
                    email=google_email,
                    display_name=google_token_info["name"],
                    auth_source='google')
                if new_user is None:
                    self.delete_user(new_user_id)
                    raise Exception(Message.REGISTER_USER_FAILED)
                return google_email, new_user_id, True
        except Exception as e:
            logger.info(e)
            raise Exception(Message.GOOGLE_AUTH_FAILED)

    # login office
    def office_login(self, office_access_token):
        # office login by using office access token, return user_name, user_id and boolean variable indicate if this user is new user
        try:
            verify_token_url = "https://graph.microsoft.com/v1.0/me"
            bearer = 'Bearer ' + office_access_token
            headers = {'Authorization': bearer}

            req = requests.get(url=verify_token_url, headers=headers)
            if req.status_code != 200:
                raise Exception(Message.OFFICE_ACCESS_TOKEN_INVALID)
            office_token_info = req.json()

            logger.info("Office login token spec:")
            logger.info(office_token_info)

            office_id = office_token_info["id"]
            # check account exits
            user = self.get_user_by_email(office_id)
            if user:
                user_info = UserService().get_user_by_id(user["id"])
                return office_id, user[
                    "id"], user_info.password_verifier is None or user_info.password_verifier == ""
            else:
                display_name = office_token_info["displayName"]
                email = ""
                if not display_name:
                    if office_token_info["userPrincipalName"]:
                        user_principal_name = office_token_info[
                            "userPrincipalName"].split("@")
                        if len(user_principal_name) > 0:
                            display_name = user_principal_name[0]
                            email = office_token_info["userPrincipalName"]
                # create new user
                new_user_id = KeyCloakUtils.create_user_without_password(
                    email, office_id, "", display_name)
                new_user = UserService().create_user_social(
                    id=new_user_id,
                    email=office_token_info["mail"],
                    display_name=display_name,
                    auth_source='office')
                if new_user is None:
                    self.delete_user(new_user_id)
                    raise Exception(Message.REGISTER_USER_FAILED)
                return office_id, new_user_id, True
        except Exception as e:
            logger.info(e)
            raise Exception(Message.OFFICE_AUTH_FAILED)

    # login facebook
    def facebook_login(self, facebook_access_token):
        # facebook login by using facebook access token, return user_name, user_id and boolean variable indicate if this user is new user
        try:
            # validate access_token
            facebook_app_id = get_system_config()["facebook_app"]
            verify_token_app_id = "https://graph.facebook.com/debug_token?input_token={}&access_token={}|{}".format(
                facebook_access_token, facebook_app_id["app_id"],
                facebook_app_id["app_secret"])
            req = requests.get(url=verify_token_app_id)
            if req.status_code != 200:
                raise Exception(Message.FACEBOOK_ACCESS_TOKEN_INVALID)
            facebook_token_app_id_info = req.json()
            facebook_token_app_id = facebook_token_app_id_info["data"][
                "app_id"]
            if facebook_token_app_id != facebook_app_id["app_id"]:
                raise Exception(Message.FACEBOOK_ACCESS_TOKEN_INVALID)

            verify_token_url = "https://graph.facebook.com/me?fields=id,name,email&access_token=" + facebook_access_token
            req = requests.get(url=verify_token_url)

            if req.status_code != 200:
                raise Exception(Message.FACEBOOK_ACCESS_TOKEN_INVALID)
            facebook_token_info = req.json()

            logger.info("Facebook login token spec:")
            logger.info(facebook_token_info)

            facebook_id = facebook_token_info["id"]
            facebook_email = facebook_token_info["email"]
            facebook_name = facebook_token_info["name"]
            # check account exits
            user = self.get_user_by_email(facebook_id)
            if user:
                user_info = UserService().get_user_by_id(user["id"])
                return facebook_id, user[
                    "id"], user_info.password_verifier is None or user_info.password_verifier == ""
            else:
                # create new user
                new_user_id = KeyCloakUtils.create_user_without_password(
                    facebook_email, facebook_id, "", facebook_name)
                new_user = UserService().create_user_social(
                    id=new_user_id,
                    email=facebook_email,
                    display_name=facebook_name,
                    auth_source='facebook')
                if new_user is None:
                    self.delete_user(new_user_id)
                    raise Exception(Message.REGISTER_USER_FAILED)
                return facebook_id, new_user_id, True
        except Exception as e:
            logger.info(e)
            raise Exception(Message.FACEBOOK_AUTH_FAILED)

    def create_otp_service(self, client_id):
        # start opt service for client if mfa is enabled, raising FROZEN_STATE_OTP_SERVICE if mfa is enabled and in frozen state (request resend otp too many times)
        user_info = self.user_db.get(client_id)
        user_authen_setting = self.authen_setting.get(client_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:
            hash_otp = OTPServer.get_otp(user_info.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
            # we create hash_key with valid_time to make hash_key will change each request
            hash_key = OTPServer.hash_uid(client_id,
                                          user_authen_setting.token_valid_time)
            return hash_key
        except Exception as e:
            logger.info(e)
            raise Exception(Message.OTP_SERVER_NOT_RESPONDING)

    def verify_otp(self, client_id, hash_key, otp):
        # verify opt for client if mfa is enabled, raising FROZEN_STATE_OTP_SERVICE if mfa is enabled and in frozen state (request resend otp too many times)
        # if client try to verify an otp too many times, this otp will become invalid
        user_authen_setting = self.authen_setting.get(client_id)
        if user_authen_setting is None:
            raise Exception(Message.GET_MFA_STATE_FALED)
        success = OTPServer.verify_hash_code(
            client_id, user_authen_setting.token_valid_time, hash_key)
        if not success:
            raise Exception(Message.GET_VALIDATE_HASH_OTP_FAILED)
        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 False:
            user_authen_setting.otp_tried_time += 1
            user_authen_setting.update()
            raise Exception(Message.WRONG_OTP)
        else:
            user_authen_setting.otp = None
            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()
        return success

    def resend_otp(self, client_id, hash_key):
        # resend opt for client if mfa is enabled, raising FROZEN_STATE_OTP_SERVICE if mfa is enabled and in frozen state (request resend otp too many times)
        user_authen_setting = self.authen_setting.get(client_id)
        if user_authen_setting is None:
            logger.info(Message.GET_MFA_STATE_FALED)
            raise Exception(Message.GET_MFA_STATE_FALED)
        success = OTPServer.verify_hash_code(
            client_id, user_authen_setting.token_valid_time, hash_key)
        if not success:
            logger.info(Message.GET_VALIDATE_HASH_OTP_FAILED)
            raise Exception(Message.GET_VALIDATE_HASH_OTP_FAILED)
        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()
            logger.info(Message.FROZEN_STATE_OTP_SERVICE)
            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:
            user_info = self.user_db.get(client_id)
            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()
            # we create hash_key with valid_time to make hash_key will change each request
            hash_key = OTPServer.hash_uid(client_id,
                                          user_authen_setting.token_valid_time)
            return hash_key
        except Exception as e:
            logger.info(e)
            raise Exception(Message.OTP_SERVER_NOT_RESPONDING)

    def hash_pre_access_token(self, user_name, require_action):
        # create a pre access_token by signing a jws with user_name as iss, require_action as aud
        hash_key = OTPServer.sign_message(user_name, require_action)
        return hash_key

    def verify_hash_pre_access_token(self, user_name, signed_message,
                                     require_action):
        # verify a pre access_token signed by jws with user_name as iss, require_action as aud
        try:
            message = OTPServer.verify_message(signed_message)
            if message.get("iss", None) != user_name:
                return False
            if message.get("aud", None) != require_action:
                return False
            if message.get("exp", 0) < int(time.time()):
                return False
            return True
        except Exception as e:
            logger.error(e)
            return False
Exemple #7
0
 def __init__(self):
     super().__init__(User())
     self.authen_setting = AuthenSetting()
Exemple #8
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