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') 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 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 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)
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)