Skip to content

Commit 7b7661e

Browse files
authored
Merge pull request vyuldashev#52 from ArrowWebSolutions/develop
Ability to turn off security at operation level
2 parents de5f355 + 94fbca8 commit 7b7661e

File tree

4 files changed

+251
-13
lines changed

4 files changed

+251
-13
lines changed

src/Attributes/Operation.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?php
2-
32
namespace Vyuldashev\LaravelOpenApi\Attributes;
43

54
use Attribute;
@@ -31,6 +30,12 @@ public function __construct(string $id = null, array $tags = [], string $securit
3130
$this->tags = $tags;
3231
$this->method = $method;
3332

33+
if ($security === '') {
34+
//user wants to turn off security on this operation
35+
$this->security = $security;
36+
return;
37+
}
38+
3439
if ($security) {
3540
$this->security = class_exists($security) ? $security : app()->getNamespace().'OpenApi\\SecuritySchemes\\'.$security;
3641

src/Builders/Paths/Operation/SecurityBuilder.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
<?php
2-
32
namespace Vyuldashev\LaravelOpenApi\Builders\Paths\Operation;
43

4+
use Vyuldashev\LaravelOpenApi\RouteInformation;
55
use GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityRequirement;
66
use Vyuldashev\LaravelOpenApi\Attributes\Operation as OperationAttribute;
7-
use Vyuldashev\LaravelOpenApi\RouteInformation;
87

98
class SecurityBuilder
109
{
@@ -14,6 +13,10 @@ public function build(RouteInformation $route): array
1413
->filter(static fn (object $attribute) => $attribute instanceof OperationAttribute)
1514
->filter(static fn (OperationAttribute $attribute) => isset($attribute->security))
1615
->map(static function (OperationAttribute $attribute) {
16+
// return a null scheme if the security is set to ''
17+
if ($attribute->security === '') {
18+
return SecurityRequirement::create()->securityScheme(null);
19+
}
1720
$security = app($attribute->security);
1821
$scheme = $security->build();
1922

src/Builders/Paths/OperationsBuilder.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
<?php
2-
32
namespace Vyuldashev\LaravelOpenApi\Builders\Paths;
43

5-
use GoldSpecDigital\ObjectOrientedOAS\Exceptions\InvalidArgumentException;
6-
use GoldSpecDigital\ObjectOrientedOAS\Objects\Operation;
7-
use Illuminate\Support\Collection;
84
use Illuminate\Support\Str;
9-
use Vyuldashev\LaravelOpenApi\Attributes\Operation as OperationAttribute;
5+
use Illuminate\Support\Collection;
6+
use Vyuldashev\LaravelOpenApi\RouteInformation;
7+
use GoldSpecDigital\ObjectOrientedOAS\Objects\Operation;
108
use Vyuldashev\LaravelOpenApi\Builders\ExtensionsBuilder;
9+
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\SecurityBuilder;
1110
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\CallbacksBuilder;
11+
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\ResponsesBuilder;
12+
use Vyuldashev\LaravelOpenApi\Attributes\Operation as OperationAttribute;
1213
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\ParametersBuilder;
14+
use GoldSpecDigital\ObjectOrientedOAS\Exceptions\InvalidArgumentException;
1315
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\RequestBodyBuilder;
14-
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\ResponsesBuilder;
15-
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\SecurityBuilder;
16-
use Vyuldashev\LaravelOpenApi\RouteInformation;
1716

1817
class OperationsBuilder
1918
{
@@ -73,8 +72,14 @@ public function build(array | Collection $routes): array
7372
->parameters(...$parameters)
7473
->requestBody($requestBody)
7574
->responses(...$responses)
76-
->callbacks(...$callbacks)
77-
->security(...$security);
75+
->callbacks(...$callbacks);
76+
77+
/** Not the cleanest code, we need to call notSecurity instead of security when our security has been turned off */
78+
if (count($security) === 1 && $security[0]->securityScheme === null) {
79+
$operation = $operation->noSecurity();
80+
} else {
81+
$operation = $operation->security(...$security);
82+
}
7883

7984
$this->extensionsBuilder->build($operation, $route->actionAttributes);
8085

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
namespace Vyuldashev\LaravelOpenApi\Tests\Builders;
3+
4+
use phpDocumentor\Reflection\DocBlock;
5+
use Vyuldashev\LaravelOpenApi\Tests\TestCase;
6+
use GoldSpecDigital\ObjectOrientedOAS\OpenApi;
7+
use Vyuldashev\LaravelOpenApi\RouteInformation;
8+
use GoldSpecDigital\ObjectOrientedOAS\Objects\PathItem;
9+
use GoldSpecDigital\ObjectOrientedOAS\Objects\Operation;
10+
use GoldSpecDigital\ObjectOrientedOAS\Objects\Components;
11+
use GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityScheme;
12+
use Vyuldashev\LaravelOpenApi\Factories\SecuritySchemeFactory;
13+
use Vyuldashev\LaravelOpenApi\Builders\Paths\OperationsBuilder;
14+
use GoldSpecDigital\ObjectOrientedOAS\Objects\SecurityRequirement;
15+
use Vyuldashev\LaravelOpenApi\Builders\Paths\Operation\SecurityBuilder;
16+
use Vyuldashev\LaravelOpenApi\Attributes\Operation as AttributesOperation;
17+
18+
class SecurityBuilderTest extends TestCase
19+
{
20+
/**
21+
* We're just making sure we're getting the expected output
22+
*/
23+
public function testWeCanBuildUpTheSecurityScheme(): void
24+
{
25+
$securityFactory = resolve(JwtSecurityScheme::class);
26+
$testJwtScheme = $securityFactory->build();
27+
28+
$globalRequirement = SecurityRequirement::create('JWT')
29+
->securityScheme($testJwtScheme);
30+
31+
$components = Components::create()
32+
->securitySchemes($testJwtScheme);
33+
34+
$operation = Operation::create()
35+
->action('get');
36+
37+
$openApi = OpenApi::create()
38+
->security($globalRequirement)
39+
->components($components)
40+
->paths(
41+
PathItem::create()
42+
->route('/foo')
43+
->operations($operation)
44+
);
45+
46+
self::assertSame([
47+
'paths' => [
48+
'/foo' => [
49+
'get' => [],
50+
],
51+
],
52+
'components' => [
53+
'securitySchemes' => [
54+
'JWT' => [
55+
'type' => 'http',
56+
'name' => 'TestScheme',
57+
'in' => 'header',
58+
'scheme' => 'bearer',
59+
'bearerFormat' => 'JWT',
60+
],
61+
],
62+
],
63+
'security' => [
64+
[
65+
'JWT' => [],
66+
]
67+
]
68+
], $openApi->toArray());
69+
}
70+
71+
/**
72+
* We're just verifying that the builder is capable of
73+
* adding security information to the operation
74+
*/
75+
public function testWeCanAddOperationSecurityUsingBuilder()
76+
{
77+
$securityFactory = resolve(JwtSecurityScheme::class);
78+
$testJwtScheme = $securityFactory->build();
79+
80+
$globalRequirement = SecurityRequirement::create('JWT')
81+
->securityScheme($testJwtScheme);
82+
83+
$components = Components::create()
84+
->securitySchemes($testJwtScheme);
85+
86+
$routeInfo = new RouteInformation;
87+
$routeInfo->action = 'get';
88+
$routeInfo->name = 'test route';
89+
$routeInfo->actionAttributes = collect([
90+
new AttributesOperation(security: JwtSecurityScheme::class),
91+
]);
92+
$routeInfo->uri = '/example';
93+
94+
/** @var SecurityBuilder */
95+
$builder = resolve(SecurityBuilder::class);
96+
97+
$operation = Operation::create()
98+
->security(...$builder->build($routeInfo))
99+
->action('get');
100+
101+
$openApi = OpenApi::create()
102+
->security($globalRequirement)
103+
->components($components)
104+
->paths(
105+
PathItem::create()
106+
->route('/foo')
107+
->operations($operation)
108+
);
109+
110+
self::assertSame([
111+
'paths' => [
112+
'/foo' => [
113+
'get' => [
114+
'security' => [
115+
[
116+
'JWT' => []
117+
],
118+
],
119+
],
120+
],
121+
],
122+
'components' => [
123+
'securitySchemes' => [
124+
'JWT' => [
125+
'type' => 'http',
126+
'name' => 'TestScheme',
127+
'in' => 'header',
128+
'scheme' => 'bearer',
129+
'bearerFormat' => 'JWT',
130+
],
131+
],
132+
],
133+
'security' => [
134+
[
135+
'JWT' => [],
136+
]
137+
]
138+
], $openApi->toArray());
139+
}
140+
141+
/**
142+
* He's the main part of the PR. It's not possible to turn
143+
* off security for an operation.
144+
*/
145+
public function testWeCanAddTurnOffOperationSecurityUsingBuilder()
146+
{
147+
$securityFactory = resolve(JwtSecurityScheme::class);
148+
$testJwtScheme = $securityFactory->build();
149+
150+
$globalRequirement = SecurityRequirement::create('JWT')
151+
->securityScheme($testJwtScheme);
152+
153+
$components = Components::create()
154+
->securitySchemes($testJwtScheme);
155+
156+
$routeInfo = new RouteInformation;
157+
$routeInfo->parameters = collect();
158+
$routeInfo->action = 'foo';
159+
$routeInfo->method = 'get';
160+
$routeInfo->name = 'test route';
161+
$routeInfo->actionDocBlock = new DocBlock('Test');
162+
$routeInfo->actionAttributes = collect([
163+
/**
164+
* we can set secuity to null to turn it off, as
165+
* that's the default value. So '' is next best
166+
* option?
167+
*/
168+
new AttributesOperation(security: ''),
169+
]);
170+
171+
/** @var OperationsBuilder */
172+
$operationsBuilder = resolve(OperationsBuilder::class);
173+
174+
$operations = $operationsBuilder->build([$routeInfo]);
175+
176+
$openApi = OpenApi::create()
177+
->security($globalRequirement)
178+
->components($components)
179+
->paths(
180+
PathItem::create()
181+
->route('/foo')
182+
->operations(...$operations)
183+
);
184+
185+
self::assertSame([
186+
'paths' => [
187+
'/foo' => [
188+
'get' => [
189+
'summary' => 'Test',
190+
'security' => [],
191+
],
192+
],
193+
],
194+
'components' => [
195+
'securitySchemes' => [
196+
'JWT' => [
197+
'type' => 'http',
198+
'name' => 'TestScheme',
199+
'in' => 'header',
200+
'scheme' => 'bearer',
201+
'bearerFormat' => 'JWT',
202+
],
203+
],
204+
],
205+
'security' => [
206+
[
207+
'JWT' => [],
208+
]
209+
]
210+
], $openApi->toArray());
211+
}
212+
}
213+
214+
class JwtSecurityScheme extends SecuritySchemeFactory
215+
{
216+
public function build(): SecurityScheme
217+
{
218+
return SecurityScheme::create('JWT')
219+
->name('TestScheme')
220+
->type(SecurityScheme::TYPE_HTTP)
221+
->in(SecurityScheme::IN_HEADER)
222+
->scheme('bearer')
223+
->bearerFormat('JWT');
224+
}
225+
}

0 commit comments

Comments
 (0)