PK!‡‡9acct_mgr/__init__.pyPKm‡‡9¼dn ††acct_mgr/__init__.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good import inspect from trac.core import * from trac.config import Option from trac.perm import PermissionSystem from trac.util.datefmt import format_datetime from trac.web.chrome import ITemplateProvider from trac.admin import IAdminPanelProvider from acct_mgr.api import AccountManager from acct_mgr.web_ui import _create_user def _getoptions(cls): if isinstance(cls, Component): cls = cls.__class__ return [(name, value) for name, value in inspect.getmembers(cls) if isinstance(value, Option)] def _setorder(req, stores): """Pull the password store ordering out of the req object""" for store in stores.get_all_stores(): stores[store] = int(req.args.get(store.__class__.__name__, 0)) continue class StoreOrder(dict): """Keeps the order of the Password Stores""" instance = 0 def __init__(self, d={}, stores=[], list=[]): self.instance += 1 self.d = {} self.sxref = {} for store in stores: self.d[store] = 0 self[0] = store self.sxref[store.__class__.__name__] = store continue for i, s in enumerate(list): self.d[s] = i + 1 self[i + 1] = s def __getitem__(self, key): """Lookup a store in the list""" return self.d[key] def __setitem__(self, key, value): if isinstance(key, Component): order = self.d[key] self.d[key] = value self.d[order].remove(key) self[value] = key elif isinstance(key, basestring): self.d[self.sxref[key]] = value elif isinstance(key, int): self.d.setdefault(key, []) self.d[key].append(value) else: raise KeyError('Invalid key type (%s) for StoreOrder' % str(type(key))) pass def get_enabled_stores(self): """Return an ordered list of password stores All stores that are order 0 are dropped from the list. """ keys = [k for k in self.d.keys() if isinstance(k, int)] keys.sort() storelist = [] for k in keys[1:]: storelist.extend(self.d[k]) continue return storelist def get_enabled_store_names(self): """Returns the class names of the enabled password stores""" stores = self.get_enabled_stores() return [s.__class__.__name__ for s in stores] def get_all_stores(self): return [k for k in self.d.keys() if isinstance(k, Component)] def numstores(self): return len(self.get_all_stores()) 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' if listing_enabled: accounts = {} for username in self.account_manager.get_users(): accounts[username] = {'username': username} for username, name, email in self.env.get_known_users(): account = accounts.get(username) if account: account['name'] = name account['email'] = email db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT sid,last_visit FROM session WHERE " "authenticated=1") for username, last_visit in cursor: account = accounts.get(username) if account and last_visit: account['last_visit'] = format_datetime(last_visit) data['accounts'] = sorted(accounts.itervalues(), key=lambda acct: acct['username']) return 'admin_users.html', data # ITemplateProvider def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). """ return [] def get_templates_dirs(self): """Return the absolute path of the directory containing the provided ClearSilver templates. """ from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] PKm‡‡9®˜¨õ/õ/acct_mgr/admin.pyc;ò Þp›siissectionss numstoressadmin_accountsconfig.html((s StoreOrdersselfsaccount_managersstoresspassword_storesreqsmethods _setordersconfigssetsjoinsget_enabled_store_namessget_all_storessstores _getoptionssattrsoptionsargssgets __class__s__name__snewvalueslogsdebugsNonessectionsnamessavessectionssoptionss__get__sopt_vals isinstances Componentsappendssortedsranges numstoressforce_passwd_changesdata( sselfsreqsstoressattrsopt_valsoptionssdatas numstoresssectionssnewvaluesstoresoption((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pys _do_configvsD   "#   #8A'cCst|iƒ}|iidƒ} |iidƒ} |iidƒ}|iidƒ}hd| <d| <d|<d|<dhd t <d t <d t <<}|i d jo#|iid ƒoe| oPyt||idtƒWq)tj o#}|i|d<|i|dûssaccountssadmin_users.html(.sPermissionSystemsselfsenvspermsaccount_managerssupportsslisting_enabledscreate_enabledspassword_change_enabledsdelete_enabledsNonesdatasreqsmethodsargssgets _create_usersFalses TracErrorsesmessagesacctmgrssels isinstanceslistsaccounts delete_usersuserserrorspasswords set_passwordsgetattrsaccountss get_userssusernamesget_known_userssnamesemails get_db_cnxsdbscursorsexecutes last_visitsformat_datetimessorteds itervalues(sselfsreqsusernames last_visitspassword_change_enabledsaccountssacctmgrsselspermsemailscreate_enabledsdbslisting_enabledsusersespasswordsdatasdelete_enabledsaccountsnamescursorserror((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pys _do_users¢s†N                 cCsgSdS(s„Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). N((sself((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pysget_htdocs_dirsscCs!dkl}|tdƒgSdS(siReturn the absolute path of the directory containing the provided ClearSilver templates. (sresource_filenames templatesN(s pkg_resourcessresource_filenames__name__(sselfsresource_filename((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pysget_templates_dirss ( s__name__s __module__s implementssIAdminPanelProvidersITemplateProviders__init__sget_admin_panelssrender_admin_panels _do_configs _do_userssget_htdocs_dirssget_templates_dirs(((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pysAccountManagerAdminPagecs     , _ (sinspects trac.cores trac.configsOptions trac.permsPermissionSystemstrac.util.datefmtsformat_datetimestrac.web.chromesITemplateProviders trac.adminsIAdminPanelProviders acct_mgr.apisAccountManagersacct_mgr.web_uis _create_users _getoptionss _setordersdicts StoreOrders ComponentsAccountManagerAdminPage( s _getoptionss StoreOrdersIAdminPanelProvidersOptions _setorders _create_usersinspectsformat_datetimesITemplateProvidersPermissionSystemsAccountManagerAdminPagesAccountManager((s3build/bdist.darwin-9.5.0-i386/egg/acct_mgr/admin.pys? s          ?PK!‡‡9 Ómd""acct_mgr/api.py# -*- coding: utf-8 -*- # # Copyright (C) 2005 Matthew Good # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good from trac.core import * from trac.config import Option, BoolOption, ExtensionOption, OrderedExtensionsOption class IPasswordStore(Interface): """An interface for Components that provide a storage method for users and passwords. """ def config_key(self): """ '''Deprecated''': new implementations of this interface are not required to implement this method, since the prefered way to configure the `IPasswordStore` implemenation is by using its class name in the `password_store` option. Returns a string used to identify this implementation in the config. This password storage implementation will be used if the value of the config property "account-manager.password_format" matches. """ def get_users(self): """Returns an iterable of the known usernames """ def has_user(self, user): """Returns whether the user account exists. """ def set_password(self, user, password): """Sets the password for the user. This should create the user account if it doesn't already exist. Returns True if a new account was created, False if an existing account was updated. """ def check_password(self, user, password): """Checks if the password is valid for the user. Returns True if the correct user and password are specfied. Returns False if the incorrect password was specified. Returns None if the user doesn't exist in this password store. Note: Returing `False` is an active rejection of the login attempt. Return None to let the auth fall through to the next store in the chain. """ def delete_user(self, user): """Deletes the user account. Returns True if the account existed and was deleted, False otherwise. """ class IAccountChangeListener(Interface): """An interface for receiving account change events. """ def user_created(self, user, password): """New user """ def user_password_changed(self, user, password): """Password changed """ def user_deleted(self, user): """User deleted """ class AccountManager(Component): """The AccountManager component handles all user account management methods provided by the IPasswordStore interface. The methods will be handled by the underlying password storage implementation set in trac.ini with the "account-manager.password_format" setting. The "account-manager.password_store" may be an ordered list of password stores. if it is a list, then each password store is queried in turn. """ implements(IAccountChangeListener) _password_store = OrderedExtensionsOption('account-manager', 'password_store', IPasswordStore, include_missing=False) _password_format = Option('account-manager', 'password_format') stores = ExtensionPoint(IPasswordStore) change_listeners = ExtensionPoint(IAccountChangeListener) force_passwd_change = BoolOption('account-manager', 'force_passwd_change', True, doc="Forge the user to change " "password when it's reset.") # Public API def get_users(self): users = [] for store in self._password_store: users.extend(store.get_users()) return users def has_user(self, user): exists = False for store in self._password_store: if store.has_user(user): exists = True break continue return exists def set_password(self, user, password): store = self.find_user_store(user) if store and not hasattr(store, 'set_password'): raise TracError('The authentication backend for the user, %s, ' 'does not support setting the password' % user) elif not store: store = self.get_supporting_store('set_password') if store: if store.set_password(user, password): self._notify('created', user, password) else: self._notify('password_changed', user, password) else: raise TracError('None of the IPasswordStore components listed in ' 'the trac.ini support setting the password or ' 'creating users') def check_password(self, user, password): valid = False for store in self._password_store: valid = store.check_password(user, password) if valid or (valid == False): break continue return valid def delete_user(self, user): db = self.env.get_db_cnx() cursor = db.cursor() # Delete session attributes cursor.execute("DELETE FROM session_attribute where sid=%s", (user,)) # Delete session cursor.execute("DELETE FROM session where sid=%s", (user,)) # Delete any custom permissions set for the user cursor.execute("DELETE FROM permission where username=%s", (user,)) db.commit() db.close() # Delete from password store self.log.debug('deleted user: %s' % user) store = self.find_user_store(user) if hasattr(store, 'delete_user'): if store and store.delete_user(user): self._notify('deleted', user) def supports(self, operation): try: stores = self.password_store except AttributeError: return False else: if self.get_supporting_store(operation): return True else: return False def password_store(self): try: return self._password_store except AttributeError: # fall back on old "password_format" option fmt = self._password_format for store in self.stores: config_key = getattr(store, 'config_key', None) if config_key is None: continue if config_key() == fmt: return [store] # if the "password_format" is not set re-raise the AttributeError raise password_store = property(password_store) def get_supporting_store(self, operation): """Returns the IPasswordStore that implements the specified operaion None is returned if no supporting store can be found """ supports = False for store in self.password_store: if hasattr(store, operation): supports = True break continue store = supports and store or None return store def get_all_supporting_stores(self, operation): """Returns a list of stores that implement the specified operation""" stores = [] for store in self.password_store: if hasattr(store, operation): stores.append(store) continue return stores def find_user_store(self, user): """Locates which store contains the user specified. If the user isn't found in any IPasswordStore in the chain, None is returned """ ignore_auth_case = self.env.config.get('trac', 'ignore_auth_case') user_stores = [] for store in self._password_store: userlist = store.get_users() if ignore_auth_case: userlist = [u.lower() for u in userlist] user_stores.append((store, userlist)) continue user = ignore_auth_case and user.lower() or user for store in user_stores: if user in store[1]: return store[0] continue return None def _notify(self, func, *args): func = 'user_' + func for l in self.change_listeners: getattr(l, func)(*args) # IAccountChangeListener methods def user_created(self, user, password): self.log.info('Created new user: %s' % user) def user_password_changed(self, user, password): self.log.info('Updated password for user: %s' % user) def user_deleted(self, user): self.log.info('Deleted user: %s' % user) PKm‡‡9¶¦"³‹/‹/acct_mgr/api.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good from trac.core import * from trac.config import ExtensionOption from api import IPasswordStore from pwhash import IPasswordHashMethod class SessionStore(Component): implements(IPasswordStore) hash_method = ExtensionOption('account-manager', 'hash_method', IPasswordHashMethod, 'HtDigestHashMethod') def get_users(self): """Returns an iterable of the known usernames """ db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT DISTINCT sid FROM session_attribute " "WHERE authenticated=1 AND name='password'") for sid, in cursor: yield sid def has_user(self, user): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT * FROM session_attribute " "WHERE authenticated=1 AND name='password' " "AND sid=%s", (user,)) for row in cursor: return True return False def set_password(self, user, password): """Sets the password for the user. This should create the user account if it doesn't already exist. Returns True if a new account was created, False if an existing account was updated. """ hash = self.hash_method.generate_hash(user, password) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("UPDATE session_attribute " "SET value=%s " "WHERE authenticated=1 AND name='password' " "AND sid=%s", (hash, user)) if cursor.rowcount > 0: db.commit() return False # updated existing password cursor.execute("INSERT INTO session_attribute " "(sid,authenticated,name,value) " "VALUES (%s,1,'password',%s)", (user, hash)) db.commit() return True def check_password(self, user, password): """Checks if the password is valid for the user. """ db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT value FROM session_attribute " "WHERE authenticated=1 AND name='password' " "AND sid=%s", (user,)) for hash, in cursor: return self.hash_method.check_hash(user, password, hash) return None def delete_user(self, user): """Deletes the user account. Returns True if the account existed and was deleted, False otherwise. """ if not self.has_user(user): return False db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("DELETE FROM session_attribute " "WHERE authenticated=1 AND name='password' " "AND sid=%s", (user,)) # TODO cursor.rowcount doesn't seem to get # deleted # is there another way to get count instead of using has_user? db.commit() return True PKm‡‡9qO.ÀÀacct_mgr/db.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good import errno import os.path import fileinput from trac.core import * from trac.config import Option from api import IPasswordStore from pwhash import htpasswd, htdigest from util import EnvRelativePathOption class AbstractPasswordFileStore(Component): """Base class for managing password files such as Apache's htpasswd and htdigest formats. See the concrete sub-classes for usage information. """ filename = EnvRelativePathOption('account-manager', 'password_file') def has_user(self, user): return user in self.get_users() def get_users(self): filename = self.filename if not os.path.exists(filename): return [] return self._get_users(filename) def set_password(self, user, password): user = user.encode('utf-8') password = password.encode('utf-8') return not self._update_file(self.prefix(user), self.userline(user, password)) def delete_user(self, user): user = user.encode('utf-8') return self._update_file(self.prefix(user), None) def check_password(self, user, password): filename = self.filename if not os.path.exists(filename): return False user = user.encode('utf-8') password = password.encode('utf-8') prefix = self.prefix(user) fd = file(filename) try: for line in fd: if line.startswith(prefix): return self._check_userline(user, password, line[len(prefix):].rstrip('\n')) finally: fd.close() return None def _update_file(self, prefix, userline): """If `userline` is empty the line starting with `prefix` is removed from the user file. Otherwise the line starting with `prefix` is updated to `userline`. If no line starts with `prefix` the `userline` is appended to the file. Returns `True` if a line matching `prefix` was updated, `False` otherwise. """ filename = self.filename matched = False try: for line in fileinput.input(str(filename), inplace=True): if line.startswith(prefix): if not matched and userline: print userline matched = True elif line.endswith('\n'): print line, else: # make sure the last line has a newline print line except EnvironmentError, e: if e.errno == errno.ENOENT: pass # ignore when file doesn't exist and create it below elif e.errno == errno.EACCES: raise TracError('The password file could not be updated. ' 'Trac requires read and write access to both ' 'the password file and its parent directory.') else: raise if not matched and userline: f = open(filename, 'a') try: print >>f, userline finally: f.close() return matched class HtPasswdStore(AbstractPasswordFileStore): """Manages user accounts stored in Apache's htpasswd format. To use this implementation add the following configuration section to trac.ini: {{{ [account-manager] password_store = HtPasswdStore password_file = /path/to/trac.htpasswd }}} """ implements(IPasswordStore) def config_key(self): return 'htpasswd' def prefix(self, user): return user + ':' def userline(self, user, password): return self.prefix(user) + htpasswd(password) def _check_userline(self, user, password, suffix): return suffix == htpasswd(password, suffix) def _get_users(self, filename): f = open(filename) for line in f: user = line.split(':', 1)[0] if user: yield user.decode('utf-8') class HtDigestStore(AbstractPasswordFileStore): """Manages user accounts stored in Apache's htdigest format. To use this implementation add the following configuration section to trac.ini: {{{ [account-manager] password_store = HtDigestStore password_file = /path/to/trac.htdigest htdigest_realm = TracDigestRealm }}} """ implements(IPasswordStore) realm = Option('account-manager', 'htdigest_realm') def config_key(self): return 'htdigest' def prefix(self, user): return '%s:%s:' % (user, self.realm) def userline(self, user, password): return self.prefix(user) + htdigest(user, self.realm, password) def _check_userline(self, user, password, suffix): return suffix == htdigest(user, self.realm, password) def _get_users(self, filename): _realm = self.realm f = open(filename) for line in f: args = line.split(':')[:2] if len(args) == 2: user, realm = args if realm == _realm and user: yield user.decode('utf-8') PKm‡‡9‘Ä#·Q!Q!acct_mgr/htfile.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good from urllib2 import build_opener, HTTPBasicAuthHandler, \ HTTPDigestAuthHandler, HTTPPasswordMgrWithDefaultRealm from trac.core import * from trac.config import Option from api import IPasswordStore class HttpAuthStore(Component): implements(IPasswordStore) auth_url = Option('account-manager', 'authentication_url') def check_password(self, user, password): mgr = HTTPPasswordMgrWithDefaultRealm() mgr.add_password(None, self.auth_url, user, password) try: build_opener(HTTPBasicAuthHandler(mgr), HTTPDigestAuthHandler(mgr)).open(self.auth_url) except IOError: return None else: return True def get_users(self): return [] def has_user(self, user): return False PKm‡‡9E&Ö¨¨acct_mgr/http.pyc;ò Þp wrote this file. As long as you retain this notice you # * can do whatever you want with this stuff. If we meet some day, and you think # * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp # This port adds no further stipulations. I forfeit any copyright interest. import md5 def md5crypt(password, salt, magic='$1$'): # /* The password first, since that is what is most unknown */ /* Then our magic string */ /* Then the raw salt */ m = md5.new() m.update(password + magic + salt) # /* Then just as many characters of the MD5(pw,salt,pw) */ mixin = md5.md5(password + salt + password).digest() for i in range(0, len(password)): m.update(mixin[i % 16]) # /* Then something really weird... */ # Also really broken, as far as I can tell. -m i = len(password) while i: if i & 1: m.update('\x00') else: m.update(password[0]) i >>= 1 final = m.digest() # /* and now, just to make sure things don't run too fast */ for i in range(1000): m2 = md5.md5() if i & 1: m2.update(password) else: m2.update(final) if i % 3: m2.update(salt) if i % 7: m2.update(password) if i & 1: m2.update(final) else: m2.update(password) final = m2.digest() # This is the bit that uses to64() in the original code. itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' rearranged = '' for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)): v = ord(final[a]) << 16 | ord(final[b]) << 8 | ord(final[c]) for i in range(4): rearranged += itoa64[v & 0x3f]; v >>= 6 v = ord(final[11]) for i in range(2): rearranged += itoa64[v & 0x3f]; v >>= 6 return magic + salt + '$' + rearranged if __name__ == '__main__': def test(clear_password, the_hash): magic, salt = the_hash[1:].split('$')[:2] magic = '$' + magic + '$' return md5crypt(clear_password, salt, magic) == the_hash test_cases = ( (' ', '$1$yiiZbNIH$YiCsHZjcTkYd31wkgW8JF.'), ('pass', '$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90'), ('____fifteen____', '$1$s9lUWACI$Kk1jtIVVdmT01p0z3b/hw1'), ('____sixteen_____', '$1$dL3xbVZI$kkgqhCanLdxODGq14g/tW1'), ('____seventeen____', '$1$NaH5na7J$j7y8Iss0hcRbu3kzoJs5V.'), ('__________thirty-three___________', '$1$HO7Q6vzJ$yGwp2wbL5D7eOVzOmxpsy.'), ('apache', '$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1') ) for clearpw, hashpw in test_cases: if test(clearpw, hashpw): print '%s: pass' % clearpw else: print '%s: FAIL' % clearpw PKm‡‡9GS¨EY Y acct_mgr/md5crypt.pyc;ò ÞpeD]2\ZZeeeƒo deGHqwdeGHqwWndS(Ns$1$cCsªtiƒ} | i|||ƒti|||ƒiƒ} x2t dt |ƒƒD]}| i| |dƒqTWt |ƒ}xC|o;|d@o| idƒn| i|dƒ|dL}q‚W| iƒ} x¶t dƒD]¨}tiƒ} |d@o| i|ƒn| i| ƒ|do| i|ƒn|do| i|ƒn|d@o| i| ƒn| i|ƒ| iƒ} qÞWd}d }xºdd d fddd fd ddfdddfdddffD]v\}} }t| |ƒd>t| | ƒd>Bt| |ƒB}x0t dƒD]"}|||d@7}|d L}q)WqÙWt| dƒ}x0t d ƒD]"}|||d@7}|d L}qpW||d|SdS(Niiisièiis@./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzsii i iiii iii ii?i s$(smd5snewsmsupdatespasswordsmagicssaltsdigestsmixinsrangeslensisfinalsm2sitoa64s rearrangedsasbscsordsv(spasswordssaltsmagicsascsitoa64s rearrangedsisvsmsbsmixinsm2sfinal((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/md5crypt.pysmd5cryptsR            C4 $  s__main__cCsE|didƒd \}}d|d}t|||ƒ|jSdS(Nis$i(sthe_hashssplitsmagicssaltsmd5cryptsclear_password(sclear_passwordsthe_hashsmagicssalt((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/md5crypt.pystestMss s"$1$yiiZbNIH$YiCsHZjcTkYd31wkgW8JF.spasss"$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90s____fifteen____s"$1$s9lUWACI$Kk1jtIVVdmT01p0z3b/hw1s____sixteen_____s"$1$dL3xbVZI$kkgqhCanLdxODGq14g/tW1s____seventeen____s"$1$NaH5na7J$j7y8Iss0hcRbu3kzoJs5V.s!__________thirty-three___________s"$1$HO7Q6vzJ$yGwp2wbL5D7eOVzOmxpsy.sapaches%$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1s%s: passs%s: FAIL(smd5smd5crypts__name__stests test_casessclearpwshashpw(shashpwsclearpwsmd5cryptstests test_casessmd5((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/md5crypt.pys?s  ;  E   PK!‡‡9AÛ“acct_mgr/notification.py# -*- coding: utf-8 -*- # # Copyright (C) 2008 Pedro Algarvio # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Pedro Algarvio import re from trac import __version__ from trac.core import * from trac.admin import IAdminPanelProvider from trac.config import Option, ListOption from trac.web.chrome import ITemplateProvider from trac.notification import NotifyEmail from trac.util.text import CRLF from trac.util.translation import _ from pkg_resources import resource_filename from api import IAccountChangeListener class AccountChangeListener(Component): implements(IAccountChangeListener) _notify_actions = ListOption( 'account-manager', 'notify_actions', [], doc="""Comma separated list of actions to notify of. Available actions 'new', 'change', 'delete'.""") # IAccountChangeListener methods def user_created(self, username, password): if 'new' in self._notify_actions: notifier = AccountChangeNotification(self.env) notifier.notify(username, 'New user registration') def user_password_changed(self, username, password): if 'change' in self._notify_actions: notifier = AccountChangeNotification(self.env) notifier.notify(username, 'Password reset for user') def user_deleted(self, username): if 'delete' in self._notify_actions: notifier = AccountChangeNotification(self.env) notifier.notify(username, 'Deleted User') class AccountChangeNotification(NotifyEmail): template_name = 'user_changes_email.txt' _recipients = Option( 'account-manager', 'account_changes_notify_addresses', '', """List of email addresses that get notified of user changes, ie, new user, password change and delete user.""") def get_recipients(self, resid): recipients = self._recipients.split() return (recipients,[]) def notify(self, username, action): self.data.update({ 'account': { 'username': username, 'action': action }, 'login': { 'link': self.env.abs_href.login(), } }) projname = self.config.get('project', 'name') subject = '[%s] %s: %s' % (projname, action, username) NotifyEmail.notify(self, username, subject) def send(self, torcpts, ccrcpts, mime_headers={}): from email.MIMEText import MIMEText from email.Utils import formatdate stream = self.template.generate(**self.data) body = stream.render('text') projname = self.config.get('project', 'name') public_cc = self.config.getbool('notification', 'use_public_cc') headers = {} headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__ headers['X-Trac-Version'] = __version__ headers['X-Trac-Project'] = projname headers['X-URL'] = self.config.get('project', 'url') headers['Precedence'] = 'bulk' headers['Auto-Submitted'] = 'auto-generated' headers['Subject'] = self.subject headers['From'] = (self.from_name or projname, self.from_email) headers['Reply-To'] = self.replyto_email def build_addresses(rcpts): """Format and remove invalid addresses""" return filter(lambda x: x, \ [self.get_smtp_address(addr) for addr in rcpts]) def remove_dup(rcpts, all): """Remove duplicates""" tmp = [] for rcpt in rcpts: if not rcpt in all: tmp.append(rcpt) all.append(rcpt) return (tmp, all) toaddrs = build_addresses(torcpts) ccaddrs = build_addresses(ccrcpts) recipients = [] (toaddrs, recipients) = remove_dup(toaddrs, recipients) (ccaddrs, recipients) = remove_dup(ccaddrs, recipients) # if there is not valid recipient, leave immediately if len(recipients) < 1: self.env.log.info('no recipient for account change notification') return pcc = [] if public_cc: pcc += ccaddrs if toaddrs: headers['To'] = ', '.join(toaddrs) if pcc: headers['Cc'] = ', '.join(pcc) headers['Date'] = formatdate() # sanity check if not self._charset.body_encoding: try: dummy = body.encode('ascii') except UnicodeDecodeError: raise TracError(_("Ticket contains non-ASCII chars. " \ "Please change encoding setting")) msg = MIMEText(body, 'plain') # Message class computes the wrong type from MIMEText constructor, # which does not take a Charset object as initializer. Reset the # encoding type to force a new, valid evaluation del msg['Content-Transfer-Encoding'] msg.set_charset(self._charset) self.add_headers(msg, headers); self.add_headers(msg, mime_headers); self.env.log.info("Sending SMTP notification to %s:%d to %s" % (self.smtp_server, self.smtp_port, recipients)) msgtext = msg.as_string() # Ensure the message complies with RFC2822: use CRLF line endings recrlf = re.compile("\r?\n") msgtext = CRLF.join(recrlf.split(msgtext)) self.server.sendmail(msg['From'], recipients, msgtext) class AccountChangeNotificationAdminPanel(Component): implements(IAdminPanelProvider, ITemplateProvider) # IAdminPageProvider def get_admin_panels(self, req): if req.perm.has_permission('TRAC_ADMIN'): yield ('accounts', 'Accounts', 'notification', 'Notification') def render_admin_panel(self, req, cat, page, path_info): if page == 'notification': return self._do_config(req) def _do_config(self, req): if req.method == 'POST': self.config.set( 'account-manager', 'account_changes_notify_addresses', ' '.join(req.args.get('notify_addresses').strip('\n').split())) self.config.set('account-manager', 'notify_actions', ','.join(req.args.getlist('notify_actions')) ) self.config.save() notify_addresses = self.config.get( 'account-manager', 'account_changes_notify_addresses').split() notify_actions = self.config.getlist('account-manager', 'notify_actions') data = dict(notify_actions=notify_actions, notify_addresses=notify_addresses) return 'admin_accountsnotification.html', data # ITemplateProvider def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [resource_filename(__name__, 'templates')] PKm‡‡9:­+À2'2'acct_mgr/notification.pyc;ò Þptd„gi}|D]}|ˆi|ƒƒq~ƒSdS(s#Format and remove invalid addressescCs|S(N(sx(sx((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysesN(sfiltersappends_[1]srcptssaddrsselfsget_smtp_address(srcptss_[1]saddr(sself(s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysbuild_addressescs cCsQg}x:|D]2}||j o|i|ƒ|i|ƒq q W||fSdS(sRemove duplicatesN(stmpsrcptssrcptsallsappend(srcptssallstmpsrcpt((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pys remove_duphs is,no recipient for account change notifications, sTosCcsDatesasciis?Ticket contains non-ASCII chars. Please change encoding settingsplainsContent-Transfer-Encodings(Sending SMTP notification to %s:%d to %ss ? (9semail.MIMETextsMIMETexts email.Utilss formatdatesselfstemplatesgeneratesdatasstreamsrendersbodysconfigsgetsprojnamesgetbools public_ccsheaderss __version__ssubjects from_names from_emails replyto_emailsbuild_addressess remove_dupstorcptsstoaddrssccrcptssccaddrss recipientsslensenvslogsinfospccsjoins_charsets body_encodingsencodesdummysUnicodeDecodeErrors TracErrors_smsgs set_charsets add_headerss mime_headerss smtp_servers smtp_ports as_stringsmsgtextsrescompilesrecrlfsCRLFssplitsserverssendmail(sselfstorcptssccrcptss mime_headerssstreamsrecrlfsccaddrss public_ccsMIMETextsprojnamestoaddrssbuild_addressess formatdatesmsgspccsbodys recipientssmsgtextsdummys remove_dupsheaders((sselfs:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pyssendQsb             & (s__name__s __module__s template_namesOptions _recipientssget_recipientssnotifyssend(((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysAccountChangeNotification5s     s#AccountChangeNotificationAdminPanelcBsBtZeeeƒd„Zd„Zd„Zd„Zd„Z RS(Nccs+|iidƒoddddfVndS(Ns TRAC_ADMINsaccountssAccountss notifications Notification(sreqspermshas_permission(sselfsreq((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysget_admin_panels£scCs"|djo|i|ƒSndS(Ns notification(spagesselfs _do_configsreq(sselfsreqscatspages path_info((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysrender_admin_panel§s cCsÙ|idjov|iidddi|iidƒidƒi ƒƒƒ|iidddi|ii dƒƒƒ|ii ƒn|iiddƒi ƒ}|ii ddƒ}td|d|ƒ}d |fSdS( NsPOSTsaccount-managers account_changes_notify_addressess snotify_addressess snotify_actionss,sadmin_accountsnotification.html(sreqsmethodsselfsconfigssetsjoinsargssgetsstripssplitsgetlistssavesnotify_addressessnotify_actionssdictsdata(sselfsreqsnotify_addressessdatasnotify_actions((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pys _do_config«s+    cCsgSdS(N((sself((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysget_htdocs_dirs¾scCsttdƒgSdS(Ns templates(sresource_filenames__name__(sself((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pysget_templates_dirsÁs( s__name__s __module__s implementssIAdminPanelProvidersITemplateProvidersget_admin_panelssrender_admin_panels _do_configsget_htdocs_dirssget_templates_dirs(((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pys#AccountChangeNotificationAdminPanelŸs      (srestracs __version__s trac.cores trac.adminsIAdminPanelProviders trac.configsOptions ListOptionstrac.web.chromesITemplateProviderstrac.notifications NotifyEmailstrac.util.textsCRLFstrac.util.translations_s pkg_resourcessresource_filenamesapisIAccountChangeListeners ComponentsAccountChangeListenersAccountChangeNotifications#AccountChangeNotificationAdminPanel(s ListOptions NotifyEmailsIAdminPanelProvidersOptionsAccountChangeListenersresresource_filenamesITemplateProviders#AccountChangeNotificationAdminPanelsCRLFsIAccountChangeListeners __version__s_sAccountChangeNotification((s:build/bdist.darwin-9.5.0-i386/egg/acct_mgr/notification.pys? s         jPK!‡‡9 Ò‚  acct_mgr/pwhash.py# -*- coding: utf8 -*- # # Copyright (C) 2007 Matthew Good # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good from binascii import hexlify import md5, sha from trac.core import * from trac.config import Option from md5crypt import md5crypt from acct_mgr.util import urandom class IPasswordHashMethod(Interface): def generate_hash(user, password): pass def test_hash(user, password, hash): pass class HtPasswdHashMethod(Component): implements(IPasswordHashMethod) def generate_hash(self, user, password): password = password.encode('utf-8') return htpasswd(password) def check_hash(self, user, password, hash): password = password.encode('utf-8') hash2 = htpasswd(password, hash) return hash == hash2 class HtDigestHashMethod(Component): implements(IPasswordHashMethod) realm = Option('account-manager', 'htdigest_realm') def generate_hash(self, user, password): user,password,realm = _encode(user, password, self.realm) return ':'.join([realm, htdigest(user, realm, password)]) def check_hash(self, user, password, hash): user,password,realm = _encode(user, password, self.realm) return hash == self.generate_hash(user, password) def _encode(*args): return [a.encode('utf-8') for a in args] # check for the availability of the "crypt" module for checking passwords on # Unix-like platforms # MD5 is still used when adding/updating passwords try: from crypt import crypt except ImportError: crypt = None def salt(): s = '' v = long(hexlify(urandom(4)), 16) itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' for i in range(8): s += itoa64[v & 0x3f]; v >>= 6 return s def htpasswd(password, salt_=None): # TODO need unit test of generating new hash if salt_ is None: salt_ = salt() if crypt is None: salt_ = '$apr1$' + salt_ if salt_.startswith('$apr1$'): return md5crypt(password, salt_[6:].split('$')[0], '$apr1$') elif salt_.startswith('{SHA}'): return '{SHA}' + sha.new(password).digest().encode('base64')[:-1] elif crypt is None: # crypt passwords are only supported on Unix-like systems raise NotImplementedError('The "crypt" module is unavailable ' 'on this platform.') else: return crypt(password, salt_) def htdigest(user, realm, password): p = ':'.join([user, realm, password]) return md5.new(p).hexdigest() PKm‡‡9P“ª_»»acct_mgr/pwhash.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good import os from trac.core import * from trac.config import Configuration from trac.versioncontrol import RepositoryManager from api import IPasswordStore from util import EnvRelativePathOption class SvnServePasswordStore(Component): """PasswordStore implementation for reading svnserve's password file format """ implements(IPasswordStore) filename = EnvRelativePathOption('account-manager', 'password_file', doc='Path to the users file. Leave ' 'blank to locate the users file ' 'by reading svnserve.conf') def __init__(self): repo_dir = RepositoryManager(self.env).repository_dir self._svnserve_conf = Configuration(os.path.join(repo_dir, 'svnserve.conf')) self._userconf = None def _config(self): filename = self.filename if not filename: self._svnserve_conf.parse_if_needed() filename = self._svnserve_conf['general'].getpath('password-db') if self._userconf is None or filename != self._userconf.filename: self._userconf = Configuration(filename) else: self._userconf.parse_if_needed() return self._userconf _config = property(_config) # IPasswordStore methods def get_users(self): return [user for (user,password) in self._config.options('users')] def has_user(self, user): return user in self._config['users'] def set_password(self, user, password): cfg = self._config cfg.set('users', user, password) cfg.save() def check_password(self, user, password): if self.has_user(user): return password == self._config.get('users', user) return None def delete_user(self, user): cfg = self._config cfg.remove('users', user) cfg.save() PKm‡‡9F‘G5VVacct_mgr/svnserve.pyc;ò ÞpscCs'|i}|id|ƒ|iƒdS(Nsusers(sselfs_configscfgsremovesuserssave(sselfsuserscfg((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/svnserve.pys delete_userCs (s__name__s __module__s__doc__s implementssIPasswordStoresEnvRelativePathOptionsfilenames__init__s_configspropertys get_usersshas_users set_passwordscheck_passwords delete_user(((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/svnserve.pysSvnServePasswordStores          ( soss trac.cores trac.configs Configurationstrac.versioncontrolsRepositoryManagersapisIPasswordStoresutilsEnvRelativePathOptions ComponentsSvnServePasswordStore(sIPasswordStoresSvnServePasswordStoresRepositoryManagers ConfigurationsossEnvRelativePathOption((s6build/bdist.darwin-9.5.0-i386/egg/acct_mgr/svnserve.pys? s     PK!‡‡9—$ ]acct_mgr/util.py# -*- coding: utf8 -*- # # Copyright (C) 2005 Matthew Good # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good import os from trac.config import Option class EnvRelativePathOption(Option): def __get__(self, instance, owner): if instance is None: return self path = super(EnvRelativePathOption, self).__get__(instance, owner) if not path: return path return os.path.normpath(os.path.join(instance.env.path, path)) # os.urandom was added in Python 2.4 # try to fall back on pseudo-random values if it's not available try: from os import urandom except ImportError: from random import randrange def urandom(n): return ''.join([chr(randrange(256)) for _ in xrange(n)]) PKm‡‡93A acct_mgr/util.pyc;ò Þp # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good import base64 import os import random import string from trac import perm, util from trac.core import * from trac.config import IntOption from trac.notification import NotificationSystem, NotifyEmail from trac.prefs import IPreferencePanelProvider from trac.web import auth from trac.web.api import IAuthenticator from trac.web.main import IRequestHandler, IRequestFilter from trac.web import chrome from trac.web.chrome import INavigationContributor, ITemplateProvider from genshi.builder import tag from api import AccountManager from acct_mgr.util import urandom def _create_user(req, env, check_permissions=True): mgr = AccountManager(env) user = req.args.get('user') name = req.args.get('name') email = req.args.get('email') acctmgr = {'username' : user, 'name' : name, 'email' : email, } error = TracError('') error.acctmgr = acctmgr if not user: error.message = 'Username cannot be empty.' raise error if mgr.has_user(user): error.message = 'Another account with that name already exists.' raise error if check_permissions: # disallow registration of accounts which have existing permissions permission_system = perm.PermissionSystem(env) if permission_system.get_user_permissions(user) != \ permission_system.get_user_permissions('authenticated'): error.message = 'Another account with that name already exists.' raise error 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 mgr.set_password(user, password) db = env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT count(*) FROM session " "WHERE sid=%s AND authenticated=1", (user,)) exists, = cursor.fetchone() if not exists: cursor.execute("INSERT INTO session " "(sid, authenticated, last_visit) " "VALUES (%s, 1, 0)", (user,)) for key in ('name', 'email'): value = req.args.get(key) if not value: continue cursor.execute("UPDATE session_attribute SET value=%s " "WHERE name=%s AND sid=%s AND authenticated=1", (value, key, user)) if not cursor.rowcount: cursor.execute("INSERT INTO session_attribute " "(sid,authenticated,name,value) " "VALUES (%s,1,%s,%s)", (user, key, value)) db.commit() class SingleUserNofification(NotifyEmail): """Helper class used for account email notifications which should only be sent to one persion, not including the rest of the normally CCed users """ _username = None def get_recipients(self, resid): return ([resid],[]) def get_smtp_address(self, addr): """Overrides `get_smtp_address` in order to prevent CCing users other than the user whose password is being reset. """ if addr == self._username: return NotifyEmail.get_smtp_address(self, addr) else: return None def notify(self, username, subject): # save the username for use in `get_smtp_address` self._username = username old_public_cc = self.config.getbool('notification', 'use_public_cc') # override public cc option so that the user's email is included in the To: field self.config.set('notification', 'use_public_cc', 'true') try: NotifyEmail.notify(self, username, subject) finally: self.config.set('notification', 'use_public_cc', old_public_cc) class PasswordResetNotification(SingleUserNofification): template_name = 'reset_password_email.txt' def notify(self, username, password): self.data.update({ 'account': { 'username': username, 'password': password, }, 'login': { 'link': self.env.abs_href.login(), } }) projname = self.config.get('project', 'name') subject = '[%s] Trac password reset for user: %s' % (projname, username) SingleUserNofification.notify(self, username, subject) class AccountModule(Component): """Allows users to change their password, reset their password if they've forgotten it, or delete their account. The settings for the AccountManager module must be set in trac.ini in order to use this. """ implements(IPreferencePanelProvider, IRequestHandler, ITemplateProvider, 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.') def __init__(self): self._write_check(log=True) def _write_check(self, log=False): writable = AccountManager(self.env).get_all_supporting_stores('set_password') if not writable and log: self.log.warn('AccountModule is disabled because the password ' 'store does not support writing.') return writable #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 = AccountManager(self.env).find_user_store(req.authname) if user_store in writable: yield 'account', 'Account' def render_preference_panel(self, req, panel): data = {'account': self._do_account(req)} return 'prefs_account.html', data # IRequestHandler methods def match_request(self, req): return (req.path_info == '/reset_password' and self._write_check(log=True)) def process_request(self, req): data = {'reset': self._do_reset_password(req)} return 'reset_password.html', data, None # IRequestFilter methods def pre_process_request(self, req, handler): 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): redirect_url = req.href.prefs('account') if req.path_info != redirect_url: req.redirect(redirect_url) return (template, data, content_type) # 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): return (self.env.is_component_enabled(AccountModule) and NotificationSystem(self.env).smtp_enabled and self._write_check()) reset_password_enabled = property(reset_password_enabled) def _do_account(self, req): if not req.authname or req.authname == 'anonymous': req.redirect(req.href.wiki()) action = req.args.get('action') delete_enabled = AccountManager(self.env).supports('delete_user') data = {'delete_enabled': delete_enabled} force_change_password = req.session.get('force_change_passwd', False) if req.method == 'POST': if action == 'save': data.update(self._do_change_password(req)) if force_change_password: del(req.session['force_change_passwd']) req.session.save() chrome.add_notice(req, MessageWrapper(tag( "Thank you for taking the time to update your password." ))) force_change_password = False elif action == 'delete' and delete_enabled: data.update(self._do_delete(req)) else: data.update({'error': 'Invalid action'}) if force_change_password: chrome.add_warning(req, MessageWrapper(tag( "You are required to change password because of a recent " "password change request. ", tag.b("Please change your password now.")))) return data def _do_reset_password(self, req): if req.authname and req.authname != 'anonymous': return {'logged_in': True} if req.method != 'POST': return {} username = req.args.get('username') email = req.args.get('email') if not username: return {'error': 'Username is required'} if not email: return {'error': 'Email is required'} notifier = PasswordResetNotification(self.env) if email != notifier.email_map.get(username): return {'error': 'The email and username do not ' 'match a known account.'} new_password = self._random_password() notifier.notify(username, new_password) mgr = AccountManager(self.env) mgr.set_password(username, new_password) if mgr.force_passwd_change: db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("UPDATE session_attribute SET value=%s " "WHERE name=%s AND sid=%s AND authenticated=1", (1, "force_change_passwd", username)) if not cursor.rowcount: cursor.execute("INSERT INTO session_attribute " "(sid,authenticated,name,value) " "VALUES (%s,1,%s,%s)", (username, "force_change_passwd", 1)) db.commit() return {'sent_to_email': email} def _random_password(self): return ''.join([random.choice(self._password_chars) for _ in xrange(self.password_length)]) def _do_change_password(self, req): user = req.authname mgr = AccountManager(self.env) old_password = req.args.get('old_password') if not old_password: return {'save_error': 'Old Password cannot be empty.'} if not mgr.check_password(user, old_password): return {'save_error': 'Old Password is incorrect.'} password = req.args.get('password') if not password: return {'save_error': 'Password cannot be empty.'} if password != req.args.get('password_confirm'): return {'save_error': 'The passwords must match.'} mgr.set_password(user, password) return {'message': 'Password successfully updated.'} def _do_delete(self, req): user = req.authname mgr = AccountManager(self.env) password = req.args.get('password') if not password: return {'delete_error': 'Password cannot be empty.'} if not mgr.check_password(user, password): return {'delete_error': 'Password is incorrect.'} mgr.delete_user(user) req.redirect(req.href.logout()) # ITemplateProvider def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). """ return [] def get_templates_dirs(self): """Return the absolute path of the directory containing the provided ClearSilver templates. """ from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] class RegistrationModule(Component): """Provides users the ability to register a new account. Requires configuration of the AccountManager module in trac.ini. """ implements(INavigationContributor, IRequestHandler, ITemplateProvider) def __init__(self): self._enable_check(log=True) def _enable_check(self, log=False): writable = AccountManager(self.env).supports('set_password') ignore_case = auth.LoginModule(self.env).ignore_case if log: if not writable: self.log.warn('RegistrationModule is disabled because the ' 'password store does not support writing.') if ignore_case: self.log.warn('RegistrationModule is disabled because ' 'ignore_auth_case is enabled in trac.ini. ' 'This setting needs disabled to support ' 'registration.') return writable and not ignore_case #INavigationContributor methods def get_active_navigation_item(self, req): return 'register' def get_navigation_items(self, req): if not self._enable_check(): return if req.authname == 'anonymous': yield 'metanav', 'register', tag.a("Register", href=req.href.register()) # IRequestHandler methods def match_request(self, req): return req.path_info == '/register' and self._enable_check(log=True) def process_request(self, req): if req.authname != 'anonymous': req.redirect(req.href.prefs('account')) action = req.args.get('action') data = {'acctmgr' : { 'username' : None, 'name' : None, 'email' : None, }, } if req.method == 'POST' and action == 'create': try: _create_user(req, self.env) except TracError, e: data['registration_error'] = e.message data['acctmgr'] = e.acctmgr else: req.redirect(req.href.login()) data['reset_password_enabled'] = \ (self.env.is_component_enabled(AccountModule) and NotificationSystem(self.env).smtp_enabled) return 'register.html', data, None # ITemplateProvider def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). """ return [] def get_templates_dirs(self): """Return the absolute path of the directory containing the provided ClearSilver templates. """ from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def if_enabled(func): def wrap(self, *args, **kwds): if not self.enabled: return None return func(self, *args, **kwds) return wrap class LoginModule(auth.LoginModule): implements(ITemplateProvider) def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login'): req.environ['REMOTE_USER'] = self._remote_user(req) return auth.LoginModule.authenticate(self, req) authenticate = if_enabled(authenticate) match_request = if_enabled(auth.LoginModule.match_request) def process_request(self, req): if req.path_info.startswith('/login') and req.authname == 'anonymous': data = { 'referer': self._referer(req), 'reset_password_enabled': AccountModule(self.env).reset_password_enabled } if req.method == 'POST': data['login_error'] = 'Invalid username or password' return 'login.html', data, None return auth.LoginModule.process_request(self, req) def _do_login(self, req): if not req.remote_user: req.redirect(self.env.abs_href()) return auth.LoginModule._do_login(self, req) def _remote_user(self, req): user = req.args.get('user') password = req.args.get('password') if not user or not password: return None if AccountManager(self.env).check_password(user, password): return user return None def _redirect_back(self, req): """Redirect the user back to the URL she came from.""" referer = self._referer(req) if referer and not referer.startswith(req.base_url): # don't redirect to external sites referer = None req.redirect(referer or self.env.abs_href()) def _referer(self, req): return req.args.get('referer') or req.get_header('Referer') def enabled(self): # Users should disable the built-in authentication to use this one return not self.env.is_component_enabled(auth.LoginModule) enabled = property(enabled) # ITemplateProvider def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). """ return [] def get_templates_dirs(self): """Return the absolute path of the directory containing the provided ClearSilver templates. """ from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] class MessageWrapper(object): """Wrapper for add_warning and add_notice to work around the requirement for a % operator.""" def __init__(self, body): self.body = body def __mod__(self, rhs): return self.body class EmailVerificationNotification(SingleUserNofification): template_name = 'verify_email.txt' def notify(self, username, token): self.data.update({ 'account': { 'username': username, 'token': token, }, 'verify': { 'link': self.env.abs_href.verify_email(token=token), } }) projname = self.config.get('project', 'name') subject = '[%s] Trac email verification for user: %s' % (projname, username) SingleUserNofification.notify(self, username, subject) class EmailVerificationModule(Component): implements(IRequestFilter, IRequestHandler) # IRequestFilter methods def pre_process_request(self, req, handler): if not req.session.authenticated: # Anonymous users should register and perms should be tweaked so # that anonymous users can't edit wiki pages and change or create # tickets. As such, this email verifying code won't be used on them return handler if handler is not self and 'email_verification_token' in req.session: chrome.add_warning(req, MessageWrapper(tag.span( 'Your permissions have been limited until you ', tag.a(href=req.href.verify_email())( 'verify your email address')))) req.perm = perm.PermissionCache(self.env, 'anonymous') return handler def post_process_request(self, req, template, data, content_type): if not req.session.authenticated: # Anonymous users should register and perms should be tweaked so # that anonymous users can't edit wiki pages and change or create # tickets. As such, this email verifying code won't be used on them return template, data, content_type if req.session.get('email') != req.session.get('email_verification_sent_to'): req.session['email_verification_token'] = self._gen_token() req.session['email_verification_sent_to'] = req.session.get('email') self._send_email(req) chrome.add_notice(req, MessageWrapper(tag.span( 'An email has been sent to ', req.session['email'], ' with a token to ', tag.a(href=req.href.verify_email())( 'verify your new email address')))) return template, data, content_type # IRequestHandler methods def match_request(self, req): return req.path_info == '/verify_email' def process_request(self, req): if 'email_verification_token' not in req.session: chrome.add_notice(req, 'Your email is already verified') elif req.method != 'POST': pass elif 'resend' in req.args: self._send_email(req) chrome.add_notice(req, 'A notification email has been resent to %s.', req.session.get('email')) elif 'verify' in req.args: if req.args['token'] == req.session['email_verification_token']: del req.session['email_verification_token'] chrome.add_notice(req, 'Thank you for verifying your email address') else: chrome.add_warning(req, 'Invalid verification token') data = {} if 'token' in req.args: data['token'] = req.args['token'] return 'verify_email.html', data, None def _gen_token(self): return base64.urlsafe_b64encode(urandom(6)) def _send_email(self, req): notifier = EmailVerificationNotification(self.env) notifier.notify(req.authname, req.session['email_verification_token']) PKm‡‡9 D‹Ë=x=xacct_mgr/web_ui.pyc;ò Þp Accounts: Configuration

