Skip to content

Commit

Permalink
Merge pull request #61 from DoclerLabs/add-bearer-authentication
Browse files Browse the repository at this point in the history
Add bearer authentication
  • Loading branch information
vsouz4 authored Nov 23, 2021
2 parents 12615bb + 82355b5 commit 29e9dd8
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [8.0.0] - 2021-11-19
### Added
- Bearer Authentication strategy added

## [7.2.3] - 2021-11-03
### Fixed
- Request parameters with same name but different $ref should not override each other
Expand Down
10 changes: 9 additions & 1 deletion src/Entity/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ class Operation
private Response $successfulResponse;
private array $errorResponses;
private array $tags;
private array $security;

public function __construct(
string $name,
string $description,
Request $request,
Response $successfulResponse,
array $errorResponses = [],
array $tags = []
array $tags = [],
array $security = []
) {
$this->name = $name;
$this->description = $description;
$this->request = $request;
$this->successfulResponse = $successfulResponse;
$this->errorResponses = $errorResponses;
$this->tags = $tags;
$this->security = $security;
}

public function getName(): string
Expand Down Expand Up @@ -58,4 +61,9 @@ public function getTags(): array
{
return $this->tags;
}

public function getSecurity(): array
{
return $this->security;
}
}
80 changes: 65 additions & 15 deletions src/Generator/RequestGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace DoclerLabs\ApiClientGenerator\Generator;

use DoclerLabs\ApiClientGenerator\Ast\Builder\CodeBuilder;
use DoclerLabs\ApiClientGenerator\Ast\ParameterNode;
use DoclerLabs\ApiClientGenerator\Entity\Field;
use DoclerLabs\ApiClientGenerator\Entity\Operation;
Expand All @@ -21,29 +22,45 @@ class RequestGenerator extends MutatorAccessorClassGeneratorAbstract
public const NAMESPACE_SUBPATH = '\\Request';
public const SUBDIRECTORY = 'Request/';

/** @var SecurityStrategyInterface[] */
private array $securityStrategies;

public function __construct(
string $baseNamespace,
CodeBuilder $builder,
SecurityStrategyInterface ...$securityStrategies
) {
parent::__construct($baseNamespace, $builder);

$this->securityStrategies = $securityStrategies;
}

public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
{
foreach ($specification->getOperations() as $operation) {
$this->generateRequest($fileRegistry, $operation);
$this->generateRequest($fileRegistry, $operation, $specification);
}
}

