Skip to content

Fix: Add support for empty Security Requirement Object ({}) in security requirement #238 #239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,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
Expand Down
54 changes: 46 additions & 8 deletions src/spec/SecurityRequirements.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ 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 (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);
}
}
}

if ($data === []) {
$this->_securityRequirements = [];
}
Expand Down Expand Up @@ -59,20 +67,50 @@ protected function performValidation()
public function getSerializableData()
{
$data = [];
foreach ($this->_securityRequirements ?? [] as $name => $securityRequirement) {
/** @var SecurityRequirement $securityRequirement */
$data[] = [$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;
}

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
}
}
48 changes: 34 additions & 14 deletions tests/WriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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", <<<JSON
{
Expand All @@ -52,7 +53,7 @@ public function testWriteJsonMofify()
'description' => 'something'
]);

$json = \cebe\openapi\Writer::writeToJson($openapi);
$json = Writer::writeToJson($openapi);

$this->assertEquals(preg_replace('~\R~', "\n", <<<JSON
{
Expand All @@ -77,7 +78,7 @@ public function testWriteYaml()
{
$openapi = $this->createOpenAPI();

$yaml = \cebe\openapi\Writer::writeToYaml($openapi);
$yaml = Writer::writeToYaml($openapi);


$this->assertEquals(preg_replace('~\R~', "\n", <<<YAML
Expand All @@ -99,7 +100,7 @@ public function testWriteEmptySecurityJson()
'security' => [],
]);

$json = \cebe\openapi\Writer::writeToJson($openapi);
$json = Writer::writeToJson($openapi);

$this->assertEquals(preg_replace('~\R~', "\n", <<<JSON
{
Expand All @@ -124,7 +125,7 @@ public function testWriteEmptySecurityYaml()
'security' => [],
]);

$yaml = \cebe\openapi\Writer::writeToYaml($openapi);
$yaml = Writer::writeToYaml($openapi);


$this->assertEquals(preg_replace('~\R~', "\n", <<<YAML
Expand All @@ -149,7 +150,7 @@ public function testWriteEmptySecurityPartJson()
]),
]);

$json = \cebe\openapi\Writer::writeToJson($openapi);
$json = Writer::writeToJson($openapi);

$this->assertEquals(preg_replace('~\R~', "\n", <<<JSON
{
Expand Down Expand Up @@ -180,7 +181,7 @@ public function testWriteEmptySecurityPartYaml()
]),
]);

$yaml = \cebe\openapi\Writer::writeToYaml($openapi);
$yaml = Writer::writeToYaml($openapi);


$this->assertEquals(preg_replace('~\R~', "\n", <<<YAML
Expand Down Expand Up @@ -225,7 +226,7 @@ public function testSecurityAtPathOperationLevel()
]
]);

$yaml = \cebe\openapi\Writer::writeToYaml($openapi);
$yaml = Writer::writeToYaml($openapi);


$this->assertEquals(preg_replace('~\R~', "\n", <<<YAML
Expand Down Expand Up @@ -272,11 +273,30 @@ public function testSecurityAtGlobalLevel()
]),
'paths' => [],
]);
$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", <<<YAML
$expected = <<<YAML
openapi: 3.0.0
info:
title: 'Test API'
Expand All @@ -292,9 +312,9 @@ public function testSecurityAtGlobalLevel()
-
BearerAuth: []

YAML
),
$yaml
);
YAML;

$this->assertEquals(preg_replace('~\R~', "\n", $expected), $yaml);
$this->assertEquals(preg_replace('~\R~', "\n", $expected), $yaml2);
}
}
28 changes: 28 additions & 0 deletions tests/data/issue/238/spec.yml
Original file line number Diff line number Diff line change
@@ -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: []
39 changes: 39 additions & 0 deletions tests/data/issue/242/multiple_auth.yml
Original file line number Diff line number Diff line change
@@ -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]
Loading