User:Dantman/OAuth/Brainstorm

From mediawiki.org
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:
    1. /api.php?action=oauth2&endpoint=auth&response_type=...
    2. /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)
    3. /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:
    1. /api.php?action=oauth2&endpoint=token&grant_type=authorization_code&code=...&redirect_uri=...
    2. /api.php?action=oauth2.token&grant_type=authorization_code&code=...&redirect_uri=...

Revocation[edit]

Besides 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[edit]

Haven't quite decided on this. A lot of ideas and thoughts floating around on this idea.

Extra reading[edit]

(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"
}