⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.1
Server IP:
185.238.29.86
Server:
Linux server2 6.8.12-6-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-6 (2024-12-19T19:05Z) x86_64
Server Software:
nginx/1.18.0
PHP Version:
8.1.31
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
var
/
www
/
muhasebe
/
vendor
/
google
/
cloud-storage
/
src
/
Edit File: SigningHelper.php
<?php /** * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace Google\Cloud\Storage; use Google\Auth\CredentialsLoader; use Google\Auth\SignBlobInterface; use Google\Cloud\Core\ArrayTrait; use Google\Cloud\Core\JsonTrait; use Google\Cloud\Core\Timestamp; use Google\Cloud\Storage\Connection\ConnectionInterface; /** * Provides common methods for signing storage URLs. * * @internal */ class SigningHelper { use ArrayTrait; use JsonTrait; const DEFAULT_URL_SIGNING_VERSION = 'v2'; const DEFAULT_DOWNLOAD_HOST = 'storage.googleapis.com'; const V4_ALGO_NAME = 'GOOG4-RSA-SHA256'; const V4_TIMESTAMP_FORMAT = 'Ymd\THis\Z'; const V4_DATESTAMP_FORMAT = 'Ymd'; /** * Create or fetch a SigningHelper instance. * * @return SigningHelper */ public static function getHelper() { static $helper; if (!$helper) { $helper = new static; } return $helper; } /** * Sign using the version inferred from `$options.version`. * * @param ConnectionInterface $connection A connection to the Cloud Storage * API. This object is created by StorageClient, * and should not be instantiated outside of this client. * @param Timestamp|\DateTimeInterface|int $expires The signed URL * expiration. * @param string $resource The URI to the storage resource, preceded by a * leading slash. * @param int|null $generation The resource generation. * @param array $options Configuration options. See * {@see StorageObject::signedUrl()} for * details. * @return string * @throws \InvalidArgumentException * @throws \RuntimeException If required data could not be gathered from * credentials. * @throws \RuntimeException If OpenSSL signing is required by user input * and OpenSSL is not available. */ public function sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options) { $version = $options['version'] ?? self::DEFAULT_URL_SIGNING_VERSION; unset($options['version']); switch (strtolower($version)) { case 'v2': $method = 'v2Sign'; break; case 'v4': $method = 'v4Sign'; break; default: throw new \InvalidArgumentException('Invalid signing version.'); } return call_user_func_array([$this, $method], [ $connection, $expires, $resource, $generation, $options ]); } /** * Sign a URL using Google Signed URLs v2. * * This method will be deprecated in the future. * * @param ConnectionInterface $connection A connection to the Cloud Storage * API. This object is created by StorageClient, * and should not be instantiated outside of this client. * @param Timestamp|\DateTimeInterface|int $expires The signed URL * expiration. * @param string $resource The URI to the storage resource, preceded by a * leading slash. * @param int|null $generation The resource generation. * @param array $options Configuration options. See * {@see StorageObject::signedUrl()} for * details. * @return string * @throws \InvalidArgumentException * @throws \RuntimeException If required data could not be gathered from * credentials. * @throws \RuntimeException If OpenSSL signing is required by user input * and OpenSSL is not available. */ public function v2Sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options) { list($credentials, $options) = $this->getSigningCredentials($connection, $options); $expires = $this->normalizeExpiration($expires); list($resource, $bucket) = $this->normalizeResource($resource); $options = $this->normalizeOptions($options); $headers = $this->normalizeHeaders($options['headers']); if ($options['virtualHostedStyle']) { $options['bucketBoundHostname'] = sprintf( '%s.storage.googleapis.com', $bucket ); } // Make sure disallowed headers are not included. $illegalHeaders = [ 'x-goog-encryption-key', 'x-goog-encryption-key-sha256' ]; if ($illegal = array_intersect_key(array_flip($illegalHeaders), $headers)) { throw new \InvalidArgumentException(sprintf( '%s %s not allowed in Signed URL headers.', implode(' and ', array_keys($illegal)), count($illegal) === 1 ? 'is' : 'are' )); } // Sort headers by name. ksort($headers); $toSign = [ $options['method'], $options['contentMd5'], $options['contentType'], $expires, ]; $signedHeaders = []; foreach ($headers as $name => $value) { $signedHeaders[] = $name .':'. $value; } // Push the headers onto the end of the signing string. if ($signedHeaders) { $toSign = array_merge($toSign, $signedHeaders); } $toSign[] = $resource; $stringToSign = $this->createV2CanonicalRequest($toSign); $signature = $credentials->signBlob($stringToSign, [ 'forceOpenssl' => $options['forceOpenssl'] ]); // Start with user-provided query params and add required parameters. $params = $options['queryParams']; $params['GoogleAccessId'] = $credentials->getClientName(); $params['Expires'] = $expires; $params['Signature'] = $signature; // urlencode parameter values foreach ($params as &$value) { $value = rawurlencode($value); } $params = $this->addCommonParams($generation, $params, $options); $queryString = $this->buildQueryString($params); $resource = $this->normalizeUriPath($options['bucketBoundHostname'], $resource); return 'https://' . $options['bucketBoundHostname'] . $resource . '?' . $queryString; } /** * Sign a storage URL using Google Signed URLs v4. * * @param ConnectionInterface $connection A connection to the Cloud Storage * API. This object is created by StorageClient, * and should not be instantiated outside of this client. * @param Timestamp|\DateTimeInterface|int $expires The signed URL * expiration. * @param string $resource The URI to the storage resource, preceded by a * leading slash. * @param int|null $generation The resource generation. * @param array $options Configuration options. See * {@see StorageObject::signedUrl()} for * details. * @return string * @throws \InvalidArgumentException * @throws \RuntimeException If required data could not be gathered from * credentials. * @throws \RuntimeException If OpenSSL signing is required by user input * and OpenSSL is not available. */ public function v4Sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options) { list($credentials, $options) = $this->getSigningCredentials($connection, $options); $expires = $this->normalizeExpiration($expires); list($resource, $bucket) = $this->normalizeResource($resource); $options = $this->normalizeOptions($options); $time = $options['timestamp']; $requestTimestamp = $time->format(self::V4_TIMESTAMP_FORMAT); $requestDatestamp = $time->format(self::V4_DATESTAMP_FORMAT); $timeSeconds = $time->format('U'); $expireLimit = $timeSeconds + 604800; if ($expires > $expireLimit) { throw new \InvalidArgumentException( 'V4 Signed URLs may not have an expiration greater than seven days in the future.' ); } $clientEmail = $credentials->getClientName(); $credentialScope = sprintf('%s/auto/storage/goog4_request', $requestDatestamp); $credential = sprintf('%s/%s', $clientEmail, $credentialScope); if ($options['virtualHostedStyle']) { $options['bucketBoundHostname'] = sprintf( '%s.storage.googleapis.com', $bucket ); } // Add headers and query params based on provided options. $params = $options['queryParams']; $headers = $options['headers'] + [ 'host' => $options['bucketBoundHostname'] ]; if ($options['contentType']) { $headers['content-type'] = $options['contentType']; } if ($options['contentMd5']) { $headers['content-md5'] = $options['contentMd5']; } $params = $this->addCommonParams($generation, $params, $options); $headers = $this->normalizeHeaders($headers); // sort headers by name ksort($headers, SORT_NATURAL | SORT_FLAG_CASE); // Canonical headers are a list, newline separated, of keys and values, // comma separated. // Signed headers are a list of keys, separated by a semicolon. $canonicalHeaders = []; $signedHeaders = []; foreach ($headers as $key => $val) { $canonicalHeaders[] = sprintf('%s:%s', $key, $val); $signedHeaders[] = $key; } $canonicalHeaders = implode("\n", $canonicalHeaders) . "\n"; $signedHeaders = implode(';', $signedHeaders); // Add required query parameters. $params = [ 'X-Goog-Algorithm' => self::V4_ALGO_NAME, 'X-Goog-Credential' => $credential, 'X-Goog-Date' => $requestTimestamp, 'X-Goog-Expires' => $expires - $timeSeconds, 'X-Goog-SignedHeaders' => $signedHeaders, ] + $params; $paramNames = []; foreach ($params as $key => $val) { $paramNames[] = $key; } sort($paramNames, SORT_REGULAR); $sortedParams = []; foreach ($paramNames as $name) { $sortedParams[rawurlencode($name)] = rawurlencode($params[$name]); } $canonicalQueryString = $this->buildQueryString($sortedParams); $canonicalResource = $this->normalizeCanonicalRequestResource( $resource, $options['bucketBoundHostname'], $options['virtualHostedStyle'] ); $canonicalRequest = [ $options['method'], $canonicalResource, $canonicalQueryString, $canonicalHeaders, $signedHeaders, $this->getPayloadHash($headers) ]; $requestHash = $this->createV4CanonicalRequest($canonicalRequest); // Construct the string to sign. $stringToSign = implode("\n", [ self::V4_ALGO_NAME, $requestTimestamp, $credentialScope, $requestHash ]); $signature = bin2hex(base64_decode($credentials->signBlob($stringToSign, [ 'forceOpenssl' => $options['forceOpenssl'] ]))); // Construct the modified resource name. If a custom hostname is provided, // this will remove the bucket name from the resource. $resource = $this->normalizeUriPath($options['bucketBoundHostname'], $resource); $scheme = $this->chooseScheme( $options['scheme'], $options['bucketBoundHostname'], $options['virtualHostedStyle'] ); return sprintf( '%s://%s%s?%s&X-Goog-Signature=%s', $scheme, $options['bucketBoundHostname'], $resource, $canonicalQueryString, $signature ); } /** * Create an HTTP POST policy using v4 signing. * * @param ConnectionInterface $connection A Connection to Google Cloud Storage. * This object is created by StorageClient, * and should not be instantiated outside of this client. * @param Timestamp|\DateTimeInterface|int $expires The signed URL * expiration. * @param string $resource The URI to the storage resource, preceded by a * leading slash. * @param array $options Configuration options. See * {@see Bucket::generateSignedPostPolicyV4()} for details. * @return array An associative array, containing (string) `uri` and * (array) `fields` keys. */ public function v4PostPolicy( ConnectionInterface $connection, $expires, $resource, array $options = [] ) { list($credentials, $options) = $this->getSigningCredentials($connection, $options); $expires = $this->normalizeExpiration($expires); list($resource, $bucket, $object) = $this->normalizeResource($resource, false); $object = trim($object, '/'); $options = $this->normalizeOptions($options) + [ 'fields' => [], 'conditions' => [], 'successActionRedirect' => null, 'successActionStatus' => null ]; $time = $options['timestamp']; $requestTimestamp = $time->format(self::V4_TIMESTAMP_FORMAT); $requestDatestamp = $time->format(self::V4_DATESTAMP_FORMAT); $expiration = \DateTimeImmutable::createFromFormat('U', (string) $expires); $expirationTimestamp = str_replace( '+00:00', 'Z', $expiration->format(\DateTime::RFC3339) ); $clientEmail = $credentials->getClientName(); $credentialScope = sprintf('%s/auto/storage/goog4_request', $requestDatestamp); $credential = sprintf('%s/%s', $clientEmail, $credentialScope); if ($options['virtualHostedStyle']) { $options['bucketBoundHostname'] = sprintf( '%s.storage.googleapis.com', $bucket ); } $fields = array_merge($options['fields'], [ 'key' => $object, 'x-goog-algorithm' => self::V4_ALGO_NAME, 'x-goog-credential' => $credential, 'x-goog-date' => $requestTimestamp ]); $conditions = $options['conditions']; foreach ($options['fields'] as $key => $value) { $conditions[] = [$key => $value]; } foreach ($conditions as $key => $value) { $key = $key; $value = $value; $conditions[$key] = $value; } $conditions = array_merge($conditions, [ ['bucket' => $bucket], ['key' => $object], ['x-goog-date' => $requestTimestamp], ['x-goog-credential' => $credential], ['x-goog-algorithm' => self::V4_ALGO_NAME], ]); $policy = [ 'conditions' => $conditions, 'expiration' => $expirationTimestamp ]; $json = str_replace('\\\u', '\\u', json_encode($policy, JSON_UNESCAPED_SLASHES)); $stringToSign = base64_encode($json); $signature = bin2hex(base64_decode($credentials->signBlob($stringToSign, [ 'forceOpenssl' => $options['forceOpenssl'] ]))); $fields['x-goog-signature'] = $signature; $fields['policy'] = $stringToSign; // Construct the modified resource name. If a custom hostname is provided, // this will remove the bucket name from the resource. $resource = $this->normalizeUriPath($options['bucketBoundHostname'], '/' . $bucket, true); $scheme = $this->chooseScheme( $options['scheme'], $options['bucketBoundHostname'], $options['virtualHostedStyle'] ); return [ 'url' => sprintf( '%s://%s%s', $scheme, $options['bucketBoundHostname'], $resource ), 'fields' => $fields ]; } /** * Creates a canonical request hash for a V4 Signed URL. * * NOTE: While in most cases `PHP_EOL` is preferable to a system-specific * character, in this case `\n` is required. * * @param array $canonicalRequest The canonical request, with each element * representing a line in the request. * @return string */ private function createV4CanonicalRequest(array $canonicalRequest) { $canonicalRequestString = implode("\n", $canonicalRequest); return bin2hex(hash('sha256', $canonicalRequestString, true)); } /** * Creates a canonical request for a V2 Signed URL. * * NOTE: While in most cases `PHP_EOL` is preferable to a system-specific * character, in this case `\n` is required. * * @param array $canonicalRequest The canonical request, with each element * representing a line in the request. * @return string */ private function createV2CanonicalRequest(array $canonicalRequest) { return implode("\n", $canonicalRequest); } /** * Choose the correct URL scheme. * * @param string $scheme The scheme provided by the user or defaults. * @param string $bucketBoundHostname The bucketBoundHostname provided by the user or defaults. * @param bool $virtualHostedStyle Whether virtual host style is enabled. * @return string */ private function chooseScheme($scheme, $bucketBoundHostname, $virtualHostedStyle = false) { // bucketBoundHostname not used -- always https. if ($bucketBoundHostname === self::DEFAULT_DOWNLOAD_HOST) { return 'https'; } // virtualHostedStyle enabled -- always https. if ($virtualHostedStyle) { return 'https'; } // not virtual hosted style, and custom hostname -- use default (http) or user choice. return $scheme; } /** * If `X-Goog-Content-SHA256` header is provided, use that as the payload. * Otherwise, `UNSIGNED-PAYLOAD`. * * @param array $headers * @return string */ private function getPayloadHash(array $headers) { if (!isset($headers['x-goog-content-sha256'])) { return 'UNSIGNED-PAYLOAD'; } return $headers['x-goog-content-sha256']; } /** * Normalizes and validates an expiration. * * @param Timestamp|\DateTimeInterface|int $expires The expiration * @return int * @throws \InvalidArgumentException If an invalid value is given. */ private function normalizeExpiration($expires) { if ($expires instanceof Timestamp) { $seconds = $expires->get()->format('U'); } elseif ($expires instanceof \DateTimeInterface) { $seconds = $expires->format('U'); } elseif (is_numeric($expires)) { $seconds = (int) $expires; } else { throw new \InvalidArgumentException('Invalid expiration.'); } return $seconds; } /** * Normalizes and encodes the resource identifier. * * @param string $resource The resource identifier. In form * `[/]$bucket/$object`. * @return array A list, where index 0 is the resource path, with pieces * encoded and prefixed with a forward slash, index 1 is the bucket * name, and index 2 is the object name, relative to the bucket. */ private function normalizeResource($resource, $urlencode = true) { $pieces = explode('/', trim($resource, '/')); if ($urlencode) { array_walk($pieces, function (&$piece) { $piece = rawurlencode($piece); }); } $bucket = $pieces[0]; $relative = $pieces; array_shift($relative); return [ '/' . implode('/', $pieces), $bucket, '/' . implode('/', $relative), ]; } /** * Fixes the user input options, filters and validates data. * * @param array $options Signed URL configuration options. * @return array * @throws \InvalidArgumentException */ private function normalizeOptions(array $options) { $options += [ 'allowPost' => false, 'cname' => null, //@deprecated 'bucketBoundHostname' => self::DEFAULT_DOWNLOAD_HOST, 'contentMd5' => null, 'contentType' => null, 'forceOpenssl' => false, 'headers' => [], 'keyFile' => null, 'keyFilePath' => null, 'method' => 'GET', 'queryParams' => [], 'responseDisposition' => null, 'responseType' => null, 'saveAsName' => null, // note that in almost every case this default will be overridden. 'scheme' => 'http', 'timestamp' => null, 'virtualHostedStyle' => false, ]; $allowedMethods = ['GET', 'PUT', 'POST', 'DELETE']; $options['method'] = strtoupper($options['method']); if (!in_array($options['method'], $allowedMethods)) { throw new \InvalidArgumentException('$options.method must be one of `GET`, `PUT` or `DELETE`.'); } if ($options['method'] === 'POST' && !$options['allowPost']) { throw new \InvalidArgumentException( 'Invalid method. To create an upload URI, use StorageObject::signedUploadUrl().' ); } // Rewrite deprecated `cname` to new `bucketBoundHostname`. if ($options['cname'] && $options['bucketBoundHostname'] === self::DEFAULT_DOWNLOAD_HOST) { $options['bucketBoundHostname'] = $options['cname']; } // strip protocol from hostname. $hostnameParts = explode('//', $options['bucketBoundHostname']); if (count($hostnameParts) > 1) { $options['bucketBoundHostname'] = $hostnameParts[1]; } $options['bucketBoundHostname'] = trim($options['bucketBoundHostname'], '/'); // If a timestamp is provided, use it in place of `now` for v4 URLs only.. // This option exists for testing purposes, and should not generally be provided by users. if ($options['timestamp']) { if (!($options['timestamp'] instanceof \DateTimeInterface)) { if (!is_string($options['timestamp'])) { throw new \InvalidArgumentException( 'User-provided timestamps must be a string or instance of `\DateTimeInterface`.' ); } $options['timestamp'] = \DateTimeImmutable::createFromFormat( \DateTime::RFC3339, $options['timestamp'], new \DateTimeZone('UTC') ); if (!$options['timestamp']) { throw new \InvalidArgumentException( 'Given timestamp string is in an invalid format. Provide timestamp formatted as follows: `' . \DateTime::RFC3339 . '`. Note that timestamps MUST be in UTC.' ); } } } else { $options['timestamp'] = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); } unset( $options['cname'], $options['allowPost'] ); return $options; } /** * Cleans and normalizes header values. * * Arrays of values are collapsed into a comma-separated list, trailing and * leading spaces are removed, newlines are replaced by empty strings, and * multiple whitespace chars are replaced by a single space. * * @param array $headers Input headers * @return array */ private function normalizeHeaders(array $headers) { $out = []; foreach ($headers as $name => $value) { $name = strtolower(trim($name)); // collapse arrays of values into a comma-separated list. if (!is_array($value)) { $value = [$value]; } foreach ($value as &$headerValue) { // strip trailing and leading spaces. $headerValue = trim($headerValue); // replace newlines with empty strings. $headerValue = str_replace(PHP_EOL, '', $headerValue); // collapse multiple whitespace chars to a single space. $headerValue = preg_replace('/[\s]+/', ' ', $headerValue); } $out[$name] = implode(', ', $value); } return $out; } /** * Returns a resource formatted for use in a URI. * * If the bucketBoundHostname is other than the default, will omit the bucket name. * * @param string $bucketBoundHostname The bucketBoundHostname provided by the user, or the default * value. * @param string $resource The GCS resource path (i.e. /bucket/object). * @return string */ private function normalizeUriPath($bucketBoundHostname, $resource, $withTrailingSlash = false) { if ($bucketBoundHostname !== self::DEFAULT_DOWNLOAD_HOST) { $resourceParts = explode('/', trim($resource, '/')); array_shift($resourceParts); // Resource is a Bucket. if (empty($resourceParts)) { $resource = '/'; } else { $resource = '/' . implode('/', $resourceParts); } } $resource = rtrim($resource, '/'); return $withTrailingSlash ? $resource . '/' : $resource; } /** * Normalize the resource provided to the canonical request string. * * @param string $resource * @param string $bucketBoundHostname * @param boolean $virtualHostedStyle * @return string */ private function normalizeCanonicalRequestResource($resource, $bucketBoundHostname, $virtualHostedStyle = false) { if ($bucketBoundHostname === self::DEFAULT_DOWNLOAD_HOST && !$virtualHostedStyle) { return $resource; } $pieces = explode('/', trim($resource, '/')); array_shift($pieces); return '/' . implode('/', $pieces); } /** * Get the credentials for use with signing. * * @param ConnectionInterface $connection A Storage connection object. * This object is created by StorageClient, * and should not be instantiated outside of this client. * @param array $options Configuration options. * @return array A list containing a credentials object at index 0 and the * modified options at index 1. * @throws \RuntimeException If the credentials type is not valid for signing. * @throws \InvalidArgumentException If a keyfile is given and is not valid. */ private function getSigningCredentials(ConnectionInterface $connection, array $options) { $keyFilePath = $options['keyFilePath'] ?? null; if ($keyFilePath) { if (!file_exists($keyFilePath)) { throw new \InvalidArgumentException(sprintf( 'Keyfile path %s does not exist.', $keyFilePath )); } $options['keyFile'] = self::jsonDecode(file_get_contents($keyFilePath), true); } $rw = $connection->requestWrapper(); $keyFile = $options['keyFile'] ?? null; if ($keyFile) { $scopes = $options['scopes'] ?? $rw->scopes(); $credentials = CredentialsLoader::makeCredentials($scopes, $keyFile); } else { $credentials = $rw->getCredentialsFetcher(); } //@codeCoverageIgnoreStart if (!($credentials instanceof SignBlobInterface)) { throw new \RuntimeException(sprintf( 'Credentials object is of type `%s` and is not valid for signing.', get_class($credentials) )); } //@codeCoverageIgnoreEnd unset( $options['keyFilePath'], $options['keyFile'], $options['scopes'] ); return [$credentials, $options]; } /** * Add parameters common to all signed URL versions. * * @param int|null $generation * @param array $params * @param array $options * @return array */ private function addCommonParams($generation, array $params, array $options) { if ($options['responseType']) { $params['response-content-type'] = $options['responseType']; } if ($options['responseDisposition']) { $params['response-content-disposition'] = $options['responseDisposition']; } elseif ($options['saveAsName']) { $params['response-content-disposition'] = 'attachment; filename=' . '"' . $options['saveAsName'] . '"'; } if ($generation) { $params['generation'] = $generation; } return $params; } /** * Create a query string from an array. * * Note that this method does NOT urlencode keys or values. * * @param array $input * @return string */ private function buildQueryString(array $input) { $q = []; foreach ($input as $key => $val) { $q[] = $key . '=' . $val; } return implode('&', $q); } }
Simpan