clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Steve Rhoades <sedonami@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,30 @@
{
"name": "steverhoades/oauth2-openid-connect-server",
"description": "An OpenID Connect Server that sites on The PHP League's OAuth2 Server",
"license": "MIT",
"authors": [
{
"name": "Steve Rhoades",
"email": "sedonami@gmail.com"
}
],
"require": {
"league/oauth2-server": "^5.1|^6.0|^7.0|^8.0",
"lcobucci/jwt": "4.1.5|^4.2|^4.3|^5.0"
},
"require-dev": {
"phpunit/phpunit": "^5.0|^9.5",
"laminas/laminas-diactoros": "^1.3.2"
},
"autoload": {
"psr-4": {
"OpenIDConnectServer\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"OpenIDConnectServer\\Test\\": "tests/",
"LeagueTests\\": "vendor/league/oauth2-server/tests/"
}
}
}

View File

@@ -0,0 +1,41 @@
# OpenID Connect Example Implementations
The following examples piggyback off the PHP Leagues OAuth2 Server examples. Please follow the instructions below carefully.
## Installation
0. Run `composer install --prefer-source` in this directory to install dependencies
0. Create a private key `openssl genrsa -out private.key 2048`
0. Create a public key `openssl rsa -in private.key -pubout > public.key`
0. Change permissions of the .key files or a PHP Notice will be thrown `chmod 660 *.key`
0. `cd` into the public directory
0. Start a PHP server `php -S localhost:4444`
## Testing the client credentials grant example
Send the following cURL request:
```
curl -X "POST" "http://localhost:4444/client_credentials.php/access_token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: 1.0" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=myawesomeapp" \
--data-urlencode "client_secret=abc123" \
--data-urlencode "scope=openid email"
```
## Testing the password grant example
Send the following cURL request:
```
curl -X "POST" "http://localhost:4444/password.php/access_token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: 1.0" \
--data-urlencode "grant_type=password" \
--data-urlencode "client_id=myawesomeapp" \
--data-urlencode "client_secret=abc123" \
--data-urlencode "username=alex" \
--data-urlencode "password=whisky" \
--data-urlencode "scope=openid email"
```

View File

@@ -0,0 +1,21 @@
{
"require": {
"slim/slim": "3.0.*",
"league/oauth2-server": "^7.0"
},
"require-dev": {
"league/event": "^2.1",
"lcobucci/jwt": "^3.1",
"paragonie/random_compat": "^2.0",
"psr/http-message": "^1.0",
"defuse/php-encryption": "^2.1",
"zendframework/zend-diactoros": "^1.0"
},
"autoload": {
"psr-4": {
"OpenIDConnectServerExamples\\": "src/",
"OpenIDConnectServer\\": "../src/",
"OAuth2ServerExamples\\": "vendor/league/oauth2-server/examples/src"
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use OAuth2ServerExamples\Entities\UserEntity;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\AuthCodeRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
use Laminas\Diactoros\Stream;
use OpenIDConnectServer\IdTokenResponse;
use OpenIDConnectServerExamples\Repositories\IdentityRepository;
use OpenIDConnectServerExamples\Repositories\ScopeRepository;
use OpenIDConnectServer\ClaimExtractor;
include __DIR__ . '/../vendor/autoload.php';
$app = new App([
'settings' => [
'displayErrorDetails' => true,
],
AuthorizationServer::class => function () {
// Init our repositories
$clientRepository = new ClientRepository();
$scopeRepository = new ScopeRepository();
$accessTokenRepository = new AccessTokenRepository();
$authCodeRepository = new AuthCodeRepository();
$refreshTokenRepository = new RefreshTokenRepository();
$privateKeyPath = 'file://' . __DIR__ . '/../private.key';
// OpenID Connect Response Type
$responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());
// Setup the authorization server
$server = new AuthorizationServer(
$clientRepository,
$accessTokenRepository,
$scopeRepository,
$privateKeyPath,
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',
$responseType
);
// Enable the authentication code grant on the server with a token TTL of 1 hour
$server->enableGrantType(
new AuthCodeGrant(
$authCodeRepository,
$refreshTokenRepository,
new \DateInterval('PT10M')
),
new \DateInterval('PT1H')
);
return $server;
},
]);
$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
try {
// Validate the HTTP request and return an AuthorizationRequest object.
// The auth request object can be serialized into a user's session
$authRequest = $server->validateAuthorizationRequest($request);
// Once the user has logged in set the user on the AuthorizationRequest
$authRequest->setUser(new UserEntity());
// Once the user has approved or denied the client update the status
// (true = approved, false = denied)
$authRequest->setAuthorizationApproved(true);
// Return the HTTP redirect response
return $server->completeAuthorizationRequest($authRequest, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500)->withBody($body);
}
});
$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
try {
return $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500)->withBody($body);
}
});
$app->run();

