def test_verify_conf_changes(self): """Registration challenges with EmailVerificationModule enabled.""" self.env = EnvironmentStub( enable=['trac.*', 'acct_mgr.admin.*', 'acct_mgr.register.*']) self.env.path = tempfile.mkdtemp() set_user_attribute(self.env, 'admin', 'email', '*****@*****.**') check = EmailCheck(self.env) req = self.req # Inspector provides the email text input field. old_email_input = '*****@*****.**' acct = dict(username='******', email=old_email_input, name='User') req.args.update(acct) field_res = check.render_registration_fields(req, acct) self.assertEqual(len(field_res), 2) self.assertTrue(Markup(field_res[0]).startswith('<label>Email:')) # Ensure, that old input is restored on failure. self.assertTrue(old_email_input in Markup(field_res[0])) # Ensure, that template data dict is passed unchanged. self.assertEqual(field_res[1], acct) req.args.update(dict(email='')) # 1st: Initially try with account verification disabled by setting. self.env.config.set('account-manager', 'verify_email', False) self.assertEqual(check.validate_registration(req), None) # 2nd: Again no email, but now with account verification enabled. self.env.config.set('account-manager', 'verify_email', True) self.assertRaises(RegistrationError, check.validate_registration, req) # 3th attempt: Valid email, but already registered with a username. req.args['email'] = '*****@*****.**' self.assertRaises(RegistrationError, check.validate_registration, req) # 4th attempt: Finally some valid input. req.args['email'] = '*****@*****.**' self.assertEqual(check.validate_registration(req), None)
def _create_user(self, req): """Set password and prime a new authenticated Trac session.""" username = req.args.get('username', '').strip() username = self.handle_username_casing(username) # Result of a successful account creation request is a made-up # authenticated session, that a new user can refer to later on. # Strictly required to create a primary key for additional attributes, # perhaps even something as critical as the SessionStore password. from acct_mgr.model import prime_auth_session, set_user_attribute try: prime_auth_session(self.env, username) # Save attributes for the user with reference to that session ID. # Done before writing to a password store to preserve attributes # in case of non-fatal errors (especially notification errors). for attribute in ('name', 'email'): value = req.args.get(attribute) if not value: continue set_user_attribute(self.env, username, attribute, value) # Create the user in the configured (primary) password store. self.set_password(username, req.args.get('password'), overwrite=False) finally: if not self.has_user(username): # Rollback. from acct_mgr.model import delete_user delete_user(self.env, username)
def failed_count(self, user, ipnr=None, reset=False): """Report number of previously logged failed login attempts. Enforce login policy with regards to tracking of login attempts and user account lock behavior. Default `False` for reset value causes logging of another attempt. `None` value for reset just reads failed login attempts count. `True` value for reset triggers final log deletion. """ value = get_user_attribute(self.env, user, 1, 'failed_logins_count') count = value and int(value[user][1].get('failed_logins_count')) or 0 if reset is None: # Report failed attempts count only. return count if not reset: # Trigger the failed attempt logger. attempts = self.get_failed_log(user) log_length = len(attempts) if log_length > self.login_attempt_max_count: # Truncate attempts list preserving most recent events. del attempts[:(log_length - self.login_attempt_max_count)] attempts.append({'ipnr': ipnr, 'time': to_utimestamp(to_datetime(None))}) count += 1 # Update or create attempts counter and list. set_user_attribute(self.env, user, 'failed_logins', str(attempts)) set_user_attribute(self.env, user, 'failed_logins_count', count) self.log.debug("AcctMgr:failed_count(%s): %s" % (user, count)) else: # Delete existing attempts counter and list. del_user_attribute(self.env, user, 1, 'failed_logins') del_user_attribute(self.env, user, 1, 'failed_logins_count') # Delete the lock count too. self.lock_count(user, 'reset') return count
def test_check_email_used(self): set_user_attribute(self.env, 'admin', 'email', '*****@*****.**') # Try email, that is already associated to another user. self.req.args['email'] = '*****@*****.**' self.vmod.pre_process_request(self.req, None) warnings = self.req.chrome.get('warnings') self.assertTrue(string.find(str(warnings and warnings[0] or ''), 'already in use') > 0)
def process_request(self, req): acctmgr = self.acctmgr if req.authname != 'anonymous': req.redirect(req.href.prefs('account')) action = req.args.get('action') name = req.args.get('name', '').strip() username = acctmgr.handle_username_casing(req.args.get('username', '').strip()) data = { '_dgettext': dgettext, 'acctmgr': dict(name=name, username=username), 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case') } verify_enabled = is_enabled(self.env, EmailVerificationModule) and \ EmailVerificationModule(self.env).verify_email data['verify_account_enabled'] = verify_enabled if req.method == 'POST' and action == 'create': try: # Check request and prime account on success. acctmgr.validate_account(req, True) except RegistrationError, e: # Attempt deferred translation. message = gettext(e.message) # Check for (matching number of) message arguments before # attempting string substitution. if e.msg_args and \ len(e.msg_args) == len(re.findall('%s', message)): message = message % e.msg_args chrome.add_warning(req, Markup(message)) else: if self.require_approval: set_user_attribute(self.env, username, 'approval', N_('pending')) # Notify admin user about registration pending for review. acctmgr._notify('registration_approval_required', username) chrome.add_notice(req, Markup(tag.span(Markup(_( "Your username has been registered successfully, but " "your account requires administrative approval. " "Please proceed according to local policy.")))) ) if verify_enabled: chrome.add_notice(req, Markup(tag.span(Markup(_( """Your username has been successfully registered but your account still requires activation. Please login as user %(user)s, and follow the instructions.""", user=tag.b(username))))) ) req.redirect(req.href.login()) chrome.add_notice(req, Markup(tag.span(Markup(_( """Registration has been finished successfully. You may log in as user %(user)s now.""", user=tag.b(username))))) ) req.redirect(req.href.login())
def _maybe_update_hash(self, user, password): if not get_user_attribute(self.env, 1, user, 'password_refreshed', 1): self.log.debug("Refresh password for user: %s" % user) store = self.find_user_store(user) pwstore = self.get_supporting_store('set_password') if pwstore.set_password(user, password) == True: # Account re-created according to current settings. if store and not (store.delete_user(user) == True): self.log.warn( "Failed to remove old entry for user: %s" % user) set_user_attribute(self.env, user, 'password_refreshed', 1)
def _maybe_update_hash(self, user, password): from acct_mgr.model import get_user_attribute, set_user_attribute if get_user_attribute(self.env, user, 1, 'password_refreshed', 1) == [0]: self.log.debug("Refresh password for user: %s", user) store = self.find_user_store(user) pwstore = self.get_supporting_store('set_password') if pwstore.set_password(user, password) is True: # Account re-created according to current settings. if store and not (store.delete_user(user) is True): self.log.warning("Failed to remove old entry for user: "******"%s", user) set_user_attribute(self.env, user, 'password_refreshed', 1)
def _create_user(self, req): """Set password and prime a new authenticated Trac session.""" email = req.args.get('email', '').strip() name = req.args.get('name', '').strip() username = self.handle_username_casing( req.args.get('username', '').strip()) # Create the user in the configured (primary) password store. if self.set_password(username, req.args.get('password'), None, False): # Result of a successful account creation request is a made-up # authenticated session, that a new user can refer to later on. from acct_mgr.model import prime_auth_session, set_user_attribute prime_auth_session(self.env, username) # Save attributes for the user with reference to that session ID. for attribute in ('name', 'email'): value = req.args.get(attribute) if not value: continue set_user_attribute(self.env, username, attribute, value)
def lock_count(self, user, action='get'): """Count, log and report, how often in succession user account lock conditions have been met. This is the exponent for lock time prolongation calculation too. """ key = 'lock_count' if not action == 'reset': value = get_user_attribute(self.env, user, 1, key) count = value and int(value[user][1].get(key)) or 0 if not action == 'get': # Push and create or update cached count. count += 1 set_user_attribute(self.env, user, key, count) else: # Reset/delete lock count cache. del_user_attribute(self.env, user, 1, key) count = 0 return count
def lock_count(self, user, action='get'): """Count, log and report, how often in succession user account lock conditions have been met. This is the exponent for lock time prolongation calculation too. """ key = 'lock_count' if action != 'reset': value = get_user_attribute(self.env, user, 1, key) count = value and user in value and \ int(value[user][1].get(key)) or 0 if action != 'get': # Push and create or update cached count. count += 1 set_user_attribute(self.env, user, key, count) else: # Reset/delete lock count cache. del_user_attribute(self.env, user, 1, key) count = 0 return count
def validate_registration(self, req): """Run configured registration checks and prime account on success.""" for inspector in self._register_check: inspector.validate_registration(req) username = self.handle_username_casing( req.args.get('username').strip()) name = req.args.get('name').strip() email = req.args.get('email').strip() # Create the user in the configured (primary) password store. self.set_password(username, req.args.get('password')) # Output of a successful account creation request is a made-up # authenticated session, that a new user can refer to later on. prime_auth_session(self.env, username) # Save attributes for the user with reference to that session ID. for attribute in ('name', 'email'): value = req.args.get(attribute) if not value: continue set_user_attribute(self.env, username, attribute, value)
def failed_count(self, user, ipnr=None, reset=False): """Report number of previously logged failed login attempts. Enforce login policy with regards to tracking of login attempts and user account lock behavior. Default `False` for reset value causes logging of another attempt. `None` value for reset just reads failed login attempts count. `True` value for reset triggers final log deletion. """ if not user or not user_known(self.env, user): return 0 key = 'failed_logins_count' value = get_user_attribute(self.env, user, 1, key) count = value and user in value and int(value[user][1].get(key)) or 0 if reset is None: # Report failed attempts count only. return count if not reset: # Trigger the failed attempt logger. attempts = self.get_failed_log(user) log_length = len(attempts) if log_length > self.login_attempt_max_count: # Truncate attempts list preserving most recent events. del attempts[:(log_length - self.login_attempt_max_count)] attempts.append({ 'ipnr': ipnr, 'time': to_timestamp(to_datetime(None)) }) count += 1 # Update or create attempts counter and list. set_user_attribute(self.env, user, 'failed_logins', str(attempts)) set_user_attribute(self.env, user, key, count) self.log.debug("AccountGuard.failed_count(%s) = %s" % (user, count)) else: # Delete existing attempts counter and list. del_user_attribute(self.env, user, 1, 'failed_logins') del_user_attribute(self.env, user, 1, key) # Delete the lock count too. self.lock_count(user, 'reset') return count
def test_set_user_attribute(self): set_user_attribute(self.env, 'user', 'attribute1', 'value1') cursor = self.db.cursor() cursor.execute(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """) self.assertEqual(cursor.fetchall(), [('attribute1', 'value1')]) # Setting an attribute twice will eventually just update the value. set_user_attribute(self.env, 'user', 'attribute1', 'value2') cursor.execute(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """) self.assertEqual(cursor.fetchall(), [('attribute1', 'value2')]) # All values are stored as strings internally, but the function # should take care to handle forseeable abuse gracefully. # This is a test for possible regressions of #10772. set_user_attribute(self.env, 'user', 'attribute1', 0) cursor.execute(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """) self.assertEqual(cursor.fetchall(), [('attribute1', '0')])
def test_set_user_attribute(self): set_user_attribute(self.env, 'user', 'attribute1', 'value1') with self.env.db_query as db: for name, value in db(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """): self.assertEqual(('attribute1', 'value1'), (name, value)) # Setting an attribute twice will just update the value. set_user_attribute(self.env, 'user', 'attribute1', 'value2') for name, value in db(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """): self.assertEqual(('attribute1', 'value2'), (name, value)) # All values are stored as strings internally, but the function # should take care to handle foreseeable abuse gracefully. # This is a test for possible regressions of #10772. set_user_attribute(self.env, 'user', 'attribute1', 0) for name, value in db(""" SELECT name,value FROM session_attribute WHERE sid='user' AND authenticated=1 """): self.assertEqual(('attribute1', '0'), (name, value))
def test_verify_conf_changes(self): """Registration challenges with EmailVerificationModule enabled.""" self.env = EnvironmentStub(enable=[ 'trac.*', 'acct_mgr.admin.*', 'acct_mgr.register.*', 'acct_mgr.pwhash.HtDigestHashMethod' ]) self.env.path = tempfile.mkdtemp() set_user_attribute(self.env, 'admin', 'email', '*****@*****.**') check = EmailCheck(self.env) req = self.req # Inspector provides the email text input field. old_email_input = '*****@*****.**' acct = dict(username='******', email=old_email_input, name='User') req.args.update(acct) field_res = check.render_registration_fields(req, acct) self.assertEqual(len(field_res), 2) self.assertTrue(Markup(field_res[0]).startswith('<label>Email:')) # Ensure, that old input is restored on failure. self.assertTrue(old_email_input in Markup(field_res[0])) # Ensure, that template data dict is passed unchanged. self.assertEqual(field_res[1], acct) req.args.update(dict(email='')) # 1st: Initially try with account verification disabled by setting. self.env.config.set('account-manager', 'verify_email', False) self.assertEqual(check.validate_registration(req), None) # 2nd: Again no email, but now with account verification enabled. self.env.config.set('account-manager', 'verify_email', True) self.assertRaises(RegistrationError, check.validate_registration, req) # 3th attempt: Valid email, but already registered with a username. req.args['email'] = '*****@*****.**' self.assertRaises(RegistrationError, check.validate_registration, req) # 4th attempt: Finally some valid input. req.args['email'] = '*****@*****.**' self.assertEqual(check.validate_registration(req), None)
class AccountModule(CommonTemplateProvider): """Exposes methods for users to do account management on their own. Allows users to change their password, reset their password, if they've forgotten it, even delete their account. The settings for the AccountManager module must be set in trac.ini in order to use this. Password reset procedure depends on both, ResetPwStore and an IPasswordHashMethod implementation being enabled as well. """ implements(IPreferencePanelProvider, IRequestHandler, INavigationContributor, IRequestFilter) _password_chars = string.ascii_letters + string.digits password_length = IntOption( 'account-manager', 'generated_password_length', 8, """Length of the randomly-generated passwords created when resetting the password for an account.""") reset_password = BoolOption( 'account-manager', 'reset_password', True, 'Set to False, if there is no email system setup.') def __init__(self): self.acctmgr = AccountManager(self.env) self.store = ResetPwStore(self.env) self._write_check(log=True) def _write_check(self, log=False): """Returns all configured write-enabled password stores.""" writable = self.acctmgr.get_all_supporting_stores('set_password') if writable: try: writable = writable.remove(self.store) except ValueError: # ResetPwStore is not enabled. if log: self.log.warn("ResetPwStore is disabled, therefor " "password reset won't work.") # Require at least one more write-enabled password store. if not writable and log: self.log.warn("AccountModule is disabled because no configured " "password store supports writing.") return writable # INavigationContributor methods def get_active_navigation_item(self, req): return 'reset_password' def get_navigation_items(self, req): if not self.reset_password_enabled or LoginModule(self.env).enabled: return if req.authname == 'anonymous': yield 'metanav', 'reset_password', tag.a( _("Forgot your password?"), href=req.href.reset_password()) def _reset_password_enabled(self, log=False): try: self.store.hash_method except AttributeError: return False return is_enabled(self.env, self.__class__) and \ self.reset_password and (self._write_check(log) != []) and \ is_enabled(self.env, self.store.__class__) and \ self.store.hash_method and True or False reset_password_enabled = property(_reset_password_enabled) # IPreferencePanelProvider methods def get_preference_panels(self, req): writable = self._write_check() if not writable: return if req.authname and req.authname != 'anonymous': user_store = self.acctmgr.find_user_store(req.authname) if user_store in writable: yield 'account', _("Account") def render_preference_panel(self, req, panel): data = dict(_dgettext=dgettext) data.update(self._do_account(req)) return 'prefs_account.html', data # IRequestFilter methods def pre_process_request(self, req, handler): if req.path_info == '/prefs/account' and \ not (req.authname and req.authname != 'anonymous'): # An anonymous session has no account associated with it, and # no account properies too, but general session preferences should # always be available. req.redirect(req.href.prefs()) return handler def post_process_request(self, req, template, data, content_type): if req.authname and req.authname != 'anonymous': if req.session.get('force_change_passwd', False): # Prevent authenticated usage before another password change. redirect_url = req.href.prefs('account') if req.href(req.path_info) != redirect_url: req.redirect(redirect_url) return (template, data, content_type) # IRequestHandler methods def match_request(self, req): return req.path_info == '/reset_password' and \ self._reset_password_enabled(log=True) def process_request(self, req): data = dict(_dgettext=dgettext) if req.authname and req.authname != 'anonymous': add_notice( req, Markup( tag_( "You're already logged in. If you need to change your " "password please use the %(prefs_href)s page.", prefs_href=tag.a(_("Account Preferences"), href=req.href.prefs('account'))))) data['authenticated'] = True if req.method == 'POST': self._do_reset_password(req) return 'reset_password.html', data, None def _do_account(self, req): assert (req.authname and req.authname != 'anonymous') action = req.args.get('action') delete_enabled = self.acctmgr.supports('delete_user') and \ self.acctmgr.allow_delete_account data = { 'delete_enabled': delete_enabled, 'delete_msg_confirm': _("Are you sure you want to delete your account?"), } force_change_password = req.session.get('force_change_passwd', False) if req.method == 'POST': if action == 'save': if self._do_change_password(req) and force_change_password: del req.session['force_change_passwd'] req.session.save() add_notice( req, _("Thank you for taking the time to " "update your password.")) force_change_password = False elif action == 'delete' and delete_enabled: self._do_delete(req) if force_change_password: add_warning( req, Markup( _( "You are required to change password because of a recent " "password change request. %(invitation)s", invitation=tag.b( _("Please change your password now."))))) return data def _do_change_password(self, req): username = req.authname old_password = req.args.get('old_password') if not self.acctmgr.check_password(username, old_password): if old_password: add_warning(req, _("Old password is incorrect.")) else: add_warning(req, _("Old password cannot be empty.")) return password = req.args.get('password') if not password: add_warning(req, _("Password cannot be empty.")) elif password != req.args.get('password_confirm'): add_warning(req, _("The passwords must match.")) elif password == old_password: add_warning(req, _("Password must not match old password.")) else: self.acctmgr.set_password(username, password, old_password) if req.session.get('password') is not None: # Fetch all session_attributes in case new user password is in # SessionStore, preventing overwrite by session.save(). req.session.get_session(req.authname, authenticated=True) add_notice(req, _("Password updated successfully.")) return True def _do_delete(self, req): username = req.authname password = req.args.get('password') if not password: add_warning(req, _("Password cannot be empty.")) elif not self.acctmgr.check_password(username, password): add_warning(req, _("Password is incorrect.")) else: self.acctmgr.delete_user(username) # Delete the whole session, since records in session_attribute # would get restored on logout otherwise. req.session.clear() req.session.save() req.redirect(req.href.logout()) def _do_reset_password(self, req): email = req.args.get('email') username = req.args.get('username') if not username: add_warning(req, _("Username is required.")) elif not email: add_warning(req, _("Email is required.")) else: for username_, name, email_ in self.env.get_known_users(): if username_ == username and email_ == email: self._reset_password(req, username, email) return add_warning(req, _("Email and username must match a known account.")) @property def _random_password(self): return ''.join([ random.choice(self._password_chars) for _ in xrange(self.password_length) ]) def _reset_password(self, req, username, email): acctmgr = self.acctmgr new_password = self._random_password try: self.store.set_password(username, new_password) acctmgr._notify('password_reset', username, email, new_password) # No message, if method has been called from user admin panel. if not req.path_info.startswith('/admin'): add_notice( req, _("A new password has been sent to you at " "<%(email)s>.", email=email)) except Exception, e: add_warning( req, _("Cannot reset password: %(error)s", error=', '.join(map(to_unicode, e.args)))) return if acctmgr.force_passwd_change: set_user_attribute(self.env, username, 'force_change_passwd', 1)
def process_request(self, req): acctmgr = self.acctmgr if req.authname != 'anonymous': req.redirect(req.href.prefs('account')) action = req.args.get('action') name = req.args.get('name', '') if isinstance(name, list): raise HTTPBadRequest(_("Invalid request arguments.")) name = name.strip() username = req.args.get('username', '') if isinstance(username, list): raise HTTPBadRequest(_("Invalid request arguments.")) username = acctmgr.handle_username_casing(username.strip()) data = { '_dgettext': dgettext, 'acctmgr': {'name': name, 'username': username}, 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case') } verify_enabled = self.env.is_enabled(EmailVerificationModule) and \ EmailVerificationModule(self.env).verify_email data['verify_account_enabled'] = verify_enabled if req.method == 'POST' and action == 'create': try: try: # Check request and prime account on success. acctmgr.validate_account(req, True) except NotificationError, e: chrome.add_warning(req, _( "Error raised while sending a change notification." ) + _("You should report that issue to a Trac admin.")) self.log.error( 'Unable to send registration notification: %s', exception_to_unicode(e, traceback=True)) except RegistrationError, e: chrome.add_warning(req, e) else: if self.require_approval: set_user_attribute(self.env, username, 'approval', N_('pending')) # Notify admin user about registration pending for review. try: acctmgr._notify('registration_approval_required', username) except NotificationError, e: chrome.add_warning(req, _( "Error raised while sending a change " "notification.") + _( "You should report that issue to a Trac admin.")) self.log.error( 'Unable to send admin notification: %s', exception_to_unicode(e, traceback=True)) else: chrome.add_notice(req, tag_( "Your username has been registered successfully, " "but your account requires administrative " "approval. Please proceed according to local " "policy.")) if verify_enabled: chrome.add_notice(req, tag_( "Your username has been successfully registered but " "your account still requires activation. Please " "login as user %(user)s, and follow the " "instructions.", user=tag.b(username))) req.redirect(req.href.login()) chrome.add_notice(req, tag_( "Registration has been finished successfully. " "You may log in as user %(user)s now.", user=tag.b(username))) req.redirect(req.href.login())
class AccountModule(CommonTemplateProvider): """Exposes methods for users to do account management on their own. Allows users to change their password, reset their password, if they've forgotten it, even delete their account. The settings for the AccountManager module must be set in trac.ini in order to use this. Password reset procedure depends on both, ResetPwStore and an IPasswordHashMethod implementation being enabled as well. """ implements(IPreferencePanelProvider, IRequestHandler, INavigationContributor, IRequestFilter) _password_chars = string.ascii_letters + string.digits password_length = IntOption( 'account-manager', 'generated_password_length', 8, """Length of the randomly-generated passwords created when resetting the password for an account.""") reset_password = BoolOption( 'account-manager', 'reset_password', True, 'Set to False, if there is no email system setup.') def __init__(self): self.acctmgr = AccountManager(self.env) self.store = ResetPwStore(self.env) self._write_check(log=True) def _write_check(self, log=False): writable = self.acctmgr.get_all_supporting_stores('set_password') if not writable and log: self.log.warn("AccountModule is disabled because the password " "store does not support writing.") return writable # IPreferencePanelProvider methods def get_preference_panels(self, req): writable = self._write_check() if not writable: return if req.authname and req.authname != 'anonymous': user_store = self.acctmgr.find_user_store(req.authname) if user_store in writable: yield 'account', _("Account") def render_preference_panel(self, req, panel): data = {'account': self._do_account(req), '_dgettext': dgettext, } return 'prefs_account.html', data # IRequestHandler methods def match_request(self, req): return req.path_info == '/reset_password' and \ self._reset_password_enabled(log=True) def process_request(self, req): data = {'_dgettext': dgettext, 'reset': self._do_reset_password(req) } return 'reset_password.html', data, None # IRequestFilter methods def pre_process_request(self, req, handler): if req.path_info == '/prefs/account' and \ not (req.authname and req.authname != 'anonymous'): # An anonymous session has no account associated with it, and # no account properies too, but general session preferences should # always be available. req.redirect(req.href.prefs()) return handler def post_process_request(self, req, template, data, content_type): if req.authname and req.authname != 'anonymous': if req.session.get('force_change_passwd', False): redirect_url = req.href.prefs('account') if req.href(req.path_info) != redirect_url: req.redirect(redirect_url) return (template, data, content_type) # INavigationContributor methods def get_active_navigation_item(self, req): return 'reset_password' def get_navigation_items(self, req): if not self.reset_password_enabled or LoginModule(self.env).enabled: return if req.authname == 'anonymous': yield 'metanav', 'reset_password', tag.a( _("Forgot your password?"), href=req.href.reset_password()) def _reset_password_enabled(self, log=False): return is_enabled(self.env, self.__class__) and \ self.reset_password and (self._write_check(log) != []) and \ is_enabled(self.env, self.store.__class__) and \ self.store.hash_method reset_password_enabled = property(_reset_password_enabled) def _do_account(self, req): assert(req.authname and req.authname != 'anonymous') action = req.args.get('action') delete_enabled = self.acctmgr.supports('delete_user') and \ self.acctmgr.allow_delete_account data = {'delete_enabled': delete_enabled, 'delete_msg_confirm': _( "Are you sure you want to delete your account?"), } force_change_password = req.session.get('force_change_passwd', False) if req.method == 'POST': if action == 'save': data.update(self._do_change_password(req)) if force_change_password: del(req.session['force_change_passwd']) req.session.save() chrome.add_notice(req, Markup(tag.span(tag_( "Thank you for taking the time to update your password." )))) force_change_password = False elif action == 'delete' and delete_enabled: data.update(self._do_delete(req)) else: data.update({'error': 'Invalid action'}) if force_change_password: chrome.add_warning(req, Markup(tag.span(_( "You are required to change password because of a recent " "password change request. "), tag.b(_("Please change your password now."))))) return data def _do_reset_password(self, req): if req.authname and req.authname != 'anonymous': return {'logged_in': True} if req.method != 'POST': return {} username = req.args.get('username') email = req.args.get('email') if not username: return {'error': _("Username is required")} if not email: return {'error': _("Email is required")} for username_, name, email_ in self.env.get_known_users(): if username_ == username and email_ == email: error = self._reset_password(username, email) return error and error or {'sent_to_email': email} return {'error': _( "The email and username must match a known account.")} def _reset_password(self, username, email): acctmgr = self.acctmgr new_password = self._random_password() try: self.store.set_password(username, new_password) acctmgr._notify('password_reset', username, email, new_password) except Exception, e: return {'error': ','.join(map(to_unicode, e.args))} if acctmgr.force_passwd_change: set_user_attribute(self.env, username, 'force_change_passwd', 1)
add_warning(req, msg) self.log.error('Unable to send password reset notification: %s', exception_to_unicode(e, traceback=True)) except Exception, e: add_warning(req, _("Cannot reset password: %(error)s", error=exception_to_unicode(e))) self.log.error('Unable to reset password: %s', exception_to_unicode(e, traceback=True)) return else: # No message, if method has been called from user admin panel. if not req.path_info.startswith('/admin'): add_notice(req, _("A new password has been sent to you at " "<%(email)s>.", email=email)) if acctmgr.force_passwd_change: set_user_attribute(self.env, username, 'force_change_passwd', 1) class LoginModule(auth.LoginModule, CommonTemplateProvider): """Custom login form and processing. This is woven with the trac.auth.LoginModule it inherits and overwrites. But both can't co-exist, so Trac's built-in authentication module must be disabled to use this one. """ # Trac core options, replicated here to not make them disappear by # disabling auth.LoginModule. check_ip = BoolOption('trac', 'check_auth_ip', 'false', """Whether the IP address of the user should be checked for authentication (''since 0.9'').""")
def _do_users(self, req): env = self.env perm = PermissionSystem(env) acctmgr = self.acctmgr acctmod = AccountModule(env) guard = self.guard listing_enabled = acctmgr.supports('get_users') create_enabled = acctmgr.supports('set_password') password_change_enabled = acctmgr.supports('set_password') password_reset_enabled = acctmod.reset_password_enabled delete_enabled = acctmgr.supports('delete_user') verify_enabled = acctmgr.verify_email and \ EmailVerificationModule(env).email_enabled account = dict(email=req.args.get('email', '').strip(), name=req.args.get('name', '').strip(), username=acctmgr.handle_username_casing( req.args.get('username', '').strip())) data = { '_dgettext': dgettext, 'acctmgr': account, 'email_approved': True, 'listing_enabled': listing_enabled, 'create_enabled': create_enabled, 'delete_enabled': delete_enabled, 'verify_enabled': verify_enabled, 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case'), 'password_change_enabled': password_change_enabled, 'password_reset_enabled': password_reset_enabled } if req.method == 'GET': if 'user' in req.args.iterkeys(): return self._do_acct_details(req) elif req.args.get('max_per_page'): return self._do_db_cleanup(req) if req.method == 'POST': email_approved = req.args.get('email_approved') # Preserve selection during a series of requests. data['email_approved'] = email_approved if req.args.get('add'): # Add new user account. if create_enabled: # Check request and prime account on success. try: acctmgr.validate_registration(req) # Account email approval for authoritative action. if verify_enabled and email_approved and \ account['email']: set_user_attribute(env, account['username'], 'email_verification_sent_to', account['email']) # User editor form clean-up. data['acctmgr'] = {} except RegistrationError, e: # Attempt deferred translation. message = gettext(e.message) # Check for (matching number of) message arguments # before attempting string substitution. if e.msg_args and \ len(e.msg_args) == len(re.findall('%s', message)): message = message % e.msg_args data['editor_error'] = Markup(message) else: data['editor_error'] = _( "The password store does not support creating users.") elif req.args.get('reset') and req.args.get('sel'): # Password reset for one or more accounts. if password_reset_enabled: sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for username, name, email in env.get_known_users(): if username in sel: acctmod._reset_password(username, email) else: data['deletion_error'] = _( "The password reset procedure is not enabled.") elif req.args.get('remove') and req.args.get('sel'): # Delete one or more accounts. if delete_enabled: sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for account in sel: acctmgr.delete_user(account) else: data['deletion_error'] = _( "The password store does not support deleting users.") elif req.args.get('change'): # Change attributes and or password of existing user account. attributes = { 'email': _("Email Address"), 'name': _("Pre-/Surname (Nickname)"), 'password': _("Password") } data['success'] = [] error = TracError('') username = acctmgr.handle_username_casing( req.args.get('username').strip()) try: if not username: error.account = {'username': username} error.message = _("Username cannot be empty.") raise error if not acctmgr.has_user(username): error.account = {'username': username} error.message = _("Unknown user %(user)s.", user=username) raise error password = req.args.get('password') if password and (password.strip() != ''): if password_change_enabled: if password != req.args.get('password_confirm'): error.message = _("The passwords must match.") raise error acctmgr.set_password(username, password) data['success'].append(attributes.get('password')) else: data['editor_error'] = _( """The password store does not support changing passwords. """) for attribute in ('name', 'email'): value = req.args.get(attribute, '').strip() if value: set_user_attribute(env, username, attribute, value) data['success'].append(attributes.get(attribute)) # Account email approval for authoritative action. if attribute == 'email' and verify_enabled and \ email_approved: set_user_attribute( env, username, 'email_verification_sent_to', value) # User editor form clean-up on success. data['acctmgr'] = {} except TracError, e: data['editor_error'] = e.message data['acctmgr'] = getattr(e, 'account', '')
add_warning(req, msg) self.log.error("Unable to send password reset notification: %s", exception_to_unicode(e, traceback=True)) except Exception, e: add_warning(req, _("Cannot reset password: %(error)s", error=exception_to_unicode(e))) self.log.error("Unable to reset password: %s", exception_to_unicode(e, traceback=True)) return else: # No message, if method has been called from user admin panel. if not req.path_info.startswith('/admin'): add_notice(req, _("A new password has been sent to you at " "<%(email)s>.", email=email)) if acctmgr.force_passwd_change: set_user_attribute(self.env, username, 'force_change_passwd', 1) class LoginModule(auth.LoginModule, CommonTemplateProvider): """Custom login form and processing. This is woven with the trac.auth.LoginModule it inherits and overwrites. But both can't co-exist, so Trac's built-in authentication module must be disabled to use this one. """ # Trac core options, replicated here to not make them disappear by # disabling auth.LoginModule. check_ip = BoolOption('trac', 'check_auth_ip', 'false', """Whether the IP address of the user should be checked for authentication (''since 0.9'').""")
def _do_users(self, req): env = self.env perm = PermissionSystem(env) acctmgr = self.acctmgr acctmod = AccountModule(env) guard = self.guard listing_enabled = acctmgr.supports('get_users') create_enabled = acctmgr.supports('set_password') password_change_enabled = acctmgr.supports('set_password') password_reset_enabled = acctmod.reset_password_enabled delete_enabled = acctmgr.supports('delete_user') verify_enabled = acctmgr.verify_email and \ EmailVerificationModule(env).email_enabled account = dict(email=req.args.get('email', '').strip(), name=req.args.get('name', '').strip(), username=acctmgr.handle_username_casing( req.args.get('username', '').strip())) data = { '_dgettext': dgettext, 'acctmgr': account, 'email_approved': True, 'listing_enabled': listing_enabled, 'create_enabled': create_enabled, 'delete_enabled': delete_enabled, 'verify_enabled': verify_enabled, 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case'), 'password_change_enabled': password_change_enabled, 'password_reset_enabled': password_reset_enabled } if req.method == 'GET': if 'user' in req.args.iterkeys(): return self._do_acct_details(req) elif req.args.get('max_per_page'): return self._do_db_cleanup(req) if req.method == 'POST': email_approved = req.args.get('email_approved') # Preserve selection during a series of requests. data['email_approved'] = email_approved if req.args.get('add'): # Add new user account. if create_enabled: # Check request and prime account on success. try: acctmgr.validate_registration(req) # Account email approval for authoritative action. if verify_enabled and email_approved and \ account['email']: set_user_attribute(env, account['username'], 'email_verification_sent_to', account['email']) # User editor form clean-up. data['acctmgr'] = {} except RegistrationError, e: # Attempt deferred translation. message = gettext(e.message) # Check for (matching number of) message arguments # before attempting string substitution. if e.msg_args and \ len(e.msg_args) == len(re.findall('%s', message)): message = message % e.msg_args data['editor_error'] = Markup(message) else: data['editor_error'] = _( "The password store does not support creating users.") elif req.args.get('reset') and req.args.get('sel'): # Password reset for one or more accounts. if password_reset_enabled: sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for username, name, email in env.get_known_users(): if username in sel: acctmod._reset_password(username, email) else: data['deletion_error'] = _( "The password reset procedure is not enabled.") elif req.args.get('remove') and req.args.get('sel'): # Delete one or more accounts. if delete_enabled: sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for account in sel: acctmgr.delete_user(account) else: data['deletion_error'] = _( "The password store does not support deleting users.") elif req.args.get('change'): # Change attributes and or password of existing user account. attributes = { 'email': _("Email Address"), 'name': _("Pre-/Surname (Nickname)"), 'password': _("Password") } data['success'] = [] error = TracError('') username = acctmgr.handle_username_casing( req.args.get('username').strip()) try: if not username: error.account = {'username' : username} error.message = _("Username cannot be empty.") raise error if not acctmgr.has_user(username): error.account = {'username' : username} error.message = _("Unknown user %(user)s.", user=username) raise error password = req.args.get('password') if password and (password.strip() != ''): if password_change_enabled: if password != req.args.get('password_confirm'): error.message = _("The passwords must match.") raise error acctmgr.set_password(username, password) data['success'].append(attributes.get('password')) else: data['editor_error'] = _( """The password store does not support changing passwords. """) for attribute in ('name', 'email'): value = req.args.get(attribute, '').strip() if value: set_user_attribute(env, username, attribute, value) data['success'].append(attributes.get(attribute)) # Account email approval for authoritative action. if attribute == 'email' and verify_enabled and \ email_approved: set_user_attribute(env, username, 'email_verification_sent_to', value) # User editor form clean-up on success. data['acctmgr'] = {} except TracError, e: data['editor_error'] = e.message data['acctmgr'] = getattr(e, 'account', '')
def process_request(self, req): acctmgr = self.acctmgr if req.authname != 'anonymous': req.redirect(req.href.prefs('account')) action = req.args.get('action') name = req.args.get('name', '').strip() username = acctmgr.handle_username_casing( req.args.get('username', '').strip()) data = { '_dgettext': dgettext, 'acctmgr': dict(name=name, username=username), 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case') } verify_enabled = is_enabled(self.env, EmailVerificationModule) and \ EmailVerificationModule(self.env).verify_email data['verify_account_enabled'] = verify_enabled if req.method == 'POST' and action == 'create': try: # Check request and prime account on success. acctmgr.validate_account(req, True) except RegistrationError, e: # Attempt deferred translation. message = gettext(e.message) # Check for (matching number of) message arguments before # attempting string substitution. if e.msg_args and \ len(e.msg_args) == len(re.findall('%s', message)): message = message % e.msg_args chrome.add_warning(req, Markup(message)) else: if self.require_approval: set_user_attribute(self.env, username, 'approval', N_('pending')) # Notify admin user about registration pending for review. acctmgr._notify('registration_approval_required', username) chrome.add_notice( req, Markup( tag.span( Markup( _("Your username has been registered successfully, but " "your account requires administrative approval. " "Please proceed according to local policy." ))))) if verify_enabled: chrome.add_notice( req, Markup( tag.span( Markup( _("""Your username has been successfully registered but your account still requires activation. Please login as user %(user)s, and follow the instructions.""", user=tag.b(username)))))) req.redirect(req.href.login()) chrome.add_notice( req, Markup( tag.span( Markup( _("""Registration has been finished successfully. You may log in as user %(user)s now.""", user=tag.b(username)))))) req.redirect(req.href.login())