[
  {
    "path": ".gitattributes",
    "content": "* text=auto\n\n/tests export-ignore\n/.gitattributes export-ignore\n/.gitignore export-ignore\n/.github export-ignore\n.scrutinizer.yml export-ignore\n/phpunit.xml.dist export-ignore\n/README.md export-ignore\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n    push:\n        branches:\n            - master\n            - develop\n    pull_request:\n    workflow_dispatch:\n\njobs:\n    check_composer:\n        name: Check composer.json\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - uses: shivammathur/setup-php@v2\n              with:\n                  coverage: none\n                  php-version: '8.3'\n            - run: composer validate --strict --no-check-lock\n\n    tests:\n        name: \"Tests on PHP ${{ matrix.php }}\"\n        runs-on: ubuntu-latest\n\n        strategy:\n            fail-fast: false\n            matrix:\n                php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ]\n\n        steps:\n            -   uses: actions/checkout@v4\n            -   uses: shivammathur/setup-php@v2\n                with:\n                    coverage: \"none\"\n                    php-version: \"${{ matrix.php }}\"\n                    ini-file: development\n\n            -   name: Update permissions\n                run: |\n                    chmod 600 tests/Stubs/private.key\n                    chmod 600 tests/Stubs/public.key\n\n            -   name: Install dependencies\n                run: composer update --ansi --no-progress --no-interaction\n\n            -   name: Run tests\n                run: php -d error_reporting=\"E_ALL & ~E_USER_DEPRECATED\" vendor/bin/phpunit -v --colors=always\n"
  },
  {
    "path": ".gitignore",
    "content": "/vendor/\nphpunit.xml\n/build\ncomposer.lock\n/examples/*.key\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "build:\n    environment:\n        php: 7.4.30\n\nfilter:\n    excluded_paths:\n        - tests/*\n        - vendor/*\n        - examples/*\nchecks:\n    php:\n        code_rating: true\n        remove_extra_empty_lines: true\n        remove_php_closing_tag: true\n        remove_trailing_whitespace: true\n        fix_use_statements:\n            remove_unused: true\n            preserve_multiple: false\n            preserve_blanklines: true\n            order_alphabetically: true\n        fix_php_opening_tag: true\n        fix_linefeed: true\n        fix_line_ending: true\n        fix_identation_4spaces: true\n        fix_doc_comments: true\ntools:\n    external_code_coverage:\n        timeout: 1800\n    php_code_coverage: false\n    php_code_sniffer:\n        config:\n            standard: PSR2\n        filter:\n            paths: ['src']\n    php_loc:\n        enabled: true\n        excluded_dirs: [vendor, tests, examples]\n    php_cpd:\n        enabled: true\n        excluded_dirs: [vendor, tests, examples]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Steve Rhoades <sedonami@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# OAuth 2.0 OpenID Connect Server\n\n[![Build Status](https://travis-ci.org/steverhoades/oauth2-openid-connect-server.svg?branch=master)](https://travis-ci.org/steverhoades/oauth2-openid-connect-server) [![Code Coverage](https://scrutinizer-ci.com/g/steverhoades/oauth2-openid-connect-server/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/steverhoades/oauth2-openid-connect-server/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/steverhoades/oauth2-openid-connect-server/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/steverhoades/oauth2-openid-connect-server/?branch=master)\n\nThis implements the OpenID Connect specification on top of The PHP League's [OAuth2 Server](https://github.com/thephpleague/oauth2-server).\n\n## Requirements\n\n* Requires PHP version 7.4 or greater.\n* [league/oauth2-server](https://github.com/thephpleague/oauth2-server) 8.4.2 or greater.\n\nNote: league/oauth2-server version may have a higher PHP requirement.\n\n## Usage\nThe following classes will need to be configured and passed to the AuthorizationServer in order to provide OpenID Connect functionality.\n\n1. IdentityRepository.  This MUST implement the OpenIDConnectServer\\Repositories\\IdentityProviderInterface and return the identity of the user based on the return value of $accessToken->getUserIdentifier().\n   1. The IdentityRepository MUST return a UserEntity that implements the following interfaces\n      1. OpenIDConnectServer\\Entities\\ClaimSetInterface\n      1. League\\OAuth2\\Server\\Entities\\UserEntityInterface.\n1. ClaimSet.  ClaimSet is a way to associate claims to a given scope.\n1. ClaimExtractor. The ClaimExtractor takes an array of ClaimSets and in addition provides default claims for the OpenID Connect specified scopes of: profile, email, phone and address.\n1. IdTokenResponse. This class must be passed to the AuthorizationServer during construction and is responsible for adding the id_token to the response.\n1. ScopeRepository. The getScopeEntityByIdentifier($identifier) method must return a ScopeEntity for the `openid` scope in order to enable support. See examples.\n\n### Example Configuration\n\n```php\n// Init Repositories\n$clientRepository       = new ClientRepository();\n$scopeRepository        = new ScopeRepository();\n$accessTokenRepository  = new AccessTokenRepository();\n$authCodeRepository     = new AuthCodeRepository();\n$refreshTokenRepository = new RefreshTokenRepository();\n\n$privateKeyPath = 'file://' . __DIR__ . '/../private.key';\n$publicKeyPath = 'file://' . __DIR__ . '/../public.key';\n\n// OpenID Connect Response Type\n$responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());\n\n// Setup the authorization server\n$server = new \\League\\OAuth2\\Server\\AuthorizationServer(\n    $clientRepository,\n    $accessTokenRepository,\n    $scopeRepository,\n    $privateKey,\n    $publicKey,\n    $responseType\n);\n\n$grant = new \\League\\OAuth2\\Server\\Grant\\AuthCodeGrant(\n    $authCodeRepository,\n    $refreshTokenRepository,\n    new \\DateInterval('PT10M') // authorization codes will expire after 10 minutes\n);\n\n$grant->setRefreshTokenTTL(new \\DateInterval('P1M')); // refresh tokens will expire after 1 month\n\n// Enable the authentication code grant on the server\n$server->enableGrantType(\n    $grant,\n    new \\DateInterval('PT1H') // access tokens will expire after 1 hour\n);\n\nreturn $server;\n```\nAfter the server has been configured it should be used as described in the [OAuth2 Server documentation](https://oauth2.thephpleague.com/).\n\n## UserEntity\nIn order for this library to work properly you will need to add your IdentityProvider to the IdTokenResponse object.  This will be used internally to lookup a UserEntity by it's identifier.  Additionally your UserEntity must implement the ClaimSetInterface which includes a single method getClaims().  The getClaims() method should return a list of attributes as key/value pairs that can be returned if the proper scope has been defined.\n```php\nuse League\\OAuth2\\Server\\Entities\\Traits\\EntityTrait;\nuse League\\OAuth2\\Server\\Entities\\UserEntityInterface;\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\n\nclass UserEntity implements UserEntityInterface, ClaimSetInterface\n{\n    use EntityTrait;\n\n    protected $attributes;\n\n    public function getClaims()\n    {\n        return $this->attributes;\n    }\n}\n\n```\n\n## ClaimSets\nA ClaimSet is a scope that defines a list of claims.\n```php\n// Example of the profile ClaimSet\n$claimSet = new ClaimSetEntity('profile', [\n        'name',\n        'family_name',\n        'given_name',\n        'middle_name',\n        'nickname',\n        'preferred_username',\n        'profile',\n        'picture',\n        'website',\n        'gender',\n        'birthdate',\n        'zoneinfo',\n        'locale',\n        'updated_at'\n    ]);\n\n```\nAs you can see from the above, profile lists a set of claims that can be extracted from our UserEntity if the profile scope is included with the authorization request.\n\n### Adding Custom ClaimSets\nAt some point you will likely want to include your own group of custom claims. To do this you will need to create a ClaimSetEntity, give it a scope (the value you will include in the scope parameter of your OAuth2 request) and the list of claims it supports.\n```php\n$extractor = new ClaimExtractor();\n// Create your custom scope\n$claimSet = new ClaimSetEntity('company', [\n        'company_name',\n        'company_phone',\n        'company_address'\n    ]);\n// Add it to the ClaimExtract (this is what you pass to IdTokenResponse, see configuration above)\n$extractor->addClaimSet($claimSet);\n```\nNow, when you pass the company scope with your request it will attempt to locate those properties from your UserEntity::getClaims().\n\n## Install\n\nVia Composer\n\n``` bash\n$ composer require steverhoades/oauth2-openid-connect-server\n```\n\n## Testing\nTo run the unit tests you will need to require league/oauth2-server from the source as this repository utilizes some of their existing test infrastructure.\n```bash\n$ composer require league/oauth2-server --prefer-source\n```\n\nRun PHPUnit from the root directory:\n```bash\n$ vendor/bin/phpunit\n```\n## License\n\nThe MIT License (MIT). Please see [License File](https://github.com/steverhoades/oauth2-openid-connect-client/blob/master/LICENSE) for more information.\n\n[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md\n[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md\n[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"steverhoades/oauth2-openid-connect-server\",\n    \"description\": \"An OpenID Connect Server that sites on The PHP League's OAuth2 Server\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Steve Rhoades\",\n            \"email\": \"sedonami@gmail.com\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=7.4\",\n        \"league/oauth2-server\": \"^8.4.2|^9.0\",\n        \"lcobucci/jwt\": \"4.1.5|^4.2|^4.3|^5.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^5.0|^9.5\",\n        \"laminas/laminas-diactoros\": \"^1.3.2 || ^3.3\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"OpenIDConnectServer\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"OpenIDConnectServer\\\\Test\\\\\": \"tests/\",\n            \"LeagueTests\\\\\": \"vendor/league/oauth2-server/tests/\"\n        }\n    },\n    \"config\": {\n        \"preferred-install\": {\n            \"league/oauth2-server\": \"source\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# OpenID Connect Example Implementations\nThe following examples piggyback off the PHP Leagues OAuth2 Server examples. Please follow the instructions below carefully.\n\n## Installation\n\n0. Run `composer install --prefer-source` in this directory to install dependencies\n0. Create a private key `openssl genrsa -out private.key 2048`\n0. Create a public key `openssl rsa -in private.key -pubout > public.key`\n0. Change permissions of the .key files or a PHP Notice will be thrown `chmod 660 *.key`\n0. `cd` into the public directory\n0. Start a PHP server `php -S localhost:4444`\n\n## Testing the client credentials grant example\n\nSend the following cURL request:\n\n```\ncurl -X \"POST\" \"http://localhost:4444/client_credentials.php/access_token\" \\\n\t-H \"Content-Type: application/x-www-form-urlencoded\" \\\n\t-H \"Accept: 1.0\" \\\n\t--data-urlencode \"grant_type=client_credentials\" \\\n\t--data-urlencode \"client_id=myawesomeapp\" \\\n\t--data-urlencode \"client_secret=abc123\" \\\n\t--data-urlencode \"scope=openid email\"\n```\n\n## Testing the password grant example\n\nSend the following cURL request:\n\n```\ncurl -X \"POST\" \"http://localhost:4444/password.php/access_token\" \\\n\t-H \"Content-Type: application/x-www-form-urlencoded\" \\\n\t-H \"Accept: 1.0\" \\\n\t--data-urlencode \"grant_type=password\" \\\n\t--data-urlencode \"client_id=myawesomeapp\" \\\n\t--data-urlencode \"client_secret=abc123\" \\\n\t--data-urlencode \"username=alex\" \\\n\t--data-urlencode \"password=whisky\" \\\n\t--data-urlencode \"scope=openid email\"\n```\n"
  },
  {
    "path": "examples/composer.json",
    "content": "{\n    \"require\": {\n        \"slim/slim\": \"3.0.*\",\n        \"league/oauth2-server\": \"^7.0\"\n    },\n    \"require-dev\": {\n        \"league/event\": \"^2.1\",\n        \"lcobucci/jwt\": \"^3.1\",\n        \"paragonie/random_compat\": \"^2.0\",\n        \"psr/http-message\": \"^1.0\",\n        \"defuse/php-encryption\": \"^2.1\",\n        \"zendframework/zend-diactoros\": \"^1.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"OpenIDConnectServerExamples\\\\\": \"src/\",\n            \"OpenIDConnectServer\\\\\": \"../src/\",\n            \"OAuth2ServerExamples\\\\\": \"vendor/league/oauth2-server/examples/src\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/public/auth_code.php",
    "content": "<?php\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     http://mit-license.org/\n *\n * @link        https://github.com/thephpleague/oauth2-server\n */\n\nuse League\\OAuth2\\Server\\AuthorizationServer;\nuse League\\OAuth2\\Server\\Exception\\OAuthServerException;\nuse League\\OAuth2\\Server\\Grant\\AuthCodeGrant;\nuse OAuth2ServerExamples\\Entities\\UserEntity;\nuse OAuth2ServerExamples\\Repositories\\AccessTokenRepository;\nuse OAuth2ServerExamples\\Repositories\\AuthCodeRepository;\nuse OAuth2ServerExamples\\Repositories\\ClientRepository;\nuse OAuth2ServerExamples\\Repositories\\RefreshTokenRepository;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Slim\\App;\nuse Laminas\\Diactoros\\Stream;\nuse OpenIDConnectServer\\IdTokenResponse;\nuse OpenIDConnectServerExamples\\Repositories\\IdentityRepository;\nuse OpenIDConnectServerExamples\\Repositories\\ScopeRepository;\nuse OpenIDConnectServer\\ClaimExtractor;\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\n$app = new App([\n    'settings'    => [\n        'displayErrorDetails' => true,\n    ],\n    AuthorizationServer::class => function () {\n        // Init our repositories\n        $clientRepository = new ClientRepository();\n        $scopeRepository = new ScopeRepository();\n        $accessTokenRepository = new AccessTokenRepository();\n        $authCodeRepository = new AuthCodeRepository();\n        $refreshTokenRepository = new RefreshTokenRepository();\n\n        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';\n\n        // OpenID Connect Response Type\n        $responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());\n\n        // Setup the authorization server\n        $server = new AuthorizationServer(\n            $clientRepository,\n            $accessTokenRepository,\n            $scopeRepository,\n            $privateKeyPath,\n            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',\n            $responseType\n        );\n\n        // Enable the authentication code grant on the server with a token TTL of 1 hour\n        $server->enableGrantType(\n            new AuthCodeGrant(\n                $authCodeRepository,\n                $refreshTokenRepository,\n                new \\DateInterval('PT10M')\n            ),\n            new \\DateInterval('PT1H')\n        );\n\n        return $server;\n    },\n]);\n\n$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {\n    /* @var \\League\\OAuth2\\Server\\AuthorizationServer $server */\n    $server = $app->getContainer()->get(AuthorizationServer::class);\n\n    try {\n        // Validate the HTTP request and return an AuthorizationRequest object.\n        // The auth request object can be serialized into a user's session\n        $authRequest = $server->validateAuthorizationRequest($request);\n\n        // Once the user has logged in set the user on the AuthorizationRequest\n        $authRequest->setUser(new UserEntity());\n\n        // Once the user has approved or denied the client update the status\n        // (true = approved, false = denied)\n        $authRequest->setAuthorizationApproved(true);\n\n        // Return the HTTP redirect response\n        return $server->completeAuthorizationRequest($authRequest, $response);\n    } catch (OAuthServerException $exception) {\n        return $exception->generateHttpResponse($response);\n    } catch (\\Exception $exception) {\n        $body = new Stream('php://temp', 'r+');\n        $body->write($exception->getMessage());\n\n        return $response->withStatus(500)->withBody($body);\n    }\n});\n\n$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {\n    /* @var \\League\\OAuth2\\Server\\AuthorizationServer $server */\n    $server = $app->getContainer()->get(AuthorizationServer::class);\n\n    try {\n        return $server->respondToAccessTokenRequest($request, $response);\n    } catch (OAuthServerException $exception) {\n        return $exception->generateHttpResponse($response);\n    } catch (\\Exception $exception) {\n        $body = new Stream('php://temp', 'r+');\n        $body->write($exception->getMessage());\n\n        return $response->withStatus(500)->withBody($body);\n    }\n});\n\n$app->run();\n"
  },
  {
    "path": "examples/public/client_credentials.php",
    "content": "<?php\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     http://mit-license.org/\n *\n * @link        https://github.com/thephpleague/oauth2-server\n */\n\nuse League\\OAuth2\\Server\\AuthorizationServer;\nuse League\\OAuth2\\Server\\Exception\\OAuthServerException;\nuse OAuth2ServerExamples\\Repositories\\AccessTokenRepository;\nuse OAuth2ServerExamples\\Repositories\\ClientRepository;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Slim\\App;\nuse Laminas\\Diactoros\\Stream;\nuse OpenIDConnectServer\\IdTokenResponse;\nuse OpenIDConnectServerExamples\\Repositories\\IdentityRepository;\nuse OpenIDConnectServerExamples\\Repositories\\ScopeRepository;\nuse OpenIDConnectServer\\ClaimExtractor;\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\n$app = new App([\n    'settings'                 => [\n        'displayErrorDetails' => true,\n    ],\n    AuthorizationServer::class => function () {\n        // Init our repositories\n        $clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface\n        $scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface\n        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface\n\n        // Path to public and private keys\n        $privateKey = 'file://' . __DIR__ . '/../private.key';\n        //$privateKey = new CryptKey('file://path/to/private.key', 'passphrase'); // if private key has a pass phrase\n\n        // OpenID Connect Response Type\n        $responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());\n\n        // Setup the authorization server\n        $server = new AuthorizationServer(\n            $clientRepository,\n            $accessTokenRepository,\n            $scopeRepository,\n            $privateKey,\n            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',\n            $responseType\n        );\n\n        // Enable the client credentials grant on the server\n        $server->enableGrantType(\n            new \\League\\OAuth2\\Server\\Grant\\ClientCredentialsGrant(),\n            new \\DateInterval('PT1H') // access tokens will expire after 1 hour\n        );\n\n        return $server;\n    },\n]);\n\n$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {\n\n    /* @var \\League\\OAuth2\\Server\\AuthorizationServer $server */\n    $server = $app->getContainer()->get(AuthorizationServer::class);\n\n    try {\n\n        // Try to respond to the request\n        return $server->respondToAccessTokenRequest($request, $response);\n    } catch (OAuthServerException $exception) {\n\n        // All instances of OAuthServerException can be formatted into a HTTP response\n        return $exception->generateHttpResponse($response);\n    } catch (\\Exception $exception) {\n\n        // Unknown exception\n        $body = new Stream('php://temp', 'r+');\n        $body->write($exception->getMessage());\n\n        return $response->withStatus(500)->withBody($body);\n    }\n});\n\n$app->run();\n"
  },
  {
    "path": "examples/public/implicit.php",
    "content": "<?php\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     http://mit-license.org/\n *\n * @link        https://github.com/thephpleague/oauth2-server\n */\n\nuse League\\OAuth2\\Server\\AuthorizationServer;\nuse League\\OAuth2\\Server\\Exception\\OAuthServerException;\nuse League\\OAuth2\\Server\\Grant\\ImplicitGrant;\nuse OAuth2ServerExamples\\Entities\\UserEntity;\nuse OAuth2ServerExamples\\Repositories\\AccessTokenRepository;\nuse OAuth2ServerExamples\\Repositories\\ClientRepository;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Slim\\App;\nuse Laminas\\Diactoros\\Stream;\nuse OpenIDConnectServer\\IdTokenResponse;\nuse OpenIDConnectServerExamples\\Repositories\\IdentityRepository;\nuse OpenIDConnectServerExamples\\Repositories\\ScopeRepository;\nuse OpenIDConnectServer\\ClaimExtractor;\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\n$app = new App([\n    'settings'    => [\n        'displayErrorDetails' => true,\n    ],\n    AuthorizationServer::class => function () {\n        // Init our repositories\n        $clientRepository = new ClientRepository();\n        $scopeRepository = new ScopeRepository();\n        $accessTokenRepository = new AccessTokenRepository();\n\n        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';\n\n        // OpenID Connect Response Type\n        $responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());\n\n        // Setup the authorization server\n        $server = new AuthorizationServer(\n            $clientRepository,\n            $accessTokenRepository,\n            $scopeRepository,\n            $privateKeyPath,\n            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',\n            $responseType\n        );\n\n        // Enable the implicit grant on the server with a token TTL of 1 hour\n        $server->enableGrantType(new ImplicitGrant(new \\DateInterval('PT1H')));\n\n        return $server;\n    },\n]);\n\n$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {\n    /* @var \\League\\OAuth2\\Server\\AuthorizationServer $server */\n    $server = $app->getContainer()->get(AuthorizationServer::class);\n\n    try {\n        // Validate the HTTP request and return an AuthorizationRequest object.\n        // The auth request object can be serialized into a user's session\n        $authRequest = $server->validateAuthorizationRequest($request);\n\n        // Once the user has logged in set the user on the AuthorizationRequest\n        $authRequest->setUser(new UserEntity());\n\n        // Once the user has approved or denied the client update the status\n        // (true = approved, false = denied)\n        $authRequest->setAuthorizationApproved(true);\n\n        // Return the HTTP redirect response\n        return $server->completeAuthorizationRequest($authRequest, $response);\n    } catch (OAuthServerException $exception) {\n        return $exception->generateHttpResponse($response);\n    } catch (\\Exception $exception) {\n        $body = new Stream('php://temp', 'r+');\n        $body->write($exception->getMessage());\n\n        return $response->withStatus(500)->withBody($body);\n    }\n});\n\n$app->run();\n"
  },
  {
    "path": "examples/public/password.php",
    "content": "<?php\n\nuse League\\OAuth2\\Server\\AuthorizationServer;\nuse League\\OAuth2\\Server\\Exception\\OAuthServerException;\nuse League\\OAuth2\\Server\\Grant\\PasswordGrant;\nuse OAuth2ServerExamples\\Repositories\\AccessTokenRepository;\nuse OAuth2ServerExamples\\Repositories\\ClientRepository;\nuse OAuth2ServerExamples\\Repositories\\RefreshTokenRepository;\nuse OAuth2ServerExamples\\Repositories\\UserRepository;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Slim\\App;\nuse OpenIDConnectServer\\IdTokenResponse;\nuse OpenIDConnectServerExamples\\Repositories\\IdentityRepository;\nuse OpenIDConnectServerExamples\\Repositories\\ScopeRepository;\nuse OpenIDConnectServer\\ClaimExtractor;\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\n$app = new App([\n    // Add the authorization server to the DI container\n    AuthorizationServer::class => function () {\n        // OpenID Connect Response Type\n        $responseType = new IdTokenResponse(new IdentityRepository(), new ClaimExtractor());\n\n        // Setup the authorization server\n        $server = new AuthorizationServer(\n            new ClientRepository(),                 // instance of ClientRepositoryInterface\n            new AccessTokenRepository(),            // instance of AccessTokenRepositoryInterface\n            new ScopeRepository(),                  // instance of ScopeRepositoryInterface\n            'file://' . __DIR__ . '/../private.key',    // path to private key\n            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen',      // encryption key\n            $responseType\n        );\n\n        $grant = new PasswordGrant(\n            new UserRepository(),           // instance of UserRepositoryInterface\n            new RefreshTokenRepository()    // instance of RefreshTokenRepositoryInterface\n        );\n        $grant->setRefreshTokenTTL(new \\DateInterval('P1M')); // refresh tokens will expire after 1 month\n\n        // Enable the password grant on the server with a token TTL of 1 hour\n        $server->enableGrantType(\n            $grant,\n            new \\DateInterval('PT1H') // access tokens will expire after 1 hour\n        );\n\n        return $server;\n    },\n]);\n\n$app->post(\n    '/access_token',\n    function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {\n\n        /* @var \\League\\OAuth2\\Server\\AuthorizationServer $server */\n        $server = $app->getContainer()->get(AuthorizationServer::class);\n\n        try {\n\n            // Try to respond to the access token request\n            return $server->respondToAccessTokenRequest($request, $response);\n        } catch (OAuthServerException $exception) {\n\n            // All instances of OAuthServerException can be converted to a PSR-7 response\n            return $exception->generateHttpResponse($response);\n        } catch (\\Exception $exception) {\n\n            // Catch unexpected exceptions\n            $body = $response->getBody();\n            $body->write($exception->getMessage());\n\n            return $response->withStatus(500)->withBody($body);\n        }\n    }\n);\n\n$app->run();\n"
  },
  {
    "path": "examples/src/Entities/UserEntity.php",
    "content": "<?php\n\nnamespace OpenIDConnectServerExamples\\Entities;\n\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\n\nclass UserEntity extends \\OAuth2ServerExamples\\Entities\\UserEntity implements ClaimSetInterface\n{\n    public function getClaims()\n    {\n        return [\n            // profile\n            'name' => 'John Smith',\n            'family_name' => 'Smith',\n            'given_name' => 'John',\n            'middle_name' => 'Doe',\n            'nickname' => 'JDog',\n            'preferred_username' => 'jdogsmith77',\n            'profile' => '',\n            'picture' => 'avatar.png',\n            'website' => 'http://www.google.com',\n            'gender' => 'M',\n            'birthdate' => '01/01/1990',\n            'zoneinfo' => '',\n            'locale' => 'US',\n            'updated_at' => '01/01/2018',\n            // email\n            'email' => 'john.doe@example.com',\n            'email_verified' => true,\n            // phone\n            'phone_number' => '(866) 555-5555',\n            'phone_number_verified' => true,\n            // address\n            'address' => '50 any street, any state, 55555',\n        ];\n    }\n}\n"
  },
  {
    "path": "examples/src/Repositories/IdentityRepository.php",
    "content": "<?php\nnamespace OpenIDConnectServerExamples\\Repositories;\n\nuse OpenIDConnectServer\\Repositories\\IdentityProviderInterface;\nuse OpenIDConnectServerExamples\\Entities\\UserEntity;\n\nclass IdentityRepository implements IdentityProviderInterface\n{\n    public function getUserEntityByIdentifier($identifier)\n    {\n        return new UserEntity();\n    }\n}\n"
  },
  {
    "path": "examples/src/Repositories/ScopeRepository.php",
    "content": "<?php\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     http://mit-license.org/\n *\n * @link        https://github.com/thephpleague/oauth2-server\n */\n\nnamespace OpenIDConnectServerExamples\\Repositories;\n\nuse League\\OAuth2\\Server\\Entities\\ClientEntityInterface;\nuse League\\OAuth2\\Server\\Repositories\\ScopeRepositoryInterface;\nuse OAuth2ServerExamples\\Entities\\ScopeEntity;\n\nclass ScopeRepository extends \\OAuth2ServerExamples\\Repositories\\ScopeRepository\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function getScopeEntityByIdentifier($scopeIdentifier)\n    {\n        $scopes = [\n            // Without this OpenID Connect cannot work.\n            'openid' => [\n                'description' => 'Enable OpenID Connect support'\n            ],\n            'basic' => [\n                'description' => 'Basic details about you',\n            ],\n            'email' => [\n                'description' => 'Your email address',\n            ],\n        ];\n\n        if (array_key_exists($scopeIdentifier, $scopes) === false) {\n            return;\n        }\n\n        $scope = new ScopeEntity();\n        $scope->setIdentifier($scopeIdentifier);\n\n        return $scope;\n    }\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit colors=\"true\" convertNoticesToExceptions=\"true\" convertWarningsToExceptions=\"true\" stopOnError=\"true\"\n         stopOnFailure=\"true\" stopOnIncomplete=\"false\" stopOnSkipped=\"false\" bootstrap=\"tests/Bootstrap.php\">\n    <testsuites>\n        <testsuite name=\"Tests\">\n            <directory>./tests/</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist addUncoveredFilesFromWhitelist=\"true\">\n            <directory suffix=\".php\">src</directory>\n        </whitelist>\n    </filter>\n    <logging>\n        <log type=\"coverage-text\" target=\"php://stdout\" title=\"steverhoades/oauth2-openid-connect-server\" charset=\"UTF-8\" yui=\"true\"\n             highlight=\"true\" lowUpperBound=\"60\" highLowerBound=\"90\"/>\n        <log type=\"coverage-html\" target=\"build/coverage\" title=\"steverhoades/oauth2-openid-connect-server\" charset=\"UTF-8\" yui=\"true\"\n             highlight=\"true\" lowUpperBound=\"60\" highLowerBound=\"90\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "src/ClaimExtractor.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer;\n\nuse OpenIDConnectServer\\Entities\\ClaimSetEntity;\nuse OpenIDConnectServer\\Entities\\ClaimSetEntityInterface;\nuse OpenIDConnectServer\\Exception\\InvalidArgumentException;\nuse League\\OAuth2\\Server\\Entities\\ScopeEntityInterface;\n\nclass ClaimExtractor\n{\n    protected $claimSets;\n\n    protected $protectedClaims = ['profile', 'email', 'address', 'phone'];\n\n    /**\n     * ClaimExtractor constructor.\n     * @param ClaimSetEntity[] $claimSets\n     */\n    public function __construct($claimSets = [])\n    {\n        // Add Default OpenID Connect Claims\n        // @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims\n        $this->addClaimSet(\n            new ClaimSetEntity('profile', [\n                'name',\n                'family_name',\n                'given_name',\n                'middle_name',\n                'nickname',\n                'preferred_username',\n                'profile',\n                'picture',\n                'website',\n                'gender',\n                'birthdate',\n                'zoneinfo',\n                'locale',\n                'updated_at'\n            ])\n        );\n        $this->addClaimSet(\n            new ClaimSetEntity('email', [\n                'email',\n                'email_verified'\n            ])\n        );\n        $this->addClaimSet(\n            new ClaimSetEntity('address', [\n                'address'\n            ])\n        );\n        $this->addClaimSet(\n            new ClaimSetEntity('phone', [\n                'phone_number',\n                'phone_number_verified'\n            ])\n        );\n\n        foreach ($claimSets as $claimSet) {\n            $this->addClaimSet($claimSet);\n        }\n    }\n\n    /**\n     * @param ClaimSetEntityInterface $claimSet\n     * @return $this\n     * @throws InvalidArgumentException\n     */\n    public function addClaimSet(ClaimSetEntityInterface $claimSet)\n    {\n        $scope = $claimSet->getScope();\n\n        if (in_array($scope, $this->protectedClaims) && !empty($this->claimSets[$scope])) {\n            throw new InvalidArgumentException(\n                sprintf(\"%s is a protected scope and is pre-defined by the OpenID Connect specification.\", $scope)\n            );\n        }\n\n        $this->claimSets[$scope] = $claimSet;\n\n        return $this;\n    }\n\n    /**\n     * @param string $scope\n     * @return ClaimSetEntity|null\n     */\n    public function getClaimSet($scope)\n    {\n        if (!$this->hasClaimSet($scope)) {\n            return null;\n        }\n\n        return $this->claimSets[$scope];\n    }\n\n    /**\n     * @param string $scope\n     * @return bool\n     */\n    public function hasClaimSet($scope)\n    {\n        return array_key_exists($scope, $this->claimSets);\n    }\n\n    /**\n     * For given scopes and aggregated claims get all claims that have been configured on the extractor.\n     *\n     * @param array $scopes\n     * @param array $claims\n     * @return array\n     */\n    public function extract(array $scopes, array $claims)\n    {\n        $claimData  = [];\n        $keys       = array_keys($claims);\n\n        foreach ($scopes as $scope) {\n            $scopeName = ($scope instanceof ScopeEntityInterface) ? $scope->getIdentifier() : $scope;\n\n            $claimSet = $this->getClaimSet($scopeName);\n            if (null === $claimSet) {\n                continue;\n            }\n\n            $intersected = array_intersect($claimSet->getClaims(), $keys);\n\n            if (empty($intersected)) {\n                continue;\n            }\n\n            $data = array_filter($claims,\n                function($key) use ($intersected) {\n                    return in_array($key, $intersected);\n                },\n                ARRAY_FILTER_USE_KEY\n            );\n\n            $claimData = array_merge($claimData, $data);\n        }\n\n        return $claimData;\n    }\n}\n"
  },
  {
    "path": "src/Entities/ClaimSetEntity.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Entities;\n\nclass ClaimSetEntity implements ClaimSetEntityInterface\n{\n    protected $scope;\n\n    protected $claims;\n\n    public function __construct($scope, array $claims)\n    {\n        $this->scope    = $scope;\n        $this->claims   = $claims;\n    }\n\n    public function getScope()\n    {\n        return $this->scope;\n    }\n\n    public function getClaims()\n    {\n        return $this->claims;\n    }\n}\n"
  },
  {
    "path": "src/Entities/ClaimSetEntityInterface.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Entities;\n\n\ninterface ClaimSetEntityInterface extends ClaimSetInterface, ScopeInterface\n{\n}\n"
  },
  {
    "path": "src/Entities/ClaimSetInterface.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Entities;\n\n\ninterface ClaimSetInterface\n{\n    /**\n     * @return array\n     */\n    public function getClaims();\n}\n"
  },
  {
    "path": "src/Entities/ScopeInterface.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Entities;\n\n\ninterface ScopeInterface\n{\n    /**\n     * @return string\n     */\n    public function getScope();\n}\n"
  },
  {
    "path": "src/Exception/InvalidArgumentException.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Exception;\n\n\nclass InvalidArgumentException extends \\Exception\n{\n\n}\n"
  },
  {
    "path": "src/IdTokenResponse.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer;\n\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\Key\\LocalFileReference;\nuse OpenIDConnectServer\\Repositories\\IdentityProviderInterface;\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\nuse League\\OAuth2\\Server\\Entities\\UserEntityInterface;\nuse League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterface;\nuse League\\OAuth2\\Server\\Entities\\ScopeEntityInterface;\nuse League\\OAuth2\\Server\\ResponseTypes\\BearerTokenResponse;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha256;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Token\\Builder;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\n\nclass IdTokenResponse extends BearerTokenResponse\n{\n    /**\n     * @var IdentityProviderInterface\n     */\n    protected $identityProvider;\n\n    /**\n     * @var ClaimExtractor\n     */\n    protected $claimExtractor;\n\n    /**\n     * @var string|null\n     */\n    protected $keyIdentifier;\n    \n    public function __construct(\n        IdentityProviderInterface $identityProvider,\n        ClaimExtractor $claimExtractor,\n        ?string $keyIdentifier = null\n    ) {\n        $this->identityProvider = $identityProvider;\n        $this->claimExtractor   = $claimExtractor;\n        $this->keyIdentifier   = $keyIdentifier;\n    }\n\n    protected function getBuilder(AccessTokenEntityInterface $accessToken, UserEntityInterface $userEntity)\n    {\n        $claimsFormatter = ChainedFormatter::withUnixTimestampDates();\n        $builder = new Builder(new JoseEncoder(), $claimsFormatter);\n\n        // Since version 8.0 league/oauth2-server returns \\DateTimeImmutable\n        $expiresAt = $accessToken->getExpiryDateTime();\n        if ($expiresAt instanceof \\DateTime) {\n            $expiresAt = \\DateTimeImmutable::createFromMutable($expiresAt);\n        }\n\n        // Add required id_token claims\n        return $builder\n            ->permittedFor($accessToken->getClient()->getIdentifier())\n            ->issuedBy('https://' . $_SERVER['HTTP_HOST'])\n            ->issuedAt(new \\DateTimeImmutable())\n            ->expiresAt($expiresAt)\n            ->relatedTo($userEntity->getIdentifier());\n    }\n\n    /**\n     * @param AccessTokenEntityInterface $accessToken\n     * @return array\n     */\n    protected function getExtraParams(AccessTokenEntityInterface $accessToken): array\n    {\n        if (false === $this->isOpenIDRequest($accessToken->getScopes())) {\n            return [];\n        }\n\n        /** @var UserEntityInterface $userEntity */\n        $userEntity = $this->identityProvider->getUserEntityByIdentifier($accessToken->getUserIdentifier());\n\n        if (false === is_a($userEntity, UserEntityInterface::class)) {\n            throw new \\RuntimeException('UserEntity must implement UserEntityInterface');\n        } else if (false === is_a($userEntity, ClaimSetInterface::class)) {\n            throw new \\RuntimeException('UserEntity must implement ClaimSetInterface');\n        }\n\n        // Add required id_token claims\n        $builder = $this->getBuilder($accessToken, $userEntity);\n\n        // Need a claim factory here to reduce the number of claims by provided scope.\n        $claims = $this->claimExtractor->extract($accessToken->getScopes(), $userEntity->getClaims());\n\n        foreach ($claims as $claimName => $claimValue) {\n            $builder = $builder->withClaim($claimName, $claimValue);\n        }\n\n        if ($this->keyIdentifier !== null) {\n            $builder = $builder->withHeader('kid', $this->keyIdentifier);\n        }\n\n        if (\n            method_exists($this->privateKey, 'getKeyContents')\n            && !empty($this->privateKey->getKeyContents())\n        ) {\n            $key = InMemory::plainText($this->privateKey->getKeyContents(), (string)$this->privateKey->getPassPhrase());\n        } else {\n            $key = LocalFileReference::file($this->privateKey->getKeyPath(), (string)$this->privateKey->getPassPhrase());\n        }\n\n        $token = $builder->getToken(new Sha256(), $key);\n\n        return [\n            'id_token' => $token->toString()\n        ];\n    }\n\n    /**\n     * @param ScopeEntityInterface[] $scopes\n     * @return bool\n     */\n    private function isOpenIDRequest($scopes)\n    {\n        // Verify scope and make sure openid exists.\n        $valid  = false;\n\n        foreach ($scopes as $scope) {\n            if ($scope->getIdentifier() === 'openid') {\n                $valid = true;\n                break;\n            }\n        }\n\n        return $valid;\n    }\n\n}\n"
  },
  {
    "path": "src/Repositories/ClaimSetRepositoryInterface.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Repositories;\n\n\ninterface ClaimSetRepositoryInterface\n{\n    public function getClaimSetByScopeIdentifier($scopeIdentifier);\n}\n"
  },
  {
    "path": "src/Repositories/IdentityProviderInterface.php",
    "content": "<?php\n/**\n * @author Steve Rhoades <sedonami@gmail.com>\n * @license http://opensource.org/licenses/MIT MIT\n */\nnamespace OpenIDConnectServer\\Repositories;\n\nuse League\\OAuth2\\Server\\Entities\\UserEntityInterface;\nuse League\\OAuth2\\Server\\Repositories\\RepositoryInterface;\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\n\ninterface IdentityProviderInterface extends RepositoryInterface\n{\n    /**\n     * @return UserEntityInterface&ClaimSetInterface\n     */\n    public function getUserEntityByIdentifier($identifier);\n}\n"
  },
  {
    "path": "tests/Bootstrap.php",
    "content": "<?php\n\nif (!@include_once __DIR__ . '/../vendor/autoload.php') {\n    $message = <<<MSG\nYou must set up the project dependencies, run the following commands:\n> wget http://getcomposer.org/composer.phar\n> php composer.phar install\nMSG;\n\n    exit($message);\n}\n"
  },
  {
    "path": "tests/ClaimExtractorTest.php",
    "content": "<?php\nnamespace OpenIDConnectServer\\Test;\n\nuse OpenIDConnectServer\\ClaimExtractor;\nuse OpenIDConnectServer\\Entities\\ClaimSetEntity;\nuse PHPUnit\\Framework\\TestCase;\nuse OpenIDConnectServer\\Exception\\InvalidArgumentException;\n\nclass ClaimExtractorTest extends TestCase\n{\n    public function testDefaultClaimSetsExist()\n    {\n        $extractor = new ClaimExtractor();\n        self::assertTrue($extractor->hasClaimSet('profile'));\n        self::assertTrue($extractor->hasClaimSet('email'));\n        self::assertTrue($extractor->hasClaimSet('address'));\n        self::assertTrue($extractor->hasClaimSet('phone'));\n    }\n\n    public function testCanAddCustomClaimSet()\n    {\n        $claims = new ClaimSetEntity('custom', ['custom_claim']);\n        $extractor = new ClaimExtractor([$claims]);\n        self::assertTrue($extractor->hasClaimSet('custom'));\n\n        $result = $extractor->extract(['custom'], ['custom_claim' => 'test']);\n        self::assertEquals($result['custom_claim'], 'test');\n    }\n\n    public function testCanNotOverrideDefaultScope()\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $claims = new ClaimSetEntity('profile', ['custom_claim']);\n        $extractor = new ClaimExtractor([$claims]);\n    }\n\n    public function testCanGetClaimSet()\n    {\n        $extractor = new ClaimExtractor();\n        $claimset = $extractor->getClaimSet('profile');\n        self::assertEquals($claimset->getScope(), 'profile');\n        $claimset = $extractor->getClaimSet('unknown');\n        self::assertNull($claimset);\n    }\n\n    public function testExtract()\n    {\n        $extractor = new ClaimExtractor();\n        // no result\n        $result = $extractor->extract(['custom'], ['custom_claim' => 'test']);\n        self::assertEmpty($result);\n\n        // result\n        $result = $extractor->extract(['profile'], ['name' => 'Steve']);\n        self::assertEquals($result['name'], 'Steve');\n\n        // no result\n        $result = $extractor->extract(['profile'], ['invalid' => 'Steve']);\n        self::assertEmpty($result);\n    }\n}\n"
  },
  {
    "path": "tests/ResponseTypes/IdTokenResponseTest.php",
    "content": "<?php\n\nnamespace OpenIDConnectServer\\Test\\ResponseTypes;\n\nuse OpenIDConnectServer\\ClaimExtractor;\nuse OpenIDConnectServer\\IdTokenResponse;\nuse OpenIDConnectServer\\Test\\Stubs\\IdentityProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse League\\OAuth2\\Server\\CryptKey;\nuse LeagueTests\\Stubs\\AccessTokenEntity;\nuse LeagueTests\\Stubs\\ClientEntity;\nuse LeagueTests\\Stubs\\RefreshTokenEntity;\nuse LeagueTests\\Stubs\\ScopeEntity;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Laminas\\Diactoros\\Response;\n\nclass IdTokenResponseTest extends TestCase\n{\n    /**\n     * @dataProvider provideCryptKeys\n     */\n    public function testGeneratesDefaultHttpResponse($privateKey)\n    {\n        $responseType = new IdTokenResponse(new IdentityProvider(), new ClaimExtractor());\n        $response = $this->processResponseType($responseType, $privateKey);\n\n        self::assertInstanceOf(ResponseInterface::class, $response);\n        self::assertEquals(200, $response->getStatusCode());\n        self::assertEquals('no-cache', $response->getHeader('pragma')[0]);\n        self::assertEquals('no-store', $response->getHeader('cache-control')[0]);\n        self::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]);\n\n        $response->getBody()->rewind();\n        $json = json_decode($response->getBody()->getContents());\n        self::assertEquals('Bearer', $json->token_type);\n        self::assertObjectHasAttribute('expires_in', $json);\n        self::assertObjectHasAttribute('access_token', $json);\n        self::assertObjectHasAttribute('refresh_token', $json);\n    }\n\n    /**\n     * @dataProvider provideCryptKeys\n     */\n    public function testOpenIDConnectHttpResponse($privateKey)\n    {\n        $responseType = new IdTokenResponse(new IdentityProvider(), new ClaimExtractor());\n        $response = $this->processResponseType($responseType, $privateKey, ['openid']);\n\n        self::assertInstanceOf(ResponseInterface::class, $response);\n        self::assertEquals(200, $response->getStatusCode());\n        self::assertEquals('no-cache', $response->getHeader('pragma')[0]);\n        self::assertEquals('no-store', $response->getHeader('cache-control')[0]);\n        self::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]);\n\n        $response->getBody()->rewind();\n        $json = json_decode($response->getBody()->getContents());\n        self::assertEquals('Bearer', $json->token_type);\n        self::assertObjectHasAttribute('expires_in', $json);\n        self::assertObjectHasAttribute('access_token', $json);\n        self::assertObjectHasAttribute('refresh_token', $json);\n        self::assertObjectHasAttribute('id_token', $json);\n    }\n\n    // test additional claims\n    // test fails without claimsetinterface\n    /**\n     * @dataProvider provideCryptKeys\n     */\n    public function testThrowsRuntimeExceptionWhenMissingClaimSetInterface($privateKey)\n    {\n        $this->expectException(\\RuntimeException::class);\n\n        $_SERVER['HTTP_HOST'] = 'https://localhost';\n        $responseType = new IdTokenResponse(\n            new IdentityProvider(IdentityProvider::NO_CLAIMSET),\n            new ClaimExtractor()\n        );\n        $this->processResponseType($responseType, $privateKey, ['openid']);\n        self::fail('Exception should have been thrown');\n    }\n\n    // test fails without identityinterface\n    /**\n     * @dataProvider provideCryptKeys\n     */\n    public function testThrowsRuntimeExceptionWhenMissingIdentifierSetInterface($privateKey)\n    {\n        $this->expectException(\\RuntimeException::class);\n        $responseType = new IdTokenResponse(\n            new IdentityProvider(IdentityProvider::NO_IDENTIFIER),\n            new ClaimExtractor()\n        );\n        $this->processResponseType($responseType, $privateKey, ['openid']);\n        self::fail('Exception should have been thrown');\n    }\n\n    /**\n     * @dataProvider provideCryptKeys\n     */\n    public function testClaimsGetExtractedFromUserEntity($privateKey)\n    {\n        $responseType = new IdTokenResponse(new IdentityProvider(), new ClaimExtractor());\n        $response = $this->processResponseType($responseType, $privateKey, ['openid', 'email']);\n\n        self::assertInstanceOf(ResponseInterface::class, $response);\n        self::assertEquals(200, $response->getStatusCode());\n        self::assertEquals('no-cache', $response->getHeader('pragma')[0]);\n        self::assertEquals('no-store', $response->getHeader('cache-control')[0]);\n        self::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]);\n\n        $response->getBody()->rewind();\n        $json = json_decode($response->getBody()->getContents(),false);\n\n        self::assertEquals('Bearer', $json->token_type);\n        self::assertObjectHasAttribute('expires_in', $json);\n        self::assertObjectHasAttribute('access_token', $json);\n        self::assertObjectHasAttribute('refresh_token', $json);\n        self::assertObjectHasAttribute('id_token', $json);\n\n        if (class_exists(\"\\Lcobucci\\JWT\\Token\\Parser\")) {\n            $parser = new \\Lcobucci\\JWT\\Token\\Parser(new \\Lcobucci\\JWT\\Encoding\\JoseEncoder, \\Lcobucci\\JWT\\Encoding\\ChainedFormatter::withUnixTimestampDates());\n        } else {\n            $parser = new \\Lcobucci\\JWT\\Parser();\n        }\n\n        $token = $parser->parse($json->id_token);\n        self::assertTrue($token->claims()->has(\"email\"));\n    }\n\n    public static function provideCryptKeys()\n    {\n        return array(\n            array(new CryptKey('file://'.__DIR__.'/../Stubs/private.key')),\n            array(new CryptKey(\n                <<<KEY\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAzjgUbG97UD+bZwkceejvxkbcq/17YqmriWGpBu7etXAA2WZp\nx7vLaQi6ygApfCsYDz13W27DyriH2kg56GOa2v9M88OORiW1rQMaGF4hn/L7agFc\ncvdNAWBKD8ue+QUPz3prA3TLF+lSqMn5BgF+4j7XNlODvfOX3Tra1JQcVik4pyjg\nQeTLaaBSf6KaCvDzVCcvVuYISC5oku5v6o+BIug8taRhcXN8/9gLQ9akrGG73z+u\ndAoZ2+v1k7VZ704steLM9pf7aY+L6kR2Qmc7E4j/WM9Sv8CHUaJG41MRAbjdHtGh\nzGcsZwf/mNjyjW3UalI1dih2muhPTSAZT0xL+wIDAQABAoIBAAFDBJT5RabjDL9f\npeX1D+qFqnn+7g9XfG41w8QAGCrCCa9K2iDXvFHjNMlhoN9aoCYPuTg9AEOwR1yF\njp0mZt8qKr1fF/LD7k2ltDYr9Ua2ROWMJpWpf7YfcbSRCWL6rfMWC6uUvl1iFxhj\nS/vGbJFT0xtI/YhfAjHfV1FvqpC4YwmKVe4/QqU0Kw+CjmYKLqeR0lvARP0aRfRm\nGRIy/9ZtUzbcUSnLScNS8U2HhdKEOl/R9dSjHVG+rr+/sJmFRUiQWJx3UkDZ4lTd\nlEIqj8i0CZMooARZjOIhIjP+wT9zr1sWFaxU81ourMnfoOQSKIrkUUYGc4rOk3Ao\nDuqBx9ECgYEA5miGw2v+2YIm1XtmTRvLJiDgr8mTWydylPXxjTlghYNKts4bU5rh\nEN/Ok+DUPLqPGQLnFgjcadT/clZfJXF3/gOS8V9hxVWI6Gwku1Ez7OqtCv3plA0i\n46fxh0PmK6lFOzlbWMN1wpQ/mB3dWh4YaC6ERG1Z3L1DCIqYs/LVFsMCgYEA5R/A\ndJAwtPWjdEjKFaajH7Z7iB/BSUydyPg++sMDjETbrV3wddO67LEkhH3vB4VDEKKt\nFnD1iSyWprb94gDK0PTxQyHJ3JdzDP05L+7C+lwLqEKCrh2BZpScvub0FRgECNNO\nOCuoMtX1HG/dGjkJsxB7e1lr7LGFMyPjR6I+0mkCgYEAzvcznpUCvnTX10naUgdW\nSzCbQ6xJDkd3+HCYAuh4WFXgJicrisUDyHmRgWoim05lPe1KkJNzEim/MAB/xQ2Q\n4H5rXx/zniPAMC78K7q8buM6fzYnu9K09VQldAC835lUU+eosyoYPKmYGlcxP0Lr\nX6HxM9oaL1tevGxq0LGfUasCgYEA3R4ybouE5e61KxDgLdreTEmgl/MFZwbQs1WX\n+grfzqvZUUt6N0v5dllSQ6cBWkGqQlCsOB8VZqeoUAYDp+tZ0CTC/SWLmR5zwtJS\nMUb71f+kpGJjmUMSUXwUdUuPvRerNRUvxJelQEIpxaLTP25SRQQgFx9qP0fmoz78\nJXKXrBkCgYEApBfmVsOTG5S+oO7WZFpndeofLnXYn9xRvlc738+dANY4mWwHJlBd\nz2wzJ5wfjzlXsZoKcV0I6pRWLrgw3Gd5cwu3O5+MUN89cdQuVrfB77KQJHIF+S06\nfHDgr/HSgH8LCXDq4DSd5XC0WxCPTDYrTN8iiHop2k35Ex0UXYeE+g0=\n-----END RSA PRIVATE KEY-----\nKEY\n            ),\n        ));\n    }\n\n    private function processResponseType($responseType, $privateKey,  array $scopeNames = ['basic'])\n    {\n        $_SERVER['HTTP_HOST'] = 'https://localhost';\n\n        $responseType->setPrivateKey($privateKey);\n\n        // league/oauth2-server 5.1.0 does not support this interface\n        if (method_exists($responseType, 'setEncryptionKey')) {\n            $responseType->setEncryptionKey(base64_encode(random_bytes(36)));\n        }\n\n        $client = new ClientEntity();\n        $client->setIdentifier('clientName');\n\n        $scopes = [];\n        foreach ($scopeNames as $scopeName) {\n            $scope = new ScopeEntity();\n            $scope->setIdentifier($scopeName);\n            $scopes[] = $scope;\n        }\n\n        $accessToken = new AccessTokenEntity();\n        $accessToken->setIdentifier('abcdef');\n\n        if (method_exists($accessToken, 'setPrivateKey')) {\n            $accessToken->setPrivateKey($privateKey);\n        }\n\n        // Use DateTime for older libraries, DateTimeImmutable for new ones.\n        try {\n            $accessToken->setExpiryDateTime(\n                (new \\DateTime())->add(new \\DateInterval('PT1H'))\n            );\n        } catch(\\TypeError $e) {\n            $accessToken->setExpiryDateTime(\n                (new \\DateTimeImmutable())->add(new \\DateInterval('PT1H'))\n            );\n        }\n        $accessToken->setClient($client);\n\n        foreach ($scopes as $scope) {\n            $accessToken->addScope($scope);\n        }\n\n        $refreshToken = new RefreshTokenEntity();\n        $refreshToken->setIdentifier('abcdef');\n        $refreshToken->setAccessToken($accessToken);\n\n        // Use DateTime for older libraries, DateTimeImmutable for new ones.\n        try {\n            $refreshToken->setExpiryDateTime(\n                (new \\DateTime())->add(new \\DateInterval('PT1H'))\n            );\n        } catch(\\TypeError $e) {\n            $refreshToken->setExpiryDateTime(\n                (new \\DateTimeImmutable())->add(new \\DateInterval('PT1H'))\n            );\n        }\n\n        $responseType->setAccessToken($accessToken);\n        $responseType->setRefreshToken($refreshToken);\n\n        return $responseType->generateHttpResponse(new Response());\n    }\n}\n"
  },
  {
    "path": "tests/Stubs/IdentityProvider.php",
    "content": "<?php\n\nnamespace OpenIDConnectServer\\Test\\Stubs;\n\nuse OpenIDConnectServer\\Repositories\\IdentityProviderInterface;\n\nclass IdentityProvider implements IdentityProviderInterface\n{\n    const NO_CLAIMSET = 'no_claimset';\n    const NO_IDENTIFIER = 'no_idetifier';\n\n    protected $entity;\n\n    public function __construct($type = null)\n    {\n        switch($type) {\n            case self::NO_CLAIMSET:\n                $this->entity = new UserNoClaimSetEntity();\n                break;\n            case self::NO_IDENTIFIER:\n                $this->entity = new UserNoIdentifierEntity();\n                break;\n            default:\n                $this->entity = new UserEntity();\n        }\n    }\n\n    public function getUserEntityByIdentifier($identifier)\n    {\n        return $this->entity;\n    }\n\n}\n"
  },
  {
    "path": "tests/Stubs/UserEntity.php",
    "content": "<?php\n\nnamespace OpenIDConnectServer\\Test\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\Traits\\EntityTrait;\nuse League\\OAuth2\\Server\\Entities\\UserEntityInterface;\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\n\nclass UserEntity implements UserEntityInterface, ClaimSetInterface\n{\n    use EntityTrait;\n\n    public function __construct()\n    {\n        $this->setIdentifier(123);\n    }\n\n    public function getClaims()\n    {\n        return [\n            'first_name'    => 'Steve',\n            'last_name'     => 'Rhoades',\n            'email'         => 'steve.rhoades@stephenrhoades.com'\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Stubs/UserNoClaimSetEntity.php",
    "content": "<?php\n\nnamespace OpenIDConnectServer\\Test\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\Traits\\EntityTrait;\nuse League\\OAuth2\\Server\\Entities\\UserEntityInterface;\n\nclass UserNoClaimSetEntity implements UserEntityInterface\n{\n    use EntityTrait;\n\n    public function __construct()\n    {\n        $this->setIdentifier(123);\n    }\n}\n"
  },
  {
    "path": "tests/Stubs/UserNoIdentifierEntity.php",
    "content": "<?php\n\nnamespace OpenIDConnectServer\\Test\\Stubs;\n\nuse OpenIDConnectServer\\Entities\\ClaimSetInterface;\n\nclass UserNoIdentifierEntity implements ClaimSetInterface\n{\n    public function getClaims()\n    {\n        return [\n            'first_name'    => 'Steve',\n            'last_name'     => 'Rhoades',\n            'email'         => 'steve.rhoades@stephenrhoades.com'\n        ];\n    }\n}\n\n"
  },
  {
    "path": "tests/Stubs/private.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAzjgUbG97UD+bZwkceejvxkbcq/17YqmriWGpBu7etXAA2WZp\nx7vLaQi6ygApfCsYDz13W27DyriH2kg56GOa2v9M88OORiW1rQMaGF4hn/L7agFc\ncvdNAWBKD8ue+QUPz3prA3TLF+lSqMn5BgF+4j7XNlODvfOX3Tra1JQcVik4pyjg\nQeTLaaBSf6KaCvDzVCcvVuYISC5oku5v6o+BIug8taRhcXN8/9gLQ9akrGG73z+u\ndAoZ2+v1k7VZ704steLM9pf7aY+L6kR2Qmc7E4j/WM9Sv8CHUaJG41MRAbjdHtGh\nzGcsZwf/mNjyjW3UalI1dih2muhPTSAZT0xL+wIDAQABAoIBAAFDBJT5RabjDL9f\npeX1D+qFqnn+7g9XfG41w8QAGCrCCa9K2iDXvFHjNMlhoN9aoCYPuTg9AEOwR1yF\njp0mZt8qKr1fF/LD7k2ltDYr9Ua2ROWMJpWpf7YfcbSRCWL6rfMWC6uUvl1iFxhj\nS/vGbJFT0xtI/YhfAjHfV1FvqpC4YwmKVe4/QqU0Kw+CjmYKLqeR0lvARP0aRfRm\nGRIy/9ZtUzbcUSnLScNS8U2HhdKEOl/R9dSjHVG+rr+/sJmFRUiQWJx3UkDZ4lTd\nlEIqj8i0CZMooARZjOIhIjP+wT9zr1sWFaxU81ourMnfoOQSKIrkUUYGc4rOk3Ao\nDuqBx9ECgYEA5miGw2v+2YIm1XtmTRvLJiDgr8mTWydylPXxjTlghYNKts4bU5rh\nEN/Ok+DUPLqPGQLnFgjcadT/clZfJXF3/gOS8V9hxVWI6Gwku1Ez7OqtCv3plA0i\n46fxh0PmK6lFOzlbWMN1wpQ/mB3dWh4YaC6ERG1Z3L1DCIqYs/LVFsMCgYEA5R/A\ndJAwtPWjdEjKFaajH7Z7iB/BSUydyPg++sMDjETbrV3wddO67LEkhH3vB4VDEKKt\nFnD1iSyWprb94gDK0PTxQyHJ3JdzDP05L+7C+lwLqEKCrh2BZpScvub0FRgECNNO\nOCuoMtX1HG/dGjkJsxB7e1lr7LGFMyPjR6I+0mkCgYEAzvcznpUCvnTX10naUgdW\nSzCbQ6xJDkd3+HCYAuh4WFXgJicrisUDyHmRgWoim05lPe1KkJNzEim/MAB/xQ2Q\n4H5rXx/zniPAMC78K7q8buM6fzYnu9K09VQldAC835lUU+eosyoYPKmYGlcxP0Lr\nX6HxM9oaL1tevGxq0LGfUasCgYEA3R4ybouE5e61KxDgLdreTEmgl/MFZwbQs1WX\n+grfzqvZUUt6N0v5dllSQ6cBWkGqQlCsOB8VZqeoUAYDp+tZ0CTC/SWLmR5zwtJS\nMUb71f+kpGJjmUMSUXwUdUuPvRerNRUvxJelQEIpxaLTP25SRQQgFx9qP0fmoz78\nJXKXrBkCgYEApBfmVsOTG5S+oO7WZFpndeofLnXYn9xRvlc738+dANY4mWwHJlBd\nz2wzJ5wfjzlXsZoKcV0I6pRWLrgw3Gd5cwu3O5+MUN89cdQuVrfB77KQJHIF+S06\nfHDgr/HSgH8LCXDq4DSd5XC0WxCPTDYrTN8iiHop2k35Ex0UXYeE+g0=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Stubs/private.key.crlf",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAzjgUbG97UD+bZwkceejvxkbcq/17YqmriWGpBu7etXAA2WZp\nx7vLaQi6ygApfCsYDz13W27DyriH2kg56GOa2v9M88OORiW1rQMaGF4hn/L7agFc\ncvdNAWBKD8ue+QUPz3prA3TLF+lSqMn5BgF+4j7XNlODvfOX3Tra1JQcVik4pyjg\nQeTLaaBSf6KaCvDzVCcvVuYISC5oku5v6o+BIug8taRhcXN8/9gLQ9akrGG73z+u\ndAoZ2+v1k7VZ704steLM9pf7aY+L6kR2Qmc7E4j/WM9Sv8CHUaJG41MRAbjdHtGh\nzGcsZwf/mNjyjW3UalI1dih2muhPTSAZT0xL+wIDAQABAoIBAAFDBJT5RabjDL9f\npeX1D+qFqnn+7g9XfG41w8QAGCrCCa9K2iDXvFHjNMlhoN9aoCYPuTg9AEOwR1yF\njp0mZt8qKr1fF/LD7k2ltDYr9Ua2ROWMJpWpf7YfcbSRCWL6rfMWC6uUvl1iFxhj\nS/vGbJFT0xtI/YhfAjHfV1FvqpC4YwmKVe4/QqU0Kw+CjmYKLqeR0lvARP0aRfRm\nGRIy/9ZtUzbcUSnLScNS8U2HhdKEOl/R9dSjHVG+rr+/sJmFRUiQWJx3UkDZ4lTd\nlEIqj8i0CZMooARZjOIhIjP+wT9zr1sWFaxU81ourMnfoOQSKIrkUUYGc4rOk3Ao\nDuqBx9ECgYEA5miGw2v+2YIm1XtmTRvLJiDgr8mTWydylPXxjTlghYNKts4bU5rh\nEN/Ok+DUPLqPGQLnFgjcadT/clZfJXF3/gOS8V9hxVWI6Gwku1Ez7OqtCv3plA0i\n46fxh0PmK6lFOzlbWMN1wpQ/mB3dWh4YaC6ERG1Z3L1DCIqYs/LVFsMCgYEA5R/A\ndJAwtPWjdEjKFaajH7Z7iB/BSUydyPg++sMDjETbrV3wddO67LEkhH3vB4VDEKKt\nFnD1iSyWprb94gDK0PTxQyHJ3JdzDP05L+7C+lwLqEKCrh2BZpScvub0FRgECNNO\nOCuoMtX1HG/dGjkJsxB7e1lr7LGFMyPjR6I+0mkCgYEAzvcznpUCvnTX10naUgdW\nSzCbQ6xJDkd3+HCYAuh4WFXgJicrisUDyHmRgWoim05lPe1KkJNzEim/MAB/xQ2Q\n4H5rXx/zniPAMC78K7q8buM6fzYnu9K09VQldAC835lUU+eosyoYPKmYGlcxP0Lr\nX6HxM9oaL1tevGxq0LGfUasCgYEA3R4ybouE5e61KxDgLdreTEmgl/MFZwbQs1WX\n+grfzqvZUUt6N0v5dllSQ6cBWkGqQlCsOB8VZqeoUAYDp+tZ0CTC/SWLmR5zwtJS\nMUb71f+kpGJjmUMSUXwUdUuPvRerNRUvxJelQEIpxaLTP25SRQQgFx9qP0fmoz78\nJXKXrBkCgYEApBfmVsOTG5S+oO7WZFpndeofLnXYn9xRvlc738+dANY4mWwHJlBd\nz2wzJ5wfjzlXsZoKcV0I6pRWLrgw3Gd5cwu3O5+MUN89cdQuVrfB77KQJHIF+S06\nfHDgr/HSgH8LCXDq4DSd5XC0WxCPTDYrTN8iiHop2k35Ex0UXYeE+g0=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Stubs/public.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzjgUbG97UD+bZwkceejv\nxkbcq/17YqmriWGpBu7etXAA2WZpx7vLaQi6ygApfCsYDz13W27DyriH2kg56GOa\n2v9M88OORiW1rQMaGF4hn/L7agFccvdNAWBKD8ue+QUPz3prA3TLF+lSqMn5BgF+\n4j7XNlODvfOX3Tra1JQcVik4pyjgQeTLaaBSf6KaCvDzVCcvVuYISC5oku5v6o+B\nIug8taRhcXN8/9gLQ9akrGG73z+udAoZ2+v1k7VZ704steLM9pf7aY+L6kR2Qmc7\nE4j/WM9Sv8CHUaJG41MRAbjdHtGhzGcsZwf/mNjyjW3UalI1dih2muhPTSAZT0xL\n+wIDAQAB\n-----END PUBLIC KEY-----\n"
  }
]