Leho Kraav avatar Leho Kraav committed b20474a

trac:#2456 patches from morris 3 years ago

Comments (0)

Files changed (3)

t2456-morris-3y-back/user_API_r5898.1.diff

+Index: trac/env.py
+===================================================================
+--- trac/env.py	(revision 5898)
++++ trac/env.py	(working copy)
+@@ -25,6 +25,7 @@
+ from trac.core import Component, ComponentManager, implements, Interface, \
+                       ExtensionPoint, TracError
+ from trac.db import DatabaseManager
++from trac.user import UserManager, User
+ from trac.util import get_pkginfo
+ from trac.versioncontrol import RepositoryManager
+ from trac.web.href import Href
+@@ -329,29 +330,16 @@
+         self.log = logger_factory(logtype, logfile, self.log_level, self.path,
+                                   format=format)
+ 
+-    def get_known_users(self, cnx=None):
++    def get_known_users(self):
+         """Generator that yields information about all known users, i.e. users
+         that have logged in to this Trac environment and possibly set their name
+         and email.
+ 
+         This function generates one tuple for every user, of the form
+         (username, name, email) ordered alpha-numerically by username.
+-
+-        @param cnx: the database connection; if ommitted, a new connection is
+-                    retrieved
+         """
+-        if not cnx:
+-            cnx = self.get_db_cnx()
+-        cursor = cnx.cursor()
+-        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value "
+-                       "FROM session AS s "
+-                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid "
+-                       "  and n.authenticated=1 AND n.name = 'name') "
+-                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid "
+-                       "  AND e.authenticated=1 AND e.name = 'email') "
+-                       "WHERE s.authenticated=1 ORDER BY s.sid")
+-        for username,name,email in cursor:
+-            yield username, name, email
++        for user in UserManager(self).get_all_users():
++            yield user.username, user['name'], user['email']
+ 
+     def backup(self, dest=None):
+         """Simple SQLite-specific backup of the database.
+Index: trac/notification.py
+===================================================================
+--- trac/notification.py	(revision 5898)
++++ trac/notification.py	(working copy)
+@@ -22,6 +22,7 @@
+ from trac import __version__
+ from trac.config import BoolOption, IntOption, Option
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.text import CRLF
+ from trac.web.chrome import Chrome
+ 
+@@ -158,6 +159,7 @@
+     smtp_port = 25
+     from_email = 'trac+tickets@localhost'
+     subject = ''
++    server = None
+     template_name = None
+     nodomaddr_re = re.compile(r'[\w\d_\.\-]+')
+     addrsep_re = re.compile(r'[;\s,]+')
+@@ -179,12 +181,7 @@
+         self._init_pref_encoding()
+         domains = self.env.config.get('notification', 'ignore_domains', '')
+         self._ignore_domains = [x.strip() for x in domains.lower().split(',')]
+-        # Get the email addresses of all known users
+-        self.email_map = {}
+-        for username, name, email in self.env.get_known_users(self.db):
+-            if email:
+-                self.email_map[username] = email
+-                
++
+     def _init_pref_encoding(self):
+         from email.Charset import Charset, QP, BASE64
+         self._charset = Charset()
+@@ -270,8 +267,10 @@
+         if not is_email(address):
+             if address == 'anonymous':
+                 return None
+-            if self.email_map.has_key(address):
+-                address = self.email_map[address]
++            
++            email = UserManager(self.env).get_user(address)['email']
++            if email and len(email) > 0:
++                address = email
+             elif NotifyEmail.nodomaddr_re.match(address):
+                 if self.config.getbool('notification', 'use_short_addr'):
+                     return address
+Index: trac/perm.py
+===================================================================
+--- trac/perm.py	(revision 5898)
++++ trac/perm.py	(working copy)
+@@ -20,6 +20,7 @@
+ 
+ from trac.config import ExtensionOption, OrderedExtensionsOption
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.compat import set
+ from trac.util.translation import _
+ 
+@@ -157,7 +158,8 @@
+         db = self.env.get_db_cnx()
+         cursor = db.cursor()
+         result = set()
+-        users = set([u[0] for u in self.env.get_known_users()])
++        users = set([username for username
++                    in UserManager(self.env).get_usernames()])
+         for user in users:
+             userperms = self.get_user_permissions(user)
+             for group in permissions:
+Index: trac/prefs/web_ui.py
+===================================================================
+--- trac/prefs/web_ui.py	(revision 5914)
++++ trac/prefs/web_ui.py	(working copy)
+@@ -22,6 +22,7 @@
+ 
+ from trac.core import *
+ from trac.prefs.api import IPreferencePanelProvider
++from trac.user import UserManager, User
+ from trac.util.datefmt import all_timezones, get_timezone
+ from trac.util.translation import _
+ from trac.web import HTTPNotFound, IRequestHandler
+@@ -93,8 +94,16 @@
+                 self._do_save(req)
+             req.redirect(req.href.prefs(panel or None))
+ 
++        name = email = ''
++        if req.authname != 'anonymous':
++            user = UserManager(self.env).get_user(req.authname)
++            name, email = user['name'], user['email']
++        else:
++            name = req.session.get('name')
++            email = req.session.get('email')
+         return 'prefs_%s.html' % (panel or 'general'), {
+-            'settings': {'session': req.session, 'session_id': req.session.sid},
++            'settings': {'session': req.session, 'session_id': req.session.sid,
++                         'name': name, 'email': email},
+             'timezones': all_timezones, 'timezone': get_timezone
+         }
+ 
+@@ -118,9 +127,19 @@
+                 elif field == 'newsid' and val:
+                     req.session.change_sid(val)
+                 else:
+-                    req.session[field] = val
+-            elif field in req.args and field in req.session:
+-                del req.session[field]
++                    if req.authname != 'anonymous' and \
++                            field in ('name', 'email'):
++                        user = UserManager(self.env).get_user(req.authname)
++                        user[field] = val
++                    else:
++                        req.session[field] = val
++            elif field in req.args:
++                if req.authname != 'anonymous' and \
++                        field in ('name', 'email'):
++                    user = UserManager(self.env).get_user(req.authname)
++                    user[field] = ''
++                elif field in req.session:
++                    del req.session[field]
+ 
+     def _do_load(self, req):
+         if req.authname == 'anonymous':
+Index: trac/test.py
+===================================================================
+--- trac/test.py	(revision 5898)
++++ trac/test.py	(working copy)
+@@ -187,7 +187,7 @@
+     def get_db_cnx(self):
+         return self.db
+ 
+-    def get_known_users(self, db):
++    def get_known_users(self):
+         return self.known_users
+ 
+ 
+Index: trac/ticket/admin.py
+===================================================================
+--- trac/ticket/admin.py	(revision 5898)
++++ trac/ticket/admin.py	(working copy)
+@@ -107,11 +107,7 @@
+ 
+         if self.config.getbool('ticket', 'restrict_owner'):
+             perm = PermissionSystem(self.env)
+-            def valid_owner(username):
+-                return perm.get_user_permissions(username).get('TICKET_MODIFY')
+-            data['owners'] = [username for username, name, email
+-                              in self.env.get_known_users()
+-                              if valid_owner(username)]
++            data['owners'] = perm.get_users_with_permission('TICKET_MODIFY')
+         else:
+             data['owners'] = None
+ 
+Index: trac/ticket/report.py
+===================================================================
+--- trac/ticket/report.py	(revision 5898)
++++ trac/ticket/report.py	(working copy)
+@@ -27,6 +27,7 @@
+ from trac.core import *
+ from trac.db import get_column_names
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import sorted
+ from trac.util.datefmt import format_datetime, format_time
+ from trac.util.text import to_unicode, unicode_urlencode
+@@ -369,9 +370,7 @@
+         # Get the email addresses of all known users
+         email_map = {}
+         if Chrome(self.env).show_email_addresses:
+-            for username, name, email in self.env.get_known_users():
+-                if email:
+-                    email_map[username] = email
++            email_map = UserManager(self.env).get_attribute_mapper('email')
+ 
+         data.update({'header_groups': header_groups,
+                      'row_groups': row_groups,
+Index: trac/timeline/web_ui.py
+===================================================================
+--- trac/timeline/web_ui.py	(revision 5912)
++++ trac/timeline/web_ui.py	(working copy)
+@@ -30,6 +30,7 @@
+ from trac.perm import IPermissionRequestor
+ from trac.timeline.api import ITimelineEventProvider, TimelineEvent
+ from trac.util.compat import sorted
++from trac.user import UserManager
+ from trac.util.datefmt import format_date, format_datetime, parse_date, \
+                               to_timestamp, utc, pretty_timedelta
+ from trac.util.text import to_unicode
+@@ -167,9 +168,7 @@
+             # Get the email addresses of all known users
+             email_map = {}
+             if Chrome(self.env).show_email_addresses:
+-                for username, name, email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+             data['email_map'] = email_map
+             return 'timeline.rss', data, 'application/rss+xml'
+ 
+Index: trac/user.py
+===================================================================
+--- trac/user.py	(revision 0)
++++ trac/user.py	(revision 0)
+@@ -0,0 +1,352 @@
++# -*- coding: utf-8 -*-
++#
++# Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org
++# All rights reserved.
++#
++# This software is licensed as described in the file COPYING, which
++# you should have received as part of this distribution. The terms
++# are also available at http://trac.edgewall.org/wiki/TracLicense.
++#
++# This software consists of voluntary contributions made by many
++# individuals. For the exact contribution history, see the revision
++# history and logs, available at http://trac.edgewall.org/log/.
++#
++# Author: Waldemar Kornewald, wkornewald@haiku-os.org
++
++from trac.core import *
++from trac.config import *
++
++
++class IUserStore(Interface):
++    """
++    Extension point interface for backends that store known users.
++    """
++
++    def supports_user_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return success
++        """
++
++    def get_usernames(self):
++        """
++        Generator that yields an ordered list of known usernames.
++
++        @return username
++        """
++
++    def check_password(self, username, password):
++        """
++        Checks if the password is correct for the given user.
++        """
++
++    def change_password(self, username, password):
++        """
++        Changes a user's password.
++
++        @return success
++        """
++
++    def delete_user(self, username):
++        """
++        Deletes a user.
++
++        @return success
++            Returns False if the user didn't exist.
++        """
++
++
++class IUserAttributeProvider(Interface):
++    """
++    Extension point interface for backends that store user attributes.
++    """
++
++    def supports_attribute_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def get_user_attribute(self, username, attribute):
++        """
++        Returns a user attribute.
++
++        If the attribute is not set it returs an empty string.
++        If the attribute is not supported None is returned.
++        """
++
++    def set_user_attribute(self, username, attribute, value):
++        """
++        Sets a user attribute.
++
++        @return success
++            Returns False if setting the attribute is not supported.
++        """
++
++    def delete_all_user_attributes(self, username):
++        """
++        Deletes all of the given user's attributes.
++        """
++
++class UserAttributeMapper(object):
++    """
++    Dict that paps usernames to attributes and caches those values to
++    increase efficiency.
++    """
++
++    _usernames_cache = None
++    _cache = {}
++
++    def __init__(self, attribute, manager):
++        self._attribute = attribute
++        self._manager = manager
++
++    def __getitem__(self, username):
++        if not self._usernames_cache:
++            self._usernames_cache = [username for username
++                                     in self._manager.get_usernames()]
++        if username not in self._usernames_cache:
++            return None
++        if username not in self._cache:
++            value = self._manager.get_user(username)[self._attribute]
++            if value:
++                self._cache[username] = value
++            return value
++        else:
++            return self._cache.get(username)
++
++    def __contains__(self, username):
++        value = self[username]
++        return value is not None and len(value) > 0
++
++    def __setitem__(self,key,value):
++        raise NotImplementedError, "dict is immutable"
++    def __delitem__(self,key):
++        raise NotImplementedError, "dict is immutable"
++    def clear(self):
++        raise NotImplementedError, "dict is immutable"
++    def setdefault(self,k,default=None):
++        raise NotImplementedError, "dict is immutable"
++    def popitem(self):
++        raise NotImplementedError, "dict is immutable"
++    def update(self,other):
++        raise NotImplementedError, "dict is immutable"
++
++
++class UserManager(Component):
++    """
++    Component responsible for managing users and user attributes.
++    """
++
++    store = ExtensionOption('users',
++        'store', IUserStore, 'SessionUserStore',
++        doc="""The user store that should be used for authentication
++            (''since 0.11'').""")
++    attribute_providers = OrderedExtensionsOption('users',
++        'attribute_providers', IUserAttributeProvider,
++        doc="""Ordered list of user attribute providers (''since 0.11'').""")
++
++    # public API
++
++    def supports_operation(self, operation):
++        if self.store.supports_user_operation(operation):
++            return True
++        for provider in self.attribute_providers:
++            if self.provider.supports_attribute_operation(operation):
++                return True
++        return False
++
++    # IUserStore methods
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return User object or None if the user couldn't be created
++        """
++        if not self.store.supports_user_operation('create_user'):
++            return None
++        if self.store.create_user(username, password):
++            return User(username, self.store, self.attribute_providers)
++
++    def get_user(self, username):
++        """
++        Returns a User object for the username.
++
++        @return User object or None if the user doesn't exist
++        """
++        return User(username, self.store, self.attribute_providers)
++
++    def get_all_users(self):
++        """
++        Generator for User objects.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        for username in self.store.get_usernames():
++            yield User(username, self.store, self.attribute_providers)
++
++    def get_usernames(self):
++        """
++        Generator for usernames.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        return self.store.get_usernames()
++
++    def get_attribute_mapper(self, attribute):
++        return UserAttributeMapper(attribute, self)
++
++
++class User(object):
++    """
++    Object representing a user.
++    """
++
++    def __init__(self, username, store, attribute_providers):
++        self._username = username
++        self.store = store
++        self.attribute_providers = attribute_providers
++
++    # public API
++
++    @property
++    def username(self):
++        return self._username
++
++    def exists(self):
++        return self.username in [username for username
++                                 in self.store.get_usernames()]
++
++    # IUserStore methods
++
++    def check_password(self, password):
++        if not self.store.supports_user_operation('check_password'):
++            return False
++        return self.store.check_password(self.username, password)
++
++    def change_password(self, password):
++        if not self.store.supports_user_operation('change_password'):
++            return False
++        return self.store.change_password(self.username, password)
++
++    def delete(self):
++        if not self.store.supports_user_operation('delete_user'):
++            return False
++        self.delete_all_attributes()
++        return self.store.delete_user(self.username)
++
++    # IUserAttributeProvider methods
++
++    def __getitem__(self, attribute):
++        for provider in self.attribute_providers:
++            if not provider.supports_attribute_operation('get_user_attribute'):
++                continue
++            value = provider.get_user_attribute(self.username, attribute)
++            if value is not None:
++                return value
++        return None
++
++    def __setitem__(self, attribute, value):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('set_user_attribute') \
++                    and provider.set_user_attribute(self.username, attribute,
++                                                    value):
++                return True
++        return False
++
++    def delete_all_attributes(self):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('delete_all_user_attributes'):
++                provider.delete_all_user_attributes(username)
++
++
++class SessionUserStore(Component):
++    """
++    Component for managing authenticated users stored in sessions.
++    """
++
++    implements(IUserStore)
++
++    def supports_user_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_usernames(self):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT sid FROM session "
++                       "WHERE authenticated=1 "
++                       "ORDER BY sid")
++        for row in cursor:
++            yield row[0]
++
++
++class SessionUserAttributeProvider(Component):
++    """
++    Component for providing user attributes via Trac sessions.
++    """
++
++    implements(IUserAttributeProvider)
++
++    def supports_attribute_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_user_attribute(self, username, attribute):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        row = cursor.fetchone()
++        if row:
++            return row[0]
++
++        # if the attribute doesn't exist we return an empty string to
++        # indicate that the attribute is supported, but not set
++        return ''
++
++    def set_user_attribute(self, username, attribute, value):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++
++        if not value or value == '':
++            cursor.execute("DELETE FROM session_attribute "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                          (username, attribute))
++            db.commit()
++            return True
++
++        # check if attribute exists
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        if cursor.fetchone():
++            # update the attribute
++            cursor.execute("UPDATE session_attribute SET value=%s "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                           (value, username, attribute))
++        else:
++            # create new attribute
++            cursor.execute("INSERT INTO session_attribute "
++                           "(sid,authenticated,name,value) "
++                           "VALUES(%s,1,%s,%s)",
++                           (username, attribute, value))
++        db.commit()
++        return True
++
++    def delete_all_user_attributes(self, username):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("DELETE FROM session_attribute "
++                       "WHERE sid=%s AND authenticated=1",
++                      (username,))
++        db.commit()
+Index: trac/versioncontrol/web_ui/log.py
+===================================================================
+--- trac/versioncontrol/web_ui/log.py	(revision 5898)
++++ trac/versioncontrol/web_ui/log.py	(working copy)
+@@ -23,6 +23,7 @@
+ from trac.context import Context
+ from trac.core import *
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import Ranges
+ from trac.util.datefmt import http_date
+ from trac.util.html import html
+@@ -193,9 +194,7 @@
+         if format == 'rss':
+             # Get the email addresses of all known users
+             if Chrome(self.env).show_email_addresses:
+-                for username,name,email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+         elif format == 'changelog':
+             for rev in revs:
+                 changeset = changes[rev]

t2456-morris-3y-back/user_API_r5898.diff

+Index: trac/env.py
+===================================================================
+--- trac/env.py	(revision 5898)
++++ trac/env.py	(working copy)
+@@ -25,6 +25,7 @@
+ from trac.core import Component, ComponentManager, implements, Interface, \
+                       ExtensionPoint, TracError
+ from trac.db import DatabaseManager
++from trac.user import UserManager, User
+ from trac.util import get_pkginfo
+ from trac.versioncontrol import RepositoryManager
+ from trac.web.href import Href
+@@ -329,29 +330,16 @@
+         self.log = logger_factory(logtype, logfile, self.log_level, self.path,
+                                   format=format)
+ 
+-    def get_known_users(self, cnx=None):
++    def get_known_users(self):
+         """Generator that yields information about all known users, i.e. users
+         that have logged in to this Trac environment and possibly set their name
+         and email.
+ 
+         This function generates one tuple for every user, of the form
+         (username, name, email) ordered alpha-numerically by username.
+-
+-        @param cnx: the database connection; if ommitted, a new connection is
+-                    retrieved
+         """
+-        if not cnx:
+-            cnx = self.get_db_cnx()
+-        cursor = cnx.cursor()
+-        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value "
+-                       "FROM session AS s "
+-                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid "
+-                       "  and n.authenticated=1 AND n.name = 'name') "
+-                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid "
+-                       "  AND e.authenticated=1 AND e.name = 'email') "
+-                       "WHERE s.authenticated=1 ORDER BY s.sid")
+-        for username,name,email in cursor:
+-            yield username, name, email
++        for user in UserManager(self).get_all_users():
++            yield user.username, user['name'], user['email']
+ 
+     def backup(self, dest=None):
+         """Simple SQLite-specific backup of the database.
+Index: trac/notification.py
+===================================================================
+--- trac/notification.py	(revision 5898)
++++ trac/notification.py	(working copy)
+@@ -22,6 +22,7 @@
+ from trac import __version__
+ from trac.config import BoolOption, IntOption, Option
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.text import CRLF
+ from trac.web.chrome import Chrome
+ 
+@@ -158,6 +159,7 @@
+     smtp_port = 25
+     from_email = 'trac+tickets@localhost'
+     subject = ''
++    server = None
+     template_name = None
+     nodomaddr_re = re.compile(r'[\w\d_\.\-]+')
+     addrsep_re = re.compile(r'[;\s,]+')
+@@ -177,14 +179,7 @@
+         self.longaddr_re = re.compile(r'^\s*(.*)\s+<(%s)>\s*$' % addrfmt);
+         self._use_tls = self.env.config.getbool('notification', 'use_tls')
+         self._init_pref_encoding()
+-        domains = self.env.config.get('notification', 'ignore_domains', '')
+-        self._ignore_domains = [x.strip() for x in domains.lower().split(',')]
+-        # Get the email addresses of all known users
+-        self.email_map = {}
+-        for username, name, email in self.env.get_known_users(self.db):
+-            if email:
+-                self.email_map[username] = email
+-                
++
+     def _init_pref_encoding(self):
+         from email.Charset import Charset, QP, BASE64
+         self._charset = Charset()
+@@ -270,8 +265,10 @@
+         if not is_email(address):
+             if address == 'anonymous':
+                 return None
+-            if self.email_map.has_key(address):
+-                address = self.email_map[address]
++            
++            email = UserManager(self.env).get_user(address)['email']
++            if email and len(email) > 0:
++                address = email
+             elif NotifyEmail.nodomaddr_re.match(address):
+                 if self.config.getbool('notification', 'use_short_addr'):
+                     return address
+Index: trac/perm.py
+===================================================================
+--- trac/perm.py	(revision 5898)
++++ trac/perm.py	(working copy)
+@@ -20,6 +20,7 @@
+ 
+ from trac.config import ExtensionOption, OrderedExtensionsOption
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.compat import set
+ from trac.util.translation import _
+ 
+@@ -157,7 +158,8 @@
+         db = self.env.get_db_cnx()
+         cursor = db.cursor()
+         result = set()
+-        users = set([u[0] for u in self.env.get_known_users()])
++        users = set([username for username
++                    in UserManager(self.env).get_usernames()])
+         for user in users:
+             userperms = self.get_user_permissions(user)
+             for group in permissions:
+Index: trac/prefs/web_ui.py
+===================================================================
+--- trac/prefs/web_ui.py	(revision 5914)
++++ trac/prefs/web_ui.py	(working copy)
+@@ -22,6 +22,7 @@
+ 
+ from trac.core import *
+ from trac.prefs.api import IPreferencePanelProvider
++from trac.user import UserManager, User
+ from trac.util.datefmt import all_timezones, get_timezone
+ from trac.util.translation import _
+ from trac.web import HTTPNotFound, IRequestHandler
+@@ -93,8 +94,16 @@
+                 self._do_save(req)
+             req.redirect(req.href.prefs(panel or None))
+ 
++        name = email = ''
++        if req.authname != 'anonymous':
++            user = UserManager(self.env).get_user(req.authname)
++            name, email = user['name'], user['email']
++        else:
++            name = req.session.get('name')
++            email = req.session.get('email')
+         return 'prefs_%s.html' % (panel or 'general'), {
+-            'settings': {'session': req.session, 'session_id': req.session.sid},
++            'settings': {'session': req.session, 'session_id': req.session.sid,
++                         'name': name, 'email': email},
+             'timezones': all_timezones, 'timezone': get_timezone
+         }
+ 
+@@ -118,9 +127,19 @@
+                 elif field == 'newsid' and val:
+                     req.session.change_sid(val)
+                 else:
+-                    req.session[field] = val
+-            elif field in req.args and field in req.session:
+-                del req.session[field]
++                    if req.authname != 'anonymous' and \
++                            field in ('name', 'email'):
++                        user = UserManager(self.env).get_user(req.authname)
++                        user[field] = val
++                    else:
++                        req.session[field] = val
++            elif field in req.args:
++                if req.authname != 'anonymous' and \
++                        field in ('name', 'email'):
++                    user = UserManager(self.env).get_user(req.authname)
++                    user[field] = ''
++                elif field in req.session:
++                    del req.session[field]
+ 
+     def _do_load(self, req):
+         if req.authname == 'anonymous':
+Index: trac/test.py
+===================================================================
+--- trac/test.py	(revision 5898)
++++ trac/test.py	(working copy)
+@@ -187,7 +187,7 @@
+     def get_db_cnx(self):
+         return self.db
+ 
+-    def get_known_users(self, db):
++    def get_known_users(self):
+         return self.known_users
+ 
+ 
+Index: trac/ticket/admin.py
+===================================================================
+--- trac/ticket/admin.py	(revision 5898)
++++ trac/ticket/admin.py	(working copy)
+@@ -107,11 +107,7 @@
+ 
+         if self.config.getbool('ticket', 'restrict_owner'):
+             perm = PermissionSystem(self.env)
+-            def valid_owner(username):
+-                return perm.get_user_permissions(username).get('TICKET_MODIFY')
+-            data['owners'] = [username for username, name, email
+-                              in self.env.get_known_users()
+-                              if valid_owner(username)]
++            data['owners'] = perm.get_users_with_permission('TICKET_MODIFY')
+         else:
+             data['owners'] = None
+ 
+Index: trac/ticket/report.py
+===================================================================
+--- trac/ticket/report.py	(revision 5898)
++++ trac/ticket/report.py	(working copy)
+@@ -27,6 +27,7 @@
+ from trac.core import *
+ from trac.db import get_column_names
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import sorted
+ from trac.util.datefmt import format_datetime, format_time
+ from trac.util.text import to_unicode, unicode_urlencode
+@@ -369,9 +370,7 @@
+         # Get the email addresses of all known users
+         email_map = {}
+         if Chrome(self.env).show_email_addresses:
+-            for username, name, email in self.env.get_known_users():
+-                if email:
+-                    email_map[username] = email
++            email_map = UserManager(self.env).get_attribute_mapper('email')
+ 
+         data.update({'header_groups': header_groups,
+                      'row_groups': row_groups,
+Index: trac/timeline/web_ui.py
+===================================================================
+--- trac/timeline/web_ui.py	(revision 5912)
++++ trac/timeline/web_ui.py	(working copy)
+@@ -30,6 +30,7 @@
+ from trac.perm import IPermissionRequestor
+ from trac.timeline.api import ITimelineEventProvider, TimelineEvent
+ from trac.util.compat import sorted
++from trac.user import UserManager
+ from trac.util.datefmt import format_date, format_datetime, parse_date, \
+                               to_timestamp, utc, pretty_timedelta
+ from trac.util.text import to_unicode
+@@ -167,9 +168,7 @@
+             # Get the email addresses of all known users
+             email_map = {}
+             if Chrome(self.env).show_email_addresses:
+-                for username, name, email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+             data['email_map'] = email_map
+             return 'timeline.rss', data, 'application/rss+xml'
+ 
+Index: trac/user.py
+===================================================================
+--- trac/user.py	(revision 0)
++++ trac/user.py	(revision 0)
+@@ -0,0 +1,352 @@
++# -*- coding: utf-8 -*-
++#
++# Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org
++# All rights reserved.
++#
++# This software is licensed as described in the file COPYING, which
++# you should have received as part of this distribution. The terms
++# are also available at http://trac.edgewall.org/wiki/TracLicense.
++#
++# This software consists of voluntary contributions made by many
++# individuals. For the exact contribution history, see the revision
++# history and logs, available at http://trac.edgewall.org/log/.
++#
++# Author: Waldemar Kornewald, wkornewald@haiku-os.org
++
++from trac.core import *
++from trac.config import *
++
++
++class IUserStore(Interface):
++    """
++    Extension point interface for backends that store known users.
++    """
++
++    def supports_user_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return success
++        """
++
++    def get_usernames(self):
++        """
++        Generator that yields an ordered list of known usernames.
++
++        @return username
++        """
++
++    def check_password(self, username, password):
++        """
++        Checks if the password is correct for the given user.
++        """
++
++    def change_password(self, username, password):
++        """
++        Changes a user's password.
++
++        @return success
++        """
++
++    def delete_user(self, username):
++        """
++        Deletes a user.
++
++        @return success
++            Returns False if the user didn't exist.
++        """
++
++
++class IUserAttributeProvider(Interface):
++    """
++    Extension point interface for backends that store user attributes.
++    """
++
++    def supports_attribute_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def get_user_attribute(self, username, attribute):
++        """
++        Returns a user attribute.
++
++        If the attribute is not set it returs an empty string.
++        If the attribute is not supported None is returned.
++        """
++
++    def set_user_attribute(self, username, attribute, value):
++        """
++        Sets a user attribute.
++
++        @return success
++            Returns False if setting the attribute is not supported.
++        """
++
++    def delete_all_user_attributes(self, username):
++        """
++        Deletes all of the given user's attributes.
++        """
++
++class UserAttributeMapper(object):
++    """
++    Dict that paps usernames to attributes and caches those values to
++    increase efficiency.
++    """
++
++    _usernames_cache = None
++    _cache = {}
++
++    def __init__(self, attribute, manager):
++        self._attribute = attribute
++        self._manager = manager
++
++    def __getitem__(self, username):
++        if not self._usernames_cache:
++            self._usernames_cache = [username for username
++                                     in self._manager.get_usernames()]
++        if username not in self._usernames_cache:
++            return None
++        if username not in self._cache:
++            value = self._manager.get_user(username)[self._attribute]
++            if value:
++                self._cache[username] = value
++            return value
++        else:
++            return self._cache.get(username)
++
++    def __contains__(self, username):
++        value = self[username]
++        return value is not None and len(value) > 0
++
++    def __setitem__(self,key,value):
++        raise NotImplementedError, "dict is immutable"
++    def __delitem__(self,key):
++        raise NotImplementedError, "dict is immutable"
++    def clear(self):
++        raise NotImplementedError, "dict is immutable"
++    def setdefault(self,k,default=None):
++        raise NotImplementedError, "dict is immutable"
++    def popitem(self):
++        raise NotImplementedError, "dict is immutable"
++    def update(self,other):
++        raise NotImplementedError, "dict is immutable"
++
++
++class UserManager(Component):
++    """
++    Component responsible for managing users and user attributes.
++    """
++
++    store = ExtensionOption('users',
++        'store', IUserStore, 'SessionUserStore',
++        doc="""The user store that should be used for authentication
++            (''since 0.11'').""")
++    attribute_providers = OrderedExtensionsOption('users',
++        'attribute_providers', IUserAttributeProvider,
++        doc="""Ordered list of user attribute providers (''since 0.11'').""")
++
++    # public API
++
++    def supports_operation(self, operation):
++        if self.store.supports_user_operation(operation):
++            return True
++        for provider in self.attribute_providers:
++            if self.provider.supports_attribute_operation(operation):
++                return True
++        return False
++
++    # IUserStore methods
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return User object or None if the user couldn't be created
++        """
++        if not self.store.supports_user_operation('create_user'):
++            return None
++        if self.store.create_user(username, password):
++            return User(username, self.store, self.attribute_providers)
++
++    def get_user(self, username):
++        """
++        Returns a User object for the username.
++
++        @return User object or None if the user doesn't exist
++        """
++        return User(username, self.store, self.attribute_providers)
++
++    def get_all_users(self):
++        """
++        Generator for User objects.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        for username in self.store.get_usernames():
++            yield User(username, self.store, self.attribute_providers)
++
++    def get_usernames(self):
++        """
++        Generator for usernames.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        return self.store.get_usernames()
++
++    def get_attribute_mapper(self, attribute):
++        return UserAttributeMapper(attribute, self)
++
++
++class User(object):
++    """
++    Object representing a user.
++    """
++
++    def __init__(self, username, store, attribute_providers):
++        self._username = username
++        self.store = store
++        self.attribute_providers = attribute_providers
++
++    # public API
++
++    @property
++    def username(self):
++        return self._username
++
++    def exists(self):
++        return self.username in [username for username
++                                 in self.store.get_usernames()]
++
++    # IUserStore methods
++
++    def check_password(self, password):
++        if not self.store.supports_user_operation('check_password'):
++            return False
++        return self.store.check_password(self.username, password)
++
++    def change_password(self, password):
++        if not self.store.supports_user_operation('change_password'):
++            return False
++        return self.store.change_password(self.username, password)
++
++    def delete(self):
++        if not self.store.supports_user_operation('delete_user'):
++            return False
++        self.delete_all_attributes()
++        return self.store.delete_user(self.username)
++
++    # IUserAttributeProvider methods
++
++    def __getitem__(self, attribute):
++        for provider in self.attribute_providers:
++            if not provider.supports_attribute_operation('get_user_attribute'):
++                continue
++            value = provider.get_user_attribute(self.username, attribute)
++            if value is not None:
++                return value
++        return None
++
++    def __setitem__(self, attribute, value):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('set_user_attribute') \
++                    and provider.set_user_attribute(self.username, attribute,
++                                                    value):
++                return True
++        return False
++
++    def delete_all_attributes(self):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('delete_all_user_attributes'):
++                provider.delete_all_user_attributes(username)
++
++
++class SessionUserStore(Component):
++    """
++    Component for managing authenticated users stored in sessions.
++    """
++
++    implements(IUserStore)
++
++    def supports_user_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_usernames(self):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT sid FROM session "
++                       "WHERE authenticated=1 "
++                       "ORDER BY sid")
++        for row in cursor:
++            yield row[0]
++
++
++class SessionUserAttributeProvider(Component):
++    """
++    Component for providing user attributes via Trac sessions.
++    """
++
++    implements(IUserAttributeProvider)
++
++    def supports_attribute_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_user_attribute(self, username, attribute):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        row = cursor.fetchone()
++        if row:
++            return row[0]
++
++        # if the attribute doesn't exist we return an empty string to
++        # indicate that the attribute is supported, but not set
++        return ''
++
++    def set_user_attribute(self, username, attribute, value):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++
++        if not value or value == '':
++            cursor.execute("DELETE FROM session_attribute "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                          (username, attribute))
++            db.commit()
++            return True
++
++        # check if attribute exists
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        if cursor.fetchone():
++            # update the attribute
++            cursor.execute("UPDATE session_attribute SET value=%s "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                           (value, username, attribute))
++        else:
++            # create new attribute
++            cursor.execute("INSERT INTO session_attribute "
++                           "(sid,authenticated,name,value) "
++                           "VALUES(%s,1,%s,%s)",
++                           (username, attribute, value))
++        db.commit()
++        return True
++
++    def delete_all_user_attributes(self, username):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("DELETE FROM session_attribute "
++                       "WHERE sid=%s AND authenticated=1",
++                      (username,))
++        db.commit()
+Index: trac/versioncontrol/web_ui/log.py
+===================================================================
+--- trac/versioncontrol/web_ui/log.py	(revision 5898)
++++ trac/versioncontrol/web_ui/log.py	(working copy)
+@@ -23,6 +23,7 @@
+ from trac.context import Context
+ from trac.core import *
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import Ranges
+ from trac.util.datefmt import http_date
+ from trac.util.html import html
+@@ -193,9 +194,7 @@
+         if format == 'rss':
+             # Get the email addresses of all known users
+             if Chrome(self.env).show_email_addresses:
+-                for username,name,email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+         elif format == 'changelog':
+             for rev in revs:
+                 changeset = changes[rev]

t2456-morris-3y-back/user_API_r6033.diff

+Index: trac/env.py
+===================================================================
+--- trac/env.py	(revision 6033)
++++ trac/env.py	(working copy)
+@@ -28,6 +28,7 @@
+ from trac.core import Component, ComponentManager, implements, Interface, \
+                       ExtensionPoint, TracError
+ from trac.db import DatabaseManager
++from trac.user import UserManager, User
+ from trac.util import get_pkginfo
+ from trac.versioncontrol import RepositoryManager
+ from trac.web.href import Href
+@@ -339,22 +340,9 @@
+ 
+         This function generates one tuple for every user, of the form
+         (username, name, email) ordered alpha-numerically by username.
+-
+-        @param cnx: the database connection; if ommitted, a new connection is
+-                    retrieved
+         """
+-        if not cnx:
+-            cnx = self.get_db_cnx()
+-        cursor = cnx.cursor()
+-        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value "
+-                       "FROM session AS s "
+-                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid "
+-                       "  and n.authenticated=1 AND n.name = 'name') "
+-                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid "
+-                       "  AND e.authenticated=1 AND e.name = 'email') "
+-                       "WHERE s.authenticated=1 ORDER BY s.sid")
+-        for username,name,email in cursor:
+-            yield username, name, email
++        for user in UserManager(self).get_all_users():
++            yield user.username, user['name'], user['email']
+ 
+     def backup(self, dest=None):
+         """Simple SQLite-specific backup of the database.
+Index: trac/notification.py
+===================================================================
+--- trac/notification.py	(revision 6033)
++++ trac/notification.py	(working copy)
+@@ -22,6 +22,7 @@
+ from trac import __version__
+ from trac.config import BoolOption, IntOption, Option
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.text import CRLF
+ from trac.web.chrome import Chrome
+ 
+@@ -158,6 +159,7 @@
+     smtp_port = 25
+     from_email = 'trac+tickets@localhost'
+     subject = ''
++    server = None
+     template_name = None
+     nodomaddr_re = re.compile(r'[\w\d_\.\-]+')
+     addrsep_re = re.compile(r'[;\s,]+')
+@@ -179,12 +181,7 @@
+         self._init_pref_encoding()
+         domains = self.env.config.get('notification', 'ignore_domains', '')
+         self._ignore_domains = [x.strip() for x in domains.lower().split(',')]
+-        # Get the email addresses of all known users
+-        self.email_map = {}
+-        for username, name, email in self.env.get_known_users(self.db):
+-            if email:
+-                self.email_map[username] = email
+-                
++
+     def _init_pref_encoding(self):
+         from email.Charset import Charset, QP, BASE64
+         self._charset = Charset()
+@@ -270,8 +267,10 @@
+         if not is_email(address):
+             if address == 'anonymous':
+                 return None
+-            if self.email_map.has_key(address):
+-                address = self.email_map[address]
++            
++            email = UserManager(self.env).get_user(address)['email']
++            if email and len(email) > 0:
++                address = email
+             elif NotifyEmail.nodomaddr_re.match(address):
+                 if self.config.getbool('notification', 'use_short_addr'):
+                     return address
+Index: trac/perm.py
+===================================================================
+--- trac/perm.py	(revision 6033)
++++ trac/perm.py	(working copy)
+@@ -20,6 +20,7 @@
+ 
+ from trac.config import ExtensionOption, OrderedExtensionsOption
+ from trac.core import *
++from trac.user import UserManager
+ from trac.util.compat import set
+ from trac.util.translation import _
+ 
+@@ -160,7 +161,8 @@
+         db = self.env.get_db_cnx()
+         cursor = db.cursor()
+         result = set()
+-        users = set([u[0] for u in self.env.get_known_users()])
++        users = set([username for username
++                    in UserManager(self.env).get_usernames()])
+         for user in users:
+             userperms = self.get_user_permissions(user)
+             for group in permissions:
+Index: trac/prefs/web_ui.py
+===================================================================
+--- trac/prefs/web_ui.py	(revision 6033)
++++ trac/prefs/web_ui.py	(working copy)
+@@ -22,6 +22,7 @@
+ 
+ from trac.core import *
+ from trac.prefs.api import IPreferencePanelProvider
++from trac.user import UserManager, User
+ from trac.util.datefmt import all_timezones, get_timezone
+ from trac.util.translation import _
+ from trac.web import HTTPNotFound, IRequestHandler
+@@ -93,8 +94,16 @@
+                 self._do_save(req)
+             req.redirect(req.href.prefs(panel or None))
+ 
++        name = email = ''
++        if req.authname != 'anonymous':
++            user = UserManager(self.env).get_user(req.authname)
++            name, email = user['name'], user['email']
++        else:
++            name = req.session.get('name')
++            email = req.session.get('email')
+         return 'prefs_%s.html' % (panel or 'general'), {
+-            'settings': {'session': req.session, 'session_id': req.session.sid},
++            'settings': {'session': req.session, 'session_id': req.session.sid,
++                         'name': name, 'email': email},
+             'timezones': all_timezones, 'timezone': get_timezone
+         }
+ 
+@@ -118,10 +127,19 @@
+                 elif field == 'newsid' and val:
+                     req.session.change_sid(val)
+                 else:
+-                    req.session[field] = val
+-            elif field in req.session and (field in req.args or
+-                                           field + '_cb' in req.args):
+-                del req.session[field]
++                    if req.authname != 'anonymous' and \
++                            field in ('name', 'email'):
++                        user = UserManager(self.env).get_user(req.authname)
++                        user[field] = val
++                    else:
++                        req.session[field] = val
++            elif field in req.args and field + '_cb' in req.args:
++                if req.authname != 'anonymous' and \
++                        field in ('name', 'email'):
++                    user = UserManager(self.env).get_user(req.authname)
++                    user[field] = ''
++                elif field in req.session:
++                    del req.session[field]
+ 
+     def _do_load(self, req):
+         if req.authname == 'anonymous':
+Index: trac/ticket/admin.py
+===================================================================
+--- trac/ticket/admin.py	(revision 6033)
++++ trac/ticket/admin.py	(working copy)
+@@ -107,11 +107,7 @@
+ 
+         if self.config.getbool('ticket', 'restrict_owner'):
+             perm = PermissionSystem(self.env)
+-            def valid_owner(username):
+-                return perm.get_user_permissions(username).get('TICKET_MODIFY')
+-            data['owners'] = [username for username, name, email
+-                              in self.env.get_known_users()
+-                              if valid_owner(username)]
++            data['owners'] = perm.get_users_with_permission('TICKET_MODIFY')
+         else:
+             data['owners'] = None
+ 
+Index: trac/ticket/report.py
+===================================================================
+--- trac/ticket/report.py	(revision 6033)
++++ trac/ticket/report.py	(working copy)
+@@ -27,6 +27,7 @@
+ from trac.core import *
+ from trac.db import get_column_names
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import sorted
+ from trac.util.datefmt import format_datetime, format_time
+ from trac.util.text import to_unicode, unicode_urlencode
+@@ -369,9 +370,7 @@
+         # Get the email addresses of all known users
+         email_map = {}
+         if Chrome(self.env).show_email_addresses:
+-            for username, name, email in self.env.get_known_users():
+-                if email:
+-                    email_map[username] = email
++            email_map = UserManager(self.env).get_attribute_mapper('email')
+ 
+         data.update({'header_groups': header_groups,
+                      'row_groups': row_groups,
+Index: trac/timeline/web_ui.py
+===================================================================
+--- trac/timeline/web_ui.py	(revision 6033)
++++ trac/timeline/web_ui.py	(working copy)
+@@ -30,6 +30,7 @@
+ from trac.perm import IPermissionRequestor
+ from trac.timeline.api import ITimelineEventProvider, TimelineEvent
+ from trac.util.compat import sorted
++from trac.user import UserManager
+ from trac.util.datefmt import format_date, format_datetime, parse_date, \
+                               to_timestamp, utc, pretty_timedelta
+ from trac.util.text import to_unicode
+@@ -167,9 +168,7 @@
+             # Get the email addresses of all known users
+             email_map = {}
+             if Chrome(self.env).show_email_addresses:
+-                for username, name, email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+             data['email_map'] = email_map
+             return 'timeline.rss', data, 'application/rss+xml'
+ 
+Index: trac/user.py
+===================================================================
+--- trac/user.py	(revision 0)
++++ trac/user.py	(revision 0)
+@@ -0,0 +1,352 @@
++# -*- coding: utf-8 -*-
++#
++# Copyright 2006 Waldemar Kornewald, wkornewald@haiku-os.org
++# All rights reserved.
++#
++# This software is licensed as described in the file COPYING, which
++# you should have received as part of this distribution. The terms
++# are also available at http://trac.edgewall.org/wiki/TracLicense.
++#
++# This software consists of voluntary contributions made by many
++# individuals. For the exact contribution history, see the revision
++# history and logs, available at http://trac.edgewall.org/log/.
++#
++# Author: Waldemar Kornewald, wkornewald@haiku-os.org
++
++from trac.core import *
++from trac.config import *
++
++
++class IUserStore(Interface):
++    """
++    Extension point interface for backends that store known users.
++    """
++
++    def supports_user_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return success
++        """
++
++    def get_usernames(self):
++        """
++        Generator that yields an ordered list of known usernames.
++
++        @return username
++        """
++
++    def check_password(self, username, password):
++        """
++        Checks if the password is correct for the given user.
++        """
++
++    def change_password(self, username, password):
++        """
++        Changes a user's password.
++
++        @return success
++        """
++
++    def delete_user(self, username):
++        """
++        Deletes a user.
++
++        @return success
++            Returns False if the user didn't exist.
++        """
++
++
++class IUserAttributeProvider(Interface):
++    """
++    Extension point interface for backends that store user attributes.
++    """
++
++    def supports_attribute_operation(self, operation):
++        """
++        Returns whether the operation (a method name) is supported.
++
++        @return supported
++        """
++
++    def get_user_attribute(self, username, attribute):
++        """
++        Returns a user attribute.
++
++        If the attribute is not set it returs an empty string.
++        If the attribute is not supported None is returned.
++        """
++
++    def set_user_attribute(self, username, attribute, value):
++        """
++        Sets a user attribute.
++
++        @return success
++            Returns False if setting the attribute is not supported.
++        """
++
++    def delete_all_user_attributes(self, username):
++        """
++        Deletes all of the given user's attributes.
++        """
++
++class UserAttributeMapper(object):
++    """
++    Dict that maps usernames to attributes and caches those values to
++    increase efficiency.
++    """
++
++    _usernames_cache = None
++    _cache = {}
++
++    def __init__(self, attribute, manager):
++        self._attribute = attribute
++        self._manager = manager
++
++    def __getitem__(self, username):
++        if not self._usernames_cache:
++            self._usernames_cache = [username for username
++                                     in self._manager.get_usernames()]
++        if username not in self._usernames_cache:
++            return None
++        if username not in self._cache:
++            value = self._manager.get_user(username)[self._attribute]
++            if value:
++                self._cache[username] = value
++            return value
++        else:
++            return self._cache.get(username)
++
++    def __contains__(self, username):
++        value = self[username]
++        return value is not None and len(value) > 0
++
++    def __setitem__(self,key,value):
++        raise NotImplementedError, "dict is immutable"
++    def __delitem__(self,key):
++        raise NotImplementedError, "dict is immutable"
++    def clear(self):
++        raise NotImplementedError, "dict is immutable"
++    def setdefault(self,k,default=None):
++        raise NotImplementedError, "dict is immutable"
++    def popitem(self):
++        raise NotImplementedError, "dict is immutable"
++    def update(self,other):
++        raise NotImplementedError, "dict is immutable"
++
++
++class UserManager(Component):
++    """
++    Component responsible for managing users and user attributes.
++    """
++
++    store = ExtensionOption('users',
++        'store', IUserStore, 'SessionUserStore',
++        doc="""The user store that should be used for authentication
++            (''since 0.11'').""")
++    attribute_providers = OrderedExtensionsOption('users',
++        'attribute_providers', IUserAttributeProvider,
++        doc="""Ordered list of user attribute providers (''since 0.11'').""")
++
++    # public API
++
++    def supports_operation(self, operation):
++        if self.store.supports_user_operation(operation):
++            return True
++        for provider in self.attribute_providers:
++            if self.provider.supports_attribute_operation(operation):
++                return True
++        return False
++
++    # IUserStore methods
++
++    def create_user(self, username, password):
++        """
++        Creates a new user with the given username and password.
++
++        @return User object or None if the user couldn't be created
++        """
++        if not self.store.supports_user_operation('create_user'):
++            return None
++        if self.store.create_user(username, password):
++            return User(username, self.store, self.attribute_providers)
++
++    def get_user(self, username):
++        """
++        Returns a User object for the username.
++
++        @return User object or None if the user doesn't exist
++        """
++        return User(username, self.store, self.attribute_providers)
++
++    def get_all_users(self):
++        """
++        Generator for User objects.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        for username in self.store.get_usernames():
++            yield User(username, self.store, self.attribute_providers)
++
++    def get_usernames(self):
++        """
++        Generator for usernames.
++        """
++        if not self.store.supports_user_operation('get_usernames'):
++            return
++        return self.store.get_usernames()
++
++    def get_attribute_mapper(self, attribute):
++        return UserAttributeMapper(attribute, self)
++
++
++class User(object):
++    """
++    Object representing a user.
++    """
++
++    def __init__(self, username, store, attribute_providers):
++        self._username = username
++        self.store = store
++        self.attribute_providers = attribute_providers
++
++    # public API
++
++    @property
++    def username(self):
++        return self._username
++
++    def exists(self):
++        return self.username in [username for username
++                                 in self.store.get_usernames()]
++
++    # IUserStore methods
++
++    def check_password(self, password):
++        if not self.store.supports_user_operation('check_password'):
++            return False
++        return self.store.check_password(self.username, password)
++
++    def change_password(self, password):
++        if not self.store.supports_user_operation('change_password'):
++            return False
++        return self.store.change_password(self.username, password)
++
++    def delete(self):
++        if not self.store.supports_user_operation('delete_user'):
++            return False
++        self.delete_all_attributes()
++        return self.store.delete_user(self.username)
++
++    # IUserAttributeProvider methods
++
++    def __getitem__(self, attribute):
++        for provider in self.attribute_providers:
++            if not provider.supports_attribute_operation('get_user_attribute'):
++                continue
++            value = provider.get_user_attribute(self.username, attribute)
++            if value is not None:
++                return value
++        return None
++
++    def __setitem__(self, attribute, value):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('set_user_attribute') \
++                    and provider.set_user_attribute(self.username, attribute,
++                                                    value):
++                return True
++        return False
++
++    def delete_all_attributes(self):
++        for provider in self.attribute_providers:
++            if provider.supports_attribute_operation('delete_all_user_attributes'):
++                provider.delete_all_user_attributes(username)
++
++
++class SessionUserStore(Component):
++    """
++    Component for managing authenticated users stored in sessions.
++    """
++
++    implements(IUserStore)
++
++    def supports_user_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_usernames(self):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT sid FROM session "
++                       "WHERE authenticated=1 "
++                       "ORDER BY sid")
++        for row in cursor:
++            yield row[0]
++
++
++class SessionUserAttributeProvider(Component):
++    """
++    Component for providing user attributes via Trac sessions.
++    """
++
++    implements(IUserAttributeProvider)
++
++    def supports_attribute_operation(self, operation):
++        return hasattr(self, operation)
++
++    def get_user_attribute(self, username, attribute):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        row = cursor.fetchone()
++        if row:
++            return row[0]
++
++        # if the attribute doesn't exist we return an empty string to
++        # indicate that the attribute is supported, but not set
++        return ''
++
++    def set_user_attribute(self, username, attribute, value):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++
++        if not value or value == '':
++            cursor.execute("DELETE FROM session_attribute "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                          (username, attribute))
++            db.commit()
++            return True
++
++        # check if attribute exists
++        cursor.execute("SELECT value FROM session_attribute "
++                       "WHERE sid=%s AND name=%s AND authenticated=1",
++                       (username, attribute))
++        if cursor.fetchone():
++            # update the attribute
++            cursor.execute("UPDATE session_attribute SET value=%s "
++                           "WHERE sid=%s AND name=%s AND authenticated=1",
++                           (value, username, attribute))
++        else:
++            # create new attribute
++            cursor.execute("INSERT INTO session_attribute "
++                           "(sid,authenticated,name,value) "
++                           "VALUES(%s,1,%s,%s)",
++                           (username, attribute, value))
++        db.commit()
++        return True
++
++    def delete_all_user_attributes(self, username):
++        db = self.env.get_db_cnx()
++        cursor = db.cursor()
++        cursor.execute("DELETE FROM session_attribute "
++                       "WHERE sid=%s AND authenticated=1",
++                      (username,))
++        db.commit()
+
+Property changes on: trac\user.py
+___________________________________________________________________
+Name: svn:keywords
+   + URL HeadURL Author LastChangedBy Date LastChangedDate Rev Revision LastChangedRevision Id
+
+Index: trac/versioncontrol/web_ui/log.py
+===================================================================
+--- trac/versioncontrol/web_ui/log.py	(revision 6033)
++++ trac/versioncontrol/web_ui/log.py	(working copy)
+@@ -23,6 +23,7 @@
+ from trac.context import Context
+ from trac.core import *
+ from trac.perm import IPermissionRequestor
++from trac.user import UserManager
+ from trac.util import Ranges
+ from trac.util.datefmt import http_date
+ from trac.util.html import html
+@@ -193,9 +194,7 @@
+         if format == 'rss':
+             # Get the email addresses of all known users
+             if Chrome(self.env).show_email_addresses:
+-                for username,name,email in self.env.get_known_users():
+-                    if email:
+-                        email_map[username] = email
++                email_map = UserManager(self.env).get_attribute_mapper('email')
+         elif format == 'changelog':
+             for rev in revs:
+                 changeset = changes[rev]
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.