Skip to content
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();
}
}
2 changes: 1 addition & 1 deletion 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 Down
55 changes: 53 additions & 2 deletions src/Descriptors/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,60 @@ public function servers(): array
return [
Objects\Server::create()
->url('{serverUrl}')
->variables(Objects\ServerVariable::create('serverUrl')
->default($this->generator->server()->url())
->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();
}
}
16 changes: 15 additions & 1 deletion src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
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
Expand All @@ -21,6 +23,10 @@ class Generator

protected PathsBuilder $pathsBuilder;

protected SecuritySchemesBuilder $securitySchemesBuilder;

protected SecurityBuilder $securityBuilder;

protected ComponentsContainer $components;

protected ResourceContainer $resources;
Expand All @@ -42,6 +48,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);
}

public function generate(): OpenApi
Expand All @@ -51,7 +59,13 @@ public function generate(): OpenApi
->info($this->infoBuilder->build())
->servers(...$this->serverBuilder->build())
->paths(...array_values($this->pathsBuilder->build()))
->components($this->components()->components());
->components(
$this
->components()
->components()
->securitySchemes(...array_values($this->securitySchemesBuilder->build()))
)
->security(...array_values($this->securityBuilder->build()));
}

public function key(): 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 @@ -47,4 +47,18 @@ public function test_it_describes_non_eloquent_resources(): void
$this->assertEquals('Get all sites', $this->spec['paths']['/sites']['get']['summary']);
$this->assertEquals('object', $this->spec['components']['schemas']['resources.sites.resource.fetch']['type']);
}

public function test_it_creates_security_schemes()
{
$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 test_it_creates_security_entries()
{
$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 @@ -27,6 +27,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