CSteipp has uploaded a new change for review.
https://gerrit.wikimedia.org/r/68001
Change subject: WIP commit of some basic classes for /initial call
......................................................................
WIP commit of some basic classes for /initial call
Not functional, but just so everyone can start adding to some common
classes.
Change-Id: I395e7e07eab652d96216af2cafe42a0c26db4ec7
---
A backend/OAuthClientRequestHandler.php
M backend/OAuthUtils.php
A backend/requestHandler/OAuthClientRequestHandler.php
A backend/store/OAuthStore.php
A frontend/special/SpecialOAuth.php
A lib/OAuthClient.php
A lib/OAuthClientRequest.php
A lib/OAuthSignature.php
8 files changed, 650 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/OAuth
refs/changes/01/68001/1
diff --git a/backend/OAuthClientRequestHandler.php
b/backend/OAuthClientRequestHandler.php
new file mode 100644
index 0000000..9060386
--- /dev/null
+++ b/backend/OAuthClientRequestHandler.php
@@ -0,0 +1,140 @@
+<?php
+
+abstract class OAuthClientRequestHandler {
+
+ // Type of OAuth Request (initiate, authorize, token)
+ protected $requestType;
+
+ // OAuth datastore
+ protected $store;
+
+ public static function newHandler( $type, OAuthStore $store ) {
+ $validTypes = array( 'initiate', 'authorize', 'token' );
+ if ( !in_array( $type, $validTypes ) ) {
+ return null;
+ }
+ $this->requestType = $type;
+ $this->store = $store;
+ $class = 'OAuthClientRequestHandler' . ucfirst( $type );
+ return new $class;
+ }
+
+
+ /**
+ *
+ *
+ */
+ public function verifyRequest( OAuthClientRequest $request ) {
+
+ // test sanity
+ if ( !$request->consumerKey || !$request->signature ||
!$request->signatureMethod ) {
+ throw new MWOAuthException( 'missing required
parameters' );
+ }
+
+ if ( $request->version !== false && $request->version != '1.0'
) {
+ throw new MWOAuthException( 'only OAuth 1.0 is
supported at this time' );
+ }
+
+ // Check request signature
+ $sigVerify = $request->getSignatureClass();
+ $sigString = $request->getSignatureString(
$this->getSignatureParameters() );
+ if ( !$sigVerify->verify( $sigString,
$this->store->getClientFromKey( $request->consumerKey ) ) ) {
+ throw new MWOAuthException( 'invalid-signature' );
+ }
+
+ // Verify freshness and nonce
+ if ( time() > $request->timestamp + ( 60 * 5 ) ) {
+ throw new MWOAuthException( 'timestamp-too-old' );
+ }
+
+ if ( $this->store->seenNonce( $request->nonce,
$request->consumerKey ) ) {
+ throw new MWOAuthException( 'nonce-used' );
+ }
+
+ }
+}
+
+
+class OAuthClientRequestHandlerInitiate {
+
+ public function process( OAuthClientRequest $request ) {
+
+ $this->verifyRequest( $request );
+
+ $client = $this->store->getClientFromKey( $request->consumerKey
);
+ if ( !$client || !$client->isValid() || $client->isBlocked() ) {
+ throw new MWOAuthException( 'invalid-client' );
+ }
+
+ if ( !$client->isValidCallback() ) {
+ throw new MWOAuthException( 'invalid-callback' );
+ }
+
+ // register new oauth_token + secret for authorization
+ $token = OAuthToken::newForAuthorization();
+ $this->store->saveToken( $token );
+
+ return array(
+ 'oauth_token' => $token->token,
+ 'oauth_token_secret' => $token->secret,
+ 'oauth_callback_confirmed' => true
+ );
+
+ }
+
+ protected function getSignatureParameters() {
+ return array( 'oauth_consumer_key',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ 'oauth_nonce',
+ 'oauth_callback'
+ );
+ }
+
+}
+
+class OAuthClientRequestHandlerAuthorize {
+
+
+}
+
+class OAuthClientRequestHandlerToken {
+
+
+ public function process( OAuthClientRequest $request ) {
+ $this->verifyRequest( $request );
+
+ $client = $this->store->getClientFromKey( $request->consumerKey
);
+ if ( !$client || !$client->isValid() || $client->isBlocked() ) {
+ throw new MWOAuthException( 'invalid-client' );
+ }
+
+ // createa an authorized token
+ $token = OAuthToken::newAuthorizedToken( $request->consumerKey,
$request->token );
+
+ if ( !$token ) {
+ throw new MWOAuthException( 'invalid-request' );
+ }
+
+ $this->store->saveToken( $token );
+
+ return array(
+ 'oauth_token' => $token->token,
+ 'oauth_token_secret' => $token->secret,
+ 'oauth_callback_confirmed' => true
+ );
+
+ }
+
+
+ protected function getSignatureParameters() {
+ array( 'oauth_consumer_key',
+ 'oauth_token',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ 'oauth_nonce',
+ 'oauth_verifier',
+ 'oauth_callback'
+ );
+ }
+}
diff --git a/backend/OAuthUtils.php b/backend/OAuthUtils.php
index 31141b9..7acfee4 100644
--- a/backend/OAuthUtils.php
+++ b/backend/OAuthUtils.php
@@ -4,4 +4,15 @@
*/
class OAuthUtils {
+ public static function getHeaderParams( $header ) {
+ $params = array();
+ if ( preg_match_all('/([a-z_-]*)=(:?"([^"]*)"|([^,]*))/',
$header, $matches ) ) {
+ foreach ( $matches[1] as $ndx => $param ) {
+ // Note: rawurldecode in php 5.3+ should be
fine here
+ $params[$param] = rawurldecode( empty(
$matches[3][$ndx] ) ? $matches[4][$ndx] : $matches[3][$ndx] );
+ }
+ }
+ return $params;
+ }
+
}
diff --git a/backend/requestHandler/OAuthClientRequestHandler.php
b/backend/requestHandler/OAuthClientRequestHandler.php
new file mode 100644
index 0000000..c72e15b
--- /dev/null
+++ b/backend/requestHandler/OAuthClientRequestHandler.php
@@ -0,0 +1,146 @@
+<?php
+
+// Handle a special page or api request
+
+abstract class OAuthClientRequestHandler {
+
+ // Type of OAuth Request (initiate, authorize, token)
+ protected $requestType;
+
+ // OAuth datastore
+ protected $store;
+
+ abstract public function process( OAuthClientRequest $request );
+ abstract protected function getSignatureParameters();
+
+ public static function newHandler( $type, $store ) {
+ $validTypes = array( 'initiate', 'authorize', 'token' );
+ if ( !in_array( $type, $validTypes ) ) {
+ return null;
+ }
+ $this->requestType = $type;
+ $this->store = $store;
+ $class = 'OAuthClientRequestHandler' . ucfirst( $type );
+ return new $class;
+ }
+
+
+ /**
+ *
+ *
+ */
+ public function verifyRequest( OAuthClientRequest $request ) {
+
+ // test sanity
+ if ( !$request->consumerKey || !$request->signature ||
!$request->signatureMethod ) {
+ throw new MWOAuthException( 'missing required
parameters' );
+ }
+
+ if ( $request->version !== false && $request->version != '1.0'
) {
+ throw new MWOAuthException( 'only OAuth 1.0 is
supported at this time' );
+ }
+
+ // Check request signature
+ $sigVerify = $request->getSignatureClass();
+ $sigString = $request->getSignatureString(
$this->getSignatureParameters() );
+ if ( !$sigVerify->verify( $sigString,
$this->store->getClientFromKey( $request->consumerKey ) ) ) {
+ throw new MWOAuthException( 'invalid-signature' );
+ }
+
+ // Verify freshness and nonce
+ if ( time() > $request->timestamp + ( 60 * 5 ) ) {
+ throw new MWOAuthException( 'timestamp-too-old' );
+ }
+
+ if ( $this->store->seenNonce( $request->nonce,
$request->consumerKey ) ) {
+ throw new MWOAuthException( 'nonce-used' );
+ }
+
+ }
+}
+
+
+class OAuthClientRequestHandlerInitiate extends OAuthClientRequestHandler {
+
+ public function process( OAuthClientRequest $request ) {
+
+ $this->verifyRequest( $request );
+
+ $client = $this->store->getClientFromKey( $request->consumerKey
);
+ if ( !$client || !$client->isValid() || $client->isBlocked() ) {
+ throw new MWOAuthException( 'invalid-client' );
+ }
+
+ if ( !$client->isValidCallback() ) {
+ throw new MWOAuthException( 'invalid-callback' );
+ }
+
+ // register new oauth_token + secret for authorization
+ $token = OAuthToken::newForAuthorization();
+ $this->store->saveToken( $token );
+
+ return array(
+ 'oauth_token' => $token->token,
+ 'oauth_token_secret' => $token->secret,
+ 'oauth_callback_confirmed' => true
+ );
+
+ }
+
+ protected function getSignatureParameters() {
+ return array( 'oauth_consumer_key',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ 'oauth_nonce',
+ 'oauth_callback'
+ );
+ }
+
+}
+
+class OAuthClientRequestHandlerAuthorize extends OAuthClientRequestHandler {
+
+ // this may not be the best idea?
+
+}
+
+class OAuthClientRequestHandlerToken extends OAuthClientRequestHandler {
+
+
+ public function process( OAuthClientRequest $request ) {
+ $this->verifyRequest( $request );
+
+ $client = $this->store->getClientFromKey( $request->consumerKey
);
+ if ( !$client || !$client->isValid() || $client->isBlocked() ) {
+ throw new MWOAuthException( 'invalid-client' );
+ }
+
+ // createa an authorized token
+ $token = OAuthToken::newAuthorizedToken( $request->consumerKey,
$request->token );
+
+ if ( !$token ) {
+ throw new MWOAuthException( 'invalid-request' );
+ }
+
+ $this->store->saveToken( $token );
+
+ return array(
+ 'oauth_token' => $token->token,
+ 'oauth_token_secret' => $token->secret,
+ 'oauth_callback_confirmed' => true
+ );
+
+ }
+
+
+ protected function getSignatureParameters() {
+ array( 'oauth_consumer_key',
+ 'oauth_token',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ 'oauth_nonce',
+ 'oauth_verifier',
+ 'oauth_callback'
+ );
+ }
+}
diff --git a/backend/store/OAuthStore.php b/backend/store/OAuthStore.php
new file mode 100644
index 0000000..a7b2ce6
--- /dev/null
+++ b/backend/store/OAuthStore.php
@@ -0,0 +1,40 @@
+<?php
+
+
+abstract class OAuthStore {
+
+ // ObjectCache for Tokens and Nonces
+ protected $cache;
+
+ // Persistant storage (DB most likely) for logging/audit
+ protected $logging;
+
+ public function __construct( ObjectCache $cache, $log ) {
+ $this->cache = $cache;
+ $this->loggging = $log;
+ }
+
+ public static function getStore( ) {
+ global $wgOAuthStorage;
+
+ $datastore = $wgOAuthStorage['data'];
+ $cachestore = $wgOAuthStorage['cache'];
+ $logstore = $wgOAuthStorage['log'];
+ $cache = new $cachestore();
+ $log = new $logstore();
+ return new $datastore( $cache, $log );
+ }
+
+ abstract public function getClientFromKey( $key);
+
+ //abstract public function getPublicKeyForClient( $client );
+
+ abstract public function saveToken( $token );
+
+}
+
+class OAuthStoreMysql extends OAuthStore {
+
+
+
+}
diff --git a/frontend/special/SpecialOAuth.php
b/frontend/special/SpecialOAuth.php
new file mode 100644
index 0000000..e3e1051
--- /dev/null
+++ b/frontend/special/SpecialOAuth.php
@@ -0,0 +1,59 @@
+<?php
+
+class SpecialOAuth extends UnlistedSpecialPage {
+
+ function __construct() {
+ parent::__construct( 'OAuth' );
+ }
+
+ public function execute( $subpage ) {
+ global $wgOAuthStorageType;
+
+ $format = $request->getVal( 'format', 'json' );
+
+ if ( !in_array( $subpage, array( 'initiate', 'authorize',
'token' ) ) ) {
+ $this->showError( 'oauth-client-invalidrequest',
$format );
+ }
+
+ try {
+ $out = $this->getOutput();
+ $request = $this->getRequest();
+
+ $store = OAuthStore::getStore( $wgOAuthStorageType );
+ $clientRequest = new OAuthClientRequest( $request,
$subpage );
+ $oauthClientHandler =
OAuthClientRequestHandler::newHandler( $subpage, $store );
+ $oauthClientHandler->verifyRequest( $clientRequest );
+
+ if ( !$oauthClientHandler || !$clientRequest->isValid()
) {
+ $this->showError(
'oauth-client-invalidrequest', $format );
+ }
+
+ $response = $oauthClientHandler->process(
$clientRequest );
+
+ $this->showResponse( $response, $request->getVal(
'format', 'json' ) );
+
+ } catch ( MWOAuthException $exception ) {
+ $this->showError( $exception->getMessage(), $format );
+ }
+ }
+
+ /**
+ *
+ *
+ * @param string $message message key to return to the user
+ * @param string $format the format of the response: json, xml, or html
+ */
+ private function showError( $message, $format ) {
+
+ }
+
+ /**
+ *
+ *
+ * @param array $response values to give back to the client
+ * @param string $format the format of the response: json, xml, or html
+ */
+ private function showResponse( array $response, $format ) {
+
+ }
+}
diff --git a/lib/OAuthClient.php b/lib/OAuthClient.php
new file mode 100644
index 0000000..2f6367e
--- /dev/null
+++ b/lib/OAuthClient.php
@@ -0,0 +1,48 @@
+<?php
+
+
+class OAuthClient {
+
+ const DELETED_NAME = 1; // Assuming we will want to have this available
+ const SUPPRESSED_NAME = 2;
+
+ protected $consumerKey;
+ protected $hmacSecret, $rsaPublicKey;
+ protected $authorized, $blocked, $deleted;
+
+ protected $owner;
+
+ public function __construct( $consumerKey, $hmacSecret, $rsaPublicKey,
$blocked, $authorized ) {
+ $this->consumerKey = $consumerKey;
+ $this->hmacSecret = $hmacSecret;
+ $this->rsaPublicKey = $rsaPublicKey;
+ $this->authorized = $authorized;
+ $this->blocked = $blocked;
+ $this->authorized = $authorized;
+ }
+
+
+ public function isValid() {
+ return $authorized && !$this->isDeleted() &&
!$this->isSuppressed();
+ }
+
+ public function isBlocked() {
+ return ( $blocked == 1 );
+ }
+
+ public function isDeleted() {
+ return ( $deleted & DELETED_NAME );
+ }
+
+ public function isSuppressed() {
+ return ( $deleted & SUPPRESSED_NAME );
+ }
+
+ public function getPublicKey() {
+ return $this->rsaPublicKey;
+ }
+
+ public function getHmacKey() {
+ return $this->hmacSecret;
+ }
+}
diff --git a/lib/OAuthClientRequest.php b/lib/OAuthClientRequest.php
new file mode 100644
index 0000000..dd6a440
--- /dev/null
+++ b/lib/OAuthClientRequest.php
@@ -0,0 +1,126 @@
+<?php
+
+class OAuthClientRequest {
+
+ // Stage of the OAuth protocol handshake where this request came from
+ // initiate, authorize, token, api
+ public $type;
+
+ //(optional) OAuth realm
+ public $realm;
+
+ //oauth_consumer_key (rand string)
+ public $consumerKey;
+
+ //oauth_signature_method ["HMAC-SHA1" or "RSA-SHA1"]
+ public $signatureMethod;
+
+ //oauth_timestamp (unix timestamp)
+ public $timestamp;
+
+ //oauth_nonce (random string to prevent replay)
+ public $nonce;
+
+ //oauth_callback (encoded url)
+ public $callback;
+
+ //oauth_signature (encoded string)
+ public $signature;
+
+
+ public $http_method;
+ public $http_url;
+
+
+ // This should be reusable from a special page, API, or unit test
request
+ public function __construct( $request, $type ) {
+ $this->type = $type;
+
+ if ( $request instanceof WebRequest ) {
+ $params = $this->getParamsFromHeader(
$request->getHeader( 'Authorize' ) );
+ $params['http_method'] = $request->getMethod();
+ $params['http_url'] = $request->getRequestURL(); //not
sure about this..
+ } else {
+ $params = $request;
+ }
+
+ if ( is_array( $params ) ) {
+ $this->realm = $params['realm'];
+ $this->consumerKey = $params['oauth_consumer_key'];
+ $this->signatureMethod =
$params['oauth_signature_method'];
+ $this->timestamp = $params['oauth_timestamp'];
+ $this->nonce = $params['oauth_nonce'];
+ $this->callback = $params['oauth_callback'];
+ $this->signature = $params['oauth_signature'];
+
+ $this->http_method = $params['http_method'];
+ } else {
+ wfDebug();
+ $this->valid = false;
+ return null;
+ }
+ }
+
+ /**
+ * Split up and decode the Authorization Header
+ *
+ * @param string $header the HTTP header
+ */
+ protected function getParamsFromHeader( $header ) {
+ $header_parameters = null;
+ if ( $header && substr( $header, 0, 6) == 'OAuth ' ) {
+ $header_parameters = OAuthUtils::getHeaderParams(
$header );
+ }
+ return $header_parameters;
+ }
+
+
+ public function setValid( $valid ) {
+ $this->valid = $valid;
+ }
+
+ public function isValid() {
+ return $this->valid;
+ }
+
+
+ public function getSignatureClassName() {
+ if ( $this->signatureMethod === 'HMAC-SHA1' ) {
+ return new OAuthSignatureHMAC;
+ } elseif ( $this->signatureMethod === 'RSA-SHA1' ) {
+ return new OAuthSignatureRSA;
+ } else {
+ throw new MWOAuthException( 'invalid-signature-type' );
+ }
+ }
+
+ /**
+ * Returns the string that was signed (or should have been signed) for
this request
+ *
+ * @param array $paramList an array of strings, listing the params that
should have been
+ * included and signed for this request type.
+ * @return string the string to sign or chech signature.
+ */
+ public function getSignatureString( $paramList ) {
+ $parts = array(
+ strtoupper( $this->http_method ),
+ $this->http_url,
+ $this->getSignableParameters( $paramList )
+ );
+
+ // rawurlencode should be fine in php 5.3+
+ $parts = array_map( 'rawurlencode', $parts );
+
+ return implode('&', $parts);
+ }
+
+
+ protected function getSignableParameters( $paramList ) {
+ $pairs = array();
+ foreach ( $paramList as $param ) {
+ $pairs[] = rawurlencode( $param ) . '=' rawurlencode(
$this->$param );
+ }
+ return implode('&', $pairs);
+ }
+
+}
diff --git a/lib/OAuthSignature.php b/lib/OAuthSignature.php
new file mode 100644
index 0000000..c35ec47
--- /dev/null
+++ b/lib/OAuthSignature.php
@@ -0,0 +1,80 @@
+<?php
+
+
+abstract class OAuthSignature {
+
+ protected $request;
+
+ public function __construct( $request ) {
+ $this->request = $request;
+ }
+
+ // Stuff that must be signed.. or we could just check all parameters
and fail if the sig doesn't match
+ public getParamsSignedForRequest() {
+
+ switch ( $this->request->type ) {
+ case 'initiate':
+ return array( 'oauth_consumer_key',
'oauth_signature_method', 'oauth_timestamp', 'oauth_nonce', 'oauth_callback' );
+
+ case 'token':
+ return array( 'oauth_consumer_key',
'oauth_token', 'oauth_signature_method', 'oauth_timestamp', 'oauth_nonce',
'oauth_verifier', 'oauth_callback' );
+
+ case 'api':
+ // todo
+
+ default:
+ throw new MWOAuthException(
'invalid-request-type' );
+ }
+
+ }
+
+ abstract public function verify( $sigString, OAuthClient $client );
+}
+
+
+class OAuthSignatureRSA extends OAuthSignature {
+
+ public function verify( $sigString, OAuthClient $client ) {
+
+ $clientPublicKey = $client->getPublicKey();
+ $keyid = openssl_get_publickey( $clientPublicKey );
+ $ok = openssl_verify(
+ $sigString,
+ base64_decode( $request->signature ),
+ $keyid
+ );
+
+ openssl_free_key( $keyid );
+
+ if ( $ok !== 1 ) {
+ wfDebug( );
+ throw new MWOAuthException( 'invalid-signature' );
+ }
+
+
+ }
+
+}
+
+
+class OAuthSignatureHMAC extends OAuthSignature {
+
+ public function verify( $sigString, OAuthClient $client ) {
+
+ $clientSecret = $client->getHmacKey();
+ $signature = hash_hmac(
+ 'sha1',
+ $sigString,
+ $clientSecret,
+ true
+ );
+
+ if ( base64_encode( $signature ) !== $this->signature ) {
+ wfDebug( );
+ throw new MWOAuthException( 'invalid-signature' );
+ }
+
+
+ }
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/68001
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I395e7e07eab652d96216af2cafe42a0c26db4ec7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/OAuth
Gerrit-Branch: master
Gerrit-Owner: CSteipp <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits