Skip to content

Commit 4aaa117

Browse files
authored
Merge pull request #29 from flownative/feature/authorization-metadata-for-v2
Support for Authorization metadata
2 parents f3d26ad + 177635e commit 4aaa117

File tree

4 files changed

+140
-7
lines changed

4 files changed

+140
-7
lines changed

Classes/Authorization.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ class Authorization
7777
*/
7878
protected $encryptedSerializedAccessToken;
7979

80+
/**
81+
* @var string
82+
* @ORM\Column(nullable = true, type = "text")
83+
*/
84+
protected $metadata;
85+
8086
/**
8187
* @Flow\Transient
8288
* @var EncryptionService
@@ -287,4 +293,14 @@ public function setExpires(\DateTimeImmutable $expires): void
287293
{
288294
$this->expires = $expires;
289295
}
296+
297+
public function getMetadata(): ?string
298+
{
299+
return $this->metadata;
300+
}
301+
302+
public function setMetadata(string $metadata): void
303+
{
304+
$this->metadata = $metadata;
305+
}
290306
}

Classes/OAuthClient.php

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
declare(strict_types=1);
23

34
namespace Flownative\OAuth2\Client;
45

@@ -247,6 +248,18 @@ public function requestAccessToken(string $serviceName, string $clientId, string
247248
$this->entityManager->flush();
248249
}
249250

251+
/**
252+
* Returns an authorization id taking the service type and service name into account.
253+
*
254+
* @param string $clientId
255+
* @return string
256+
* @throws OAuthClientException
257+
*/
258+
public function generateAuthorizationIdForAuthorizationCodeGrant(string $clientId): string
259+
{
260+
return Authorization::generateAuthorizationIdForAuthorizationCodeGrant($this->getServiceType(), $this->getServiceName(), $clientId);
261+
}
262+
250263
/**
251264
* Start OAuth authorization with the Authorization Code flow
252265
*
@@ -259,7 +272,30 @@ public function requestAccessToken(string $serviceName, string $clientId, string
259272
*/
260273
public function startAuthorization(string $clientId, string $clientSecret, UriInterface $returnToUri, string $scope): UriInterface
261274
{
262-
$authorizationId = Authorization::generateAuthorizationIdForAuthorizationCodeGrant($this->getServiceType(), $this->getServiceName(), $clientId);
275+
$authorizationId = $this->generateAuthorizationIdForAuthorizationCodeGrant($clientId);
276+
return $this->startAuthorizationWithId($authorizationId, $clientId, $clientSecret, $returnToUri, $scope);
277+
}
278+
279+
/**
280+
* Start OAuth authorization with the Authorization Code flow
281+
* based on a specified authorization identifier.
282+
*
283+
* Note that, if you use this method, it is your responsibility to provide a
284+
* meaningful authorization id. You might weaken the security of your
285+
* application if you use an id which is deterministic or can be guessed by
286+
* an attacker.
287+
*
288+
* If in doubt, always use startAuthorization() instead.
289+
*
290+
* @param string $clientId The client id, as provided by the OAuth server
291+
* @param string $clientSecret The client secret, provided by the OAuth server
292+
* @param UriInterface $returnToUri URI to return to when authorization is finished
293+
* @param string $scope Scope to request for authorization. Must be scope ids separated by space, e.g. "openid profile email"
294+
* @return UriInterface The URL the browser should redirect to, asking the user to authorize
295+
* @throws OAuthClientException
296+
*/
297+
public function startAuthorizationWithId(string $authorizationId, string $clientId, string $clientSecret, UriInterface $returnToUri, string $scope): UriInterface
298+
{
263299
$authorization = new Authorization($authorizationId, $this->getServiceType(), $clientId, Authorization::GRANT_AUTHORIZATION_CODE, $scope);
264300
$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));
265301

@@ -472,16 +508,36 @@ public function renderFinishAuthorizationUri(): string
472508
$this->uriBuilder->setCreateAbsoluteUri(true);
473509

474510
try {
475-
$uri = $this->uriBuilder->
476-
reset()->
477-
setCreateAbsoluteUri(true)->
478-
uriFor('finishAuthorization', ['serviceType' => $this->getServiceType(), 'serviceName' => $this->getServiceName()], 'OAuth', 'Flownative.OAuth2.Client');
511+
$uri = $this->uriBuilder
512+
->reset()
513+
->setCreateAbsoluteUri(true)
514+
->uriFor('finishAuthorization', ['serviceType' => $this->getServiceType(), 'serviceName' => $this->getServiceName()], 'OAuth', 'Flownative.OAuth2.Client');
479515
return $uri;
480516
} catch (MissingActionNameException $e) {
481517
return '';
482518
}
483519
}
484520

