Skip to content

Commit 29e9dd8

Browse files
authored
Merge pull request #61 from DoclerLabs/add-bearer-authentication
Add bearer authentication
2 parents 12615bb + 82355b5 commit 29e9dd8

File tree

10 files changed

+253
-19
lines changed

10 files changed

+253
-19
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [8.0.0] - 2021-11-19
8+
### Added
9+
- Bearer Authentication strategy added
10+
711
## [7.2.3] - 2021-11-03
812
### Fixed
913
- Request parameters with same name but different $ref should not override each other

src/Entity/Operation.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@ class Operation
1212
private Response $successfulResponse;
1313
private array $errorResponses;
1414
private array $tags;
15+
private array $security;
1516

1617
public function __construct(
1718
string $name,
1819
string $description,
1920
Request $request,
2021
Response $successfulResponse,
2122
array $errorResponses = [],
22-
array $tags = []
23+
array $tags = [],
24+
array $security = []
2325
) {
2426
$this->name = $name;
2527
$this->description = $description;
2628
$this->request = $request;
2729
$this->successfulResponse = $successfulResponse;
2830
$this->errorResponses = $errorResponses;
2931
$this->tags = $tags;
32+
$this->security = $security;
3033
}
3134

3235
public function getName(): string
@@ -58,4 +61,9 @@ public function getTags(): array
5861
{
5962
return $this->tags;
6063
}
64+
65+
public function getSecurity(): array
66+
{
67+
return $this->security;
68+
}
6169
}

src/Generator/RequestGenerator.php

+65-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace DoclerLabs\ApiClientGenerator\Generator;
66

7+
use DoclerLabs\ApiClientGenerator\Ast\Builder\CodeBuilder;
78
use DoclerLabs\ApiClientGenerator\Ast\ParameterNode;
89
use DoclerLabs\ApiClientGenerator\Entity\Field;
910
use DoclerLabs\ApiClientGenerator\Entity\Operation;
@@ -21,29 +22,45 @@ class RequestGenerator extends MutatorAccessorClassGeneratorAbstract
2122
public const NAMESPACE_SUBPATH = '\\Request';
2223
public const SUBDIRECTORY = 'Request/';
2324

25+
/** @var SecurityStrategyInterface[] */
26+
private array $securityStrategies;
27+
28+
public function __construct(
29+
string $baseNamespace,
30+
CodeBuilder $builder,
31+
SecurityStrategyInterface ...$securityStrategies
32+
) {
33+
parent::__construct($baseNamespace, $builder);
34+
35+
$this->securityStrategies = $securityStrategies;
36+
}
37+
2438
public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
2539
{
2640
foreach ($specification->getOperations() as $operation) {
27-
$this->generateRequest($fileRegistry, $operation);
41+
$this->generateRequest($fileRegistry, $operation, $specification);
2842
}
2943
}
3044

31-
protected function generateRequest(PhpFileCollection $fileRegistry, Operation $operation): void
32-
{
45+
protected function generateRequest(
46+
PhpFileCollection $fileRegistry,
47+
Operation $operation,
48+
Specification $specification
49+
): void {
3350
$className = RequestNaming::getClassName($operation);
3451
$request = $operation->getRequest();
3552

3653
$classBuilder = $this->builder
3754
->class($className)
3855
->implement('RequestInterface')
3956
->addStmts($this->generateEnums($request))
40-
->addStmts($this->generateProperties($request))
41-
->addStmt($this->generateConstructor($request))
57+
->addStmts($this->generateProperties($request, $operation, $specification))
58+
->addStmt($this->generateConstructor($request, $operation, $specification))
4259
->addStmt($this->generateGetContentType())
4360
->addStmts($this->generateSetters($request))
4461
->addStmt($this->generateGetMethod($request))
4562
->addStmt($this->generateGetRoute($request))
46-
->addStmts($this->generateGetParametersMethods($request));
63+
->addStmts($this->generateGetParametersMethods($request, $operation, $specification));
4764

4865
$this->registerFile($fileRegistry, $classBuilder, self::SUBDIRECTORY, self::NAMESPACE_SUBPATH);
4966
}
@@ -62,7 +79,7 @@ protected function generateEnums(Request $request): array
6279
return $statements;
6380
}
6481

65-
protected function generateProperties(Request $request): array
82+
protected function generateProperties(Request $request, Operation $operation, Specification $specification): array
6683
{
6784
$statements = [];
6885
foreach ($request->getFields() as $fields) {
@@ -87,11 +104,19 @@ protected function generateProperties(Request $request): array
87104
}
88105
$statements[] = $this->builder->localProperty('contentType', 'string', 'string', false, $default);
89106

107+
foreach ($this->securityStrategies as $securityStrategy) {
108+
/** @var SecurityStrategyInterface $securityStrategy */
109+
array_push($statements, ...$securityStrategy->getProperties($operation, $specification));
110+
}
111+
90112
return $statements;
91113
}
92114

