├── .github
└── workflows
│ └── tests.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── src
└── OAuth2
│ ├── Autoloader.php
│ ├── ClientAssertionType
│ ├── ClientAssertionTypeInterface.php
│ └── HttpBasic.php
│ ├── Controller
│ ├── AuthorizeController.php
│ ├── AuthorizeControllerInterface.php
│ ├── ResourceController.php
│ ├── ResourceControllerInterface.php
│ ├── TokenController.php
│ └── TokenControllerInterface.php
│ ├── Encryption
│ ├── EncryptionInterface.php
│ ├── FirebaseJwt.php
│ └── Jwt.php
│ ├── GrantType
│ ├── AuthorizationCode.php
│ ├── ClientCredentials.php
│ ├── GrantTypeInterface.php
│ ├── JwtBearer.php
│ ├── RefreshToken.php
│ └── UserCredentials.php
│ ├── OpenID
│ ├── Controller
│ │ ├── AuthorizeController.php
│ │ ├── AuthorizeControllerInterface.php
│ │ ├── UserInfoController.php
│ │ └── UserInfoControllerInterface.php
│ ├── GrantType
│ │ └── AuthorizationCode.php
│ ├── ResponseType
│ │ ├── AuthorizationCode.php
│ │ ├── AuthorizationCodeInterface.php
│ │ ├── CodeIdToken.php
│ │ ├── CodeIdTokenInterface.php
│ │ ├── IdToken.php
│ │ ├── IdTokenInterface.php
│ │ ├── IdTokenToken.php
│ │ └── IdTokenTokenInterface.php
│ └── Storage
│ │ ├── AuthorizationCodeInterface.php
│ │ └── UserClaimsInterface.php
│ ├── Request.php
│ ├── RequestInterface.php
│ ├── Response.php
│ ├── ResponseInterface.php
│ ├── ResponseType
│ ├── AccessToken.php
│ ├── AccessTokenInterface.php
│ ├── AuthorizationCode.php
│ ├── AuthorizationCodeInterface.php
│ ├── JwtAccessToken.php
│ └── ResponseTypeInterface.php
│ ├── Scope.php
│ ├── ScopeInterface.php
│ ├── Server.php
│ ├── Storage
│ ├── AccessTokenInterface.php
│ ├── AuthorizationCodeInterface.php
│ ├── Cassandra.php
│ ├── ClientCredentialsInterface.php
│ ├── ClientInterface.php
│ ├── CouchbaseDB.php
│ ├── DynamoDB.php
│ ├── JwtAccessToken.php
│ ├── JwtAccessTokenInterface.php
│ ├── JwtBearerInterface.php
│ ├── Memory.php
│ ├── Mongo.php
│ ├── MongoDB.php
│ ├── Pdo.php
│ ├── PublicKeyInterface.php
│ ├── Redis.php
│ ├── RefreshTokenInterface.php
│ ├── ScopeInterface.php
│ └── UserCredentialsInterface.php
│ └── TokenType
│ ├── Bearer.php
│ ├── Mac.php
│ └── TokenTypeInterface.php
└── test
├── OAuth2
├── AutoloadTest.php
├── Controller
│ ├── AuthorizeControllerTest.php
│ ├── ResourceControllerTest.php
│ └── TokenControllerTest.php
├── Encryption
│ ├── FirebaseJwtTest.php
│ └── JwtTest.php
├── GrantType
│ ├── AuthorizationCodeTest.php
│ ├── ClientCredentialsTest.php
│ ├── ImplicitTest.php
│ ├── JwtBearerTest.php
│ ├── RefreshTokenTest.php
│ └── UserCredentialsTest.php
├── OpenID
│ ├── Controller
│ │ ├── AuthorizeControllerTest.php
│ │ └── UserInfoControllerTest.php
│ ├── GrantType
│ │ └── AuthorizationCodeTest.php
│ ├── ResponseType
│ │ ├── CodeIdTokenTest.php
│ │ ├── IdTokenTest.php
│ │ └── IdTokenTokenTest.php
│ └── Storage
│ │ ├── AuthorizationCodeTest.php
│ │ └── UserClaimsTest.php
├── RequestTest.php
├── ResponseTest.php
├── ResponseType
│ ├── AccessTokenTest.php
│ └── JwtAccessTokenTest.php
├── ScopeTest.php
├── ServerTest.php
├── Storage
│ ├── AccessTokenTest.php
│ ├── AuthorizationCodeTest.php
│ ├── ClientCredentialsTest.php
│ ├── ClientTest.php
│ ├── DynamoDBTest.php
│ ├── JwtAccessTokenTest.php
│ ├── JwtBearerTest.php
│ ├── PdoTest.php
│ ├── PublicKeyTest.php
│ ├── RefreshTokenTest.php
│ ├── ScopeTest.php
│ └── UserCredentialsTest.php
└── TokenType
│ └── BearerTest.php
├── bootstrap.php
├── config
├── keys
│ ├── id_rsa
│ └── id_rsa.pub
└── storage.json
└── lib
└── OAuth2
├── Request
└── TestRequest.php
└── Storage
├── BaseTest.php
├── Bootstrap.php
└── NullStorage.php
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Test Suite
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | services:
11 | redis:
12 | image: redis
13 | ports:
14 | - 6379:6379
15 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
16 | mongodb:
17 | image: mongo
18 | ports:
19 | - 27017:27017
20 | myriadb:
21 | image: mariadb
22 | env:
23 | MYSQL_ROOT_PASSWORD: root
24 | ports:
25 | - 3808:3808
26 | - 3306:3306
27 | postgres:
28 | image: postgres
29 | env:
30 | POSTGRES_DB: oauth2_server_php
31 | POSTGRES_USER: postgres
32 | POSTGRES_PASSWORD: postgres
33 | ports:
34 | - 5432:5432
35 | options: --health-cmd="pg_isready -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5
36 | strategy:
37 | matrix:
38 | php: [ 7.2, 7.3, 7.4, "8.0", 8.1, 8.2 ]
39 | name: "PHP ${{ matrix.php }} Unit Test"
40 | steps:
41 | - uses: actions/checkout@v2
42 | - uses: codecov/codecov-action@v1
43 | - name: Setup PHP
44 | uses: shivammathur/setup-php@v2
45 | with:
46 | php-version: ${{ matrix.php }}
47 | extensions: mongodb, mbstring, intl, redis, pdo_mysql
48 | - name: Install composer dependencies
49 | uses: nick-invision/retry@v1
50 | with:
51 | timeout_minutes: 10
52 | max_attempts: 3
53 | command: composer install
54 | - name: Run PHPUnit
55 | run: vendor/bin/phpunit -v
56 | phpstan:
57 | name: "PHPStan"
58 | runs-on: ubuntu-latest
59 | steps:
60 | - uses: actions/checkout@v2
61 | - name: Setup PHP
62 | uses: shivammathur/setup-php@v2
63 | with:
64 | php-version: 8.1
65 | - name: Install composer dependencies
66 | uses: nick-invision/retry@v1
67 | with:
68 | timeout_minutes: 10
69 | max_attempts: 3
70 | command: composer install
71 | - name: Run PHPStan
72 | run: |
73 | composer require phpstan/phpstan
74 | vendor/bin/phpstan analyse --level=0 src/
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2014 Brent Shaffer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | oauth2-server-php
2 | =================
3 |
4 | [](https://packagist.org/packages/bshaffer/oauth2-server-php)
5 |
6 | View the [complete documentation](https://bshaffer.github.io/oauth2-server-php-docs/)
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bshaffer/oauth2-server-php",
3 | "description":"OAuth2 Server for PHP",
4 | "keywords":["oauth","oauth2","auth"],
5 | "type":"library",
6 | "license":"MIT",
7 | "authors":[
8 | {
9 | "name":"Brent Shaffer",
10 | "email": "bshafs@gmail.com",
11 | "homepage":"http://brentertainment.com"
12 | }
13 | ],
14 | "homepage": "http://github.com/bshaffer/oauth2-server-php",
15 | "autoload": {
16 | "psr-0": { "OAuth2": "src/" }
17 | },
18 | "require":{
19 | "php":">=7.2"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "^7.5||^8.0",
23 | "aws/aws-sdk-php": "^2.8",
24 | "firebase/php-jwt": "^6.4",
25 | "predis/predis": "^1.1",
26 | "thobbs/phpcassa": "dev-master",
27 | "yoast/phpunit-polyfills": "^1.0"
28 | },
29 | "suggest": {
30 | "predis/predis": "Required to use Redis storage",
31 | "thobbs/phpcassa": "Required to use Cassandra storage",
32 | "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage",
33 | "firebase/php-jwt": "~v6.4 is required to use JWT features",
34 | "mongodb/mongodb": "^1.1 is required to use MongoDB storage"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/OAuth2/Autoloader.php:
--------------------------------------------------------------------------------
1 |
9 | * @license MIT License
10 | */
11 | class Autoloader
12 | {
13 | /**
14 | * @var string
15 | */
16 | private $dir;
17 |
18 | /**
19 | * @param string $dir
20 | */
21 | public function __construct($dir = null)
22 | {
23 | if (is_null($dir)) {
24 | $dir = dirname(__FILE__).'/..';
25 | }
26 | $this->dir = $dir;
27 | }
28 |
29 | /**
30 | * Registers OAuth2\Autoloader as an SPL autoloader.
31 | */
32 | public static function register($dir = null)
33 | {
34 | ini_set('unserialize_callback_func', 'spl_autoload_call');
35 | spl_autoload_register(array(new self($dir), 'autoload'));
36 | }
37 |
38 | /**
39 | * Handles autoloading of classes.
40 | *
41 | * @param string $class - A class name.
42 | * @return void
43 | */
44 | public function autoload($class)
45 | {
46 | if (0 !== strpos($class, 'OAuth2')) {
47 | return;
48 | }
49 |
50 | if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) {
51 | require $file;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class HttpBasic implements ClientAssertionTypeInterface
16 | {
17 | private $clientData;
18 |
19 | protected $storage;
20 | protected $config;
21 |
22 | /**
23 | * Config array $config should look as follows:
24 | * @code
25 | * $config = array(
26 | * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
27 | * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
28 | * );
29 | * @endcode
30 | *
31 | * @param ClientCredentialsInterface $storage Storage
32 | * @param array $config Configuration options for the server
33 | */
34 | public function __construct(ClientCredentialsInterface $storage, array $config = array())
35 | {
36 | $this->storage = $storage;
37 | $this->config = array_merge(array(
38 | 'allow_credentials_in_request_body' => true,
39 | 'allow_public_clients' => true,
40 | ), $config);
41 | }
42 |
43 | /**
44 | * Validate the OAuth request
45 | *
46 | * @param RequestInterface $request
47 | * @param ResponseInterface $response
48 | * @return bool|mixed
49 | * @throws LogicException
50 | */
51 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
52 | {
53 | if (!$clientData = $this->getClientCredentials($request, $response)) {
54 | return false;
55 | }
56 |
57 | if (!isset($clientData['client_id'])) {
58 | throw new LogicException('the clientData array must have "client_id" set');
59 | }
60 |
61 | if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
62 | if (!$this->config['allow_public_clients']) {
63 | $response->setError(400, 'invalid_client', 'client credentials are required');
64 |
65 | return false;
66 | }
67 |
68 | if (!$this->storage->isPublicClient($clientData['client_id'])) {
69 | $response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret');
70 |
71 | return false;
72 | }
73 | } elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
74 | $response->setError(400, 'invalid_client', 'The client credentials are invalid');
75 |
76 | return false;
77 | }
78 |
79 | $this->clientData = $clientData;
80 |
81 | return true;
82 | }
83 |
84 | /**
85 | * Get the client id
86 | *
87 | * @return mixed
88 | */
89 | public function getClientId()
90 | {
91 | return $this->clientData['client_id'];
92 | }
93 |
94 | /**
95 | * Internal function used to get the client credentials from HTTP basic
96 | * auth or POST data.
97 | *
98 | * According to the spec (draft 20), the client_id can be provided in
99 | * the Basic Authorization header (recommended) or via GET/POST.
100 | *
101 | * @param RequestInterface $request
102 | * @param ResponseInterface $response
103 | * @return array|null A list containing the client identifier and password, for example:
104 | * @code
105 | * return array(
106 | * "client_id" => CLIENT_ID, // REQUIRED the client id
107 | * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
108 | * );
109 | * @endcode
110 | *
111 | * @see http://tools.ietf.org/html/rfc6749#section-2.3.1
112 | *
113 | * @ingroup oauth2_section_2
114 | */
115 | public function getClientCredentials(RequestInterface $request, ?ResponseInterface $response = null)
116 | {
117 | if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) {
118 | return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW'));
119 | }
120 |
121 | if ($this->config['allow_credentials_in_request_body']) {
122 | // Using POST for HttpBasic authorization is not recommended, but is supported by specification
123 | if (!is_null($request->request('client_id'))) {
124 | /**
125 | * client_secret can be null if the client's password is an empty string
126 | * @see http://tools.ietf.org/html/rfc6749#section-2.3.1
127 | */
128 | return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
129 | }
130 | }
131 |
132 | if ($response) {
133 | $message = $this->config['allow_credentials_in_request_body'] ? ' or body' : '';
134 | $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message);
135 | }
136 |
137 | return null;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/OAuth2/Controller/AuthorizeControllerInterface.php:
--------------------------------------------------------------------------------
1 | somehowDetermineUserId();
16 | * $is_authorized = $this->somehowDetermineUserAuthorization();
17 | * $response = new OAuth2\Response();
18 | * $authorizeController->handleAuthorizeRequest(
19 | * OAuth2\Request::createFromGlobals(),
20 | * $response,
21 | * $is_authorized,
22 | * $user_id
23 | * );
24 | * $response->send();
25 | * @endcode
26 | */
27 | interface AuthorizeControllerInterface
28 | {
29 | /**
30 | * List of possible authentication response types.
31 | * The "authorization_code" mechanism exclusively supports 'code'
32 | * and the "implicit" mechanism exclusively supports 'token'.
33 | *
34 | * @var string
35 | * @see http://tools.ietf.org/html/rfc6749#section-4.1.1
36 | * @see http://tools.ietf.org/html/rfc6749#section-4.2.1
37 | */
38 | const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code';
39 | const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
40 |
41 | /**
42 | * Handle the OAuth request
43 | *
44 | * @param RequestInterface $request
45 | * @param ResponseInterface $response
46 | * @param $is_authorized
47 | * @param null $user_id
48 | * @return mixed
49 | */
50 | public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
51 |
52 | /**
53 | * @param RequestInterface $request
54 | * @param ResponseInterface $response
55 | * @return bool
56 | */
57 | public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
58 | }
59 |
--------------------------------------------------------------------------------
/src/OAuth2/Controller/ResourceController.php:
--------------------------------------------------------------------------------
1 | tokenType = $tokenType;
53 | $this->tokenStorage = $tokenStorage;
54 |
55 | $this->config = array_merge(array(
56 | 'www_realm' => 'Service',
57 | ), $config);
58 |
59 | if (is_null($scopeUtil)) {
60 | $scopeUtil = new Scope();
61 | }
62 | $this->scopeUtil = $scopeUtil;
63 | }
64 |
65 | /**
66 | * Verify the resource request
67 | *
68 | * @param RequestInterface $request
69 | * @param ResponseInterface $response
70 | * @param null $scope
71 | * @return bool
72 | */
73 | public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
74 | {
75 | $token = $this->getAccessTokenData($request, $response);
76 |
77 | // Check if we have token data
78 | if (is_null($token)) {
79 | return false;
80 | }
81 |
82 | /**
83 | * Check scope, if provided
84 | * If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
85 | * @see http://tools.ietf.org/html/rfc6750#section-3.1
86 | */
87 | if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
88 | $response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
89 | $response->addHttpHeaders(array(
90 | 'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
91 | $this->tokenType->getTokenType(),
92 | $this->config['www_realm'],
93 | $scope,
94 | $response->getParameter('error'),
95 | $response->getParameter('error_description')
96 | )
97 | ));
98 |
99 | return false;
100 | }
101 |
102 | // allow retrieval of the token
103 | $this->token = $token;
104 |
105 | return (bool) $token;
106 | }
107 |
108 | /**
109 | * Get access token data.
110 | *
111 | * @param RequestInterface $request
112 | * @param ResponseInterface $response
113 | * @return array|null
114 | */
115 | public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
116 | {
117 | // Get the token parameter
118 | if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) {
119 | // Get the stored token data (from the implementing subclass)
120 | // Check we have a well formed token
121 | // Check token expiration (expires is a mandatory paramter)
122 | if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
123 | $response->setError(401, 'invalid_token', 'The access token provided is invalid');
124 | } elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
125 | $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
126 | } elseif (time() > $token["expires"]) {
127 | $response->setError(401, 'invalid_token', 'The access token provided has expired');
128 | } else {
129 | return $token;
130 | }
131 | }
132 |
133 | $authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']);
134 |
135 | if ($error = $response->getParameter('error')) {
136 | $authHeader = sprintf('%s, error="%s"', $authHeader, $error);
137 | if ($error_description = $response->getParameter('error_description')) {
138 | $authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description);
139 | }
140 | }
141 |
142 | $response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));
143 |
144 | return null;
145 | }
146 |
147 | /**
148 | * convenience method to allow retrieval of the token.
149 | *
150 | * @return array
151 | */
152 | public function getToken()
153 | {
154 | return $this->token;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/OAuth2/Controller/ResourceControllerInterface.php:
--------------------------------------------------------------------------------
1 | verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
15 | * $response->send(); // authorization failed
16 | * die();
17 | * }
18 | * return json_encode($resource); // valid token! Send the stuff!
19 | * @endcode
20 | */
21 | interface ResourceControllerInterface
22 | {
23 | /**
24 | * Verify the resource request
25 | *
26 | * @param RequestInterface $request - Request object
27 | * @param ResponseInterface $response - Response object
28 | * @param string $scope
29 | * @return mixed
30 | */
31 | public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
32 |
33 | /**
34 | * Get access token data.
35 | *
36 | * @param RequestInterface $request - Request object
37 | * @param ResponseInterface $response - Response object
38 | * @return mixed
39 | */
40 | public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
41 | }
42 |
--------------------------------------------------------------------------------
/src/OAuth2/Controller/TokenControllerInterface.php:
--------------------------------------------------------------------------------
1 | handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response());
15 | * $response->send();
16 | * @endcode
17 | */
18 | interface TokenControllerInterface
19 | {
20 | /**
21 | * Handle the token request
22 | *
23 | * @param RequestInterface $request - The current http request
24 | * @param ResponseInterface $response - An instance of OAuth2\ResponseInterface to contain the response data
25 | */
26 | public function handleTokenRequest(RequestInterface $request, ResponseInterface $response);
27 |
28 | /**
29 | * Grant or deny a requested access token.
30 | * This would be called from the "/token" endpoint as defined in the spec.
31 | * You can call your endpoint whatever you want.
32 | *
33 | * @param RequestInterface $request - Request object to grant access token
34 | * @param ResponseInterface $response - Response object
35 | *
36 | * @return mixed
37 | */
38 | public function grantAccessToken(RequestInterface $request, ResponseInterface $response);
39 | }
40 |
--------------------------------------------------------------------------------
/src/OAuth2/Encryption/EncryptionInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class FirebaseJwt implements EncryptionInterface
13 | {
14 | public function __construct()
15 | {
16 | if (!class_exists(JWT::class)) {
17 | throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"');
18 | }
19 | }
20 |
21 | public function encode($payload, $key, $alg = 'HS256', $keyId = null)
22 | {
23 | return JWT::encode($payload, $key, $alg, $keyId);
24 | }
25 |
26 | public function decode($jwt, $key = null, $allowedAlgorithms = null)
27 | {
28 | try {
29 | //Maintain BC: Do not verify if no algorithms are passed in.
30 | if (!$allowedAlgorithms) {
31 | $tks = \explode('.', $jwt);
32 | if (\count($tks) === 3) {
33 | [$headb64] = $tks;
34 | $headerRaw = JWT::urlsafeB64Decode($headb64);
35 | if (($header = JWT::jsonDecode($headerRaw))) {
36 | $key = new Key($key, $header->alg);
37 | }
38 | }
39 | } elseif(is_array($allowedAlgorithms)) {
40 | $key = new Key($key, $allowedAlgorithms[0]);
41 | } else {
42 | $key = new Key($key, $allowedAlgorithms);
43 | }
44 |
45 | return (array) JWT::decode($jwt, $key);
46 | } catch (\Exception $e) {
47 | return false;
48 | }
49 | }
50 |
51 | public function urlSafeB64Encode($data)
52 | {
53 | return JWT::urlsafeB64Encode($data);
54 | }
55 |
56 | public function urlSafeB64Decode($b64)
57 | {
58 | return JWT::urlsafeB64Decode($b64);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/OAuth2/Encryption/Jwt.php:
--------------------------------------------------------------------------------
1 | generateJwtHeader($payload, $algo);
23 |
24 | $segments = array(
25 | $this->urlSafeB64Encode(json_encode($header)),
26 | $this->urlSafeB64Encode(json_encode($payload))
27 | );
28 |
29 | $signing_input = implode('.', $segments);
30 |
31 | $signature = $this->sign($signing_input, $key, $algo);
32 | $segments[] = $this->urlsafeB64Encode($signature);
33 |
34 | return implode('.', $segments);
35 | }
36 |
37 | /**
38 | * @param string $jwt
39 | * @param null $key
40 | * @param array|bool $allowedAlgorithms
41 | * @return bool|mixed
42 | */
43 | public function decode($jwt, $key = null, $allowedAlgorithms = true)
44 | {
45 | if (!strpos($jwt, '.')) {
46 | return false;
47 | }
48 |
49 | $tks = explode('.', $jwt);
50 |
51 | if (count($tks) != 3) {
52 | return false;
53 | }
54 |
55 | list($headb64, $payloadb64, $cryptob64) = $tks;
56 |
57 | if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
58 | return false;
59 | }
60 |
61 | if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
62 | return false;
63 | }
64 |
65 | $sig = $this->urlSafeB64Decode($cryptob64);
66 |
67 | if ((bool) $allowedAlgorithms) {
68 | if (!isset($header['alg'])) {
69 | return false;
70 | }
71 |
72 | // check if bool arg supplied here to maintain BC
73 | if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
74 | return false;
75 | }
76 |
77 | if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
78 | return false;
79 | }
80 | }
81 |
82 | return $payload;
83 | }
84 |
85 | /**
86 | * @param $signature
87 | * @param $input
88 | * @param $key
89 | * @param string $algo
90 | * @return bool
91 | * @throws InvalidArgumentException
92 | */
93 | private function verifySignature($signature, $input, $key, $algo = 'HS256')
94 | {
95 | // use constants when possible, for HipHop support
96 | switch ($algo) {
97 | case'HS256':
98 | case'HS384':
99 | case'HS512':
100 | return $this->hash_equals(
101 | $this->sign($input, $key, $algo),
102 | $signature
103 | );
104 |
105 | case 'RS256':
106 | return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
107 |
108 | case 'RS384':
109 | return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
110 |
111 | case 'RS512':
112 | return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
113 |
114 | default:
115 | throw new InvalidArgumentException("Unsupported or invalid signing algorithm.");
116 | }
117 | }
118 |
119 | /**
120 | * @param $input
121 | * @param $key
122 | * @param string $algo
123 | * @return string
124 | * @throws Exception
125 | */
126 | private function sign($input, $key, $algo = 'HS256')
127 | {
128 | switch ($algo) {
129 | case 'HS256':
130 | return hash_hmac('sha256', $input, $key, true);
131 |
132 | case 'HS384':
133 | return hash_hmac('sha384', $input, $key, true);
134 |
135 | case 'HS512':
136 | return hash_hmac('sha512', $input, $key, true);
137 |
138 | case 'RS256':
139 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
140 |
141 | case 'RS384':
142 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
143 |
144 | case 'RS512':
145 | return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
146 |
147 | default:
148 | throw new Exception("Unsupported or invalid signing algorithm.");
149 | }
150 | }
151 |
152 | /**
153 | * @param $input
154 | * @param $key
155 | * @param string $algo
156 | * @return mixed
157 | * @throws Exception
158 | */
159 | private function generateRSASignature($input, $key, $algo)
160 | {
161 | if (!openssl_sign($input, $signature, $key, $algo)) {
162 | throw new Exception("Unable to sign data.");
163 | }
164 |
165 | return $signature;
166 | }
167 |
168 | /**
169 | * @param string $data
170 | * @return string
171 | */
172 | public function urlSafeB64Encode($data)
173 | {
174 | $b64 = base64_encode($data);
175 | $b64 = str_replace(array('+', '/', "\r", "\n", '='),
176 | array('-', '_'),
177 | $b64);
178 |
179 | return $b64;
180 | }
181 |
182 | /**
183 | * @param string $b64
184 | * @return mixed|string
185 | */
186 | public function urlSafeB64Decode($b64)
187 | {
188 | $b64 = str_replace(array('-', '_'),
189 | array('+', '/'),
190 | $b64);
191 |
192 | return base64_decode($b64);
193 | }
194 |
195 | /**
196 | * Override to create a custom header
197 | */
198 | protected function generateJwtHeader($payload, $algorithm)
199 | {
200 | return array(
201 | 'typ' => 'JWT',
202 | 'alg' => $algorithm,
203 | );
204 | }
205 |
206 | /**
207 | * @param string $a
208 | * @param string $b
209 | * @return bool
210 | */
211 | protected function hash_equals($a, $b)
212 | {
213 | if (function_exists('hash_equals')) {
214 | return hash_equals($a, $b);
215 | }
216 | $diff = strlen($a) ^ strlen($b);
217 | for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
218 | $diff |= ord($a[$i]) ^ ord($b[$i]);
219 | }
220 |
221 | return $diff === 0;
222 | }
223 | }
--------------------------------------------------------------------------------
/src/OAuth2/GrantType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class AuthorizationCode implements GrantTypeInterface
15 | {
16 | /**
17 | * @var AuthorizationCodeInterface
18 | */
19 | protected $storage;
20 |
21 | /**
22 | * @var array
23 | */
24 | protected $authCode;
25 |
26 | /**
27 | * @param AuthorizationCodeInterface $storage - REQUIRED Storage class for retrieving authorization code information
28 | */
29 | public function __construct(AuthorizationCodeInterface $storage)
30 | {
31 | $this->storage = $storage;
32 | }
33 |
34 | /**
35 | * @return string
36 | */
37 | public function getQueryStringIdentifier()
38 | {
39 | return 'authorization_code';
40 | }
41 |
42 | /**
43 | * Validate the OAuth request
44 | *
45 | * @param RequestInterface $request
46 | * @param ResponseInterface $response
47 | * @return bool
48 | * @throws Exception
49 | */
50 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
51 | {
52 | if (!$request->request('code')) {
53 | $response->setError(400, 'invalid_request', 'Missing parameter: "code" is required');
54 |
55 | return false;
56 | }
57 |
58 | $code = $request->request('code');
59 | if (!$authCode = $this->storage->getAuthorizationCode($code)) {
60 | $response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client');
61 |
62 | return false;
63 | }
64 |
65 | /*
66 | * 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request
67 | * @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3
68 | */
69 | if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
70 | if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != urldecode($authCode['redirect_uri'])) {
71 | $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
72 |
73 | return false;
74 | }
75 | }
76 |
77 | if (!isset($authCode['expires'])) {
78 | throw new \Exception('Storage must return authcode with a value for "expires"');
79 | }
80 |
81 | if ($authCode["expires"] < time()) {
82 | $response->setError(400, 'invalid_grant', "The authorization code has expired");
83 |
84 | return false;
85 | }
86 |
87 | if (isset($authCode['code_challenge']) && $authCode['code_challenge']) {
88 | if (!($code_verifier = $request->request('code_verifier'))) {
89 | $response->setError(400, 'code_verifier_missing', "The PKCE code verifier parameter is required.");
90 |
91 | return false;
92 | }
93 | // Validate code_verifier according to RFC-7636
94 | // @see: https://tools.ietf.org/html/rfc7636#section-4.1
95 | if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $code_verifier) !== 1) {
96 | $response->setError(400, 'code_verifier_invalid', "The PKCE code verifier parameter is invalid.");
97 |
98 | return false;
99 | }
100 | $code_verifier = $request->request('code_verifier');
101 | switch ($authCode['code_challenge_method']) {
102 | case 'S256':
103 | $code_verifier_hashed = strtr(rtrim(base64_encode(hash('sha256', $code_verifier, true)), '='), '+/', '-_');
104 | break;
105 |
106 | case 'plain':
107 | $code_verifier_hashed = $code_verifier;
108 | break;
109 |
110 | default:
111 | $response->setError(400, 'code_challenge_method_invalid', "Unknown PKCE code challenge method.");
112 |
113 | return FALSE;
114 | }
115 | if ($code_verifier_hashed !== $authCode['code_challenge']) {
116 | $response->setError(400, 'code_verifier_mismatch', "The PKCE code verifier parameter does not match the code challenge.");
117 |
118 | return FALSE;
119 | }
120 | }
121 |
122 | if (!isset($authCode['code'])) {
123 | $authCode['code'] = $code; // used to expire the code after the access token is granted
124 | }
125 |
126 | $this->authCode = $authCode;
127 |
128 | return true;
129 | }
130 |
131 | /**
132 | * Get the client id
133 | *
134 | * @return mixed
135 | */
136 | public function getClientId()
137 | {
138 | return $this->authCode['client_id'];
139 | }
140 |
141 | /**
142 | * Get the scope
143 | *
144 | * @return string
145 | */
146 | public function getScope()
147 | {
148 | return isset($this->authCode['scope']) ? $this->authCode['scope'] : null;
149 | }
150 |
151 | /**
152 | * Get the user id
153 | *
154 | * @return mixed
155 | */
156 | public function getUserId()
157 | {
158 | return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null;
159 | }
160 |
161 | /**
162 | * Create access token
163 | *
164 | * @param AccessTokenInterface $accessToken
165 | * @param mixed $client_id - client identifier related to the access token.
166 | * @param mixed $user_id - user id associated with the access token
167 | * @param string $scope - scopes to be stored in space-separated string.
168 | * @return array
169 | */
170 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
171 | {
172 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope);
173 | $this->storage->expireAuthorizationCode($this->authCode['code']);
174 |
175 | return $token;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/OAuth2/GrantType/ClientCredentials.php:
--------------------------------------------------------------------------------
1 |
11 | *
12 | * @see HttpBasic
13 | */
14 | class ClientCredentials extends HttpBasic implements GrantTypeInterface
15 | {
16 | /**
17 | * @var array
18 | */
19 | private $clientData;
20 |
21 | /**
22 | * @param ClientCredentialsInterface $storage
23 | * @param array $config
24 | */
25 | public function __construct(ClientCredentialsInterface $storage, array $config = array())
26 | {
27 | /**
28 | * The client credentials grant type MUST only be used by confidential clients
29 | *
30 | * @see http://tools.ietf.org/html/rfc6749#section-4.4
31 | */
32 | $config['allow_public_clients'] = false;
33 |
34 | parent::__construct($storage, $config);
35 | }
36 |
37 | /**
38 | * Get query string identifier
39 | *
40 | * @return string
41 | */
42 | public function getQueryStringIdentifier()
43 | {
44 | return 'client_credentials';
45 | }
46 |
47 | /**
48 | * Get scope
49 | *
50 | * @return string|null
51 | */
52 | public function getScope()
53 | {
54 | $this->loadClientData();
55 |
56 | return isset($this->clientData['scope']) ? $this->clientData['scope'] : null;
57 | }
58 |
59 | /**
60 | * Get user id
61 | *
62 | * @return mixed
63 | */
64 | public function getUserId()
65 | {
66 | $this->loadClientData();
67 |
68 | return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null;
69 | }
70 |
71 | /**
72 | * Create access token
73 | *
74 | * @param AccessTokenInterface $accessToken
75 | * @param mixed $client_id - client identifier related to the access token.
76 | * @param mixed $user_id - user id associated with the access token
77 | * @param string $scope - scopes to be stored in space-separated string.
78 | * @return array
79 | */
80 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
81 | {
82 | /**
83 | * Client Credentials Grant does NOT include a refresh token
84 | *
85 | * @see http://tools.ietf.org/html/rfc6749#section-4.4.3
86 | */
87 | $includeRefreshToken = false;
88 |
89 | return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
90 | }
91 |
92 | private function loadClientData()
93 | {
94 | if (!$this->clientData) {
95 | $this->clientData = $this->storage->getClientDetails($this->getClientId());
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/OAuth2/GrantType/GrantTypeInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class RefreshToken implements GrantTypeInterface
14 | {
15 | /**
16 | * @var array
17 | */
18 | private $refreshToken;
19 |
20 | /**
21 | * @var RefreshTokenInterface
22 | */
23 | protected $storage;
24 |
25 | /**
26 | * @var array
27 | */
28 | protected $config;
29 |
30 | /**
31 | * @param RefreshTokenInterface $storage - REQUIRED Storage class for retrieving refresh token information
32 | * @param array $config - OPTIONAL Configuration options for the server
33 | * @code
34 | * $config = array(
35 | * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
36 | * 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using
37 | * );
38 | * @endcode
39 | */
40 | public function __construct(RefreshTokenInterface $storage, $config = array())
41 | {
42 | $this->config = array_merge(array(
43 | 'always_issue_new_refresh_token' => false,
44 | 'unset_refresh_token_after_use' => true
45 | ), $config);
46 |
47 | // to preserve B.C. with v1.6
48 | // @see https://github.com/bshaffer/oauth2-server-php/pull/580
49 | // @todo - remove in v2.0
50 | if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) {
51 | $this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token'];
52 | }
53 |
54 | $this->storage = $storage;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | public function getQueryStringIdentifier()
61 | {
62 | return 'refresh_token';
63 | }
64 |
65 | /**
66 | * Validate the OAuth request
67 | *
68 | * @param RequestInterface $request
69 | * @param ResponseInterface $response
70 | * @return bool|mixed|null
71 | */
72 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
73 | {
74 | if (!$request->request("refresh_token")) {
75 | $response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required');
76 |
77 | return null;
78 | }
79 |
80 | if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) {
81 | $response->setError(400, 'invalid_grant', 'Invalid refresh token');
82 |
83 | return null;
84 | }
85 |
86 | if ($refreshToken['expires'] > 0 && $refreshToken["expires"] < time()) {
87 | $response->setError(400, 'invalid_grant', 'Refresh token has expired');
88 |
89 | return null;
90 | }
91 |
92 | // store the refresh token locally so we can delete it when a new refresh token is generated
93 | $this->refreshToken = $refreshToken;
94 |
95 | return true;
96 | }
97 |
98 | /**
99 | * Get client id
100 | *
101 | * @return mixed
102 | */
103 | public function getClientId()
104 | {
105 | return $this->refreshToken['client_id'];
106 | }
107 |
108 | /**
109 | * Get user id
110 | *
111 | * @return mixed|null
112 | */
113 | public function getUserId()
114 | {
115 | return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null;
116 | }
117 |
118 | /**
119 | * Get scope
120 | *
121 | * @return null|string
122 | */
123 | public function getScope()
124 | {
125 | return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
126 | }
127 |
128 | /**
129 | * Create access token
130 | *
131 | * @param AccessTokenInterface $accessToken
132 | * @param mixed $client_id - client identifier related to the access token.
133 | * @param mixed $user_id - user id associated with the access token
134 | * @param string $scope - scopes to be stored in space-separated string.
135 | * @return array
136 | */
137 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
138 | {
139 | /*
140 | * It is optional to force a new refresh token when a refresh token is used.
141 | * However, if a new refresh token is issued, the old one MUST be expired
142 | * @see http://tools.ietf.org/html/rfc6749#section-6
143 | */
144 | $issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
145 | $unsetRefreshToken = $this->config['unset_refresh_token_after_use'];
146 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken);
147 |
148 | if ($unsetRefreshToken) {
149 | $this->storage->unsetRefreshToken($this->refreshToken['refresh_token']);
150 | }
151 |
152 | return $token;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/OAuth2/GrantType/UserCredentials.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class UserCredentials implements GrantTypeInterface
15 | {
16 | /**
17 | * @var array
18 | */
19 | private $userInfo;
20 |
21 | /**
22 | * @var UserCredentialsInterface
23 | */
24 | protected $storage;
25 |
26 | /**
27 | * @param UserCredentialsInterface $storage - REQUIRED Storage class for retrieving user credentials information
28 | */
29 | public function __construct(UserCredentialsInterface $storage)
30 | {
31 | $this->storage = $storage;
32 | }
33 |
34 | /**
35 | * @return string
36 | */
37 | public function getQueryStringIdentifier()
38 | {
39 | return 'password';
40 | }
41 |
42 | /**
43 | * @param RequestInterface $request
44 | * @param ResponseInterface $response
45 | * @return bool|mixed|null
46 | *
47 | * @throws LogicException
48 | */
49 | public function validateRequest(RequestInterface $request, ResponseInterface $response)
50 | {
51 | if (!$request->request("password") || !$request->request("username")) {
52 | $response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required');
53 |
54 | return null;
55 | }
56 |
57 | if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) {
58 | $response->setError(401, 'invalid_grant', 'Invalid username and password combination');
59 |
60 | return null;
61 | }
62 |
63 | $userInfo = $this->storage->getUserDetails($request->request("username"));
64 |
65 | if (empty($userInfo)) {
66 | $response->setError(400, 'invalid_grant', 'Unable to retrieve user information');
67 |
68 | return null;
69 | }
70 |
71 | if (!isset($userInfo['user_id'])) {
72 | throw new \LogicException("you must set the user_id on the array returned by getUserDetails");
73 | }
74 |
75 | $this->userInfo = $userInfo;
76 |
77 | return true;
78 | }
79 |
80 | /**
81 | * Get client id
82 | *
83 | * @return mixed|null
84 | */
85 | public function getClientId()
86 | {
87 | return null;
88 | }
89 |
90 | /**
91 | * Get user id
92 | *
93 | * @return mixed
94 | */
95 | public function getUserId()
96 | {
97 | return $this->userInfo['user_id'];
98 | }
99 |
100 | /**
101 | * Get scope
102 | *
103 | * @return null|string
104 | */
105 | public function getScope()
106 | {
107 | return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null;
108 | }
109 |
110 | /**
111 | * Create access token
112 | *
113 | * @param AccessTokenInterface $accessToken
114 | * @param mixed $client_id - client identifier related to the access token.
115 | * @param mixed $user_id - user id associated with the access token
116 | * @param string $scope - scopes to be stored in space-separated string.
117 | * @return array
118 | */
119 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
120 | {
121 | return $accessToken->createAccessToken($client_id, $user_id, $scope);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/Controller/AuthorizeController.php:
--------------------------------------------------------------------------------
1 | query('prompt', 'consent');
40 | if ($prompt == 'none') {
41 | if (is_null($user_id)) {
42 | $error = 'login_required';
43 | $error_message = 'The user must log in';
44 | } else {
45 | $error = 'interaction_required';
46 | $error_message = 'The user must grant access to your application';
47 | }
48 | } else {
49 | $error = 'consent_required';
50 | $error_message = 'The user denied access to your application';
51 | }
52 |
53 | $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message);
54 | }
55 |
56 | /**
57 | * @TODO: add dependency injection for the parameters in this method
58 | *
59 | * @param RequestInterface $request
60 | * @param ResponseInterface $response
61 | * @param mixed $user_id
62 | * @return array
63 | */
64 | protected function buildAuthorizeParameters($request, $response, $user_id)
65 | {
66 | if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) {
67 | return;
68 | }
69 |
70 | // Generate an id token if needed.
71 | if ($this->needsIdToken($this->getScope()) && $this->getResponseType() == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
72 | $params['id_token'] = $this->responseTypes['id_token']->createIdToken($this->getClientId(), $user_id, $this->nonce);
73 | }
74 |
75 | // add the nonce to return with the redirect URI
76 | $params['nonce'] = $this->nonce;
77 |
78 | // Add PKCE code challenge.
79 | $params['code_challenge'] = $this->code_challenge;
80 | $params['code_challenge_method'] = $this->code_challenge_method;
81 |
82 | return $params;
83 | }
84 |
85 | /**
86 | * @param RequestInterface $request
87 | * @param ResponseInterface $response
88 | * @return bool
89 | */
90 | public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
91 | {
92 | if (!parent::validateAuthorizeRequest($request, $response)) {
93 | return false;
94 | }
95 |
96 | $nonce = $request->query('nonce');
97 |
98 | // Validate required nonce for "id_token" and "id_token token"
99 | if (!$nonce && in_array($this->getResponseType(), array(self::RESPONSE_TYPE_ID_TOKEN, self::RESPONSE_TYPE_ID_TOKEN_TOKEN))) {
100 | $response->setError(400, 'invalid_nonce', 'This application requires you specify a nonce parameter');
101 |
102 | return false;
103 | }
104 |
105 | $this->nonce = $nonce;
106 |
107 | $code_challenge = $request->query('code_challenge');
108 | $code_challenge_method = $request->query('code_challenge_method');
109 |
110 | if ($this->config['enforce_pkce']) {
111 | if (!$code_challenge) {
112 | $response->setError(400, 'missing_code_challenge', 'This application requires you provide a PKCE code challenge');
113 |
114 | return false;
115 | }
116 |
117 | if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $code_challenge) !== 1) {
118 | $response->setError(400, 'invalid_code_challenge', 'The PKCE code challenge supplied is invalid');
119 |
120 | return false;
121 | }
122 |
123 | if (!in_array($code_challenge_method, array('plain', 'S256'), true)) {
124 | $response->setError(400, 'missing_code_challenge_method', 'This application requires you specify a PKCE code challenge method');
125 |
126 | return false;
127 | }
128 | }
129 |
130 | $this->code_challenge = $code_challenge;
131 | $this->code_challenge_method = $code_challenge_method;
132 |
133 | return true;
134 | }
135 |
136 | /**
137 | * Array of valid response types
138 | *
139 | * @return array
140 | */
141 | protected function getValidResponseTypes()
142 | {
143 | return array(
144 | self::RESPONSE_TYPE_ACCESS_TOKEN,
145 | self::RESPONSE_TYPE_AUTHORIZATION_CODE,
146 | self::RESPONSE_TYPE_ID_TOKEN,
147 | self::RESPONSE_TYPE_ID_TOKEN_TOKEN,
148 | self::RESPONSE_TYPE_CODE_ID_TOKEN,
149 | );
150 | }
151 |
152 | /**
153 | * Returns whether the current request needs to generate an id token.
154 | *
155 | * ID Tokens are a part of the OpenID Connect specification, so this
156 | * method checks whether OpenID Connect is enabled in the server settings
157 | * and whether the openid scope was requested.
158 | *
159 | * @param string $request_scope - A space-separated string of scopes.
160 | * @return boolean - TRUE if an id token is needed, FALSE otherwise.
161 | */
162 | public function needsIdToken($request_scope)
163 | {
164 | // see if the "openid" scope exists in the requested scope
165 | return $this->scopeUtil->checkScope('openid', $request_scope);
166 | }
167 |
168 | /**
169 | * @return mixed
170 | */
171 | public function getNonce()
172 | {
173 | return $this->nonce;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php:
--------------------------------------------------------------------------------
1 | userClaimsStorage = $userClaimsStorage;
38 | }
39 |
40 | /**
41 | * Handle the user info request
42 | *
43 | * @param RequestInterface $request
44 | * @param ResponseInterface $response
45 | * @return void
46 | */
47 | public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response)
48 | {
49 | if (!$this->verifyResourceRequest($request, $response, 'openid')) {
50 | return;
51 | }
52 |
53 | $token = $this->getToken();
54 | $claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']);
55 | // The sub Claim MUST always be returned in the UserInfo Response.
56 | // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
57 | $claims += array(
58 | 'sub' => $token['user_id'],
59 | );
60 | $response->addParameters($claims);
61 | }
62 | }
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php:
--------------------------------------------------------------------------------
1 | handleUserInfoRequest(
15 | * OAuth2\Request::createFromGlobals(),
16 | * $response
17 | * );
18 | * $response->send();
19 | * @endcode
20 | */
21 | interface UserInfoControllerInterface
22 | {
23 | /**
24 | * Handle user info request
25 | *
26 | * @param RequestInterface $request
27 | * @param ResponseInterface $response
28 | */
29 | public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response);
30 | }
31 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/GrantType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class AuthorizationCode extends BaseAuthorizationCode
12 | {
13 | /**
14 | * Create access token
15 | *
16 | * @param AccessTokenInterface $accessToken
17 | * @param mixed $client_id - client identifier related to the access token.
18 | * @param mixed $user_id - user id associated with the access token
19 | * @param string $scope - scopes to be stored in space-separated string.
20 | * @return array
21 | */
22 | public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
23 | {
24 | $includeRefreshToken = true;
25 | if (isset($this->authCode['id_token'])) {
26 | // OpenID Connect requests include the refresh token only if the
27 | // offline_access scope has been requested and granted.
28 | $scopes = explode(' ', trim($scope));
29 | $includeRefreshToken = in_array('offline_access', $scopes);
30 | }
31 |
32 | $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
33 | if (isset($this->authCode['id_token'])) {
34 | $token['id_token'] = $this->authCode['id_token'];
35 | }
36 |
37 | $this->storage->expireAuthorizationCode($this->authCode['code']);
38 |
39 | return $token;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface
12 | {
13 | /**
14 | * Constructor
15 | *
16 | * @param AuthorizationCodeStorageInterface $storage
17 | * @param array $config
18 | */
19 | public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
20 | {
21 | parent::__construct($storage, $config);
22 | }
23 |
24 | /**
25 | * @param $params
26 | * @param null $user_id
27 | * @return array
28 | */
29 | public function getAuthorizeResponse($params, $user_id = null)
30 | {
31 | // build the URL to redirect to
32 | $result = array('query' => array());
33 |
34 | $params += array('scope' => null, 'state' => null, 'id_token' => null, 'code_challenge' => null, 'code_challenge_method' => null);
35 |
36 | $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token'], $params['code_challenge'], $params['code_challenge_method']);
37 |
38 | if (isset($params['state'])) {
39 | $result['query']['state'] = $params['state'];
40 | }
41 |
42 | return array($params['redirect_uri'], $result);
43 | }
44 |
45 | /**
46 | * Handle the creation of the authorization code.
47 | *
48 | * @param mixed $client_id - Client identifier related to the authorization code
49 | * @param mixed $user_id - User ID associated with the authorization code
50 | * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the
51 | * user-agent to when the end-user authorization step is completed.
52 | * @param string $scope - OPTIONAL Scopes to be stored in space-separated string.
53 | * @param string $id_token - OPTIONAL The OpenID Connect id_token.
54 | *
55 | * @return string
56 | * @see http://tools.ietf.org/html/rfc6749#section-4
57 | * @ingroup oauth2_section_4
58 | */
59 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null)
60 | {
61 | $code = $this->generateAuthorizationCode();
62 | $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token, $code_challenge, $code_challenge_method);
63 |
64 | return $code;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
11 | {
12 | /**
13 | * Handle the creation of the authorization code.
14 | *
15 | * @param mixed $client_id - Client identifier related to the authorization code
16 | * @param mixed $user_id - User ID associated with the authorization code
17 | * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the
18 | * user-agent to when the end-user authorization step is completed.
19 | * @param string $scope - OPTIONAL Scopes to be stored in space-separated string.
20 | * @param string $id_token - OPTIONAL The OpenID Connect id_token.
21 | * @return string
22 | *
23 | * @see http://tools.ietf.org/html/rfc6749#section-4
24 | * @ingroup oauth2_section_4
25 | */
26 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null);
27 | }
28 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/CodeIdToken.php:
--------------------------------------------------------------------------------
1 | authCode = $authCode;
24 | $this->idToken = $idToken;
25 | }
26 |
27 | /**
28 | * @param array $params
29 | * @param mixed $user_id
30 | * @return mixed
31 | */
32 | public function getAuthorizeResponse($params, $user_id = null)
33 | {
34 | $result = $this->authCode->getAuthorizeResponse($params, $user_id);
35 | $resultIdToken = $this->idToken->getAuthorizeResponse($params, $user_id);
36 | $result[1]['query']['id_token'] = $resultIdToken[1]['fragment']['id_token'];
37 |
38 | return $result;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php:
--------------------------------------------------------------------------------
1 | userClaimsStorage = $userClaimsStorage;
44 | $this->publicKeyStorage = $publicKeyStorage;
45 | if (is_null($encryptionUtil)) {
46 | $encryptionUtil = new Jwt();
47 | }
48 | $this->encryptionUtil = $encryptionUtil;
49 |
50 | if (!isset($config['issuer'])) {
51 | throw new LogicException('config parameter "issuer" must be set');
52 | }
53 | $this->config = array_merge(array(
54 | 'id_lifetime' => 3600,
55 | ), $config);
56 | }
57 |
58 | /**
59 | * @param array $params
60 | * @param null $userInfo
61 | * @return array|mixed
62 | */
63 | public function getAuthorizeResponse($params, $userInfo = null)
64 | {
65 | // build the URL to redirect to
66 | $result = array('query' => array());
67 | $params += array('scope' => null, 'state' => null, 'nonce' => null);
68 |
69 | // create the id token.
70 | list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
71 | $userClaims = $this->userClaimsStorage->getUserClaims($user_id, $params['scope']);
72 |
73 | $id_token = $this->createIdToken($params['client_id'], $userInfo, $params['nonce'], $userClaims, null);
74 | $result["fragment"] = array('id_token' => $id_token);
75 | if (isset($params['state'])) {
76 | $result["fragment"]["state"] = $params['state'];
77 | }
78 |
79 | return array($params['redirect_uri'], $result);
80 | }
81 |
82 | /**
83 | * Create id token
84 | *
85 | * @param string $client_id
86 | * @param mixed $userInfo
87 | * @param mixed $nonce
88 | * @param mixed $userClaims
89 | * @param mixed $access_token
90 | * @return mixed|string
91 | */
92 | public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null)
93 | {
94 | // pull auth_time from user info if supplied
95 | list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
96 |
97 | $token = array(
98 | 'iss' => $this->config['issuer'],
99 | 'sub' => $user_id,
100 | 'aud' => $client_id,
101 | 'iat' => time(),
102 | 'exp' => time() + $this->config['id_lifetime'],
103 | 'auth_time' => $auth_time,
104 | );
105 |
106 | if ($nonce) {
107 | $token['nonce'] = $nonce;
108 | }
109 |
110 | if ($userClaims) {
111 | $token += $userClaims;
112 | }
113 |
114 | if ($access_token) {
115 | $token['at_hash'] = $this->createAtHash($access_token, $client_id);
116 | }
117 |
118 | return $this->encodeToken($token, $client_id);
119 | }
120 |
121 | /**
122 | * @param $access_token
123 | * @param null $client_id
124 | * @return mixed|string
125 | */
126 | protected function createAtHash($access_token, $client_id = null)
127 | {
128 | // maps HS256 and RS256 to sha256, etc.
129 | $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
130 | $hash_algorithm = 'sha' . substr($algorithm, 2);
131 | $hash = hash($hash_algorithm, $access_token, true);
132 | $at_hash = substr($hash, 0, strlen($hash) / 2);
133 |
134 | return $this->encryptionUtil->urlSafeB64Encode($at_hash);
135 | }
136 |
137 | /**
138 | * @param array $token
139 | * @param null $client_id
140 | * @return mixed|string
141 | */
142 | protected function encodeToken(array $token, $client_id = null)
143 | {
144 | $private_key = $this->publicKeyStorage->getPrivateKey($client_id);
145 | $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
146 |
147 | return $this->encryptionUtil->encode($token, $private_key, $algorithm);
148 | }
149 |
150 | /**
151 | * @param $userInfo
152 | * @return array
153 | * @throws LogicException
154 | */
155 | private function getUserIdAndAuthTime($userInfo)
156 | {
157 | $auth_time = null;
158 |
159 | // support an array for user_id / auth_time
160 | if (is_array($userInfo)) {
161 | if (!isset($userInfo['user_id'])) {
162 | throw new LogicException('if $user_id argument is an array, user_id index must be set');
163 | }
164 |
165 | $auth_time = isset($userInfo['auth_time']) ? $userInfo['auth_time'] : null;
166 | $user_id = $userInfo['user_id'];
167 | } else {
168 | $user_id = $userInfo;
169 | }
170 |
171 | if (is_null($auth_time)) {
172 | $auth_time = time();
173 | }
174 |
175 | // userInfo is a scalar, and so this is the $user_id. Auth Time is null
176 | return array($user_id, $auth_time);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php:
--------------------------------------------------------------------------------
1 | accessToken = $accessToken;
28 | $this->idToken = $idToken;
29 | }
30 |
31 | /**
32 | * @param array $params
33 | * @param mixed $user_id
34 | * @return mixed
35 | */
36 | public function getAuthorizeResponse($params, $user_id = null)
37 | {
38 | $result = $this->accessToken->getAuthorizeResponse($params, $user_id);
39 | $access_token = $result[1]['fragment']['access_token'];
40 | $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token);
41 | $result[1]['fragment']['id_token'] = $id_token;
42 |
43 | return $result;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
14 | {
15 | /**
16 | * Take the provided authorization code values and store them somewhere.
17 | *
18 | * This function should be the storage counterpart to getAuthCode().
19 | *
20 | * If storage fails for some reason, we're not currently checking for
21 | * any sort of success/failure, so you should bail out of the script
22 | * and provide a descriptive fail message.
23 | *
24 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
25 | *
26 | * @param string $code - authorization code to be stored.
27 | * @param mixed $client_id - client identifier to be stored.
28 | * @param mixed $user_id - user identifier to be stored.
29 | * @param string $redirect_uri - redirect URI(s) to be stored in a space-separated string.
30 | * @param int $expires - expiration to be stored as a Unix timestamp.
31 | * @param string $scope - OPTIONAL scopes to be stored in space-separated string.
32 | * @param string $id_token - OPTIONAL the OpenID Connect id_token.
33 | *
34 | * @ingroup oauth2_section_4
35 | */
36 | public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null);
37 | }
38 |
--------------------------------------------------------------------------------
/src/OAuth2/OpenID/Storage/UserClaimsInterface.php:
--------------------------------------------------------------------------------
1 | value format.
31 | *
32 | * @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
33 | */
34 | public function getUserClaims($user_id, $scope);
35 | }
36 |
--------------------------------------------------------------------------------
/src/OAuth2/RequestInterface.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | interface AccessTokenInterface extends ResponseTypeInterface
9 | {
10 | /**
11 | * Handle the creation of access token, also issue refresh token if supported / desirable.
12 | *
13 | * @param mixed $client_id - client identifier related to the access token.
14 | * @param mixed $user_id - user ID associated with the access token
15 | * @param string $scope - OPTONAL scopes to be stored in space-separated string.
16 | * @param bool $includeRefreshToken - if true, a new refresh_token will be added to the response
17 | *
18 | * @see http://tools.ietf.org/html/rfc6749#section-5
19 | * @ingroup oauth2_section_5
20 | */
21 | public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true);
22 |
23 | /**
24 | * Handle the revoking of refresh tokens, and access tokens if supported / desirable
25 | *
26 | * @param $token
27 | * @param $tokenTypeHint
28 | * @return mixed
29 | *
30 | * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x
31 | */
32 | //public function revokeToken($token, $tokenTypeHint);
33 | }
--------------------------------------------------------------------------------
/src/OAuth2/ResponseType/AuthorizationCode.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class AuthorizationCode implements AuthorizationCodeInterface
11 | {
12 | protected $storage;
13 | protected $config;
14 |
15 | public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
16 | {
17 | $this->storage = $storage;
18 | $this->config = array_merge(array(
19 | 'enforce_redirect' => false,
20 | 'auth_code_lifetime' => 30,
21 | ), $config);
22 | }
23 |
24 | public function getAuthorizeResponse($params, $user_id = null)
25 | {
26 | // build the URL to redirect to
27 | $result = array('query' => array());
28 |
29 | $params += array('scope' => null, 'state' => null, 'code_challenge' => null, 'code_challenge_method' => null);
30 |
31 | $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['code_challenge'], $params['code_challenge_method']);
32 |
33 | if (isset($params['state'])) {
34 | $result['query']['state'] = $params['state'];
35 | }
36 |
37 | return array($params['redirect_uri'], $result);
38 | }
39 |
40 | /**
41 | * Handle the creation of the authorization code.
42 | *
43 | * @param $client_id
44 | * Client identifier related to the authorization code
45 | * @param $user_id
46 | * User ID associated with the authorization code
47 | * @param $redirect_uri
48 | * An absolute URI to which the authorization server will redirect the
49 | * user-agent to when the end-user authorization step is completed.
50 | * @param $scope
51 | * (optional) Scopes to be stored in space-separated string.
52 | *
53 | * @see http://tools.ietf.org/html/rfc6749#section-4
54 | * @ingroup oauth2_section_4
55 | */
56 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $code_challenge = null, $code_challenge_method = null)
57 | {
58 | $code = $this->generateAuthorizationCode();
59 | $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, null, $code_challenge, $code_challenge_method);
60 |
61 | return $code;
62 | }
63 |
64 | /**
65 | * @return
66 | * TRUE if the grant type requires a redirect_uri, FALSE if not
67 | */
68 | public function enforceRedirect()
69 | {
70 | return $this->config['enforce_redirect'];
71 | }
72 |
73 | /**
74 | * Generates an unique auth code.
75 | *
76 | * Implementing classes may want to override this function to implement
77 | * other auth code generation schemes.
78 | *
79 | * @return
80 | * An unique auth code.
81 | *
82 | * @ingroup oauth2_section_4
83 | */
84 | protected function generateAuthorizationCode()
85 | {
86 | $tokenLen = 40;
87 | if (function_exists('random_bytes')) {
88 | $randomData = random_bytes(100);
89 | } elseif (function_exists('openssl_random_pseudo_bytes')) {
90 | $randomData = openssl_random_pseudo_bytes(100);
91 | } elseif (function_exists('mcrypt_create_iv')) {
92 | $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
93 | } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
94 | $randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
95 | } else {
96 | $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
97 | }
98 |
99 | return substr(hash('sha512', $randomData), 0, $tokenLen);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/OAuth2/ResponseType/AuthorizationCodeInterface.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | interface AuthorizationCodeInterface extends ResponseTypeInterface
9 | {
10 | /**
11 | * @return
12 | * TRUE if the grant type requires a redirect_uri, FALSE if not
13 | */
14 | public function enforceRedirect();
15 |
16 | /**
17 | * Handle the creation of the authorization code.
18 | *
19 | * @param mixed $client_id - Client identifier related to the authorization code
20 | * @param mixed $user_id - User ID associated with the authorization code
21 | * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the
22 | * user-agent to when the end-user authorization step is completed.
23 | * @param string $scope - OPTIONAL Scopes to be stored in space-separated string.
24 | * @return string
25 | *
26 | * @see http://tools.ietf.org/html/rfc6749#section-4
27 | * @ingroup oauth2_section_4
28 | */
29 | public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null);
30 | }
31 |
--------------------------------------------------------------------------------
/src/OAuth2/ResponseType/JwtAccessToken.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class JwtAccessToken extends AccessToken
16 | {
17 | protected $publicKeyStorage;
18 | protected $encryptionUtil;
19 |
20 | /**
21 | * @param PublicKeyInterface $publicKeyStorage -
22 | * @param AccessTokenStorageInterface $tokenStorage -
23 | * @param RefreshTokenInterface $refreshStorage -
24 | * @param array $config - array with key store_encrypted_token_string (bool true)
25 | * whether the entire encrypted string is stored,
26 | * or just the token ID is stored
27 | * @param EncryptionInterface $encryptionUtil -
28 | */
29 | public function __construct(?PublicKeyInterface $publicKeyStorage = null, ?AccessTokenStorageInterface $tokenStorage = null, ?RefreshTokenInterface $refreshStorage = null, array $config = array(), ?EncryptionInterface $encryptionUtil = null)
30 | {
31 | $this->publicKeyStorage = $publicKeyStorage;
32 | $config = array_merge(array(
33 | 'store_encrypted_token_string' => true,
34 | 'issuer' => ''
35 | ), $config);
36 | if (is_null($tokenStorage)) {
37 | // a pass-thru, so we can call the parent constructor
38 | $tokenStorage = new Memory();
39 | }
40 | if (is_null($encryptionUtil)) {
41 | $encryptionUtil = new Jwt();
42 | }
43 | $this->encryptionUtil = $encryptionUtil;
44 | parent::__construct($tokenStorage, $refreshStorage, $config);
45 | }
46 |
47 | /**
48 | * Handle the creation of access token, also issue refresh token if supported / desirable.
49 | *
50 | * @param mixed $client_id - Client identifier related to the access token.
51 | * @param mixed $user_id - User ID associated with the access token
52 | * @param string $scope - (optional) Scopes to be stored in space-separated string.
53 | * @param bool $includeRefreshToken - If true, a new refresh_token will be added to the response
54 | * @return array - The access token
55 | *
56 | * @see http://tools.ietf.org/html/rfc6749#section-5
57 | * @ingroup oauth2_section_5
58 | */
59 | public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
60 | {
61 | // payload to encrypt
62 | $payload = $this->createPayload($client_id, $user_id, $scope);
63 |
64 | /*
65 | * Encode the payload data into a single JWT access_token string
66 | */
67 | $access_token = $this->encodeToken($payload, $client_id);
68 |
69 | /*
70 | * Save the token to a secondary storage. This is implemented on the
71 | * OAuth2\Storage\JwtAccessToken side, and will not actually store anything,
72 | * if no secondary storage has been supplied
73 | */
74 | $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $payload['id'];
75 | $this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
76 |
77 | // token to return to the client
78 | $token = array(
79 | 'access_token' => $access_token,
80 | 'expires_in' => $this->config['access_lifetime'],
81 | 'token_type' => $this->config['token_type'],
82 | 'scope' => $scope
83 | );
84 |
85 | /*
86 | * Issue a refresh token also, if we support them
87 | *
88 | * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
89 | * is supplied in the constructor
90 | */
91 | if ($includeRefreshToken && $this->refreshStorage) {
92 | $refresh_token = $this->generateRefreshToken();
93 | $expires = 0;
94 | if ($this->config['refresh_token_lifetime'] > 0) {
95 | $expires = time() + $this->config['refresh_token_lifetime'];
96 | }
97 | $this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope);
98 | $token['refresh_token'] = $refresh_token;
99 | }
100 |
101 | return $token;
102 | }
103 |
104 | /**
105 | * @param array $token
106 | * @param mixed $client_id
107 | * @return mixed
108 | */
109 | protected function encodeToken(array $token, $client_id = null)
110 | {
111 | $private_key = $this->publicKeyStorage->getPrivateKey($client_id);
112 | $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
113 |
114 | return $this->encryptionUtil->encode($token, $private_key, $algorithm);
115 | }
116 |
117 | /**
118 | * This function can be used to create custom JWT payloads
119 | *
120 | * @param mixed $client_id - Client identifier related to the access token.
121 | * @param mixed $user_id - User ID associated with the access token
122 | * @param string $scope - (optional) Scopes to be stored in space-separated string.
123 | * @return array - The access token
124 | */
125 | protected function createPayload($client_id, $user_id, $scope = null)
126 | {
127 | // token to encrypt
128 | $expires = time() + $this->config['access_lifetime'];
129 | $id = $this->generateAccessToken();
130 |
131 | $payload = array(
132 | 'id' => $id, // for BC (see #591)
133 | 'jti' => $id,
134 | 'iss' => $this->config['issuer'],
135 | 'aud' => $client_id,
136 | 'sub' => $user_id,
137 | 'exp' => $expires,
138 | 'iat' => time(),
139 | 'token_type' => $this->config['token_type'],
140 | 'scope' => $scope
141 | );
142 |
143 | if (isset($this->config['jwt_extra_payload_callable'])) {
144 | if (!is_callable($this->config['jwt_extra_payload_callable'])) {
145 | throw new \InvalidArgumentException('jwt_extra_payload_callable is not callable');
146 | }
147 |
148 | $extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope);
149 |
150 | if (!is_array($extra)) {
151 | throw new \InvalidArgumentException('jwt_extra_payload_callable must return array');
152 | }
153 |
154 | $payload = array_merge($extra, $payload);
155 | }
156 |
157 | return $payload;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/OAuth2/ResponseType/ResponseTypeInterface.php:
--------------------------------------------------------------------------------
1 | storage = $storage;
34 | }
35 |
36 | /**
37 | * Check if everything in required scope is contained in available scope.
38 | *
39 | * @param string $required_scope - A space-separated string of scopes.
40 | * @param string $available_scope - A space-separated string of scopes.
41 | * @return bool - TRUE if everything in required scope is contained in available scope and FALSE
42 | * if it isn't.
43 | *
44 | * @see http://tools.ietf.org/html/rfc6749#section-7
45 | *
46 | * @ingroup oauth2_section_7
47 | */
48 | public function checkScope($required_scope, $available_scope)
49 | {
50 | $required_scope = explode(' ', trim($required_scope));
51 | $available_scope = explode(' ', trim($available_scope));
52 |
53 | return (count(array_diff($required_scope, $available_scope)) == 0);
54 | }
55 |
56 | /**
57 | * Check if the provided scope exists in storage.
58 | *
59 | * @param string $scope - A space-separated string of scopes.
60 | * @return bool - TRUE if it exists, FALSE otherwise.
61 | */
62 | public function scopeExists($scope)
63 | {
64 | // Check reserved scopes first.
65 | $scope = explode(' ', trim($scope));
66 | $reservedScope = $this->getReservedScopes();
67 | $nonReservedScopes = array_diff($scope, $reservedScope);
68 | if (count($nonReservedScopes) == 0) {
69 | return true;
70 | } else {
71 | // Check the storage for non-reserved scopes.
72 | $nonReservedScopes = implode(' ', $nonReservedScopes);
73 |
74 | return $this->storage->scopeExists($nonReservedScopes);
75 | }
76 | }
77 |
78 | /**
79 | * @param RequestInterface $request
80 | * @return string
81 | */
82 | public function getScopeFromRequest(RequestInterface $request)
83 | {
84 | // "scope" is valid if passed in either POST or QUERY
85 | return $request->request('scope', $request->query('scope'));
86 | }
87 |
88 | /**
89 | * @param null $client_id
90 | * @return mixed
91 | */
92 | public function getDefaultScope($client_id = null)
93 | {
94 | return $this->storage->getDefaultScope($client_id);
95 | }
96 |
97 | /**
98 | * Get reserved scopes needed by the server.
99 | *
100 | * In case OpenID Connect is used, these scopes must include:
101 | * 'openid', offline_access'.
102 | *
103 | * @return array - An array of reserved scopes.
104 | */
105 | public function getReservedScopes()
106 | {
107 | return array('openid', 'offline_access');
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/OAuth2/ScopeInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface AccessTokenInterface
12 | {
13 | /**
14 | * Look up the supplied oauth_token from storage.
15 | *
16 | * We need to retrieve access token data as we create and verify tokens.
17 | *
18 | * @param string $oauth_token - oauth_token to be check with.
19 | *
20 | * @return array|null - An associative array as below, and return NULL if the supplied oauth_token is invalid:
21 | * @code
22 | * array(
23 | * 'expires' => $expires, // Stored expiration in unix timestamp.
24 | * 'client_id' => $client_id, // (optional) Stored client identifier.
25 | * 'user_id' => $user_id, // (optional) Stored user identifier.
26 | * 'scope' => $scope, // (optional) Stored scope values in space-separated string.
27 | * 'id_token' => $id_token // (optional) Stored id_token (if "use_openid_connect" is true).
28 | * );
29 | * @endcode
30 | *
31 | * @ingroup oauth2_section_7
32 | */
33 | public function getAccessToken($oauth_token);
34 |
35 | /**
36 | * Store the supplied access token values to storage.
37 | *
38 | * We need to store access token data as we create and verify tokens.
39 | *
40 | * @param string $oauth_token - oauth_token to be stored.
41 | * @param mixed $client_id - client identifier to be stored.
42 | * @param mixed $user_id - user identifier to be stored.
43 | * @param int $expires - expiration to be stored as a Unix timestamp.
44 | * @param string $scope - OPTIONAL Scopes to be stored in space-separated string.
45 | *
46 | * @ingroup oauth2_section_4
47 | */
48 | public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null);
49 |
50 | /**
51 | * Expire an access token.
52 | *
53 | * This is not explicitly required in the spec, but if defined in a draft RFC for token
54 | * revoking (RFC 7009) https://tools.ietf.org/html/rfc7009
55 | *
56 | * @param $access_token
57 | * Access token to be expired.
58 | *
59 | * @return BOOL true if an access token was unset, false if not
60 | * @ingroup oauth2_section_6
61 | *
62 | * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x
63 | */
64 | //public function unsetAccessToken($access_token);
65 | }
--------------------------------------------------------------------------------
/src/OAuth2/Storage/AuthorizationCodeInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface AuthorizationCodeInterface
13 | {
14 | /**
15 | * The Authorization Code grant type supports a response type of "code".
16 | *
17 | * @var string
18 | * @see http://tools.ietf.org/html/rfc6749#section-1.4.1
19 | * @see http://tools.ietf.org/html/rfc6749#section-4.2
20 | */
21 | const RESPONSE_TYPE_CODE = "code";
22 |
23 | /**
24 | * Fetch authorization code data (probably the most common grant type).
25 | *
26 | * Retrieve the stored data for the given authorization code.
27 | *
28 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
29 | *
30 | * @param $code
31 | * Authorization code to be check with.
32 | *
33 | * @return
34 | * An associative array as below, and NULL if the code is invalid
35 | * @code
36 | * return array(
37 | * "client_id" => CLIENT_ID, // REQUIRED Stored client identifier
38 | * "user_id" => USER_ID, // REQUIRED Stored user identifier
39 | * "expires" => EXPIRES, // REQUIRED Stored expiration in unix timestamp
40 | * "redirect_uri" => REDIRECT_URI, // REQUIRED Stored redirect URI
41 | * "scope" => SCOPE, // OPTIONAL Stored scope values in space-separated string
42 | * );
43 | * @endcode
44 | *
45 | * @see http://tools.ietf.org/html/rfc6749#section-4.1
46 | *
47 | * @ingroup oauth2_section_4
48 | */
49 | public function getAuthorizationCode($code);
50 |
51 | /**
52 | * Take the provided authorization code values and store them somewhere.
53 | *
54 | * This function should be the storage counterpart to getAuthCode().
55 | *
56 | * If storage fails for some reason, we're not currently checking for
57 | * any sort of success/failure, so you should bail out of the script
58 | * and provide a descriptive fail message.
59 | *
60 | * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
61 | *
62 | * @param string $code - Authorization code to be stored.
63 | * @param mixed $client_id - Client identifier to be stored.
64 | * @param mixed $user_id - User identifier to be stored.
65 | * @param string $redirect_uri - Redirect URI(s) to be stored in a space-separated string.
66 | * @param int $expires - Expiration to be stored as a Unix timestamp.
67 | * @param string $scope - OPTIONAL Scopes to be stored in space-separated string.
68 | *
69 | * @ingroup oauth2_section_4
70 | */
71 | public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null);
72 |
73 | /**
74 | * once an Authorization Code is used, it must be expired
75 | *
76 | * @see http://tools.ietf.org/html/rfc6749#section-4.1.2
77 | *
78 | * The client MUST NOT use the authorization code
79 | * more than once. If an authorization code is used more than
80 | * once, the authorization server MUST deny the request and SHOULD
81 | * revoke (when possible) all tokens previously issued based on
82 | * that authorization code
83 | *
84 | */
85 | public function expireAuthorizationCode($code);
86 | }
87 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/ClientCredentialsInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface ClientCredentialsInterface extends ClientInterface
12 | {
13 |
14 | /**
15 | * Make sure that the client credentials is valid.
16 | *
17 | * @param $client_id
18 | * Client identifier to be check with.
19 | * @param $client_secret
20 | * (optional) If a secret is required, check that they've given the right one.
21 | *
22 | * @return
23 | * TRUE if the client credentials are valid, and MUST return FALSE if it isn't.
24 | * @endcode
25 | *
26 | * @see http://tools.ietf.org/html/rfc6749#section-3.1
27 | *
28 | * @ingroup oauth2_section_3
29 | */
30 | public function checkClientCredentials($client_id, $client_secret = null);
31 |
32 | /**
33 | * Determine if the client is a "public" client, and therefore
34 | * does not require passing credentials for certain grant types
35 | *
36 | * @param $client_id
37 | * Client identifier to be check with.
38 | *
39 | * @return
40 | * TRUE if the client is public, and FALSE if it isn't.
41 | * @endcode
42 | *
43 | * @see http://tools.ietf.org/html/rfc6749#section-2.3
44 | * @see https://github.com/bshaffer/oauth2-server-php/issues/257
45 | *
46 | * @ingroup oauth2_section_2
47 | */
48 | public function isPublicClient($client_id);
49 | }
50 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/ClientInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface ClientInterface
12 | {
13 | /**
14 | * Get client details corresponding client_id.
15 | *
16 | * OAuth says we should store request URIs for each registered client.
17 | * Implement this function to grab the stored URI for a given client id.
18 | *
19 | * @param $client_id
20 | * Client identifier to be check with.
21 | *
22 | * @return array
23 | * Client details. The only mandatory key in the array is "redirect_uri".
24 | * This function MUST return FALSE if the given client does not exist or is
25 | * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris.
26 | *
27 | * return array(
28 | * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client
29 | * "client_id" => CLIENT_ID, // OPTIONAL the client id
30 | * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types
31 | * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client
32 | * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client
33 | * );
34 | *
35 | *
36 | * @ingroup oauth2_section_4
37 | */
38 | public function getClientDetails($client_id);
39 |
40 | /**
41 | * Get the scope associated with this client
42 | *
43 | * @return
44 | * STRING the space-delineated scope list for the specified client_id
45 | */
46 | public function getClientScope($client_id);
47 |
48 | /**
49 | * Check restricted grant types of corresponding client identifier.
50 | *
51 | * If you want to restrict clients to certain grant types, override this
52 | * function.
53 | *
54 | * @param $client_id
55 | * Client identifier to be check with.
56 | * @param $grant_type
57 | * Grant type to be check with
58 | *
59 | * @return
60 | * TRUE if the grant type is supported by this client identifier, and
61 | * FALSE if it isn't.
62 | *
63 | * @ingroup oauth2_section_4
64 | */
65 | public function checkRestrictedGrantType($client_id, $grant_type);
66 | }
67 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/JwtAccessToken.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class JwtAccessToken implements JwtAccessTokenInterface
12 | {
13 | protected $publicKeyStorage;
14 | protected $tokenStorage;
15 | protected $encryptionUtil;
16 |
17 | /**
18 | * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use
19 | * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if
20 | * you want to retain access token grant information somewhere, but
21 | * is not necessary when using this grant type.
22 | * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions.
23 | */
24 | public function __construct(PublicKeyInterface $publicKeyStorage, ?AccessTokenInterface $tokenStorage = null, ?EncryptionInterface $encryptionUtil = null)
25 | {
26 | $this->publicKeyStorage = $publicKeyStorage;
27 | $this->tokenStorage = $tokenStorage;
28 | if (is_null($encryptionUtil)) {
29 | $encryptionUtil = new Jwt;
30 | }
31 | $this->encryptionUtil = $encryptionUtil;
32 | }
33 |
34 | public function getAccessToken($oauth_token)
35 | {
36 | // just decode the token, don't verify
37 | if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) {
38 | return false;
39 | }
40 |
41 | $client_id = isset($tokenData['aud']) ? $tokenData['aud'] : null;
42 | $public_key = $this->publicKeyStorage->getPublicKey($client_id);
43 | $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
44 |
45 | // now that we have the client_id, verify the token
46 | if (false === $this->encryptionUtil->decode($oauth_token, $public_key, array($algorithm))) {
47 | return false;
48 | }
49 |
50 | // normalize the JWT claims to the format expected by other components in this library
51 | return $this->convertJwtToOAuth2($tokenData);
52 | }
53 |
54 | public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null)
55 | {
56 | if ($this->tokenStorage) {
57 | return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope);
58 | }
59 | }
60 |
61 | public function unsetAccessToken($access_token)
62 | {
63 | if ($this->tokenStorage) {
64 | return $this->tokenStorage->unsetAccessToken($access_token);
65 | }
66 | }
67 |
68 |
69 | // converts a JWT access token into an OAuth2-friendly format
70 | protected function convertJwtToOAuth2($tokenData)
71 | {
72 | $keyMapping = array(
73 | 'aud' => 'client_id',
74 | 'exp' => 'expires',
75 | 'sub' => 'user_id'
76 | );
77 |
78 | foreach ($keyMapping as $jwtKey => $oauth2Key) {
79 | if (isset($tokenData[$jwtKey])) {
80 | $tokenData[$oauth2Key] = $tokenData[$jwtKey];
81 | unset($tokenData[$jwtKey]);
82 | }
83 | }
84 |
85 | return $tokenData;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/JwtAccessTokenInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface JwtAccessTokenInterface extends AccessTokenInterface
12 | {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/JwtBearerInterface.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | interface JwtBearerInterface
16 | {
17 | /**
18 | * Get the public key associated with a client_id
19 | *
20 | * @param $client_id
21 | * Client identifier to be checked with.
22 | *
23 | * @return
24 | * STRING Return the public key for the client_id if it exists, and MUST return FALSE if it doesn't.
25 | */
26 | public function getClientKey($client_id, $subject);
27 |
28 | /**
29 | * Get a jti (JSON token identifier) by matching against the client_id, subject, audience and expiration.
30 | *
31 | * @param $client_id
32 | * Client identifier to match.
33 | *
34 | * @param $subject
35 | * The subject to match.
36 | *
37 | * @param $audience
38 | * The audience to match.
39 | *
40 | * @param $expiration
41 | * The expiration of the jti.
42 | *
43 | * @param $jti
44 | * The jti to match.
45 | *
46 | * @return
47 | * An associative array as below, and return NULL if the jti does not exist.
48 | * - issuer: Stored client identifier.
49 | * - subject: Stored subject.
50 | * - audience: Stored audience.
51 | * - expires: Stored expiration in unix timestamp.
52 | * - jti: The stored jti.
53 | */
54 | public function getJti($client_id, $subject, $audience, $expiration, $jti);
55 |
56 | /**
57 | * Store a used jti so that we can check against it to prevent replay attacks.
58 | * @param $client_id
59 | * Client identifier to insert.
60 | *
61 | * @param $subject
62 | * The subject to insert.
63 | *
64 | * @param $audience
65 | * The audience to insert.
66 | *
67 | * @param $expiration
68 | * The expiration of the jti.
69 | *
70 | * @param $jti
71 | * The jti to insert.
72 | */
73 | public function setJti($client_id, $subject, $audience, $expiration, $jti);
74 | }
75 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/PublicKeyInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface PublicKeyInterface
12 | {
13 | /**
14 | * @param mixed $client_id
15 | * @return mixed
16 | */
17 | public function getPublicKey($client_id = null);
18 |
19 | /**
20 | * @param mixed $client_id
21 | * @return mixed
22 | */
23 | public function getPrivateKey($client_id = null);
24 |
25 | /**
26 | * @param mixed $client_id
27 | * @return mixed
28 | */
29 | public function getEncryptionAlgorithm($client_id = null);
30 | }
--------------------------------------------------------------------------------
/src/OAuth2/Storage/RefreshTokenInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface RefreshTokenInterface
13 | {
14 | /**
15 | * Grant refresh access tokens.
16 | *
17 | * Retrieve the stored data for the given refresh token.
18 | *
19 | * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
20 | *
21 | * @param $refresh_token
22 | * Refresh token to be check with.
23 | *
24 | * @return
25 | * An associative array as below, and NULL if the refresh_token is
26 | * invalid:
27 | * - refresh_token: Refresh token identifier.
28 | * - client_id: Client identifier.
29 | * - user_id: User identifier.
30 | * - expires: Expiration unix timestamp, or 0 if the token doesn't expire.
31 | * - scope: (optional) Scope values in space-separated string.
32 | *
33 | * @see http://tools.ietf.org/html/rfc6749#section-6
34 | *
35 | * @ingroup oauth2_section_6
36 | */
37 | public function getRefreshToken($refresh_token);
38 |
39 | /**
40 | * Take the provided refresh token values and store them somewhere.
41 | *
42 | * This function should be the storage counterpart to getRefreshToken().
43 | *
44 | * If storage fails for some reason, we're not currently checking for
45 | * any sort of success/failure, so you should bail out of the script
46 | * and provide a descriptive fail message.
47 | *
48 | * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
49 | *
50 | * @param $refresh_token
51 | * Refresh token to be stored.
52 | * @param $client_id
53 | * Client identifier to be stored.
54 | * @param $user_id
55 | * User identifier to be stored.
56 | * @param $expires
57 | * Expiration timestamp to be stored. 0 if the token doesn't expire.
58 | * @param $scope
59 | * (optional) Scopes to be stored in space-separated string.
60 | *
61 | * @ingroup oauth2_section_6
62 | */
63 | public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null);
64 |
65 | /**
66 | * Expire a used refresh token.
67 | *
68 | * This is not explicitly required in the spec, but is almost implied.
69 | * After granting a new refresh token, the old one is no longer useful and
70 | * so should be forcibly expired in the data store so it can't be used again.
71 | *
72 | * If storage fails for some reason, we're not currently checking for
73 | * any sort of success/failure, so you should bail out of the script
74 | * and provide a descriptive fail message.
75 | *
76 | * @param $refresh_token
77 | * Refresh token to be expired.
78 | *
79 | * @ingroup oauth2_section_6
80 | */
81 | public function unsetRefreshToken($refresh_token);
82 | }
83 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/ScopeInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface ScopeInterface
13 | {
14 | /**
15 | * Check if the provided scope exists.
16 | *
17 | * @param $scope
18 | * A space-separated string of scopes.
19 | *
20 | * @return
21 | * TRUE if it exists, FALSE otherwise.
22 | */
23 | public function scopeExists($scope);
24 |
25 | /**
26 | * The default scope to use in the event the client
27 | * does not request one. By returning "false", a
28 | * request_error is returned by the server to force a
29 | * scope request by the client. By returning "null",
30 | * opt out of requiring scopes
31 | *
32 | * @param $client_id
33 | * An optional client id that can be used to return customized default scopes.
34 | *
35 | * @return
36 | * string representation of default scope, null if
37 | * scopes are not defined, or false to force scope
38 | * request by the client
39 | *
40 | * ex:
41 | * 'default'
42 | * ex:
43 | * null
44 | */
45 | public function getDefaultScope($client_id = null);
46 | }
47 |
--------------------------------------------------------------------------------
/src/OAuth2/Storage/UserCredentialsInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface UserCredentialsInterface
13 | {
14 | /**
15 | * Grant access tokens for basic user credentials.
16 | *
17 | * Check the supplied username and password for validity.
18 | *
19 | * You can also use the $client_id param to do any checks required based
20 | * on a client, if you need that.
21 | *
22 | * Required for OAuth2::GRANT_TYPE_USER_CREDENTIALS.
23 | *
24 | * @param $username
25 | * Username to be check with.
26 | * @param $password
27 | * Password to be check with.
28 | *
29 | * @return
30 | * TRUE if the username and password are valid, and FALSE if it isn't.
31 | * Moreover, if the username and password are valid, and you want to
32 | *
33 | * @see http://tools.ietf.org/html/rfc6749#section-4.3
34 | *
35 | * @ingroup oauth2_section_4
36 | */
37 | public function checkUserCredentials($username, $password);
38 |
39 | /**
40 | * @param string $username - username to get details for
41 | * @return array|false - the associated "user_id" and optional "scope" values
42 | * This function MUST return FALSE if the requested user does not exist or is
43 | * invalid. "scope" is a space-separated list of restricted scopes.
44 | * @code
45 | * return array(
46 | * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token
47 | * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes
48 | * );
49 | * @endcode
50 | */
51 | public function getUserDetails($username);
52 | }
53 |
--------------------------------------------------------------------------------
/src/OAuth2/TokenType/Bearer.php:
--------------------------------------------------------------------------------
1 | config = array_merge(array(
18 | 'token_param_name' => 'access_token',
19 | 'token_bearer_header_name' => 'Bearer',
20 | ), $config);
21 | }
22 |
23 | public function getTokenType()
24 | {
25 | return 'Bearer';
26 | }
27 |
28 | /**
29 | * Check if the request has supplied token
30 | *
31 | * @see https://github.com/bshaffer/oauth2-server-php/issues/349#issuecomment-37993588
32 | */
33 | public function requestHasToken(RequestInterface $request)
34 | {
35 | $headers = $request->headers('AUTHORIZATION');
36 |
37 | // check the header, then the querystring, then the request body
38 | return !empty($headers) || (bool) ($request->request($this->config['token_param_name'])) || (bool) ($request->query($this->config['token_param_name']));
39 | }
40 |
41 | /**
42 | * This is a convenience function that can be used to get the token, which can then
43 | * be passed to getAccessTokenData(). The constraints specified by the draft are
44 | * attempted to be adheared to in this method.
45 | *
46 | * As per the Bearer spec (draft 8, section 2) - there are three ways for a client
47 | * to specify the bearer token, in order of preference: Authorization Header,
48 | * POST and GET.
49 | *
50 | * NB: Resource servers MUST accept tokens via the Authorization scheme
51 | * (http://tools.ietf.org/html/rfc6750#section-2).
52 | *
53 | * @todo Should we enforce TLS/SSL in this function?
54 | *
55 | * @see http://tools.ietf.org/html/rfc6750#section-2.1
56 | * @see http://tools.ietf.org/html/rfc6750#section-2.2
57 | * @see http://tools.ietf.org/html/rfc6750#section-2.3
58 | *
59 | * Old Android version bug (at least with version 2.2)
60 | * @see http://code.google.com/p/android/issues/detail?id=6684
61 | *
62 | */
63 | public function getAccessTokenParameter(RequestInterface $request, ResponseInterface $response)
64 | {
65 | $headers = $request->headers('AUTHORIZATION');
66 |
67 | /**
68 | * Ensure more than one method is not used for including an
69 | * access token
70 | *
71 | * @see http://tools.ietf.org/html/rfc6750#section-3.1
72 | */
73 | $methodsUsed = !empty($headers) + (bool) ($request->query($this->config['token_param_name'])) + (bool) ($request->request($this->config['token_param_name']));
74 | if ($methodsUsed > 1) {
75 | $response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)');
76 |
77 | return null;
78 | }
79 |
80 | /**
81 | * If no authentication is provided, set the status code
82 | * to 401 and return no other error information
83 | *
84 | * @see http://tools.ietf.org/html/rfc6750#section-3.1
85 | */
86 | if ($methodsUsed == 0) {
87 | $response->setStatusCode(401);
88 |
89 | return null;
90 | }
91 |
92 | // HEADER: Get the access token from the header
93 | if (!empty($headers)) {
94 | if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/i', $headers, $matches)) {
95 | $response->setError(400, 'invalid_request', 'Malformed auth header');
96 |
97 | return null;
98 | }
99 |
100 | return $matches[1];
101 | }
102 |
103 | if ($request->request($this->config['token_param_name'])) {
104 | // // POST: Get the token from POST data
105 | if (!in_array(strtolower($request->server('REQUEST_METHOD')), array('post', 'put'))) {
106 | $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST or PUT', '#section-2.2');
107 |
108 | return null;
109 | }
110 |
111 | $contentType = $request->server('CONTENT_TYPE');
112 | if (false !== $pos = strpos((string) $contentType, ';')) {
113 | $contentType = substr($contentType, 0, $pos);
114 | }
115 |
116 | if ($contentType !== null && $contentType != 'application/x-www-form-urlencoded') {
117 | // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable
118 | // @see http://tools.ietf.org/html/rfc6750#section-2.2
119 | $response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"');
120 |
121 | return null;
122 | }
123 |
124 | return $request->request($this->config['token_param_name']);
125 | }
126 |
127 | // GET method
128 | return $request->query($this->config['token_param_name']);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/OAuth2/TokenType/Mac.php:
--------------------------------------------------------------------------------
1 | assertTrue(class_exists('OAuth2\Server'));
13 | $this->assertTrue(class_exists('OAuth2\Request'));
14 | $this->assertTrue(class_exists('OAuth2\Response'));
15 | $this->assertTrue(class_exists('OAuth2\GrantType\UserCredentials'));
16 | $this->assertTrue(interface_exists('OAuth2\Storage\AccessTokenInterface'));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/OAuth2/Encryption/FirebaseJwtTest.php:
--------------------------------------------------------------------------------
1 | privateKey = << $client_id,
40 | 'exp' => time() + 1000,
41 | 'iat' => time(),
42 | 'sub' => 'testuser@ourdomain.com',
43 | 'aud' => 'http://myapp.com/oauth/auth',
44 | 'scope' => null,
45 | );
46 |
47 | $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256');
48 |
49 | // test BC behaviour of trusting the algorithm in the header
50 | $payload = $jwtUtil->decode($encoded, $client_key, array('RS256'));
51 | $this->assertEquals($params, $payload);
52 |
53 | // test BC behaviour of not verifying by passing false
54 | $payload = $jwtUtil->decode($encoded, $client_key, false);
55 | $this->assertEquals($params, $payload);
56 |
57 | // test the new restricted algorithms header
58 | $payload = $jwtUtil->decode($encoded, $client_key, array('RS256'));
59 | $this->assertEquals($params, $payload);
60 | }
61 |
62 | public function testInvalidJwt()
63 | {
64 | $jwtUtil = new FirebaseJwt();
65 |
66 | $this->assertFalse($jwtUtil->decode('goob'));
67 | $this->assertFalse($jwtUtil->decode('go.o.b'));
68 | }
69 |
70 | /** @dataProvider provideClientCredentials */
71 | public function testInvalidJwtHeader($client_id, $client_key)
72 | {
73 | $jwtUtil = new FirebaseJwt();
74 |
75 | $params = array(
76 | 'iss' => $client_id,
77 | 'exp' => time() + 1000,
78 | 'iat' => time(),
79 | 'sub' => 'testuser@ourdomain.com',
80 | 'aud' => 'http://myapp.com/oauth/auth',
81 | 'scope' => null,
82 | );
83 |
84 | // testing for algorithm tampering when only RSA256 signing is allowed
85 | // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
86 | $tampered = $jwtUtil->encode($params, $client_key, 'HS256');
87 |
88 | $payload = $jwtUtil->decode($tampered, $client_key, array('RS256'));
89 |
90 | $this->assertFalse($payload);
91 | }
92 |
93 | public function provideClientCredentials()
94 | {
95 | $storage = Bootstrap::getInstance()->getMemoryStorage();
96 | $client_id = 'Test Client ID';
97 | $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com");
98 |
99 | return array(
100 | array($client_id, $client_key),
101 | );
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/test/OAuth2/Encryption/JwtTest.php:
--------------------------------------------------------------------------------
1 | privateKey = << $client_id,
40 | 'exp' => time() + 1000,
41 | 'iat' => time(),
42 | 'sub' => 'testuser@ourdomain.com',
43 | 'aud' => 'http://myapp.com/oauth/auth',
44 | 'scope' => null,
45 | );
46 |
47 | $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256');
48 |
49 | // test BC behaviour of trusting the algorithm in the header
50 | $payload = $jwtUtil->decode($encoded, $client_key);
51 | $this->assertEquals($params, $payload);
52 |
53 | // test BC behaviour of not verifying by passing false
54 | $payload = $jwtUtil->decode($encoded, $client_key, false);
55 | $this->assertEquals($params, $payload);
56 |
57 | // test the new restricted algorithms header
58 | $payload = $jwtUtil->decode($encoded, $client_key, array('RS256'));
59 | $this->assertEquals($params, $payload);
60 | }
61 |
62 | public function testInvalidJwt()
63 | {
64 | $jwtUtil = new Jwt();
65 |
66 | $this->assertFalse($jwtUtil->decode('goob'));
67 | $this->assertFalse($jwtUtil->decode('go.o.b'));
68 | }
69 |
70 | /** @dataProvider provideClientCredentials */
71 | public function testInvalidJwtHeader($client_id, $client_key)
72 | {
73 | $jwtUtil = new Jwt();
74 |
75 | $params = array(
76 | 'iss' => $client_id,
77 | 'exp' => time() + 1000,
78 | 'iat' => time(),
79 | 'sub' => 'testuser@ourdomain.com',
80 | 'aud' => 'http://myapp.com/oauth/auth',
81 | 'scope' => null,
82 | );
83 |
84 | // testing for algorithm tampering when only RSA256 signing is allowed
85 | // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
86 | $tampered = $jwtUtil->encode($params, $client_key, 'HS256');
87 |
88 | $payload = $jwtUtil->decode($tampered, $client_key, array('RS256'));
89 |
90 | $this->assertFalse($payload);
91 | }
92 |
93 | public function provideClientCredentials()
94 | {
95 | $storage = Bootstrap::getInstance()->getMemoryStorage();
96 | $client_id = 'Test Client ID';
97 | $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com");
98 |
99 | return array(
100 | array($client_id, $client_key),
101 | );
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/test/OAuth2/GrantType/ClientCredentialsTest.php:
--------------------------------------------------------------------------------
1 | getTestServer();
17 | $request = TestRequest::createPost(array(
18 | 'grant_type' => 'client_credentials', // valid grant type
19 | 'client_id' => 'Test Client ID', // valid client id
20 | 'client_secret' => 'FakeSecret', // valid client secret
21 | ));
22 | $server->handleTokenRequest($request, $response = new Response());
23 |
24 | $this->assertEquals($response->getStatusCode(), 400);
25 | $this->assertEquals($response->getParameter('error'), 'invalid_client');
26 | $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid');
27 | }
28 |
29 | public function testValidCredentials()
30 | {
31 | $server = $this->getTestServer();
32 | $request = TestRequest::createPost(array(
33 | 'grant_type' => 'client_credentials', // valid grant type
34 | 'client_id' => 'Test Client ID', // valid client id
35 | 'client_secret' => 'TestSecret', // valid client secret
36 | ));
37 | $token = $server->grantAccessToken($request, new Response());
38 |
39 | $this->assertNotNull($token);
40 | $this->assertArrayHasKey('scope', $token);
41 | $this->assertNull($token['scope']);
42 | }
43 |
44 | public function testValidCredentialsWithScope()
45 | {
46 | $server = $this->getTestServer();
47 | $request = TestRequest::createPost(array(
48 | 'grant_type' => 'client_credentials', // valid grant type
49 | 'client_id' => 'Test Client ID', // valid client id
50 | 'client_secret' => 'TestSecret', // valid client secret
51 | 'scope' => 'scope1',
52 | ));
53 | $token = $server->grantAccessToken($request, new Response());
54 |
55 | $this->assertNotNull($token);
56 | $this->assertArrayHasKey('access_token', $token);
57 | $this->assertArrayHasKey('scope', $token);
58 | $this->assertEquals($token['scope'], 'scope1');
59 | }
60 |
61 | public function testValidCredentialsInvalidScope()
62 | {
63 | $server = $this->getTestServer();
64 | $request = TestRequest::createPost(array(
65 | 'grant_type' => 'client_credentials', // valid grant type
66 | 'client_id' => 'Test Client ID', // valid client id
67 | 'client_secret' => 'TestSecret', // valid client secret
68 | 'scope' => 'invalid-scope',
69 | ));
70 | $token = $server->grantAccessToken($request, $response = new Response());
71 |
72 | $this->assertEquals($response->getStatusCode(), 400);
73 | $this->assertEquals($response->getParameter('error'), 'invalid_scope');
74 | $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested');
75 | }
76 |
77 | public function testValidCredentialsInHeader()
78 | {
79 | // create with HTTP_AUTHORIZATION in header
80 | $server = $this->getTestServer();
81 | $headers = array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('Test Client ID:TestSecret'), 'REQUEST_METHOD' => 'POST');
82 | $params = array('grant_type' => 'client_credentials');
83 | $request = new Request(array(), $params, array(), array(), array(), $headers);
84 | $token = $server->grantAccessToken($request, new Response());
85 |
86 | $this->assertNotNull($token);
87 | $this->assertArrayHasKey('access_token', $token);
88 | $this->assertNotNull($token['access_token']);
89 |
90 | // create using PHP Authorization Globals
91 | $headers = array('PHP_AUTH_USER' => 'Test Client ID', 'PHP_AUTH_PW' => 'TestSecret', 'REQUEST_METHOD' => 'POST');
92 | $params = array('grant_type' => 'client_credentials');
93 | $request = new Request(array(), $params, array(), array(), array(), $headers);
94 | $token = $server->grantAccessToken($request, new Response());
95 |
96 | $this->assertNotNull($token);
97 | $this->assertArrayHasKey('access_token', $token);
98 | $this->assertNotNull($token['access_token']);
99 | }
100 |
101 | public function testValidCredentialsInRequest()
102 | {
103 | $server = $this->getTestServer();
104 | $request = TestRequest::createPost(array(
105 | 'grant_type' => 'client_credentials', // valid grant type
106 | 'client_id' => 'Test Client ID', // valid client id
107 | 'client_secret' => 'TestSecret', // valid client secret
108 | ));
109 | $token = $server->grantAccessToken($request, new Response());
110 |
111 | $this->assertNotNull($token);
112 | $this->assertArrayHasKey('access_token', $token);
113 | $this->assertNotNull($token['access_token']);
114 | }
115 |
116 | public function testValidCredentialsInQuerystring()
117 | {
118 | $server = $this->getTestServer();
119 | $request = TestRequest::createPost(array(
120 | 'grant_type' => 'client_credentials', // valid grant type
121 | 'client_id' => 'Test Client ID', // valid client id
122 | 'client_secret' => 'TestSecret', // valid client secret
123 | ));
124 | $token = $server->grantAccessToken($request, new Response());
125 |
126 | $this->assertNotNull($token);
127 | $this->assertArrayHasKey('access_token', $token);
128 | $this->assertNotNull($token['access_token']);
129 | }
130 |
131 | public function testClientUserIdIsSetInAccessToken()
132 | {
133 | $server = $this->getTestServer();
134 | $request = TestRequest::createPost(array(
135 | 'grant_type' => 'client_credentials', // valid grant type
136 | 'client_id' => 'Client ID With User ID', // valid client id
137 | 'client_secret' => 'TestSecret', // valid client secret
138 | ));
139 | $token = $server->grantAccessToken($request, new Response());
140 |
141 | $this->assertNotNull($token);
142 | $this->assertArrayHasKey('access_token', $token);
143 |
144 | // verify the user_id was associated with the token
145 | $storage = $server->getStorage('client');
146 | $token = $storage->getAccessToken($token['access_token']);
147 | $this->assertNotNull($token);
148 | $this->assertArrayHasKey('user_id', $token);
149 | $this->assertEquals($token['user_id'], 'brent@brentertainment.com');
150 | }
151 |
152 | private function getTestServer()
153 | {
154 | $storage = Bootstrap::getInstance()->getMemoryStorage();
155 | $server = new Server($storage);
156 | $server->addGrantType(new ClientCredentials($storage));
157 |
158 | return $server;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/test/OAuth2/GrantType/ImplicitTest.php:
--------------------------------------------------------------------------------
1 | getTestServer();
16 | $request = new Request(array(
17 | 'client_id' => 'Test Client ID', // valid client id
18 | 'redirect_uri' => 'http://adobe.com', // valid redirect URI
19 | 'response_type' => 'token', // invalid response type
20 | ));
21 | $server->handleAuthorizeRequest($request, $response = new Response(), false);
22 |
23 | $this->assertEquals($response->getStatusCode(), 302);
24 | $location = $response->getHttpHeader('Location');
25 | $parts = parse_url($location);
26 | parse_str($parts['query'], $query);
27 |
28 | $this->assertEquals($query['error'], 'unsupported_response_type');
29 | $this->assertEquals($query['error_description'], 'implicit grant type not supported');
30 | }
31 |
32 | public function testUserDeniesAccessResponse()
33 | {
34 | $server = $this->getTestServer(array('allow_implicit' => true));
35 | $request = new Request(array(
36 | 'client_id' => 'Test Client ID', // valid client id
37 | 'redirect_uri' => 'http://adobe.com', // valid redirect URI
38 | 'response_type' => 'token', // valid response type
39 | 'state' => 'xyz',
40 | ));
41 | $server->handleAuthorizeRequest($request, $response = new Response(), false);
42 |
43 | $this->assertEquals($response->getStatusCode(), 302);
44 | $location = $response->getHttpHeader('Location');
45 | $parts = parse_url($location);
46 | parse_str($parts['query'], $query);
47 |
48 | $this->assertEquals($query['error'], 'access_denied');
49 | $this->assertEquals($query['error_description'], 'The user denied access to your application');
50 | }
51 |
52 | public function testSuccessfulRequestFragmentParameter()
53 | {
54 | $server = $this->getTestServer(array('allow_implicit' => true));
55 | $request = new Request(array(
56 | 'client_id' => 'Test Client ID', // valid client id
57 | 'redirect_uri' => 'http://adobe.com', // valid redirect URI
58 | 'response_type' => 'token', // valid response type
59 | 'state' => 'xyz',
60 | ));
61 | $server->handleAuthorizeRequest($request, $response = new Response(), true);
62 |
63 | $this->assertEquals($response->getStatusCode(), 302);
64 | $this->assertNull($response->getParameter('error'));
65 | $this->assertNull($response->getParameter('error_description'));
66 |
67 | $location = $response->getHttpHeader('Location');
68 | $parts = parse_url($location);
69 |
70 | $this->assertEquals('http', $parts['scheme']); // same as passed in to redirect_uri
71 | $this->assertEquals('adobe.com', $parts['host']); // same as passed in to redirect_uri
72 | $this->assertArrayHasKey('fragment', $parts);
73 | $this->assertFalse(isset($parts['query']));
74 |
75 | // assert fragment is in "application/x-www-form-urlencoded" format
76 | parse_str($parts['fragment'], $params);
77 | $this->assertNotNull($params);
78 | $this->assertArrayHasKey('access_token', $params);
79 | $this->assertArrayHasKey('expires_in', $params);
80 | $this->assertArrayHasKey('token_type', $params);
81 | }
82 |
83 | public function testSuccessfulRequestReturnsStateParameter()
84 | {
85 | $server = $this->getTestServer(array('allow_implicit' => true));
86 | $request = new Request(array(
87 | 'client_id' => 'Test Client ID', // valid client id
88 | 'redirect_uri' => 'http://adobe.com', // valid redirect URI
89 | 'response_type' => 'token', // valid response type
90 | 'state' => 'test', // valid state string (just needs to be passed back to us)
91 | ));
92 | $server->handleAuthorizeRequest($request, $response = new Response(), true);
93 |
94 | $this->assertEquals($response->getStatusCode(), 302);
95 | $this->assertNull($response->getParameter('error'));
96 | $this->assertNull($response->getParameter('error_description'));
97 |
98 | $location = $response->getHttpHeader('Location');
99 | $parts = parse_url($location);
100 | $this->assertArrayHasKey('fragment', $parts);
101 | parse_str($parts['fragment'], $params);
102 |
103 | $this->assertArrayHasKey('state', $params);
104 | $this->assertEquals($params['state'], 'test');
105 | }
106 |
107 | public function testSuccessfulRequestStripsExtraParameters()
108 | {
109 | $server = $this->getTestServer(array('allow_implicit' => true));
110 | $request = new Request(array(
111 | 'client_id' => 'Test Client ID', // valid client id
112 | 'redirect_uri' => 'http://adobe.com?fake=something', // valid redirect URI
113 | 'response_type' => 'token', // valid response type
114 | 'state' => 'test', // valid state string (just needs to be passed back to us)
115 | 'fake' => 'something', // add extra param to querystring
116 | ));
117 | $server->handleAuthorizeRequest($request, $response = new Response(), true);
118 |
119 | $this->assertEquals($response->getStatusCode(), 302);
120 | $this->assertNull($response->getParameter('error'));
121 | $this->assertNull($response->getParameter('error_description'));
122 |
123 | $location = $response->getHttpHeader('Location');
124 | $parts = parse_url($location);
125 | $this->assertFalse(isset($parts['fake']));
126 | $this->assertArrayHasKey('fragment', $parts);
127 | parse_str($parts['fragment'], $params);
128 |
129 | $this->assertFalse(isset($params['fake']));
130 | $this->assertArrayHasKey('state', $params);
131 | $this->assertEquals($params['state'], 'test');
132 | }
133 |
134 | private function getTestServer($config = array())
135 | {
136 | $storage = Bootstrap::getInstance()->getMemoryStorage();
137 | $server = new Server($storage, $config);
138 |
139 | // Add the two types supported for authorization grant
140 | $server->addGrantType(new AuthorizationCode($storage));
141 |
142 | return $server;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php:
--------------------------------------------------------------------------------
1 | handleUserInfoRequest(new Request(), $response);
21 | $this->assertEquals(401, $response->getStatusCode());
22 | }
23 |
24 | public function testValidToken()
25 | {
26 | $server = $this->getTestServer();
27 | $request = Request::createFromGlobals();
28 | $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-openid-connect';
29 | $response = new Response();
30 |
31 | $server->handleUserInfoRequest($request, $response);
32 | $parameters = $response->getParameters();
33 | $this->assertEquals($parameters['sub'], 'testuser');
34 | $this->assertEquals($parameters['email'], 'testuser@test.com');
35 | $this->assertEquals($parameters['email_verified'], true);
36 | }
37 |
38 | private function getTestServer($config = array())
39 | {
40 | $storage = Bootstrap::getInstance()->getMemoryStorage();
41 | $server = new Server($storage, $config);
42 |
43 | return $server;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php:
--------------------------------------------------------------------------------
1 | getTestServer();
16 | $request = TestRequest::createPost(array(
17 | 'grant_type' => 'authorization_code', // valid grant type
18 | 'client_id' => 'Test Client ID', // valid client id
19 | 'client_secret' => 'TestSecret', // valid client secret
20 | 'code' => 'testcode-openid', // valid code
21 | ));
22 | $token = $server->grantAccessToken($request, new Response());
23 |
24 | $this->assertNotNull($token);
25 | $this->assertArrayHasKey('id_token', $token);
26 | $this->assertEquals('test_id_token', $token['id_token']);
27 |
28 | // this is only true if "offline_access" was requested
29 | $this->assertFalse(isset($token['refresh_token']));
30 | }
31 |
32 | public function testOfflineAccess()
33 | {
34 | $server = $this->getTestServer();
35 | $request = TestRequest::createPost(array(
36 | 'grant_type' => 'authorization_code', // valid grant type
37 | 'client_id' => 'Test Client ID', // valid client id
38 | 'client_secret' => 'TestSecret', // valid client secret
39 | 'code' => 'testcode-openid', // valid code
40 | 'scope' => 'offline_access', // valid code
41 | ));
42 | $token = $server->grantAccessToken($request, new Response());
43 |
44 | $this->assertNotNull($token);
45 | $this->assertArrayHasKey('id_token', $token);
46 | $this->assertEquals('test_id_token', $token['id_token']);
47 | $this->assertTrue(isset($token['refresh_token']));
48 | }
49 |
50 | private function getTestServer()
51 | {
52 | $storage = Bootstrap::getInstance()->getMemoryStorage();
53 | $server = new Server($storage, array('use_openid_connect' => true));
54 | $server->addGrantType(new AuthorizationCode($storage));
55 |
56 | return $server;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php:
--------------------------------------------------------------------------------
1 | getTestServer();
18 |
19 | $request = new Request(array(
20 | 'response_type' => 'code id_token',
21 | 'redirect_uri' => 'http://adobe.com',
22 | 'client_id' => 'Test Client ID',
23 | 'scope' => 'openid',
24 | 'state' => 'test',
25 | 'nonce' => 'test',
26 | ));
27 |
28 | $server->handleAuthorizeRequest($request, $response = new Response(), true);
29 |
30 | $this->assertEquals($response->getStatusCode(), 302);
31 | $location = $response->getHttpHeader('Location');
32 | $this->assertStringNotContainsString('error', $location);
33 |
34 | $parts = parse_url($location);
35 | $this->assertArrayHasKey('query', $parts);
36 |
37 | // assert fragment is in "application/x-www-form-urlencoded" format
38 | parse_str($parts['query'], $params);
39 | $this->assertNotNull($params);
40 | $this->assertArrayHasKey('id_token', $params);
41 | $this->assertArrayHasKey('code', $params);
42 |
43 | // validate ID Token
44 | $parts = explode('.', $params['id_token']);
45 | foreach ($parts as &$part) {
46 | // Each part is a base64url encoded json string.
47 | $part = str_replace(array('-', '_'), array('+', '/'), $part);
48 | $part = base64_decode($part);
49 | $part = json_decode($part, true);
50 | }
51 | list($header, $claims, $signature) = $parts;
52 |
53 | $this->assertArrayHasKey('iss', $claims);
54 | $this->assertArrayHasKey('sub', $claims);
55 | $this->assertArrayHasKey('aud', $claims);
56 | $this->assertArrayHasKey('iat', $claims);
57 | $this->assertArrayHasKey('exp', $claims);
58 | $this->assertArrayHasKey('auth_time', $claims);
59 | $this->assertArrayHasKey('nonce', $claims);
60 |
61 | // only exists if an access token was granted along with the id_token
62 | $this->assertArrayNotHasKey('at_hash', $claims);
63 |
64 | $this->assertEquals($claims['iss'], 'test');
65 | $this->assertEquals($claims['aud'], 'Test Client ID');
66 | $this->assertEquals($claims['nonce'], 'test');
67 | $duration = $claims['exp'] - $claims['iat'];
68 | $this->assertEquals($duration, 3600);
69 | }
70 |
71 | public function testUserClaimsWithUserId()
72 | {
73 | // add the test parameters in memory
74 | $server = $this->getTestServer();
75 |
76 | $request = new Request(array(
77 | 'response_type' => 'code id_token',
78 | 'redirect_uri' => 'http://adobe.com',
79 | 'client_id' => 'Test Client ID',
80 | 'scope' => 'openid email',
81 | 'state' => 'test',
82 | 'nonce' => 'test',
83 | ));
84 |
85 | $userId = 'testuser';
86 | $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId);
87 |
88 | $this->assertEquals($response->getStatusCode(), 302);
89 | $location = $response->getHttpHeader('Location');
90 | $this->assertStringNotContainsString('error', $location);
91 |
92 | $parts = parse_url($location);
93 | $this->assertArrayHasKey('query', $parts);
94 |
95 | // assert fragment is in "application/x-www-form-urlencoded" format
96 | parse_str($parts['query'], $params);
97 | $this->assertNotNull($params);
98 | $this->assertArrayHasKey('id_token', $params);
99 | $this->assertArrayHasKey('code', $params);
100 |
101 | // validate ID Token
102 | $parts = explode('.', $params['id_token']);
103 | foreach ($parts as &$part) {
104 | // Each part is a base64url encoded json string.
105 | $part = str_replace(array('-', '_'), array('+', '/'), $part);
106 | $part = base64_decode($part);
107 | $part = json_decode($part, true);
108 | }
109 | list($header, $claims, $signature) = $parts;
110 |
111 | $this->assertArrayHasKey('email', $claims);
112 | $this->assertArrayHasKey('email_verified', $claims);
113 | $this->assertNotNull($claims['email']);
114 | $this->assertNotNull($claims['email_verified']);
115 | }
116 |
117 | public function testUserClaimsWithoutUserId()
118 | {
119 | // add the test parameters in memory
120 | $server = $this->getTestServer();
121 |
122 | $request = new Request(array(
123 | 'response_type' => 'code id_token',
124 | 'redirect_uri' => 'http://adobe.com',
125 | 'client_id' => 'Test Client ID',
126 | 'scope' => 'openid email',
127 | 'state' => 'test',
128 | 'nonce' => 'test',
129 | ));
130 |
131 | $userId = null;
132 | $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId);
133 |
134 | $this->assertEquals($response->getStatusCode(), 302);
135 | $location = $response->getHttpHeader('Location');
136 | $this->assertStringNotContainsString('error', $location);
137 |
138 | $parts = parse_url($location);
139 | $this->assertArrayHasKey('query', $parts);
140 |
141 | // assert fragment is in "application/x-www-form-urlencoded" format
142 | parse_str($parts['query'], $params);
143 | $this->assertNotNull($params);
144 | $this->assertArrayHasKey('id_token', $params);
145 | $this->assertArrayHasKey('code', $params);
146 |
147 | // validate ID Token
148 | $parts = explode('.', $params['id_token']);
149 | foreach ($parts as &$part) {
150 | // Each part is a base64url encoded json string.
151 | $part = str_replace(array('-', '_'), array('+', '/'), $part);
152 | $part = base64_decode($part);
153 | $part = json_decode($part, true);
154 | }
155 | list($header, $claims, $signature) = $parts;
156 |
157 | $this->assertArrayNotHasKey('email', $claims);
158 | $this->assertArrayNotHasKey('email_verified', $claims);
159 | }
160 |
161 | private function getTestServer($config = array())
162 | {
163 | $config += array(
164 | 'use_openid_connect' => true,
165 | 'issuer' => 'test',
166 | 'id_lifetime' => 3600,
167 | 'allow_implicit' => true,
168 | );
169 |
170 | $memoryStorage = Bootstrap::getInstance()->getMemoryStorage();
171 | $memoryStorage->supportedScopes[] = 'email';
172 | $responseTypes = array(
173 | 'code' => $code = new AuthorizationCode($memoryStorage),
174 | 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config),
175 | 'code id_token' => new CodeIdToken($code, $idToken),
176 | );
177 |
178 | $server = new Server($memoryStorage, $config, array(), $responseTypes);
179 | $server->addGrantType(new ClientCredentials($memoryStorage));
180 |
181 | return $server;
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php:
--------------------------------------------------------------------------------
1 | getTestServer(array('allow_implicit' => true));
20 |
21 | $request = new Request(array(
22 | 'response_type' => 'id_token token',
23 | 'redirect_uri' => 'http://adobe.com',
24 | 'client_id' => 'Test Client ID',
25 | 'scope' => 'openid',
26 | 'state' => 'test',
27 | 'nonce' => 'test',
28 | ));
29 |
30 | $server->handleAuthorizeRequest($request, $response = new Response(), true);
31 |
32 | $this->assertEquals($response->getStatusCode(), 302);
33 | $location = $response->getHttpHeader('Location');
34 | $this->assertStringNotContainsString('error', $location);
35 |
36 | $parts = parse_url($location);
37 | $this->assertArrayHasKey('fragment', $parts);
38 | $this->assertFalse(isset($parts['query']));
39 |
40 | // assert fragment is in "application/x-www-form-urlencoded" format
41 | parse_str($parts['fragment'], $params);
42 | $this->assertNotNull($params);
43 | $this->assertArrayHasKey('id_token', $params);
44 | $this->assertArrayHasKey('access_token', $params);
45 |
46 | // validate ID Token
47 | $parts = explode('.', $params['id_token']);
48 | foreach ($parts as &$part) {
49 | // Each part is a base64url encoded json string.
50 | $part = str_replace(array('-', '_'), array('+', '/'), $part);
51 | $part = base64_decode($part);
52 | $part = json_decode($part, true);
53 | }
54 | list($header, $claims, $signature) = $parts;
55 |
56 | $this->assertArrayHasKey('iss', $claims);
57 | $this->assertArrayHasKey('sub', $claims);
58 | $this->assertArrayHasKey('aud', $claims);
59 | $this->assertArrayHasKey('iat', $claims);
60 | $this->assertArrayHasKey('exp', $claims);
61 | $this->assertArrayHasKey('auth_time', $claims);
62 | $this->assertArrayHasKey('nonce', $claims);
63 | $this->assertArrayHasKey('at_hash', $claims);
64 |
65 | $this->assertEquals($claims['iss'], 'test');
66 | $this->assertEquals($claims['aud'], 'Test Client ID');
67 | $this->assertEquals($claims['nonce'], 'test');
68 | $duration = $claims['exp'] - $claims['iat'];
69 | $this->assertEquals($duration, 3600);
70 | }
71 |
72 | private function getTestServer($config = array())
73 | {
74 | $config += array(
75 | 'use_openid_connect' => true,
76 | 'issuer' => 'test',
77 | 'id_lifetime' => 3600,
78 | );
79 |
80 | $memoryStorage = Bootstrap::getInstance()->getMemoryStorage();
81 | $responseTypes = array(
82 | 'token' => $token = new AccessToken($memoryStorage, $memoryStorage),
83 | 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config),
84 | 'id_token token' => new IdTokenToken($token, $idToken),
85 | );
86 |
87 | $server = new Server($memoryStorage, $config, array(), $responseTypes);
88 | $server->addGrantType(new ClientCredentials($memoryStorage));
89 |
90 | return $server;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
15 |
16 | return;
17 | }
18 |
19 | if (!$storage instanceof AuthorizationCodeInterface) {
20 | return;
21 | }
22 |
23 | // assert code we are about to add does not exist
24 | $code = $storage->getAuthorizationCode('new-openid-code');
25 | $this->assertFalse($code);
26 |
27 | // add new code
28 | $expires = time() + 20;
29 | $scope = null;
30 | $id_token = 'fake_id_token';
31 | $success = $storage->setAuthorizationCode('new-openid-code', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token);
32 | $this->assertTrue($success);
33 |
34 | $code = $storage->getAuthorizationCode('new-openid-code');
35 | $this->assertNotNull($code);
36 | $this->assertArrayHasKey('authorization_code', $code);
37 | $this->assertArrayHasKey('client_id', $code);
38 | $this->assertArrayHasKey('user_id', $code);
39 | $this->assertArrayHasKey('redirect_uri', $code);
40 | $this->assertArrayHasKey('expires', $code);
41 | $this->assertEquals($code['authorization_code'], 'new-openid-code');
42 | $this->assertEquals($code['client_id'], 'client ID');
43 | $this->assertEquals($code['user_id'], 'SOMEUSERID');
44 | $this->assertEquals($code['redirect_uri'], 'http://example.com');
45 | $this->assertEquals($code['expires'], $expires);
46 | $this->assertEquals($code['id_token'], $id_token);
47 |
48 | // change existing code
49 | $expires = time() + 42;
50 | $new_id_token = 'fake_id_token-2';
51 | $success = $storage->setAuthorizationCode('new-openid-code', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires, $scope, $new_id_token);
52 | $this->assertTrue($success);
53 |
54 | $code = $storage->getAuthorizationCode('new-openid-code');
55 | $this->assertNotNull($code);
56 | $this->assertArrayHasKey('authorization_code', $code);
57 | $this->assertArrayHasKey('client_id', $code);
58 | $this->assertArrayHasKey('user_id', $code);
59 | $this->assertArrayHasKey('redirect_uri', $code);
60 | $this->assertArrayHasKey('expires', $code);
61 | $this->assertEquals($code['authorization_code'], 'new-openid-code');
62 | $this->assertEquals($code['client_id'], 'client ID2');
63 | $this->assertEquals($code['user_id'], 'SOMEOTHERID');
64 | $this->assertEquals($code['redirect_uri'], 'http://example.org');
65 | $this->assertEquals($code['expires'], $expires);
66 | $this->assertEquals($code['id_token'], $new_id_token);
67 | }
68 |
69 | /** @dataProvider provideStorage */
70 | public function testRemoveIdTokenFromAuthorizationCode($storage)
71 | {
72 | // add new code
73 | $expires = time() + 20;
74 | $scope = null;
75 | $id_token = 'fake_id_token_to_remove';
76 | $authcode = 'new-openid-code-'.rand();
77 | $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token);
78 | $this->assertTrue($success);
79 |
80 | // verify params were set
81 | $code = $storage->getAuthorizationCode($authcode);
82 | $this->assertNotNull($code);
83 | $this->assertArrayHasKey('id_token', $code);
84 | $this->assertEquals($code['id_token'], $id_token);
85 |
86 | // remove the id_token
87 | $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, null);
88 |
89 | // verify the "id_token" is now null
90 | $code = $storage->getAuthorizationCode($authcode);
91 | $this->assertNotNull($code);
92 | $this->assertArrayHasKey('id_token', $code);
93 | $this->assertEquals($code['id_token'], null);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/test/OAuth2/OpenID/Storage/UserClaimsTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
15 |
16 | return;
17 | }
18 |
19 | if (!$storage instanceof UserClaimsInterface) {
20 | // incompatible storage
21 | return;
22 | }
23 |
24 | // invalid user
25 | $claims = $storage->getUserClaims('fake-user', '');
26 | $this->assertFalse($claims);
27 |
28 | // valid user (no scope)
29 | $claims = $storage->getUserClaims('testuser', '');
30 |
31 | /* assert the decoded token is the same */
32 | $this->assertFalse(isset($claims['email']));
33 |
34 | // valid user
35 | $claims = $storage->getUserClaims('testuser', 'email');
36 |
37 | /* assert the decoded token is the same */
38 | $this->assertEquals($claims['email'], "testuser@test.com");
39 | $this->assertEquals($claims['email_verified'], true);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/OAuth2/RequestTest.php:
--------------------------------------------------------------------------------
1 | getTestServer();
16 |
17 | // Smoke test for override request class
18 | // $server->handleTokenRequest($request, $response = new Response());
19 | // $this->assertInstanceOf('Response', $response);
20 | // $server->handleAuthorizeRequest($request, $response = new Response(), true);
21 | // $this->assertInstanceOf('Response', $response);
22 | // $response = $server->verifyResourceRequest($request, $response = new Response());
23 | // $this->assertTrue(is_bool($response));
24 |
25 | /*** make some valid requests ***/
26 |
27 | // Valid Token Request
28 | $request->setPost(array(
29 | 'grant_type' => 'authorization_code',
30 | 'client_id' => 'Test Client ID',
31 | 'client_secret' => 'TestSecret',
32 | 'code' => 'testcode',
33 | ));
34 | $server->handleTokenRequest($request, $response = new Response());
35 | $this->assertEquals($response->getStatusCode(), 200);
36 | $this->assertNull($response->getParameter('error'));
37 | $this->assertNotNUll($response->getParameter('access_token'));
38 | }
39 |
40 | public function testHeadersReturnsValueByKey()
41 | {
42 | $request = new Request(
43 | array(),
44 | array(),
45 | array(),
46 | array(),
47 | array(),
48 | array(),
49 | array(),
50 | array('AUTHORIZATION' => 'Basic secret')
51 | );
52 |
53 | $this->assertEquals('Basic secret', $request->headers('AUTHORIZATION'));
54 | }
55 |
56 | public function testHeadersReturnsDefaultIfHeaderNotPresent()
57 | {
58 | $request = new Request();
59 |
60 | $this->assertEquals('Bearer', $request->headers('AUTHORIZATION', 'Bearer'));
61 | }
62 |
63 | public function testHeadersIsCaseInsensitive()
64 | {
65 | $request = new Request(
66 | array(),
67 | array(),
68 | array(),
69 | array(),
70 | array(),
71 | array(),
72 | array(),
73 | array('AUTHORIZATION' => 'Basic secret')
74 | );
75 |
76 | $this->assertEquals('Basic secret', $request->headers('Authorization'));
77 | }
78 |
79 | public function testRequestReturnsPostParamIfNoQueryParamAvailable()
80 | {
81 | $request = new Request(
82 | array(),
83 | array('client_id' => 'correct')
84 | );
85 |
86 | $this->assertEquals('correct', $request->query('client_id', $request->request('client_id')));
87 | }
88 |
89 | public function testRequestHasHeadersAndServerHeaders()
90 | {
91 | $request = new Request(
92 | array(),
93 | array(),
94 | array(),
95 | array(),
96 | array(),
97 | array('CONTENT_TYPE' => 'text/xml', 'PHP_AUTH_USER' => 'client_id', 'PHP_AUTH_PW' => 'client_pass'),
98 | null,
99 | array('CONTENT_TYPE' => 'application/json')
100 | );
101 |
102 | $this->assertSame('client_id', $request->headers('PHP_AUTH_USER'));
103 | $this->assertSame('client_pass', $request->headers('PHP_AUTH_PW'));
104 | $this->assertSame('application/json', $request->headers('CONTENT_TYPE'));
105 | }
106 |
107 | private function getTestServer($config = array())
108 | {
109 | $storage = Bootstrap::getInstance()->getMemoryStorage();
110 | $server = new Server($storage, $config);
111 |
112 | // Add the two types supported for authorization grant
113 | $server->addGrantType(new AuthorizationCode($storage));
114 |
115 | return $server;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test/OAuth2/ResponseTest.php:
--------------------------------------------------------------------------------
1 | 'bar',
13 | 'halland' => 'oates',
14 | ));
15 |
16 | $string = $response->getResponseBody('xml');
17 | $this->assertStringContainsString('baroates', $string);
18 | }
19 |
20 | public function testSetRedirect()
21 | {
22 | $response = new Response();
23 | $url = 'https://foo/bar';
24 | $state = 'stateparam';
25 | $response->setRedirect(301, $url, $state);
26 | $this->assertEquals(
27 | sprintf('%s?state=%s', $url, $state),
28 | $response->getHttpHeader('Location')
29 | );
30 |
31 | $query = 'query=foo';
32 | $response->setRedirect(301, $url . '?' . $query, $state);
33 | $this->assertEquals(
34 | sprintf('%s?%s&state=%s', $url, $query, $state),
35 | $response->getHttpHeader('Location')
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/OAuth2/ResponseType/AccessTokenTest.php:
--------------------------------------------------------------------------------
1 | array(
15 | 'revoke' => array('mytoken'),
16 | ),
17 | ));
18 |
19 | $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke'));
20 | $accessToken = new AccessToken($tokenStorage);
21 | $accessToken->revokeToken('revoke', 'access_token');
22 | $this->assertFalse($tokenStorage->getAccessToken('revoke'));
23 | }
24 |
25 | public function testRevokeAccessTokenWithoutTypeHint()
26 | {
27 | $tokenStorage = new Memory(array(
28 | 'access_tokens' => array(
29 | 'revoke' => array('mytoken'),
30 | ),
31 | ));
32 |
33 | $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke'));
34 | $accessToken = new AccessToken($tokenStorage);
35 | $accessToken->revokeToken('revoke');
36 | $this->assertFalse($tokenStorage->getAccessToken('revoke'));
37 | }
38 |
39 | public function testRevokeRefreshTokenWithTypeHint()
40 | {
41 | $tokenStorage = new Memory(array(
42 | 'refresh_tokens' => array(
43 | 'revoke' => array('mytoken'),
44 | ),
45 | ));
46 |
47 | $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke'));
48 | $accessToken = new AccessToken(new Memory, $tokenStorage);
49 | $accessToken->revokeToken('revoke', 'refresh_token');
50 | $this->assertFalse($tokenStorage->getRefreshToken('revoke'));
51 | }
52 |
53 | public function testRevokeRefreshTokenWithoutTypeHint()
54 | {
55 | $tokenStorage = new Memory(array(
56 | 'refresh_tokens' => array(
57 | 'revoke' => array('mytoken'),
58 | ),
59 | ));
60 |
61 | $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke'));
62 | $accessToken = new AccessToken(new Memory, $tokenStorage);
63 | $accessToken->revokeToken('revoke');
64 | $this->assertFalse($tokenStorage->getRefreshToken('revoke'));
65 | }
66 |
67 | public function testRevokeAccessTokenWithRefreshTokenTypeHint()
68 | {
69 | $tokenStorage = new Memory(array(
70 | 'access_tokens' => array(
71 | 'revoke' => array('mytoken'),
72 | ),
73 | ));
74 |
75 | $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke'));
76 | $accessToken = new AccessToken($tokenStorage);
77 | $accessToken->revokeToken('revoke', 'refresh_token');
78 | $this->assertFalse($tokenStorage->getAccessToken('revoke'));
79 | }
80 |
81 | public function testRevokeAccessTokenWithBogusTypeHint()
82 | {
83 | $tokenStorage = new Memory(array(
84 | 'access_tokens' => array(
85 | 'revoke' => array('mytoken'),
86 | ),
87 | ));
88 |
89 | $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke'));
90 | $accessToken = new AccessToken($tokenStorage);
91 | $accessToken->revokeToken('revoke', 'foo');
92 | $this->assertFalse($tokenStorage->getAccessToken('revoke'));
93 | }
94 |
95 | public function testRevokeRefreshTokenWithBogusTypeHint()
96 | {
97 | $tokenStorage = new Memory(array(
98 | 'refresh_tokens' => array(
99 | 'revoke' => array('mytoken'),
100 | ),
101 | ));
102 |
103 | $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke'));
104 | $accessToken = new AccessToken(new Memory, $tokenStorage);
105 | $accessToken->revokeToken('revoke', 'foo');
106 | $this->assertFalse($tokenStorage->getRefreshToken('revoke'));
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/test/OAuth2/ScopeTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($scopeUtil->checkScope('invalid', 'list of scopes'));
15 | $this->assertTrue($scopeUtil->checkScope('valid', 'valid and-some other-scopes'));
16 | $this->assertTrue($scopeUtil->checkScope('valid another-valid', 'valid another-valid and-some other-scopes'));
17 | // all scopes must match
18 | $this->assertFalse($scopeUtil->checkScope('valid invalid', 'valid and-some other-scopes'));
19 | $this->assertFalse($scopeUtil->checkScope('valid valid2 invalid', 'valid valid2 and-some other-scopes'));
20 | }
21 |
22 | public function testScopeStorage()
23 | {
24 | $scopeUtil = new Scope();
25 | $this->assertEquals($scopeUtil->getDefaultScope(), null);
26 |
27 | $scopeUtil = new Scope(array(
28 | 'default_scope' => 'default',
29 | 'supported_scopes' => array('this', 'that', 'another'),
30 | ));
31 | $this->assertEquals($scopeUtil->getDefaultScope(), 'default');
32 | $this->assertTrue($scopeUtil->scopeExists('this that another', 'client_id'));
33 |
34 | $memoryStorage = new Memory(array(
35 | 'default_scope' => 'base',
36 | 'supported_scopes' => array('only-this-one'),
37 | ));
38 | $scopeUtil = new Scope($memoryStorage);
39 |
40 | $this->assertEquals($scopeUtil->getDefaultScope(), 'base');
41 | $this->assertTrue($scopeUtil->scopeExists('only-this-one', 'client_id'));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/AccessTokenTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // assert token we are about to add does not exist
17 | $token = $storage->getAccessToken('newtoken');
18 | $this->assertFalse($token);
19 |
20 | // add new token
21 | $expires = time() + 20;
22 | $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires);
23 | $this->assertTrue($success);
24 |
25 | $token = $storage->getAccessToken('newtoken');
26 | $this->assertNotNull($token);
27 | $this->assertArrayHasKey('access_token', $token);
28 | $this->assertArrayHasKey('client_id', $token);
29 | $this->assertArrayHasKey('user_id', $token);
30 | $this->assertArrayHasKey('expires', $token);
31 | $this->assertEquals($token['access_token'], 'newtoken');
32 | $this->assertEquals($token['client_id'], 'client ID');
33 | $this->assertEquals($token['user_id'], 'SOMEUSERID');
34 | $this->assertEquals($token['expires'], $expires);
35 |
36 | // change existing token
37 | $expires = time() + 42;
38 | $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires);
39 | $this->assertTrue($success);
40 |
41 | $token = $storage->getAccessToken('newtoken');
42 | $this->assertNotNull($token);
43 | $this->assertArrayHasKey('access_token', $token);
44 | $this->assertArrayHasKey('client_id', $token);
45 | $this->assertArrayHasKey('user_id', $token);
46 | $this->assertArrayHasKey('expires', $token);
47 | $this->assertEquals($token['access_token'], 'newtoken');
48 | $this->assertEquals($token['client_id'], 'client ID2');
49 | $this->assertEquals($token['user_id'], 'SOMEOTHERID');
50 | $this->assertEquals($token['expires'], $expires);
51 |
52 | // add token with scope having an empty string value
53 | $expires = time() + 42;
54 | $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEOTHERID', $expires, '');
55 | $this->assertTrue($success);
56 | }
57 |
58 | /** @dataProvider provideStorage */
59 | public function testUnsetAccessToken(AccessTokenInterface $storage)
60 | {
61 | if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) {
62 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
63 |
64 | return;
65 | }
66 |
67 | // assert token we are about to unset does not exist
68 | $token = $storage->getAccessToken('revokabletoken');
69 | $this->assertFalse($token);
70 |
71 | // add new token
72 | $expires = time() + 20;
73 | $success = $storage->setAccessToken('revokabletoken', 'client ID', 'SOMEUSERID', $expires);
74 | $this->assertTrue($success);
75 |
76 | // assert unsetAccessToken returns true
77 | $result = $storage->unsetAccessToken('revokabletoken');
78 | $this->assertTrue($result);
79 |
80 | // assert token we unset does not exist
81 | $token = $storage->getAccessToken('revokabletoken');
82 | $this->assertFalse($token);
83 | }
84 |
85 | /** @dataProvider provideStorage */
86 | public function testUnsetAccessTokenReturnsFalse(AccessTokenInterface $storage)
87 | {
88 | if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) {
89 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
90 |
91 | return;
92 | }
93 |
94 | // assert token we are about to unset does not exist
95 | $token = $storage->getAccessToken('nonexistanttoken');
96 | $this->assertFalse($token);
97 |
98 | // assert unsetAccessToken returns false
99 | $result = $storage->unsetAccessToken('nonexistanttoken');
100 | $this->assertFalse($result);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/AuthorizationCodeTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // nonexistant client_id
17 | $details = $storage->getAuthorizationCode('faketoken');
18 | $this->assertFalse($details);
19 |
20 | // valid client_id
21 | $details = $storage->getAuthorizationCode('testtoken');
22 | $this->assertNotNull($details);
23 | }
24 |
25 | /** @dataProvider provideStorage */
26 | public function testSetAuthorizationCode(AuthorizationCodeInterface $storage)
27 | {
28 | if ($storage instanceof NullStorage) {
29 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
30 |
31 | return;
32 | }
33 |
34 | // assert code we are about to add does not exist
35 | $code = $storage->getAuthorizationCode('newcode');
36 | $this->assertFalse($code);
37 |
38 | // add new code
39 | $expires = time() + 20;
40 | $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires);
41 | $this->assertTrue($success);
42 |
43 | $code = $storage->getAuthorizationCode('newcode');
44 | $this->assertNotNull($code);
45 | $this->assertArrayHasKey('authorization_code', $code);
46 | $this->assertArrayHasKey('client_id', $code);
47 | $this->assertArrayHasKey('user_id', $code);
48 | $this->assertArrayHasKey('redirect_uri', $code);
49 | $this->assertArrayHasKey('expires', $code);
50 | $this->assertEquals($code['authorization_code'], 'newcode');
51 | $this->assertEquals($code['client_id'], 'client ID');
52 | $this->assertEquals($code['user_id'], 'SOMEUSERID');
53 | $this->assertEquals($code['redirect_uri'], 'http://example.com');
54 | $this->assertEquals($code['expires'], $expires);
55 |
56 | // change existing code
57 | $expires = time() + 42;
58 | $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires);
59 | $this->assertTrue($success);
60 |
61 | $code = $storage->getAuthorizationCode('newcode');
62 | $this->assertNotNull($code);
63 | $this->assertArrayHasKey('authorization_code', $code);
64 | $this->assertArrayHasKey('client_id', $code);
65 | $this->assertArrayHasKey('user_id', $code);
66 | $this->assertArrayHasKey('redirect_uri', $code);
67 | $this->assertArrayHasKey('expires', $code);
68 | $this->assertEquals($code['authorization_code'], 'newcode');
69 | $this->assertEquals($code['client_id'], 'client ID2');
70 | $this->assertEquals($code['user_id'], 'SOMEOTHERID');
71 | $this->assertEquals($code['redirect_uri'], 'http://example.org');
72 | $this->assertEquals($code['expires'], $expires);
73 |
74 | // add new code with scope having an empty string value
75 | $expires = time() + 20;
76 | $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, '');
77 | $this->assertTrue($success);
78 | }
79 |
80 | /** @dataProvider provideStorage */
81 | public function testExpireAccessToken(AccessTokenInterface $storage)
82 | {
83 | if ($storage instanceof NullStorage) {
84 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
85 |
86 | return;
87 | }
88 |
89 | // create a valid code
90 | $expires = time() + 20;
91 | $success = $storage->setAuthorizationCode('code-to-expire', 'client ID', 'SOMEUSERID', 'http://example.com', time() + 20);
92 | $this->assertTrue($success);
93 |
94 | // verify the new code exists
95 | $code = $storage->getAuthorizationCode('code-to-expire');
96 | $this->assertNotNull($code);
97 |
98 | $this->assertArrayHasKey('authorization_code', $code);
99 | $this->assertEquals($code['authorization_code'], 'code-to-expire');
100 |
101 | // now expire the code and ensure it's no longer available
102 | $storage->expireAuthorizationCode('code-to-expire');
103 | $code = $storage->getAuthorizationCode('code-to-expire');
104 | $this->assertFalse($code);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/ClientCredentialsTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // nonexistant client_id
17 | $pass = $storage->checkClientCredentials('fakeclient', 'testpass');
18 | $this->assertFalse($pass);
19 |
20 | // invalid password
21 | $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials');
22 | $this->assertFalse($pass);
23 |
24 | // valid credentials
25 | $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass');
26 | $this->assertTrue($pass);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/ClientTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // nonexistant client_id
17 | $details = $storage->getClientDetails('fakeclient');
18 | $this->assertFalse($details);
19 |
20 | // valid client_id
21 | $details = $storage->getClientDetails('oauth_test_client');
22 | $this->assertNotNull($details);
23 | $this->assertArrayHasKey('client_id', $details);
24 | $this->assertArrayHasKey('client_secret', $details);
25 | $this->assertArrayHasKey('redirect_uri', $details);
26 | }
27 |
28 | /** @dataProvider provideStorage */
29 | public function testCheckRestrictedGrantType(ClientInterface $storage)
30 | {
31 | if ($storage instanceof NullStorage) {
32 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
33 |
34 | return;
35 | }
36 |
37 | // Check invalid
38 | $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code');
39 | $this->assertFalse($pass);
40 |
41 | // Check valid
42 | $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit');
43 | $this->assertTrue($pass);
44 | }
45 |
46 | /** @dataProvider provideStorage */
47 | public function testGetAccessToken(ClientInterface $storage)
48 | {
49 | if ($storage instanceof NullStorage) {
50 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
51 |
52 | return;
53 | }
54 |
55 | // nonexistant client_id
56 | $details = $storage->getAccessToken('faketoken');
57 | $this->assertFalse($details);
58 |
59 | // valid client_id
60 | $details = $storage->getAccessToken('testtoken');
61 | $this->assertNotNull($details);
62 | }
63 |
64 | /** @dataProvider provideStorage */
65 | public function testIsPublicClient(ClientInterface $storage)
66 | {
67 | if ($storage instanceof NullStorage) {
68 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
69 |
70 | return;
71 | }
72 |
73 | $publicClientId = 'public-client-'.rand();
74 | $confidentialClientId = 'confidential-client-'.rand();
75 |
76 | // create a new client
77 | $success1 = $storage->setClientDetails($publicClientId, '');
78 | $success2 = $storage->setClientDetails($confidentialClientId, 'some-secret');
79 | $this->assertTrue($success1);
80 | $this->assertTrue($success2);
81 |
82 | // assert isPublicClient for both
83 | $this->assertTrue($storage->isPublicClient($publicClientId));
84 | $this->assertFalse($storage->isPublicClient($confidentialClientId));
85 | }
86 |
87 | /** @dataProvider provideStorage */
88 | public function testSaveClient(ClientInterface $storage)
89 | {
90 | if ($storage instanceof NullStorage) {
91 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
92 |
93 | return;
94 | }
95 |
96 | $clientId = 'some-client-'.rand();
97 |
98 | // create a new client
99 | $success = $storage->setClientDetails($clientId, 'somesecret', 'http://test.com', 'client_credentials', 'clientscope1', 'brent@brentertainment.com');
100 | $this->assertTrue($success);
101 |
102 | // valid client_id
103 | $details = $storage->getClientDetails($clientId);
104 | $this->assertEquals($details['client_secret'], 'somesecret');
105 | $this->assertEquals($details['redirect_uri'], 'http://test.com');
106 | $this->assertEquals($details['grant_types'], 'client_credentials');
107 | $this->assertEquals($details['scope'], 'clientscope1');
108 | $this->assertEquals($details['user_id'], 'brent@brentertainment.com');
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/DynamoDBTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder('\Aws\DynamoDb\DynamoDbClient')
10 | ->disableOriginalConstructor()
11 | ->setMethods(array('query'))
12 | ->getMock();
13 |
14 | $return = $this->getMockBuilder('\Guzzle\Service\Resource\Model')
15 | ->setMethods(array('count', 'toArray'))
16 | ->getMock();
17 |
18 | $data = array(
19 | 'Items' => array(),
20 | 'Count' => 0,
21 | 'ScannedCount'=> 0
22 | );
23 |
24 | $return->expects($this->once())
25 | ->method('count')
26 | ->will($this->returnValue(count($data)));
27 |
28 | $return->expects($this->once())
29 | ->method('toArray')
30 | ->will($this->returnValue($data));
31 |
32 | // should return null default scope if none is set in database
33 | $client->expects($this->once())
34 | ->method('query')
35 | ->will($this->returnValue($return));
36 |
37 | $storage = new DynamoDB($client);
38 | $this->assertNull($storage->getDefaultScope());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/JwtAccessTokenTest.php:
--------------------------------------------------------------------------------
1 | getMemoryStorage();
20 | $encryptionUtil = new Jwt();
21 |
22 | $jwtAccessToken = array(
23 | 'access_token' => rand(),
24 | 'expires' => time() + 100,
25 | 'scope' => 'foo',
26 | );
27 |
28 | $token = $encryptionUtil->encode($jwtAccessToken, $storage->getPrivateKey(), $storage->getEncryptionAlgorithm());
29 |
30 | $this->assertNotNull($token);
31 |
32 | $tokenData = $crypto->getAccessToken($token);
33 |
34 | $this->assertTrue(is_array($tokenData));
35 |
36 | /* assert the decoded token is the same */
37 | $this->assertEquals($tokenData['access_token'], $jwtAccessToken['access_token']);
38 | $this->assertEquals($tokenData['expires'], $jwtAccessToken['expires']);
39 | $this->assertEquals($tokenData['scope'], $jwtAccessToken['scope']);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/JwtBearerTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // nonexistant client_id
17 | $key = $storage->getClientKey('this-is-not-real', 'nor-is-this');
18 | $this->assertFalse($key);
19 |
20 | // valid client_id and subject
21 | $key = $storage->getClientKey('oauth_test_client', 'test_subject');
22 | $this->assertNotNull($key);
23 | $this->assertEquals($key, Bootstrap::getInstance()->getTestPublicKey());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/PdoTest.php:
--------------------------------------------------------------------------------
1 | getSqliteDir());
14 | $pdo = new \PDO($dsn);
15 | $storage = new Pdo($pdo);
16 |
17 | $this->assertNotNull($storage->getClientDetails('oauth_test_client'));
18 | }
19 |
20 | public function testCreatePdoStorageUsingDSN()
21 | {
22 | $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir());
23 | $storage = new Pdo($dsn);
24 |
25 | $this->assertNotNull($storage->getClientDetails('oauth_test_client'));
26 | }
27 |
28 | public function testCreatePdoStorageUsingConfig()
29 | {
30 | $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir());
31 | $config = array('dsn' => $dsn);
32 | $storage = new Pdo($config);
33 |
34 | $this->assertNotNull($storage->getClientDetails('oauth_test_client'));
35 | }
36 |
37 | public function testCreatePdoStorageWithoutDSNThrowsException()
38 | {
39 | $this->expectErrorMessage('dsn');
40 | $config = array('username' => 'brent', 'password' => 'brentisaballer');
41 | $storage = new Pdo($config);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/PublicKeyTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | if (!$storage instanceof PublicKeyInterface) {
17 | // incompatible storage
18 | return;
19 | }
20 |
21 | $configDir = Bootstrap::getInstance()->getConfigDir();
22 | $globalPublicKey = file_get_contents($configDir.'/keys/id_rsa.pub');
23 | $globalPrivateKey = file_get_contents($configDir.'/keys/id_rsa');
24 |
25 | /* assert values from storage */
26 | $this->assertEquals($storage->getPublicKey(), $globalPublicKey);
27 | $this->assertEquals($storage->getPrivateKey(), $globalPrivateKey);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/RefreshTokenTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // assert token we are about to add does not exist
17 | $token = $storage->getRefreshToken('refreshtoken');
18 | $this->assertFalse($token);
19 |
20 | // add new token
21 | $expires = time() + 20;
22 | $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires);
23 | $this->assertTrue($success);
24 |
25 | $token = $storage->getRefreshToken('refreshtoken');
26 | $this->assertNotNull($token);
27 | $this->assertArrayHasKey('refresh_token', $token);
28 | $this->assertArrayHasKey('client_id', $token);
29 | $this->assertArrayHasKey('user_id', $token);
30 | $this->assertArrayHasKey('expires', $token);
31 | $this->assertEquals($token['refresh_token'], 'refreshtoken');
32 | $this->assertEquals($token['client_id'], 'client ID');
33 | $this->assertEquals($token['user_id'], 'SOMEUSERID');
34 | $this->assertEquals($token['expires'], $expires);
35 |
36 | // add token with scope having an empty string value
37 | $expires = time() + 20;
38 | $success = $storage->setRefreshToken('refreshtoken2', 'client ID', 'SOMEUSERID', $expires, '');
39 | $this->assertTrue($success);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/ScopeTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
14 |
15 | return;
16 | }
17 |
18 | if (!$storage instanceof ScopeInterface) {
19 | // incompatible storage
20 | return;
21 | }
22 |
23 | //Test getting scopes
24 | $scopeUtil = new Scope($storage);
25 | $this->assertTrue($scopeUtil->scopeExists('supportedscope1'));
26 | $this->assertTrue($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3'));
27 | $this->assertFalse($scopeUtil->scopeExists('fakescope'));
28 | $this->assertFalse($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3 fakescope'));
29 | }
30 |
31 | /** @dataProvider provideStorage */
32 | public function testGetDefaultScope($storage)
33 | {
34 | if ($storage instanceof NullStorage) {
35 | $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage());
36 |
37 | return;
38 | }
39 |
40 | if (!$storage instanceof ScopeInterface) {
41 | // incompatible storage
42 | return;
43 | }
44 |
45 | // test getting default scope
46 | $scopeUtil = new Scope($storage);
47 | $expected = explode(' ', $scopeUtil->getDefaultScope());
48 | $actual = explode(' ', 'defaultscope1 defaultscope2');
49 | sort($expected);
50 | sort($actual);
51 | $this->assertEquals($expected, $actual);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/OAuth2/Storage/UserCredentialsTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Skipped Storage: ' . $storage->getMessage());
12 |
13 | return;
14 | }
15 |
16 | // create a new user for testing
17 | $success = $storage->setUser('testusername', 'testpass', 'Test', 'User');
18 | $this->assertTrue($success);
19 |
20 | // correct credentials
21 | $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass'));
22 | // invalid password
23 | $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass'));
24 | // invalid username
25 | $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass'));
26 |
27 | // invalid username
28 | $this->assertFalse($storage->getUserDetails('fakeusername'));
29 |
30 | // ensure all properties are set
31 | $user = $storage->getUserDetails('testusername');
32 | $this->assertTrue($user !== false);
33 | $this->assertArrayHasKey('user_id', $user);
34 | $this->assertArrayHasKey('first_name', $user);
35 | $this->assertArrayHasKey('last_name', $user);
36 | $this->assertEquals($user['user_id'], 'testusername');
37 | $this->assertEquals($user['first_name'], 'Test');
38 | $this->assertEquals($user['last_name'], 'User');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/OAuth2/TokenType/BearerTest.php:
--------------------------------------------------------------------------------
1 | 'ThisIsMyAccessToken'
16 | ));
17 | $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8';
18 |
19 | $param = $bearer->getAccessTokenParameter($request, $response = new Response());
20 | $this->assertEquals($param, 'ThisIsMyAccessToken');
21 | }
22 |
23 | public function testInvalidContentType()
24 | {
25 | $bearer = new Bearer();
26 | $request = TestRequest::createPost(array(
27 | 'access_token' => 'ThisIsMyAccessToken'
28 | ));
29 | $request->server['CONTENT_TYPE'] = 'application/json; charset=UTF-8';
30 |
31 | $param = $bearer->getAccessTokenParameter($request, $response = new Response());
32 | $this->assertNull($param);
33 | $this->assertEquals($response->getStatusCode(), 400);
34 | $this->assertEquals($response->getParameter('error'), 'invalid_request');
35 | $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"');
36 | }
37 |
38 | public function testMissingContentTypeExpectsToBeCorrectContent()
39 | {
40 | $bearer = new Bearer();
41 | $request = TestRequest::createPost(array(
42 | 'access_token' => 'ThisIsMyAccessToken'
43 | ));
44 |
45 | $param = $bearer->getAccessTokenParameter($request, $response = new Response());
46 | $this->assertEquals($param, 'ThisIsMyAccessToken');
47 | }
48 |
49 | public function testValidRequestUsingAuthorizationHeader()
50 | {
51 | $bearer = new Bearer();
52 | $request = new TestRequest();
53 | $request->headers['AUTHORIZATION'] = 'Bearer MyToken';
54 | $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8';
55 |
56 | $param = $bearer->getAccessTokenParameter($request, $response = new Response());
57 | $this->assertEquals('MyToken', $param);
58 | }
59 |
60 | public function testValidRequestUsingAuthorizationHeaderCaseInsensitive()
61 | {
62 | $bearer = new Bearer();
63 | $request = new TestRequest();
64 | $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8';
65 | $request->headers['Authorization'] = 'Bearer MyToken';
66 |
67 | $param = $bearer->getAccessTokenParameter($request, $response = new Response());
68 | $this->assertEquals('MyToken', $param);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/bootstrap.php:
--------------------------------------------------------------------------------
1 | query = $_GET;
18 | $this->request = $_POST;
19 | $this->server = $_SERVER;
20 | $this->headers = array();
21 | }
22 |
23 | public function query($name, $default = null)
24 | {
25 | return isset($this->query[$name]) ? $this->query[$name] : $default;
26 | }
27 |
28 | public function request($name, $default = null)
29 | {
30 | return isset($this->request[$name]) ? $this->request[$name] : $default;
31 | }
32 |
33 | public function server($name, $default = null)
34 | {
35 | return isset($this->server[$name]) ? $this->server[$name] : $default;
36 | }
37 |
38 | public function getAllQueryParameters()
39 | {
40 | return $this->query;
41 | }
42 |
43 | public function setQuery(array $query)
44 | {
45 | $this->query = $query;
46 | }
47 |
48 | public function setMethod($method)
49 | {
50 | $this->server['REQUEST_METHOD'] = $method;
51 | }
52 |
53 | public function setPost(array $params)
54 | {
55 | $this->setMethod('POST');
56 | $this->request = $params;
57 | }
58 |
59 | public static function createPost(array $params = array())
60 | {
61 | $request = new self();
62 | $request->setPost($params);
63 |
64 | return $request;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/test/lib/OAuth2/Storage/BaseTest.php:
--------------------------------------------------------------------------------
1 | getMemoryStorage();
12 | $sqlite = Bootstrap::getInstance()->getSqlitePdo();
13 | $mysql = Bootstrap::getInstance()->getMysqlPdo();
14 | $postgres = Bootstrap::getInstance()->getPostgresPdo();
15 | $mongo = Bootstrap::getInstance()->getMongo();
16 | $mongoDb = Bootstrap::getInstance()->getMongoDB();
17 | $redis = Bootstrap::getInstance()->getRedisStorage();
18 | $cassandra = Bootstrap::getInstance()->getCassandraStorage();
19 | $dynamodb = Bootstrap::getInstance()->getDynamoDbStorage();
20 | $couchbase = Bootstrap::getInstance()->getCouchbase();
21 |
22 | /* hack until we can fix "default_scope" dependencies in other tests */
23 | $memory->defaultScope = 'defaultscope1 defaultscope2';
24 |
25 | return array(
26 | array($memory),
27 | array($sqlite),
28 | array($mysql),
29 | array($postgres),
30 | array($mongo),
31 | array($mongoDb),
32 | array($redis),
33 | array($cassandra),
34 | array($dynamodb),
35 | array($couchbase),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/lib/OAuth2/Storage/NullStorage.php:
--------------------------------------------------------------------------------
1 | name = $name;
16 | $this->description = $description;
17 | }
18 |
19 | public function __toString()
20 | {
21 | return $this->name;
22 | }
23 |
24 | public function getMessage()
25 | {
26 | if ($this->description) {
27 | return $this->description;
28 | }
29 |
30 | return $this->name;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------