├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bin └── bcrypt.php ├── composer.json ├── config ├── module.config.php └── oauth2.local.php.dist ├── data ├── db_oauth2.sql ├── db_oauth2_postgresql.sql └── dbtest.sqlite ├── src ├── Adapter │ ├── BcryptTrait.php │ ├── Exception │ │ └── RuntimeException.php │ ├── IbmDb2Adapter.php │ ├── MongoAdapter.php │ └── PdoAdapter.php ├── Controller │ ├── AuthController.php │ └── Exception │ │ ├── ExceptionInterface.php │ │ └── RuntimeException.php ├── ExceptionInterface.php ├── Factory │ ├── AuthControllerFactory.php │ ├── IbmDb2AdapterFactory.php │ ├── MongoAdapterFactory.php │ ├── OAuth2ServerFactory.php │ ├── OAuth2ServerInstanceFactory.php │ └── PdoAdapterFactory.php ├── Module.php └── Provider │ └── UserId │ ├── AuthenticationService.php │ ├── AuthenticationServiceFactory.php │ └── UserIdProviderInterface.php └── view └── zf └── auth ├── authorize.phtml └── receive-code.phtml /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 1.5.1 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 1.5.0 - 2018-05-07 28 | 29 | ### Added 30 | 31 | - [#167](https://github.com/zfcampus/zf-oauth2/pull/167) adds support for PHP 7.1 and 7.2. 32 | 33 | ### Changed 34 | 35 | - [#160](https://github.com/zfcampus/zf-oauth2/pull/160) alters `AuthController::tokenAction()` such that it uses the exception code from 36 | a caught `ProblemExceptionInterface` instance as the ApiProblem status if it falls in the 400-600 range. 37 | 38 | - [#151](https://github.com/zfcampus/zf-oauth2/pull/151) updates `ZF\OAuth2\Provider\UserId\AuthenticationService` to allow injecting any 39 | `Zend\Authentication\AuthenticationServiceInterface` implementation, not just `Zend\Authentication\AuthenticationService`. 40 | 41 | ### Deprecated 42 | 43 | - Nothing. 44 | 45 | ### Removed 46 | 47 | - [#167](https://github.com/zfcampus/zf-oauth2/pull/167) removes support for HHVM. 48 | 49 | ### Fixed 50 | 51 | - Nothing. 52 | 53 | ## 1.4.0 - 2016-07-10 54 | 55 | ### Added 56 | 57 | - [#149](https://github.com/zfcampus/zf-oauth2/pull/149) adds support for usage 58 | of ext/mongodb with `ZF\OAuth2\Adapter\MongoAdapter`; users will need to also 59 | install a compatibility package to do so: 60 | `composer require alcaeus/mongo-php-adapter` 61 | - [#141](https://github.com/zfcampus/zf-oauth2/pull/141) and 62 | [#148](https://github.com/zfcampus/zf-oauth2/pull/148) update the component to 63 | allow usage with v3 releases of Zend Framework components on which it depends, 64 | while maintaining backwards compatibility with v2 components. 65 | - [#141](https://github.com/zfcampus/zf-oauth2/pull/141) and 66 | [#148](https://github.com/zfcampus/zf-oauth2/pull/148) add support for PHP 7. 67 | - [#122](https://github.com/zfcampus/zf-oauth2/pull/122) adds support for token 68 | revocation via the `/oauth/revoke` path. The path expects a POST request as 69 | either urlencoded or JSON values with the parameters: 70 | - `token`, the access token to revoke 71 | - `token_type_hint => access_token` to indicate an access token is being 72 | revoked. 73 | - [#146](https://github.com/zfcampus/zf-oauth2/pull/146) updates the 74 | `AuthController` to catch `ZF\ApiProblem\Exception\ProblemExceptionInterface` 75 | instances thrown by the OAuth2 server and return `ApiProblemResponse`s. 76 | 77 | ### Deprecated 78 | 79 | - Nothing. 80 | 81 | ### Removed 82 | 83 | - [#141](https://github.com/zfcampus/zf-oauth2/pull/141) removes support for PHP 5.5. 84 | 85 | ### Fixed 86 | 87 | - Nothing. 88 | 89 | ## 1.3.3 - 2016-07-07 90 | 91 | ### Added 92 | 93 | - Nothing. 94 | 95 | ### Changed 96 | 97 | - Nothing. 98 | 99 | ### Deprecated 100 | 101 | - Nothing. 102 | 103 | ### Removed 104 | 105 | - Nothing. 106 | 107 | ### Fixed 108 | 109 | - [#147](https://github.com/zfcampus/zf-oauth2/pull/147) fixes an issue in the 110 | `AuthControllerFactory` introduced originally by a change in zend-mvc (and 111 | since corrected in that component). The patch to `AuthControllerFactory` makes 112 | it forwards compatible with zend-servicemanager v3, and prevents the original 113 | issue from recurring in the future. 114 | - [#144](https://github.com/zfcampus/zf-oauth2/pull/144) removes an unused 115 | variable from the `receive-code` template. 116 | 117 | ## 1.3.2 - 2016-06-24 118 | 119 | ### Added 120 | 121 | - Nothing. 122 | 123 | ### Deprecated 124 | 125 | - Nothing. 126 | 127 | ### Removed 128 | 129 | - Nothing. 130 | 131 | ### Fixed 132 | 133 | - [#120](https://github.com/zfcampus/zf-oauth2/pull/120) fixes a typo in the 134 | `ZF\OAuth2\Provider\UserId\AuthenticationService` which prevented returning of 135 | the user identifier. 136 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zf-oauth2 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas-api-tools/api-tools-oauth2](https://github.com/laminas-api-tools/api-tools-oauth2). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zfcampus/zf-oauth2.svg?branch=master)](https://secure.travis-ci.org/zfcampus/zf-oauth2) 8 | [![Coverage Status](https://coveralls.io/repos/github/zfcampus/zf-oauth2/badge.svg?branch=master)](https://coveralls.io/github/zfcampus/zf-oauth2?branch=master) 9 | 10 | ZF module for [OAuth2](http://oauth.net/2/) authentication. 11 | 12 | This module uses the [oauth2-server-php](https://github.com/bshaffer/oauth2-server-php) 13 | library by Brent Shaffer to provide OAuth2 support. 14 | 15 | ## Requirements 16 | 17 | Please see the [composer.json](composer.json) file. 18 | 19 | ## Installation 20 | 21 | You can install using: 22 | 23 | ```bash 24 | $ composer require zfcampus/zf-oauth2 25 | ``` 26 | 27 | If you are using ext/mongodb, you will also need to install a compatibility 28 | package: 29 | 30 | ```bash 31 | $ composer require alcaeus/mongo-php-adapter 32 | ``` 33 | 34 | Finally, you will need to add the following modules to your application's 35 | configuration: 36 | 37 | ```php 38 | 'modules' => [ 39 | /* ... */ 40 | 'ZF\ApiProblem', 41 | 'ZF\ContentNegotiation', 42 | 'ZF\OAuth2', 43 | ], 44 | ``` 45 | 46 | > ### zf-component-installer 47 | > 48 | > If you use [zf-component-installer](https://github.com/zendframework/zf-component-installer), 49 | > that plugin will install zf-oauth2 and its other Apigility dependencies as 50 | > modules for you. 51 | 52 | ## Configuration 53 | 54 | This module uses any PDO-suported database to manage the OAuth2 information 55 | (users, client, token, etc). The database structure is stored in 56 | `data/db_oauth2.sql`. 57 | 58 | ```sql 59 | CREATE TABLE oauth_clients ( 60 | client_id VARCHAR(80) NOT NULL, 61 | client_secret VARCHAR(80) NOT NULL, 62 | redirect_uri VARCHAR(2000) NOT NULL, 63 | grant_types VARCHAR(80), 64 | scope VARCHAR(2000), 65 | user_id VARCHAR(255), 66 | CONSTRAINT clients_client_id_pk PRIMARY KEY (client_id) 67 | ); 68 | CREATE TABLE oauth_access_tokens ( 69 | access_token VARCHAR(40) NOT NULL, 70 | client_id VARCHAR(80) NOT NULL, 71 | user_id VARCHAR(255), 72 | expires TIMESTAMP NOT NULL, 73 | scope VARCHAR(2000), 74 | CONSTRAINT access_token_pk PRIMARY KEY (access_token) 75 | ); 76 | CREATE TABLE oauth_authorization_codes ( 77 | authorization_code VARCHAR(40) NOT NULL, 78 | client_id VARCHAR(80) NOT NULL, 79 | user_id VARCHAR(255), 80 | redirect_uri VARCHAR(2000), 81 | expires TIMESTAMP NOT NULL, 82 | scope VARCHAR(2000), 83 | id_token VARCHAR(2000), 84 | CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code) 85 | ); 86 | CREATE TABLE oauth_refresh_tokens ( 87 | refresh_token VARCHAR(40) NOT NULL, 88 | client_id VARCHAR(80) NOT NULL, 89 | user_id VARCHAR(255), 90 | expires TIMESTAMP NOT NULL, 91 | scope VARCHAR(2000), 92 | CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token) 93 | ); 94 | CREATE TABLE oauth_users ( 95 | username VARCHAR(255) NOT NULL, 96 | password VARCHAR(2000), 97 | first_name VARCHAR(255), 98 | last_name VARCHAR(255), 99 | CONSTRAINT username_pk PRIMARY KEY (username) 100 | ); 101 | CREATE TABLE oauth_scopes ( 102 | type VARCHAR(255) NOT NULL DEFAULT "supported", 103 | scope VARCHAR(2000), 104 | client_id VARCHAR (80), 105 | is_default SMALLINT DEFAULT NULL 106 | ); 107 | CREATE TABLE oauth_jwt ( 108 | client_id VARCHAR(80) NOT NULL, 109 | subject VARCHAR(80), 110 | public_key VARCHAR(2000), 111 | CONSTRAINT jwt_client_id_pk PRIMARY KEY (client_id) 112 | ); 113 | ``` 114 | 115 | > ### PostgreSQL 116 | > 117 | > We also have a PostgreSQL-specific DDL in `data/db_oauth2_postgresql.sql`. 118 | 119 | For security reasons, we encrypt the fields `client_secret` (table 120 | `oauth_clients`) and `password` (table `oauth_users`) using the 121 | [bcrypt](http://en.wikipedia.org/wiki/Bcrypt) algorithm (via the class 122 | [Zend\Crypt\Password\Bcrypt](http://framework.zend.com/manual/2.2/en/modules/zend.crypt.password.html#bcrypt)). 123 | 124 | In order to configure the zf-oauth2 module for database access, you need to copy 125 | the file `config/oauth2.local.php.dist` to `config/autoload/oauth2.local.php` in 126 | your ZF2 application, and edit it to provide your DB credentials (DNS, username, 127 | password). 128 | 129 | We also include a SQLite database in `data/dbtest.sqlite` that you can use in a 130 | test environment. In this database, you will find a test client account with 131 | the `client_id` "testclient" and the `client_secret` "testpass". If you want to 132 | use this database, you can configure your `config/autoload/oauth2.local.php` 133 | file as follow: 134 | 135 | ```php 136 | return array( 137 | 'zf-oauth2' => array( 138 | 'db' => array( 139 | 'dsn' => 'sqlite:/data/dbtest.sqlite', 140 | ), 141 | ), 142 | ); 143 | ``` 144 | 145 | ## Mongo Configuration 146 | 147 | The Mongo OAuth2 adapter wraps the bshaffer adapter by adding the same password encryption 148 | as the rest of apigility. The collections needed are the same as above in the PDO 149 | adapter. To create an OAuth2 client, insert a document like the following into the 150 | oauth_clients collection: 151 | 152 | ```javascript 153 | { 154 | "client_id": "testclient", 155 | "client_secret": "$2y$14$f3qml4G2hG6sxM26VMq.geDYbsS089IBtVJ7DlD05BoViS9PFykE2", 156 | "redirect_uri": "/oauth/receivecode", 157 | "grant_types": null 158 | } 159 | ``` 160 | 161 | ## User ID Provider 162 | 163 | When a user requests an authorization code they may provide their user_id as a request parameter to 164 | the `/oauth/authorize` route. This will store the `user_id` in the `access_token`, `refresh_token`, 165 | and `authorization_code` tables as the user goes throught the oauth2 process. 166 | 167 | A user may be authenticated through `Zend\Authentication\AuthenticationService` or another 168 | authentication means. When a user must provide authentication before they may access the 169 | `/oauth/authorize` route, the authenticated user ID should be used. This is done with the service 170 | manager alias `ZF\OAuth2\Provider\UserId`. 171 | 172 | The default User ID Provider uses the request query parameter `user_id` and is handled via the class 173 | `ZF\OAuth2\Provider\UserId\Request`. 174 | 175 | Provided with this repository is an alternative provider, 176 | `ZF\OAuth2\Provider\UserId\AuthorizationService`, which uses 177 | `Zend\Authentication\AuthenticationService` to fetch the identity. To change the User ID Provider 178 | to use this service, change the `ZF\OAuth2\Provider\UserId` service alias to point at it: 179 | 180 | ```php 181 | return array( 182 | 'service_manager' => 183 | 'aliases' => array( 184 | 'ZF\OAuth2\Provider\UserId' => 'ZF\OAuth2\Provider\UserId\AuthenticationService', 185 | ), 186 | ), 187 | ); 188 | ``` 189 | 190 | ## How to test OAuth2 191 | 192 | To test the OAuth2 module, you have to add a `client_id` and a `client_secret` 193 | into the oauth2 database. If you are using the SQLite test database, you don't 194 | need to add a `client_id`; just use the default "testclient"/"testpass" account. 195 | 196 | Because we encrypt the password using the `bcrypt` algorithm, you need to 197 | encrypt the password using the [Zend\Crypt\Password\Bcrypt](http://framework.zend.com/manual/2.2/en/modules/zend.crypt.password.html#bcrypt) 198 | class from Zend Framework 2. We provided a simple script in `/bin/bcrypt.php` to 199 | generate the hash value of a user's password. You can use this tool from the 200 | command line, with the following syntax: 201 | 202 | ```bash 203 | php bin/bcrypt.php testpass 204 | ``` 205 | 206 | where "testpass" is the user's password that you want to encrypt. The output of 207 | the previous command will be the hash value of the user's password, a string of 208 | 60 bytes like the following: 209 | 210 | ``` 211 | $2y$14$f3qml4G2hG6sxM26VMq.geDYbsS089IBtVJ7DlD05BoViS9PFykE2 212 | ``` 213 | 214 | After the generation of the hash value of the password (`client_secret`), you can 215 | add a new `client_id` in the database using the following SQL statement: 216 | 217 | ```sql 218 | INSERT INTO oauth_clients ( 219 | client_id, 220 | client_secret, 221 | redirect_uri) 222 | VALUES ( 223 | "testclient", 224 | "$2y$14$f3qml4G2hG6sxM26VMq.geDYbsS089IBtVJ7DlD05BoViS9PFykE2", 225 | "/oauth/receivecode" 226 | ); 227 | ``` 228 | 229 | To test the OAuth2 module, you can use an HTTP client like 230 | [HTTPie](https://github.com/jkbr/httpie) or [CURL](http://curl.haxx.se/). The 231 | examples below use HTTPie and the test account "testclient"/"testpass". 232 | 233 | ## REQUEST TOKEN (client\_credentials) 234 | 235 | You can request an OAuth2 token using the following HTTPie command: 236 | 237 | ```bash 238 | http --auth testclient:testpass -f POST http:///oauth grant_type=client_credentials 239 | ``` 240 | 241 | This POST requests a new token to the OAuth2 server using the *client_credentials* 242 | mode. This is typical in machine-to-machine interaction for application access. 243 | If everything works fine, you should receive a response like this: 244 | 245 | ```json 246 | { 247 | "access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35", 248 | "expires_in":3600, 249 | "token_type":"bearer", 250 | "scope":null 251 | } 252 | ``` 253 | 254 | *Security note:* because this POST uses basic HTTP authentication, the 255 | `client_secret` is exposed in plaintext in the HTTP request. To protect this 256 | call, a [TLS/SSL](http://en.wikipedia.org/wiki/Transport_Layer_Security) 257 | connection is required. 258 | 259 | 260 | ## AUTHORIZE (code) 261 | 262 | If you have to integrate an OAuth2 service with a web application, you need to 263 | use the Authorization Code grant type. This grant requires an approval step to 264 | authorize the web application. This step is implemented using a simple form that 265 | requests the user approve access to the resource (account). This module 266 | provides a simple form to authorize a specific client. This form can be accessed 267 | by a browser using the following URL: 268 | 269 | ```bash 270 | http:///oauth/authorize?response_type=code&client_id=testclient&redirect_uri=/oauth/receivecode&state=xyz 271 | ``` 272 | 273 | This page will render the form asking the user to authorize or deny the access 274 | for the client. If they authorize the access, the OAuth2 module will reply with 275 | an Authorization code. This code must be used to request an OAuth2 token; the 276 | following HTTPie command provides an example of how to do that: 277 | 278 | ```bash 279 | http --auth testclient:testpass -f POST http:///oauth grant_type=authorization_code&code=YOUR_CODE&redirect_uri=/oauth/receivecode 280 | ``` 281 | 282 | In client-side scenarios (i.e mobile) where you cannot store the Client 283 | Credentials in a secure way, you cannot use the previous workflow. In this case 284 | we can use an *implicit grant*. This is similar to the authorization code, but 285 | rather than an authorization code being returned from the authorization request, 286 | a *token* is returned. 287 | 288 | To enable the module to accept the implicit grant type, you need to change the 289 | configuration of `allow_implicit` to `true` in the 290 | `config/autoload/oauth2.local.php` file: 291 | 292 | 293 | ```php 294 | return array( 295 | 'zf-oauth2' => array( 296 | // ... 297 | 'allow_implicit' => true, 298 | // ... 299 | ), 300 | ); 301 | ``` 302 | 303 | To request a token from the client side, you need to request authorization via 304 | the OAuth2 server: 305 | 306 | ``` 307 | http:///oauth/authorize?response_type=token&client_id=testclient&redirect_uri=/oauth/receivecode&state=xyz 308 | ``` 309 | 310 | This request will render the authorization form as in the previous example. If 311 | you authorize the access, the request will be redirected to `/oauth/receivecode` 312 | (as provided in the `redirect_uri` parameter in the above example), with the 313 | `access_token` specified in the URI fragment, per the following sample: 314 | 315 | ``` 316 | /oauth/receivecode#access_token=559d8f9b6bedd8d94c8e8d708f87475f4838c514&expires_in=3600&token_type=Bearer&state=xyz 317 | ``` 318 | 319 | To get the `access_token`, you can parse the URI. We used the URI fragment to 320 | pass the `access_token` because in this way the token is not transmitted to the 321 | server; it will available only to the client. 322 | 323 | In JavaScript, you can easily parse the URI with this snippet of code: 324 | 325 | ```javascript 326 | // function to parse fragment parameters 327 | var parseQueryString = function( queryString ) { 328 | var params = {}, queries, temp, i, l; 329 | 330 | // Split into key/value pairs 331 | queries = queryString.split("&"); 332 | 333 | // Convert the array of strings into an object 334 | for ( i = 0, l = queries.length; i < l; i++ ) { 335 | temp = queries[i].split('='); 336 | params[temp[0]] = temp[1]; 337 | } 338 | return params; 339 | }; 340 | 341 | // get token params from URL fragment 342 | var tokenParams = parseQueryString(window.location.hash.substr(1)); 343 | ``` 344 | 345 | ## REVOKE (code) 346 | 347 | Starting with version 1.4.0, you can revoke access tokens. By default, revocation 348 | happens via a POST request to the path `/oauth/revoke`, which expects a payload 349 | with: 350 | 351 | - `token`, the OAuth2 access token to revoke. 352 | - `token_type_hint => 'access_token'`, indicating that an access token is being 353 | revoked. 354 | 355 | The payload may be delivered as `application/x-www-form-urlencoded` or as JSON. 356 | 357 | ## Access a test resource 358 | 359 | When you obtain a valid token, you can access a restricted API resource. The 360 | OAuth2 module is shipped with a test resource that is accessible with the URL 361 | `/oauth/resource`. This is a simple resource that returns JSON data. 362 | 363 | To access the test resource, you can use the following HTTPie command: 364 | 365 | ```bash 366 | http -f POST http:///oauth/resource access_token=000ab5afab4cbbbda803fb9e50e7943f5e766748 367 | # or 368 | http http://</oauth/resource "Authorization:Bearer 000ab5afab4cbbbda803fb9e50e7943f5e766748" 369 | ``` 370 | 371 | As you can see, the OAuth2 module supports the data either via POST, using the 372 | `access_token` value, or using the [Bearer](http://tools.ietf.org/html/rfc6750) 373 | authorization header. 374 | 375 | ## How to protect your API using OAuth2 376 | 377 | You can protect your API using the following code (for instance, at the top of a 378 | controller): 379 | 380 | ```php 381 | if (!$this->server->verifyResourceRequest(OAuth2Request::createFromGlobals())) { 382 | // Not authorized return 401 error 383 | $this->getResponse()->setStatusCode(401); 384 | return; 385 | } 386 | ``` 387 | 388 | where `$this->server` is an instance of `OAuth2\Server` (see the 389 | [AuthController.php](src/Controller/AuthController.php)). 390 | -------------------------------------------------------------------------------- /bin/bcrypt.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [cost] 31 | 32 | Arguments: 33 | The user's password 34 | [cost] The value of the cost parameter of bcrypt. 35 | (default is %d) 36 | 37 | EOH; 38 | $bcrypt = new Bcrypt(); 39 | 40 | if ($argc < 2) { 41 | printf($help, $bcrypt->getCost()); 42 | exit(1); 43 | } 44 | 45 | if (isset($argv[2])) { 46 | $bcrypt->setCost($argv[2]); 47 | } 48 | printf("%s\n", $bcrypt->create($argv[1])); 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfcampus/zf-oauth2", 3 | "description": "ZF module for implementing an OAuth2 server", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zendframework", 7 | "api", 8 | "oauth2", 9 | "framework", 10 | "zf" 11 | ], 12 | "support": { 13 | "issues": "https://github.com/zfcampus/zf-oauth2/issues", 14 | "source": "https://github.com/zfcampus/zf-oauth2", 15 | "rss": "https://github.com/zfcampus/zf-oauth2/releases.atom", 16 | "chat": "https://zendframework-slack.herokuapp.com", 17 | "forum": "https://discourse.zendframework.com/c/questions/apigility" 18 | }, 19 | "require": { 20 | "php": "^5.6 || ^7.0", 21 | "bshaffer/oauth2-server-php": "^1.10", 22 | "zendframework/zend-crypt": "^3.3", 23 | "zendframework/zend-http": "^2.5.4", 24 | "zendframework/zend-mvc": "^2.7.15 || ^3.0.2", 25 | "zendframework/zend-servicemanager": "^2.7.6 || ^3.1", 26 | "zfcampus/zf-api-problem": "^1.2.1", 27 | "zfcampus/zf-content-negotiation": "^1.2.1" 28 | }, 29 | "require-dev": { 30 | "mockery/mockery": "^1.0", 31 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 32 | "zendframework/zend-authentication": "^2.5.3", 33 | "zendframework/zend-coding-standard": "~1.0.0", 34 | "zendframework/zend-db": "^2.8.1", 35 | "zendframework/zend-i18n": "^2.7.3", 36 | "zendframework/zend-log": "^2.9", 37 | "zendframework/zend-modulemanager": "^2.7.2", 38 | "zendframework/zend-serializer": "^2.8", 39 | "zendframework/zend-test": "^2.6.1 || ^3.0.1" 40 | }, 41 | "suggest": { 42 | "alcaeus/mongo-php-adapter": "^1.0.5, if you are using ext/mongodb and wish to use the MongoAdapter for OAuth2 credential storage." 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "ZF\\OAuth2\\": "src/" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "ZFTest\\OAuth2\\": "test/" 52 | } 53 | }, 54 | "config": { 55 | "sort-packages": true 56 | }, 57 | "extra": { 58 | "branch-alias": { 59 | "dev-master": "1.5.x-dev", 60 | "dev-develop": "1.6.x-dev" 61 | } 62 | }, 63 | "bin": [ 64 | "bin/bcrypt.php" 65 | ], 66 | "scripts": { 67 | "check": [ 68 | "@cs-check", 69 | "@test" 70 | ], 71 | "cs-check": "phpcs", 72 | "cs-fix": "phpcbf", 73 | "test": "phpunit --colors=always", 74 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'factories' => [ 12 | 'ZF\OAuth2\Controller\Auth' => Factory\AuthControllerFactory::class, 13 | ], 14 | ], 15 | 'router' => [ 16 | 'routes' => [ 17 | 'oauth' => [ 18 | 'type' => 'literal', 19 | 'options' => [ 20 | 'route' => '/oauth', 21 | 'defaults' => [ 22 | 'controller' => 'ZF\OAuth2\Controller\Auth', 23 | 'action' => 'token', 24 | ], 25 | ], 26 | 'may_terminate' => true, 27 | 'child_routes' => [ 28 | 'revoke' => [ 29 | 'type' => 'literal', 30 | 'options' => [ 31 | 'route' => '/revoke', 32 | 'defaults' => [ 33 | 'action' => 'revoke', 34 | ], 35 | ], 36 | ], 37 | 'authorize' => [ 38 | 'type' => 'literal', 39 | 'options' => [ 40 | 'route' => '/authorize', 41 | 'defaults' => [ 42 | 'action' => 'authorize', 43 | ], 44 | ], 45 | ], 46 | 'resource' => [ 47 | 'type' => 'literal', 48 | 'options' => [ 49 | 'route' => '/resource', 50 | 'defaults' => [ 51 | 'action' => 'resource', 52 | ], 53 | ], 54 | ], 55 | 'code' => [ 56 | 'type' => 'literal', 57 | 'options' => [ 58 | 'route' => '/receivecode', 59 | 'defaults' => [ 60 | 'action' => 'receiveCode', 61 | ], 62 | ], 63 | ], 64 | ], 65 | ], 66 | ], 67 | ], 68 | 'service_manager' => [ 69 | 'aliases' => [ 70 | 'ZF\OAuth2\Provider\UserId' => Provider\UserId\AuthenticationService::class, 71 | ], 72 | 'factories' => [ 73 | Adapter\PdoAdapter::class => Factory\PdoAdapterFactory::class, 74 | Adapter\IbmDb2Adapter::class => Factory\IbmDb2AdapterFactory::class, 75 | Adapter\MongoAdapter::class => Factory\MongoAdapterFactory::class, 76 | Provider\UserId\AuthenticationService::class => Provider\UserId\AuthenticationServiceFactory::class, 77 | 'ZF\OAuth2\Service\OAuth2Server' => Factory\OAuth2ServerFactory::class 78 | ] 79 | ], 80 | 'view_manager' => [ 81 | 'template_map' => [ 82 | 'oauth/authorize' => __DIR__ . '/../view/zf/auth/authorize.phtml', 83 | 'oauth/receive-code' => __DIR__ . '/../view/zf/auth/receive-code.phtml', 84 | ], 85 | 'template_path_stack' => [ 86 | __DIR__ . '/../view', 87 | ], 88 | ], 89 | 'zf-oauth2' => [ 90 | /* 91 | * Config can include: 92 | * - 'storage' => 'name of storage service' - typically ZF\OAuth2\Adapter\PdoAdapter 93 | * - 'db' => [ // database configuration for the above PdoAdapter 94 | * 'dsn' => 'PDO DSN', 95 | * 'username' => 'username', 96 | * 'password' => 'password' 97 | * ] 98 | * - 'storage_settings' => [ // configuration to pass to the storage adapter 99 | * // see https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Pdo.php#L57-L66 100 | * ] 101 | */ 102 | 'grant_types' => [ 103 | 'client_credentials' => true, 104 | 'authorization_code' => true, 105 | 'password' => true, 106 | 'refresh_token' => true, 107 | 'jwt' => true, 108 | ], 109 | /* 110 | * Error reporting style 111 | * 112 | * If true, client errors are returned using the 113 | * application/problem+json content type, 114 | * otherwise in the format described in the oauth2 specification 115 | * (default: true) 116 | */ 117 | 'api_problem_error_response' => true, 118 | ], 119 | 'zf-content-negotiation' => [ 120 | 'controllers' => [ 121 | 'ZF\OAuth2\Controller\Auth' => [ 122 | 'ZF\ContentNegotiation\JsonModel' => [ 123 | 'application/json', 124 | 'application/*+json', 125 | ], 126 | 'Zend\View\Model\ViewModel' => [ 127 | 'text/html', 128 | 'application/xhtml+xml', 129 | ], 130 | ], 131 | ], 132 | ], 133 | ]; 134 | -------------------------------------------------------------------------------- /config/oauth2.local.php.dist: -------------------------------------------------------------------------------- 1 | [ 4 | 'db' => [ 5 | 'dsn' => 'insert here the DSN for DB connection', // for example "mysql:dbname=oauth2_db;host=localhost" 6 | 'username' => 'insert here the DB username', 7 | 'password' => 'insert here the DB password', 8 | ], 9 | 'storage' => 'ZF\OAuth2\Adapter\PdoAdapter', // service name for the OAuth2 storage adapter 10 | 11 | /** 12 | * These special OAuth2Server options are parsed outside the options array 13 | */ 14 | 'allow_implicit' => false, // default (set to true when you need to support browser-based or mobile apps) 15 | 'access_lifetime' => 3600, // default (set a value in seconds for access tokens lifetime) 16 | 'enforce_state' => true, // default 17 | 18 | /** 19 | * These are all OAuth2Server options with their default values 20 | */ 21 | 'options' => [ 22 | 'use_jwt_access_tokens' => false, 23 | 'store_encrypted_token_string' => true, 24 | 'use_openid_connect' => false, 25 | 'id_lifetime' => 3600, 26 | 'www_realm' => 'Service', 27 | 'token_param_name' => 'access_token', 28 | 'token_bearer_header_name' => 'Bearer', 29 | 'require_exact_redirect_uri' => true, 30 | 'allow_credentials_in_request_body' => true, 31 | 'allow_public_clients' => true, 32 | 'always_issue_new_refresh_token' => false, 33 | 'unset_refresh_token_after_use' => true, 34 | ], 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /data/db_oauth2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE oauth_clients ( 2 | client_id VARCHAR(80) NOT NULL, 3 | client_secret VARCHAR(80) NOT NULL, 4 | redirect_uri VARCHAR(2000) NOT NULL, 5 | grant_types VARCHAR(80), 6 | scope VARCHAR(2000), 7 | user_id VARCHAR(255), 8 | CONSTRAINT clients_client_id_pk PRIMARY KEY (client_id) 9 | ); 10 | CREATE TABLE oauth_access_tokens ( 11 | access_token VARCHAR(40) NOT NULL, 12 | client_id VARCHAR(80) NOT NULL, 13 | user_id VARCHAR(255), 14 | expires TIMESTAMP NOT NULL, 15 | scope VARCHAR(2000), 16 | CONSTRAINT access_token_pk PRIMARY KEY (access_token) 17 | ); 18 | CREATE TABLE oauth_authorization_codes ( 19 | authorization_code VARCHAR(40) NOT NULL, 20 | client_id VARCHAR(80) NOT NULL, 21 | user_id VARCHAR(255), 22 | redirect_uri VARCHAR(2000), 23 | expires TIMESTAMP NOT NULL, 24 | scope VARCHAR(2000), 25 | id_token VARCHAR(2000), 26 | CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code) 27 | ); 28 | CREATE TABLE oauth_refresh_tokens ( 29 | refresh_token VARCHAR(40) NOT NULL, 30 | client_id VARCHAR(80) NOT NULL, 31 | user_id VARCHAR(255), 32 | expires TIMESTAMP NOT NULL, 33 | scope VARCHAR(2000), 34 | CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token) 35 | ); 36 | CREATE TABLE oauth_users ( 37 | username VARCHAR(255) NOT NULL, 38 | password VARCHAR(2000), 39 | first_name VARCHAR(255), 40 | last_name VARCHAR(255), 41 | CONSTRAINT username_pk PRIMARY KEY (username) 42 | ); 43 | CREATE TABLE oauth_scopes ( 44 | type VARCHAR(255) NOT NULL DEFAULT "supported", 45 | scope VARCHAR(2000), 46 | client_id VARCHAR (80), 47 | is_default SMALLINT DEFAULT NULL 48 | ); 49 | CREATE TABLE oauth_jwt ( 50 | client_id VARCHAR(80) NOT NULL, 51 | subject VARCHAR(80), 52 | public_key VARCHAR(2000), 53 | CONSTRAINT jwt_client_id_pk PRIMARY KEY (client_id) 54 | ); 55 | -------------------------------------------------------------------------------- /data/db_oauth2_postgresql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE oauth_access_tokens 2 | ( 3 | access_token character varying(40) NOT NULL, 4 | client_id character varying(80) NOT NULL, 5 | user_id character varying(255), 6 | expires timestamp(0) without time zone NOT NULL, 7 | scope character varying(2000), 8 | CONSTRAINT access_token_pk PRIMARY KEY (access_token) 9 | ); 10 | 11 | CREATE TABLE oauth_authorization_codes 12 | ( 13 | authorization_code character varying(40) NOT NULL, 14 | client_id character varying(80) NOT NULL, 15 | user_id character varying(255), 16 | redirect_uri character varying(2000), 17 | expires timestamp(0) without time zone NOT NULL, 18 | scope character varying(2000), 19 | id_token character varying(2000), 20 | CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code) 21 | ); 22 | 23 | CREATE TABLE oauth_clients 24 | ( 25 | client_id character varying(80) NOT NULL, 26 | client_secret character varying(80) NOT NULL, 27 | redirect_uri character varying(2000) NOT NULL, 28 | grant_types character varying(80), 29 | scope character varying(2000), 30 | user_id character varying(255), 31 | CONSTRAINT clients_client_id_pk PRIMARY KEY (client_id) 32 | ); 33 | 34 | CREATE TABLE oauth_jwt 35 | ( 36 | client_id character varying(80) NOT NULL, 37 | subject character varying(80), 38 | public_key character varying(2000), 39 | CONSTRAINT jwt_client_id_pk PRIMARY KEY (client_id) 40 | ); 41 | 42 | 43 | CREATE TABLE oauth_refresh_tokens 44 | ( 45 | refresh_token character varying(40) NOT NULL, 46 | client_id character varying(80) NOT NULL, 47 | user_id character varying(255), 48 | expires timestamp(0) without time zone NOT NULL, 49 | scope character varying(2000), 50 | CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token) 51 | ); 52 | 53 | CREATE TABLE oauth_scopes 54 | ( 55 | type character varying(255) NOT NULL DEFAULT 'supported'::character varying, 56 | scope character varying(2000), 57 | client_id character varying(80), 58 | is_default smallint 59 | ); 60 | 61 | CREATE TABLE oauth_users 62 | ( 63 | username character varying(255) NOT NULL, 64 | password character varying(2000), 65 | first_name character varying(255), 66 | last_name character varying(255), 67 | CONSTRAINT username_pk PRIMARY KEY (username) 68 | ); 69 | 70 | -------------------------------------------------------------------------------- /data/dbtest.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfcampus/zf-oauth2/89bc7ce0783c61a45f6b2cca5b8c3583a3614b42/data/dbtest.sqlite -------------------------------------------------------------------------------- /src/Adapter/BcryptTrait.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | trait BcryptTrait 18 | { 19 | /** 20 | * @var int 21 | */ 22 | protected $bcryptCost = 10; 23 | 24 | /** 25 | * @var Bcrypt 26 | */ 27 | protected $bcrypt; 28 | 29 | /** 30 | * @return Bcrypt 31 | */ 32 | public function getBcrypt() 33 | { 34 | if (null === $this->bcrypt) { 35 | $this->bcrypt = new Bcrypt(); 36 | $this->bcrypt->setCost($this->bcryptCost); 37 | } 38 | 39 | return $this->bcrypt; 40 | } 41 | 42 | /** 43 | * @param $value 44 | * @return $this 45 | */ 46 | public function setBcryptCost($value) 47 | { 48 | $this->bcryptCost = (int) $value; 49 | return $this; 50 | } 51 | 52 | /** 53 | * Check password using bcrypt 54 | * 55 | * @param string $user 56 | * @param string $password 57 | * @return bool 58 | */ 59 | protected function checkPassword($user, $password) 60 | { 61 | return $this->verifyHash($password, $user['password']); 62 | } 63 | 64 | /** 65 | * @param $string 66 | */ 67 | protected function createBcryptHash(&$string) 68 | { 69 | $string = $this->getBcrypt()->create($string); 70 | } 71 | 72 | /** 73 | * Check hash using bcrypt 74 | * 75 | * @param $hash 76 | * @param $check 77 | * @return bool 78 | */ 79 | protected function verifyHash($check, $hash) 80 | { 81 | return $this->getBcrypt()->verify($check, $hash); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Adapter/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | (of IbmDb2 changes) 18 | */ 19 | class IbmDb2Adapter extends OAuth2Db2 20 | { 21 | /** 22 | * @var int 23 | */ 24 | protected $bcryptCost = 10; 25 | 26 | /** 27 | * @var Bcrypt 28 | */ 29 | protected $bcrypt; 30 | 31 | /** 32 | * @param string $connection 33 | * @param array $config 34 | */ 35 | public function __construct($connection, $config = []) 36 | { 37 | parent::__construct($connection, $config); 38 | 39 | if (isset($config['bcrypt_cost'])) { 40 | $this->setBcryptCost($config['bcrypt_cost']); 41 | } 42 | } 43 | 44 | /** 45 | * @return Bcrypt 46 | */ 47 | public function getBcrypt() 48 | { 49 | if (null === $this->bcrypt) { 50 | $this->bcrypt = new Bcrypt(); 51 | $this->bcrypt->setCost($this->bcryptCost); 52 | } 53 | 54 | return $this->bcrypt; 55 | } 56 | 57 | /** 58 | * @param $value 59 | * @return $this 60 | */ 61 | public function setBcryptCost($value) 62 | { 63 | $this->bcryptCost = (int) $value; 64 | return $this; 65 | } 66 | 67 | /** 68 | * Check password using bcrypt 69 | * 70 | * @param string $user 71 | * @param string $password 72 | * @return bool 73 | */ 74 | protected function checkPassword($user, $password) 75 | { 76 | return $this->verifyHash($password, $user['password']); 77 | } 78 | 79 | /** 80 | * @param string $string 81 | * @return string 82 | */ 83 | protected function createBcryptHash($string) 84 | { 85 | return $this->getBcrypt()->create($string); 86 | } 87 | 88 | /** 89 | * Check hash using bcrypt 90 | * 91 | * @param string $hash 92 | * @param string $check 93 | * @return bool 94 | */ 95 | protected function verifyHash($check, $hash) 96 | { 97 | return $this->getBcrypt()->verify($check, $hash); 98 | } 99 | 100 | /** 101 | * Check client credentials 102 | * 103 | * @param string $clientId 104 | * @param null|string $clientSecret 105 | * @return bool 106 | */ 107 | public function checkClientCredentials($clientId, $clientSecret = null) 108 | { 109 | $stmt = db2_prepare($this->db, sprintf( 110 | 'SELECT * from %s where client_id = ?', 111 | $this->config['client_table'] 112 | )); 113 | if (false == $stmt) { 114 | throw new \Exception(db2_stmt_errormsg()); 115 | } 116 | 117 | $successfulExecute = db2_execute($stmt, compact('clientId')); 118 | $result = db2_fetch_assoc($stmt); 119 | 120 | // bcrypt verify 121 | return $this->verifyHash($clientSecret, $result['client_secret']); 122 | } 123 | 124 | /** 125 | * Set client details 126 | * 127 | * @param string $clientId 128 | * @param null|string $clientSecret 129 | * @param null|string $redirectUri 130 | * @param null|string $grantTypes 131 | * @param null|string $scopeOrUserId If 5 arguments, userId; if 6, scope. 132 | * @param null|string $userId 133 | * @return bool 134 | */ 135 | public function setClientDetails( 136 | $clientId, 137 | $clientSecret = null, 138 | $redirectUri = null, 139 | $grantTypes = null, 140 | $scopeOrUserId = null, 141 | $userId = null 142 | ) { 143 | if (func_num_args() > 5) { 144 | $scope = $scopeOrUserId; 145 | } else { 146 | $userId = $scopeOrUserId; 147 | $scope = null; 148 | } 149 | 150 | if (! empty($clientSecret)) { 151 | $clientSecret = $this->createBcryptHash($clientSecret); 152 | } 153 | // if it exists, update it. 154 | if ($this->getClientDetails($clientId)) { 155 | $stmt = db2_prepare($this->db, sprintf( 156 | 'UPDATE %s ' 157 | . 'SET ' 158 | . 'client_secret=?, ' 159 | . 'redirect_uri=?, ' 160 | . 'grant_types=?, ' 161 | . 'scope=?, ' 162 | . 'user_id=? ' 163 | . 'WHERE client_id=?', 164 | $this->config['client_table'] 165 | )); 166 | $params = compact('clientSecret', 'redirectUri', 'grantTypes', 'scope', 'userId', 'clientId'); 167 | } else { 168 | $stmt = db2_prepare($this->db, sprintf( 169 | 'INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) ' 170 | . 'VALUES (?, ?, ?, ?, ?, ?)', 171 | $this->config['client_table'] 172 | )); 173 | $params = compact('clientId', 'clientSecret', 'redirectUri', 'grantTypes', 'scope', 'userId'); 174 | } 175 | if (false === $stmt) { 176 | throw new RuntimeException(db2_stmt_errormsg()); 177 | } 178 | return db2_execute($stmt, $params); 179 | } 180 | 181 | /** 182 | * Set the user 183 | * 184 | * @param string $username 185 | * @param string $password 186 | * @param string $firstName 187 | * @param string $lastName 188 | * @return bool 189 | */ 190 | public function setUser($username, $password, $firstName = null, $lastName = null) 191 | { 192 | // do not store in plaintext, use bcrypt 193 | $password = $this->createBcryptHash($password); 194 | 195 | // if it exists, update it. 196 | if ($this->getUser($username)) { 197 | $stmt = db2_prepare($this->db, sprintf( 198 | 'UPDATE %s SET password=?, first_name=?, last_name=? where username=?', 199 | $this->config['user_table'] 200 | )); 201 | $params = compact('password', 'firstName', 'lastName', 'username'); 202 | } else { 203 | $stmt = db2_prepare($this->db, sprintf( 204 | 'INSERT INTO %s (username, password, first_name, last_name) ' 205 | . 'VALUES (?, ?, ?, ?)', 206 | $this->config['user_table'] 207 | )); 208 | $params = compact('username', 'password', 'firstName', 'lastName'); 209 | } 210 | if (false === $stmt) { 211 | throw new RuntimeException(db2_stmt_errormsg()); 212 | } 213 | 214 | return db2_execute($stmt, $params); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Adapter/MongoAdapter.php: -------------------------------------------------------------------------------- 1 | bcrypt) { 35 | $this->bcrypt = new Bcrypt(); 36 | $this->bcrypt->setCost($this->bcryptCost); 37 | } 38 | 39 | return $this->bcrypt; 40 | } 41 | 42 | /** 43 | * @param $value 44 | * @return $this 45 | */ 46 | public function setBcryptCost($value) 47 | { 48 | $this->bcryptCost = (int) $value; 49 | return $this; 50 | } 51 | 52 | /** 53 | * Check password using bcrypt 54 | * 55 | * @param string $user 56 | * @param string $password 57 | * @return bool 58 | */ 59 | protected function checkPassword($user, $password) 60 | { 61 | return $this->verifyHash($password, $user['password']); 62 | } 63 | 64 | /** 65 | * @param $string 66 | */ 67 | protected function createBcryptHash(&$string) 68 | { 69 | $string = $this->getBcrypt()->create($string); 70 | } 71 | 72 | /** 73 | * Check hash using bcrypt 74 | * 75 | * @param $hash 76 | * @param $check 77 | * @return bool 78 | */ 79 | protected function verifyHash($check, $hash) 80 | { 81 | return $this->getBcrypt()->verify($check, $hash); 82 | } 83 | 84 | /** 85 | * @param $connection 86 | * @param array $config 87 | * @throws Exception\RuntimeException 88 | */ 89 | public function __construct($connection, $config = []) 90 | { 91 | // @codeCoverageIgnoreStart 92 | if (! (extension_loaded('mongodb') || extension_loaded('mongo')) 93 | || ! class_exists(MongoClient::class) 94 | || version_compare(MongoClient::VERSION, '1.4.1', '<') 95 | ) { 96 | throw new Exception\RuntimeException( 97 | 'The MongoAdapter requires either the Mongo Driver v1.4.1 or ' 98 | . 'ext/mongodb + the alcaeus/mongo-php-adapter package (which provides ' 99 | . 'backwards compatibility for ext/mongo classes)' 100 | ); 101 | } 102 | // @codeCoverageIgnoreEnd 103 | 104 | parent::__construct($connection, $config); 105 | } 106 | 107 | /** 108 | * Check client credentials 109 | * 110 | * @param string $client_id 111 | * @param string $client_secret 112 | * @return bool 113 | */ 114 | public function checkClientCredentials($client_id, $client_secret = null) 115 | { 116 | if ($result = $this->collection('client_table')->findOne(['client_id' => $client_id])) { 117 | return $this->verifyHash($client_secret, $result['client_secret']); 118 | } 119 | 120 | return false; 121 | } 122 | 123 | /** 124 | * Set client details 125 | * 126 | * @param string $client_id 127 | * @param string $client_secret 128 | * @param string $redirect_uri 129 | * @param string $grant_types 130 | * @param string $scope_or_user_id If 5 arguments, user_id; if 6, scope. 131 | * @param string $user_id 132 | * @return bool 133 | */ 134 | public function setClientDetails( 135 | $client_id, 136 | $client_secret = null, 137 | $redirect_uri = null, 138 | $grant_types = null, 139 | $scope_or_user_id = null, 140 | $user_id = null 141 | ) { 142 | if (func_num_args() > 5) { 143 | $scope = $scope_or_user_id; 144 | } else { 145 | $user_id = $scope_or_user_id; 146 | $scope = null; 147 | } 148 | 149 | if (! empty($client_secret)) { 150 | $this->createBcryptHash($client_secret); 151 | } 152 | 153 | if ($this->getClientDetails($client_id)) { 154 | $this->collection('client_table')->update( 155 | ['client_id' => $client_id], 156 | ['$set' => [ 157 | 'client_secret' => $client_secret, 158 | 'redirect_uri' => $redirect_uri, 159 | 'grant_types' => $grant_types, 160 | 'scope' => $scope, 161 | 'user_id' => $user_id, 162 | ]] 163 | ); 164 | } else { 165 | $this->collection('client_table')->insert( 166 | [ 167 | 'client_id' => $client_id, 168 | 'client_secret' => $client_secret, 169 | 'redirect_uri' => $redirect_uri, 170 | 'grant_types' => $grant_types, 171 | 'scope' => $scope, 172 | 'user_id' => $user_id, 173 | ] 174 | ); 175 | } 176 | 177 | return true; 178 | } 179 | 180 | /** 181 | * Set the user 182 | * 183 | * @param string $username 184 | * @param string $password 185 | * @param string $firstName 186 | * @param string $lastName 187 | * @return bool 188 | */ 189 | public function setUser($username, $password, $firstName = null, $lastName = null) 190 | { 191 | $this->createBcryptHash($password); 192 | 193 | if ($this->getUser($username)) { 194 | $this->collection('user_table')->update( 195 | ['username' => $username], 196 | ['$set' => [ 197 | 'password' => $password, 198 | 'first_name' => $firstName, 199 | 'last_name' => $lastName 200 | ]] 201 | ); 202 | } else { 203 | $this->collection('user_table')->insert([ 204 | 'username' => $username, 205 | 'password' => $password, 206 | 'first_name' => $firstName, 207 | 'last_name' => $lastName 208 | ]); 209 | } 210 | 211 | return true; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Adapter/PdoAdapter.php: -------------------------------------------------------------------------------- 1 | bcrypt) { 34 | $this->bcrypt = new Bcrypt(); 35 | $this->bcrypt->setCost($this->bcryptCost); 36 | } 37 | 38 | return $this->bcrypt; 39 | } 40 | 41 | /** 42 | * @param $value 43 | * @return $this 44 | */ 45 | public function setBcryptCost($value) 46 | { 47 | $this->bcryptCost = (int) $value; 48 | return $this; 49 | } 50 | 51 | /** 52 | * Check password using bcrypt 53 | * 54 | * @param string $user 55 | * @param string $password 56 | * @return bool 57 | */ 58 | protected function checkPassword($user, $password) 59 | { 60 | return $this->verifyHash($password, $user['password']); 61 | } 62 | 63 | /** 64 | * @param $string 65 | */ 66 | protected function createBcryptHash(&$string) 67 | { 68 | $string = $this->getBcrypt()->create($string); 69 | } 70 | 71 | /** 72 | * Check hash using bcrypt 73 | * 74 | * @param $hash 75 | * @param $check 76 | * @return bool 77 | */ 78 | protected function verifyHash($check, $hash) 79 | { 80 | return $this->getBcrypt()->verify($check, $hash); 81 | } 82 | 83 | /** 84 | * @param string $connection 85 | * @param array $config 86 | */ 87 | public function __construct($connection, $config = []) 88 | { 89 | parent::__construct($connection, $config); 90 | if (isset($config['bcrypt_cost'])) { 91 | $this->setBcryptCost($config['bcrypt_cost']); 92 | } 93 | } 94 | 95 | /** 96 | * Check client credentials 97 | * 98 | * @param string $client_id 99 | * @param string $client_secret 100 | * @return bool 101 | */ 102 | public function checkClientCredentials($client_id, $client_secret = null) 103 | { 104 | $stmt = $this->db->prepare(sprintf( 105 | 'SELECT * from %s where client_id = :client_id', 106 | $this->config['client_table'] 107 | )); 108 | $stmt->execute(compact('client_id')); 109 | $result = $stmt->fetch(); 110 | 111 | // Do not bother verifying if the secret is missing or empty. 112 | if (! isset($result['client_secret']) || empty($result['client_secret'])) { 113 | return false; 114 | } 115 | 116 | // bcrypt verify 117 | return $this->verifyHash($client_secret, $result['client_secret']); 118 | } 119 | 120 | /** 121 | * Set client details 122 | * 123 | * @param string $client_id 124 | * @param string $client_secret 125 | * @param string $redirect_uri 126 | * @param string $grant_types 127 | * @param string $scope_or_user_id If 5 arguments, user_id; if 6, scope. 128 | * @param string $user_id 129 | * @return bool 130 | */ 131 | public function setClientDetails( 132 | $client_id, 133 | $client_secret = null, 134 | $redirect_uri = null, 135 | $grant_types = null, 136 | $scope_or_user_id = null, 137 | $user_id = null 138 | ) { 139 | if (func_num_args() > 5) { 140 | $scope = $scope_or_user_id; 141 | } else { 142 | $user_id = $scope_or_user_id; 143 | $scope = null; 144 | } 145 | 146 | if (! empty($client_secret)) { 147 | $this->createBcryptHash($client_secret); 148 | } 149 | // if it exists, update it. 150 | if ($this->getClientDetails($client_id)) { 151 | $stmt = $this->db->prepare(sprintf( 152 | 'UPDATE %s ' 153 | . 'SET ' 154 | . 'client_secret=:client_secret, ' 155 | . 'redirect_uri=:redirect_uri, ' 156 | . 'grant_types=:grant_types, ' 157 | . 'scope=:scope, ' 158 | . 'user_id=:user_id ' 159 | . 'WHERE client_id=:client_id', 160 | $this->config['client_table'] 161 | )); 162 | } else { 163 | $stmt = $this->db->prepare(sprintf( 164 | 'INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) ' 165 | . 'VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :scope, :user_id)', 166 | $this->config['client_table'] 167 | )); 168 | } 169 | return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id')); 170 | } 171 | 172 | /** 173 | * Set the user 174 | * 175 | * @param string $username 176 | * @param string $password 177 | * @param string $firstName 178 | * @param string $lastName 179 | * @return bool 180 | */ 181 | public function setUser($username, $password, $firstName = null, $lastName = null) 182 | { 183 | // do not store in plaintext, use bcrypt 184 | $this->createBcryptHash($password); 185 | 186 | // if it exists, update it. 187 | if ($this->getUser($username)) { 188 | $stmt = $this->db->prepare(sprintf( 189 | 'UPDATE %s SET password=:password, first_name=:firstName, last_name=:lastName where username=:username', 190 | $this->config['user_table'] 191 | )); 192 | } else { 193 | $stmt = $this->db->prepare(sprintf( 194 | 'INSERT INTO %s (username, password, first_name, last_name) ' 195 | . 'VALUES (:username, :password, :firstName, :lastName)', 196 | $this->config['user_table'] 197 | )); 198 | } 199 | 200 | return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Controller/AuthController.php: -------------------------------------------------------------------------------- 1 | serverFactory = $serverFactory; 60 | $this->userIdProvider = $userIdProvider; 61 | } 62 | 63 | /** 64 | * Should the controller return ApiProblemResponse? 65 | * 66 | * @return bool 67 | */ 68 | public function isApiProblemErrorResponse() 69 | { 70 | return $this->apiProblemErrorResponse; 71 | } 72 | 73 | /** 74 | * Indicate whether ApiProblemResponse or oauth2 errors should be returned. 75 | * 76 | * Boolean true indicates ApiProblemResponse should be returned (the 77 | * default), while false indicates oauth2 errors (per the oauth2 spec) 78 | * should be returned. 79 | * 80 | * @param bool $apiProblemErrorResponse 81 | */ 82 | public function setApiProblemErrorResponse($apiProblemErrorResponse) 83 | { 84 | $this->apiProblemErrorResponse = (bool) $apiProblemErrorResponse; 85 | } 86 | 87 | /** 88 | * Token Action (/oauth) 89 | */ 90 | public function tokenAction() 91 | { 92 | $request = $this->getRequest(); 93 | if (! $request instanceof HttpRequest) { 94 | // not an HTTP request; nothing left to do 95 | return; 96 | } 97 | 98 | if ($request->isOptions()) { 99 | // OPTIONS request. 100 | // This is most likely a CORS attempt; as such, pass the response on. 101 | return $this->getResponse(); 102 | } 103 | 104 | $oauth2request = $this->getOAuth2Request(); 105 | $oauth2server = $this->getOAuth2Server($this->params('oauth')); 106 | try { 107 | $response = $oauth2server->handleTokenRequest($oauth2request); 108 | } catch (ProblemExceptionInterface $ex) { 109 | $status = $ex->getCode() ?: 401; 110 | $status = $status >= 400 && $status < 600 ? $status : 401; 111 | 112 | return new ApiProblemResponse( 113 | new ApiProblem($status, $ex) 114 | ); 115 | } 116 | 117 | if ($response->isClientError()) { 118 | return $this->getErrorResponse($response); 119 | } 120 | 121 | return $this->setHttpResponse($response); 122 | } 123 | 124 | /** 125 | * Token Revoke (/oauth/revoke) 126 | */ 127 | public function revokeAction() 128 | { 129 | $request = $this->getRequest(); 130 | if (! $request instanceof HttpRequest) { 131 | // not an HTTP request; nothing left to do 132 | return; 133 | } 134 | 135 | if ($request->isOptions()) { 136 | // OPTIONS request. 137 | // This is most likely a CORS attempt; as such, pass the response on. 138 | return $this->getResponse(); 139 | } 140 | 141 | $oauth2request = $this->getOAuth2Request(); 142 | $response = $this->getOAuth2Server($this->params('oauth'))->handleRevokeRequest($oauth2request); 143 | 144 | if ($response->isClientError()) { 145 | return $this->getErrorResponse($response); 146 | } 147 | 148 | return $this->setHttpResponse($response); 149 | } 150 | 151 | /** 152 | * Test resource (/oauth/resource) 153 | */ 154 | public function resourceAction() 155 | { 156 | $server = $this->getOAuth2Server($this->params('oauth')); 157 | 158 | // Handle a request for an OAuth2.0 Access Token and send the response to the client 159 | if (! $server->verifyResourceRequest($this->getOAuth2Request())) { 160 | $response = $server->getResponse(); 161 | return $this->getApiProblemResponse($response); 162 | } 163 | 164 | $httpResponse = $this->getResponse(); 165 | $httpResponse->setStatusCode(200); 166 | $httpResponse->getHeaders()->addHeaders(['Content-type' => 'application/json']); 167 | $httpResponse->setContent( 168 | json_encode(['success' => true, 'message' => 'You accessed my APIs!']) 169 | ); 170 | return $httpResponse; 171 | } 172 | 173 | /** 174 | * Authorize action (/oauth/authorize) 175 | */ 176 | public function authorizeAction() 177 | { 178 | $server = $this->getOAuth2Server($this->params('oauth')); 179 | $request = $this->getOAuth2Request(); 180 | $response = new OAuth2Response(); 181 | 182 | // validate the authorize request 183 | $isValid = $this->server->validateAuthorizeRequest($request, $response); 184 | 185 | if (! $isValid) { 186 | return $this->getErrorResponse($response); 187 | } 188 | 189 | $authorized = $request->request('authorized', false); 190 | if (empty($authorized)) { 191 | $clientId = $request->query('client_id', false); 192 | $view = new ViewModel(['clientId' => $clientId]); 193 | $view->setTemplate('oauth/authorize'); 194 | return $view; 195 | } 196 | 197 | $isAuthorized = ($authorized === 'yes'); 198 | $userIdProvider = $this->userIdProvider; 199 | 200 | $this->server->handleAuthorizeRequest( 201 | $request, 202 | $response, 203 | $isAuthorized, 204 | $userIdProvider($this->getRequest()) 205 | ); 206 | 207 | $redirect = $response->getHttpHeader('Location'); 208 | if (! empty($redirect)) { 209 | return $this->redirect()->toUrl($redirect); 210 | } 211 | 212 | return $this->getErrorResponse($response); 213 | } 214 | 215 | /** 216 | * Receive code action prints the code/token access 217 | */ 218 | public function receiveCodeAction() 219 | { 220 | $code = $this->params()->fromQuery('code', false); 221 | $view = new ViewModel([ 222 | 'code' => $code 223 | ]); 224 | $view->setTemplate('oauth/receive-code'); 225 | return $view; 226 | } 227 | 228 | /** 229 | * @param OAuth2Response $response 230 | * @return ApiProblemResponse|\Zend\Stdlib\ResponseInterface 231 | */ 232 | protected function getErrorResponse(OAuth2Response $response) 233 | { 234 | if ($this->isApiProblemErrorResponse()) { 235 | return $this->getApiProblemResponse($response); 236 | } 237 | 238 | return $this->setHttpResponse($response); 239 | } 240 | 241 | /** 242 | * Map OAuth2Response to ApiProblemResponse 243 | * 244 | * @param OAuth2Response $response 245 | * @return ApiProblemResponse 246 | */ 247 | protected function getApiProblemResponse(OAuth2Response $response) 248 | { 249 | $parameters = $response->getParameters(); 250 | $errorUri = isset($parameters['error_uri']) ? $parameters['error_uri'] : null; 251 | $error = isset($parameters['error']) ? $parameters['error'] : null; 252 | $errorDescription = isset($parameters['error_description']) ? $parameters['error_description'] : null; 253 | 254 | return new ApiProblemResponse( 255 | new ApiProblem( 256 | $response->getStatusCode(), 257 | $errorDescription, 258 | $errorUri, 259 | $error 260 | ) 261 | ); 262 | } 263 | 264 | /** 265 | * Create an OAuth2 request based on the ZF2 request object 266 | * 267 | * Marshals: 268 | * 269 | * - query string 270 | * - body parameters, via content negotiation 271 | * - "server", specifically the request method and content type 272 | * - raw content 273 | * - headers 274 | * 275 | * This ensures that JSON requests providing credentials for OAuth2 276 | * verification/validation can be processed. 277 | * 278 | * @return OAuth2Request 279 | */ 280 | protected function getOAuth2Request() 281 | { 282 | $zf2Request = $this->getRequest(); 283 | $headers = $zf2Request->getHeaders(); 284 | 285 | // Marshal content type, so we can seed it into the $_SERVER array 286 | $contentType = ''; 287 | if ($headers->has('Content-Type')) { 288 | $contentType = $headers->get('Content-Type')->getFieldValue(); 289 | } 290 | 291 | // Get $_SERVER superglobal 292 | $server = []; 293 | if ($zf2Request instanceof PhpEnvironmentRequest) { 294 | $server = $zf2Request->getServer()->toArray(); 295 | } elseif (! empty($_SERVER)) { 296 | $server = $_SERVER; 297 | } 298 | $server['REQUEST_METHOD'] = $zf2Request->getMethod(); 299 | 300 | // Seed headers with HTTP auth information 301 | $headers = $headers->toArray(); 302 | if (isset($server['PHP_AUTH_USER'])) { 303 | $headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER']; 304 | } 305 | if (isset($server['PHP_AUTH_PW'])) { 306 | $headers['PHP_AUTH_PW'] = $server['PHP_AUTH_PW']; 307 | } 308 | 309 | // Ensure the bodyParams are passed as an array 310 | $bodyParams = $this->bodyParams() ?: []; 311 | 312 | return new OAuth2Request( 313 | $zf2Request->getQuery()->toArray(), 314 | $bodyParams, 315 | [], // attributes 316 | [], // cookies 317 | [], // files 318 | $server, 319 | $zf2Request->getContent(), 320 | $headers 321 | ); 322 | } 323 | 324 | /** 325 | * Convert the OAuth2 response to a \Zend\Http\Response 326 | * 327 | * @param $response OAuth2Response 328 | * @return \Zend\Http\Response 329 | */ 330 | private function setHttpResponse(OAuth2Response $response) 331 | { 332 | $httpResponse = $this->getResponse(); 333 | $httpResponse->setStatusCode($response->getStatusCode()); 334 | 335 | $headers = $httpResponse->getHeaders(); 336 | $headers->addHeaders($response->getHttpHeaders()); 337 | $headers->addHeaderLine('Content-type', 'application/json'); 338 | 339 | $httpResponse->setContent($response->getResponseBody()); 340 | return $httpResponse; 341 | } 342 | 343 | /** 344 | * Retrieve the OAuth2\Server instance. 345 | * 346 | * If not already created by the composed $serverFactory, that callable 347 | * is invoked with the provided $type as an argument, and the value 348 | * returned. 349 | * 350 | * @param string $type 351 | * @return OAuth2Server 352 | * @throws RuntimeException if the factory does not return an OAuth2Server instance. 353 | */ 354 | private function getOAuth2Server($type) 355 | { 356 | if ($this->server instanceof OAuth2Server) { 357 | return $this->server; 358 | } 359 | 360 | $server = call_user_func($this->serverFactory, $type); 361 | if (! $server instanceof OAuth2Server) { 362 | throw new RuntimeException(sprintf( 363 | 'OAuth2\Server factory did not return a valid instance; received %s', 364 | (is_object($server) ? get_class($server) : gettype($server)) 365 | )); 366 | } 367 | $this->server = $server; 368 | return $server; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/Controller/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | getOAuth2ServerFactory($container), 29 | $container->get(UserId::class) 30 | ); 31 | 32 | $authController->setApiProblemErrorResponse( 33 | $this->marshalApiProblemErrorResponse($container) 34 | ); 35 | 36 | return $authController; 37 | } 38 | 39 | /** 40 | * @param ServiceLocatorInterface $controllers 41 | * @param null|string $name 42 | * @param null|string $requestedName 43 | * @return AuthController 44 | */ 45 | public function createService(ServiceLocatorInterface $controllers, $name = null, $requestedName = null) 46 | { 47 | if ($controllers instanceof AbstractPluginManager) { 48 | $container = $controllers->getServiceLocator() ?: $controllers; 49 | } else { 50 | $container = $controllers; 51 | } 52 | 53 | $requestedName = $requestedName ?: AuthController::class; 54 | 55 | return $this($container, $requestedName); 56 | } 57 | 58 | /** 59 | * Retrieve the OAuth2\Server factory. 60 | * 61 | * For BC purposes, if the OAuth2Server service returns an actual 62 | * instance, this will wrap it in a closure before returning it. 63 | * 64 | * @param ContainerInterface $container 65 | * @return callable 66 | */ 67 | private function getOAuth2ServerFactory(ContainerInterface $container) 68 | { 69 | $oauth2ServerFactory = $container->get('ZF\OAuth2\Service\OAuth2Server'); 70 | if (! $oauth2ServerFactory instanceof OAuth2Server) { 71 | return $oauth2ServerFactory; 72 | } 73 | 74 | return function () use ($oauth2ServerFactory) { 75 | return $oauth2ServerFactory; 76 | }; 77 | } 78 | 79 | /** 80 | * Determine whether or not to render API Problem error responses. 81 | * 82 | * @param ContainerInterface $container 83 | * @return bool 84 | */ 85 | private function marshalApiProblemErrorResponse(ContainerInterface $container) 86 | { 87 | if (! $container->has('config')) { 88 | return false; 89 | } 90 | 91 | $config = $container->get('config'); 92 | 93 | return (isset($config['zf-oauth2']['api_problem_error_response']) 94 | && $config['zf-oauth2']['api_problem_error_response'] === true); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Factory/IbmDb2AdapterFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 22 | 23 | if (! isset($config['zf-oauth2']['db']) || empty($config['zf-oauth2']['db'])) { 24 | throw new Exception\RuntimeException( 25 | 'The database configuration [\'zf-oauth2\'][\'db\'] for OAuth2 is missing' 26 | ); 27 | } 28 | 29 | $username = isset($config['zf-oauth2']['db']['username']) 30 | ? $config['zf-oauth2']['db']['username'] 31 | : null; 32 | $password = isset($config['zf-oauth2']['db']['password']) 33 | ? $config['zf-oauth2']['db']['password'] 34 | : null; 35 | $driver_options = isset($config['zf-oauth2']['db']['driver_options']) 36 | ? $config['zf-oauth2']['db']['driver_options'] 37 | : []; 38 | 39 | $oauth2ServerConfig = []; 40 | if (isset($config['zf-oauth2']['storage_settings']) 41 | && is_array($config['zf-oauth2']['storage_settings']) 42 | ) { 43 | $oauth2ServerConfig = $config['zf-oauth2']['storage_settings']; 44 | } 45 | 46 | return new IbmDb2Adapter([ 47 | 'database' => $config['zf-oauth2']['db']['database'], 48 | 'username' => $username, 49 | 'password' => $password, 50 | 'driver_options' => $driver_options, 51 | ], $oauth2ServerConfig); 52 | } 53 | 54 | /** 55 | * Provided for backwards compatibility; proxies to __invoke(). 56 | * 57 | * @param \Zend\ServiceManager\ServiceLocatorInterface $container 58 | * @return IbmDb2Adapter 59 | */ 60 | public function createService($container) 61 | { 62 | return $this($container); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Factory/MongoAdapterFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class MongoAdapterFactory 18 | { 19 | /** 20 | * @param ContainerInterface $container 21 | * @return MongoAdapter 22 | */ 23 | public function __invoke(ContainerInterface $container) 24 | { 25 | $config = $container->get('config'); 26 | return new MongoAdapter( 27 | $this->getMongoDb($container, $config), 28 | $this->getOauth2ServerConfig($config) 29 | ); 30 | } 31 | 32 | /** 33 | * Provided for backwards compatibility; proxies to __invoke(). 34 | * 35 | * @param \Zend\ServiceManager\ServiceLocatorInterface $container 36 | * @return MongoAdapter 37 | */ 38 | public function createService($container) 39 | { 40 | return $this($container); 41 | } 42 | 43 | /** 44 | * Get the mongo database 45 | * 46 | * @param ContainerInterface $container 47 | * @param array|\ArrayAccess $config 48 | * @return \MongoDB 49 | */ 50 | protected function getMongoDb(ContainerInterface $container, $config) 51 | { 52 | $dbLocatorName = isset($config['zf-oauth2']['mongo']['locator_name']) 53 | ? $config['zf-oauth2']['mongo']['locator_name'] 54 | : 'MongoDB'; 55 | 56 | if ($container->has($dbLocatorName)) { 57 | return $container->get($dbLocatorName); 58 | } 59 | 60 | if (! isset($config['zf-oauth2']['mongo']) 61 | || empty($config['zf-oauth2']['mongo']['database']) 62 | ) { 63 | throw new Exception\RuntimeException( 64 | 'The database configuration [\'zf-oauth2\'][\'mongo\'] for OAuth2 is missing' 65 | ); 66 | } 67 | 68 | $options = isset($config['zf-oauth2']['mongo']['options']) 69 | ? $config['zf-oauth2']['mongo']['options'] 70 | : []; 71 | $options['connect'] = false; 72 | $server = isset($config['zf-oauth2']['mongo']['dsn']) 73 | ? $config['zf-oauth2']['mongo']['dsn'] 74 | : null; 75 | $mongo = new MongoClient($server, $options); 76 | 77 | return $mongo->{$config['zf-oauth2']['mongo']['database']}; 78 | } 79 | 80 | /** 81 | * Retrieve oauth2-server-php configuration 82 | * 83 | * @param array|\ArrayAccess $config 84 | * @return array 85 | */ 86 | protected function getOauth2ServerConfig($config) 87 | { 88 | if (isset($config['zf-oauth2']['storage_settings']) 89 | && is_array($config['zf-oauth2']['storage_settings']) 90 | ) { 91 | return $config['zf-oauth2']['storage_settings']; 92 | } 93 | 94 | return []; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Factory/OAuth2ServerFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 19 | $config = isset($config['zf-oauth2']) ? $config['zf-oauth2'] : []; 20 | return new OAuth2ServerInstanceFactory($config, $container); 21 | } 22 | 23 | /** 24 | * Provided for backwards compatibility; proxies to __invoke(). 25 | * 26 | * @param \Zend\ServiceManager\ServiceLocatorInterface $container 27 | * @return OAuth2ServerInstanceFactory 28 | */ 29 | public function createService($container) 30 | { 31 | return $this($container); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Factory/OAuth2ServerInstanceFactory.php: -------------------------------------------------------------------------------- 1 | config = $config; 42 | $this->services = $services; 43 | } 44 | 45 | /** 46 | * Create an OAuth2\Server instance. 47 | * 48 | * @return OAuth2Server 49 | * @throws Exception\RuntimeException 50 | */ 51 | public function __invoke() 52 | { 53 | if ($this->server) { 54 | return $this->server; 55 | } 56 | 57 | $config = $this->config; 58 | 59 | if (! isset($config['storage']) || empty($config['storage'])) { 60 | throw new Exception\RuntimeException( 61 | 'The storage configuration for OAuth2 is missing' 62 | ); 63 | } 64 | 65 | $storagesServices = []; 66 | if (is_string($config['storage'])) { 67 | $storagesServices[] = $config['storage']; 68 | } elseif (is_array($config['storage'])) { 69 | $storagesServices = $config['storage']; 70 | } else { 71 | throw new Exception\RuntimeException( 72 | 'The storage configuration for OAuth2 should be string or array' 73 | ); 74 | } 75 | 76 | $storage = []; 77 | 78 | foreach ($storagesServices as $storageKey => $storagesService) { 79 | $storage[$storageKey] = $this->services->get($storagesService); 80 | } 81 | 82 | $enforceState = isset($config['enforce_state']) 83 | ? $config['enforce_state'] 84 | : true; 85 | $allowImplicit = isset($config['allow_implicit']) 86 | ? $config['allow_implicit'] 87 | : false; 88 | $accessLifetime = isset($config['access_lifetime']) 89 | ? $config['access_lifetime'] 90 | : 3600; 91 | $audience = isset($config['audience']) 92 | ? $config['audience'] 93 | : ''; 94 | $options = isset($config['options']) 95 | ? $config['options'] 96 | : []; 97 | $options = array_merge([ 98 | 'enforce_state' => $enforceState, 99 | 'allow_implicit' => $allowImplicit, 100 | 'access_lifetime' => $accessLifetime 101 | ], $options); 102 | 103 | // Pass a storage object or array of storage objects to the OAuth2 server class 104 | $server = new OAuth2Server($storage, $options); 105 | $availableGrantTypes = $config['grant_types']; 106 | 107 | if (isset($availableGrantTypes['client_credentials']) && $availableGrantTypes['client_credentials'] === true) { 108 | $clientOptions = []; 109 | if (isset($options['allow_credentials_in_request_body'])) { 110 | $clientOptions['allow_credentials_in_request_body'] = $options['allow_credentials_in_request_body']; 111 | } 112 | 113 | // Add the "Client Credentials" grant type (it is the simplest of the grant types) 114 | $server->addGrantType(new ClientCredentials($server->getStorage('client_credentials'), $clientOptions)); 115 | } 116 | 117 | if (isset($availableGrantTypes['authorization_code']) && $availableGrantTypes['authorization_code'] === true) { 118 | // Add the "Authorization Code" grant type (this is where the oauth magic happens) 119 | $server->addGrantType(new AuthorizationCode($server->getStorage('authorization_code'))); 120 | } 121 | 122 | if (isset($availableGrantTypes['password']) && $availableGrantTypes['password'] === true) { 123 | // Add the "User Credentials" grant type 124 | $server->addGrantType(new UserCredentials($server->getStorage('user_credentials'))); 125 | } 126 | 127 | if (isset($availableGrantTypes['jwt']) && $availableGrantTypes['jwt'] === true) { 128 | // Add the "JWT Bearer" grant type 129 | $server->addGrantType(new JwtBearer($server->getStorage('jwt_bearer'), $audience)); 130 | } 131 | 132 | if (isset($availableGrantTypes['refresh_token']) && $availableGrantTypes['refresh_token'] === true) { 133 | $refreshOptions = []; 134 | if (isset($options['always_issue_new_refresh_token'])) { 135 | $refreshOptions['always_issue_new_refresh_token'] = $options['always_issue_new_refresh_token']; 136 | } 137 | if (isset($options['unset_refresh_token_after_use'])) { 138 | $refreshOptions['unset_refresh_token_after_use'] = $options['unset_refresh_token_after_use']; 139 | } 140 | 141 | // Add the "Refresh Token" grant type 142 | $server->addGrantType(new RefreshToken($server->getStorage('refresh_token'), $refreshOptions)); 143 | } 144 | 145 | return $this->server = $server; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Factory/PdoAdapterFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 22 | 23 | if (empty($config['zf-oauth2']['db'])) { 24 | throw new Exception\RuntimeException( 25 | 'The database configuration [\'zf-oauth2\'][\'db\'] for OAuth2 is missing' 26 | ); 27 | } 28 | 29 | $username = isset($config['zf-oauth2']['db']['username']) ? $config['zf-oauth2']['db']['username'] : null; 30 | $password = isset($config['zf-oauth2']['db']['password']) ? $config['zf-oauth2']['db']['password'] : null; 31 | $options = isset($config['zf-oauth2']['db']['options']) ? $config['zf-oauth2']['db']['options'] : []; 32 | 33 | $oauth2ServerConfig = []; 34 | if (isset($config['zf-oauth2']['storage_settings']) 35 | && is_array($config['zf-oauth2']['storage_settings']) 36 | ) { 37 | $oauth2ServerConfig = $config['zf-oauth2']['storage_settings']; 38 | } 39 | 40 | return new PdoAdapter([ 41 | 'dsn' => $config['zf-oauth2']['db']['dsn'], 42 | 'username' => $username, 43 | 'password' => $password, 44 | 'options' => $options, 45 | ], $oauth2ServerConfig); 46 | } 47 | 48 | /** 49 | * Provided for backwards compatibility; proxies to __invoke(). 50 | * 51 | * @param \Zend\ServiceManager\ServiceLocatorInterface $container 52 | * @return PdoAdapter 53 | */ 54 | public function createService($container) 55 | { 56 | return $this($container); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | authenticationService = $service; 33 | 34 | if (isset($config['zf-oauth2']['user_id'])) { 35 | $this->userId = $config['zf-oauth2']['user_id']; 36 | } 37 | } 38 | 39 | /** 40 | * Use implementation of Zend\Authentication\AuthenticationServiceInterface to fetch the identity. 41 | * 42 | * @param RequestInterface $request 43 | * @return mixed 44 | */ 45 | public function __invoke(RequestInterface $request) 46 | { 47 | if (null === $this->authenticationService) { 48 | return null; 49 | } 50 | 51 | $identity = $this->authenticationService->getIdentity(); 52 | 53 | if (is_object($identity)) { 54 | if (property_exists($identity, $this->userId)) { 55 | return $identity->{$this->userId}; 56 | } 57 | 58 | $method = "get" . ucfirst($this->userId); 59 | if (method_exists($identity, $method)) { 60 | return $identity->$method(); 61 | } 62 | 63 | return null; 64 | } 65 | 66 | if (is_array($identity) && isset($identity[$this->userId])) { 67 | return $identity[$this->userId]; 68 | } 69 | 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Provider/UserId/AuthenticationServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 20 | 21 | if ($container->has('Zend\Authentication\AuthenticationService')) { 22 | return new AuthenticationService( 23 | $container->get('Zend\Authentication\AuthenticationService'), 24 | $config 25 | ); 26 | } 27 | 28 | return new AuthenticationService(null, $config); 29 | } 30 | 31 | /** 32 | * Provided for backwards compatibility; proxies to __invoke(). 33 | * 34 | * @param \Zend\ServiceManager\ServiceLocatorInterface $container 35 | * @return AuthenticationService 36 | */ 37 | public function createService($container) 38 | { 39 | return $this($container); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Provider/UserId/UserIdProviderInterface.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /view/zf/auth/receive-code.phtml: -------------------------------------------------------------------------------- 1 | code) { 3 | printf("

The authentication code is %s

", $this->code); 4 | printf("

Use this code to request an access token.

"); 5 | printf("For instance, using HTTPie:

"); 6 | printf( 7 | "

http POST %s grant_type=authorization_code code=%s redirect_uri=%s client_id=testclient client_secret=testpass

", 8 | $this->serverUrl('/oauth'), 9 | $this->code, 10 | '/oauth/receivecode' 11 | ); 12 | printf("

or using CURL:

"); 13 | printf( 14 | "

curl -u testclient:testpass %s -d 'grant_type=authorization_code&code=%s&redirect_uri=%s'

", 15 | $this->serverUrl('/oauth'), 16 | $this->code, 17 | '/oauth/receivecode' 18 | ); 19 | printf("

or

"); 20 | printf( 21 | '

curl -H "Content-Type: application/json" -X POST -d \'{"redirect_uri":"%s","client_id":"testclient","client_secret":"testpass","code":"%s","grant_type":"authorization_code"}\' %s

', 22 | '/oauth/receivecode', 23 | $this->code, 24 | $this->serverUrl('/oauth') 25 | ); 26 | } else { 27 | printf("

The access token is (Click here to read the token from URL fragment)

"); 28 | ?> 29 | 54 | 57 | --------------------------------------------------------------------------------