93-
protected function generateConstructor(Request $request): ?ClassMethod
94-
{
115+
protected function generateConstructor(
116+
Request $request,
117+
Operation $operation,
118+
Specification $specification
119+
): ?ClassMethod {
95120
$params = [];
96121
$paramInits = [];
97122
foreach ($request->getFields() as $fields) {
@@ -117,6 +142,12 @@ protected function generateConstructor(Request $request): ?ClassMethod
117142
}
118143
}
119144

145+
foreach ($this->securityStrategies as $securityStrategy) {
146+
/** @var SecurityStrategyInterface $securityStrategy */
147+
array_push($params, ...$securityStrategy->getConstructorParams($operation, $specification));
148+
array_push($paramInits, ...$securityStrategy->getConstructorParamInits($operation, $specification));
149+
}
150+
120151
if (count($request->getBodyContentTypes()) > 1) {
121152
$contentTypeVariableName = 'contentType';
122153

@@ -224,8 +255,11 @@ private function generateGetRoute(Request $request): ClassMethod
224255
->getNode();
225256
}
226257

227-
private function generateGetParametersMethods(Request $request): array
228-
{
258+
private function generateGetParametersMethods(
259+
Request $request,
260+
Operation $operation,
261+
Specification $specification
262+
): array {
229263
$methods = [];
230264
$fields = $request->getFields();
231265
$methods[] = $this->generateGetParametersMethod(
@@ -240,7 +274,7 @@ private function generateGetParametersMethods(Request $request): array
240274
'getCookies',
241275
$fields->getCookieFields()
242276
);
243-
$methods[] = $this->generateGetHeadersMethod($request, $fields->getHeaderFields());
277+
$methods[] = $this->generateGetHeadersMethod($request, $fields->getHeaderFields(), $operation, $specification);
244278
$methods[] = $this->generateGetBody($fields->getBody());
245279

246280
return $methods;
@@ -305,9 +339,13 @@ private function generateGetBody(?Field $body): ClassMethod
305339
->getNode();
306340
}
307341

308-
private function generateGetHeadersMethod(Request $request, array $fields): ClassMethod
309-
{
310-
$headers = [];
342+
private function generateGetHeadersMethod(
343+
Request $request,
344+
array $fields,
345+
Operation $operation,
346+
Specification $specification
347+
): ClassMethod {
348+
$headers = $this->getSecurityHeaders($operation, $specification);
311349
if (!empty($request->getBodyContentTypes())) {
312350
$headers['Content-Type'] = $this->builder->localPropertyFetch('contentType');
313351
}
@@ -334,6 +372,18 @@ private function generateGetHeadersMethod(Request $request, array $fields): Clas
334372
->getNode();
335373
}
336374

375+
private function getSecurityHeaders(Operation $operation, Specification $specification): array
376+
{
377+
$headers = [];
378+
379+
foreach ($this->securityStrategies as $securityStrategy) {
380+
/** @var SecurityStrategyInterface $securityStrategy */
381+
$headers += $securityStrategy->getSecurityHeaders($operation, $specification);
382+
}
383+
384+
return $headers;
385+
}
386+
337387
private function generateParametersFromFields(array $fields): FuncCall
338388
{
339389
$filterCallbackBody = $this->builder->return(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoclerLabs\ApiClientGenerator\Generator\Security;
6+
7+
use cebe\openapi\spec\SecurityRequirement;
8+
use cebe\openapi\spec\SecurityScheme;
9+
use DoclerLabs\ApiClientGenerator\Ast\Builder\CodeBuilder;
10+
use DoclerLabs\ApiClientGenerator\Entity\Operation;
11+
use DoclerLabs\ApiClientGenerator\Generator\SecurityStrategyInterface;
12+
use DoclerLabs\ApiClientGenerator\Input\Specification;
13+
14+
class BearerAuthentication implements SecurityStrategyInterface
15+
{
16+
private const PROPERTY_NAME = 'bearerToken';
17+
private const SCHEME = 'bearer';
18+
private const TYPE = 'http';
19+
20+
private CodeBuilder $builder;
21+
22+
public function __construct(CodeBuilder $builder)
23+
{
24+
$this->builder = $builder;
25+
}
26+
27+
public function getProperties(Operation $operation, Specification $specification): array
28+
{
29+
$statements = [];
30+
31+
if ($this->requiresBearerToken($operation, $specification)) {
32+
$statements[] = $this->builder->localProperty('bearerToken', 'string', 'string');
33+
}
34+
35+
return $statements;
36+
}
37+
38+
public function getConstructorParams(Operation $operation, Specification $specification): array
39+
{
40+
$params = [];
41+
42+
if ($this->requiresBearerToken($operation, $specification)) {
43+
$params[] = $this->builder
44+
->param(self::PROPERTY_NAME)
45+
->setType('string')
46+
->getNode();
47+
}
48+
49+
return $params;
50+
}
51+
52+
public function getConstructorParamInits(Operation $operation, Specification $specification): array
53+
{
54+
$paramInits = [];
55+
56+
if ($this->requiresBearerToken($operation, $specification)) {
57+
$paramInits[] = $this->builder->assign(
58+
$this->builder->localPropertyFetch(self::PROPERTY_NAME),
59+
$this->builder->var(self::PROPERTY_NAME)
60+
);
61+
}
62+
63+
return $paramInits;
64+
}
65+
66+
public function getSecurityHeaders(Operation $operation, Specification $specification): array
67+
{
68+
$headers = [];
69+
70+
foreach ($this->loopSecuritySchemes($operation, $specification) as $securityScheme) {
71+
/** @var SecurityScheme $securityScheme */
72+
if (
73+
$securityScheme->scheme === self::SCHEME
74+
&& $securityScheme->type === self::TYPE
75+
) {
76+
$headers['Authorization'] = $this->builder->funcCall(
77+
'sprintf',
78+
['Bearer %s', $this->builder->localPropertyFetch(self::PROPERTY_NAME)]
79+
);
80+
}
81+
}
82+
83+
return $headers;
84+
}
85+
86+
private function requiresBearerToken(Operation $operation, Specification $specification): bool
87+
{
88+
foreach ($this->loopSecuritySchemes($operation, $specification) as $securityScheme) {
89+
/** @var SecurityScheme $securityScheme */
90+
if (
91+
$securityScheme->scheme === self::SCHEME
92+
&& $securityScheme->type === self::TYPE
93+
) {
94+
return true;
95+
}
96+
}
97+
98+
return false;
99+
}
100+
101+
private function loopSecuritySchemes(Operation $operation, Specification $specification): iterable
102+
{
103+
if (
104+
!empty($specification->getSecuritySchemes())
105+
&& !empty($operation->getSecurity())
106+
) {
107+
/** @var SecurityScheme $securityScheme */
108+
foreach ($specification->getSecuritySchemes() as $name => $securityScheme) {
109+
/** @var SecurityRequirement $securityRequirement */
110+
foreach ($operation->getSecurity() as $securityRequirement) {
111+
if (isset($securityRequirement->$name)) {
112+
yield $securityScheme;
113+
}
114+
}
115+
}
116+
}
117+
118+
yield from [];
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoclerLabs\ApiClientGenerator\Generator;
6+
7+
use DoclerLabs\ApiClientGenerator\Entity\Operation;
8+
use DoclerLabs\ApiClientGenerator\Input\Specification;
9+
10+
interface SecurityStrategyInterface
11+
{
12+
public function getProperties(Operation $operation, Specification $specification): array;
13+
14+
public function getConstructorParams(Operation $operation, Specification $specification): array;
15+
16+
public function getConstructorParamInits(Operation $operation, Specification $specification): array;
17+
18+
public function getSecurityHeaders(Operation $operation, Specification $specification): array;
19+
}

src/Input/Factory/OperationFactory.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public function create(
5959
$this->requestMapper->create($operationId, $path, $method, $parameters, $requestBody),
6060
$this->responseMapper->createSuccessful($operationId, $operation->responses->getResponses()),
6161
$this->responseMapper->createPossibleErrors($operation->responses->getResponses()),
62-
$operation->tags
62+
$operation->tags,
63+
$operation->security ?? []
6364
);
6465
} catch (Throwable $exception) {
6566
throw new InvalidSpecificationException(

src/Input/Specification.php

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace DoclerLabs\ApiClientGenerator\Input;
66

77
use cebe\openapi\spec\OpenApi;
8+
use cebe\openapi\spec\Reference;
9+
use cebe\openapi\spec\SecurityScheme;
810
use DoclerLabs\ApiClientGenerator\Entity\Constraint\ConstraintInterface;
911
use DoclerLabs\ApiClientGenerator\Entity\Constraint\MaxLengthConstraint;
1012
use DoclerLabs\ApiClientGenerator\Entity\Constraint\MinLengthConstraint;
@@ -83,6 +85,20 @@ public function getCompositeResponseFields(): FieldCollection
8385
return $this->compositeResponseFields;
8486
}
8587

88+
/**
89+
* @return SecurityScheme[]
90+
*/
91+
public function getSecuritySchemes(): array
92+
{
93+
return array_map(static function ($securityScheme): SecurityScheme {
94+
if ($securityScheme instanceof Reference) {
95+
$securityScheme = $securityScheme->resolve();
96+
}
97+
98+
return $securityScheme;
99+
}, $this->openApi->components->securitySchemes ?? []);
100+
}
101+
86102
public function getAllContentTypes(): array
87103
{
88104
$allContentTypes = [

0 commit comments

Comments
 (0)