Index: trac/env.py
===================================================================
--- trac/env.py	(revision 4673)
+++ 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 *
 from trac.versioncontrol import RepositoryManager
 
 __all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment']
@@ -61,6 +62,7 @@
      * wiki and ticket attachments.
     """   
     setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)
+    userdirs = ExtensionPoint(IUserDirectory)
 
     base_url = Option('trac', 'base_url', '',
         """Base URL of the Trac deployment.
@@ -296,30 +298,22 @@
         self.log = logger_factory(logtype, logfile, self.log_level, self.path,
                                   format=format)
 
-    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.
+    def get_known_user_info(self, limit=None):
+        cnx = self.get_db_cnx()
+        
+        # should accumulate user_info from all IUserDirectory providers - desirable?
+        for userdir in self.userdirs:
+            for username, name, email in userdir.get_known_user_info(limit):
+                yield username, name, email
 
-        This function generates one tuple for every user, of the form
-        (username, name, email) ordered alpha-numerically by username.
+    def get_known_users(self, limit=None):
+        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(limit):
+                yield 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
-
     def backup(self, dest=None):
         """Simple SQLite-specific backup of the database.
 
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 4673)
+++ trac/ticket/api.py	(working copy)
@@ -100,8 +100,9 @@
         if self.restrict_owner:
             field['type'] = 'select'
             users = []
+
             perm = PermissionSystem(self.env)
-            for username, name, email in self.env.get_known_users(db):
+            for username in self.env.get_known_users():
                 if perm.get_user_permissions(username).get('TICKET_MODIFY'):
                     users.append(username)
             field['options'] = users
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 4673)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -164,7 +164,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 4673)
+++ trac/Timeline.py	(working copy)
@@ -155,7 +155,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 4673)
+++ trac/tests/env.py	(working copy)
@@ -23,8 +23,8 @@
         """Testing env.get_version"""
         assert self.env.get_version() == db_default.db_version
 
-    def test_get_known_users(self):
-        """Testing env.get_known_users"""
+    def test_get_known_user_info(self):
+        """Testing env.get_known_user_info"""
         cursor = self.db.cursor()
         cursor.executemany("INSERT INTO session VALUES (%s,%s,0)",
                            [('123', 0),('tom', 1), ('joe', 1), ('jane', 1)])
@@ -35,7 +35,7 @@
                             ('joe', 1, 'email', 'joe@example.com'),
                             ('jane', 1, '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():
             users[username] = (name, email)
 
         assert not users.has_key('anonymous')
Index: trac/notification.py
===================================================================
--- trac/notification.py        (revision 4673)
+++ trac/notification.py        (working copy)
@@ -163,7 +163,7 @@
         self._init_pref_encoding()
         # Get the email addresses of all known users
         self.email_map = {}
-        for username, name, email in self.env.get_known_users(self.db):
+        for username, name, email in self.env.get_known_user_info():
             if email:
                 self.email_map[username] = email

Index: trac/userdir.py
===================================================================
--- trac/userdir.py	(revision 0)
+++ trac/userdir.py	(revision 0)
@@ -0,0 +1,125 @@
+# -*- 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, 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, 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, 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)
+        """
+        cnx = self.env.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")
+        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, limit=None):
+        cnx = self.env.get_db_cnx()
+        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
