diff --git a/Makefile b/Makefile index acfca9e2..add949a6 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ fix-style: php-cs-fixer.phar $(DOCKER_PHP) vendor/bin/indent --spaces .php_cs.dist $(DOCKER_PHP) ./php-cs-fixer.phar fix src/ --diff +cli: + docker-compose run --rm php bash + install: $(DOCKER_PHP) composer install --prefer-dist --no-interaction --no-progress --ansi $(DOCKER_NODE) yarn install @@ -82,4 +85,3 @@ coverage: .php-openapi-covA .php-openapi-covB grep -rhPo '^class \w+' src/spec/ | awk '{print $$2}' |grep -v '^Type$$' | sort > $@ .PHONY: all check-style fix-style install test lint coverage - diff --git a/src/spec/OpenApi.php b/src/spec/OpenApi.php index 29d38b38..4a8aa243 100644 --- a/src/spec/OpenApi.php +++ b/src/spec/OpenApi.php @@ -7,7 +7,6 @@ namespace cebe\openapi\spec; -use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\SpecBaseObject; /** @@ -38,7 +37,7 @@ protected function attributes(): array 'servers' => [Server::class], 'paths' => Paths::class, 'components' => Components::class, - 'security' => [SecurityRequirement::class], + 'security' => SecurityRequirements::class, 'tags' => [Tag::class], 'externalDocs' => ExternalDocumentation::class, ]; diff --git a/src/spec/Operation.php b/src/spec/Operation.php index b0525327..8becca10 100644 --- a/src/spec/Operation.php +++ b/src/spec/Operation.php @@ -46,7 +46,7 @@ protected function attributes(): array 'responses' => Responses::class, 'callbacks' => [Type::STRING, Callback::class], 'deprecated' => Type::BOOLEAN, - 'security' => [SecurityRequirement::class], + 'security' => SecurityRequirements::class, 'servers' => [Server::class], ]; } diff --git a/src/spec/SecurityRequirement.php b/src/spec/SecurityRequirement.php index 71f805c8..ec3e1d90 100644 --- a/src/spec/SecurityRequirement.php +++ b/src/spec/SecurityRequirement.php @@ -10,13 +10,20 @@ use cebe\openapi\SpecBaseObject; /** - * Lists the required security schemes to execute this operation. + * A required security scheme to execute this operation. * * @link https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#securityRequirementObject * */ class SecurityRequirement extends SpecBaseObject { + private $_securityRequirement; + public function __construct(array $data) + { + parent::__construct($data); + $this->_securityRequirement = $data; + } + /** * @return array array of attributes available in this object. */ @@ -34,4 +41,9 @@ protected function attributes(): array protected function performValidation() { } + + public function getSerializableData() + { + return $this->_securityRequirement; + } } diff --git a/src/spec/SecurityRequirements.php b/src/spec/SecurityRequirements.php new file mode 100644 index 00000000..cbda1ba7 --- /dev/null +++ b/src/spec/SecurityRequirements.php @@ -0,0 +1,78 @@ + and contributors + * @license https://github.com/cebe/php-openapi/blob/master/LICENSE + */ + +namespace cebe\openapi\spec; + +use cebe\openapi\SpecBaseObject; + +/** + * Lists the required security schemes to execute this operation. + * + * @link https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#securityRequirementObject + * + */ +class SecurityRequirements extends SpecBaseObject +{ + private $_securityRequirements; + + public function __construct(array $data) + { + parent::__construct($data); + + foreach ($data as $index => $value) { + if (is_numeric($index)) { // read + $this->_securityRequirements[array_keys($value)[0]] = new SecurityRequirement(array_values($value)[0]); + } else { // write + $this->_securityRequirements[$index] = $value; + } + } + if ($data === []) { + $this->_securityRequirements = []; + } + } + + /** + * @return array array of attributes available in this object. + */ + protected function attributes(): array + { + // this object does not have a fixed set of attribute names + return []; + } + + /** + * Perform validation on this object, check data against OpenAPI Specification rules. + * + * Call `addError()` in case of validation errors. + */ + protected function performValidation() + { + } + + /** + * {@inheritDoc} + */ + public function getSerializableData() + { + $data = []; + foreach ($this->_securityRequirements ?? [] as $name => $securityRequirement) { + /** @var SecurityRequirement $securityRequirement */ + $data[] = [$name => $securityRequirement->getSerializableData()]; + } + return $data; + } + + public function getRequirement(string $name) + { + return $this->_securityRequirements[$name] ?? null; + } + + public function getRequirements() + { + return $this->_securityRequirements; + } +} diff --git a/tests/WriterTest.php b/tests/WriterTest.php index fc415b51..355a7449 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -1,6 +1,13 @@ createOpenAPI([ - 'security' => [new SecurityRequirement(['Bearer' => []])], + 'security' => new SecurityRequirements([ + 'Bearer' => new SecurityRequirement([]) + ]), ]); $json = \cebe\openapi\Writer::writeToJson($openapi); @@ -166,7 +175,9 @@ public function testWriteEmptySecurityPartJson() public function testWriteEmptySecurityPartYaml() { $openapi = $this->createOpenAPI([ - 'security' => [new SecurityRequirement(['Bearer' => []])], + 'security' => new SecurityRequirements([ + 'Bearer' => new SecurityRequirement([]) + ]), ]); $yaml = \cebe\openapi\Writer::writeToYaml($openapi); @@ -182,6 +193,105 @@ public function testWriteEmptySecurityPartYaml() - Bearer: [] +YAML + ), + $yaml + ); + } + + public function testSecurityAtPathOperationLevel() + { + $openapi = $this->createOpenAPI([ + 'components' => new Components([ + 'securitySchemes' => [ + 'BearerAuth' => new SecurityScheme([ + 'type' => 'http', + 'scheme' => 'bearer', + 'bearerFormat' => 'AuthToken and JWT Format' # optional, arbitrary value for documentation purposes + ]), + ], + ]), + 'paths' => [ + '/test' => new PathItem([ + 'get' => new Operation([ + 'security' => new SecurityRequirements([ + 'BearerAuth' => new SecurityRequirement([]), + ]), + 'responses' => new Responses([ + 200 => new Response(['description' => 'OK']), + ]) + ]) + ]) + ] + ]); + + $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + + + $this->assertEquals(preg_replace('~\R~', "\n", <<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' => [], + ]); + + $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + + + $this->assertEquals(preg_replace('~\R~', "\n", <<assertInstanceOf(\cebe\openapi\spec\Components::class, $openapi->components); // security - $this->assertAllInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $openapi->security); + $this->assertNull($openapi->security); # since it is not present in spec // tags $this->assertAllInstanceOf(\cebe\openapi\spec\Tag::class, $openapi->tags); @@ -221,7 +221,8 @@ public function testSpecs($openApiFile) } // security - $this->assertAllInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $openapi->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()); // tags $this->assertAllInstanceOf(\cebe\openapi\spec\Tag::class, $openapi->tags); diff --git a/tests/spec/OperationTest.php b/tests/spec/OperationTest.php index 6e30566f..734db321 100644 --- a/tests/spec/OperationTest.php +++ b/tests/spec/OperationTest.php @@ -79,10 +79,11 @@ public function testRead() $this->assertInstanceOf(\cebe\openapi\spec\Responses::class, $operation->responses); - $this->assertCount(1, $operation->security); - $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $operation->security[0]); - $this->assertCount(2, $operation->security[0]->petstore_auth); - $this->assertEquals(['write:pets', 'read:pets'], $operation->security[0]->petstore_auth); + $this->assertCount(1, $operation->security->getRequirements()); + $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirements::class, $operation->security); + $this->assertInstanceOf(\cebe\openapi\spec\SecurityRequirement::class, $operation->security->getRequirement('petstore_auth')); + $this->assertCount(2, $operation->security->getRequirement('petstore_auth')->getSerializableData()); + $this->assertEquals(['write:pets', 'read:pets'], $operation->security->getRequirement('petstore_auth')->getSerializableData()); $this->assertInstanceOf(ExternalDocumentation::class, $operation->externalDocs); $this->assertEquals('Find more info here', $operation->externalDocs->description); diff --git a/tests/spec/SecuritySchemeTest.php b/tests/spec/SecuritySchemeTest.php index 5e14d5f7..4dc6a2a1 100644 --- a/tests/spec/SecuritySchemeTest.php +++ b/tests/spec/SecuritySchemeTest.php @@ -11,6 +11,7 @@ * @covers \cebe\openapi\spec\OAuthFlows * @covers \cebe\openapi\spec\OAuthFlow * @covers \cebe\openapi\spec\SecurityRequirement + * @covers \cebe\openapi\spec\SecurityRequirements */ class SecuritySchemeTest extends \PHPUnit\Framework\TestCase { @@ -199,10 +200,10 @@ public function testDefaultSecurity() YAML ); - $this->assertSame([], $openapi->paths->getPath('/path/one')->post->security); + $this->assertSame([], $openapi->paths->getPath('/path/one')->post->security->getRequirements()); $this->assertSame(null, $openapi->paths->getPath('/path/two')->post->security); - $this->assertCount(1, $openapi->security); - $this->assertSame([], $openapi->security[0]->Bearer); + $this->assertCount(1, $openapi->security->getRequirements()); + $this->assertSame([], $openapi->security->getRequirement('Bearer')->getSerializableData()); } }