Index: trac/env.py
===================================================================
--- trac/env.py	(revision 2637)
+++ trac/env.py	(working copy)
@@ -21,6 +21,7 @@
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
 from trac.db import DatabaseManager
+from trac.userdir import *
 
 __all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment']
 
@@ -60,6 +61,7 @@
      * wiki and ticket attachments.
     """   
     setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)
+    userdirs = ExtensionPoint(IUserDirectory)
 
     def __init__(self, path, create=False, options=[]):
         """Initialize the Trac environment.
@@ -231,30 +233,19 @@
         logid = self.path # Env-path provides process-unique ID
         self.log = logger_factory(logtype, logfile, loglevel, logid)
 
-    def get_known_users(self, cnx=None):
-        """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
-        """
+    def get_known_users(self, cnx=None, attrs=None, limit=None):
+                
         if not cnx:
             cnx = self.get_db_cnx()
-        cursor = cnx.cursor()
-        cursor.execute("SELECT DISTINCT s.sid, n.var_value, e.var_value "
-                       "FROM session AS s "
-                       " LEFT JOIN session AS n ON (n.sid=s.sid "
-                       "  AND n.authenticated=1 AND n.var_name = 'name') "
-                       " LEFT JOIN session AS e ON (e.sid=s.sid "
-                       "  AND e.authenticated=1 AND e.var_name = 'email') "
-                       "WHERE s.authenticated=1 ORDER BY s.sid")
-        for username,name,email in cursor:
-            yield username, name, email
-
+        
+        # should accumulate users from all IUserDirectory providers - desirable?
+        for userdir in self.userdirs:
+            for user in userdir.get_known_users(cnx, attrs, limit):
+                yield user
+        
+        # yield username, name, email
+        
+    
     def backup(self, dest=None):
         """Simple SQLite-specific backup of the database.
 
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 2637)
+++ trac/ticket/api.py	(working copy)
@@ -56,8 +56,8 @@
         if self.config.getbool('ticket', 'restrict_owner'):
             field['type'] = 'select'
             users = []
-            for username, name, email in self.env.get_known_users(db):
-                users.append(username)
+            for user in self.env.get_known_users(cnx=db):
+                users.append(user['username'])
             field['options'] = users
             field['optional'] = True
         else:
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 2637)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -153,9 +153,9 @@
         if format == 'rss':
             # Get the email addresses of all known users
             email_map = {}
-            for username,name,email in self.env.get_known_users():
-                if email:
-                    email_map[username] = email
+            for user in self.env.get_known_users(attrs=('email')):
+                if user['email']:
+                    email_map[user['username']] = user['email']
             for cs in changes.values():
                 cs['message'] = util.escape(cs['message'])
                 cs['shortlog'] = util.escape(cs['shortlog'].replace('\n', ' '))
Index: trac/Timeline.py
===================================================================
--- trac/Timeline.py	(revision 2637)
+++ trac/Timeline.py	(working copy)
@@ -143,9 +143,9 @@
 
         # Get the email addresses of all known users
         email_map = {}
-        for username, name, email in self.env.get_known_users():
-            if email:
-                email_map[username] = email
+        for user in self.env.get_known_users(attrs=('email')):
+            if user['email']:
+                email_map[user['username']] = user['email']
 
         idx = 0
         for kind, href, title, date, author, message in events:
Index: trac/tests/env.py
===================================================================
--- trac/tests/env.py	(revision 2637)
+++ trac/tests/env.py	(working copy)
@@ -34,8 +34,8 @@
                             ('joe', 'email', 'joe@example.com'),
                             ('jane', 'name', 'Jane')])
         users = {}
-        for username,name,email in self.env.get_known_users(self.db):
-            users[username] = (name, email)
+        for user in self.env.get_known_users(cnx=self.db):
+            users[user['username']] = (user['name'], user['email'])
 
         assert not users.has_key('anonymous')
         self.assertEqual(('Tom', 'tom@example.com'), users['tom'])
Index: trac/userdir.py
===================================================================
--- trac/userdir.py	(revision 0)
+++ trac/userdir.py	(revision 0)
@@ -0,0 +1,105 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
+# 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.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Bruce Christensen <me@brucec.net>
+# Author: Brad Anderson <brad@dsource.org>
+
+from trac.core import Component, implements, Interface, ExtensionPoint, TracError
+
+class IUserDirectory(Interface):
+    def get_known_users(cnx=None, attrs=('name', 'email'), limit=None):
+        """Generator that yields information about known users.
+        
+        Generates a dictionary for each known user, of the form
+        {   'username': 'john',
+            'attr1': attr1 value,
+            'attr2': attr2 value,
+            ...
+            }
+        
+        Raises KeyError if any of attrs isn't supported.
+        
+        @param cnx:   db connection object
+        @param attrs: tuple of attributes to be included in the generated dict
+                      (in addition to 'username', which is always included)
+        @param limit: maximum number of results to generate. None means no limit.
+        """
+        pass
+
+    def get_user_attribute(user, attr):
+        """Provides values of one or more attributes of a user.
+
+        Raises UnknownUserError if the requested user doesn't exist.
+
+        Raises KeyError if the specified attribute isn't supported.
+
+        @param user: a username
+        @param attr: the name of the attribute to return. Can also be a tuple of
+                    attribute names.
+        @return:     the value of a user attribute, or, if attr is a tuple, a
+                    dictionary of attributes like get_known_users generates
+        """
+        pass
+
+    def get_supported_attributes():
+        """
+        @return: tuple of supported attribute names. Always includes 'username'.
+        """
+        pass
+
+
+class SessionUserDirectory(Component):
+    implements (IUserDirectory)
+    
+    # This plugin basically duplicates the existing functionality in Trac, providing a
+    # list of users based on the 'session' table.  It makes no attempt to use the 'limit'
+    # parameter of the function right now.
+    
+    # IUserDirectory methods
+    def get_known_users(self, cnx=None, attrs=None, limit=None):
+        """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 dictionary for every user, of the form
+        {'username':username,'attr1':attr1,...}
+
+        @param cnx: the database connection
+        @param attrs: attributes of the user to return
+        @param limit: limit the number of users returned
+
+        """
+
+        cursor = cnx.cursor()
+        cursor.execute("SELECT DISTINCT s.sid, n.var_value, e.var_value "
+                       "FROM session AS s "
+                       " LEFT JOIN session AS n ON (n.sid=s.sid "
+                       "  AND n.authenticated=1 AND n.var_name = 'name') "
+                       " LEFT JOIN session AS e ON (e.sid=s.sid "
+                       "  AND e.authenticated=1 AND e.var_name = 'email') "
+                       "WHERE s.authenticated=1 ORDER BY s.sid")
+        for username,name,email in cursor:
+            user = {}
+            user['username'] = username  # required key/value
+            if attrs and 'name' in attrs:
+                user['name'] = name
+            if attrs and 'email' in attrs:
+                user['email'] = email
+            yield user
+    
+    def get_user_attribute(self, user, attr):
+        pass
+    
+    def get_supported_attributes(self):
+        pass
