Skip to content

Commit

Permalink
Invalid security schemes
Browse files Browse the repository at this point in the history
  • Loading branch information
uderline committed Dec 21, 2022
1 parent a0a19d6 commit 08b265b
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 31 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ A new file called `openapi.json` has been generated !
class Controller {
#[
GET("/path/{id}", ["Tag1", "Tag2"], "Description of the method"),
Property(PropertyType::STRING, "prop1", description: "Property description", enum: ["val1", "val2"]),
Property(PropertyType::INT, "prop2", example: 1),
Property(PropertyType::BOOLEAN, "prop3"),
Property(PropertyType::REF, "prop4", ref: RefSchema::class)
Property(Type::STRING, "prop1", description: "Property description", enum: ["val1", "val2"]),
Property(Type::INT, "prop2", example: 1),
Property(Type::BOOLEAN, "prop3"),
Property(Type::REF, "prop4", ref: RefSchema::class)
Response(ref: SchemaName::class, description: "Response description")
]
public function get(#[Parameter("Parameter description")] int $id): JsonResponse {
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"require": {
"php": ">=8.0",
"symfony/finder": "^6.0",
"symfony/http-foundation": "^6.0"
"symfony/http-foundation": "^6.0",
"symfony/string": "^6.2"
},
"autoload": {
"psr-4": {
Expand Down
9 changes: 8 additions & 1 deletion opag
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ foreach ($files as $autoload) {
include_once $autoload->getPathName();
}

$generator = \OpenApiGenerator\Generator::create()->generate();
try {
$generator = \OpenApiGenerator\Generator::create()->generate();
} catch (\Exception $e) {
echo "[ERROR] ".$e->getMessage();
die;
}

$schema = json_encode($generator, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

file_put_contents($outputFile, $schema);

echo "DONE !";
67 changes: 67 additions & 0 deletions src/ApiDescriptionChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ private function __construct(private array $definition)
$this->checkOpenApiVersion();
$this->checkInfo();
$this->checkServers();
$this->checkSecurityScheme();
$this->checkSecurity();
}

/**
Expand Down Expand Up @@ -65,6 +67,71 @@ private function checkServers(): void
}
}

private function checkSecurityScheme(): void
{
if (!isset($this->definition['securitySchemes'])) {
return;
}

foreach ($this->definition['securitySchemes'] as $securityScheme) {
switch ($securityScheme["type"]) {
case "apiKey":
if (empty($securityScheme["name"]) || !in_array(
$securityScheme["in"],
['query', 'header', 'cookie'],
true
)) {
throw new \InvalidArgumentException("SecurityScheme: apiKey must have name and in");
}
break;
case "http":
if (empty($securityScheme["scheme"])) {
throw new \InvalidArgumentException("SecurityScheme: http must have scheme");
}
break;
case "mutualTLS":
break;
case "oauth2":
if (empty($securityScheme["flows"])) {
throw new \InvalidArgumentException("SecurityScheme: oauth2 must have flows");
}
break;
case "openIdConnect":
if (empty($securityScheme["openIdConnectUrl"])) {
throw new \InvalidArgumentException("SecurityScheme: openIdConnect must have openIdConnectUrl");
}
break;
default:
throw new \InvalidArgumentException(
'Invalid security scheme type: should be one of "apiKey", "http", "oauth2", "openIdConnect"'
);
}
}
}

private function checkSecurity(): void
{
if (!isset($this->definition['security'])) {
return;
}

foreach ($this->definition['security'] as $security) {
if ($security instanceof \stdClass) {
continue;
}

$availableValues = array_keys($this->definition['components']['securitySchemes']);
$securityName = array_keys($security)[0];

if (!in_array($securityName, $availableValues, true)) {
throw new \InvalidArgumentException(
"Security: security scheme not found. Please choose one of the followings: " .
implode(', ', $availableValues)
);
}
}
}

/**
* Check the API description for any omitted mandatory fields or wrong formats.
*/
Expand Down
34 changes: 34 additions & 0 deletions src/Attributes/Security.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace OpenApiGenerator\Attributes;

use Attribute;
use JsonSerializable;
use Symfony\Component\String\UnicodeString;

#[Attribute(Attribute::TARGET_CLASS)]
class Security implements JsonSerializable
{
/**
* Be aware that security scheme keys are the slugified names or the type of the security scheme.
* An empty array means that security is optional: https://spec.openapis.org/oas/v3.1.0#fixed-fields
*/
public function __construct(private array $securitySchemeKeys = [])
{
// ...
}

/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
if (empty($this->securitySchemeKeys)) {
return [new \stdClass()];
}

return array_map(fn (string $key) => [$key => []], $this->securitySchemeKeys);
}
}
28 changes: 18 additions & 10 deletions src/Attributes/SecurityScheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,42 @@

use Attribute;
use JsonSerializable;
use Symfony\Component\String\UnicodeString;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class SecurityScheme implements JsonSerializable
{
public function __construct(
private string $securityKey = '',
private string $type = '',
private string $name= '',
private string $in = '',
private string $bearerFormat = '',
private string $scheme = '',
private string $type,
private ?string $description = null,
private ?string $name = null,
private ?string $in = null,
private ?string $scheme = null,
private ?string $bearerFormat = null,
private array $flows = [],
private ?string $openIdConnectUrl = null,
) {
//
}

/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
$slugger = new UnicodeString($this->name ?: $this->type);
$slugName = (string)$slugger->snake();

return [
$this->securityKey => [
$slugName => array_filter([
'type' => $this->type,
'description' => $this->description,
'name' => $this->name,
'in' => $this->in,
'bearerFormat' => $this->bearerFormat,
'scheme' => $this->scheme,
],
'bearerFormat' => $this->bearerFormat,
'flows' => $this->flows,
'openIdConnectUrl' => $this->openIdConnectUrl,
])
];
}
}
24 changes: 17 additions & 7 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use OpenApiGenerator\Attributes\Controller;
use OpenApiGenerator\Attributes\Info;
use OpenApiGenerator\Attributes\Schema;
use OpenApiGenerator\Attributes\Security;
use OpenApiGenerator\Attributes\SecurityScheme;
use OpenApiGenerator\Attributes\Server;
use ReflectionAttribute;
Expand All @@ -16,7 +17,7 @@

class Generator
{
public const OPENAPI_VERSION = "3.0.0";
public const OPENAPI_VERSION = "3.1.0";

/**
* API description
Expand Down Expand Up @@ -68,6 +69,7 @@ public function generate(): array
$this->loadSchema($reflectionClass);
$this->loadServer($reflectionClass);
$this->loadSecurityScheme($reflectionClass);
$this->loadSecurity($reflectionClass);
}

$this->description['paths'] = $this->generatorHttp->build();
Expand Down Expand Up @@ -158,18 +160,26 @@ private function loadServer(ReflectionClass $reflectionClass): void
private function loadSecurityScheme(ReflectionClass $reflectionClass): void
{
if (count($reflectionClass->getAttributes(SecurityScheme::class))) {
$securitySchemas = $reflectionClass->getAttributes(SecurityScheme::class);
$securitySchemes = $reflectionClass->getAttributes(SecurityScheme::class);

foreach ($securitySchemas as $item) {
foreach ($securitySchemes as $item) {
$data = $item->newInstance()->jsonSerialize();
$key = array_keys($data)[0];
$this->description['components']['securitySchemes'][$key] = $data[$key];
}
}
}
/**
* @param ReflectionClass $reflectionClass
* @return void
*/
private function loadSecurity(ReflectionClass $reflectionClass): void
{
if (count($reflectionClass->getAttributes(Security::class))) {
$securityAttributes = $reflectionClass->getAttributes(Security::class);
$security = reset($securityAttributes);

$this->description['security'] = array_map(
fn(string $canonicalName) => [$canonicalName => []],
array_keys($this->description['components']['securitySchemes'])
);
$this->description['security'] = $security->newInstance()->jsonSerialize();
}
}
}
11 changes: 3 additions & 8 deletions tests/Examples/Controller/SimpleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use OpenApiGenerator\Attributes\PathParameter;
use OpenApiGenerator\Attributes\Property;
use OpenApiGenerator\Attributes\Response;
use OpenApiGenerator\Attributes\Security;
use OpenApiGenerator\Attributes\SecurityScheme;
use OpenApiGenerator\Attributes\Server;
use OpenApiGenerator\Type;
Expand All @@ -19,14 +20,8 @@
Server('same server1', 'same url1'),
Info("title", "1.0.0", "The description"),
Server('same server2', 'same url2'),
SecurityScheme(
'bearerAuth',
'http',
'bearerAuth',
'header',
'JWT',
'bearer',
),
Security(['http']),
SecurityScheme(type: 'http', scheme: 'Bearer', bearerFormat: 'JWT bearer token'),
Controller,
]
class SimpleController
Expand Down

0 comments on commit 08b265b

Please sign in to comment.