diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..a3a733140 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ +**PLEASE READ THIS ENTIRE MESSAGE** + +Hello, and thank you for your contribution! Please note that this repository is +a read-only split of `googleapis/google-cloud-php`. As such, we are +unable to accept pull requests to this repository. + +We welcome your pull request and would be happy to consider it for inclusion in +our library if you follow these steps: + +* Clone the parent client library repository: + +```sh +$ git clone git@github.com:googleapis/google-cloud-php.git +``` + +* Move your changes into the correct location in that library. Library code +belongs in `Grafeas/src`, and tests in `Grafeas/tests`. + +* Push the changes in a new branch to a fork, and open a new pull request +[here](https://github.com/googleapis/google-cloud-php). + +Thanks again, and we look forward to seeing your proposed change! + +The Google Cloud PHP team diff --git a/.github/release-please.yml b/.github/release-please.yml deleted file mode 100644 index 461b00a3c..000000000 --- a/.github/release-please.yml +++ /dev/null @@ -1,3 +0,0 @@ -releaseType: simple -handleGHRelease: true -primaryBranch: main \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 2d6d57bc8..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Test Suite -on: - push: - branches: - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - php: [ "8.0", "8.1", "8.2", "8.3", "8.4", "8.5" ] - name: PHP ${{matrix.php }} Unit Test - steps: - - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - - name: Install Dependencies - uses: nick-invision/retry@v1 - with: - timeout_minutes: 10 - max_attempts: 3 - command: composer install - - name: Run Script - run: vendor/bin/phpunit - - style: - runs-on: ubuntu-latest - name: PHP Style Check - steps: - - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.3" - - name: Run Script - run: | - composer global require friendsofphp/php-cs-fixer - ~/.composer/vendor/bin/php-cs-fixer fix --diff --dry-run --allow-risky=yes . - - staticanalysis: - runs-on: ubuntu-latest - name: PHPStan Static Analysis - steps: - - uses: actions/checkout@v2 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - name: Run Script - run: | - composer install - composer global require phpstan/phpstan:~1.10.0 - ~/.composer/vendor/bin/phpstan analyse diff --git a/composer.json b/composer.json index 84360a96f..9333b685a 100644 --- a/composer.json +++ b/composer.json @@ -34,12 +34,20 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.4||^8.0", - "phpspec/prophecy-phpunit": "^2.0", + "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "phpfastcache/phpfastcache": "^9.2", "phpseclib/phpseclib": "~3.0" + }, + "extra": { + "component": { + "id": "jwt", + "target": "googleapis/php-jwt.git", + "path": "Jwt", + "entry": "README.md" + } } } diff --git a/src/JWK.php b/src/JWK.php index 7a1f44883..1870c6fe0 100644 --- a/src/JWK.php +++ b/src/JWK.php @@ -293,7 +293,8 @@ private static function createPemFromModulusAndExponent( * DER-encode the length * * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} + * for more information. * * @param int $length * @return string diff --git a/src/JWT.php b/src/JWT.php index f03972f78..f9a21e207 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -90,7 +90,8 @@ class JWT * @throws UnexpectedValueException Provided JWT was invalid * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' - * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws BeforeValidException Provided JWT is trying to be used before it's + * been created as defined by 'iat' * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim * * @uses jsonDecode @@ -313,7 +314,8 @@ public static function sign( * * @param string $msg The original message (header and body) * @param string $signature The original signature - * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. + * for RS*, must be an instance of OpenSSLAsymmetricKey * @param string $alg The algorithm * * @return bool diff --git a/src/Key.php b/src/Key.php index 694d3b13b..39d7f432e 100644 --- a/src/Key.php +++ b/src/Key.php @@ -17,8 +17,7 @@ public function __construct( #[\SensitiveParameter] private $keyMaterial, private string $algorithm ) { - if ( - !\is_string($keyMaterial) + if (!\is_string($keyMaterial) && !$keyMaterial instanceof OpenSSLAsymmetricKey && !$keyMaterial instanceof OpenSSLCertificate ) { diff --git a/tests/CachedKeySetTest.php b/tests/CachedKeySetTest.php index 39bbc919d..3488cf7cf 100644 --- a/tests/CachedKeySetTest.php +++ b/tests/CachedKeySetTest.php @@ -345,7 +345,7 @@ public function testRateLimit() $this->testJwksUri, $this->getMockHttpClient($this->testJwks1, $shouldBeCalledTimes), $this->getMockHttpFactory($shouldBeCalledTimes), - new TestMemoryCacheItemPool(), + $this->getTestMemoryCacheItemPool(), 10, // expires after seconds true // enable rate limiting ); @@ -367,7 +367,7 @@ public function testRateLimitWithExpiresAfter() $totalHttpTimes = $shouldBeCalledTimes + $afterExpirationTimes; - $cachePool = new TestMemoryCacheItemPool(); + $cachePool = $this->getTestMemoryCacheItemPool(); // Instantiate the cached key set $cachedKeySet = new CachedKeySet( @@ -415,7 +415,7 @@ public function testFullIntegration(string $jwkUri): void self::markTestSkipped('Guzzle 7 only'); } // Create cache and http objects - $cache = new TestMemoryCacheItemPool(); + $cache = $this->getTestMemoryCacheItemPool(); $http = new \GuzzleHttp\Client(); $factory = new \GuzzleHttp\Psr7\HttpFactory(); @@ -502,148 +502,148 @@ private function getMockEmptyCache() return $cache->reveal(); } -} - -/** - * A cache item pool - */ -final class TestMemoryCacheItemPool implements CacheItemPoolInterface -{ - private $items; - private $deferredItems; - - public function getItem($key): CacheItemInterface - { - $item = current($this->getItems([$key])); - $item->expiresAt(null); // mimic symfony cache behavior - - return $item; - } - - public function getItems(array $keys = []): iterable - { - $items = []; - - foreach ($keys as $key) { - $items[$key] = $this->hasItem($key) ? clone $this->items[$key] : new TestMemoryCacheItem($key); - } - - return $items; - } - - public function hasItem($key): bool - { - return isset($this->items[$key]) && $this->items[$key]->isHit(); - } - - public function clear(): bool - { - $this->items = []; - $this->deferredItems = []; - - return true; - } - - public function deleteItem($key): bool - { - return $this->deleteItems([$key]); - } - - public function deleteItems(array $keys): bool - { - foreach ($keys as $key) { - unset($this->items[$key]); - } - - return true; - } - - public function save(CacheItemInterface $item): bool - { - $this->items[$item->getKey()] = $item; - - return true; - } - - public function saveDeferred(CacheItemInterface $item): bool - { - $this->deferredItems[$item->getKey()] = $item; - - return true; - } - - public function commit(): bool - { - foreach ($this->deferredItems as $item) { - $this->save($item); - } - - $this->deferredItems = []; - - return true; - } -} - -/** - * A cache item. - */ -final class TestMemoryCacheItem implements CacheItemInterface -{ - private $key; - private $value; - private $expiration; - private $isHit = false; - public function __construct(string $key) + private function getTestMemoryCacheItemPool() { - $this->key = $key; - } - - public function getKey(): string - { - return $this->key; - } + return new class() implements CacheItemPoolInterface { + private $items; + private $deferredItems; + + public function getItem($key): CacheItemInterface + { + $item = current($this->getItems([$key])); + $item->expiresAt(null); // mimic symfony cache behavior + + return $item; + } + + public function getItems(array $keys = []): iterable + { + $items = []; + + foreach ($keys as $key) { + $items[$key] = $this->hasItem($key) ? + clone $this->items[$key] : + $this->getTestMemoryCacheItem($key); + } - public function get(): mixed - { - return $this->isHit() ? $this->value : null; - } - - public function isHit(): bool - { - if (!$this->isHit) { - return false; - } - - if ($this->expiration === null) { - return true; - } - - return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp(); - } - - public function set(mixed $value): static - { - $this->isHit = true; - $this->value = $value; - - return $this; - } - - public function expiresAt($expiration): static - { - $this->expiration = $expiration; - return $this; - } - - public function expiresAfter($time): static - { - $this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S")); - return $this; - } - - protected function currentTime() - { - return new \DateTime('now', new \DateTimeZone('UTC')); + return $items; + } + + public function hasItem($key): bool + { + return isset($this->items[$key]) && $this->items[$key]->isHit(); + } + + public function clear(): bool + { + $this->items = []; + $this->deferredItems = []; + + return true; + } + + public function deleteItem($key): bool + { + return $this->deleteItems([$key]); + } + + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + unset($this->items[$key]); + } + + return true; + } + + public function save(CacheItemInterface $item): bool + { + $this->items[$item->getKey()] = $item; + + return true; + } + + public function saveDeferred(CacheItemInterface $item): bool + { + $this->deferredItems[$item->getKey()] = $item; + + return true; + } + + public function commit(): bool + { + foreach ($this->deferredItems as $item) { + $this->save($item); + } + + $this->deferredItems = []; + + return true; + } + + private function getTestMemoryCacheItem(string $key) + { + return new class($key) implements CacheItemInterface{ + private $key; + private $value; + private $expiration; + private $isHit = false; + + public function __construct(string $key) + { + $this->key = $key; + } + + public function getKey(): string + { + return $this->key; + } + + public function get(): mixed + { + return $this->isHit() ? $this->value : null; + } + + public function isHit(): bool + { + if (!$this->isHit) { + return false; + } + + if ($this->expiration === null) { + return true; + } + + return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp(); + } + + public function set(mixed $value): static + { + $this->isHit = true; + $this->value = $value; + + return $this; + } + + public function expiresAt($expiration): static + { + $this->expiration = $expiration; + return $this; + } + + public function expiresAfter($time): static + { + $this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S")); + return $this; + } + + protected function currentTime() + { + return new \DateTime('now', new \DateTimeZone('UTC')); + } + }; + } + }; } } diff --git a/tests/JWKTest.php b/tests/JWKTest.php index 3d3caf9a6..c223745ba 100644 --- a/tests/JWKTest.php +++ b/tests/JWKTest.php @@ -108,7 +108,7 @@ public function testParseJwkKeySet($jwkFile, $keyId, $pubkeyFile) $publicKey = openssl_pkey_get_details($keyMaterial)['key']; $this->assertEquals( - file_get_contents(__DIR__ . '/data/' . $pubkeyFile), + str_replace("\r\n", "\n", file_get_contents(__DIR__ . '/data/' . $pubkeyFile)), $publicKey ); } @@ -121,7 +121,7 @@ public function provideParseJwkKeySet() ]; } - public function testParseJwkKey_empty() + public function testParseJwkKeyEmpty() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('JWK must not be empty'); @@ -129,7 +129,7 @@ public function testParseJwkKey_empty() JWK::parseKeySet(['keys' => [[]]]); } - public function testParseJwkKeySet_empty() + public function testParseJwkKeySetEmpty() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('JWK Set did not contain any keys'); @@ -258,7 +258,10 @@ public function testParseKey() $jwk = [ 'alg' => 'RS256', 'kty' => 'RSA', - 'n' => 'hsYvCPtkUV7SIxwkOkJsJfhwV_CMdXU5i0UmY2QEs-Pa7v0-0y-s4EjEDtsQ8Yow6hc670JhkGBcMzhU4DtrqNGROXebyOse5FX0m0UvWo1qXqNTf28uBKB990mY42Icr8sGjtOw8ajyT9kufbmXi3eZKagKpG0TDGK90oBEfoGzCxoFT87F95liNth_GoyU5S8-G3OqIqLlQCwxkI5s-g2qvg_aooALfh1rhvx2wt4EJVMSrdnxtPQSPAtZBiw5SwCnVglc6OnalVNvAB2JArbqC9GAzzz9pApAk28SYg5a4hPiPyqwRv-4X1CXEK8bO5VesIeRX0oDf7UoM-pVAw', + 'n' => 'hsYvCPtkUV7SIxwkOkJsJfhwV_CMdXU5i0UmY2QEs-Pa7v0-0y-s4EjEDtsQ8Yow6hc670JhkGBcMzhU4DtrqNGROXebyO' . + 'se5FX0m0UvWo1qXqNTf28uBKB990mY42Icr8sGjtOw8ajyT9kufbmXi3eZKagKpG0TDGK90oBEfoGzCxoFT87F95liNth_Goy' . + 'U5S8-G3OqIqLlQCwxkI5s-g2qvg_aooALfh1rhvx2wt4EJVMSrdnxtPQSPAtZBiw5SwCnVglc6OnalVNvAB2JArbqC9GAzzz9' . + 'pApAk28SYg5a4hPiPyqwRv-4X1CXEK8bO5VesIeRX0oDf7UoM-pVAw', 'use' => 'sig', 'e' => 'AQAB', 'kid' => '838c06c62046c2d948affe137dd5310129f4d5d1' @@ -272,18 +275,18 @@ public function testParseKey() $keyData = openssl_pkey_get_details($pubKey); $expectedPublicKey = <<assertEquals($expectedPublicKey, $keyData['key']); + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhsYvCPtkUV7SIxwkOkJs + JfhwV/CMdXU5i0UmY2QEs+Pa7v0+0y+s4EjEDtsQ8Yow6hc670JhkGBcMzhU4Dtr + qNGROXebyOse5FX0m0UvWo1qXqNTf28uBKB990mY42Icr8sGjtOw8ajyT9kufbmX + i3eZKagKpG0TDGK90oBEfoGzCxoFT87F95liNth/GoyU5S8+G3OqIqLlQCwxkI5s + +g2qvg/aooALfh1rhvx2wt4EJVMSrdnxtPQSPAtZBiw5SwCnVglc6OnalVNvAB2J + ArbqC9GAzzz9pApAk28SYg5a4hPiPyqwRv+4X1CXEK8bO5VesIeRX0oDf7UoM+pV + AwIDAQAB + -----END PUBLIC KEY----- + + EOF; + + $this->assertEquals(str_replace("\r\n", "\n", $expectedPublicKey), $keyData['key']); } } diff --git a/tests/JWTTest.php b/tests/JWTTest.php index d7fe5309c..58c33bf77 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -325,14 +325,19 @@ public function testInvalidToken() public function testInvalidTokenSegments() { - $dummyToken = 'dGhlIHZhbHVlIGRvZXNuJ3QgbWF0dGVy.T25seSB0aGUgbnVtYmVyIG9mIHNlZ21lbnRz.VGhpcyBzaG91bGQgYmUgYSBzaWduYXR1cmU.YnV0IHRoZXJlIGlzIG1vcmU'; + $dummyToken = 'dGhlIHZhbHVlIGRvZXNuJ3QgbWF0dGVy' . + '.T25seSB0aGUgbnVtYmVyIG9mIHNlZ21lbnRz.' . + 'VGhpcyBzaG91bGQgYmUgYSBzaWduYXR1cmU.' . + 'YnV0IHRoZXJlIGlzIG1vcmU'; $this->expectException(UnexpectedValueException::class); JWT::decode($dummyToken, $this->hmacKey); } public function testInvalidTokenManySegments() { - $dummyToken = 'dGhlIHZhbHVlIGRvZXNuJ3QgbWF0dGVy.T25seSB0aGUgbnVtYmVyIG9mIHNlZ21lbnRz.VGhpcyBzaG91bGQgYmUgYSBzaWduYXR1cmU'; + $dummyToken = 'dGhlIHZhbHVlIGRvZXNuJ3QgbWF0dGVy.' . + 'T25seSB0aGUgbnVtYmVyIG9mIHNlZ21lbnRz.' . + 'VGhpcyBzaG91bGQgYmUgYSBzaWduYXR1cmU'; $dummyToken .= str_repeat('.KzE', 999999); $this->expectException(UnexpectedValueException::class); JWT::decode($dummyToken, $this->hmacKey); @@ -412,7 +417,13 @@ public function testEmptyAlgorithm() public function testAdditionalHeaders() { - $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256', null, ['cty' => 'test-eit;v=1']); + $msg = JWT::encode( + ['message' => 'abc'], + $this->hmacKey->getKeyMaterial(), + 'HS256', + null, + ['cty' => 'test-eit;v=1'] + ); $expected = new stdClass(); $expected->message = 'abc'; $this->assertEquals(JWT::decode($msg, $this->hmacKey), $expected); @@ -426,7 +437,8 @@ public function testInvalidSegmentCount() public function testInvalidSignatureEncoding() { - $msg = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImZvbyJ9.Q4Kee9E8o0Xfo4ADXvYA8t7dN_X_bU9K5w6tXuiSjlUxx'; + $msg = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImZvbyJ9.' . + 'Q4Kee9E8o0Xfo4ADXvYA8t7dN_X_bU9K5w6tXuiSjlUxx'; $this->expectException(UnexpectedValueException::class); JWT::decode($msg, $this->hmacKey); }