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]
