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 <cste...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to