diff options
Diffstat (limited to 'OAuth/src/Lib/OAuthServer.php')
-rw-r--r-- | OAuth/src/Lib/OAuthServer.php | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/OAuth/src/Lib/OAuthServer.php b/OAuth/src/Lib/OAuthServer.php new file mode 100644 index 00000000..49d2aac6 --- /dev/null +++ b/OAuth/src/Lib/OAuthServer.php @@ -0,0 +1,270 @@ +<?php +// vim: foldmethod=marker +/** + * The MIT License + * + * Copyright (c) 2007 Andy Smith + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files ( the "Software" ), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +namespace MediaWiki\Extensions\OAuth\Lib; + +use MediaWiki\Extensions\OAuth\Lib\OAuthDataStore; +use MediaWiki\Extensions\OAuth\Lib\OAuthException; +use MediaWiki\Extensions\OAuth\Lib\OAuthRequest; +use MediaWiki\Logger\LoggerFactory; + +class OAuthServer { + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = '1.0'; // hi blaine + protected $signature_methods = array(); + + /** @var OAuthDataStore */ + protected $data_store; + + /** @var \\Psr\\Log\\LoggerInterface */ + protected $logger; + + function __construct( $data_store ) { + $this->data_store = $data_store; + $this->logger = LoggerFactory::getInstance( 'OAuth' ); + } + + public function add_signature_method( $signature_method ) { + $this->signature_methods[$signature_method->get_name()] = $signature_method; + } + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token( &$request ) { + $this->get_version( $request ); + + $consumer = $this->get_consumer( $request ); + + // no token required for the initial token request + $token = null; + + $this->check_signature( $request, $consumer, $token ); + + // Rev A change + $callback = $request->get_parameter( 'oauth_callback' ); + $new_token = $this->data_store->new_request_token( $consumer, $callback ); + + return $new_token; + } + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token( &$request ) { + $this->get_version( $request ); + + $consumer = $this->get_consumer( $request ); + + // requires authorized request token + $token = $this->get_token( $request, $consumer, "request" ); + + $this->check_signature( $request, $consumer, $token ); + + // Rev A change + $verifier = $request->get_parameter( 'oauth_verifier' ); + $new_token = $this->data_store->new_access_token( $token, $consumer, $verifier ); + + return $new_token; + } + + /** + * verify an api call, checks all the parameters + */ + public function verify_request( &$request ) { + $this->get_version( $request ); + $consumer = $this->get_consumer( $request ); + $token = $this->get_token( $request, $consumer, "access" ); + $this->check_signature( $request, $consumer, $token ); + + return array( + $consumer, + $token + ); + } + + // Internals from here + + /** + * version 1 + */ + protected function get_version( &$request ) { + $version = $request->get_parameter( "oauth_version" ); + if ( !$version ) { + // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. + // Chapter 7.0 ( "Accessing Protected Ressources" ) + $version = '1.0'; + } + if ( $version !== $this->version ) { + throw new OAuthException( "OAuth version '$version' not supported" ); + } + + return $version; + } + + /** + * figure out the signature with some defaults + */ + private function get_signature_method( $request ) { + $signature_method = $request instanceof OAuthRequest ? $request->get_parameter( + "oauth_signature_method" + ) : null; + + if ( !$signature_method ) { + // According to chapter 7 ( "Accessing Protected Ressources" ) the signature-method + // parameter is required, and we can't just fallback to PLAINTEXT + throw new OAuthException( 'No signature method parameter. This parameter is required' ); + } + + if ( !in_array( $signature_method, array_keys( $this->signature_methods ) ) ) { + throw new OAuthException( + "Signature method '$signature_method' not supported " . "try one of the following: " . implode( + ", ", + array_keys( $this->signature_methods ) + ) + ); + } + + return $this->signature_methods[$signature_method]; + } + + /** + * try to find the consumer for the provided request's consumer key + */ + protected function get_consumer( $request ) { + $consumer_key = $request instanceof OAuthRequest ? $request->get_parameter( + "oauth_consumer_key" + ) : null; + + if ( !$consumer_key ) { + throw new OAuthException( "Invalid consumer key" ); + } + $this->logger->debug( __METHOD__ . ": getting consumer for '$consumer_key'" ); + $consumer = $this->data_store->lookup_consumer( $consumer_key ); + if ( !$consumer ) { + throw new OAuthException( "Invalid consumer" ); + } + + return $consumer; + } + + /** + * try to find the token for the provided request's token key + */ + protected function get_token( $request, $consumer, $token_type = "access" ) { + $token_field = $request instanceof OAuthRequest ? $request->get_parameter( + 'oauth_token' + ) : null; + + $token = $this->data_store->lookup_token( + $consumer, + $token_type, + $token_field + ); + if ( !$token ) { + throw new OAuthException( "Invalid $token_type token: $token_field" ); + } + + return $token; + } + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + protected function check_signature( $request, $consumer, $token ) { + // this should probably be in a different method + $timestamp = $request instanceof OAuthRequest ? $request->get_parameter( + 'oauth_timestamp' + ) : null; + $nonce = $request instanceof OAuthRequest ? $request->get_parameter( 'oauth_nonce' ) : null; + + $this->check_timestamp( $timestamp ); + $this->check_nonce( $consumer, $token, $nonce, $timestamp ); + + $signature_method = $this->get_signature_method( $request ); + $signature = $request->get_parameter( 'oauth_signature' ); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if ( !$valid_sig ) { + $this->logger->info( + __METHOD__ . ': Signature check (' . get_class( $signature_method ) . ') failed' + ); + throw new OAuthException( "Invalid signature" ); + } + } + + /** + * check that the timestamp is new enough + */ + private function check_timestamp( $timestamp ) { + if ( !$timestamp ) { + throw new OAuthException( + 'Missing timestamp parameter. The parameter is required' + ); + } + + // verify that timestamp is recentish + $now = time(); + if ( abs( $now - $timestamp ) > $this->timestamp_threshold ) { + throw new OAuthException( + "Expired timestamp, yours $timestamp, ours $now" + ); + } + } + + /** + * check that the nonce is not repeated + */ + private function check_nonce( $consumer, $token, $nonce, $timestamp ) { + if ( !$nonce ) { + throw new OAuthException( + 'Missing nonce parameter. The parameter is required' + ); + } + + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce( + $consumer, + $token, + $nonce, + $timestamp + ); + if ( $found ) { + throw new OAuthException( "Nonce already used: $nonce" ); + } + } + +} |