def test_user_known(self): user = '******' self.assertFalse(user_known(self.env, user)) # Don't care for anonymous session IDs. self._create_session(user, False) self.assertFalse(user_known(self.env, user)) self._create_session(user) self.assertTrue(user_known(self.env, user))
def user_locked(self, user): """Returns whether the user account is currently locked. Expect True, if locked, False, if not and None otherwise. """ if self.login_attempt_max_count < 1 or not user or \ not user_known(self.env, user): self.log.debug( "AccountGuard.user_locked(%s) = None (%s)" % (user, self.login_attempt_max_count < 1 and \ 'disabled by configuration' or 'anonymous user')) return None count = self.failed_count(user, reset=None) if count < self.login_attempt_max_count: self.log.debug( "AccountGuard.user_locked(%s) = False (try left)" % user) return False ts_release = self.release_time(user) if ts_release == 0: # Account locked permanently. self.log.debug( "AccountGuard.user_locked(%s) = True (permanently)" % user) return True # Time-locked or time-lock expired. ts_now = to_timestamp(to_datetime(None)) locked = ts_release - ts_now > 0 self.log.debug( "AccountGuard.user_locked(%s) = %s (%s)" % (user, locked, locked and 'time-lock' or 'lock expired')) return locked
def lock_time(self, user, next=False): """Calculate current time-lock length for user account.""" base = self.lock_time_progression lock_count = self.lock_count(user) if not user or not user_known(self.env, user): return 0 else: if next: # Preview calculation. exponent = lock_count else: exponent = lock_count - 1 t_lock = self.user_lock_time * base ** exponent # Limit maximum lock time. if t_lock > self.user_lock_max_time: t_lock = self.user_lock_max_time self.log.debug("AccountGuard.lock_time(%s) = %s%s" % (user, t_lock, next and ' (preview)' or '')) return t_lock
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 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 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)