Index: trac/env.py
===================================================================
--- trac/env.py	(revision 2659)
+++ 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,24 @@
         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_user_info(self, cnx=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 user_info from all IUserDirectory providers - desirable?
+        for userdir in self.userdirs:
+            for username, name, email in userdir.get_known_user_info(cnx, limit):
+                yield username, name, email
 
+    def get_known_users(self, cnx=None, limit=None):
+        if not cnx:
+            cnx = self.get_db_cnx()
+        
+        # should accumulate users from all IUserDirectory providers - desirable?
+        for userdir in self.userdirs:
+            for username in userdir.get_known_users(cnx, limit):
+                yield username
+    
     def backup(self, dest=None):
         """Simple SQLite-specific backup of the database.
 
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 2659)
+++ trac/ticket/api.py	(working copy)
@@ -56,7 +56,7 @@
         if self.config.getbool('ticket', 'restrict_owner'):
             field['type'] = 'select'
             users = []
-            for username, name, email in self.env.get_known_users(db):
+            for username in self.env.get_known_users(cnx=db):
                 users.append(username)
             field['options'] = users
             field['optional'] = True
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 2659)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -153,7 +153,7 @@
         if format == 'rss':
             # Get the email addresses of all known users
             email_map = {}
-            for username,name,email in self.env.get_known_users():
+            for username,name,email in self.env.get_known_user_info():
                 if email:
                     email_map[username] = email
             for cs in changes.values():
Index: trac/Timeline.py
===================================================================
--- trac/Timeline.py	(revision 2659)
+++ trac/Timeline.py	(working copy)
@@ -143,7 +143,7 @@
 
         # Get the email addresses of all known users
         email_map = {}
-        for username, name, email in self.env.get_known_users():
+        for username, name, email in self.env.get_known_user_info():
             if email:
                 email_map[username] = email
 
Index: trac/tests/env.py
===================================================================
--- trac/tests/env.py	(revision 2659)
+++ trac/tests/env.py	(working copy)
@@ -34,7 +34,7 @@
                             ('joe', 'email', 'joe@example.com'),
                             ('jane', 'name', 'Jane')])
         users = {}
-        for username,name,email in self.env.get_known_users(self.db):
+        for username,name,email in self.env.get_known_user_info(self.db):
             users[username] = (name, email)
 
         assert not users.has_key('anonymous')
Index: trac/userdir.py
===================================================================
--- trac/userdir.py	(revision 0)
+++ trac/userdir.py	(revision 0)
@@ -0,0 +1,129 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2005 Brad Anderson <brad@dsource.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.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: Brad Anderson <brad@dsource.org>
+
+from trac.core import Component, implements, Interface, ExtensionPoint, TracError
+
+class IUserDirectory(Interface):
+    def get_known_user_info(self, cnx=None, limit=None):
+        """Generator that yields information about known users.
+        
+        This function generates one tuple for every user, of the form
+        (username, name, email) ordered alpha-numerically by username.
+        
+        @param cnx:   db connection object
+        @param limit: maximum number of results to generate. None means no limit.
+        
+        @return (username, name, email)
+            if plugin does not support name or email, return None in their place
+        """
+        pass
+
+    def get_known_users(self, cnx=None, limit=None):
+        """
+        Generator that yields a list of known users.
+
+        @param cnx:   db connection object
+        @param limit: maximum number of results to generate. None means no limit.
+        
+        @return username
+        """
+        pass
+    
+    def get_user_attribute(self, 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(self):
+        """
+        @return: tuple of supported attribute names. Always includes 'username'.
+        """
+        pass
+
+
+class DefaultUserDirectory(Component):
+    implements (IUserDirectory)
+    
+    # This plugin basically duplicates the existing functionality in Trac, providing a
+    # list of users based on the 'session' table.  
+    
+    # IUserDirectory methods
+    
+    def get_known_user_info(self, cnx=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 tuple for every user, of the form
+        (username, name, email) ordered alpha-numerically by username.
+        
+        @param cnx: the database connection
+        @param attrs: attributes of the user to return
+        @param limit: limit the number of users returned
+
+        @return (username, name, email)
+        """
+
+        if not cnx:
+            pass  # FIXME - need to implement other plugin here to get cnx?  For now, the
+                  #         DefaultUserDirectory is fine, b/c the method in env gets a cnx.
+                  #         But what about other plugins?
+        
+        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")
+        iter = 0
+        for username,name,email in cursor:
+            if limit:
+                iter += 1
+                if iter > limit:
+                    break
+            yield username,name,email
+    
+    def get_known_users(self, cnx=None, limit=None):
+        cursor = cnx.cursor()
+        cursor.execute("SELECT DISTINCT sid "
+                       "FROM session "
+                       "WHERE authenticated=1 "
+                       "ORDER BY sid")
+        iter = 0
+        for username in cursor:
+            if limit:
+                iter += 1
+                if iter > limit:
+                    break
+            yield username[0]
+            
+    def get_user_attribute(self, user, attr):
+        pass
+    
+    def get_supported_attributes(self):
+        pass
