def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'acct_mgr.guard.*']) self.env.path = tempfile.mkdtemp() self.env.config.set('account-manager', 'login_attempt_max_count', 1) self.user = '******' self.session = self._create_session(self.user) self.guard = AccountGuard(self.env)
def process_request(self, req): env = self.env if req.path_info.startswith('/login') and req.authname == 'anonymous': guard = AccountGuard(env) try: referer = self._referer(req) except AttributeError: # Fallback for Trac 0.11 compatibility. referer = req.get_header('Referer') # Steer clear of requests going nowhere or loop to self if referer is None or \ referer.startswith(str(req.abs_href()) + '/login'): referer = req.abs_href() data = { '_dgettext': dgettext, 'login_opt_list': self.login_opt_list == True, 'persistent_sessions': AccountManager(env).persistent_sessions, 'referer': referer, 'registration_enabled': RegistrationModule(env).enabled, 'reset_password_enabled': AccountModule(env).reset_password_enabled } if req.method == 'POST': self.log.debug('user_locked: ' + \ str(req.args.get('user_locked', False))) if not req.args.get('user_locked') is True: # TRANSLATOR: Intentionally obfuscated login error data['login_error'] = _("Invalid username or password") else: f_user = req.args.get('user') release_time = guard.pretty_release_time(req, f_user) if not release_time is None: data['login_error'] = _( """Account locked, please try again after %(release_time)s """, release_time=release_time) else: data['login_error'] = _("Account locked") return 'login.html', data, None else: n_plural = req.args.get('failed_logins') if n_plural > 0: chrome.add_warning( req, Markup( tag.span( tag( ngettext( "Login after %(attempts)s failed attempt", "Login after %(attempts)s failed attempts", n_plural, attempts=n_plural))))) return auth.LoginModule.process_request(self, req)
def process_request(self, req): env = self.env if req.path_info.startswith('/login') and req.authname == 'anonymous': guard = AccountGuard(env) try: referer = self._referer(req) except AttributeError: # Fallback for Trac 0.11 compatibility. referer = req.get_header('Referer') # Steer clear of requests going nowhere or loop to self if referer is None or \ referer.startswith(str(req.abs_href()) + '/login'): referer = req.abs_href() data = { '_dgettext': dgettext, 'login_opt_list': self.login_opt_list == True, 'persistent_sessions': AccountManager(env ).persistent_sessions, 'referer': referer, 'registration_enabled': RegistrationModule(env).enabled, 'reset_password_enabled': AccountModule(env ).reset_password_enabled } if req.method == 'POST': self.log.debug('user_locked: ' + \ str(req.args.get('user_locked', False))) if not req.args.get('user_locked') is True: # TRANSLATOR: Intentionally obfuscated login error data['login_error'] = _("Invalid username or password") else: f_user = req.args.get('user') release_time = guard.pretty_release_time(req, f_user) if not release_time is None: data['login_error'] = _( """Account locked, please try again after %(release_time)s """, release_time=release_time) else: data['login_error'] = _("Account locked") return 'login.html', data, None else: n_plural=req.args.get('failed_logins') if n_plural > 0: chrome.add_warning(req, Markup(tag.span(tag(ngettext( "Login after %(attempts)s failed attempt", "Login after %(attempts)s failed attempts", n_plural, attempts=n_plural ))))) return auth.LoginModule.process_request(self, req)
def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login'): user = self._remote_user(req) acctmgr = AccountManager(self.env) guard = AccountGuard(self.env) if guard.login_attempt_max_count > 0: if user is None: if req.args.get('user_locked') is None: # get user for failed authentication attempt f_user = req.args.get('user') req.args['user_locked'] = False if acctmgr.user_known(f_user) is True: if guard.user_locked(f_user) is False: # log current failed login attempt guard.failed_count(f_user, req.remote_addr) if guard.user_locked(f_user) is True: # step up lock time prolongation # only when just triggering the lock guard.lock_count(f_user, 'up') req.args['user_locked'] = True else: # enforce lock req.args['user_locked'] = True else: if guard.user_locked(user) is not False: req.args['user_locked'] = True # void successful login as long as user is locked user = None else: req.args['user_locked'] = False if req.args.get('failed_logins') is None: # Reset failed login attempts counter req.args['failed_logins'] = guard.failed_count( user, reset=True) if 'REMOTE_USER' not in req.environ: req.environ['REMOTE_USER'] = user return auth.LoginModule.authenticate(self, req)
def fetch_user_data(env, req): acctmgr = AccountManager(env) guard = AccountGuard(env) accounts = {} for username in acctmgr.get_users(): if req.perm.has_permission('ACCTMGR_USER_ADMIN'): url = req.href.admin('accounts', 'users', user=username) else: url = None accounts[username] = {'username': username, 'review_url': url} if guard.user_locked(username): accounts[username]['locked'] = True t_lock = guard.lock_time(username) if t_lock > 0: t_release = guard.pretty_release_time(req, username) accounts[username]['release_hint'] = _( "Locked until %(t_release)s", t_release=t_release) for acct, status in get_user_attribute(env, username=None, authenticated=None).iteritems(): account = accounts.get(acct) if account is not None and 1 in status: # Only use attributes related to authenticated # accounts. account['name'] = status[1].get('name') account['email'] = status[1].get('email') if account['email']: account['email'] = Chrome(env).format_author(req, account['email']) ts_seen = last_seen(env) if ts_seen is not None: for username, last_visit in ts_seen: account = accounts.get(username) if account and last_visit: account['last_visit'] = to_datetime(last_visit) return sorted(accounts.itervalues(), key=lambda acct: acct['username'])
def fetch_user_data(env, req): acctmgr = AccountManager(env) guard = AccountGuard(env) accounts = {} for username in acctmgr.get_users(): if req.perm.has_permission('ACCTMGR_USER_ADMIN'): url = req.href.admin('accounts', 'users', user=username) else: url = None accounts[username] = {'username': username, 'review_url': url} if guard.user_locked(username): accounts[username]['locked'] = True t_lock = guard.lock_time(username) if t_lock > 0: t_release = guard.pretty_release_time(req, username) accounts[username]['release_hint'] = _( "Locked until %(t_release)s", t_release=t_release) for acct, status in get_user_attribute(env, username=None, authenticated=None).iteritems(): account = accounts.get(acct) if account is not None and 1 in status: # Only use attributes related to authenticated # accounts. account['name'] = status[1].get('name') account['email'] = status[1].get('email') if account['email']: account['email'] = Chrome(env).format_author( req, account['email']) ts_seen = last_seen(env) if ts_seen is not None: for username, last_visit in ts_seen: account = accounts.get(username) if account and last_visit: account['last_visit'] = to_datetime(last_visit) return sorted(accounts.itervalues(), key=lambda acct: acct['username'])
def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login') and \ req.args.get('user_locked') is None: username = self._remote_user(req) acctmgr = AccountManager(self.env) guard = AccountGuard(self.env) if guard.login_attempt_max_count > 0: if username is None: # Get user for failed authentication attempt. f_user = req.args.get('username') req.args['user_locked'] = False # Log current failed login attempt. guard.failed_count(f_user, req.remote_addr) if guard.user_locked(f_user): # Step up lock time prolongation only while locked. guard.lock_count(f_user, 'up') req.args['user_locked'] = True elif guard.user_locked(username): req.args['user_locked'] = True # Void successful login as long as user is locked. username = None else: req.args['user_locked'] = False if req.args.get('failed_logins') is None: # Reset failed login attempts counter. req.args['failed_logins'] = guard.failed_count( username, reset=True) else: req.args['user_locked'] = False if not 'REMOTE_USER' in req.environ or self.environ_auth_overwrite: if 'REMOTE_USER' in req.environ: # Complain about another component setting environment # variable for authenticated user. self.env.log.warn("LoginModule.authenticate: " "'REMOTE_USER' was set to '%s'" % req.environ['REMOTE_USER']) self.env.log.debug("LoginModule.authenticate: Set " "'REMOTE_USER' = '%s'" % username) req.environ['REMOTE_USER'] = username return auth.LoginModule.authenticate(self, req)
def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login'): user = self._remote_user(req) acctmgr = AccountManager(self.env) guard = AccountGuard(self.env) if guard.login_attempt_max_count > 0: if user is None: if req.args.get('user_locked') is None: # get user for failed authentication attempt f_user = req.args.get('user') req.args['user_locked'] = False if user_known(self.env, f_user): if guard.user_locked(f_user) is False: # log current failed login attempt guard.failed_count(f_user, req.remote_addr) if guard.user_locked(f_user) is True: # step up lock time prolongation # only when just triggering the lock guard.lock_count(f_user, 'up') req.args['user_locked'] = True else: # enforce lock req.args['user_locked'] = True else: if guard.user_locked(user) is not False: req.args['user_locked'] = True # void successful login as long as user is locked user = None else: req.args['user_locked'] = False if req.args.get('failed_logins') is None: # Reset failed login attempts counter req.args['failed_logins'] = guard.failed_count( user, reset = True) if 'REMOTE_USER' not in req.environ: req.environ['REMOTE_USER'] = user return auth.LoginModule.authenticate(self, req)
def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login') and \ req.args.get('user_locked') is None: user = self._remote_user(req) acctmgr = AccountManager(self.env) guard = AccountGuard(self.env) if guard.login_attempt_max_count > 0: if user is None: # Get user for failed authentication attempt. f_user = req.args.get('user') req.args['user_locked'] = False # Log current failed login attempt. guard.failed_count(f_user, req.remote_addr) if guard.user_locked(f_user): # Step up lock time prolongation only while locked. guard.lock_count(f_user, 'up') req.args['user_locked'] = True elif guard.user_locked(user): req.args['user_locked'] = True # Void successful login as long as user is locked. user = None else: req.args['user_locked'] = False if req.args.get('failed_logins') is None: # Reset failed login attempts counter. req.args['failed_logins'] = guard.failed_count(user, reset=True) else: req.args['user_locked'] = False if not 'REMOTE_USER' in req.environ or self.environ_auth_overwrite: if 'REMOTE_USER' in req.environ: # Complain about another component setting environment # variable for authenticated user. self.env.log.warn("LoginModule.authenticate: " "'REMOTE_USER' was set to '%s'" % req.environ['REMOTE_USER']) self.env.log.debug("LoginModule.authenticate: Set " "'REMOTE_USER' = '%s'" % user) req.environ['REMOTE_USER'] = user return auth.LoginModule.authenticate(self, req)
def expand_macro(self, formatter, name, content): env = formatter.env req = formatter.req if not content: args = [] kw = {} else: args, kw = parse_args(content) if name == 'ProjectStats': if 'wiki' in kw.keys(): prefix = 'prefix' in kw.keys() and kw['prefix'] or None wiki = WikiSystem(env) if kw['wiki'] == 'count' or 'count' in args: return tag(len(list(wiki.get_pages(prefix)))) elif name == 'UserQuery': msg_no_perm = tag.p(tag_("(required %(perm)s missing)", perm=tag.strong('USER_VIEW')), class_='hint') if 'perm' in kw.keys(): perm_sys = PermissionSystem(self.env) users = perm_sys.get_users_with_permission(kw['perm'].upper()) else: acct_mgr = AccountManager(env) users = list(set(acct_mgr.get_users())) if 'locked' in kw.keys() or 'locked' in args: guard = AccountGuard(env) locked = [] for user in users: if guard.user_locked(user): locked.append(user) if kw.get('locked', 'True').lower() in ('true', 'yes', '1'): users = locked else: users = list(set(users) - set(locked)) elif 'visit' in kw.keys() or 'visit' in args: if 'USER_VIEW' not in req.perm: return msg_no_perm cols = [] data = {'accounts': fetch_user_data(env, req), 'cls': 'wiki'} for col in ('email', 'name'): if col in args: cols.append(col) data['cols'] = cols return Chrome(env).render_template(req, 'user_table.html', data, 'text/html', True) if kw.get('format') == 'count' or 'count' in args: return tag(len(users)) if 'USER_VIEW' not in req.perm: return msg_no_perm if 'email' in args or 'name' in args: # Replace username with full name, add email if available. for username, name, email in self.env.get_known_users(): if username in users: if 'name' not in args or name is None: name = username if 'email' in args and email is not None: email = ''.join(['<', email, '>']) name = ' '.join([name, email]) if not username == name: users.pop(users.index(username)) users.append(name) if not users and 'nomatch' in kw.keys(): return format_to_oneliner(env, formatter.context, kw['nomatch']) users = sorted(users) if kw.get('format') == 'list': return tag.ul([ tag.li(Chrome(env).format_author(req, user)) for user in users ]) else: # Default output format: comma-separated list. return tag(', '.join( [Chrome(env).format_author(req, user) for user in users]))
def __init__(self): self.acctmgr = AccountManager(self.env) self.guard = AccountGuard(self.env)
def expand_macro(self, formatter, name, content): env = formatter.env req = formatter.req if not content: args = [] kw = {} else: args, kw = parse_args(content) if name == 'ProjectStats': if 'wiki' in kw.keys(): prefix = 'prefix' in kw.keys() and kw['prefix'] or None wiki = WikiSystem(env) if kw['wiki'] == 'count' or 'count' in args: return tag(len(list(wiki.get_pages(prefix)))) elif name == 'UserQuery': msg_no_perm = tag.p(tag_("(required %(perm)s missing)", perm=tag.strong('USER_VIEW')), class_='hint') if 'perm' in kw.keys(): perm_sys = PermissionSystem(self.env) users = perm_sys.get_users_with_permission(kw['perm'].upper()) else: acct_mgr = AccountManager(env) users = list(set(acct_mgr.get_users())) if 'locked' in kw.keys() or 'locked' in args: guard = AccountGuard(env) locked = [] for user in users: if guard.user_locked(user): locked.append(user) if kw.get('locked', 'True').lower() in ('true', 'yes', '1'): users = locked else: users = list(set(users) - set(locked)) elif 'visit' in kw.keys() or 'visit' in args: if 'USER_VIEW' not in req.perm: return msg_no_perm cols = [] data = {'accounts': fetch_user_data(env, req), 'cls': 'wiki'} for col in ('email', 'name'): if col in args: cols.append(col) data['cols'] = cols return Chrome(env).render_template( req, 'user_table.html', data, 'text/html', True) if kw.get('format') == 'count' or 'count' in args: return tag(len(users)) if 'USER_VIEW' not in req.perm: return msg_no_perm if 'email' in args or 'name' in args: # Replace username with full name, add email if available. for username, name, email in self.env.get_known_users(): if username in users: if 'name' not in args or name is None: name = username if 'email' in args and email is not None: email = ''.join(['<', email, '>']) name = ' '.join([name, email]) if not username == name: users.pop(users.index(username)) users.append(name) if not users and 'nomatch' in kw.keys(): return format_to_oneliner(env, formatter.context, kw['nomatch']) users = sorted(users) if kw.get('format') == 'list': return tag.ul([tag.li(Chrome(env).format_author(req, user)) for user in users]) else: # Default output format: comma-separated list. return tag(', '.join([Chrome(env).format_author(req, user) for user in users]))
class AccountGuardTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'acct_mgr.guard.*']) self.env.path = tempfile.mkdtemp() self.env.config.set('account-manager', 'login_attempt_max_count', 1) self.db = self.env.get_db_cnx() self.user = '******' self.session = self._create_session(self.user) self.guard = AccountGuard(self.env) def tearDown(self): self.db.close() # Really close db connections. self.env.shutdown() shutil.rmtree(self.env.path) # Helpers def _create_session(self, user, authenticated=1, name='', email=''): args = dict(username=user, name=name, email=email) incookie = Cookie() incookie['trac_session'] = '123456' req = Mock(authname=bool(authenticated) and user or 'anonymous', args=args, base_path='/', chrome=dict(warnings=list()), href=Mock(prefs=lambda x: None), incookie=incookie, outcookie=Cookie(), redirect=lambda x: None) req.session = Session(self.env, req) req.session.save() return req.session def _mock_failed_attempt(self, requests=1): ipnr = '127.0.0.1' ts = to_timestamp(to_datetime(None)) attempts = eval(self.session.get('failed_logins', '[]')) count = int(self.session.get('failed_logins_count', 0)) lock_count = int(self.session.get('lock_count', 0)) max = self.env.config.getint('account-manager', 'login_attempt_max_count') for r in range(requests): attempts.append(dict(ipnr=ipnr, time=ts)) count += 1 # Assume, that every lock is enforced. if not count < max: lock_count += 1 self.session['failed_logins'] = str(attempts) self.session['failed_logins_count'] = count self.session['lock_count'] = lock_count self.session.save() return ts # Tests def test_failed_count(self): ipnr = '127.0.0.1' # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.failed_count(None, ipnr), 0) # Regular account without failed attempts logged. user = self.user # Start without failed attempts logged, accumulating failed attempts. self.assertEqual(self.guard.failed_count(user, ipnr), 1) self.assertEqual(self.guard.failed_count(user, ipnr), 2) # Read failed attempts. self.assertEqual(self.guard.failed_count(user, ipnr, None), 2) # Reset failed attempts, returning deleted attemps. self.assertEqual(self.guard.failed_count(user, reset=True), 2) self.assertEqual(self.guard.failed_count(user, reset=None), 0) def test_functional(self): ipnr = '127.0.0.1' user = self.user # Regular account without failed attempts logged. self.assertEqual(self.guard.lock_count(user), 0) self.assertEqual(self.guard.lock_time(user), 0) self.assertEqual(self.guard.release_time(user), 0) self.assertEqual(self.guard.user_locked(user), False) # Log failed attempt - this time with the real method. self.assertEqual(self.guard.failed_count(user, ipnr), 1) # Mock acct_mgr.LoginModule.authenticate behavior. if self.guard.user_locked(user): self.guard.lock_count(user, 'up') self.assertEqual(self.guard.lock_count(user), 1) self.assertEqual(self.guard.lock_time(user), 0) self.assertEqual(self.guard.release_time(user), 0) self.assertEqual(self.guard.user_locked(user), True) # Switch to time lock. self.env.config.set('account-manager', 'user_lock_time', 2) self.assertTrue(self.guard.release_time(user) > 0) self.assertEqual(self.guard.user_locked(user), True) sleep(2) self.assertEqual(self.guard.user_locked(user), False) self.assertEqual(self.guard.lock_time(user), 2) self.assertEqual(self.guard.lock_time(user, True), 2) self.env.config.set('account-manager', 'user_lock_time_progression', 3) self.assertEqual(self.guard.lock_time(user, True), 6) # Switch-back to permanent locking. self.env.config.set('account-manager', 'user_lock_time', 0) self.assertEqual(self.guard.user_locked(user), True) def test_lock_count(self): user = self.user self.assertEqual(self.guard.lock_count(user), 0) # Validate helper method too. self._mock_failed_attempt() # Increment per failed login. self.assertEqual(self.guard.lock_count(user, 'set'), 2) self.assertEqual(self.guard.lock_count(user), 2) # Return updated value on reset as well. self.assertEqual(self.guard.lock_count(user, 'reset'), 0) def test_lock_time(self): self.env.config.set('account-manager', 'user_lock_time', 30) self.env.config.set('account-manager', 'user_lock_time_progression', 1) # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.lock_time(None), 0) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.lock_time(user), 30) self._mock_failed_attempt(5) # Fixed lock time, no progression, with default configuration values. self.assertEqual(self.guard.lock_time(user), 30) # Preview calculation. self.assertEqual(self.guard.lock_time(user, True), 30) # Progression with base 3. self.env.config.set('account-manager', 'user_lock_time_progression', 3) self.assertEqual(self.guard.lock_time(user, True), 30 * 3**5) self.env.config.set('account-manager', 'user_lock_max_time', 1800) self.assertEqual(self.guard.lock_time(user, True), 1800) def test_release_time(self): lock_time = 30 self.env.config.set('account-manager', 'user_lock_time', lock_time) self.env.config.set('account-manager', 'user_lock_time_progression', 1) # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.release_time(None), None) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.release_time(user), None) # Account with failed attempts logged. release_ts = self._mock_failed_attempt() + lock_time self.assertEqual(self.guard.release_time(user), release_ts) release_ts = self._mock_failed_attempt() + lock_time self.assertEqual(self.guard.release_time(user), release_ts) # Permanently locked account. self.env.config.set('account-manager', 'user_lock_time', 0) self.assertEqual(self.guard.release_time(user), 0) # Result with locking disabled. self.env.config.set('account-manager', 'login_attempt_max_count', 0) self.env.config.set('account-manager', 'user_lock_time', 30) self.assertEqual(self.guard.release_time(user), None) def test_user_locked(self): # Won't track anonymous sessions and unknown accounts/users. for user in [None, 'anonymous']: self.assertEqual(self.guard.user_locked(user), None) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.user_locked(user), False) # Permanently locked account. self._mock_failed_attempt() self.assertEqual(self.guard.user_locked(user), True) # Result with locking disabled. self.env.config.set('account-manager', 'login_attempt_max_count', 0) self.assertEqual(self.guard.user_locked(user), None)
class AccountGuardTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'acct_mgr.guard.*']) self.env.path = tempfile.mkdtemp() self.env.config.set('account-manager', 'login_attempt_max_count', 1) self.user = '******' self.session = self._create_session(self.user) self.guard = AccountGuard(self.env) def tearDown(self): self.env.shutdown() shutil.rmtree(self.env.path) # Helpers def _create_session(self, user, authenticated=1, name='', email=''): args = dict(username=user, name=name, email=email) incookie = Cookie() incookie['trac_session'] = '123456' req = Mock(authname=bool(authenticated) and user or 'anonymous', args=args, base_path='/', chrome=dict(warnings=list()), href=Mock(prefs=lambda x: None), incookie=incookie, outcookie=Cookie(), redirect=lambda x: None) req.session = Session(self.env, req) req.session.save() return req.session def _mock_failed_attempt(self, requests=1): ipnr = '127.0.0.1' ts = to_timestamp(to_datetime(None)) attempts = eval(self.session.get('failed_logins', '[]')) count = int(self.session.get('failed_logins_count', 0)) lock_count = int(self.session.get('lock_count', 0)) max_ = self.env.config.getint('account-manager', 'login_attempt_max_count') for r in range(requests): attempts.append(dict(ipnr=ipnr, time=ts)) count += 1 # Assume, that every lock is enforced. if not count < max_: lock_count += 1 self.session['failed_logins'] = str(attempts) self.session['failed_logins_count'] = count self.session['lock_count'] = lock_count self.session.save() return ts # Tests def test_failed_count(self): ipnr = '127.0.0.1' # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.failed_count(None, ipnr), 0) # Regular account without failed attempts logged. user = self.user # Start without failed attempts logged, accumulating failed attempts. self.assertEqual(self.guard.failed_count(user, ipnr), 1) self.assertEqual(self.guard.failed_count(user, ipnr), 2) # Read failed attempts. self.assertEqual(self.guard.failed_count(user, ipnr, None), 2) # Reset failed attempts, returning deleted attemps. self.assertEqual(self.guard.failed_count(user, reset=True), 2) self.assertEqual(self.guard.failed_count(user, reset=None), 0) def test_functional(self): ipnr = '127.0.0.1' user = self.user # Regular account without failed attempts logged. self.assertEqual(self.guard.lock_count(user), 0) self.assertEqual(self.guard.lock_time(user), 0) self.assertEqual(self.guard.release_time(user), 0) self.assertEqual(self.guard.user_locked(user), False) # Log failed attempt - this time with the real method. self.assertEqual(self.guard.failed_count(user, ipnr), 1) # Mock acct_mgr.LoginModule.authenticate behavior. if self.guard.user_locked(user): self.guard.lock_count(user, 'up') self.assertEqual(self.guard.lock_count(user), 1) self.assertEqual(self.guard.lock_time(user), 0) self.assertEqual(self.guard.release_time(user), 0) self.assertEqual(self.guard.user_locked(user), True) # Switch to time lock. self.env.config.set('account-manager', 'user_lock_time', 2) self.assertTrue(self.guard.release_time(user) > 0) self.assertEqual(self.guard.user_locked(user), True) sleep(2) self.assertEqual(self.guard.user_locked(user), False) self.assertEqual(self.guard.lock_time(user), 2) self.assertEqual(self.guard.lock_time(user, True), 2) self.env.config.set('account-manager', 'user_lock_time_progression', 3) self.assertEqual(self.guard.lock_time(user, True), 6) # Switch-back to permanent locking. self.env.config.set('account-manager', 'user_lock_time', 0) self.assertEqual(self.guard.user_locked(user), True) def test_lock_count(self): user = self.user self.assertEqual(self.guard.lock_count(user), 0) # Validate helper method too. self._mock_failed_attempt() # Increment per failed login. self.assertEqual(self.guard.lock_count(user, 'set'), 2) self.assertEqual(self.guard.lock_count(user), 2) # Return updated value on reset as well. self.assertEqual(self.guard.lock_count(user, 'reset'), 0) def test_lock_time(self): self.env.config.set('account-manager', 'user_lock_time', 30) self.env.config.set('account-manager', 'user_lock_time_progression', 1) # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.lock_time(None), 0) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.lock_time(user), 30) self._mock_failed_attempt(5) # Fixed lock time, no progression, with default configuration values. self.assertEqual(self.guard.lock_time(user), 30) # Preview calculation. self.assertEqual(self.guard.lock_time(user, True), 30) # Progression with base 3. self.env.config.set('account-manager', 'user_lock_time_progression', 3) self.assertEqual(self.guard.lock_time(user, True), 30 * 3 ** 5) self.env.config.set('account-manager', 'user_lock_max_time', 1800) self.assertEqual(self.guard.lock_time(user, True), 1800) def test_release_time(self): lock_time = 30 self.env.config.set('account-manager', 'user_lock_time', lock_time) self.env.config.set('account-manager', 'user_lock_time_progression', 1) # Won't track anonymous sessions and unknown accounts/users. self.assertEqual(self.guard.release_time(None), None) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.release_time(user), None) # Account with failed attempts logged. release_ts = self._mock_failed_attempt() + lock_time self.assertEqual(self.guard.release_time(user), release_ts) release_ts = self._mock_failed_attempt() + lock_time self.assertEqual(self.guard.release_time(user), release_ts) # Permanently locked account. self.env.config.set('account-manager', 'user_lock_time', 0) self.assertEqual(self.guard.release_time(user), 0) # Result with locking disabled. self.env.config.set('account-manager', 'login_attempt_max_count', 0) self.env.config.set('account-manager', 'user_lock_time', 30) self.assertEqual(self.guard.release_time(user), None) def test_user_locked(self): # Won't track anonymous sessions and unknown accounts/users. for user in [None, 'anonymous']: self.assertEqual(self.guard.user_locked(user), None) # Regular account without failed attempts logged. user = self.user self.assertEqual(self.guard.user_locked(user), False) # Permanently locked account. self._mock_failed_attempt() self.assertEqual(self.guard.user_locked(user), True) # Result with locking disabled. self.env.config.set('account-manager', 'login_attempt_max_count', 0) self.assertEqual(self.guard.user_locked(user), None)