def test_get_user_attribute(self): self.assertEqual(get_user_attribute(self.env, authenticated=None), {}) with self.env.db_transaction as db: db.executemany(""" INSERT INTO session_attribute (sid,authenticated,name,value) VALUES (%s,%s,%s,%s) """, [('user', 0, 'attribute1', 'value1'), ('user', 0, 'attribute2', 'value2'), ('user', 1, 'attribute1', 'value1'), ('user', 1, 'attribute2', 'value2'), ('another', 1, 'attribute2', 'value3')]) no_constraints = get_user_attribute(self.env, authenticated=None) # Distinct session IDs form top-level keys. self.assertEqual(set(no_constraints.keys()), set([u'user', u'another'])) # There are probably anonymous sessions named equally to # authenticated ones, causing different nested dicts below each # session ID. Btw, only authenticated ones are real usernames. self.assertTrue(0 in no_constraints['user']) self.assertTrue(1 in no_constraints['user']) self.assertFalse(0 in no_constraints['another']) self.assertTrue(1 in no_constraints['another']) # Touch some of the attributes stored before. self.assertTrue(no_constraints['user'][0]['attribute1'], 'value1') self.assertTrue(no_constraints['user'][1]['attribute2'], 'value2') self.assertEqual(no_constraints['another'].get(0), None) self.assertTrue(no_constraints['another'][1]['attribute2'], 'value3')
def test_get_user_attribute(self): self.assertEqual(get_user_attribute(self.env, authenticated=None), {}) cursor = self.db.cursor() cursor.executemany( """ INSERT INTO session_attribute (sid,authenticated,name,value) VALUES (%s,%s,%s,%s) """, [('user', 0, 'attribute1', 'value1'), ('user', 0, 'attribute2', 'value2'), ('user', 1, 'attribute1', 'value1'), ('user', 1, 'attribute2', 'value2'), ('another', 1, 'attribute2', 'value3')]) no_constraints = get_user_attribute(self.env, authenticated=None) # Distinct session IDs form top-level keys. self.assertEqual(set(no_constraints.keys()), set([u'user', u'another'])) # There are probably anonymous sessions named equally to # authenticated ones, causing different nested dicts below each # session ID. Btw, only authenticated ones are real usernames. self.assertTrue(0 in no_constraints['user']) self.assertTrue(1 in no_constraints['user']) self.assertFalse(0 in no_constraints['another']) self.assertTrue(1 in no_constraints['another']) # Touch some of the attributes stored before. self.assertTrue(no_constraints['user'][0]['attribute1'], 'value1') self.assertTrue(no_constraints['user'][1]['attribute2'], 'value2') self.assertEqual(no_constraints['another'].get(0), None) self.assertTrue(no_constraints['another'][1]['attribute2'], 'value3')
def pre_process_request(self, req, handler): if not req.authname or req.authname == 'anonymous': # Permissions for anonymous users remain unchanged. return handler elif req.path_info == '/prefs' and \ req.method == 'POST' and \ 'restore' not in req.args and \ req.get_header( 'X-Requested-With') != 'XMLHttpRequest': try: AccountManager(self.env).validate_account(req) # Check passed without error: New email address seems good. except RegistrationError, e: # Always warn about issues. chrome.add_warning(req, e) # Look, if the issue existed before. attributes = get_user_attribute(self.env, req.authname, attribute='email') email = req.authname in attributes and \ attributes[req.authname][1].get('email') or None new_email = req.args.get('email', '').strip() if (email or new_email) and email != new_email: # Attempt to change email to an empty or invalid # address detected, resetting to previously stored value. req.redirect(req.href.prefs(None))
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 get_failed_log(self, user): """Returns an iterable of previously logged failed login attempts. The iterable contains a list of dicts in the following form: {'ipnr': ipnr, 'time': time_stamp} or an empty list. The time stamp format depends on Trac support for POSIX seconds (before 0.12) or POSIX microseconds in more recent Trac versions. """ attempts = get_user_attribute(self.env, user, 1, 'failed_logins') return attempts and eval(attempts[user][1].get('failed_logins')) or []
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): 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 _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 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 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 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 _do_db_cleanup(self, req): if req.perm.has_permission('ACCTMGR_ADMIN'): env = self.env changed = False # Get all data from 'session_attributes' db table. attr = get_user_attribute(self.env, username=None, authenticated=None) attrs = {} sel = req.args.get('sel') if req.args.get('purge') and sel is not None: sel = isinstance(sel, list) and sel or [sel] sel_len = len(sel) matched = [] for acct, states in attr.iteritems(): for state in states['id'].keys(): for elem, id in states[state]['id'].iteritems(): if id in sel: if acct in attrs.keys(): if state in attrs[acct].keys(): attrs[acct][state] \ .append(elem) else: attrs[acct][state] = [elem] else: attrs[acct] = {state: [elem]} matched.append(id) if len(matched) == sel_len: break if len(matched) == sel_len: break if len(matched) == sel_len: break for id in (frozenset(sel) - frozenset(matched)): for acct, states in attr.iteritems(): for state, id_ in states['id'].iteritems(): if id == id_: # Full account is marked, forget attributes. if acct in attrs.keys(): attrs[acct].update({state: []}) else: attrs[acct] = {state: []} matched.append(id) if len(matched) == sel_len: break if len(matched) == sel_len: break # DEVEL: for Python>2.4 better use defaultdict for counters del_count = {'acct': 0, 'attr': 0} for account, states in attrs.iteritems(): for state, elem in states.iteritems(): if len(elem) == 0: del_user_attribute(env, account, state) del_count['acct'] += 1 else: for attribute in elem: del_user_attribute(env, account, state, attribute) del_count['attr'] += 1 changed = True elif req.args.get('list'): req.redirect(req.href.admin('accounts', 'users')) if changed == True: # Update the dict after changes. attr = get_user_attribute(env, username=None, authenticated=None) data = {'_dgettext': dgettext} data.update(self._prepare_attrs(req, attr)) if req.args.get('purge') and sel is not None: accounts = attributes = '' n_plural=del_count['acct'] if n_plural > 0: accounts = tag.li(tag.span(tag(ngettext( "%(count)s account", "%(count)s accounts", n_plural, count=n_plural )))) n_plural=del_count['attr'] if n_plural > 0: attributes = tag.li(tag.span(tag(ngettext( "%(count)s account attribute", "%(count)s account attributes", n_plural, count=n_plural )))) data['result'] = tag(_("Successfully deleted:"), tag.ul(accounts, attributes)) add_stylesheet(req, 'acct_mgr/acct_mgr.css') return 'db_cleanup.html', data
def _do_db_cleanup(self, req): if req.perm.has_permission('ACCTMGR_ADMIN'): env = self.env changed = False # Get all data from 'session_attributes' db table. attr = get_user_attribute(self.env, username=None, authenticated=None) attrs = {} sel = req.args.get('sel') if req.args.get('purge') and sel is not None: sel = isinstance(sel, list) and sel or [sel] sel_len = len(sel) matched = [] for acct, states in attr.iteritems(): for state in states['id'].keys(): for elem, id in states[state]['id'].iteritems(): if id in sel: if acct in attrs.keys(): if state in attrs[acct].keys(): attrs[acct][state] \ .append(elem) else: attrs[acct][state] = [elem] else: attrs[acct] = {state: [elem]} matched.append(id) if len(matched) == sel_len: break if len(matched) == sel_len: break if len(matched) == sel_len: break for id in (frozenset(sel) - frozenset(matched)): for acct, states in attr.iteritems(): for state, id_ in states['id'].iteritems(): if id == id_: # Full account is marked, forget attributes. if acct in attrs.keys(): attrs[acct].update({state: []}) else: attrs[acct] = {state: []} matched.append(id) if len(matched) == sel_len: break if len(matched) == sel_len: break # DEVEL: for Python>2.4 better use defaultdict for counters del_count = {'acct': 0, 'attr': 0} for account, states in attrs.iteritems(): for state, elem in states.iteritems(): if len(elem) == 0: del_user_attribute(env, account, state) del_count['acct'] += 1 else: for attribute in elem: del_user_attribute(env, account, state, attribute) del_count['attr'] += 1 changed = True elif req.args.get('list'): req.redirect(req.href.admin('accounts', 'users')) if changed == True: # Update the dict after changes. attr = get_user_attribute(env, username=None, authenticated=None) data = {'_dgettext': dgettext} data.update(self._prepare_attrs(req, attr)) if req.args.get('purge') and sel is not None: accounts = attributes = '' n_plural = del_count['acct'] if n_plural > 0: accounts = tag.li( tag.span( tag( ngettext("%(count)s account", "%(count)s accounts", n_plural, count=n_plural)))) n_plural = del_count['attr'] if n_plural > 0: attributes = tag.li( tag.span( tag( ngettext("%(count)s account attribute", "%(count)s account attributes", n_plural, count=n_plural)))) data['result'] = tag(_("Successfully deleted:"), tag.ul(accounts, attributes)) add_stylesheet(req, 'acct_mgr/acct_mgr.css') return 'db_cleanup.html', data