Skip to content

Commit 9696845

Browse files
committed
Merge branch 'master' into bugfix/expires-property
# Conflicts: # Classes/Authorization.php # Classes/OAuthClient.php
2 parents aa2ff91 + ac4aabc commit 9696845

File tree

9 files changed

+414
-121
lines changed

9 files changed

+414
-121
lines changed

Classes/Authorization.php

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
*/
1515

1616
use Doctrine\ORM\Mapping as ORM;
17+
use Exception;
18+
use JsonException;
1719
use League\OAuth2\Client\Token\AccessToken;
1820
use League\OAuth2\Client\Token\AccessTokenInterface;
1921
use Neos\Flow\Annotations as Flow;
22+
use Ramsey\Uuid\Uuid;
2023

2124
/**
2225
* An OAuth2 Authorization
@@ -45,12 +48,6 @@ class Authorization
4548
*/
4649
protected $clientId;
4750

48-
/**
49-
* @var string
50-
* @ORM\Column(nullable = true, length=5000)
51-
*/
52-
protected $clientSecret;
53-
5451
/**
5552
* @var string
5653
*/
@@ -68,38 +65,59 @@ class Authorization
6865
protected $expires;
6966

7067
/**
71-
* @var array
72-
* @ORM\Column(type="json_array", nullable = true)
68+
* @var string
69+
* @ORM\Column(nullable = true, type = "text")
7370
*/
7471
protected $serializedAccessToken;
7572

7673
/**
74+
* @param string $authorizationId
7775
* @param string $serviceName
7876
* @param string $clientId
7977
* @param string $grantType
8078
* @param string $scope
8179
*/
82-
public function __construct(string $serviceName, string $clientId, string $grantType, string $scope)
80+
public function __construct(string $authorizationId, string $serviceName, string $clientId, string $grantType, string $scope)
8381
{
84-
$this->authorizationId = self::calculateAuthorizationId($serviceName, $clientId, $scope, $grantType);
82+
$this->authorizationId = $authorizationId;
8583
$this->serviceName = $serviceName;
8684
$this->clientId = $clientId;
8785
$this->grantType = $grantType;
8886
$this->scope = $scope;
8987
}
9088

89+
/**
90+
* Calculate an authorization identifier (for this model) from the given parameters.
91+
*
92+
* @param string $serviceType
93+
* @param string $serviceName
94+
* @param string $clientId
95+
* @return string
96+
* @throws OAuthClientException
97+
*/
98+
public static function generateAuthorizationIdForAuthorizationCodeGrant(string $serviceType, string $serviceName, string $clientId): string
99+
{
100+
try {
101+
return $serviceType . '-' . $serviceName . '-' . Uuid::uuid4()->toString();
102+
// @codeCoverageIgnoreStart
103+
} catch (Exception $e) {
104+
throw new OAuthClientException(sprintf('Failed generating authorization id for %s %s', $serviceName, $clientId), 1597311416, $e);
105+
}
106+
// @codeCoverageIgnoreEnd
107+
}
108+
91109
/**
92110
* Calculate an authorization identifier (for this model) from the given parameters.
93111
*
94112
* @param string $serviceName
95113
* @param string $clientId
114+
* @param string $clientSecret
96115
* @param string $scope
97-
* @param string $grantType
98116
* @return string
99117
*/
100-
public static function calculateAuthorizationId(string $serviceName, string $clientId, string $scope, string $grantType): string
118+
public static function generateAuthorizationIdForClientCredentialsGrant(string $serviceName, string $clientId, string $clientSecret, string $scope): string
101119
{
102-
return sha1($serviceName . $clientId . $scope . $grantType);
120+
return hash('sha512', $serviceName . $clientId . $clientSecret . $scope . self::GRANT_CLIENT_CREDENTIALS);
103121
}
104122

