From 1abfb3528e8dad7f340ebe32e211a64bbec9b7ed Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Mon, 12 May 2025 17:38:28 +0530 Subject: [PATCH 01/13] Create PR --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d7cd0715..1c9bddea 100644 --- a/README.md +++ b/README.md @@ -332,3 +332,4 @@ Professional support, consulting as well as software development services are av https://www.cebe.cc/en/contact Development of this library is sponsored by [cebe.:cloud: "Your Professional Deployment Platform"](https://cebe.cloud). + From 02b980ec5f13f5dc6fd8208c69288f0cf45cd786 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 May 2025 10:31:46 +0530 Subject: [PATCH 02/13] Fix issue --- README.md | 1 - src/spec/SecurityRequirements.php | 15 +++++++++++++-- tests/data/issue/238/spec.yml | 28 ++++++++++++++++++++++++++++ tests/issues/238/Issue238Test.php | 19 +++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 tests/data/issue/238/spec.yml create mode 100644 tests/issues/238/Issue238Test.php diff --git a/README.md b/README.md index 1c9bddea..d7cd0715 100644 --- a/README.md +++ b/README.md @@ -332,4 +332,3 @@ Professional support, consulting as well as software development services are av https://www.cebe.cc/en/contact Development of this library is sponsored by [cebe.:cloud: "Your Professional Deployment Platform"](https://cebe.cloud). - diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php index cbda1ba7..76a16c1e 100644 --- a/src/spec/SecurityRequirements.php +++ b/src/spec/SecurityRequirements.php @@ -25,11 +25,17 @@ public function __construct(array $data) foreach ($data as $index => $value) { if (is_numeric($index)) { // read - $this->_securityRequirements[array_keys($value)[0]] = new SecurityRequirement(array_values($value)[0]); + if ($value === []) { # empty Security Requirement Object (`{}`) = anonymous access https://github.com/cebe/php-openapi/issues/238 + $this->_securityRequirements[$index] = new SecurityRequirement($value); +// break; + } else { + $this->_securityRequirements[array_keys($value)[0]] = new SecurityRequirement(array_values($value)[0]); + } } else { // write $this->_securityRequirements[$index] = $value; } } + if ($data === []) { $this->_securityRequirements = []; } @@ -61,7 +67,12 @@ public function getSerializableData() $data = []; foreach ($this->_securityRequirements ?? [] as $name => $securityRequirement) { /** @var SecurityRequirement $securityRequirement */ - $data[] = [$name => $securityRequirement->getSerializableData()]; + + if (is_numeric($name)) { + $data[$name] = $securityRequirement->getSerializableData(); + } else { + $data[] = [$name => $securityRequirement->getSerializableData()]; + } } return $data; } diff --git a/tests/data/issue/238/spec.yml b/tests/data/issue/238/spec.yml new file mode 100644 index 00000000..df9c1830 --- /dev/null +++ b/tests/data/issue/238/spec.yml @@ -0,0 +1,28 @@ +openapi: 3.0.0 +info: + title: Secured API + version: 1.0.0 +paths: + /global-secured: + get: + responses: + '200': + description: OK + /path-secured: + get: + security: + - {} + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + BearerAuth: + type: http + scheme: bearer +security: + - ApiKeyAuth: [] diff --git a/tests/issues/238/Issue238Test.php b/tests/issues/238/Issue238Test.php new file mode 100644 index 00000000..9e4643af --- /dev/null +++ b/tests/issues/238/Issue238Test.php @@ -0,0 +1,19 @@ +assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); + $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $openapi->paths->getPath('/path-secured')->getOperations()['get']->security); + $this->assertSame($openapi->paths->getPath('/path-secured')->getOperations()['get']->security->getSerializableData(), [[]]); + +// return; # TODO +// $openapi = Reader::readFromJsonFile(__DIR__.'/data/issue/238/spec.json'); +// $this->assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); + } +} From 864fb056c88fb9a70d9d70c8450cd26781ac87b8 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 May 2025 17:51:31 +0530 Subject: [PATCH 03/13] Fix issue 2 --- src/spec/SecurityRequirements.php | 5 ++-- tests/issues/238/Issue238Test.php | 44 +++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php index 76a16c1e..1f7dc1c5 100644 --- a/src/spec/SecurityRequirements.php +++ b/src/spec/SecurityRequirements.php @@ -26,8 +26,7 @@ public function __construct(array $data) foreach ($data as $index => $value) { if (is_numeric($index)) { // read if ($value === []) { # empty Security Requirement Object (`{}`) = anonymous access https://github.com/cebe/php-openapi/issues/238 - $this->_securityRequirements[$index] = new SecurityRequirement($value); -// break; + $this->_securityRequirements[$index] = $value; } else { $this->_securityRequirements[array_keys($value)[0]] = new SecurityRequirement(array_values($value)[0]); } @@ -69,7 +68,7 @@ public function getSerializableData() /** @var SecurityRequirement $securityRequirement */ if (is_numeric($name)) { - $data[$name] = $securityRequirement->getSerializableData(); + $data[$name] = (object) $securityRequirement; # case https://github.com/cebe/php-openapi/issues/238 } else { $data[] = [$name => $securityRequirement->getSerializableData()]; } diff --git a/tests/issues/238/Issue238Test.php b/tests/issues/238/Issue238Test.php index 9e4643af..12e583bf 100644 --- a/tests/issues/238/Issue238Test.php +++ b/tests/issues/238/Issue238Test.php @@ -1,19 +1,59 @@ assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $openapi->paths->getPath('/path-secured')->getOperations()['get']->security); - $this->assertSame($openapi->paths->getPath('/path-secured')->getOperations()['get']->security->getSerializableData(), [[]]); + $this->assertSame(json_decode(json_encode($openapi->paths->getPath('/path-secured')->getOperations()['get']->security->getSerializableData()), true), [[]]); // return; # TODO // $openapi = Reader::readFromJsonFile(__DIR__.'/data/issue/238/spec.json'); // $this->assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); } + + public function test238AddSupportForEmptySecurityRequirementObjectInSecurityRequirementWrite() + { + $openapi = $this->createOpenAPI([ + 'security' => new SecurityRequirements([ + [] + ]), + ]); + + $yaml = Writer::writeToYaml($openapi); + + $this->assertEquals(preg_replace('~\R~', "\n", << '3.0.0', + 'info' => [ + 'title' => 'Test API', + 'version' => '1.0.0', + ], + 'paths' => [], + ], $merge)); + } } From d0c82270f0e0478e070b7e08f4995e9679f0e9f4 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 May 2025 18:06:02 +0530 Subject: [PATCH 04/13] Add tests for JSON read + write --- tests/issues/238/Issue238Test.php | 84 +++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/tests/issues/238/Issue238Test.php b/tests/issues/238/Issue238Test.php index 12e583bf..530bd481 100644 --- a/tests/issues/238/Issue238Test.php +++ b/tests/issues/238/Issue238Test.php @@ -15,9 +15,62 @@ public function test238AddSupportForEmptySecurityRequirementObjectInSecurityRequ $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $openapi->paths->getPath('/path-secured')->getOperations()['get']->security); $this->assertSame(json_decode(json_encode($openapi->paths->getPath('/path-secured')->getOperations()['get']->security->getSerializableData()), true), [[]]); -// return; # TODO -// $openapi = Reader::readFromJsonFile(__DIR__.'/data/issue/238/spec.json'); -// $this->assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); + $openapiJson = Reader::readFromJson(<<assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapiJson); + $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $openapiJson->paths->getPath('/path-secured')->getOperations()['get']->security); + $this->assertSame(json_decode(json_encode($openapiJson->paths->getPath('/path-secured')->getOperations()['get']->security->getSerializableData()), true), [[]]); } public function test238AddSupportForEmptySecurityRequirementObjectInSecurityRequirementWrite() @@ -43,6 +96,31 @@ public function test238AddSupportForEmptySecurityRequirementObjectInSecurityRequ ), $yaml ); + + $openapiJson = $this->createOpenAPI([ + 'security' => new SecurityRequirements([ + [] + ]), + ]); + + $json = Writer::writeToJson($openapiJson); + + $this->assertEquals(preg_replace('~\R~', "\n", << Date: Thu, 15 May 2025 17:54:07 +0530 Subject: [PATCH 05/13] Fix https://github.com/cebe/php-openapi/issues/242 --- src/spec/SecurityRequirements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php index 1f7dc1c5..c3fc79fc 100644 --- a/src/spec/SecurityRequirements.php +++ b/src/spec/SecurityRequirements.php @@ -70,7 +70,7 @@ public function getSerializableData() if (is_numeric($name)) { $data[$name] = (object) $securityRequirement; # case https://github.com/cebe/php-openapi/issues/238 } else { - $data[] = [$name => $securityRequirement->getSerializableData()]; + $data[] = (object) [$name => $securityRequirement->getSerializableData()]; } } return $data; From 6d5bde9e648feae3b0e02de0fa9b0ab06f4413b0 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 15 May 2025 18:28:44 +0530 Subject: [PATCH 06/13] Add test --- tests/data/issue/242/spec.json | 41 +++++++++++++++++++++++++++++++ tests/issues/242/Issue242Test.php | 29 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/data/issue/242/spec.json create mode 100644 tests/issues/242/Issue242Test.php diff --git a/tests/data/issue/242/spec.json b/tests/data/issue/242/spec.json new file mode 100644 index 00000000..5321bb94 --- /dev/null +++ b/tests/data/issue/242/spec.json @@ -0,0 +1,41 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "My API", + "version": "1, 2" + }, + "paths": { + "/v1/users/profile": { + "get": { + "operationId": "V1GetUserProfile", + "summary": "Returns the user profile", + "responses": { + "200": { + "description": "dummy" + } + }, + "security": [ + { + "test_test": ["test:scope:foo"] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "test_test": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://example.com/openid-connect/auth", + "tokenUrl": "https://example.com/openid-connect/token", + "scopes": { + "test:scope:foo": "test_scope" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/issues/242/Issue242Test.php b/tests/issues/242/Issue242Test.php new file mode 100644 index 00000000..1bb1c1f7 --- /dev/null +++ b/tests/issues/242/Issue242Test.php @@ -0,0 +1,29 @@ +assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); + + $file = dirname(__DIR__, 2) . '/data/issue/242/spec.json'; + $dirSep = DIRECTORY_SEPARATOR; + $cmd = 'php ' . dirname(__DIR__, 3) . "{$dirSep}bin{$dirSep}php-openapi validate " . $file . " 2>&1"; + exec($cmd, $op, $ec); + $this->assertSame($this->removeCliFormatting($op[0]), 'The supplied API Description validates against the OpenAPI v3.0 schema.'); + $this->assertSame(0, $ec); + } + + private function removeCliFormatting($string) + { + // Regex to remove ANSI escape codes + return preg_replace('/\e\[[0-9;]*m/', '', $string); + } +} \ No newline at end of file From 7b5f3dabd8a93bfcedca8bee7d63765a01bb7b60 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 May 2025 12:59:25 +0530 Subject: [PATCH 07/13] Fix https://github.com/cebe/php-openapi/issues/242#issuecomment-2886431173 --- src/spec/SecurityRequirements.php | 57 +++++++++++++++++++++++-------- tests/data/issue/242/spec2.yml | 30 ++++++++++++++++ tests/issues/242/Issue242Test.php | 32 ++++++++++++----- 3 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 tests/data/issue/242/spec2.yml diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php index c3fc79fc..055d5550 100644 --- a/src/spec/SecurityRequirements.php +++ b/src/spec/SecurityRequirements.php @@ -24,14 +24,17 @@ public function __construct(array $data) parent::__construct($data); foreach ($data as $index => $value) { - if (is_numeric($index)) { // read - if ($value === []) { # empty Security Requirement Object (`{}`) = anonymous access https://github.com/cebe/php-openapi/issues/238 - $this->_securityRequirements[$index] = $value; - } else { - $this->_securityRequirements[array_keys($value)[0]] = new SecurityRequirement(array_values($value)[0]); + if (is_numeric($index) && $value === []) { # empty Security Requirement Object (`{}`) = anonymous access + $this->_securityRequirements[$index] = []; + continue; + } + + if (is_string($index)) { + $this->_securityRequirements[] = [$index => $value instanceof SecurityRequirement ? $value : new SecurityRequirement($value)]; + } elseif (is_numeric($index)) { + foreach ($value as $innerIndex => $subValue) { + $this->_securityRequirements[$index][$innerIndex] = $subValue instanceof SecurityRequirement ? $subValue : new SecurityRequirement($subValue); } - } else { // write - $this->_securityRequirements[$index] = $value; } } @@ -64,13 +67,22 @@ protected function performValidation() public function getSerializableData() { $data = []; - foreach ($this->_securityRequirements ?? [] as $name => $securityRequirement) { - /** @var SecurityRequirement $securityRequirement */ - if (is_numeric($name)) { - $data[$name] = (object) $securityRequirement; # case https://github.com/cebe/php-openapi/issues/238 - } else { - $data[] = (object) [$name => $securityRequirement->getSerializableData()]; + foreach ($this->_securityRequirements ?? [] as $outerIndex => $content) { + + if (is_string($outerIndex)) { + $data[] = [$outerIndex => $content->getSerializableData()]; + } elseif (is_numeric($outerIndex)) { + if ($content === []) { + $data[$outerIndex] = (object)$content; + continue; + } + $innerResult = []; + foreach ($content as $innerIndex => $innerContent) { + $result = is_object($innerContent) && method_exists($innerContent, 'getSerializableData') ? $innerContent->getSerializableData() : $innerContent; + $innerResult[$innerIndex] = $result; + } + $data[$outerIndex] = (object)$innerResult; } } return $data; @@ -78,11 +90,28 @@ public function getSerializableData() public function getRequirement(string $name) { - return $this->_securityRequirements[$name] ?? null; + return static::searchKey($this->_securityRequirements, $name); } public function getRequirements() { return $this->_securityRequirements; } + + private static function searchKey(array $array, string $searchKey) + { + foreach ($array as $key => $value) { + if ($key === $searchKey) { + return $value; + } + if (is_array($value)) { + $mt = __METHOD__; + $result = $mt($value, $searchKey); + if ($result !== null) { + return $result; // key found in deeply nested/associative array + } + } + } + return null; // key not found + } } diff --git a/tests/data/issue/242/spec2.yml b/tests/data/issue/242/spec2.yml new file mode 100644 index 00000000..27cb58e6 --- /dev/null +++ b/tests/data/issue/242/spec2.yml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: API Documentation + description: All API endpoints are presented here. + version: 1.0.0 +servers: + - url: http://127.0.0.1:8080/ + +paths: + + /endpoint: + get: + responses: + '200': + description: OK + security: + - apiKey: [] + bearerAuth: [] +components: + securitySchemes: + apiKey: + type: apiKey + in: header + name: X-APi-Key + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: JWT Authorization header using the Bearer scheme. + diff --git a/tests/issues/242/Issue242Test.php b/tests/issues/242/Issue242Test.php index 1bb1c1f7..1bed3481 100644 --- a/tests/issues/242/Issue242Test.php +++ b/tests/issues/242/Issue242Test.php @@ -1,19 +1,18 @@ assertInstanceOf(\cebe\openapi\SpecObjectInterface::class, $openapi); - $file = dirname(__DIR__, 2) . '/data/issue/242/spec.json'; + $openapi = Reader::readFromJsonFile($file); + $this->assertInstanceOf(SpecObjectInterface::class, $openapi); + $dirSep = DIRECTORY_SEPARATOR; $cmd = 'php ' . dirname(__DIR__, 3) . "{$dirSep}bin{$dirSep}php-openapi validate " . $file . " 2>&1"; exec($cmd, $op, $ec); @@ -26,4 +25,21 @@ private function removeCliFormatting($string) // Regex to remove ANSI escape codes return preg_replace('/\e\[[0-9;]*m/', '', $string); } -} \ No newline at end of file + + public function test242Case2() + { + // read in yml + $openapi = Reader::readFromYamlFile(dirname(__DIR__, 2) . '/data/issue/242/spec2.yml'); + $this->assertInstanceOf(SpecObjectInterface::class, $openapi); + $this->assertSame(json_decode(json_encode($openapi->paths['/endpoint']->get->security->getSerializableData()), true), [ + [ + 'apiKey' => [], + 'bearerAuth' => [] + ] + ]); + + // write in yml # TODO + // read in json # TODO + // write in json # TODO + } +} From 19fabbc08e99e1f88e4b0c161e59fab742a7e084 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 May 2025 13:36:02 +0530 Subject: [PATCH 08/13] Add more tests --- tests/data/issue/242/spec2.json | 45 ++++++++++++ tests/data/issue/242/spec2.yml | 1 + tests/issues/242/Issue242Test.php | 110 ++++++++++++++++++++++++++++-- 3 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 tests/data/issue/242/spec2.json diff --git a/tests/data/issue/242/spec2.json b/tests/data/issue/242/spec2.json new file mode 100644 index 00000000..331d8655 --- /dev/null +++ b/tests/data/issue/242/spec2.json @@ -0,0 +1,45 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "API Documentation", + "description": "All API endpoints are presented here.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://127.0.0.1:8080/" + } + ], + "paths": { + "/endpoint": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey": [], + "bearerAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "apiKey": { + "type": "apiKey", + "in": "header", + "name": "X-APi-Key" + }, + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Authorization header using the Bearer scheme." + } + } + } +} \ No newline at end of file diff --git a/tests/data/issue/242/spec2.yml b/tests/data/issue/242/spec2.yml index 27cb58e6..eebe517b 100644 --- a/tests/data/issue/242/spec2.yml +++ b/tests/data/issue/242/spec2.yml @@ -1,3 +1,4 @@ +# https://github.com/cebe/php-openapi/issues/242#issuecomment-2886431173 openapi: 3.0.0 info: title: API Documentation diff --git a/tests/issues/242/Issue242Test.php b/tests/issues/242/Issue242Test.php index 1bed3481..8c8b83eb 100644 --- a/tests/issues/242/Issue242Test.php +++ b/tests/issues/242/Issue242Test.php @@ -2,6 +2,7 @@ use cebe\openapi\Reader; use cebe\openapi\SpecObjectInterface; +use cebe\openapi\Writer; use PHPUnit\Framework\TestCase; // https://github.com/cebe/php-openapi/issues/242 @@ -26,10 +27,11 @@ private function removeCliFormatting($string) return preg_replace('/\e\[[0-9;]*m/', '', $string); } - public function test242Case2() + public function test242Case2() # https://github.com/cebe/php-openapi/issues/242#issuecomment-2886431173 { // read in yml - $openapi = Reader::readFromYamlFile(dirname(__DIR__, 2) . '/data/issue/242/spec2.yml'); + $file = dirname(__DIR__, 2) . '/data/issue/242/spec2.yml'; + $openapi = Reader::readFromYamlFile($file); $this->assertInstanceOf(SpecObjectInterface::class, $openapi); $this->assertSame(json_decode(json_encode($openapi->paths['/endpoint']->get->security->getSerializableData()), true), [ [ @@ -38,8 +40,106 @@ public function test242Case2() ] ]); - // write in yml # TODO - // read in json # TODO - // write in json # TODO + # write back to yml + $json = Writer::writeToYaml($openapi); + $this->assertEquals(preg_replace('~\R~', "\n", <<assertInstanceOf(SpecObjectInterface::class, $openapi); + $this->assertSame(json_decode(json_encode($openapi->paths['/endpoint']->get->security->getSerializableData()), true), [ + [ + 'apiKey' => [], + 'bearerAuth' => [] + ] + ]); + + // write back in json + $json = Writer::writeToJson($openapi); + $this->assertEquals(preg_replace('~\R~', "\n", << Date: Sat, 17 May 2025 13:55:53 +0530 Subject: [PATCH 09/13] Fix failing tests in CI --- README.md | 2 ++ src/spec/SecurityRequirements.php | 1 - tests/spec/OpenApiTest.php | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7cd0715..f8171d43 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # php-openapi +TODO docs related to issue 242, 238 + Read and write [OpenAPI](https://www.openapis.org/) 3.0.x YAML and JSON files and make the content accessible in PHP objects. It also provides a CLI tool for validating and converting OpenAPI 3.0.x Description files. diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php index 055d5550..58a5d3bf 100644 --- a/src/spec/SecurityRequirements.php +++ b/src/spec/SecurityRequirements.php @@ -69,7 +69,6 @@ public function getSerializableData() $data = []; foreach ($this->_securityRequirements ?? [] as $outerIndex => $content) { - if (is_string($outerIndex)) { $data[] = [$outerIndex => $content->getSerializableData()]; } elseif (is_numeric($outerIndex)) { diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 433bd352..7999daea 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -88,6 +88,21 @@ public function assertAllInstanceOf($className, $array) } } + public function assertFewInstanceOf($className, $array) + { + foreach($array as $k => $v) { + if (is_numeric($k) && $v === []) { # https://github.com/cebe/php-openapi/issues/238 + continue; + } + + if (is_array($v)) { + $this->{__FUNCTION__}($className, $v); + } else { + $this->assertInstanceOf($className, $v, "Asserting that item with key '$k' is instance of $className"); + } + } + } + public function specProvider() { // examples from https://github.com/OAI/OpenAPI-Specification/tree/master/examples/v3.0 @@ -222,7 +237,7 @@ public function testSpecs($openApiFile) // security $openapi->security !== null && $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $openapi->security); - $openapi->security !== null && $this->assertAllInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $openapi->security->getRequirements()); + $openapi->security !== null && $this->assertFewInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $openapi->security->getRequirements()); // tags $this->assertAllInstanceOf(\cebe\openapi\spec\Tag::class, $openapi->tags); From 3004221d506dfa7f3149708de079a0920417b860 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Mon, 19 May 2025 15:44:12 +0530 Subject: [PATCH 10/13] Add more tests --- tests/WriterTest.php | 48 +++++++--- tests/issues/242/Issue242Test.php | 146 +++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 18 deletions(-) diff --git a/tests/WriterTest.php b/tests/WriterTest.php index 355a7449..d6112d05 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -8,6 +8,7 @@ use cebe\openapi\spec\SecurityRequirement; use cebe\openapi\spec\SecurityRequirements; use cebe\openapi\spec\SecurityScheme; +use cebe\openapi\Writer; class WriterTest extends \PHPUnit\Framework\TestCase { @@ -27,7 +28,7 @@ public function testWriteJson() { $openapi = $this->createOpenAPI(); - $json = \cebe\openapi\Writer::writeToJson($openapi); + $json = Writer::writeToJson($openapi); $this->assertEquals(preg_replace('~\R~', "\n", << 'something' ]); - $json = \cebe\openapi\Writer::writeToJson($openapi); + $json = Writer::writeToJson($openapi); $this->assertEquals(preg_replace('~\R~', "\n", <<createOpenAPI(); - $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + $yaml = Writer::writeToYaml($openapi); $this->assertEquals(preg_replace('~\R~', "\n", << [], ]); - $json = \cebe\openapi\Writer::writeToJson($openapi); + $json = Writer::writeToJson($openapi); $this->assertEquals(preg_replace('~\R~', "\n", << [], ]); - $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + $yaml = Writer::writeToYaml($openapi); $this->assertEquals(preg_replace('~\R~', "\n", <<assertEquals(preg_replace('~\R~', "\n", <<assertEquals(preg_replace('~\R~', "\n", <<assertEquals(preg_replace('~\R~', "\n", << [], ]); + $yaml = Writer::writeToYaml($openapi); - $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + // case 2 + $openapi2 = $this->createOpenAPI([ + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + 'bearerFormat' => 'AuthToken and JWT Format' # optional, arbitrary value for documentation purposes + ]) + ], + ]), + 'security' => new SecurityRequirements([ + [ + 'BearerAuth' => new SecurityRequirement([]) + ] + ]), + 'paths' => [], + ]); + $yaml2 = Writer::writeToYaml($openapi2); - $this->assertEquals(preg_replace('~\R~', "\n", <<assertEquals(preg_replace('~\R~', "\n", $expected), $yaml); + $this->assertEquals(preg_replace('~\R~', "\n", $expected), $yaml2); } } diff --git a/tests/issues/242/Issue242Test.php b/tests/issues/242/Issue242Test.php index 8c8b83eb..7ab89cc2 100644 --- a/tests/issues/242/Issue242Test.php +++ b/tests/issues/242/Issue242Test.php @@ -1,6 +1,10 @@ assertEquals(preg_replace('~\R~', "\n", <<assertEquals(preg_replace('~\R~', "\n", <<assertInstanceOf(SpecObjectInterface::class, $openapi); + $act = json_decode(json_encode($openapi->security->getSerializableData()), true); + $this->assertSame([], $act[0]['BasicAuth']); + + # write back to yml + $yaml = Writer::writeToYaml($openapi); + $this->assertEquals(preg_replace('~\R~', "\n", <<createOpenAPI([ + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + ]), + 'BasicAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'basic', + ]), + 'ApiKeyAuth' => new SecurityScheme([ + 'type' => 'apiKey', + 'name' => 'X-API-Key', + 'in' => 'header' + ]) + ], + ]), + 'security' => new SecurityRequirements([ + [ + 'BearerAuth' => new SecurityRequirement([]), + 'BasicAuth' => new SecurityRequirement([]) + ], + [ + 'ApiKeyAuth' => new SecurityRequirement([]) + ] + ]), + 'paths' => [], + ]); + + + $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + + $this->assertEquals(preg_replace('~\R~', "\n", << '3.0.0', + 'info' => [ + 'title' => 'Test API', + 'version' => '1.0.0', + ], + 'paths' => [], + ], $merge)); + } } From 8c14866afed83bbb450ba2f76e5bace8c3615a0c Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Mon, 19 May 2025 15:44:21 +0530 Subject: [PATCH 11/13] Add more tests 2 --- tests/data/issue/242/multiple_auth.yml | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/data/issue/242/multiple_auth.yml diff --git a/tests/data/issue/242/multiple_auth.yml b/tests/data/issue/242/multiple_auth.yml new file mode 100644 index 00000000..904663f4 --- /dev/null +++ b/tests/data/issue/242/multiple_auth.yml @@ -0,0 +1,39 @@ +openapi: 3.0.0 +info: + title: Multiple auth + version: 1.0.0 +paths: {} +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + + BearerAuth: + type: http + scheme: bearer + + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + + OpenID: + type: openIdConnect + openIdConnectUrl: https://example.com/.well-known/openid-configuration + + OAuth2: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + tokenUrl: https://example.com/oauth/token + scopes: + read: Grants read access + write: Grants write access + admin: Grants access to admin operations +security: + - BasicAuth: [] + BearerAuth: [] + - ApiKeyAuth: [] + OAuth2: [read] \ No newline at end of file From a9471af75313841c363988558a6bc4b276d71109 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Mon, 19 May 2025 17:14:23 +0530 Subject: [PATCH 12/13] Add docs --- README.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/README.md b/README.md index f8171d43..97dcd262 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,116 @@ $openapi = new OpenApi([ $json = \cebe\openapi\Writer::writeToJson($openapi); ``` +Write empty Security Requirement Object (`{}`): + +```php +$openapi = new OpenApi([ + ... + 'security' => new SecurityRequirements([ + [] + ] + ... +]); +``` + +```yaml +... +security: + - {} +... +``` + +Write security for multiple authentication: + +```php +$openapi = new OpenApi([ + ... + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + ]), + 'BasicAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'basic', + ]), + 'ApiKeyAuth' => new SecurityScheme([ + 'type' => 'apiKey', + 'name' => 'X-API-Key', + 'in' => 'header' + ]) + ], + ]), + 'security' => new SecurityRequirements([ + [ + 'BearerAuth' => new SecurityRequirement([]), + 'BasicAuth' => new SecurityRequirement([]) + ], + [ + 'ApiKeyAuth' => new SecurityRequirement([]) + ] + ]), + ... +]); +``` + +```yaml +security: + - + BearerAuth: [] + BasicAuth: [] + - + ApiKeyAuth: [] + +``` + +Write single authentication (note that both below case will yield same output): + +```php +$openapi = new OpenApi([ + ... + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + ]) + ], + ]), + 'security' => new SecurityRequirements([ + 'BearerAuth' => new SecurityRequirement([]) + ]), + ... +]); +``` + +```php +$openapi = new OpenApi([ + ... + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + ]) + ], + ]), + 'security' => new SecurityRequirements([ + [ + 'BearerAuth' => new SecurityRequirement([]) + ] + ]), + ... +]); +``` + +```yaml +security: + - + BearerAuth: [] +``` + ### Reading API Description Files and Resolving References In the above we have passed the raw JSON or YAML data to the Reader. In order to be able to resolve From 3a33be4fcaed83666c2957e7982647d484fcb033 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Mon, 19 May 2025 17:19:07 +0530 Subject: [PATCH 13/13] Resolve TODOs --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 97dcd262..489dab28 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # php-openapi -TODO docs related to issue 242, 238 - Read and write [OpenAPI](https://www.openapis.org/) 3.0.x YAML and JSON files and make the content accessible in PHP objects. It also provides a CLI tool for validating and converting OpenAPI 3.0.x Description files.