Skip to content

Commit 9d69b8e

Browse files
committed
Support for client credentials grant
1 parent 85018e4 commit 9d69b8e

File tree

5 files changed

+193
-2
lines changed

5 files changed

+193
-2
lines changed

Classes/OAuthClient.php

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ public function getRequestFactory(): RequestFactory
139139
return new RequestFactory();
140140
}
141141

142+
/**
143+
* Add credentials for a Client Credentials Grant
144+
*
145+
* @param string $clientId
146+
* @param string $clientSecret
147+
* @param string $scope
148+
* @throws IdentityProviderException
149+
*/
150+
public function addClientCredentials(string $clientId, string $clientSecret, string $scope = '')
151+
{
152+
$oAuthProvider = $this->createOAuthProvider($clientId, $clientSecret);
153+
154+
try {
155+
$this->logger->log(sprintf(static::getServiceName() . 'Setting client credentials for client "%s" using a %s bytes long secret.', $clientId, strlen($clientSecret)), LOG_INFO);
156+
157+
$oldOAuthToken = $this->getOAuthToken();
158+
if ($oldOAuthToken !== null) {
159+
$this->entityManager->remove($oldOAuthToken);
160+
$this->entityManager->flush();
161+
162+
$this->logger->log(sprintf(static::getServiceName() . 'Removed old OAuth token for client "%s".', $clientId), LOG_INFO);
163+
}
164+
165+
$accessToken = $oAuthProvider->getAccessToken('client_credentials');
166+
$oAuthToken = $this->createNewOAuthToken($clientId, $clientSecret, 'client_credentials', $accessToken, $scope);
167+
168+
$this->logger->log(sprintf(static::getServiceName() . 'Persisted new OAuth token for client "%s" with expiry time %s.', $clientId, $accessToken->getExpires()), LOG_INFO);
169+
170+
$this->entityManager->persist($oAuthToken);
171+
$this->entityManager->flush();
172+
} catch (IdentityProviderException $e) {
173+
throw $e;
174+
}
175+
}
176+
142177
/**
143178
* Start OAuth authorization
144179
*
@@ -202,7 +237,7 @@ public function finishAuthorization(string $code, string $state, string $scope)
202237
}
203238

204239
$accessToken = $oAuthProvider->getAccessToken('authorization_code', ['code' => $code]);
205-
$oAuthToken = $this->createNewOAuthToken($clientId, $clientSecret, $accessToken, $scope);
240+
$oAuthToken = $this->createNewOAuthToken($clientId, $clientSecret, 'authorization_code', $accessToken, $scope);
206241

207242
$this->logger->log(sprintf(static::getServiceName() . ': Persisted new OAuth token for client "%s" with expiry time %s.', $clientId, $accessToken->getExpires()), LOG_INFO);
208243

@@ -280,6 +315,32 @@ public function getAuthenticatedRequest(string $relativeUri, string $method = 'G
280315
$oAuthToken = $this->getOAuthToken();
281316
}
282317
$oAuthProvider = $this->createOAuthProvider($oAuthToken->clientId, $oAuthToken->clientSecret);
318+
319+
if ($oAuthToken->expires < new \DateTimeImmutable()) {
320+
switch($oAuthToken->grantType) {
321+
case 'authorization_code':
322+
$this->refreshAuthorization($oAuthToken->clientId, '');
323+
$oAuthToken = $this->getOAuthToken();
324+
break;
325+
case 'client_credentials':
326+
try {
327+
$newAccessToken = $oAuthProvider->getAccessToken('client_credentials');
328+
} catch(IdentityProviderException $exception) {
329+
$this->logger->log(sprintf(static::getServiceName() . 'Failed retrieving new OAuth access token for client "%s" (client credentials grant): %s', $oAuthToken->clientId, $exception->getMessage()), LOG_ERR);
330+
throw $exception;
331+
}
332+
333+
$oAuthToken->accessToken = $newAccessToken->getToken();
334+
$oAuthToken->expires = ($newAccessToken->getExpires() ? \DateTimeImmutable::createFromFormat('U', $newAccessToken->getExpires()) : null);
335+
336+
$this->logger->log(sprintf(static::getServiceName() . 'Persisted new OAuth token for client "%s" with expiry time %s.', $oAuthToken->clientId, $newAccessToken->getExpires()), LOG_INFO);
337+
338+
$this->entityManager->persist($oAuthToken);
339+
$this->entityManager->flush();
340+
break;
341+
}
342+
}
343+
283344
$body = ($bodyFields !== [] ? \GuzzleHttp\json_encode($bodyFields) : '');
284345

285346
return $oAuthProvider->getAuthenticatedRequest(
@@ -336,15 +397,17 @@ public function renderRedirectUri(): string
336397
*
337398
* @param string $clientId
338399
* @param string $clientSecret
400+
* @param string $grantType
339401
* @param AccessToken $accessToken
340402
* @param string $scope
341403
* @return OAuthToken
342404
*/
343-
protected function createNewOAuthToken(string $clientId, string $clientSecret, AccessToken $accessToken, string $scope): OAuthToken
405+
protected function createNewOAuthToken(string $clientId, string $clientSecret, string $grantType, AccessToken $accessToken, string $scope): OAuthToken
344406
{
345407
$oAuthToken = new OAuthToken();
346408
$oAuthToken->clientId = $clientId;
347409
$oAuthToken->serviceName = static::getServiceName();
410+
$oAuthToken->grantType = $grantType;
348411
$oAuthToken->clientSecret = $clientSecret;
349412
$oAuthToken->accessToken = $accessToken->getToken();
350413
$oAuthToken->refreshToken = $accessToken->getRefreshToken();

Classes/OAuthToken.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,23 @@ class OAuthToken
2626
/**
2727
* @var string
2828
*/
29+
public $grantType;
30+
31+
/**
32+
* @var string
33+
* @ORM\Column(nullable = true, length=5000)
34+
*/
2935
public $clientSecret;
3036

3137
/**
3238
* @var string
39+
* @ORM\Column(length=5000)
3340
*/
3441
public $accessToken;
3542

3643
/**
3744
* @var string
45+
* @ORM\Column(nullable = true, length=5000)
3846
*/
3947
public $refreshToken;
4048

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
namespace Neos\Flow\Persistence\Doctrine\Migrations;
3+
4+
use Doctrine\DBAL\Migrations\AbstractMigration;
5+
use Doctrine\DBAL\Schema\Schema;
6+
7+
/**
8+
*
9+
*/
10+
class Version20171121130544 extends AbstractMigration
11+
{
12+
13+
/**
14+
* @return string
15+
*/
16+
public function getDescription()
17+
{
18+
return 'Introduce grant type';
19+
}
20+
21+
/**
22+
* @param Schema $schema
23+
* @return void
24+
*/
25+
public function up(Schema $schema)
26+
{
27+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
28+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken ADD granttype VARCHAR(255) NOT NULL');
29+
}
30+
31+
/**
32+
* @param Schema $schema
33+
* @return void
34+
*/
35+
public function down(Schema $schema)
36+
{
37+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
38+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken DROP granttype');
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
namespace Neos\Flow\Persistence\Doctrine\Migrations;
3+
4+
use Doctrine\DBAL\Migrations\AbstractMigration;
5+
use Doctrine\DBAL\Schema\Schema;
6+
7+
/**
8+
*
9+
*/
10+
class Version20171121134206 extends AbstractMigration
11+
{
12+
13+
/**
14+
* @return string
15+
*/
16+
public function getDescription()
17+
{
18+
return 'Make refresh token optional';
19+
}
20+
21+
/**
22+
* @param Schema $schema
23+
* @return void
24+
*/
25+
public function up(Schema $schema)
26+
{
27+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
28+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken CHANGE refreshtoken refreshtoken VARCHAR(255) DEFAULT NULL');
29+
}
30+
31+
/**
32+
* @param Schema $schema
33+
* @return void
34+
*/
35+
public function down(Schema $schema)
36+
{
37+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
38+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken CHANGE refreshtoken refreshtoken VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci');
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
namespace Neos\Flow\Persistence\Doctrine\Migrations;
3+
4+
use Doctrine\DBAL\Migrations\AbstractMigration;
5+
use Doctrine\DBAL\Schema\Schema;
6+
7+
/**
8+
*
9+
*/
10+
class Version20171121134844 extends AbstractMigration
11+
{
12+
13+
/**
14+
* @return string
15+
*/
16+
public function getDescription()
17+
{
18+
return 'Longer fields for tokens and secret';
19+
}
20+
21+
/**
22+
* @param Schema $schema
23+
* @return void
24+
*/
25+
public function up(Schema $schema)
26+
{
27+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
28+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken CHANGE clientsecret clientsecret VARCHAR(5000) DEFAULT NULL, CHANGE accesstoken accesstoken VARCHAR(5000) NOT NULL, CHANGE refreshtoken refreshtoken VARCHAR(5000) DEFAULT NULL');
29+
}
30+
31+
/**
32+
* @param Schema $schema
33+
* @return void
34+
*/
35+
public function down(Schema $schema)
36+
{
37+
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
38+
$this->addSql('ALTER TABLE flownative_oauth2_client_oauthtoken CHANGE clientsecret clientsecret VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci, CHANGE accesstoken accesstoken VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci, CHANGE refreshtoken refreshtoken VARCHAR(255) DEFAULT NULL COLLATE utf8_unicode_ci');
39+
}
40+
}

0 commit comments

Comments
 (0)