Accounts: Configuration

Password Reset Yes No
PK!‡‡9æ#bJ772acct_mgr/templates/admin_accountsnotification.html Accounts: Notification Configuration

Accounts: Notification Configuration

Account Notification

Set the following options in order to be notified of account creation, password reset and account deletion.

Notification Actions

This is a list of actions which you can enable or disable by checking the checkboxes.

Get notified of new account creation
Get notified of password reset
Get notified of account deletion

Notification Recipient Addresses

These are the email addresses that get notified of the above actions.

PK!‡‡9æ…%oo#acct_mgr/templates/admin_users.html Accounts

Manage User Accounts

${registration_error}

Add Account:

Add a new user account.

${password_change_error}

Change Password:

Change a user's password.

This password store does not support listing users

${deletion_error}

  AccountNameEmailLast Login
${account.username} ${account.name} ${account.email} ${account.last_visit}
PK!‡‡9ÒEÇ66acct_mgr/templates/login.html Login PK!‡‡9…@‘û__%acct_mgr/templates/prefs_account.html
${select('*')}

Delete Account

Error

$account.delete_error

Account

Error

$account.error

$account.message

Change Password

Error

$account.save_error

PK!‡‡9[H™ii acct_mgr/templates/register.html Register

Register an account

Error

$registration_error

Required
Optional

Entering your email address will enable you to reset your password if you ever forget it.

PK!‡‡9ú ^##&acct_mgr/templates/reset_password.html Reset Password

Reset Password

Already logged in

You're already logged in. If you need to change your password please use the Account Preferences page.

A new password has been emailed to you at ${reset.sent_to_email}.

If you've forgot your password enter your username and email address below and you'll be emailed a new password.

Error

$reset.error

$reset.message

PK!‡‡9Ù;dÖÖ+acct_mgr/templates/reset_password_email.txtYour Trac password has been reset. Here is your account information: Login URL: <${login.link}> Username: ${account.username} Password: ${account.password} -- ${project.name} <${project.url}> ${project.descr} PK!‡‡9̘ff)acct_mgr/templates/user_changes_email.txt${account.action} for user ${account.username} -- ${project.name} <${project.url}> ${project.descr} PK!‡‡9{"KÀ$acct_mgr/templates/verify_email.html Verify Email PK!‡‡9` w©ÖÖ#acct_mgr/templates/verify_email.txtPlease visit the following URL to confirm your email address. Verification URL: <$verify.link> Username: $account.username Verification Token: $account.token -- ${project.name} <${project.url}> ${project.descr} PKl‡‡9“×2EGG-INFO/dependency_links.txt PKl‡‡95J`>>EGG-INFO/entry_points.txt[trac.plugins] acct_mgr.web_ui = acct_mgr.web_ui acct_mgr.admin = acct_mgr.admin acct_mgr.db = acct_mgr.db acct_mgr.htfile = acct_mgr.htfile acct_mgr.http = acct_mgr.http acct_mgr.notification = acct_mgr.notification acct_mgr.pwhash = acct_mgr.pwhash acct_mgr.svnserve = acct_mgr.svnserve acct_mgr.api = acct_mgr.api PKl‡‡9²œ„õ//EGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: TracAccountManager Version: 0.2.1dev-r4679 Summary: User account management plugin for Trac Home-page: http://trac-hacks.org/wiki/AccountManagerPlugin Author: Matthew Good Author-email: trac@matt-good.net License: THE BEER-WARE LICENSE Description: UNKNOWN Platform: UNKNOWN PKm‡‡9ÑàsTÈÈEGG-INFO/SOURCES.txtREADME setup.cfg setup.py TracAccountManager.egg-info/PKG-INFO TracAccountManager.egg-info/SOURCES.txt TracAccountManager.egg-info/dependency_links.txt TracAccountManager.egg-info/entry_points.txt TracAccountManager.egg-info/top_level.txt TracAccountManager.egg-info/zip-safe acct_mgr/__init__.py acct_mgr/admin.py acct_mgr/api.py acct_mgr/db.py acct_mgr/htfile.py acct_mgr/http.py acct_mgr/md5crypt.py acct_mgr/notification.py acct_mgr/pwhash.py acct_mgr/svnserve.py acct_mgr/util.py acct_mgr/web_ui.py acct_mgr/templates/admin_accountsconfig.html acct_mgr/templates/admin_accountsnotification.html acct_mgr/templates/admin_users.html acct_mgr/templates/login.html acct_mgr/templates/prefs_account.html acct_mgr/templates/register.html acct_mgr/templates/reset_password.html acct_mgr/templates/reset_password_email.txt acct_mgr/templates/user_changes_email.txt acct_mgr/templates/verify_email.html acct_mgr/templates/verify_email.txt acct_mgr/tests/__init__.py acct_mgr/tests/db.py acct_mgr/tests/htfile.py acct_mgr/tests/functional/__init__.py acct_mgr/tests/functional/smtpd.py acct_mgr/tests/functional/testcases.py acct_mgr/tests/functional/testenv.py acct_mgr/tests/functional/tester.py contrib/sessionstore_convert.pyPKl‡‡9µgÓ EGG-INFO/top_level.txtacct_mgr PK%‡‡9“×2EGG-INFO/zip-safe PK!‡‡9¤acct_mgr/__init__.pyPKm‡‡9¼dn ††¤2acct_mgr/__init__.pycPK!‡‡9*%$Î(Î(¤ëacct_mgr/admin.pyPKm‡‡9®˜¨õ/õ/¤è)acct_mgr/admin.pycPK!‡‡9 Ómd""¤ Zacct_mgr/api.pyPKm‡‡9¶¦"³‹/‹/¤»|acct_mgr/api.pycPK!‡‡9»¨§¦ ¦ ¤t¬acct_mgr/db.pyPKm‡‡9qO.ÀÀ¤Fºacct_mgr/db.pycPK!‡‡9t”ï¤3Êacct_mgr/htfile.pyPKm‡‡9‘Ä#·Q!Q!¤fàacct_mgr/htfile.pycPK!‡‡9»³Ä‹Íͤèacct_mgr/http.pyPKm‡‡9E&Ö¨¨¤ãacct_mgr/http.pycPK!‡‡9ê♼ ¤ºacct_mgr/md5crypt.pyPKm‡‡9GS¨EY Y ¤ùacct_mgr/md5crypt.pycPK!‡‡9AÛ“¤…%acct_mgr/notification.pyPKm‡‡9:­+À2'2'¤ØAacct_mgr/notification.pycPK!‡‡9 Ò‚  ¤Aiacct_mgr/pwhash.pyPKm‡‡9P“ª_»»¤tacct_mgr/pwhash.pycPK!‡‡9ì|  ¤{ˆacct_mgr/svnserve.pyPKm‡‡9F‘G5VV¤Ã‘acct_mgr/svnserve.pycPK!‡‡9—$ ]¤L acct_mgr/util.pyPKm‡‡93A ¤Ž¤acct_mgr/util.pycPK!‡‡9u†žuÛVÛV¤Lªacct_mgr/web_ui.pyPKm‡‡9 D‹Ë=x=x¤Wacct_mgr/web_ui.pycPK!‡‡9×úqMM,¤Åyacct_mgr/templates/admin_accountsconfig.htmlPK!‡‡9æ#bJ772¤\acct_mgr/templates/admin_accountsnotification.htmlPK!‡‡9æ…%oo#¤ãˆacct_mgr/templates/admin_users.htmlPK!‡‡9ÒEÇ66¤“—acct_mgr/templates/login.htmlPK!‡‡9…@‘û__%¤acct_mgr/templates/prefs_account.htmlPK!‡‡9[H™ii ¤¦¥acct_mgr/templates/register.htmlPK!‡‡9ú ^##&¤M®acct_mgr/templates/reset_password.htmlPK!‡‡9Ù;dÖÖ+¤´µacct_mgr/templates/reset_password_email.txtPK!‡‡9̘ff)¤Ó¶acct_mgr/templates/user_changes_email.txtPK!‡‡9{"KÀ$¤€·acct_mgr/templates/verify_email.htmlPK!‡‡9` w©ÖÖ#¤Ò¼acct_mgr/templates/verify_email.txtPKl‡‡9“×2¤é½EGG-INFO/dependency_links.txtPKl‡‡95J`>>¤%¾EGG-INFO/entry_points.txtPKl‡‡9²œ„õ//¤š¿EGG-INFO/PKG-INFOPKm‡‡9ÑàsTÈȤøÀEGG-INFO/SOURCES.txtPKl‡‡9µgÓ ¤òÅEGG-INFO/top_level.txtPK%‡‡9“×2¤/ÆEGG-INFO/zip-safePK))@ _Æ