diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2014-12-27 15:41:37 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2014-12-31 11:43:28 +0100 |
commit | c1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch) | |
tree | 2b38796e738dd74cb42ecd9bfd151803108386bc /includes/libs/virtualrest/SwiftVirtualRESTService.php | |
parent | b88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff) |
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/libs/virtualrest/SwiftVirtualRESTService.php')
-rw-r--r-- | includes/libs/virtualrest/SwiftVirtualRESTService.php | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/includes/libs/virtualrest/SwiftVirtualRESTService.php b/includes/libs/virtualrest/SwiftVirtualRESTService.php new file mode 100644 index 00000000..011dabe0 --- /dev/null +++ b/includes/libs/virtualrest/SwiftVirtualRESTService.php @@ -0,0 +1,175 @@ +<?php +/** + * Virtual HTTP service client for Swift + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Example virtual rest service for OpenStack Swift + * @TODO: caching support (APC/memcached) + * @since 1.23 + */ +class SwiftVirtualRESTService extends VirtualRESTService { + /** @var array */ + protected $authCreds; + /** @var int UNIX timestamp */ + protected $authSessionTimestamp = 0; + /** @var int UNIX timestamp */ + protected $authErrorTimestamp = null; + /** @var int */ + protected $authCachedStatus = null; + /** @var string */ + protected $authCachedReason = null; + + /** + * @param array $params Key/value map + * - swiftAuthUrl : Swift authentication server URL + * - swiftUser : Swift user used by MediaWiki (account:username) + * - swiftKey : Swift authentication key for the above user + * - swiftAuthTTL : Swift authentication TTL (seconds) + */ + public function __construct( array $params ) { + parent::__construct( $params ); + } + + /** + * @return int|bool HTTP status on cached failure + */ + protected function needsAuthRequest() { + if ( !$this->authCreds ) { + return true; + } + if ( $this->authErrorTimestamp !== null ) { + if ( ( time() - $this->authErrorTimestamp ) < 60 ) { + return $this->authCachedStatus; // failed last attempt; don't bother + } else { // actually retry this time + $this->authErrorTimestamp = null; + } + } + // Session keys expire after a while, so we renew them periodically + return ( ( time() - $this->authSessionTimestamp ) > $this->params['swiftAuthTTL'] ); + } + + protected function applyAuthResponse( array $req ) { + $this->authSessionTimestamp = 0; + list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response']; + if ( $rcode >= 200 && $rcode <= 299 ) { // OK + $this->authCreds = array( + 'auth_token' => $rhdrs['x-auth-token'], + 'storage_url' => $rhdrs['x-storage-url'] + ); + $this->authSessionTimestamp = time(); + return true; + } elseif ( $rcode === 403 ) { + $this->authCachedStatus = 401; + $this->authCachedReason = 'Authorization Required'; + $this->authErrorTimestamp = time(); + return false; + } else { + $this->authCachedStatus = $rcode; + $this->authCachedReason = $rdesc; + $this->authErrorTimestamp = time(); + return null; + } + } + + public function onRequests( array $reqs, Closure $idGeneratorFunc ) { + $result = array(); + $firstReq = reset( $reqs ); + if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) { + // This was an authentication request for work requests... + $result = $reqs; // no change + } else { + // These are actual work requests... + $needsAuth = $this->needsAuthRequest(); + if ( $needsAuth === true ) { + // These are work requests and we don't have any token to use. + // Replace the work requests with an authentication request. + $result = array( + $idGeneratorFunc() => array( + 'method' => 'GET', + 'url' => $this->params['swiftAuthUrl'] . "/v1.0", + 'headers' => array( + 'x-auth-user' => $this->params['swiftUser'], + 'x-auth-key' => $this->params['swiftKey'] ), + 'isAuth' => true, + 'chain' => $reqs + ) + ); + } elseif ( $needsAuth !== false ) { + // These are work requests and authentication has previously failed. + // It is most efficient to just give failed pseudo responses back for + // the original work requests. + foreach ( $reqs as $key => $req ) { + $req['response'] = array( + 'code' => $this->authCachedStatus, + 'reason' => $this->authCachedReason, + 'headers' => array(), + 'body' => '', + 'error' => '' + ); + $result[$key] = $req; + } + } else { + // These are work requests and we have a token already. + // Go through and mangle each request to include a token. + foreach ( $reqs as $key => $req ) { + // The default encoding treats the URL as a REST style path that uses + // forward slash as a hierarchical delimiter (and never otherwise). + // Subclasses can override this, and should be documented in any case. + $parts = array_map( 'rawurlencode', explode( '/', $req['url'] ) ); + $req['url'] = $this->authCreds['storage_url'] . '/' . implode( '/', $parts ); + $req['headers']['x-auth-token'] = $this->authCreds['auth_token']; + $result[$key] = $req; + // @TODO: add ETag/Content-Length and such as needed + } + } + } + return $result; + } + + public function onResponses( array $reqs, Closure $idGeneratorFunc ) { + $firstReq = reset( $reqs ); + if ( $firstReq && count( $reqs ) == 1 && isset( $firstReq['isAuth'] ) ) { + $result = array(); + // This was an authentication request for work requests... + if ( $this->applyAuthResponse( $firstReq ) ) { + // If it succeeded, we can subsitute the work requests back. + // Call this recursively in order to munge and add headers. + $result = $this->onRequests( $firstReq['chain'], $idGeneratorFunc ); + } else { + // If it failed, it is most efficient to just give failing + // pseudo-responses back for the actual work requests. + foreach ( $firstReq['chain'] as $key => $req ) { + $req['response'] = array( + 'code' => $this->authCachedStatus, + 'reason' => $this->authCachedReason, + 'headers' => array(), + 'body' => '', + 'error' => '' + ); + $result[$key] = $req; + } + } + } else { + $result = $reqs; // no change + } + return $result; + } +} |