--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -28,12 +28,23 @@
 log = logging.getLogger(__name__)
 
 class AuthenticationProvider(object):
+    '''
+    An interface to provide authentication services for Allura.
+
+    To use a new provider, expose an entry point in setup.py:
+
+        [allura.auth]
+        myprovider = foo.bar.MyAuthProvider
+
+    Then in your .ini file, set auth.method=myprovider
+    '''
 
     def __init__(self, request):
         self.request = request
 
     @classmethod
     def get(cls, request):
+        '''returns the AuthenticationProvider instance for this request'''
         try:
             result = cls._loaded_ep
         except AttributeError:
@@ -58,9 +69,21 @@
         return user
 
     def register_user(self, user_doc):
+        '''
+        Register a user.
+
+        :param user_doc: a dict with 'username' and 'display_name'.  Optionally 'password' and others
+        :rtype: :class:`User <allura.model.auth.User>`
+        '''
         raise NotImplementedError, 'register_user'
 
     def _login(self):
+        '''
+        Authorize a user, usually using self.request.params['username'] and ['password']
+
+        :rtype: :class:`User <allura.model.auth.User>`
+        :raises: HTTPUnauthorized if user not found, or credentials are not valid
+        '''
         raise NotImplementedError, '_login'
 
     def login(self, user=None):
@@ -78,15 +101,37 @@
         self.session.save()
 
     def by_username(self, username):
+        '''
+        Find a user by username.
+
+        :rtype: :class:`User <allura.model.auth.User>` or None
+        '''
         raise NotImplementedError, 'by_username'
 
     def set_password(self, user, old_password, new_password):
+        '''
+        Set a user's password.
+
+        :param user: a :class:`User <allura.model.auth.User>`
+        :rtype: None
+        :raises: HTTPUnauthorized if old_password is not valid
+        '''
         raise NotImplementedError, 'set_password'
 
     def upload_sshkey(self, username, pubkey):
+        '''
+        Upload an SSH Key.  Providers do not necessarily need to implement this.
+
+        :rtype: None
+        :raises: AssertionError with user message, upon any error
+        '''
         raise NotImplemented, 'upload_sshkey'
 
 class LocalAuthenticationProvider(AuthenticationProvider):
+    '''
+    Stores user passwords on the User model, in mongo.  Uses per-user salt and
+    SHA-256 encryption.
+    '''
 
     def register_user(self, user_doc):
         from allura import model as M
@@ -209,6 +254,18 @@
         return user
 
 class ProjectRegistrationProvider(object):
+    '''
+    Project registration services for Allura.  This is a full implementation
+    and the default.  Extend this class with your own if you need to add more
+    functionality.
+
+    To use a new provider, expose an entry point in setup.py:
+
+        [allura.project_registration]
+        myprovider = foo.bar.MyAuthProvider
+
+    Then in your .ini file, set registration.method=myprovider
+    '''
 
     @classmethod
     def get(cls):
@@ -338,6 +395,26 @@
         return None
 
 class ThemeProvider(object):
+    '''
+    Theme information for Allura.  This is a full implementation
+    and the default.  Extend this class with your own if you need to add more
+    functionality.
+
+    To use a new provider, expose an entry point in setup.py:
+
+        [allura.theme]
+        myprovider = foo.bar.MyThemeProvider
+
+    Then in your .ini file, set theme=mytheme
+
+    The variables referencing jinja template files can be changed to point at your
+    own jinja templates.  Use the standard templates as a reference, you should
+    provide matching macros and block names.
+
+    :var base_css: tuple of (css-resource, theme-name), or just a string css-resource
+    :var icons: a dictionary of sized icons for each tool
+    '''
+
     master_template = 'allura:templates/jinja_master/master.html'
     jinja_macros = 'allura:templates/jinja_master/theme_macros.html'
     nav_menu = 'allura:templates/jinja_master/nav_menu.html'
@@ -355,11 +432,17 @@
 
     @LazyProperty
     def password_change_form(self):
+        '''
+        :return: None, or an easywidgets Form to render on the user preferences page
+        '''
         from allura.lib.widgets.forms import PasswordChangeForm
         return PasswordChangeForm(action='/auth/prefs/change_password')
 
     @LazyProperty
     def upload_key_form(self):
+        '''
+        :return: None, or an easywidgets Form to render on the user preferences page
+        '''
         from allura.lib.widgets.forms import UploadKeyForm
         return UploadKeyForm(action='/auth/prefs/upload_sshkey')
 
@@ -394,6 +477,16 @@
     pass
 
 class UserPreferencesProvider(object):
+    '''
+    An interface for user preferences, like display_name and email_address
+
+    To use a new provider, expose an entry point in setup.py:
+
+        [allura.user_prefs]
+        myprefs = foo.bar.MyUserPrefProvider
+
+    Then in your .ini file, set user_prefs_storage.method=myprefs
+    '''
 
     @classmethod
     def get(cls):
@@ -402,15 +495,33 @@
             return ep.load()()
 
     def get_pref(self, user, pref_name):
+        '''
+        :param user: a :class:`User <allura.model.auth.User>`
+        :param str pref_name:
+        :return: pref_value
+        :raises: AttributeError if pref_name not found
+        '''
         raise NotImplementedError, 'get_pref'
 
     def save_pref(self, user, pref_name, pref_value):
+        '''
+        :param user: a :class:`User <allura.model.auth.User>`
+        :param str pref_name:
+        :param pref_value:
+        '''
         raise NotImplementedError, 'set_pref'
 
-    def find_by_display_name(self, name, substring=True):
+    def find_by_display_name(self, name):
+        '''
+        :rtype: list of :class:`Users <allura.model.auth.User>`
+        '''
         raise NotImplementedError, 'find_by_display_name'
 
 class LocalUserPreferencesProvider(UserPreferencesProvider):
+    '''
+    The default UserPreferencesProvider, storing preferences on the User object
+    in mongo.
+    '''
 
     def get_pref(self, user, pref_name):
         if pref_name in user.preferences:
@@ -424,10 +535,8 @@
         else:
             setattr(user, pref_name, pref_value)
 
-    def find_by_display_name(self, name, substring=True):
-        from allura import model as M
-        if not substring:
-            raise NotImplementedError, 'non-substring'
+    def find_by_display_name(self, name):
+        from allura import model as M
         name_regex = re.compile('(?i)%s' % re.escape(name))
         users = M.User.query.find(dict(
                 display_name=name_regex)).sort('username').all()