X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Ffilebackend%2FSwiftFileBackend.php;h=4212ff5f0704798c9243c3291c3adc4eff0284fa;hb=39f0f919c5708f4c672a8eb7e0891f50bf16883e;hp=de5a1038315568fe1d627613743aba37a60b5b16;hpb=fc5af1cb1c6fe7516fa045c90187d7def05223ba;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/filebackend/SwiftFileBackend.php b/includes/libs/filebackend/SwiftFileBackend.php index de5a103831..bce83348d7 100644 --- a/includes/libs/filebackend/SwiftFileBackend.php +++ b/includes/libs/filebackend/SwiftFileBackend.php @@ -50,6 +50,10 @@ class SwiftFileBackend extends FileBackendStore { protected $rgwS3AccessKey; /** @var string S3 authentication key (RADOS Gateway) */ protected $rgwS3SecretKey; + /** @var array Additional users (account:user) to open read permissions for */ + protected $readUsers; + /** @var array Additional users (account:user) to open write permissions for */ + protected $writeUsers; /** @var BagOStuff */ protected $srvCache; @@ -83,7 +87,7 @@ class SwiftFileBackend extends FileBackendStore { * - levels : the number of hash levels (and digits) * - repeat : hash subdirectories are prefixed with all the * parent hash directory names (e.g. "a/ab/abc") - * - cacheAuthInfo : Whether to cache authentication tokens in APC, XCache, ect. + * - cacheAuthInfo : Whether to cache authentication tokens in APC, etc. * If those are not available, then the main cache will be used. * This is probably insecure in shared hosting environments. * - rgwS3AccessKey : Rados Gateway S3 "access key" value on the account. @@ -96,6 +100,8 @@ class SwiftFileBackend extends FileBackendStore { * This is used for generating expiring pre-authenticated URLs. * Only use this when using rgw and to work around * http://tracker.newdream.net/issues/3454. + * - readUsers : Swift users that should have read access (account:username) + * - writeUsers : Swift users that should have write access (account:username) */ public function __construct( array $config ) { parent::__construct( $config ); @@ -136,6 +142,12 @@ class SwiftFileBackend extends FileBackendStore { } else { $this->srvCache = new EmptyBagOStuff(); } + $this->readUsers = isset( $config['readUsers'] ) + ? $config['readUsers'] + : []; + $this->writeUsers = isset( $config['writeUsers'] ) + ? $config['writeUsers'] + : []; } public function getFeatures() { @@ -146,7 +158,7 @@ class SwiftFileBackend extends FileBackendStore { protected function resolveContainerPath( $container, $relStoragePath ) { if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { return null; // not UTF-8, makes it hard to use CF and the swift HTTP API - } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) { + } elseif ( strlen( rawurlencode( $relStoragePath ) ) > 1024 ) { return null; // too long for Swift } @@ -169,6 +181,29 @@ class SwiftFileBackend extends FileBackendStore { * @param array $params * @return array Sanitized value of 'headers' field in $params */ + protected function sanitizeHdrsStrict( array $params ) { + if ( !isset( $params['headers'] ) ) { + return []; + } + + $headers = $this->getCustomHeaders( $params['headers'] ); + unset( $headers[ 'content-type' ] ); + + return $headers; + } + + /** + * Sanitize and filter the custom headers from a $params array. + * Only allows certain "standard" Content- and X-Content- headers. + * + * When POSTing data, libcurl adds Content-Type: application/x-www-form-urlencoded + * if Content-Type is not set, which overwrites the stored Content-Type header + * in Swift - therefore for POSTing data do not strip the Content-Type header (the + * previously-stored header that has been already read back from swift is sent) + * + * @param array $params + * @return array Sanitized value of 'headers' field in $params + */ protected function sanitizeHdrs( array $params ) { return isset( $params['headers'] ) ? $this->getCustomHeaders( $params['headers'] ) @@ -185,7 +220,7 @@ class SwiftFileBackend extends FileBackendStore { // Normalize casing, and strip out illegal headers foreach ( $rawHeaders as $name => $value ) { $name = strtolower( $name ); - if ( preg_match( '/^content-(type|length)$/', $name ) ) { + if ( preg_match( '/^content-length$/', $name ) ) { continue; // blacklisted } elseif ( preg_match( '/^(x-)?content-/', $name ) ) { $headers[$name] = $value; // allowed @@ -264,7 +299,7 @@ class SwiftFileBackend extends FileBackendStore { 'etag' => md5( $params['content'] ), 'content-type' => $contentType, 'x-object-meta-sha1base36' => $sha1Hash - ] + $this->sanitizeHdrs( $params ), + ] + $this->sanitizeHdrsStrict( $params ), 'body' => $params['content'] ] ]; @@ -300,9 +335,9 @@ class SwiftFileBackend extends FileBackendStore { return $status; } - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $sha1Hash = sha1_file( $params['src'] ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); if ( $sha1Hash === false ) { // source doesn't exist? $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); @@ -328,7 +363,7 @@ class SwiftFileBackend extends FileBackendStore { 'etag' => md5_file( $params['src'] ), 'content-type' => $contentType, 'x-object-meta-sha1base36' => $sha1Hash - ] + $this->sanitizeHdrs( $params ), + ] + $this->sanitizeHdrsStrict( $params ), 'body' => $handle // resource ] ]; @@ -379,7 +414,7 @@ class SwiftFileBackend extends FileBackendStore { 'headers' => [ 'x-copy-from' => '/' . rawurlencode( $srcCont ) . '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) ) - ] + $this->sanitizeHdrs( $params ), // extra headers merged into object + ] + $this->sanitizeHdrsStrict( $params ), // extra headers merged into object ] ]; $method = __METHOD__; @@ -428,7 +463,7 @@ class SwiftFileBackend extends FileBackendStore { 'headers' => [ 'x-copy-from' => '/' . rawurlencode( $srcCont ) . '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) ) - ] + $this->sanitizeHdrs( $params ) // extra headers merged into object + ] + $this->sanitizeHdrsStrict( $params ) // extra headers merged into object ] ]; if ( "{$srcCont}/{$srcRel}" !== "{$dstCont}/{$dstRel}" ) { @@ -590,11 +625,13 @@ class SwiftFileBackend extends FileBackendStore { $stat = $this->getContainerStat( $fullCont ); if ( is_array( $stat ) ) { + $readUsers = array_merge( $this->readUsers, [ $this->swiftUser ] ); + $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] ); // Make container private to end-users... $status->merge( $this->setContainerAccess( $fullCont, - [ $this->swiftUser ], // read - [ $this->swiftUser ] // write + $readUsers, + $writeUsers ) ); } elseif ( $stat === false ) { $status->fatal( 'backend-fail-usable', $params['dir'] ); @@ -611,11 +648,14 @@ class SwiftFileBackend extends FileBackendStore { $stat = $this->getContainerStat( $fullCont ); if ( is_array( $stat ) ) { + $readUsers = array_merge( $this->readUsers, [ $this->swiftUser, '.r:*' ] ); + $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] ); + // Make container public to end-users... $status->merge( $this->setContainerAccess( $fullCont, - [ $this->swiftUser, '.r:*' ], // read - [ $this->swiftUser ] // write + $readUsers, + $writeUsers ) ); } elseif ( $stat === false ) { $status->fatal( 'backend-fail-usable', $params['dir'] ); @@ -697,7 +737,8 @@ class SwiftFileBackend extends FileBackendStore { /** @noinspection PhpUnusedLocalVariableInspection */ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" ); - $this->logger->error( __METHOD__ . ": $path was not stored with SHA-1 metadata." ); + $this->logger->error( __METHOD__ . ": {path} was not stored with SHA-1 metadata.", + [ 'path' => $path ] ); $objHdrs['x-object-meta-sha1base36'] = false; @@ -737,7 +778,8 @@ class SwiftFileBackend extends FileBackendStore { } } - $this->logger->error( __METHOD__ . ": unable to set SHA-1 metadata for $path" ); + $this->logger->error( __METHOD__ . ': unable to set SHA-1 metadata for {path}', + [ 'path' => $path ] ); return $objHdrs; // failed } @@ -1309,7 +1351,7 @@ class SwiftFileBackend extends FileBackendStore { * (lists are truncated to 10000 item with no way to page), and is just a performance risk. * * @param string $container Resolved Swift container - * @param array $readGrps List of the possible criteria for a request to have + * @param array $readUsers List of the possible criteria for a request to have * access to read a container. Each item is one of the following formats: * - account:user : Grants access if the request is by the given user * - ".r:" : Grants access if the request is from a referrer host that @@ -1317,12 +1359,12 @@ class SwiftFileBackend extends FileBackendStore { * Setting this to '*' effectively makes a container public. * -".rlistings:" : Grants access if the request is from a referrer host that * matches the expression and the request is for a listing. - * @param array $writeGrps A list of the possible criteria for a request to have + * @param array $writeUsers A list of the possible criteria for a request to have * access to write to a container. Each item is of the following format: * - account:user : Grants access if the request is by the given user * @return StatusValue */ - protected function setContainerAccess( $container, array $readGrps, array $writeGrps ) { + protected function setContainerAccess( $container, array $readUsers, array $writeUsers ) { $status = $this->newStatus(); $auth = $this->getAuthentication(); @@ -1336,14 +1378,15 @@ class SwiftFileBackend extends FileBackendStore { 'method' => 'POST', 'url' => $this->storageUrl( $auth, $container ), 'headers' => $this->authTokenHeaders( $auth ) + [ - 'x-container-read' => implode( ',', $readGrps ), - 'x-container-write' => implode( ',', $writeGrps ) + 'x-container-read' => implode( ',', $readUsers ), + 'x-container-write' => implode( ',', $writeUsers ) ] ] ); if ( $rcode != 204 && $rcode !== 202 ) { $status->fatal( 'backend-fail-internal', $this->name ); - $this->logger->error( __METHOD__ . ': unexpected rcode value (' . $rcode . ')' ); + $this->logger->error( __METHOD__ . ': unexpected rcode value ({rcode})', + [ 'rcode' => $rcode ] ); } return $status; @@ -1420,18 +1463,19 @@ class SwiftFileBackend extends FileBackendStore { // @see SwiftFileBackend::setContainerAccess() if ( empty( $params['noAccess'] ) ) { - $readGrps = [ '.r:*', $this->swiftUser ]; // public + $readUsers = array_merge( $this->readUsers, [ '.r:*', $this->swiftUser ] ); // public } else { - $readGrps = [ $this->swiftUser ]; // private + $readUsers = array_merge( $this->readUsers, [ $this->swiftUser ] ); // private } - $writeGrps = [ $this->swiftUser ]; // sanity + + $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] ); // sanity list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [ 'method' => 'PUT', 'url' => $this->storageUrl( $auth, $container ), 'headers' => $this->authTokenHeaders( $auth ) + [ - 'x-container-read' => implode( ',', $readGrps ), - 'x-container-write' => implode( ',', $writeGrps ) + 'x-container-read' => implode( ',', $readUsers ), + 'x-container-write' => implode( ',', $writeUsers ) ] ] ); @@ -1753,10 +1797,18 @@ class SwiftFileBackend extends FileBackendStore { if ( $code == 401 ) { // possibly a stale token $this->srvCache->delete( $this->getCredsCacheKey( $this->swiftUser ) ); } - $this->logger->error( - "HTTP $code ($desc) in '{$func}' (given '" . FormatJson::encode( $params ) . "')" . - ( $err ? ": $err" : "" ) - ); + $msg = "HTTP {code} ({desc}) in '{func}' (given '{params}')"; + $msgParams = [ + 'code' => $code, + 'desc' => $desc, + 'func' => $func, + 'req_params' => FormatJson::encode( $params ), + ]; + if ( $err ) { + $msg .= ': {err}'; + $msgParams['err'] = $err; + } + $this->logger->error( $msg, $msgParams ); } }