├── 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 | [](https://secure.travis-ci.org/zfcampus/zf-oauth2)
8 | [](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("");
28 | ?>
29 |
54 |
57 |
--------------------------------------------------------------------------------