521+
/**
522+
* Helper method to set metadata on an Authorization instance. Changes are
523+
* persisted immediately.
524+
*
525+
* @param string $authorizationId
526+
* @param string $metadata
527+
* @return void
528+
*/
529+
public function setAuthorizationMetadata(string $authorizationId, string $metadata): void
530+
{
531+
$authorization = $this->getAuthorization($authorizationId);
532+
if ($authorization === null) {
533+
throw new \RuntimeException(sprintf('Failed setting authorization metadata: authorization %s was not found', $authorizationId), 1631821719);
534+
}
535+
$authorization->setMetadata($metadata);
536+
537+
$this->entityManager->persist($authorization);
538+
$this->entityManager->flush();
539+
}
540+
485541
/**
486542
* @param string $clientId
487543
* @param string $clientSecret
@@ -527,13 +583,13 @@ protected function removeExpiredAuthorizations(): void
527583
*/
528584
public function shutdownObject(): void
529585
{
530-
$decimals = (integer)strlen(strrchr($this->garbageCollectionProbability, '.')) - 1;
586+
$garbageCollectionProbability = (string)$this->garbageCollectionProbability;
587+
$decimals = strlen(strrchr($garbageCollectionProbability, '.') ?: '') - 1;
531588
$factor = ($decimals > -1) ? $decimals * 10 : 1;
532589
try {
533590
if (random_int(1, 100 * $factor) <= ($this->garbageCollectionProbability * $factor)) {
534591
$this->removeExpiredAuthorizations();
535592
}
536-
} catch (InvalidQueryException $e) {
537593
} catch (\Exception $e) {
538594
}
539595
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\Flow\Persistence\Doctrine\Migrations;
5+
6+
use Doctrine\DBAL\Schema\Schema;
7+
use Doctrine\Migrations\AbstractMigration;
8+
9+
/**
10+
* Add metedata column on Authorization table
11+
*/
12+
final class Version20210916194112 extends AbstractMigration
13+
{
14+
public function getDescription(): string
15+
{
16+
return 'Add metedata column on Authorization table';
17+
}
18+
19+
public function up(Schema $schema): void
20+
{
21+
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
22+
23+
$this->addSql('ALTER TABLE flownative_oauth2_client_authorization ADD metadata LONGTEXT DEFAULT NULL');
24+
}
25+
26+
public function down(Schema $schema): void
27+
{
28+
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
29+
30+
$this->addSql('ALTER TABLE flownative_oauth2_client_authorization DROP metadata');
31+
}
32+
}

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,35 @@ authorization" request. Another example is the client credentials flow,
2626
where an access token is stored in the authorizations table which is
2727
needed for executing authorized requests to the respective service.
2828

29+
Authorizations also may contain developer-provided metadata. For
30+
example, you may attach an account identifier to an authorization when
31+
an authorization process starts and use that information when
32+
authorization finishes to make sure that the authorization is only used
33+
for a specific account (or customer number, or participant id).
34+
35+
To set metadata, you need to know the authorization id when starting the
36+
authorization code flow. This code could be used in an overloaded
37+
`startAuthorizationAction()`:
38+
39+
```php
40+
$authorizationId = $oAuthClient->generateAuthorizationIdForAuthorizationCodeGrant($this->appId);
41+
$loginUri = $oAuthClient->startAuthorizationWithId(
42+
$authorizationId,
43+
$this->appId,
44+
$this->appSecret,
45+
$returnToUri,
46+
$scope
47+
);
48+
$oAuthClient->setAuthorizationMetadata($authorizationId, json_encode($metadata));
49+
```
50+
And later, in `finishAuthorization()`, you may retrieve the metadata as
51+
follows:
52+
53+
```php
54+
$authorization = $this->getAuthorization($authorizationId);
55+
$metadata = json_decode($authorization->getMetadata());
56+
```
57+
2958
## Encryption
3059

3160
By default, access tokens are serialized and stored unencrypted in the

0 commit comments

Comments
 (0)