105123
/**
@@ -126,22 +144,6 @@ public function getClientId(): string
126144
return $this->clientId;
127145
}
128146

129-
/**
130-
* @param string $clientSecret
131-
*/
132-
public function setClientSecret(string $clientSecret): void
133-
{
134-
$this->clientSecret = $clientSecret;
135-
}
136-
137-
/**
138-
* @return string
139-
*/
140-
public function getClientSecret(): string
141-
{
142-
return $this->clientSecret;
143-
}
144-
145147
/**
146148
* @return string
147149
*/
@@ -168,42 +170,56 @@ public function setScope(string $scope): void
168170
}
169171

170172
/**
171-
* @return array
173+
* @return string
172174
*/
173-
public function getSerializedAccessToken(): array
175+
public function getSerializedAccessToken(): string
174176
{
175-
return $this->serializedAccessToken ?? [];
177+
return $this->serializedAccessToken ?? '';
176178
}
177179

178180
/**
179-
* @param array $serializedAccessToken
181+
* @param string $serializedAccessToken
180182
*/
181-
public function setSerializedAccessToken(array $serializedAccessToken): void
183+
public function setSerializedAccessToken(string $serializedAccessToken): void
182184
{
183185
$this->serializedAccessToken = $serializedAccessToken;
184186
}
185187

186188
/**
187189
* @param AccessTokenInterface $accessToken
188190
* @return void
191+
* @throws \InvalidArgumentException
189192
*/
190193
public function setAccessToken(AccessTokenInterface $accessToken): void
191194
{
192-
$this->serializedAccessToken = $accessToken->jsonSerialize();
195+
try {
196+
$this->serializedAccessToken = json_encode($accessToken, JSON_THROW_ON_ERROR, 512);
197+
// @codeCoverageIgnoreStart
198+
} catch (JsonException $e) {
199+
throw new \InvalidArgumentException('Failed serializing the given access token', 1602515717);
200+
// @codeCoverageIgnoreEnd
201+
}
193202
}
194203

195204
/**
196205
* @return AccessToken
197206
*/
198207
public function getAccessToken(): ?AccessToken
199208
{
200-
return !empty($this->serializedAccessToken) ? new AccessToken($this->serializedAccessToken) : null;
209+
try {
210+
if (!empty($this->serializedAccessToken)) {
211+
$unserializedAccessToken = json_decode($this->serializedAccessToken, true, 512, JSON_THROW_ON_ERROR);
212+
return new AccessToken($unserializedAccessToken);
213+
}
214+
} catch (JsonException $e) {
215+
}
216+
return null;
201217
}
202218

203219
/**
204220
* @return \DateTimeImmutable
205221
*/
206-
public function getExpires(): \DateTimeImmutable
222+
public function getExpires(): ?\DateTimeImmutable
207223
{
208224
return $this->expires;
209225
}

Classes/Controller/OAuthController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
use Flownative\OAuth2\Client\OAuthClient;
55
use Flownative\OAuth2\Client\OAuthClientException;
6+
use GuzzleHttp\Psr7\Uri;
67
use Neos\Flow\Annotations\CompileStatic;
7-
use Neos\Flow\Http\Uri;
88
use Neos\Flow\Mvc\Controller\ActionController;
99
use Neos\Flow\Mvc\Exception\StopActionException;
1010
use Neos\Flow\Mvc\Exception\UnsupportedRequestTypeException;

Classes/OAuthClient.php

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66
use Doctrine\ORM\ORMException;
77
use GuzzleHttp\Client;
88
use GuzzleHttp\Exception\GuzzleException;
9-
use GuzzleHttp\Psr7\Request;
109
use GuzzleHttp\Psr7\Response;
1110
use GuzzleHttp\Psr7\Uri;
1211
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
1312
use League\OAuth2\Client\Provider\GenericProvider;
14-
use League\OAuth2\Client\Token\AccessTokenInterface;
1513
use League\OAuth2\Client\Tool\RequestFactory;
1614
use Neos\Cache\Exception;
1715
use Neos\Cache\Frontend\VariableFrontend;
@@ -40,7 +38,7 @@ abstract class OAuthClient
4038
*
4139
* @const string
4240
*/
43-
public const AUTHORIZATION_ID_QUERY_PARAMETER_NAME = 'flownative_oauth2_authorization_id';
41+
public const AUTHORIZATION_ID_QUERY_PARAMETER_NAME_PREFIX = 'flownative_oauth2_authorization_id';
4442

