def email_change_request( self, email ): # request an email address to be modified. Create a rollback option. result = 'cannot_remove' emailCurrent = self.enki_user.email userId = self.enki_user.key.id() if email != '' and enki.libuser.exist_EnkiUser( email ): # if the new email matches an existing verified user email, reject it if emailCurrent == email: result = 'same' else: result = ERROR_EMAIL_IN_USE # Note: send an email to emailcurrent regardless to prevent email checking (see below) else: if email == '': # if the user erased the email, and they can log in through auth, store "removed" in the email field, so it isn't overwritten by an auth login with a verified email if self.enki_user.auth_ids_provider: self.enki_user.email = 'removed' self.enki_user.put() result = 'removed' else: return result else: # email the new, unverified address with a link to allow the user to verify the email tokenEntity = EnkiModelTokenVerify.get_by_user_id_email_type( userId, email, 'emailchange' ) if tokenEntity: # if a verify token for the same new email address and user already exists, use its token token = tokenEntity.token else: # otherwise create a new token token = security.generate_random_string( entropy = 256 ) emailToken = EnkiModelTokenVerify( token = token, email = email, user_id = userId, type = 'emailchange' ) emailToken.put() link = enki.libutil.get_local_url( 'emailchangeconfirm', { 'verifytoken': token }) self.send_email( email, MSG.SEND_EMAIL_EMAIL_CHANGE_CONFIRM_SUBJECT(), MSG.SEND_EMAIL_EMAIL_CHANGE_CONFIRM_BODY( link, email )) result = 'change' if emailCurrent and emailCurrent != 'removed' and result != 'same': # email the current, verified address in case they want to undo the change (useful if account has been hacked) # skip this step if the current email is empty (case if user logged in with auth id without email with e.g. Steam) or "removed". # If the email is already in use, mask the fact to prevent email checking. tokenEntity = enki.libuser.get_EmailRollbackToken_by_user_id_email( userId, emailCurrent ) if tokenEntity: # if the old email is already in the archive, use its token token = tokenEntity.token else: # otherwise create a new token token = security.generate_random_string( entropy = 256 ) emailOldToken = EnkiModelTokenEmailRollback( token = token, email = emailCurrent, user_id = userId ) emailOldToken.put() if result == ERROR_EMAIL_IN_USE: self.add_debugmessage( '''Comment - whether the email is available or not, the feedback through both the UI AND EMAIL is identical to prevent email checking.''' ) link = enki.libutil.get_local_url( 'emailrollback', { 'rollbacktoken': token } ) self.send_email( emailCurrent, MSG.SEND_EMAIL_EMAIL_CHANGE_UNDO_SUBJECT(), MSG.SEND_EMAIL_EMAIL_CHANGE_UNDO_BODY( link, emailCurrent )) return result
def get_EmailRollbackToken_by_user_id_email(user_id, email): entity = EnkiModelTokenEmailRollback.query( ndb.AND(EnkiModelTokenEmailRollback.user_id == user_id, EnkiModelTokenEmailRollback.email == email) ).get() if entity: return entity else: return None
def delete_account(self, delete_posts=False, token=''): token_to_save = 'accountdelete' if not token: # there is no token if the user has no email address: they are deleted immediately. They must be logged in. user_to_delete = self.enki_user else: # a user has followed a accountdelete token link. The user account associated with the token will be deleted tokenEntity = EnkiModelTokenVerify.get_by_token(token) user_to_delete = EnkiModelUser.get_by_id(tokenEntity.user_id) # delete all user related tokens except any verify token related to account deletion that's not yet been used if tokenEntity.type == token_to_save: token_to_save = 'accountandpostsdelete' verify_tokens_to_delete = EnkiModelTokenVerify.fetch_keys_by_user_id_except_type( user_to_delete.key.id(), token_to_save) if verify_tokens_to_delete: ndb.delete_multi(verify_tokens_to_delete) email_rollback_tokens_to_delete = EnkiModelTokenEmailRollback.fetch_keys_by_user_id( user_to_delete.key.id()) if email_rollback_tokens_to_delete: ndb.delete_multi(email_rollback_tokens_to_delete) # Delete the user account and log them out. if not HandlerBase.account_is_active(user_to_delete.key.id()): # delete user if the account is inactive display_names = EnkiModelDisplayName.fetch_keys_by_user_id( user_to_delete.key.id()) if display_names: ndb.delete_multi(display_names) user_to_delete.key.delete() else: # anonymise the user if user_to_delete.email: # delete email subscriptions EnkiModelEmailSubscriptions.remove_by_email( user_to_delete.email) user_to_delete.email = None if user_to_delete.password: user_to_delete.password = None if user_to_delete.auth_ids_provider: user_to_delete.auth_ids_provider = [] user_to_delete.put() # keep all historical display_names. Add a new current display_name '[deleted]' (unless it's already been deleted) display_name = EnkiModelDisplayName.get_by_user_id_current( user_to_delete.key.id()) if display_name: if display_name.prefix != EnkiModelDisplayName.DELETED_PREFIX or display_name.suffix != EnkiModelDisplayName.DELETED_SUFFIX: EnkiModelDisplayName.set_display_name( user_to_delete.key.id(), EnkiModelDisplayName.DELETED_PREFIX, EnkiModelDisplayName.DELETED_SUFFIX) # delete user's sent and received messages EnkiModelMessage.delete_user_messages(user_to_delete.key.id()) # delete user's posts if required if delete_posts: EnkiModelPost.delete_user_posts(user_to_delete.key.id()) # log the deleted user out if self.enki_user == user_to_delete.key.id(): self.log_out() EnkiModelTokenAuth.revoke_user_authentications(user_to_delete.key.id())
def fetch_keys_RollbackToken_by_time(user_id, time_created): keys = EnkiModelTokenEmailRollback.query( ndb.AND( EnkiModelTokenEmailRollback.time_created >= time_created, EnkiModelTokenEmailRollback.user_id == user_id ) ).fetch(keys_only=True) if keys: return keys else: return None
def email_rollback(self, token): email = token.email user_id = token.user_id # change the email user = self.set_email(email, user_id) if user: # retrieve all rollback tokens that are more recent, including the current one, and delete them tokenDateCreated = token.time_created youngerTokens = EnkiModelTokenEmailRollback.fetch_keys_by_user_id_time( user_id, tokenDateCreated) if youngerTokens: ndb.delete_multi(youngerTokens) # delete all potential remaining email verify tokens for that user userTokens = EnkiModelTokenVerify.fetch_keys_by_user_id_type( user_id, 'emailchange') if userTokens: ndb.delete_multi(userTokens)
def email_change_request(self, email): # request an email address to be modified. Create a rollback option. result = 'cannot_remove' emailCurrent = self.enki_user.email userId = self.enki_user.key.id() if email != '' and EnkiModelUser.exist_by_email(email): # if the new email matches an existing verified user email, reject it if emailCurrent == email: result = 'same' else: result = self.ERROR_EMAIL_IN_USE # Note: send an email to emailcurrent regardless to prevent email checking (see below) else: if email == '': # if the user erased the email, and they can log in through auth, store "removed" in the email field, so it isn't overwritten by an auth login with a verified email if self.enki_user.auth_ids_provider: self.enki_user.email = 'removed' self.enki_user.put() result = 'removed' else: return result else: # email the new, unverified address with a link to allow the user to verify the email tokenEntity = EnkiModelTokenVerify.get_by_user_id_email_type( userId, email, 'emailchange') if tokenEntity: # if a verify token for the same new email address and user already exists, use its token token = tokenEntity.token else: # otherwise create a new token token = security.generate_random_string(entropy=256) emailToken = EnkiModelTokenVerify(token=token, email=email, user_id=userId, type='emailchange') emailToken.put() link = enki.libutil.get_local_url('emailchangeconfirm', {'verifytoken': token}) self.send_email( email, MSG.SEND_EMAIL_EMAIL_CHANGE_CONFIRM_SUBJECT(), MSG.SEND_EMAIL_EMAIL_CHANGE_CONFIRM_BODY(link, email)) result = 'change' if emailCurrent and emailCurrent != 'removed' and result != 'same': # email the current, verified address in case they want to undo the change (useful if account has been hacked) # skip this step if the current email is empty (case if user logged in with auth id without email with e.g. Steam) or "removed". # If the email is already in use, mask the fact to prevent email checking. tokenEntity = EnkiModelTokenEmailRollback.get_by_user_id_email( userId, emailCurrent) if tokenEntity: # if the old email is already in the archive, use its token token = tokenEntity.token else: # otherwise create a new token token = security.generate_random_string(entropy=256) emailOldToken = EnkiModelTokenEmailRollback(token=token, email=emailCurrent, user_id=userId) emailOldToken.put() if result == self.ERROR_EMAIL_IN_USE: self.add_debugmessage( '''Comment - whether the email is available or not, the feedback through both the UI AND EMAIL is identical to prevent email checking.''' ) link = enki.libutil.get_local_url('emailrollback', {'rollbacktoken': token}) self.send_email( emailCurrent, MSG.SEND_EMAIL_EMAIL_CHANGE_UNDO_SUBJECT(), MSG.SEND_EMAIL_EMAIL_CHANGE_UNDO_BODY(link, emailCurrent)) return result
def fetch_keys_RollbackToken(user_id): keys = EnkiModelTokenEmailRollback.query(EnkiModelTokenEmailRollback.user_id == user_id).fetch(keys_only=True) if keys: return keys else: return None
def get_RollbackToken_by_token(token): entity = EnkiModelTokenEmailRollback.query(EnkiModelTokenEmailRollback.token == token).get() if entity: return entity else: return None
def fetch_keys_RollbackToken_by_time( user_id, time_created ): keys = EnkiModelTokenEmailRollback.query( ndb.AND( EnkiModelTokenEmailRollback.time_created >= time_created , EnkiModelTokenEmailRollback.user_id == user_id )).fetch( keys_only = True ) return keys
def fetch_keys_RollbackToken( user_id ): keys = EnkiModelTokenEmailRollback.query( EnkiModelTokenEmailRollback.user_id == user_id ).fetch( keys_only = True ) return keys
def get_RollbackToken_by_token( token ): entity = EnkiModelTokenEmailRollback.query( EnkiModelTokenEmailRollback.token == token ).get() return entity
def get_EmailRollbackToken_by_user_id_email( user_id, email ): entity = EnkiModelTokenEmailRollback.query( ndb.AND( EnkiModelTokenEmailRollback.user_id == user_id, EnkiModelTokenEmailRollback.email == email )).get() return entity