protected function generateRequest(PhpFileCollection $fileRegistry, Operation $operation): void
{
protected function generateRequest(
PhpFileCollection $fileRegistry,
Operation $operation,
Specification $specification
): void {
$className = RequestNaming::getClassName($operation);
$request = $operation->getRequest();

$classBuilder = $this->builder
->class($className)
->implement('RequestInterface')
->addStmts($this->generateEnums($request))
->addStmts($this->generateProperties($request))
->addStmt($this->generateConstructor($request))
->addStmts($this->generateProperties($request, $operation, $specification))
->addStmt($this->generateConstructor($request, $operation, $specification))
->addStmt($this->generateGetContentType())
->addStmts($this->generateSetters($request))
->addStmt($this->generateGetMethod($request))
->addStmt($this->generateGetRoute($request))
->addStmts($this->generateGetParametersMethods($request));
->addStmts($this->generateGetParametersMethods($request, $operation, $specification));

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

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

foreach ($this->securityStrategies as $securityStrategy) {
/** @var SecurityStrategyInterface $securityStrategy */
array_push($statements, ...$securityStrategy->getProperties($operation, $specification));
}

return $statements;
}

protected function generateConstructor(Request $request): ?ClassMethod
{
protected function generateConstructor(
Request $request,
Operation $operation,
Specification $specification
): ?ClassMethod {
$params = [];
$paramInits = [];
foreach ($request->getFields() as $fields) {
Expand All @@ -117,6 +142,12 @@ protected function generateConstructor(Request $request): ?ClassMethod
}
}

foreach ($this->securityStrategies as $securityStrategy) {
/** @var SecurityStrategyInterface $securityStrategy */
array_push($params, ...$securityStrategy->getConstructorParams($operation, $specification));
array_push($paramInits, ...$securityStrategy->getConstructorParamInits($operation, $specification));
}

if (count($request->getBodyContentTypes()) > 1) {
$contentTypeVariableName = 'contentType';

Expand Down Expand Up @@ -224,8 +255,11 @@ private function generateGetRoute(Request $request): ClassMethod
->getNode();
}

private function generateGetParametersMethods(Request $request): array
{
private function generateGetParametersMethods(
Request $request,
Operation $operation,
Specification $specification
): array {
$methods = [];
$fields = $request->getFields();
$methods[] = $this->generateGetParametersMethod(
Expand All @@ -240,7 +274,7 @@ private function generateGetParametersMethods(Request $request): array
'getCookies',
$fields->getCookieFields()
);
$methods[] = $this->generateGetHeadersMethod($request, $fields->getHeaderFields());
$methods[] = $this->generateGetHeadersMethod($request, $fields->getHeaderFields(), $operation, $specification);
$methods[] = $this->generateGetBody($fields->getBody());

return $methods;
Expand Down Expand Up @@ -305,9 +339,13 @@ private function generateGetBody(?Field $body): ClassMethod
->getNode();
}

private function generateGetHeadersMethod(Request $request, array $fields): ClassMethod
{
$headers = [];
private function generateGetHeadersMethod(
Request $request,
array $fields,
Operation $operation,
Specification $specification
): ClassMethod {
$headers = $this->getSecurityHeaders($operation, $specification);
if (!empty($request->getBodyContentTypes())) {
$headers['Content-Type'] = $this->builder->localPropertyFetch('contentType');
}
Expand All @@ -334,6 +372,18 @@ private function generateGetHeadersMethod(Request $request, array $fields): Clas
->getNode();
}

private function getSecurityHeaders(Operation $operation, Specification $specification): array
{
$headers = [];

foreach ($this->securityStrategies as $securityStrategy) {
/** @var SecurityStrategyInterface $securityStrategy */
$headers += $securityStrategy->getSecurityHeaders($operation, $specification);
}

return $headers;
}

private function generateParametersFromFields(array $fields): FuncCall
{
$filterCallbackBody = $this->builder->return(
Expand Down
120 changes: 120 additions & 0 deletions src/Generator/Security/BearerAuthentication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

declare(strict_types=1);

namespace DoclerLabs\ApiClientGenerator\Generator\Security;

use cebe\openapi\spec\SecurityRequirement;
use cebe\openapi\spec\SecurityScheme;
use DoclerLabs\ApiClientGenerator\Ast\Builder\CodeBuilder;
use DoclerLabs\ApiClientGenerator\Entity\Operation;
use DoclerLabs\ApiClientGenerator\Generator\SecurityStrategyInterface;
use DoclerLabs\ApiClientGenerator\Input\Specification;

class BearerAuthentication implements SecurityStrategyInterface
{
private const PROPERTY_NAME = 'bearerToken';
private const SCHEME = 'bearer';
private const TYPE = 'http';

private CodeBuilder $builder;

public function __construct(CodeBuilder $builder)
{
$this->builder = $builder;
}

public function getProperties(Operation $operation, Specification $specification): array
{
$statements = [];

if ($this->requiresBearerToken($operation, $specification)) {
$statements[] = $this->builder->localProperty('bearerToken', 'string', 'string');
}

return $statements;
}

public function getConstructorParams(Operation $operation, Specification $specification): array
{
$params = [];

if ($this->requiresBearerToken($operation, $specification)) {
$params[] = $this->builder
->param(self::PROPERTY_NAME)
->setType('string')
->getNode();
}

return $params;
}

public function getConstructorParamInits(Operation $operation, Specification $specification): array
{
$paramInits = [];

if ($this->requiresBearerToken($operation, $specification)) {
$paramInits[] = $this->builder->assign(
$this->builder->localPropertyFetch(self::PROPERTY_NAME),
$this->builder->var(self::PROPERTY_NAME)
);
}

return $paramInits;
}

public function getSecurityHeaders(Operation $operation, Specification $specification): array
{
$headers = [];

foreach ($this->loopSecuritySchemes($operation, $specification) as $securityScheme) {
/** @var SecurityScheme $securityScheme */
if (
$securityScheme->scheme === self::SCHEME
&& $securityScheme->type === self::TYPE
) {
$headers['Authorization'] = $this->builder->funcCall(
'sprintf',
['Bearer %s', $this->builder->localPropertyFetch(self::PROPERTY_NAME)]
);
}
}

return $headers;
}

private function requiresBearerToken(Operation $operation, Specification $specification): bool
{
foreach ($this->loopSecuritySchemes($operation, $specification) as $securityScheme) {
/** @var SecurityScheme $securityScheme */
if (
$securityScheme->scheme === self::SCHEME
&& $securityScheme->type === self::TYPE
) {
return true;
}
}

return false;
}

private function loopSecuritySchemes(Operation $operation, Specification $specification): iterable
{
if (
!empty($specification->getSecuritySchemes())
&& !empty($operation->getSecurity())
) {
/** @var SecurityScheme $securityScheme */
foreach ($specification->getSecuritySchemes() as $name => $securityScheme) {
/** @var SecurityRequirement $securityRequirement */
foreach ($operation->getSecurity() as $securityRequirement) {
if (isset($securityRequirement->$name)) {
yield $securityScheme;
}
}
}
}

yield from [];
}
}
19 changes: 19 additions & 0 deletions src/Generator/SecurityStrategyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace DoclerLabs\ApiClientGenerator\Generator;

use DoclerLabs\ApiClientGenerator\Entity\Operation;
use DoclerLabs\ApiClientGenerator\Input\Specification;

interface SecurityStrategyInterface
{
public function getProperties(Operation $operation, Specification $specification): array;

public function getConstructorParams(Operation $operation, Specification $specification): array;

public function getConstructorParamInits(Operation $operation, Specification $specification): array;

public function getSecurityHeaders(Operation $operation, Specification $specification): array;
}
3 changes: 2 additions & 1 deletion src/Input/Factory/OperationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public function create(
$this->requestMapper->create($operationId, $path, $method, $parameters, $requestBody),
$this->responseMapper->createSuccessful($operationId, $operation->responses->getResponses()),
$this->responseMapper->createPossibleErrors($operation->responses->getResponses()),
$operation->tags
$operation->tags,
$operation->security ?? []
);
} catch (Throwable $exception) {
throw new InvalidSpecificationException(
Expand Down
16 changes: 16 additions & 0 deletions src/Input/Specification.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace DoclerLabs\ApiClientGenerator\Input;

use cebe\openapi\spec\OpenApi;
use cebe\openapi\spec\Reference;
use cebe\openapi\spec\SecurityScheme;
use DoclerLabs\ApiClientGenerator\Entity\Constraint\ConstraintInterface;
use DoclerLabs\ApiClientGenerator\Entity\Constraint\MaxLengthConstraint;
use DoclerLabs\ApiClientGenerator\Entity\Constraint\MinLengthConstraint;
Expand Down Expand Up @@ -83,6 +85,20 @@ public function getCompositeResponseFields(): FieldCollection
return $this->compositeResponseFields;
}

/**
* @return SecurityScheme[]
*/
public function getSecuritySchemes(): array
{
return array_map(static function ($securityScheme): SecurityScheme {
if ($securityScheme instanceof Reference) {
$securityScheme = $securityScheme->resolve();
}

return $securityScheme;
}, $this->openApi->components->securitySchemes ?? []);
}

public function getAllContentTypes(): array
{
$allContentTypes = [
Expand Down
Loading

0 comments on commit 29e9dd8

Please sign in to comment.