Auth systems/OAuth/Design

Features

 * To simplify the design, and to work with many of the existing OAuth client libraries that current work with Twitter's OAuth 1.0 implementation, we will start by implementing OAuth 1.0a
 * We can move to one or more OAuth 2 flows in next year if there is demand
 * This will allow users to use HTTPS or HTTP when using OAuth, since every call must be signed. To keep things simple, we may only support HMAC to start?


 * There will be an Application Registration page, for registering applications
 * If this process is usable for power users, it may enable Desktop apps, as each user would generate their own appid


 * There will be an approval page, where a logged in user will grant permissions to an application
 * Users should be warned of privacy implications
 * Should users reenter their password?
 * Allow application to update their privileges? This is not part of the specification, but should be a simple addition.
 * What's the difference between an application that already has X updating to add Y, and the application just making a new request for X+Y?
 * The application would need to start using new client credentials (we don't want to track what users have accepted or not), so when the user is redirected to the WMF to authorize the new client, in the UI, it's the difference between saying that an app wants to authorize with certain permissions vs. saying an existing application would like to to add / remove these permissions.


 * There will be a page where Users can see and manage their approved applications
 * See grants
 * revoke if desired


 * The MW API accepts signed requests
 * wgUser setup for the user
 * Hook ApiCheckCanExecute to authorize?

Permission grants
Users will have the ability to grant relatively fine-grained permissions for which API features a client is allowed to use. The list of granted permissions will be supplied by the AuthPlugin (i.e. OAuth), but will otherwise not depend on the specific implementation of the auth plugin.


 * Granted permissions are identified by string tokens. These are entirely independent of the existing user rights system: to successfully execute a module, both the existing user rights checks and the granted permissions check have to pass.
 * Granted permissions are checked by the API code calling a method on $wgAuth, which then (if necessary) loads the list of granted permissions and calls a method on the API module to check if the granted permissions are sufficient.
 * By default, there will be one perimssion token per API module (per action, or per query prop / list / meta), named the same as the API module.
 * API modules can override this. For example,
 * ApiBlock and ApiUnblock would probably both use the same permission token.
 * Many query submodules would probably use a generic "query" token.
 * On the other hand, ApiEditPage would probably have separate tokens for basic editing, creating new pages, editing the user's css/js, editing protected pages, editing MediaWiki-namespace pages, etc. Which one(s) it checks for any particular query would be depend on the page being edited.
 * An API module may use the list of granted rights (passed from $wgAuth before ->execute, as mentioned above) to modify its behavior when executed. For example, it might prevent using any deleted revision data (as if the user lacked the 'deletedtext' user right) unless a "viewdeleted" permission were granted, but otherwise proceed normally.
 * One idea to implement this would be for the API module to be able to instruct the User object to temporarily filter out certain user rights, so for all intents and purposes the user wouldn't have "deletedtext" during the execution of that API module.
 * API modules will also have to be able to supply a list of which permissions they might need to be granted; this may be output as part of the online help, and will be used to create a (cached) list of all possible granted rights for use by OAuth (if it needs it).

Open questions:
 * Should i18n of granted permissions be forced to use systematic message keys, e.g. "granted-permission-{$name}-name", "granted-permission-{$name}-description"? Or should it be allowed that the API module specifies the key? And if the latter, how are conflicts dealt with?

Logging

 * Lots of people gave the input that it would be desirable to log the appid, userid, and revisionid (or other identifying id for the action), to identify apps that were misbehaving.
 * Logs would only be written on edits, or api calls that made a change to the system (specifically, we will exclude any calls for searching or reading)
 * Viewing these logs would be available to a privileged user, and/or the user
 * Collection/Storage: It seems like we could use either the EventLogging system, or our own logging table, to collect and store the data
 * EL is asynchronous, which is nice. However, this would mean other MW users, who wanted to use OAuth, would either need to have EventLogging setup, or not have a log of the activity.
 * For the anticipated load, keeping the logs on extension1 would probably be fine

Implementation Notes

 * While discussing implementation strategies (irc check-in 20130404)
 * OAuth should likely be implemented as an extension
 * The extension should be usable by mediawiki installs that do not include CentralAuth
 * For the WMF deployment, some CentralAuth aspects are necessary. Likely will need hooks to allow CentralAuth to update pieces, or we may extend some classes to implement CentralAuth-specific functionality
 * The storage for OAuth data (Client registration, user permissions, logging) will be in a single database. User permissions will include a "wiki" parameter to allow giving a client permissions on only some wikis, or "*" for authorizing the client on all wikis.
 * We'll probably use some pieces from existing/reference libraries to handle the OAuth encoding and signatures (one library had a good implementation of both RSA and HMAC signatures in ~50 loc). For actual interactions, we'll probably write the integrations from scratch. We are not trying developing our own library.
 * NB: we'll chose a couple libraries soon to test as clients, to ensure our system integrates with them.
 * It seems like a good idea to require https for the WMF endpoints, to address

Open Questions

 * Does a Consumer need to get an AccessToken for each wiki, or will a single Token for each user authorization suffice?
 * Should/Can we enforce using Authorization headers only, as recommended in the standard? Adding the information to the body or query parameters is supported, but the header is preferred.

Basic Authorization Flow

 * Diagram of the basic authn flow



Wireframes of pages with UI

 * Page for app-owners to register their client




 * Result page after submission. Secret is only generated and shown if no rsa key was given.




 * Page user see when they are redirected to the wiki by the Client




 * Page user sees when they want to manage their authorizations (may in their preferences?)



Special:OAuthClientRegistration
Special page where app providers register their "Client". Only over ssl if $wgSecureLogin. Disallow javascript, etc. Probably legal agreement too.


 * Inputs:
 * Client's Name (string)
 * Permission (list of strings?)
 * Wiki
 * Callback URL (url string)
 * (optional) RSA Key
 * Contact address


 * Processing:
 * Ensure name is unique and valid
 * Ensure callback isn't blacklisted ?
 * Generate Client Token
 * Create DB object (unapproved)


 * Return (page):
 * Client Token
 * Client Secret, if an RSA key wasn't input

Special:OAuthClientRegistrationApproval
list of applications that need to be approved by authorized person. Probably also a form to Block apps?
 * Apps that were submitted by users with confirmed email addresses should be identifiable as well, since that may give us some extra level of trust for approving it.

Special:OAuth/initiate
(This will be called by the Client app, so no human interaction.)
 * Input:
 * (optional) OAuth realm
 * oauth_consumer_key (rand string)
 * oauth_signature_method ["HMAC-SHA1" or "RSA-SHA1"]
 * oauth_timestamp (unix timestamp)
 * oauth_nonce (random string to prevent replay)
 * oauth_callback (encoded url)
 * oauth_signature (encoded string)


 * Processing
 * Ensure Client is in good standing (approved, not blocked)
 * Verify Signature
 * Check freshness
 * Ensure nonce has not been used
 * Check callback is ok (it was the one, or one of the ones, that was registered)


 * Return:
 * oauth_token (random string)
 * oauth_token_secret (random string)
 * oauth_callback_confirmed (true)

Special:OAuth/authorize
Special page accessed by end users, to approve the Client for their account. Only ssl if $wgSecureLogin. No javascript allowed.


 * Input:
 * oauth_token (from return of /initiate)


 * Processing:
 * Ensure token is good
 * Ensure User is logged in
 * Retrieve Client info based on token, ensure client is in good standing
 * Show page asking for approval


 * Return (Redirect to callback url with):
 * oauth_token (from Input)
 * oauth_verifier (random string)

Special:OAuth/token
(Accessed by Client App only, no human interaction)
 * Input:
 * (optional) OAuth realm
 * oauth_consumer_key
 * oauth_token
 * oauth_signature_method ["HMAC-SHA1"|"RSA-SHA1"]
 * oauth_timestamp (unix timestamp)
 * oauth_nonce (random string to prevent replay)
 * oauth_verifier (from return of /authorize)
 * oauth_signature (encoded string)


 * Processing:
 * Ensure Client is in good standing (approved, not blocked)
 * Verify Signature
 * Check freshness
 * Ensure nonce has not been used


 * Return:
 * oauth_token (new random string)
 * oauth_token_secret (new random string)

api.php

 * Input also includes:
 * (optional) OAuth realm
 * oauth_consumer_key
 * oauth_token (returned from /token)
 * oauth_signature_method ["HMAC-SHA1"|"RSA-SHA1"]
 * oauth_timestamp (current unix timestamp)
 * oauth_nonce (new random string to prevent replay)
 * oauth_signature (encoded string)


 * By the spec, these inputs can come in via the Authorization header, or as query parameters.


 * Processing:
 * Ensure Client is in good standing (approved, not blocked)
 * Verify Signature
 * Check freshness
 * Ensure nonce has not been used
 * Ensure token is good (exists, not expired)
 * Load User based on token