def _remote_user(self, req): """The real authentication using configured providers and stores.""" user = req.args.get('user') self.env.log.debug( "LoginModule._remote_user: Authentication attempted for '%s'" % user) password = req.args.get('password') if not user: return None acctmgr = AccountManager(self.env) acctmod = AccountModule(self.env) if acctmod.reset_password_enabled == True: reset_store = acctmod.store else: reset_store = None if acctmgr.check_password(user, password) == True: if reset_store: # Purge any temporary password set for this user before, # to avoid DOS by continuously triggered resets from # a malicious third party. if reset_store.delete_user(user) == True and \ 'PASSWORD_RESET' not in req.environ: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( """ DELETE FROM session_attribute WHERE sid=%s AND name='force_change_passwd' AND authenticated=1 """, (user, )) db.commit() return user # Alternative authentication provided by password reset procedure elif reset_store: if reset_store.check_password(user, password) == True: # Lock, required to prevent another authentication # (spawned by `set_password()`) from possibly deleting # a 'force_change_passwd' db entry for this user. req.environ['PASSWORD_RESET'] = user # Change password to temporary password from reset procedure acctmgr.set_password(user, password) return user return None
def _remote_user(self, req): """The real authentication using configured providers and stores.""" user = req.args.get('user') self.env.log.debug("LoginModule._remote_user: Authentication attempted for '%s'" % user) password = req.args.get('password') if not user or not password: return None acctmgr = AccountManager(self.env) acctmod = AccountModule(self.env) if acctmod.reset_password_enabled == True: reset_store = acctmod.store else: reset_store = None if acctmgr.check_password(user, password) == True: if reset_store: # Purge any temporary password set for this user before, # to avoid DOS by continuously triggered resets from # a malicious third party. if reset_store.delete_user(user) == True and \ 'PASSWORD_RESET' not in req.environ: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" DELETE FROM session_attribute WHERE sid=%s AND name='force_change_passwd' AND authenticated=1 """, (user,)) db.commit() return user # Alternative authentication provided by password reset procedure elif reset_store: if reset_store.check_password(user, password) == True: # Lock, required to prevent another authentication # (spawned by `set_password()`) from possibly deleting # a 'force_change_passwd' db entry for this user. req.environ['PASSWORD_RESET'] = user # Change password to temporary password from reset procedure acctmgr.set_password(user, password) return user return None
def _create_user(req, env, check_permissions=True): acctmgr = AccountManager(env) username = acctmgr.handle_username_casing(req.args.get('username').strip()) name = req.args.get('name') email = req.args.get('email').strip() account = { 'username': username, 'name': name, 'email': email, } error = TracError('') error.account = account if not username: error.message = _("Username cannot be empty.") raise error # Prohibit some user names that are important for Trac and therefor # reserved, even if they're not in the permission store for some reason. if username in ['authenticated', 'anonymous']: error.message = _("Username %s is not allowed.") % username raise error # NOTE: A user may exist in the password store but not in the permission # store. I.e. this happens, when the user (from the password store) # never logged in into Trac. So we have to perform this test here # and cannot just check for the user being in the permission store. # And obfuscate whether an existing user or group name # was responsible for rejection of this user name. if acctmgr.has_user(username): error.message = _( "Another account or group named %s already exists.") % username raise error # Check whether there is also a user or a group with that name. if check_permissions: # NOTE: We can't use 'get_user_permissions(username)' here # as this always returns a list - even if the user doesn't exist. # In this case the permissions of "anonymous" are returned. # # Also note that we can't simply compare the result of # 'get_user_permissions(username)' to some known set of permission, # i.e. "get_user_permissions('authenticated') as this is always # false when 'username' is the name of an existing permission group. # # And again obfuscate whether an existing user or group name # was responsible for rejection of this username. for (perm_user, perm_action) in \ perm.PermissionSystem(env).get_all_permissions(): if perm_user == username: error.message = _( "Another account or group named %s already exists.") \ % username raise error # Always exclude some special characters, i.e. # ':' can't be used in HtPasswdStore # '[' and ']' can't be used in SvnServePasswordStore blacklist = acctmgr.username_char_blacklist if containsAny(username, blacklist): pretty_blacklist = '' for c in blacklist: if pretty_blacklist == '': pretty_blacklist = tag(' \'', tag.b(c), '\'') else: pretty_blacklist = tag(pretty_blacklist, ', \'', tag.b(c), '\'') error.message = tag( _("The username must not contain any of these characters:"), pretty_blacklist) raise error # Validation of username passed. password = req.args.get('password') if not password: error.message = _("Password cannot be empty.") raise error if password != req.args.get('password_confirm'): error.message = _("The passwords must match.") raise error # Validation of password passed. if if_enabled(EmailVerificationModule) and acctmgr.verify_email: if not email: error.message = _("You must specify a valid email address.") raise error elif not re.match('^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', email, re.IGNORECASE): error.message = _("""The email address specified appears to be invalid. Please specify a valid email address. """) raise error elif acctmgr.has_email(email): error.message = _("""The email address specified is already in use. Please specify a different one. """) raise error # Validation of email address passed. acctmgr.set_password(username, password) # INSERT new sid, needed as foreign key in some db schemata later on, # at least for PostgreSQL. db = env.get_db_cnx() cursor = db.cursor() cursor.execute( """ SELECT COUNT(*) FROM session WHERE sid=%s """, (username, )) exists = cursor.fetchone() if not exists: cursor.execute( """ INSERT INTO session (sid,authenticated,last_visit) VALUES (%s,0,0) """, (username, )) for attribute in ('name', 'email'): value = req.args.get(attribute) if not value: continue set_user_attribute(env, username, attribute, value)
class AccountManagerAdminPage(Component): implements(IAdminPanelProvider, ITemplateProvider) def __init__(self): self.account_manager = AccountManager(self.env) # IAdminPageProvider def get_admin_panels(self, req): if req.perm.has_permission('TRAC_ADMIN'): yield ('accounts', 'Accounts', 'config', 'Configuration') yield ('accounts', 'Accounts', 'users', 'Users') def render_admin_panel(self, req, cat, page, path_info): if page == 'config': return self._do_config(req) elif page == 'users': return self._do_users(req) def _do_config(self, req): stores = StoreOrder(stores=self.account_manager.stores, list=self.account_manager.password_store) if req.method == 'POST': _setorder(req, stores) self.config.set('account-manager', 'password_store', ','.join(stores.get_enabled_store_names())) for store in stores.get_all_stores(): for attr, option in _getoptions(store): newvalue = req.args.get('%s.%s' % (store.__class__.__name__, attr)) self.log.debug("%s.%s: %s" % (store.__class__.__name__, attr, newvalue)) if newvalue is not None: self.config.set(option.section, option.name, newvalue) self.config.save() self.config.set('account-manager', 'force_passwd_change', req.args.get('force_passwd_change')) self.config.save() sections = [] for store in self.account_manager.stores: options = [] for attr, option in _getoptions(store): opt_val = option.__get__(store, store) opt_val = isinstance(opt_val, Component) and \ opt_val.__class__.__name__ or opt_val options.append( {'label': attr, 'name': '%s.%s' % (store.__class__.__name__, attr), 'value': opt_val, }) continue sections.append( {'name': store.__class__.__name__, 'classname': store.__class__.__name__, 'order': stores[store], 'options' : options, }) continue sections = sorted(sections, key=lambda i: i['name']) numstores = range(0, stores.numstores() + 1) data = {'sections': sections, 'numstores': numstores, 'force_passwd_change': self.account_manager.force_passwd_change} return 'admin_accountsconfig.html', data def _do_users(self, req): perm = PermissionSystem(self.env) listing_enabled = self.account_manager.supports('get_users') create_enabled = self.account_manager.supports('set_password') password_change_enabled = self.account_manager.supports('set_password') delete_enabled = self.account_manager.supports('delete_user') data = { 'listing_enabled': listing_enabled, 'create_enabled': create_enabled, 'delete_enabled': delete_enabled, 'password_change_enabled': password_change_enabled, 'acctmgr' : { 'username' : None, 'name' : None, 'email' : None, } } if req.method == 'POST': if req.args.get('add'): if create_enabled: try: _create_user(req, self.env, check_permissions=False) except TracError, e: data['registration_error'] = e.message data['acctmgr'] = e.acctmgr else: data['registration_error'] = 'The password store does ' \ 'not support creating users' elif req.args.get('remove'): if delete_enabled: sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for account in sel: self.account_manager.delete_user(account) else: data['deletion_error'] = 'The password store does not ' \ 'support deleting users' elif req.args.get('change'): if password_change_enabled: try: user = req.args.get('change_user') acctmgr = { 'change_username' : user, } error = TracError('') error.acctmgr = acctmgr if not user: error.message = 'Username cannot be empty.' raise error password = req.args.get('change_password') if not password: error.message = 'Password cannot be empty.' raise error if password != req.args.get('change_password_confirm'): error.message = 'The passwords must match.' raise error self.account_manager.set_password(user, password) except TracError, e: data['password_change_error'] = e.message data['acctmgr'] = getattr(e, 'acctmgr', '') else: data['password_change_error'] = 'The password store does not ' \ 'support changing passwords'
class AccountManagerAdminPage(Component): implements(IAdminPanelProvider, ITemplateProvider) def __init__(self): self.account_manager = AccountManager(self.env) # IAdminPageProvider def get_admin_panels(self, req): if req.perm.has_permission("TRAC_ADMIN"): yield ("accounts", "Accounts", "config", "Configuration") yield ("accounts", "Accounts", "users", "Users") def render_admin_panel(self, req, cat, page, path_info): if page == "config": return self._do_config(req) elif page == "users": return self._do_users(req) def _do_config(self, req): stores = StoreOrder(stores=self.account_manager.stores, list=self.account_manager.password_store) if req.method == "POST": _setorder(req, stores) self.config.set("account-manager", "password_store", ",".join(stores.get_enabled_store_names())) for store in stores.get_all_stores(): for attr, option in _getoptions(store): newvalue = req.args.get("%s.%s" % (store.__class__.__name__, attr)) self.log.debug("%s.%s: %s" % (store.__class__.__name__, attr, newvalue)) if newvalue is not None: self.config.set(option.section, option.name, newvalue) self.config.save() self.config.set("account-manager", "force_passwd_change", req.args.get("force_passwd_change")) self.config.set("account-manager", "persistent_sessions", req.args.get("persistent_sessions")) self.config.save() sections = [] for store in self.account_manager.stores: options = [] for attr, option in _getoptions(store): opt_val = option.__get__(store, store) opt_val = isinstance(opt_val, Component) and opt_val.__class__.__name__ or opt_val options.append({"label": attr, "name": "%s.%s" % (store.__class__.__name__, attr), "value": opt_val}) continue sections.append( { "name": store.__class__.__name__, "classname": store.__class__.__name__, "order": stores[store], "options": options, } ) continue sections = sorted(sections, key=lambda i: i["name"]) numstores = range(0, stores.numstores() + 1) data = { "sections": sections, "numstores": numstores, "force_passwd_change": self.account_manager.force_passwd_change, "persistent_sessions": self.account_manager.persistent_sessions, } return "admin_accountsconfig.html", data def _do_users(self, req): perm = PermissionSystem(self.env) listing_enabled = self.account_manager.supports("get_users") create_enabled = self.account_manager.supports("set_password") password_change_enabled = self.account_manager.supports("set_password") delete_enabled = self.account_manager.supports("delete_user") data = { "listing_enabled": listing_enabled, "create_enabled": create_enabled, "delete_enabled": delete_enabled, "password_change_enabled": password_change_enabled, "acctmgr": {"username": None, "name": None, "email": None}, } if req.method == "POST": if req.args.get("add"): if create_enabled: try: _create_user(req, self.env, check_permissions=False) except TracError, e: data["registration_error"] = e.message data["acctmgr"] = e.acctmgr else: data["registration_error"] = "The password store does " "not support creating users" elif req.args.get("remove"): if delete_enabled: sel = req.args.get("sel") sel = isinstance(sel, list) and sel or [sel] for account in sel: self.account_manager.delete_user(account) else: data["deletion_error"] = "The password store does not " "support deleting users" elif req.args.get("change"): if password_change_enabled: try: user = req.args.get("change_user") acctmgr = {"change_username": user} error = TracError("") error.acctmgr = acctmgr if not user: error.message = "Username cannot be empty." raise error password = req.args.get("change_password") if not password: error.message = "Password cannot be empty." raise error if password != req.args.get("change_password_confirm"): error.message = "The passwords must match." raise error self.account_manager.set_password(user, password) except TracError, e: data["password_change_error"] = e.message data["acctmgr"] = getattr(e, "acctmgr", "") else: data["password_change_error"] = "The password store does not " "support changing passwords"
#!/usr/bin/python import os import sys try: from trac.env import Environment from acct_mgr.api import AccountManager env = Environment(sys.argv[1]) user, passwd = sys.argv[2], sys.argv[3] account_manager = AccountManager(env) account_manager.set_password(user, passwd) except Exception, e: print "Oops !! %s" % e print "Usage: python set_password <env> <user> <password>"
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)
#!/usr/bin/env python import os import sys from trac.env import Environment from acct_mgr.api import AccountManager env = Environment(sys.argv[1]) mgr = AccountManager(env) mgr.set_password(sys.argv[2], sys.argv[3])
def _create_user(req, env, check_permissions=True): acctmgr = AccountManager(env) username = acctmgr.handle_username_casing( req.args.get('username').strip()) name = req.args.get('name') email = req.args.get('email').strip() account = {'username' : username, 'name' : name, 'email' : email, } error = TracError('') error.account = account if not username: error.message = _("Username cannot be empty.") raise error # Prohibit some user names that are important for Trac and therefor # reserved, even if they're not in the permission store for some reason. if username in ['authenticated', 'anonymous']: error.message = _("Username %s is not allowed.") % username raise error # NOTE: A user may exist in the password store but not in the permission # store. I.e. this happens, when the user (from the password store) # never logged in into Trac. So we have to perform this test here # and cannot just check for the user being in the permission store. # And obfuscate whether an existing user or group name # was responsible for rejection of this user name. if acctmgr.has_user(username): error.message = _( "Another account or group named %s already exists.") % username raise error # Check whether there is also a user or a group with that name. if check_permissions: # NOTE: We can't use 'get_user_permissions(username)' here # as this always returns a list - even if the user doesn't exist. # In this case the permissions of "anonymous" are returned. # # Also note that we can't simply compare the result of # 'get_user_permissions(username)' to some known set of permission, # i.e. "get_user_permissions('authenticated') as this is always # false when 'username' is the name of an existing permission group. # # And again obfuscate whether an existing user or group name # was responsible for rejection of this username. for (perm_user, perm_action) in \ perm.PermissionSystem(env).get_all_permissions(): if perm_user == username: error.message = _( "Another account or group named %s already exists.") \ % username raise error # Always exclude some special characters, i.e. # ':' can't be used in HtPasswdStore # '[' and ']' can't be used in SvnServePasswordStore blacklist = acctmgr.username_char_blacklist if containsAny(username, blacklist): pretty_blacklist = '' for c in blacklist: if pretty_blacklist == '': pretty_blacklist = tag(' \'', tag.b(c), '\'') else: pretty_blacklist = tag(pretty_blacklist, ', \'', tag.b(c), '\'') error.message = tag(_( "The username must not contain any of these characters:"), pretty_blacklist) raise error # Validation of username passed. password = req.args.get('password') if not password: error.message = _("Password cannot be empty.") raise error if password != req.args.get('password_confirm'): error.message = _("The passwords must match.") raise error # Validation of password passed. if if_enabled(EmailVerificationModule) and acctmgr.verify_email: if not email: error.message = _("You must specify a valid email address.") raise error elif not re.match('^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', email, re.IGNORECASE): error.message = _("""The email address specified appears to be invalid. Please specify a valid email address. """) raise error elif acctmgr.has_email(email): error.message = _("""The email address specified is already in use. Please specify a different one. """) raise error # Validation of email address passed. acctmgr.set_password(username, password) # INSERT new sid, needed as foreign key in some db schemata later on, # at least for PostgreSQL. db = env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT COUNT(*) FROM session WHERE sid=%s """, (username,)) exists = cursor.fetchone() if not exists: cursor.execute(""" INSERT INTO session (sid,authenticated,last_visit) VALUES (%s,0,0) """, (username,)) for attribute in ('name', 'email'): value = req.args.get(attribute) if not value: continue set_user_attribute(env, username, attribute, value)
class AccountManagerTestCase(_BaseTestCase): def setUp(self): _BaseTestCase.setUp(self) self.mgr = AccountManager(self.env) self.store = SessionStore(self.env) self.store.set_password('user', 'passwd') args = dict(username='******', name='', email='') incookie = Cookie() incookie['trac_session'] = '123456' self.req = Mock(authname='', args=args, authenticated=True, base_path='/', callbacks=dict(), href=Mock(prefs=lambda x: None), incookie=incookie, outcookie=Cookie(), redirect=lambda x: None) self.req.path_info = '/' # Tests def test_set_password(self): # Can't work without at least one password store. self.assertRaises(TracError, self.mgr.set_password, 'user', 'passwd') self.env.config.set( 'account-manager', 'password_store', 'SessionStore') self.mgr.set_password('user', 'passwd') # Refuse to overwrite existing credetialy, if requested. self.assertRaises(TracError, self.mgr.set_password, 'user', 'passwd', overwrite=False) def test_approval_admin_keep_perm(self): self.perm.grant_permission('admin', 'ACCTMGR_ADMIN') # Some elevated permission action. action = 'USER_VIEW' self.assertFalse(action in PermissionCache(self.env)) req = self.req req.perm = PermissionCache(self.env, 'admin') req.session = Session(self.env, req) req.session.save() self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) # Mock an authenticated request with account approval pending. req.session['approval'] = 'pending' req.session.save() # Don't touch admin user requests. self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) def test_approval_user_strip_perm(self): # Some elevated permission action. action = 'USER_VIEW' self.assertFalse(action in PermissionCache(self.env)) self.perm.grant_permission('user', action) req = self.req req.perm = PermissionCache(self.env, 'user') req.session = Session(self.env, req) req.session.save() self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) # Mock an authenticated request with account approval pending. req.session['approval'] = 'pending' req.session.save() # Remove elevated permission, if account approval is pending. self.mgr.pre_process_request(req, None) self.assertFalse(action in req.perm) def test_maybe_update_hash(self): # Configure another, primary password store. self.env.config.set('account-manager', 'password_store', 'HtDigestStore, SessionStore') self.env.config.set('account-manager', 'htdigest_file', '.htdigest') cursor = self.db.cursor() cursor.execute(""" INSERT INTO session_attribute (sid,authenticated,name,value) VALUES (%s,%s,%s,%s) """, ('user', 1, 'password_refreshed', '1')) # Refresh not happening due to 'password_refreshed' attribute. self.mgr._maybe_update_hash('user', 'passwd') cursor.execute(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password' """) self.assertNotEqual(cursor.fetchall(), []) cursor.execute(""" DELETE FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password_refreshed' """) # Refresh (and effectively migrate) user credentials. self.mgr._maybe_update_hash('user', 'passwd') cursor.execute(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password' """) self.assertEqual(cursor.fetchall(), []) cursor.execute(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password_refreshed' """) self.assertEqual(cursor.fetchall(), [('1',)])
class AccountManagerTestCase(_BaseTestCase): def setUp(self): _BaseTestCase.setUp(self) self.mgr = AccountManager(self.env) self.store = SessionStore(self.env) self.store.set_password('user', 'passwd') args = dict(username='******', name='', email='') incookie = Cookie() incookie['trac_session'] = '123456' self.req = Mock(authname='', args=args, authenticated=True, base_path='/', callbacks=dict(), chrome={'warnings': [], 'notices': []}, href=Mock(prefs=lambda x: None), incookie=incookie, outcookie=Cookie(), redirect=lambda x: None) self.req.path_info = '/' # Tests def test_set_password(self): # Can't work without at least one password store. self.assertRaises(TracError, self.mgr.set_password, 'user', 'passwd') self.env.config.set( 'account-manager', 'password_store', 'SessionStore') self.mgr.set_password('user', 'passwd') # Refuse to overwrite existing credentials, if requested. self.assertRaises(TracError, self.mgr.set_password, 'user', 'passwd', overwrite=False) def test_approval_admin_keep_perm(self): self.perm.grant_permission('admin', 'ACCTMGR_ADMIN') # Some elevated permission action. action = 'USER_VIEW' self.assertFalse(action in PermissionCache(self.env)) req = self.req req.perm = PermissionCache(self.env, 'admin') req.session = Session(self.env, req) req.session.save() self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) # Mock an authenticated request with account approval pending. req.session['approval'] = 'pending' req.session.save() # Don't touch admin user requests. self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) def test_approval_user_strip_perm(self): # Some elevated permission action. action = 'USER_VIEW' self.assertFalse(action in PermissionCache(self.env)) self.perm.grant_permission('user', action) req = self.req req.perm = PermissionCache(self.env, 'user') req.session = Session(self.env, req) req.session.save() self.mgr.pre_process_request(req, None) self.assertTrue(action in req.perm) # Mock an authenticated request with account approval pending. req.session['approval'] = 'pending' req.session.save() # Remove elevated permission, if account approval is pending. self.mgr.pre_process_request(req, None) self.assertFalse(action in req.perm) def test_maybe_update_hash(self): # Configure another, primary password store. self.env.config.set('account-manager', 'password_store', 'HtDigestStore, SessionStore') self.env.config.set('account-manager', 'htdigest_file', '.htdigest') self.env.db_transaction(""" INSERT INTO session_attribute (sid,authenticated,name,value) VALUES (%s,%s,%s,%s) """, ('user', 1, 'password_refreshed', '1')) # Refresh not happening due to 'password_refreshed' attribute. self.mgr._maybe_update_hash('user', 'passwd') for _, in self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password' """): break else: self.fail("Session attribute 'password' not found.") self.env.db_transaction(""" DELETE FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password_refreshed' """) # Refresh (and effectively migrate) user credentials. self.mgr._maybe_update_hash('user', 'passwd') for _, in self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password' """): self.fail("Session attribute 'password' should not be found.") for value, in self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='user' AND authenticated=1 AND name='password_refreshed' """): self.assertEqual('1', value) break else: self.fail("Session attribute 'password_refreshed' not found.")