| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
# |
|---|
| 3 |
# Copyright (C) 2005 Matthew Good <trac@matt-good.net> |
|---|
| 4 |
# |
|---|
| 5 |
# "THE BEER-WARE LICENSE" (Revision 42): |
|---|
| 6 |
# <trac@matt-good.net> wrote this file. As long as you retain this notice you |
|---|
| 7 |
# can do whatever you want with this stuff. If we meet some day, and you think |
|---|
| 8 |
# this stuff is worth it, you can buy me a beer in return. Matthew Good |
|---|
| 9 |
# |
|---|
| 10 |
# Author: Matthew Good <trac@matt-good.net> |
|---|
| 11 |
|
|---|
| 12 |
import inspect |
|---|
| 13 |
|
|---|
| 14 |
from trac.core import * |
|---|
| 15 |
from trac.config import Option |
|---|
| 16 |
from trac.perm import PermissionSystem |
|---|
| 17 |
from trac.util import sorted |
|---|
| 18 |
from trac.util.datefmt import format_datetime |
|---|
| 19 |
from trac.web.chrome import ITemplateProvider |
|---|
| 20 |
from webadmin.web_ui import IAdminPageProvider |
|---|
| 21 |
|
|---|
| 22 |
from acct_mgr.api import AccountManager |
|---|
| 23 |
from acct_mgr.web_ui import _create_user |
|---|
| 24 |
|
|---|
| 25 |
def _getoptions(cls): |
|---|
| 26 |
if isinstance(cls, Component): |
|---|
| 27 |
cls = cls.__class__ |
|---|
| 28 |
return [(name, value) for name, value in inspect.getmembers(cls) |
|---|
| 29 |
if isinstance(value, Option)] |
|---|
| 30 |
|
|---|
| 31 |
class AccountManagerAdminPage(Component): |
|---|
| 32 |
|
|---|
| 33 |
implements(IAdminPageProvider) |
|---|
| 34 |
|
|---|
| 35 |
def __init__(self): |
|---|
| 36 |
self.account_manager = AccountManager(self.env) |
|---|
| 37 |
|
|---|
| 38 |
# IAdminPageProvider |
|---|
| 39 |
def get_admin_pages(self, req): |
|---|
| 40 |
if req.perm.has_permission('TRAC_ADMIN'): |
|---|
| 41 |
yield ('accounts', 'Accounts', 'config', 'Configuration') |
|---|
| 42 |
yield ('accounts', 'Accounts', 'users', 'Users') |
|---|
| 43 |
|
|---|
| 44 |
def process_admin_request(self, req, cat, page, path_info): |
|---|
| 45 |
if page == 'config': |
|---|
| 46 |
return self._do_config(req) |
|---|
| 47 |
elif page == 'users': |
|---|
| 48 |
return self._do_users(req) |
|---|
| 49 |
|
|---|
| 50 |
def _do_config(self, req): |
|---|
| 51 |
if req.method == 'POST': |
|---|
| 52 |
selected_class = req.args.get('selected') |
|---|
| 53 |
self.config.set('account-manager', 'password_store', selected_class) |
|---|
| 54 |
selected = self.account_manager.password_store |
|---|
| 55 |
for attr, option in _getoptions(selected): |
|---|
| 56 |
newvalue = req.args.get('%s.%s' % (selected_class, attr)) |
|---|
| 57 |
if newvalue is not None: |
|---|
| 58 |
self.config.set(option.section, option.name, newvalue) |
|---|
| 59 |
self.config.save() |
|---|
| 60 |
try: |
|---|
| 61 |
selected = self.account_manager.password_store |
|---|
| 62 |
except AttributeError: |
|---|
| 63 |
selected = None |
|---|
| 64 |
sections = [ |
|---|
| 65 |
{'name': store.__class__.__name__, |
|---|
| 66 |
'classname': store.__class__.__name__, |
|---|
| 67 |
'selected': store is selected, |
|---|
| 68 |
'options': [ |
|---|
| 69 |
{'label': attr, |
|---|
| 70 |
'name': '%s.%s' % (store.__class__.__name__, attr), |
|---|
| 71 |
'value': option.__get__(store, store), |
|---|
| 72 |
} |
|---|
| 73 |
for attr, option in _getoptions(store) |
|---|
| 74 |
], |
|---|
| 75 |
} for store in self.account_manager.stores |
|---|
| 76 |
] |
|---|
| 77 |
sections = sorted(sections, key=lambda i: i['name']) |
|---|
| 78 |
req.hdf['sections'] = sections |
|---|
| 79 |
return 'admin_accountsconfig.cs', None |
|---|
| 80 |
|
|---|
| 81 |
def _do_users(self, req): |
|---|
| 82 |
perm = PermissionSystem(self.env) |
|---|
| 83 |
listing_enabled = self.account_manager.supports('get_users') |
|---|
| 84 |
create_enabled = self.account_manager.supports('set_password') |
|---|
| 85 |
delete_enabled = self.account_manager.supports('delete_user') |
|---|
| 86 |
|
|---|
| 87 |
req.hdf['listing_enabled'] = listing_enabled |
|---|
| 88 |
req.hdf['create_enabled'] = create_enabled |
|---|
| 89 |
req.hdf['delete_enabled'] = delete_enabled |
|---|
| 90 |
|
|---|
| 91 |
if req.method == 'POST': |
|---|
| 92 |
if req.args.get('add'): |
|---|
| 93 |
if create_enabled: |
|---|
| 94 |
try: |
|---|
| 95 |
_create_user(req, self.env, check_permissions=False) |
|---|
| 96 |
except TracError, e: |
|---|
| 97 |
req.hdf['registration.error'] = e.message |
|---|
| 98 |
else: |
|---|
| 99 |
req.hdf['registration_error'] = 'The password store does ' \ |
|---|
| 100 |
'not support creating users' |
|---|
| 101 |
elif req.args.get('remove'): |
|---|
| 102 |
if delete_enabled: |
|---|
| 103 |
sel = req.args.get('sel') |
|---|
| 104 |
sel = isinstance(sel, list) and sel or [sel] |
|---|
| 105 |
for account in sel: |
|---|
| 106 |
self.account_manager.delete_user(account) |
|---|
| 107 |
else: |
|---|
| 108 |
req.hdf['deletion_error'] = 'The password store does not ' \ |
|---|
| 109 |
'support deleting users' |
|---|
| 110 |
if listing_enabled: |
|---|
| 111 |
accounts = {} |
|---|
| 112 |
for username in self.account_manager.get_users(): |
|---|
| 113 |
accounts[username] = {'username': username} |
|---|
| 114 |
|
|---|
| 115 |
for username, name, email in self.env.get_known_users(): |
|---|
| 116 |
account = accounts.get(username) |
|---|
| 117 |
if account: |
|---|
| 118 |
account['name'] = name |
|---|
| 119 |
account['email'] = email |
|---|
| 120 |
|
|---|
| 121 |
db = self.env.get_db_cnx() |
|---|
| 122 |
cursor = db.cursor() |
|---|
| 123 |
cursor.execute("SELECT sid,last_visit FROM session WHERE authenticated=1") |
|---|
| 124 |
for username, last_visit in cursor: |
|---|
| 125 |
account = accounts.get(username) |
|---|
| 126 |
if account and last_visit: |
|---|
| 127 |
account['last_visit'] = format_datetime(last_visit) |
|---|
| 128 |
|
|---|
| 129 |
req.hdf['accounts'] = sorted(accounts.itervalues(), |
|---|
| 130 |
key=lambda acct: acct['username']) |
|---|
| 131 |
|
|---|
| 132 |
return 'admin_users.cs', None |
|---|
| 133 |
|
|---|
| 134 |
# ITemplateProvider |
|---|
| 135 |
|
|---|
| 136 |
def get_htdocs_dirs(self): |
|---|
| 137 |
"""Return the absolute path of a directory containing additional |
|---|
| 138 |
static resources (such as images, style sheets, etc). |
|---|
| 139 |
""" |
|---|
| 140 |
return [] |
|---|
| 141 |
|
|---|
| 142 |
def get_templates_dirs(self): |
|---|
| 143 |
"""Return the absolute path of the directory containing the provided |
|---|
| 144 |
ClearSilver templates. |
|---|
| 145 |
""" |
|---|
| 146 |
from pkg_resources import resource_filename |
|---|
| 147 |
return [resource_filename(__name__, 'templates')] |
|---|