ExternalAuth 2009 ideas

This page outlines ideas for a new authentication mechanism to supplement (and perhaps eventually replace) AuthPlugin. It may well be vaporware, and of course the exact interfaces proposed are extremely tentative.

Problems with AuthPlugin
The fundamental problem with AuthPlugin (not counting insufficient documentation) is that it mixes policy with implementation details. Implementation details for external authentication include:


 * Does $username exist in the external database?
 * Is ($username, $password) a valid login in the external database?
 * What is the e-mail of $user in the external database?
 * What valid external user login (if any) does $cookie correspond to?
 * What extra privileges does the user have in the foreign database?
 * Change some data (password, e-mail, etc.) of the user in the external database.

Policy decisions include:


 * Should creation of non-external accounts be allowed?
 * Should users be allowed to have their local groups changed independent of the external database?
 * What type of logging should exist for automated account creation, group changes, etc.?
 * Should users be automatically logged in (if cookies exist to allow this)?
 * Should local users be automatically created if they don't exist?
 * Should local settings changes propagate to the external database, remain independent, or be disallowed entirely?
 * Should local renames be prohibited, or somehow track the external database?

The latter decisions shouldn't be decided by the backend. They should be decided the same way for all external authentication, and implemented in core code. Observe how much code CentralAuth implements, for instance, that could apply just as well to any foreign database. A goal for a new interface should be that it takes, say, <100 lines of code to implement a new auth mechanism, and that all of that code should be highly specific to the exact backend and not reusable for other backends.

Core functionality
Core functionality should probably be mostly implemented on top of AuthPlugin to make things simpler. Core configuration usage should include (rough draft):


 * $wgGroupPermissions: Should have createaccount set to false for all groups if local account creation is to be disabled. Should have userrights set to false (also in the other user-rights settings) to prohibit local group changes.  No changes needed here.
 * $wgAutoPromote: Should allow extra parameters to query the external database for group memberships. The exact values that the parameter will take would be dependent on the auth plugin, they'd be transparently checked against items of ExternalUser::getGroups (see below).
 * $wgCreateAccountOnView: If true, ask the authentication plugin to check cookies for a valid login on every view, and create the account automatically if it doesn't exist already. If false, only create an account on an explicit user login attempt.  Default false, since anything else messes up any preexisting accounts.  Even if true, might fail if an account of that name already exists, or the name from the cookie doesn't meet MediaWiki standards.
 * $wgAllowPrefChange: An associative array.  Keys can be any preference name.  Values can be:
 * local: allow changes to this pref through the wiki interface but only apply them locally (default)
 * semiglobal: allow changes through the wiki interface and try to apply them to the foreign database, but continue on anyway if that fails
 * global: allow changes through the wiki interface, but only let them go through if they successfully update the foreign database
 * message: allow no local changes; replace the change form with a message provided by the auth plugin, telling the user how to change the setting externally (maybe providing a link, etc.)
 * hide: allow no local changes, hide the form (identical to $wgHiddenPrefs, could be merged with that or omitted in favor of that)

The core functionality should handle logging (on autocreation of local accounts, rights changes based on foreign rights, etc.). It should also provide a default mechanism for linking wiki accounts to external accounts, say an external_user table with eu_wiki_id (foreign key to user.user_id) and eu_foreign_id (arbitrary string uniquely identifying an external user, provided by ExternalUser::getId).

A special page, Special:LinkAccount, would be added. Users with an unlinked account could go there and enter their login info for the foreign database to link their account.

It would be nice if we could also get blocks working somehow, for foreign databases that support that concept (e.g., most web apps).

Plugin functionality
The plugin would create an ExternalUser subclass, which would have the following public accessors:


 * getId: Some opaque identifier that uniquely, stably identifies the user. For instance, in a typical web app, this could be the user id.  Must be a string between 1 and 255 bytes, to support cases where non-integer id's are used (like OpenID, Unix password file, etc.).
 * authenticate( $password ): Return true if the given plaintext password is valid for this user in the external database.
 * getPref( $prefkey ): Return the preference with the given key in the foreign database, or null if the preference doesn't make sense for the other DB or a value isn't available. It's expected that 'email' would be the major pref anyone cares about, and that would be assumed to be validated.  Other prefs that are checked might include interface language, etc.  A list of the ones we'd normally check should be provided.
 * getGroups: Return an array of arbitrary group identifiers (could be strings, integers, etc.). These will be checked against $wgAutoPromote.

The following static methods would be supported:


 * newFromName( $username ): Given a username, return an ExternalUser object corresponding to that name, or false if none exists. $username will not be munged to meet MediaWiki requirements, it will be exactly what the user types (with whitespace stripped, maybe).  Core code will handle cases where the name can't be used for a MediaWiki account, e.g., by prompting the user for a wiki username when they first log in.  This constructor will be called by Special:UserLogin, which will create the account and add an entry to external_user for later reference.  It will also be called by Special:LinkAccount.  Once an account is created and linked, newFromId will be called instead, so renames can happen on both sides without difficulty.
 * newFromId( $id ): Given an id (as returned by getId above), return an ExternalUser object corresponding to that id.
 * newFromCookie( $cookie ): Given a cookie, return an ExternalUser object returning the foreign user, or false if it's not a valid external cookie. This will be used both for autologin, and (depending on configuration) account auto-creation.
 * prefMessage( $pref ): Given a pref key like 'password' or 'email', return some HTML telling the user how to change it (for when the admin has disabled local pref changing).

The following functions would change the user entry in the external database. All return a boolean value to indicate success, and can just return false unconditionally if the underlying auth mechanism doesn't support changing them.


 * setPassword( $password )
 * setPref( $key, $val )

Current status
A crude version of ExternalAuth was checked in as of. It should be considered experimental and not suitable for production use unless you're desperate. Possibly the most notable omission is that nothing whatsoever is currently logged, so local accounts will magically appear without any record of when.

Todo list:


 * Write Special:LinkAccount as described above.
 * Do something sensible if the external name can't be used in MediaWiki, or is already in use.
 * Allow some prefs (at least password and e-mail) to mirror the external database, so we never refer to a local copy for linked accounts.
 * Autocreate on view.
 * Allow autopromotion based on external user group. (A naïve implementation might have performance implications.)