Skip to content
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

Add ability to specify root security and securitySchemes components #16

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,37 @@ class Post extends Schema implements DescribesEndpoints
}
```

### Security schemes & requirements

It is possible to declare security schemes and requirements for each server using our config file.
Examples of each security scheme can be found in the config file.

`config/openapi.php`:
``` php
return [
'servers' => [
'v1' => [
//...

'securitySchemes' => [
'MyBearerScheme' => [
'type' => 'http',
'description' => 'Example scheme instructions, can be done in Markdown for long / formatted descriptions',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
],
],

'security' => [
'MyBearerScheme',
],

//...
],
],
//...
```

## Generating Documentation

### [Speccy](https://github.com/wework/speccy)
Expand Down
75 changes: 75 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,81 @@
'description' => 'JSON:API built using Laravel',
'version' => '1.0.0',
],

/*
* Available security schemes, each scheme needs a unique name as key of the entry
* This unique name can be used in the security array below to enable the scheme at the root level
* Examples commented below
*/
'securitySchemes' => [
// 'Bearer' => [
// 'type' => 'http',
// 'description' => 'My http Scheme description',
// 'scheme' => 'bearer',
// 'bearerFormat' => 'JWT',
// ],
// 'ApiKey' => [
// 'type' => 'apiKey',
// 'description' => 'My apiKey Scheme description',
// 'name' => 'X-API-KEY',
// 'in' => 'header',
// ],
// 'OAuth2' => [
// 'type' => 'oauth2',
// 'description' => 'My oauth2 Scheme description',
// 'flows' => [
// 'implicit' => [
// 'authorizationUrl' => 'https://example.com/api/oauth/dialog',
// 'scopes' => [
// 'write:posts' => 'modify posts in your account',
// 'read:posts' => 'read your posts',
// ],
// ],
// 'password' => [
// 'tokenUrl' => 'https://example.com/api/oauth/token',
// 'scopes' => [
// 'write:posts' => 'modify posts in your account',
// 'read:posts' => 'read your posts',
// ],
// ],
// 'clientCredentials' => [
// 'tokenUrl' => 'https://example.com/api/oauth/token',
// 'scopes' => [
// 'write:posts' => 'modify posts in your account',
// 'read:posts' => 'read your posts',
// ],
// ],
// 'authorizationCode' => [
// 'authorizationUrl' => 'https://example.com/api/oauth/dialog',
// 'tokenUrl' => 'https://example.com/api/oauth/token',
// 'refreshUrl' => 'https://example.com/api/oauth/refresh',
// 'scopes' => [
// 'write:posts' => 'modify posts in your account',
// 'read:posts' => 'read your posts',
// ],
// ],
// ],
// ],
// 'OpenId' => [
// 'type' => 'openIdConnect',
// 'description' => 'My openIdConnect Scheme description',
// 'openIdConnectUrl' => 'https://example.com/api/oauth/openid',
// ],
],

/*
* Root level security array, each entry should be a reference to a security scheme declared above
* Examples commented below
*/
'security' => [
// 'Bearer',
// 'ApiKey',
// 'OAuth2' => [
// 'write:posts',
// 'read:posts',
// ],
// 'OpenId',
],
],
],

Expand Down
16 changes: 16 additions & 0 deletions src/Builders/SecurityBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace LaravelJsonApi\OpenApiSpec\Builders;

use LaravelJsonApi\OpenApiSpec\Descriptors\Server;

class SecurityBuilder extends Builder
{
/**
* @return \GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityRequirement[]
*/
public function build(): array
{
return (new Server($this->generator))->security();
}
}
16 changes: 16 additions & 0 deletions src/Builders/SecuritySchemesBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace LaravelJsonApi\OpenApiSpec\Builders;

use LaravelJsonApi\OpenApiSpec\Descriptors\Server;

class SecuritySchemesBuilder extends Builder
{
/**
* @return \GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityScheme[]
*/
public function build(): array
{
return (new Server($this->generator))->securitySchemes();
}
}
10 changes: 5 additions & 5 deletions src/Commands/GenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function handle()
collect($exception->getErrors())
->map(function ($val) {
return collect($val)->map(function ($val, $key) {
return sprintf('%s: %s', ucfirst($key), $val);
return sprintf('%s: %s', ucfirst($key), is_string($val) ? $val : json_encode($val));
})->join("\n");
})->each(function ($string) {
$this->line($string);
Expand All @@ -55,13 +55,13 @@ public function handle()
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk(config('openapi.filesystem_disk'));

$fileName = $serverKey.'_openapi.'.$format;
$filePath = str_replace(base_path().'/', '', $storageDisk->path($fileName));
$fileName = $serverKey . '_openapi.' . $format;
$filePath = str_replace(base_path() . '/', '', $storageDisk->path($fileName));

$this->line('Complete! '.$filePath);
$this->line('Complete! ' . $filePath);
$this->newLine();
$this->line('Run the following to see your API docs');
$this->info('speccy serve '.$filePath);
$this->info('speccy serve ' . $filePath);
$this->newLine();

return 0;
Expand Down
67 changes: 59 additions & 8 deletions src/Descriptors/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ class Server extends BaseDescriptor
public function info(): Objects\Info
{
return Objects\Info::create()
->title(config("openapi.servers.{$this->generator->key()}.info.title"))
->description(config("openapi.servers.{$this->generator->key()}.info.description"))
->version(config("openapi.servers.{$this->generator->key()}.info.version"));
->title(config("openapi.servers.{$this->generator->key()}.info.title"))
->description(config("openapi.servers.{$this->generator->key()}.info.description"))
->version(config("openapi.servers.{$this->generator->key()}.info.version"));
}

/**
Expand All @@ -32,11 +32,62 @@ public function info(): Objects\Info
public function servers(): array
{
return [
Objects\Server::create()
->url('{serverUrl}')
->variables(Objects\ServerVariable::create('serverUrl')
->default($this->generator->server()->url())
),
Objects\Server::create()
->url('{serverUrl}')
->variables(
Objects\ServerVariable::create('serverUrl')
->default($this->generator->server()->url())
),
];
}

/**
* @return \GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityScheme[]
*/
public function securitySchemes(): array
{
return collect(config("openapi.servers.{$this->generator->key()}.securitySchemes") ?? [])
->map(function ($scheme, $key) {
return Objects\SecurityScheme::create()
->objectId($key)
->type($scheme['type'])
->scheme($scheme['scheme'] ?? null)
->bearerFormat($scheme['bearerFormat'] ?? null)
->description($scheme['description'] ?? null)
->name($scheme['name'] ?? null)
->in($scheme['in'] ?? null)
->openIdConnectUrl($scheme['openIdConnectUrl'] ?? null)
->flows(
...collect($scheme['flows'] ?? [])
->map(function ($flow, $key) {
return Objects\OAuthFlow::create()
->flow($key)
->authorizationUrl($flow['authorizationUrl'] ?? null)
->tokenUrl($flow['tokenUrl'] ?? null)
->refreshUrl($flow['refreshUrl'] ?? null)
->scopes($flow['scopes'] ?? []);
})
->toArray()
);
})
->toArray();
}

/**
* @return \GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityRequirement[]
*/
public function security(): array
{
return collect(config("openapi.servers.{$this->generator->key()}.security") ?? [])
->map(function ($security, $key) {
return Objects\SecurityRequirement::create()
->securityScheme(
is_string($key) ? $key : $security
)
->scopes(
...(is_array($security) ? $security : [])
);
})
->toArray();
}
}
30 changes: 18 additions & 12 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@
use LaravelJsonApi\Core\Support\AppResolver;
use LaravelJsonApi\OpenApiSpec\Builders\InfoBuilder;
use LaravelJsonApi\OpenApiSpec\Builders\PathsBuilder;
use LaravelJsonApi\OpenApiSpec\Builders\SecurityBuilder;
use LaravelJsonApi\OpenApiSpec\Builders\SecuritySchemesBuilder;
use LaravelJsonApi\OpenApiSpec\Builders\ServerBuilder;

class Generator
{
protected string $key;

protected Server $server;

protected InfoBuilder $infoBuilder;

protected ServerBuilder $serverBuilder;

protected PathsBuilder $pathsBuilder;

protected SecuritySchemesBuilder $securitySchemesBuilder;
protected SecurityBuilder $securityBuilder;
protected ComponentsContainer $components;

protected ResourceContainer $resources;

/**
Expand All @@ -44,6 +42,8 @@ public function __construct($key)
$this->components = new ComponentsContainer();
$this->resources = new ResourceContainer($this->server);
$this->pathsBuilder = new PathsBuilder($this, $this->components);
$this->securitySchemesBuilder = new SecuritySchemesBuilder($this);
$this->securityBuilder = new SecurityBuilder($this);
}

/**
Expand All @@ -52,13 +52,19 @@ public function __construct($key)
public function generate(): OpenApi
{
return OpenApi::create()
->openapi(OpenApi::OPENAPI_3_0_2)
->info($this->infoBuilder->build())
->servers(...$this->serverBuilder->build())
->paths(...array_values($this->pathsBuilder->build()))
->components($this->components()->components());
->openapi(OpenApi::OPENAPI_3_0_2)
->info($this->infoBuilder->build())
->servers(...$this->serverBuilder->build())
->paths(...array_values($this->pathsBuilder->build()))
->components(
$this
->components()
->components()
->securitySchemes(...array_values($this->securitySchemesBuilder->build()))
)
->security(...array_values($this->securityBuilder->build()));
}

/**
* @return string
*/
Expand Down
2 changes: 1 addition & 1 deletion src/OpenApiGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function generate(string $serverKey, string $format = 'yaml'): string
$fileName = $serverKey.'_openapi.'.$format;

if ($format === 'yaml') {
$output = Yaml::dump($openapi->toArray());
$output = Yaml::dump($openapi->toArray(), 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
} elseif ($format === 'json') {
$output = json_encode($openapi->toArray(), JSON_PRETTY_PRINT);
}
Expand Down
14 changes: 14 additions & 0 deletions tests/Feature/OpenApiSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,18 @@ public function testItCreatesAnEmptyDescriptionIfASchemaDoesNotImplementTheDescr
{
$this->assertEquals('', $this->spec['paths']['/videos']['get']['description']);
}

public function testItCreatesSecuritySchemes()
{
$this->assertEquals('http', $this->spec['components']['securitySchemes']['Bearer']['type']);
$this->assertEquals('bearer', $this->spec['components']['securitySchemes']['Bearer']['scheme']);
$this->assertEquals('JWT', $this->spec['components']['securitySchemes']['Bearer']['bearerFormat']);
$this->assertEquals('Test Bearer description', $this->spec['components']['securitySchemes']['Bearer']['description']);
}

public function testItCreatesSecurityEntries()
{
$this->assertArrayHasKey('Bearer', $this->spec['security'][0]);
$this->assertIsArray($this->spec['security'][0]['Bearer']);
}
}
13 changes: 13 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ protected function defineEnvironment($app)
],
],
]);

$app['config']->set('openapi.servers.v1.securitySchemes', [
'Bearer' => [
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
'description' => 'Test Bearer description',
],
]);

$app['config']->set('openapi.servers.v1.security', [
'Bearer',
]);
}

protected function defineRoutes($router)
Expand Down