4543
/**
4644
* @var string
@@ -125,14 +123,14 @@ public function injectEntityManager(EntityManagerInterface $entityManager): void
125123
/**
126124
* Returns the service type, i.e. a specific implementation of this client to use
127125
*
128-
* @return string For example, "FlownativeBeach", "oidc", ...
126+
* @return string For example, "Github", "oidc", ...
129127
*/
130128
abstract public function getServiceType(): string;
131129

132130
/**
133131
* Returns the service name, i.e. something like an instance name of the concrete implementation of this client
134132
*
135-
* @return string For example, "Github", "MySpecialService", ...
133+
* @return string For example, "SpecificGithubConnection", "MySpecialService", ...
136134
*/
137135
public function getServiceName(): string
138136
{
@@ -200,25 +198,36 @@ public function getRequestFactory(): RequestFactory
200198
}
201199

202200
/**
203-
* Requests an access token using a specified grant type.
201+
* Generates the URL query parameter name which is used for passing the authorization id of a
202+
* finishing authorization to Flow (via the "Return URL").
204203
*
205-
* This method is usually used using the OAuth Client Credentials Flow for machine-to-machine applications.
206-
* Therefore the grant type is usually Authorization::GRANT_CLIENT_CREDENTIALS. You need to specify the
204+
* @param string $serviceType "Class" of the of the service, for example, "Github", "oidc", ...
205+
* @return string
206+
*/
207+
public static function generateAuthorizationIdQueryParameterName(string $serviceType): string
208+
{
209+
return self::AUTHORIZATION_ID_QUERY_PARAMETER_NAME_PREFIX . '_' . $serviceType;
210+
}
211+
212+
/**
213+
* Requests an access token.
214+
*
215+
* This method is used using the OAuth Client Credentials Flow for machine-to-machine applications.
216+
* Therefore the grant type must be Authorization::GRANT_CLIENT_CREDENTIALS. You need to specify the
207217
* client identifier and client secret and may optionally specify a scope.
208218
*
209219
* @param string $serviceName
210220
* @param string $clientId Client ID
211221
* @param string $clientSecret Client Secret
212222
* @param string $scope Scope which may consist of multiple identifiers, separated by comma
213-
* @param string $grantType One of the Authorization::GRAND_* constants
214223
* @param array $additionalParameters Additional parameters to provide in the request body while requesting the token. For example ['audience' => 'https://www.example.com/api/v1']
215224
* @return void
216225
* @throws IdentityProviderException
217226
*/
218-
public function requestAccessToken(string $serviceName, string $clientId, string $clientSecret, string $scope, string $grantType, array $additionalParameters = []): void
227+
public function requestAccessToken(string $serviceName, string $clientId, string $clientSecret, string $scope, array $additionalParameters = []): void
219228
{
220-
$authorizationId = Authorization::calculateAuthorizationId($serviceName, $clientId, $scope, $grantType);
221-
$this->logger->info(sprintf('OAuth (%s): Retrieving access token using %s grant for client "%s" using a %s bytes long secret. (authorization id: %s)', $this->getServiceType(), $grantType, $clientId, strlen($clientSecret), $authorizationId), LogEnvironment::fromMethodName(__METHOD__));
229+
$authorizationId = Authorization::generateAuthorizationIdForClientCredentialsGrant($serviceName, $clientId, $clientSecret, $scope);
230+
$this->logger->info(sprintf('OAuth (%s): Retrieving access token using client credentials grant for client "%s" using a %s bytes long secret. (authorization id: %s)', $this->getServiceType(), $clientId, strlen($clientSecret), $authorizationId));
222231

223232
$existingAuthorization = $this->getAuthorization($authorizationId);
224233
if ($existingAuthorization !== null) {
@@ -228,9 +237,9 @@ public function requestAccessToken(string $serviceName, string $clientId, string
228237
$this->logger->info(sprintf('OAuth (%s): Removed old OAuth token for client "%s". (authorization id: %s)', $this->getServiceType(), $clientId, $authorizationId), LogEnvironment::fromMethodName(__METHOD__));
229238
}
230239

231-
$accessToken = $this->createOAuthProvider($clientId, $clientSecret)->getAccessToken($grantType, $additionalParameters);
232-
233-
$authorization = $this->createNewAuthorization($serviceName, $clientId, $scope, $grantType, $accessToken);
240+
$accessToken = $this->createOAuthProvider($clientId, $clientSecret)->getAccessToken(Authorization::GRANT_CLIENT_CREDENTIALS, $additionalParameters);
241+
$authorization = new Authorization($authorizationId, $serviceName, $clientId, Authorization::GRANT_CLIENT_CREDENTIALS, $scope);
242+
$authorization->setAccessToken($accessToken);
234243

235244
$this->logger->info(sprintf('OAuth (%s): Persisted new OAuth authorization %s for client "%s" with expiry time %s. (authorization id: %s)', $this->getServiceType(), $authorizationId, $clientId, $accessToken->getExpires(), $authorizationId), LogEnvironment::fromMethodName(__METHOD__));
236245

@@ -250,15 +259,15 @@ public function requestAccessToken(string $serviceName, string $clientId, string
250259
*/
251260
public function startAuthorization(string $clientId, string $clientSecret, UriInterface $returnToUri, string $scope): UriInterface
252261
{
253-
$authorization = new Authorization($this->getServiceType(), $clientId, Authorization::GRANT_AUTHORIZATION_CODE, $scope);
254-
$this->logger->info(sprintf('OAuth (%s): Starting authorization %s using client id "%s", a %s bytes long secret and scope "%s".', $this->getServiceType(), $authorization->getAuthorizationId(), $clientId, strlen($clientSecret), $scope), LogEnvironment::fromMethodName(__METHOD__));
262+
$authorizationId = Authorization::generateAuthorizationIdForAuthorizationCodeGrant($this->getServiceType(), $this->getServiceName(), $clientId);
263+
$authorization = new Authorization($authorizationId, $this->getServiceType(), $clientId, Authorization::GRANT_AUTHORIZATION_CODE, $scope);
264+
$this->logger->info(sprintf('OAuth (%s): Starting authorization %s using client id "%s", a %s bytes long secret and scope "%s".', $this->getServiceType(), $authorization->getAuthorizationId(), $clientId, strlen($clientSecret), $scope));
255265

256266
try {
257267
$oldAuthorization = $this->entityManager->find(Authorization::class, $authorization->getAuthorizationId());
258268
if ($oldAuthorization !== null) {
259269
$authorization = $oldAuthorization;
260270
}
261-
$authorization->setClientSecret($clientSecret);
262271
$this->entityManager->persist($authorization);
263272
$this->entityManager->flush();
264273
} catch (ORMException $exception) {
@@ -317,6 +326,10 @@ public function finishAuthorization(string $stateIdentifier, string $code, strin
317326
throw new OAuthClientException(sprintf('OAuth2 (%s): Finishing authorization failed because authorization %s could not be retrieved from the database.', $this->getServiceType(), $authorizationId), 1568710771);
318327
}
319328

329+
if ($authorization->getGrantType() !== Authorization::GRANT_AUTHORIZATION_CODE) {
330+
throw new OAuthClientException(sprintf('OAuth2 (%s): Finishing authorization failed because authorization %s does not have the authorization code flow type!', $this->getServiceType(), $authorizationId), 1597312780);
331+
}
332+
320333
$this->logger->debug(sprintf('OAuth (%s): Retrieving an OAuth access token for authorization "%s" in exchange for the code %s', $this->getServiceType(), $authorizationId, str_repeat('*', strlen($code) - 3) . substr($code, -3, 3)));
321334
$accessToken = $oAuthProvider->getAccessToken(Authorization::GRANT_AUTHORIZATION_CODE, ['code' => $code]);
322335
$this->logger->info(sprintf('OAuth (%s): Persisting OAuth token for authorization "%s" with expiry time %s.', $this->getServiceType(), $authorizationId, $accessToken->getExpires()));
@@ -335,7 +348,7 @@ public function finishAuthorization(string $stateIdentifier, string $code, strin
335348
}
336349

337350
$returnToUri = new Uri($stateFromCache['returnToUri']);
338-
$returnToUri = $returnToUri->withQuery(trim($returnToUri->getQuery() . '&' . self::AUTHORIZATION_ID_QUERY_PARAMETER_NAME . '=' . $authorizationId, '&'));
351+
$returnToUri = $returnToUri->withQuery(trim($returnToUri->getQuery() . '&' . self::generateAuthorizationIdQueryParameterName($this->getServiceType()) . '=' . $authorizationId, '&'));
339352

340353
$this->logger->debug(sprintf('OAuth (%s): Finished authorization "%s", $returnToUri is %s.', $this->getServiceType(), $authorizationId, $returnToUri));
341354
return $returnToUri;
@@ -352,11 +365,13 @@ public function finishAuthorization(string $stateIdentifier, string $code, strin
352365
*/
353366
public function refreshAuthorization(string $authorizationId, string $clientId, string $returnToUri): string
354367
{
368+
throw new OAuthClientException('refreshAuthorization is currently not implemented', 1602519541);
369+
355370
$authorization = $this->entityManager->find(Authorization::class, ['authorizationId' => $authorizationId]);
356371
if (!$authorization instanceof Authorization) {
357372
throw new OAuthClientException(sprintf('OAuth2: Could not refresh OAuth token because authorization %s was not found in our database.', $authorization), 1505317044316);
358373
}
359-
$oAuthProvider = $this->createOAuthProvider($clientId, $authorization->getClientSecret());
374+
$oAuthProvider = $this->createOAuthProvider($clientId, $authorization->getClientSecret()); // FIXME
360375

361376
$this->logger->info(sprintf('OAuth (%s): Refreshing authorization %s for client "%s" using a %s bytes long secret and refresh token "%s".', $this->getServiceType(), $authorizationId, $clientId, strlen($authorization->getClientSecret()), $authorization->refreshToken));
362377

@@ -377,6 +392,8 @@ public function refreshAuthorization(string $authorizationId, string $clientId,
377392
}
378393

379394
/**
395+
* Returns the specified Authorization record, if it exists
396+
*
380397
* @param string $authorizationId
381398
* @return Authorization|null
382399
*/
@@ -465,23 +482,6 @@ public function renderFinishAuthorizationUri(): string
465482
}
466483
}
467484

468-
/**
469-
* Create a new OAuthToken instance
470-
*
471-
* @param string $serviceName
472-
* @param string $clientId
473-
* @param string $scope
474-
* @param string $grantType
475-
* @param AccessTokenInterface $accessToken
476-
* @return Authorization
477-
*/
478-
protected function createNewAuthorization(string $serviceName, string $clientId, string $scope, string $grantType, AccessTokenInterface $accessToken): Authorization
479-
{
480-
$authorization = new Authorization($serviceName, $clientId, $grantType, $scope);
481-
$authorization->setAccessToken($accessToken);
482-
return $authorization;
483-
}
484-
485485
/**
486486
* @param string $clientId
487487
* @param string $clientSecret

0 commit comments

Comments
 (0)