View File

@@ -0,0 +1,85 @@
<?php
/**
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
use Laminas\Diactoros\Stream;
use OpenIDConnectServer\IdTokenResponse;
use OpenIDConnectServerExamples\Repositories\IdentityRepository;
use OpenIDConnectServerExamples\Repositories\ScopeRepository;
use OpenIDConnectServer\ClaimExtractor;
include __DIR__ . '/../vendor/autoload.php';
$app = new App([
'settings' => [
'displayErrorDetails' => true,
],
AuthorizationServer::class => function () {
// Init our repositories
$clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface
$scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface
$accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
// Path to public and private keys
$privateKey = 'file://' . __DIR__ . '/../private.key';
//$privateKey = new CryptKey('file://path/to/private.key', 'passphrase'); // if private key has a pass phrase
// OpenID Connect Response Type
$responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());
// Setup the authorization server
$server = new AuthorizationServer(
$clientRepository,
$accessTokenRepository,
$scopeRepository,
$privateKey,
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',
$responseType
);
// Enable the client credentials grant on the server
$server->enableGrantType(
new \League\OAuth2\Server\Grant\ClientCredentialsGrant(),
new \DateInterval('PT1H') // access tokens will expire after 1 hour
);
return $server;
},
]);
$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
try {
// Try to respond to the request
return $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
// All instances of OAuthServerException can be formatted into a HTTP response
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
// Unknown exception
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500)->withBody($body);
}
});
$app->run();

View File

@@ -0,0 +1,87 @@
<?php
/**
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\ImplicitGrant;
use OAuth2ServerExamples\Entities\UserEntity;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
use Laminas\Diactoros\Stream;
use OpenIDConnectServer\IdTokenResponse;
use OpenIDConnectServerExamples\Repositories\IdentityRepository;
use OpenIDConnectServerExamples\Repositories\ScopeRepository;
use OpenIDConnectServer\ClaimExtractor;
include __DIR__ . '/../vendor/autoload.php';
$app = new App([
'settings' => [
'displayErrorDetails' => true,
],
AuthorizationServer::class => function () {
// Init our repositories
$clientRepository = new ClientRepository();
$scopeRepository = new ScopeRepository();
$accessTokenRepository = new AccessTokenRepository();
$privateKeyPath = 'file://' . __DIR__ . '/../private.key';
// OpenID Connect Response Type
$responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());
// Setup the authorization server
$server = new AuthorizationServer(
$clientRepository,
$accessTokenRepository,
$scopeRepository,
$privateKeyPath,
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',
$responseType
);
// Enable the implicit grant on the server with a token TTL of 1 hour
$server->enableGrantType(new ImplicitGrant(new \DateInterval('PT1H')));
return $server;
},
]);
$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
try {
// Validate the HTTP request and return an AuthorizationRequest object.
// The auth request object can be serialized into a user's session
$authRequest = $server->validateAuthorizationRequest($request);
// Once the user has logged in set the user on the AuthorizationRequest
$authRequest->setUser(new UserEntity());
// Once the user has approved or denied the client update the status
// (true = approved, false = denied)
$authRequest->setAuthorizationApproved(true);
// Return the HTTP redirect response
return $server->completeAuthorizationRequest($authRequest, $response);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500)->withBody($body);
}
});
$app->run();

View File

@@ -0,0 +1,78 @@
<?php
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\PasswordGrant;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\UserRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
use OpenIDConnectServer\IdTokenResponse;
use OpenIDConnectServerExamples\Repositories\IdentityRepository;
use OpenIDConnectServerExamples\Repositories\ScopeRepository;
use OpenIDConnectServer\ClaimExtractor;
include __DIR__ . '/../vendor/autoload.php';
$app = new App([
// Add the authorization server to the DI container
AuthorizationServer::class => function () {
// OpenID Connect Response Type
$responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());
// Setup the authorization server
$server = new AuthorizationServer(
new ClientRepository(), // instance of ClientRepositoryInterface
new AccessTokenRepository(), // instance of AccessTokenRepositoryInterface
new ScopeRepository(), // instance of ScopeRepositoryInterface
'file://' . __DIR__ . '/../private.key', // path to private key
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen', // encryption key
$responseType
);
$grant = new PasswordGrant(
new UserRepository(), // instance of UserRepositoryInterface
new RefreshTokenRepository() // instance of RefreshTokenRepositoryInterface
);
$grant->setRefreshTokenTTL(new \DateInterval('P1M')); // refresh tokens will expire after 1 month
// Enable the password grant on the server with a token TTL of 1 hour
$server->enableGrantType(
$grant,
new \DateInterval('PT1H') // access tokens will expire after 1 hour
);
return $server;
},
]);
$app->post(
'/access_token',
function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);
try {
// Try to respond to the access token request
return $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $exception) {
// All instances of OAuthServerException can be converted to a PSR-7 response
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
// Catch unexpected exceptions
$body = $response->getBody();
$body->write($exception->getMessage());
return $response->withStatus(500)->withBody($body);
}
}
);
$app->run();

View File

@@ -0,0 +1,37 @@
<?php
namespace OpenIDConnectServerExamples\Entities;
use OpenIDConnectServer\Entities\ClaimSetInterface;
class UserEntity extends \OAuth2ServerExamples\Entities\UserEntity implements ClaimSetInterface
{
public function getClaims()
{
return [
// profile
'name' => 'John Smith',
'family_name' => 'Smith',
'given_name' => 'John',
'middle_name' => 'Doe',
'nickname' => 'JDog',
'preferred_username' => 'jdogsmith77',
'profile' => '',
'picture' => 'avatar.png',
'website' => 'http://www.google.com',
'gender' => 'M',
'birthdate' => '01/01/1990',
'zoneinfo' => '',
'locale' => 'US',
'updated_at' => '01/01/2018',
// email
'email' => 'john.doe@example.com',
'email_verified' => true,
// phone
'phone_number' => '(866) 555-5555',
'phone_number_verified' => true,
// address
'address' => '50 any street, any state, 55555',
];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace OpenIDConnectServerExamples\Repositories;
use OpenIDConnectServer\Repositories\IdentityProviderInterface;
use OpenIDConnectServerExamples\Entities\UserEntity;
class IdentityRepository implements IdentityProviderInterface
{
public function getUserEntityByIdentifier($identifier)
{
return new UserEntity();
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
namespace OpenIDConnectServerExamples\Repositories;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use OAuth2ServerExamples\Entities\ScopeEntity;
class ScopeRepository extends \OAuth2ServerExamples\Repositories\ScopeRepository
{
/**
* {@inheritdoc}
*/
public function getScopeEntityByIdentifier($scopeIdentifier)
{
$scopes = [
// Without this OpenID Connect cannot work.
'openid' => [
'description' => 'Enable OpenID Connect support'
],
'basic' => [
'description' => 'Basic details about you',
],
'email' => [
'description' => 'Your email address',
],
];
if (array_key_exists($scopeIdentifier, $scopes) === false) {
return;
}
$scope = new ScopeEntity();
$scope->setIdentifier($scopeIdentifier);
return $scope;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer;
use OpenIDConnectServer\Entities\ClaimSetEntity;
use OpenIDConnectServer\Entities\ClaimSetEntityInterface;
use OpenIDConnectServer\Exception\InvalidArgumentException;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
class ClaimExtractor
{
protected $claimSets;
protected $protectedClaims = ['profile', 'email', 'address', 'phone'];
/**
* ClaimExtractor constructor.
* @param ClaimSetEntity[] $claimSets
*/
public function __construct($claimSets = [])
{
// Add Default OpenID Connect Claims
// @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
$this->addClaimSet(
new ClaimSetEntity('profile', [
'name',
'family_name',
'given_name',
'middle_name',
'nickname',
'preferred_username',
'profile',
'picture',
'website',
'gender',
'birthdate',
'zoneinfo',
'locale',
'updated_at'
])
);
$this->addClaimSet(
new ClaimSetEntity('email', [
'email',
'email_verified'
])
);
$this->addClaimSet(
new ClaimSetEntity('address', [
'address'
])
);
$this->addClaimSet(
new ClaimSetEntity('phone', [
'phone_number',
'phone_number_verified'
])
);
foreach ($claimSets as $claimSet) {
$this->addClaimSet($claimSet);
}
}
/**
* @param ClaimSetEntityInterface $claimSet
* @return $this
* @throws InvalidArgumentException
*/
public function addClaimSet(ClaimSetEntityInterface $claimSet)
{
$scope = $claimSet->getScope();
if (in_array($scope, $this->protectedClaims) && !empty($this->claimSets[$scope])) {
throw new InvalidArgumentException(
sprintf("%s is a protected scope and is pre-defined by the OpenID Connect specification.", $scope)
);
}
$this->claimSets[$scope] = $claimSet;
return $this;
}
/**
* @param string $scope
* @return ClaimSetEntity|null
*/
public function getClaimSet($scope)
{
if (!$this->hasClaimSet($scope)) {
return null;
}
return $this->claimSets[$scope];
}
/**
* @param string $scope
* @return bool
*/
public function hasClaimSet($scope)
{
return array_key_exists($scope, $this->claimSets);
}
/**
* For given scopes and aggregated claims get all claims that have been configured on the extractor.
*
* @param array $scopes
* @param array $claims
* @return array
*/
public function extract(array $scopes, array $claims)
{
$claimData = [];
$keys = array_keys($claims);
foreach ($scopes as $scope) {
$scopeName = ($scope instanceof ScopeEntityInterface) ? $scope->getIdentifier() : $scope;
$claimSet = $this->getClaimSet($scopeName);
if (null === $claimSet) {
continue;
}
$intersected = array_intersect($claimSet->getClaims(), $keys);
if (empty($intersected)) {
continue;
}
$data = array_filter($claims,
function($key) use ($intersected) {
return in_array($key, $intersected);
},
ARRAY_FILTER_USE_KEY
);
$claimData = array_merge($claimData, $data);
}
return $claimData;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Entities;
class ClaimSetEntity implements ClaimSetEntityInterface
{
protected $scope;
protected $claims;
public function __construct($scope, array $claims)
{
$this->scope = $scope;
$this->claims = $claims;
}
public function getScope()
{
return $this->scope;
}
public function getClaims()
{
return $this->claims;
}
}

View File

@@ -0,0 +1,11 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Entities;
interface ClaimSetEntityInterface extends ClaimSetInterface, ScopeInterface
{
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Entities;
interface ClaimSetInterface
{
/**
* @return array
*/
public function getClaims();
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Entities;
interface ScopeInterface
{
/**
* @return string
*/
public function getScope();
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Exception;
class InvalidArgumentException extends \Exception
{
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Key\LocalFileReference;
use OpenIDConnectServer\Repositories\IdentityProviderInterface;
use OpenIDConnectServer\Entities\ClaimSetInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Token\Builder;
use Lcobucci\JWT\Encoding\JoseEncoder;
class IdTokenResponse extends BearerTokenResponse
{
/**
* @var IdentityProviderInterface
*/
protected $identityProvider;
/**
* @var ClaimExtractor
*/
protected $claimExtractor;
public function __construct(
IdentityProviderInterface $identityProvider,
ClaimExtractor $claimExtractor
) {
$this->identityProvider = $identityProvider;
$this->claimExtractor = $claimExtractor;
}
protected function getBuilder(AccessTokenEntityInterface $accessToken, UserEntityInterface $userEntity)
{
$claimsFormatter = ChainedFormatter::withUnixTimestampDates();
$builder = new Builder(new JoseEncoder(), $claimsFormatter);
// Since version 8.0 league/oauth2-server returns \DateTimeImmutable
$expiresAt = $accessToken->getExpiryDateTime();
if ($expiresAt instanceof \DateTime) {
$expiresAt = \DateTimeImmutable::createFromMutable($expiresAt);
}
// Add required id_token claims
return $builder
->permittedFor($accessToken->getClient()->getIdentifier())
->issuedBy('https://' . $_SERVER['HTTP_HOST'])
->issuedAt(new \DateTimeImmutable())
->expiresAt($expiresAt)
->relatedTo($userEntity->getIdentifier());
}
/**
* @param AccessTokenEntityInterface $accessToken
* @return array
*/
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
{
if (false === $this->isOpenIDRequest($accessToken->getScopes())) {
return [];
}
/** @var UserEntityInterface $userEntity */
$userEntity = $this->identityProvider->getUserEntityByIdentifier($accessToken->getUserIdentifier());
if (false === is_a($userEntity, UserEntityInterface::class)) {
throw new \RuntimeException('UserEntity must implement UserEntityInterface');
} else if (false === is_a($userEntity, ClaimSetInterface::class)) {
throw new \RuntimeException('UserEntity must implement ClaimSetInterface');
}
// Add required id_token claims
$builder = $this->getBuilder($accessToken, $userEntity);
// Need a claim factory here to reduce the number of claims by provided scope.
$claims = $this->claimExtractor->extract($accessToken->getScopes(), $userEntity->getClaims());
foreach ($claims as $claimName => $claimValue) {
$builder = $builder->withClaim($claimName, $claimValue);
}
if (
method_exists($this->privateKey, 'getKeyContents')
&& !empty($this->privateKey->getKeyContents())
) {
$key = InMemory::plainText($this->privateKey->getKeyContents(), (string)$this->privateKey->getPassPhrase());
} else {
$key = LocalFileReference::file($this->privateKey->getKeyPath(), (string)$this->privateKey->getPassPhrase());
}
$token = $builder->getToken(new Sha256(), $key);
return [
'id_token' => $token->toString()
];
}
/**
* @param ScopeEntityInterface[] $scopes
* @return bool
*/
private function isOpenIDRequest($scopes)
{
// Verify scope and make sure openid exists.
$valid = false;
foreach ($scopes as $scope) {
if ($scope->getIdentifier() === 'openid') {
$valid = true;
break;
}
}
return $valid;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Repositories;
interface ClaimSetRepositoryInterface
{
public function getClaimSetByScopeIdentifier($scopeIdentifier);
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* @author Steve Rhoades <sedonami@gmail.com>
* @license http://opensource.org/licenses/MIT MIT
*/
namespace OpenIDConnectServer\Repositories;
use League\OAuth2\Server\Repositories\RepositoryInterface;
interface IdentityProviderInterface extends RepositoryInterface
{
public function getUserEntityByIdentifier($identifier);
}