From fdf2665f06b7060eab55d0d48b4b90c3441a11fc Mon Sep 17 00:00:00 2001 From: Matthew Hilton Date: Thu, 31 Oct 2024 14:14:37 +1000 Subject: [PATCH] bugfix: fix sas token redaction --- classes/api.php | 46 +++++++++++++++++++++++++++----------- classes/stream_wrapper.php | 6 ++--- version.php | 4 ++-- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/classes/api.php b/classes/api.php index 7ae1d42..1309695 100644 --- a/classes/api.php +++ b/classes/api.php @@ -23,6 +23,7 @@ use GuzzleHttp\Psr7\Request; use Psr\Http\Message\StreamInterface; use coding_exception; +use GuzzleHttp\Exception\RequestException; /** * Azure blob storage API. @@ -76,14 +77,17 @@ class api { * @param string $account Azure storage account name * @param string $container Azure storage container name (inside the given storage account). * @param string $sastoken SAS (Shared access secret) token for authentication. + * @param bool $redactsastoken If should react SAS token from error messages to avoid accidental leakage. */ public function __construct( /** @var string Azure storage account name */ - public readonly string $account, + public string $account, /** @var string Azure storage container name */ - public readonly string $container, + public string $container, /** @var string SAS token for authentication */ - public readonly string $sastoken + public string $sastoken, + /** @var bool If should redact SAS token from error messages to avoid accidental leakage */ + public bool $redactsastoken = true ) { $this->client = new Client(); } @@ -133,7 +137,8 @@ private function build_blob_properties_url(string $blobkey): string { */ public function get_blob_async(string $key): PromiseInterface { // Enable streaming response, useful for large files e.g. videos. - return $this->client->getAsync($this->build_blob_url($key), ['stream' => true]); + return $this->client->getAsync($this->build_blob_url($key), ['stream' => true]) + ->then(null, $this->clean_exception_sas_if_needed()); } /** @@ -142,7 +147,7 @@ public function get_blob_async(string $key): PromiseInterface { * @return PromiseInterface Promise that resolves a ResponseInterface value where the properties are in the response headers. */ public function get_blob_properties_async(string $key): PromiseInterface { - return $this->client->headAsync($this->build_blob_url($key)); + return $this->client->headAsync($this->build_blob_url($key))->then(null, $this->clean_exception_sas_if_needed()); } /** @@ -151,7 +156,7 @@ public function get_blob_properties_async(string $key): PromiseInterface { * @return PromiseInterface Promise that resolves once the delete request succeeds. */ public function delete_blob_async(string $key): PromiseInterface { - return $this->client->deleteAsync($this->build_blob_url($key)); + return $this->client->deleteAsync($this->build_blob_url($key))->then(null, $this->clean_exception_sas_if_needed()); } /** @@ -194,7 +199,7 @@ public function put_blob_single_async(string $key, StreamInterface $contentstrea ], 'body' => $contentstream, ] - ); + )->then(null, $this->clean_exception_sas_if_needed()); } /** @@ -222,14 +227,14 @@ public function put_blob_multipart_async(string $key, StreamInterface $contentst while (true) { $content = $contentstream->read(self::MULTIPART_BLOCK_SIZE); - // Each block has its own md5 specific to itself. - $blockmd5 = base64_encode(hex2bin(md5($content))); - // Finished reading, nothing more to upload. if (empty($content)) { break; } + // Each block has its own md5 specific to itself. + $blockmd5 = base64_encode(hex2bin(md5($content))); + // The block ID must be the same length regardles of the counter value. // So pad them with zeros. $blockid = base64_encode( @@ -237,7 +242,7 @@ public function put_blob_multipart_async(string $key, StreamInterface $contentst ); $request = new Request('PUT', $this->build_blob_block_url($key, $blockid), ['content-md5' => $blockmd5], $content); - $promises[] = $this->client->sendAsync($request); + $promises[] = $this->client->sendAsync($request)->then(null, $this->clean_exception_sas_if_needed()); $blockids[] = $blockid; }; @@ -253,14 +258,14 @@ public function put_blob_multipart_async(string $key, StreamInterface $contentst $bodymd5 = base64_encode(hex2bin(md5($body))); $request = new Request('PUT', $this->build_blocklist_url($key), ['Content-Type' => 'application/xml', 'content-md5' => $bodymd5], $body); - $this->client->send($request); + $this->client->sendAsync($request)->then(null, $this->clean_exception_sas_if_needed())->wait(); // Now it is combined, set the md5 and content type on the completed blob. $request = new Request('PUT', $this->build_blob_properties_url($key), [ 'x-ms-blob-content-md5' => base64_encode($md5), 'x-ms-blob-content-type' => $contenttype, ]); - $this->client->send($request); + $this->client->sendAsync($request)->then(null, $this->clean_exception_sas_if_needed())->wait(); // Done, resolve the entire promise. $entirepromise->resolve('fulfilled'); @@ -294,4 +299,19 @@ private function make_block_list_xml(array $blockidlist): string { $string .= "\n"; return $string; } + + /** + * Returns a request exception handling function that redacts the SAS token from error messages if needed. + * @return callable + */ + private function clean_exception_sas_if_needed(): callable { + return function(RequestException $ex) { + if ($this->redactsastoken) { + $newmsg = str_replace($this->sastoken, '[SAS TOKEN REDACTED]', $ex->getMessage()); + $exceptiontype = get_class($ex); + throw new $exceptiontype($newmsg, $ex->getRequest(), $ex->getResponse(), $ex, $ex->getHandlerContext()); + } + throw $ex; + }; + } } diff --git a/classes/stream_wrapper.php b/classes/stream_wrapper.php index bd1b830..b565713 100644 --- a/classes/stream_wrapper.php +++ b/classes/stream_wrapper.php @@ -197,12 +197,10 @@ public function stream_seek($offset, $whence = SEEK_SET) { /** * Returns current position of stream. - * @return bool + * @return int */ public function stream_tell() { - return $this->boolCall(function() { - return $this->body->tell(); - }); + return $this->body->tell(); } /** diff --git a/version.php b/version.php index f386c56..0028dda 100644 --- a/version.php +++ b/version.php @@ -27,7 +27,7 @@ $plugin->version = 2024101400; // The current plugin version (Date: YYYYMMDDXX). $plugin->release = 2024101400; // Same as version. -$plugin->requires = 2024042200; // 4.4.0, PHP 8.1.0+ +$plugin->requires = 2023042400; // 4.2.0, PHP 8.0.0+ $plugin->component = "local_azureblobstorage"; $plugin->maturity = MATURITY_ALPHA; -$plugin->supported = [404, 405]; +$plugin->supported = [402, 405];