User:Dantman/OAuth/Brainstorm
Application (abstract) { getID() => int getType() => NULL for the unknown and internal applications (Or should we use "unknown" and "internal"?) "oauth2" for OAuth applications getRights() => array() "apihighlimits": Allows the app to have the apihighlimits when anonymously using the Client Credentials Grant. "passwordgrant": Permits the application to use the "4.3. Resource Owner Password Credentials Grant" flow. getName() getDescription() getWebsite() getImage() etc...? } application: app_id PRIMARY KEY AUTO_INCREMENT app_type varbinary(32) application_right: ari_app int unsigned NOT NULL ari_right varbinary(32) NOT NULL application_owner: ao_app int unsigned NOT NULL ao_user int unsigned NOT NULL ---- OAuthApplication < Application { getClientID() => OAuth 2 client_id (randomly generated on registration) getClientSecret() => Undefined; Haven't decided how to handle the possibility of something better than just client secrets Should we support MAC and just hand over a MAC identifier and key? Or should we use some fancy pubkey based request or request signed with a client secret to request MAC credentials to be issued securely getClientType() => "confidential" / "public" isConfidential() and isPublic()? getRedirectionURIs() => array() of redirection_uris ((TODO: Do we need a special class for wildcard handling?)) getAuthScheme() => "Basic" is the normal value This sits around in case we implement some other way of doing client authentication instead of just Basic HTTP Auth over TLS. Since "2.3.2. Other Authentication Methods" appears to indicate we need to make the client specify what auth scheme they are going to use if we use others. } oauth2_application: oapp_id PRIMARY KEY (FOREIGN?) => application.app_id oapp_client_id varbinary() NOT NULL UNIQUE oapp_client_type varbinary(32) NOT NULL ???oapp_client_secret??? auth_scheme varbinary(32) NOT NULL oauth2_redirection_uri: oru_id PRIMARY KEY (FOREIGN?) => application.app_id / oauth_application.oapp_id oru_uri BLOB ---- Authorization (abstract) { getUser() getApplication() ***some sort of get-rights interface*** } InternalAuthorization < Authorization { (( Need to think of the api in full before we figure if this is useful The idea would be it's a dummy authorization for the "Unknown" and "Internal" cases where MediaWiki itself is running and has all of the user's rights. On this topic we may actually want getAuthorization() instead of getApplication() )) ***get-rights interface uses gives access to all the user's rights*** } OAuthAuthorization < Authorization { } // @todo Figure out where access_tokens and refresh_tokens fit into the OAuthAuthorization (single, multiple?) OAuthAuthCode { getCode() => auth_code getApplication() => OAuthApplication getUser() => User; ->getAuthorization()->getUser() getIssued() => Timestamp getExpires() => Timestamp getRedirectURI() isUsed() => Boolean getScope() => OAuthScope ((When used then used=True should be set, the row should NOT be deleted. Deletion should only occur when expired. This is to allow tokens to be revoked if the auth_code is used twice.)) } oauth2_authcode: oac_code varbinary() PRIMARY KEY oac_auth => OAuthAuthorization // oac_user => user.user_id oac_issued binary(14) NOT NULL oac_expires binary(14) NOT NULL oac_redirect_uri BLOB oac_used bool NOT NULL default 0 oac_scope (format?) OAuthAccessToken { getToken() => (string) Access token getType() => Token type; "bearer" / "mac" getApplication() => OAuthApplication getUser() => User; May be NULL if "Client Credentials Grant" was used getIssued() => Timestamp getExpires() => Timestamp getScope() => OAuthScope } OAuthBearerAccessToken < OAuthAccessToken {} OAuthMACAccessToken < OAuthAccessToken { getMACKey() => The issued MAC key getMACAlgo() => Algorithm "hmac-sha-1" / "hmac-sha-256" getDelta() => (int) Time delta to the client. NULL before first request. } oauth2_accesstoken: oat_token varbinary() PRIMARY KEY oat_type varbinary(32) NOT NULL oat_app int unsigned NOT NULL oat_user int unsigned ((@todo This almost feels like it should be oac_auth pointing to an authorization but that wouldn't for for Client Credentials Grant)) oat_issued binary(14) NOT NULL oat_expires binary(14) NOT NULL oat_scope (format?) // @todo Decide where MAC data goes /*OAuthMACNonce { access_token (reference) timestamp INDEXed nonce PK(access_token, nonce) }*/ (( Not using a link to an access token's PK or oauth allows us room to use this for things other than just OAuth 2 MAC Access tokens. The spec itself is written in a way that you could apply it to almost any situation you are using HTTP Authorization )) mac_nonce: mac_identifier varbinary() mac_timestamp binary(14) NOT NULL INDEX mac_nonce varbinary() NOT NULL UNIQUE(mac_id, timestamp, nonce) OAuthRefreshToken { getApplication() => OAuthApplication getUser() => User; ->getAuthorization()->getUser() getIssued() => Timestamp getScope() => OAuthScope ((Add some way to indicate the client credentials have changed and the refresh token should be reissued? Or maybe we'll just issue a new refresh token on every access token request?)) ((Consider keeping old refresh tokens to track clients that leak tokens?)) } oauth2_refreshtoken: ort_token varbinary() PRIMARY KEY ort_auth int unsigned NOT NULL ort_issued binary(14) NOT NULL ort_scope (format?) // @todo We may need an optional link from access_tokens to a refresh_token so when we revoke a refresh token the access_tokens are revoked
Every user object comes with a ->getApplication() ((@note Should that be getAuthorzation() instead?)) method that returns an Application object. MediaWiki comes with 2 built in non-oauth Applications used for normal cases.
When you just plainly create an instance of a User object the default is for an id=0 Application to be used. This id is reserved for an "Unknown" application. As a result automatic system-made edits like the first edit to the Main Page on installation and some changes made by extensions using dummy users will be attributed to this app.
The other application reserved at id=1 is an "Internal" application. When a user instance is created using session data the "Internal" application is then set on that user instance. As a result edits made by normal user actions will be attributed to this application.
Endpoint possibilities
edit- OAuth 2 Authorization endpoint:
/api.php?action=oauth2&endpoint=auth&response_type=...
/api.php?action=oauth2.auth&response_type=...
- This has the advantage of an easy to find location within the API (ie: You can use RSD discovery and then use ?action=oauth2.action to get the endpoint easily)
/index.php?title=Special:OAuth&response_type=...
- The Authorization endpoint requires user interaction with a user interface; Logging in. Reviewing permissions. And allowing the application access.
- OAuth 2 Token endpoint:
/api.php?action=oauth2&endpoint=token&grant_type=authorization_code&code=...&redirect_uri=...
/api.php?action=oauth2.token&grant_type=authorization_code&code=...&redirect_uri=...
Revocation
editBesides the normal user-invoked revocation there's an interesting draft spec on letting clients request for one of their own access tokens or refresh token to be revoked. In some cases it's suggested that it's like a "Log out" or "Reset" to the user. But one possible use case would be signing up or linking an account with an OAuth connection. And later telling the app to "Forget" that link and log you in in a different way. In that situation instead of just leaving authorization data in the ether the application could use the revocation spec to revoke it's own access.
- Suggested endpoint
/api.php?action=oauth2.revoke
- Spec
- https://tools.ietf.org/html/draft-lodderstedt-oauth-revocation-04
Discovery
editHaven't quite decided on this. A lot of ideas and thoughts floating around on this idea.
Extra reading
edit- https://tools.ietf.org/html/draft-ietf-oauth-v2-29 - OAuth 2
- https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 - OAuth 2 MAC access_token
- https://tools.ietf.org/html/draft-hardjono-oauth-dynreg-03 - Spec draft on one standard for dynamic registration of OAuth clients.
- http://phpseclib.sourceforge.net/ - PHP Implementation of secret key and pubkey cryptography. Useful if we decide to implement custom discoverability with extra security.
(Throwaway) idea on obtaining Authorization: MAC credentials for a client by registering a pubkey for the client:
POST /api.php?action=oauth2.mac&client_id=[...] HTTP/1.1 ...:... HTTP/1.1 200 OK Content-Type: [BASE64 encoded RSA(?) encrypted data] Decodes to: { "client_secret":"...", "token_type":"mac", "expires_in":3600, "mac_key":"...", "mac_algorithm":"hmac-sha-256" }