├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── module.config.php └── src ├── Authentication ├── AbstractAdapter.php ├── AdapterInterface.php ├── DefaultAuthenticationListener.php ├── DefaultAuthenticationPostListener.php ├── HttpAdapter.php └── OAuth2Adapter.php ├── Authorization ├── AclAuthorization.php ├── AclAuthorizationFactory.php ├── AuthorizationInterface.php ├── DefaultAuthorizationListener.php ├── DefaultAuthorizationPostListener.php └── DefaultResourceResolverListener.php ├── Factory ├── AclAuthorizationFactory.php ├── ApacheResolverFactory.php ├── AuthenticationAdapterDelegatorFactory.php ├── AuthenticationHttpAdapterFactory.php ├── AuthenticationOAuth2AdapterFactory.php ├── AuthenticationServiceFactory.php ├── DefaultAuthHttpAdapterFactory.php ├── DefaultAuthenticationListenerFactory.php ├── DefaultAuthorizationListenerFactory.php ├── DefaultResourceResolverListenerFactory.php ├── FileResolverFactory.php ├── HttpAdapterFactory.php ├── NamedOAuth2ServerFactory.php └── OAuth2ServerFactory.php ├── Identity ├── AuthenticatedIdentity.php ├── GuestIdentity.php ├── IdentityInterface.php └── IdentityPlugin.php ├── Module.php ├── MvcAuthEvent.php └── MvcRouteListener.php /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.2 - 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.1 - 2018-05-31 28 | 29 | ### Added 30 | 31 | - Nothing. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - [#143](https://github.com/zfcampus/zf-mvc-auth/pull/143) provides an update to `ZF\MvcAuth\Factory\OAuth2ServerFactory` to allow the `zf-oauth2.options.use_openid_connect` 48 | option (or adapter-specific setting `options.use_openid_connect`) to vary which class is used for an 49 | `authorization_code` grant type. If the setting is present and a boolean `true` value, the class 50 | `OAuth2\OpenID\GrantType\AuthorizationCode` will be used instead of `OAuth2\GrantType\AuthorizationCode`. 51 | 52 | ## 1.5.0 - 2018-05-02 53 | 54 | ### Added 55 | 56 | - [#137](https://github.com/zfcampus/zf-mvc-auth/pull/137) adds support for zend-permissions-rbac 3.0. 57 | 58 | - [#137](https://github.com/zfcampus/zf-mvc-auth/pull/137) adds support for PHP 7.1 and 7.2. 59 | 60 | ### Changed 61 | 62 | - Nothing. 63 | 64 | ### Deprecated 65 | 66 | - Nothing. 67 | 68 | ### Removed 69 | 70 | - [#137](https://github.com/zfcampus/zf-mvc-auth/pull/137) removes support for HHVM. 71 | 72 | ### Fixed 73 | 74 | - [#136](https://github.com/zfcampus/zf-mvc-auth/pull/136) provides changes to the `OAuth2Adapter` that prevent hitting 75 | the database twice when the token is valid. 76 | 77 | ## 1.4.3 - 2016-09-30 78 | 79 | ### Added 80 | 81 | - Nothing. 82 | 83 | ### Deprecated 84 | 85 | - Nothing. 86 | 87 | ### Removed 88 | 89 | - Nothing. 90 | 91 | ### Fixed 92 | 93 | - [#128](https://github.com/zfcampus/zf-mvc-auth/pull/128) fixes an issue 94 | stemming from changes in the Admin API; controller service names are often 95 | written in configuration using dash, versus namespace, separators, which 96 | causes authorization lookups to fail. This version now converts dashes to 97 | namespace separators in the controller names when creating the ACL. 98 | 99 | ## 1.4.2 - 2016-08-03 100 | 101 | ### Added 102 | 103 | - Nothing. 104 | 105 | ### Deprecated 106 | 107 | - Nothing. 108 | 109 | ### Removed 110 | 111 | - Nothing. 112 | 113 | ### Fixed 114 | 115 | - [#125](https://github.com/zfcampus/zf-mvc-auth/pull/125) updates the 116 | `MvcRouteListener` to trigger events using `triggerEventUntil()` instead 117 | of using argument overloading on `trigger()`; this change ensures that the 118 | code will work with zend-eventmanager v3 properly. 119 | 120 | ## 1.4.1 - 2016-07-25 121 | 122 | ### Added 123 | 124 | - Nothing. 125 | 126 | ### Deprecated 127 | 128 | - Nothing. 129 | 130 | ### Removed 131 | 132 | - Nothing. 133 | 134 | ### Fixed 135 | 136 | - [#120](https://github.com/zfcampus/zf-mvc-auth/pull/120) fixes the 137 | `Module::onBootstrap()` method to re-introduce attachment of the 138 | `MvcRouteListener`. 139 | - [#119](https://github.com/zfcampus/zf-mvc-auth/pull/119) fixes a comparisoin 140 | in `DefaultResourceResolverListener::getIdentifier()` whereby an identifier of 141 | `0` was incorrectly resulting in matching to a collection request. As 142 | collections and entities often have different permissions, this could lead to 143 | potential false-positiive authorization checks. 144 | 145 | ## 1.4.0 - 2016-07-11 146 | 147 | ### Added 148 | 149 | - [#114](https://github.com/zfcampus/zf-mvc-auth/pull/114) and 150 | [#116](https://github.com/zfcampus/zf-mvc-auth/pull/116) add support for both 151 | PHP 7 and version 3 components from Zend Framework (while retaining 152 | compatibility for version 2 components). 153 | 154 | ### Deprecated 155 | 156 | - Nothing. 157 | 158 | ### Removed 159 | 160 | - [#116](https://github.com/zfcampus/zf-mvc-auth/pull/116) removes support for 161 | PHP 5.5. 162 | 163 | ### Fixed 164 | 165 | - Nothing. 166 | 167 | ## 1.3.2 - 2016-07-11 168 | 169 | ### Added 170 | 171 | - Nothing. 172 | 173 | ### Deprecated 174 | 175 | - Nothing. 176 | 177 | ### Removed 178 | 179 | - Nothing. 180 | 181 | ### Fixed 182 | 183 | - [#111](https://github.com/zfcampus/zf-mvc-auth/pull/111) adds a check for the 184 | `unset_refresh_token_after_use` configuration flag when creating an 185 | `OAuth2\Server` instance, passing it to the instance when discovered. 186 | -------------------------------------------------------------------------------- /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 MVC Auth 2 | =========== 3 | 4 | > ## Repository abandoned 2019-12-31 5 | > 6 | > This repository has moved to [laminas-api-tools/api-tools-mvc-auth](https://github.com/laminas-api-tools/api-tools-mvc-auth). 7 | 8 | [![Build Status](https://secure.travis-ci.org/zfcampus/zf-mvc-auth.svg?branch=master)](https://secure.travis-ci.org/zfcampus/zf-mvc-auth) 9 | [![Coverage Status](https://coveralls.io/repos/github/zfcampus/zf-mvc-auth/badge.svg?branch=master)](https://coveralls.io/github/zfcampus/zf-mvc-auth?branch=master) 10 | 11 | Introduction 12 | ------------ 13 | 14 | `zf-mvc-auth` is a ZF2 module that adds services, events, and configuration that extends the base 15 | ZF2 MVC lifecycle to handle authentication and authorization. 16 | 17 | For authentication, 3 primary methods are supported out of the box: HTTP Basic authentication, 18 | HTTP Digest authentication, and OAuth2 (this requires Brent Shaffer's [OAuth2 19 | Server](https://github.com/bshaffer/oauth2-server-php)). 20 | 21 | For authorization, this particular module delivers a pre-dispatch time listener that will 22 | identify if the given route match, along with the HTTP method, is authorized to be dispatched. 23 | 24 | Requirements 25 | ------------ 26 | 27 | Please see the [composer.json](composer.json) file. 28 | 29 | Installation 30 | ------------ 31 | 32 | Run the following `composer` command: 33 | 34 | ```console 35 | $ composer require "zfcampus/zf-mvc-auth" 36 | ``` 37 | 38 | Alternately, manually add the following to your `composer.json`, in the `require` section: 39 | 40 | ```javascript 41 | "require": { 42 | "zfcampus/zf-mvc-auth": "^1.4" 43 | } 44 | ``` 45 | 46 | And then run `composer update` to ensure the module is installed. 47 | 48 | Finally, add the module name to your project's `config/application.config.php` under the `modules` 49 | key: 50 | 51 | 52 | ```php 53 | return [ 54 | /* ... */ 55 | 'modules' => [ 56 | /* ... */ 57 | 'ZF\MvcAuth', 58 | ], 59 | /* ... */ 60 | ]; 61 | ``` 62 | 63 | Configuration 64 | ------------- 65 | 66 | ### User Configuration 67 | 68 | The top-level configuration key for user configuration of this module is `zf-mvc-auth`. Under this 69 | key, there are two sub-keys, one for `authentication` and the other for `authorization`. 70 | 71 | #### Key: `authentication` 72 | 73 | The `authentication` key is used for any configuration that is related to the process of 74 | authentication, or the process of validating an identity. 75 | 76 | ##### Sub-key: `http` 77 | 78 | The `http` sub-key is utilized for configuring an HTTP-based authentication scheme. These schemes 79 | utilize ZF2's `Zend\Authentication\Adapter\Http` adapter, which implements both HTTP 80 | Basic and HTTP Digest authentication. To accomplish this, the HTTP adapter uses a file based 81 | "resolver" in order to resolve the file containing credentials. These implementation nuances can be 82 | explored in the [Authentication portion of the ZF2 manual](http://framework.zend.com/manual/2.0/en/modules/zend.authentication.adapter.http.html). 83 | 84 | The `http` sub-key has several fields: 85 | 86 | - `accept_schemes`: *required*; an array of configured schemes; one or both of `basic` and `digest`. 87 | - `realm`: *required*; this is typically a string that identifies the HTTP realm; e.g., "My Site". 88 | - `digest_domains`: *required* for HTTP Digest; this is the relative URI for the protected area, 89 | typically `/`. 90 | - `nonce_timeout`: *required* for HTTP Digest; the number of seconds in which to expire the digest 91 | nonce, typically `3600`. 92 | 93 | Beyond those configuration options, one or both of the following resolver configurations is required: 94 | 95 | - `htpasswd`: the path to a file created in the `htpasswd` file format 96 | - `htdigest`: the path to a file created in the `htdigest` file format 97 | 98 | An example might look like the following: 99 | 100 | ```php 101 | 'http' => [ 102 | 'accept_schemes' => ['basic', 'digest'], 103 | 'realm' => 'My Web Site', 104 | 'digest_domains' => '/', 105 | 'nonce_timeout' => 3600, 106 | 'htpasswd' => APPLICATION_PATH . '/data/htpasswd', // htpasswd tool generated 107 | 'htdigest' => APPLICATION_PATH . '/data/htdigest', // @see http://www.askapache.com/online-tools/htpasswd-generator/ 108 | ], 109 | ``` 110 | 111 | ##### Sub-key: `map` 112 | 113 | - Since 1.1.0. 114 | 115 | The `map` subkey is used to map an API module (optionally, with a version 116 | namespace) to a given authentication type (typically, one of `basic`, `digest`, or 117 | `oauth2`). This can be used to enfore different authentication methods for 118 | different APIs, or even versions of the same API. 119 | 120 | ```php 121 | return [ 122 | 'zf-mvc-auth' => [ 123 | 'authentication' => [ 124 | 'map' => [ 125 | 'Status\V1' => 'basic', // v1 only! 126 | 'Status\V2' => 'oauth2', // v2 only! 127 | 'Ping' => 'digest', // all versions! 128 | ], 129 | ], 130 | ], 131 | ]; 132 | ``` 133 | 134 | In the absence of a `map` subkey, if any authentication adapter configuration 135 | is defined, that configuration will be used for any API. 136 | 137 | **Note for users migrating from 1.0**: In the 1.0 series, authentication was 138 | *per-application*, not per API. The migration to 1.1 should be seamless; if you 139 | do not edit your authentication settings, or provide authentication information 140 | to any APIs, your API will continue to act as it did. The first time you perform 141 | one of these actions, the Admin API will create a map, mapping each version of 142 | each service to the configured authentication scheme, and thus ensuring that 143 | your API continues to work as previously configured, while giving you the 144 | flexibility to define authentication per-API and per-version in the future. 145 | 146 | ##### Sub-key: `types` 147 | 148 | - Since 1.1.0. 149 | 150 | Starting in 1.1.0, the concept of authentication adapters was provided. Adapters 151 | "provide" one or more authentication types; these are then used internally to 152 | determine which adapter to use, as well as by the Admin API to allow mapping 153 | APIs to specific authentication types. 154 | 155 | In some instances you may be using listeners or other facilities for 156 | authenticating an API. In order to allow mapping these (which is primarily a 157 | documentation feature in such instances), the `types` subkey exists. This key is 158 | an array of string authentication types: 159 | 160 | ```php 161 | return [ 162 | 'zf-mvc-auth' => [ 163 | 'authentication' => [ 164 | 'types' => [ 165 | 'token', 166 | 'key', 167 | ], 168 | ], 169 | ], 170 | ]; 171 | ``` 172 | 173 | This key and its contents **must** be created manually. 174 | 175 | ##### Sub-key: `adapters` 176 | 177 | - Since 1.1.0. 178 | 179 | Starting in 1.1.0, with the introduction of adapters, you can also configure 180 | named HTTP and OAuth2 adapters. The name provided will be used as the 181 | authentication type for purposes of mapping APIs to an authentication adapter. 182 | 183 | The format for the `adapters` key is a key/value pair, with the key acting as 184 | the type, and the value as configuration for providing a 185 | `ZF\MvcAuth\Authentication\HttpAdapter` or 186 | `ZF\MvcAuth\Authentication\OAuth2Adapter` instance, as follows: 187 | 188 | ```php 189 | return [ 190 | 'zf-mvc-auth' => [ 191 | 'authentication' => [ 192 | 'adapters' => [ 193 | 'api' => [ 194 | // This defines an HTTP adapter that can satisfy both 195 | // basic and digest. 196 | 'adapter' => 'ZF\MvcAuth\Authentication\HttpAdapter', 197 | 'options' => [ 198 | 'accept_schemes' => ['basic', 'digest'], 199 | 'realm' => 'api', 200 | 'digest_domains' => 'https://example.com', 201 | 'nonce_timeout' => 3600, 202 | 'htpasswd' => 'data/htpasswd', 203 | 'htdigest' => 'data/htdigest', 204 | ], 205 | ], 206 | 'user' => [ 207 | // This defines an OAuth2 adapter backed by PDO. 208 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 209 | 'storage' => [ 210 | 'adapter' => 'pdo', 211 | 'dsn' => 'mysql:host=localhost;dbname=oauth2', 212 | 'username' => 'username', 213 | 'password' => 'password', 214 | 'options' => [ 215 | 1002 => 'SET NAMES utf8', // PDO::MYSQL_ATTR_INIT_COMMAND 216 | ], 217 | ], 218 | ], 219 | 'client' => [ 220 | // This defines an OAuth2 adapter backed by Mongo. 221 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 222 | 'storage' => [ 223 | 'adapter' => 'mongo', 224 | 'locator_name' => 'SomeServiceName', // If provided, pulls the given service 225 | 'dsn' => 'mongodb://localhost', 226 | 'database' => 'oauth2', 227 | 'options' => [ 228 | 'username' => 'username', 229 | 'password' => 'password', 230 | 'connectTimeoutMS' => 500, 231 | ], 232 | ], 233 | ], 234 | ], 235 | ], 236 | ], 237 | ]; 238 | ``` 239 | 240 | #### Key: `authorization` 241 | 242 | #### Sub-Key: `deny_by_default` 243 | 244 | `deny_by_default` toggles the default behavior for the `Zend\Permissions\Acl` implementation. The 245 | default value is `false`, which means that if no authenticated user is present, and no permissions 246 | rule applies for the current resource, then access is allowed. Change this setting to `true` to 247 | require authenticated identities by default. 248 | 249 | Example: 250 | 251 | ```php 252 | 'deny_by_default' => false, 253 | ``` 254 | 255 | > ##### deny_by_default with zf-oauth2 256 | > 257 | > When using `deny_by_default => true` with > [zf-oauth2](https://github.com/zfcampus/zf-oauth2), 258 | > you will need to explicitly allow POST on the OAuth2 controller in order for Authentication 259 | > requests to be made. 260 | > 261 | > As an example: 262 | > 263 | > ```php 264 | > 'authorization' => [ 265 | > 'deny_by_default' => true, 266 | > 'ZF\\OAuth2\\Controller\\Auth' => [ 267 | > 'actions' => [ 268 | > 'token' => [ 269 | > 'GET' => false, 270 | > 'POST' => true, // <----- 271 | > 'PATCH' => false, 272 | > 'PUT' => false, 273 | > 'DELETE' => false, 274 | > ], 275 | > ], 276 | > ], 277 | > ], 278 | > ``` 279 | 280 | #### Sub-Key: Controller Service Name 281 | 282 | Under the `authorization` key is an array of _controller service name_ keyed authorization 283 | configuration settings. The structure of these arrays depends on the type of the controller 284 | service that you're attempting to grant or restrict access to. 285 | 286 | For the typical ZF2 based action controller, this array is keyed with `actions`. Under this 287 | key, each action name for the given controller service is associated with a *permission array*. 288 | 289 | For [zf-rest](https://github.com/zfcampus/zf-rest)-based controllers, a top level key of either 290 | `collection` or `entity` is used. Under each of these keys will be an associated *permission 291 | array*. 292 | 293 | A **permission array** consists of a keyed array of either `default` or an HTTP method. The 294 | values for each of these will be a boolean value where `true` means _an authenticated user 295 | is required_ and where `false` means _an authenticated user is *not* required_. If an action 296 | or HTTP method is not idendified, the `default` value will be assumed. If there is no default, 297 | the behavior of the `deny_by_default` key (discussed above) will be assumed. 298 | 299 | Below is an example: 300 | 301 | ```php 302 | 'authorization' => [ 303 | 'Controller\Service\Name' => [ 304 | 'actions' => [ 305 | 'action' => [ 306 | 'default' => boolean, 307 | 'GET' => boolean, 308 | 'POST' => boolean, 309 | // etc. 310 | ], 311 | ], 312 | 'collection' => [ 313 | 'default' => boolean, 314 | 'GET' => boolean, 315 | 'POST' => boolean, 316 | // etc. 317 | ], 318 | 'entity' => [ 319 | 'default' => boolean, 320 | 'GET' => boolean, 321 | 'POST' => boolean, 322 | // etc. 323 | ], 324 | ], 325 | ], 326 | ``` 327 | 328 | ### System Configuration 329 | 330 | The following configuration is provided in `config/module.config.php` to enable the module to 331 | function: 332 | 333 | ```php 334 | 'service_manager' => [ 335 | 'aliases' => [ 336 | 'authentication' => 'ZF\MvcAuth\Authentication', 337 | 'authorization' => 'ZF\MvcAuth\Authorization\AuthorizationInterface', 338 | 'ZF\MvcAuth\Authorization\AuthorizationInterface' => 'ZF\MvcAuth\Authorization\AclAuthorization', 339 | ], 340 | 'factories' => [ 341 | 'ZF\MvcAuth\Authentication' => 'ZF\MvcAuth\Factory\AuthenticationServiceFactory', 342 | 'ZF\MvcAuth\ApacheResolver' => 'ZF\MvcAuth\Factory\ApacheResolverFactory', 343 | 'ZF\MvcAuth\FileResolver' => 'ZF\MvcAuth\Factory\FileResolverFactory', 344 | 'ZF\MvcAuth\Authentication\DefaultAuthenticationListener' => 'ZF\MvcAuth\Factory\DefaultAuthenticationListenerFactory', 345 | 'ZF\MvcAuth\Authentication\AuthHttpAdapter' => 'ZF\MvcAuth\Factory\DefaultAuthHttpAdapterFactory', 346 | 'ZF\MvcAuth\Authorization\AclAuthorization' => 'ZF\MvcAuth\Factory\AclAuthorizationFactory', 347 | 'ZF\MvcAuth\Authorization\DefaultAuthorizationListener' => 'ZF\MvcAuth\Factory\DefaultAuthorizationListenerFactory', 348 | 'ZF\MvcAuth\Authorization\DefaultResourceResolverListener' => 'ZF\MvcAuth\Factory\DefaultResourceResolverListenerFactory', 349 | ], 350 | 'invokables' => [ 351 | 'ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener' => 'ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener', 352 | 'ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener' => 'ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener', 353 | ], 354 | ], 355 | ``` 356 | 357 | These services will be described in the events and services section. 358 | 359 | ZF2 Events 360 | ---------- 361 | 362 | ### Events 363 | 364 | #### ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHENTICATION (a.k.a "authentication") 365 | 366 | This event is triggered in relation to `MvcEvent::EVENT_ROUTE` at `500` priority. It is registered 367 | via the `ZF\MvcAuth\MvcRouteListener` event listener aggregate. 368 | 369 | #### ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHENTICATION_POST (a.k.a "authentication.post") 370 | 371 | This event is triggered in relation to `MvcEvent::EVENT_ROUTE` at `499` priority. It is 372 | registered via the `ZF\MvcAuth\MvcRouteListener` event listener aggregate. 373 | 374 | #### ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHORIZATION (a.k.a "authorization") 375 | 376 | This event is triggered in relation to `MvcEvent::EVENT_ROUTE` at `-600` priority. It is 377 | registered via the `ZF\MvcAuth\MvcRouteListener` event listener aggregate. 378 | 379 | #### ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHORIZATION_POST (a.k.a "authorization.post") 380 | 381 | This event is triggered in relation to `MvcEvent::EVENT_ROUTE` at `-601` priority. It is 382 | registered via the `ZF\MvcAuth\MvcRouteListener` event listener aggregate. 383 | 384 | #### ZF\MvcAuth\MvcAuthEvent object 385 | 386 | The `MvcAuthEvent` object provides contextual information when any authentication 387 | or authorization event is triggered. It persists the following: 388 | 389 | - identity: `setIdentity()` and `getIdentity()` 390 | - authentication service: `setAuthentication()` and `getAuthentication()` 391 | - authorization service: `setAuthorization()` and `getAuthorization()` 392 | - authorization result: `setIsAuthorized` and `isAuthorized()` 393 | - original MVC event: `getMvcEvent()` 394 | 395 | ### Listeners 396 | 397 | #### ZF\MvcAuth\Authentication\DefaultAuthenticationListener 398 | 399 | This listener is attached to the `MvcAuth::EVENT_AUTHENTICATION` event. It is primarily 400 | responsible for preforming any authentication and ensuring that an authenticated 401 | identity is persisted in both the `MvcAuthEvent` and `MvcEvent` objects (the latter under the event 402 | parameter `ZF\MvcAuth\Identity`). 403 | 404 | #### ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener 405 | 406 | This listener is attached to the `MvcAuth::EVENT_AUTHENTICATION_POST` event. It is primarily 407 | responsible for determining if an unsuccessful authentication was preformed, and in that case 408 | it will attempt to set a `401 Unauthorized` status on the `MvcEvent`'s response object. 409 | 410 | #### ZF\MvcAuth\Authorization\DefaultAuthorizationListener 411 | 412 | This listener is attached to the `MvcAuth::EVENT_AUTHORIZATION` event. It is primarily 413 | responsible for executing the `isAuthorized()` method on the configured authorization service. 414 | 415 | #### ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener 416 | 417 | This listener is attached to the `MvcAuth::EVENT_AUTHORIZATION_POST` event. It is primarily 418 | responsible for determining if the current request is authorized. In the case where the current 419 | request is not authorized, it will attempt to set a `403 Forbidden` status on the `MvcEvent`'s 420 | response object. 421 | 422 | #### ZF\MvcAuth\Authorization\DefaultResourceResolverListener 423 | 424 | This listener is attached to the `MvcAuth::EVENT_AUTHENTICATION_POST` with a priority of `-1`. 425 | It is primarily responsible for creating and persisting a special name in the current event 426 | for zf-rest-based controllers when used in conjunction with `zf-rest` module. 427 | 428 | ZF2 Services 429 | ------------ 430 | 431 | #### Controller Plugins 432 | 433 | This module exposes the controller plugin `getIdentity()`, mapping to 434 | `ZF\MvcAuth\Identity\IdentityPlugin`. This plugin will return the identity discovered during 435 | authentication as injected into the `Zend\Mvc\MvcEvent`'s `ZF\MvcAuth\Identity` parameter. If no 436 | identity is present in the `MvcEvent`, or the identity present is not an instance of 437 | `ZF\MvcAuth\Identity\IdentityInterface`, an instance of `ZF\MvcAuth\Identity\GuestIdentity` will be 438 | returned. 439 | 440 | #### Event Listener Services 441 | 442 | The following services are provided and serve as event listeners: 443 | 444 | - `ZF\MvcAuth\Authentication\DefaultAuthenticationListener` 445 | - `ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener` 446 | - `ZF\MvcAuth\Authorization\DefaultAuthorizationListener` 447 | - `ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener` 448 | - `ZF\MvcAuth\Authorization\DefaultResourceResolverListener` 449 | 450 | #### ZF\MvcAuth\Authentication (a.k.a "authentication") 451 | 452 | This is an instance of `Zend\Authentication\AuthenticationService`. 453 | 454 | #### ZF\MvcAuth\Authentication\AuthHttpAdapter 455 | 456 | This is an instance of `Zend\Authentication\Adapter\Http`. 457 | 458 | #### ZF\MvcAuth\Authorization\AclAuthorization (a.k.a "authorization", "ZF\MvcAuth\Authorization\AuthorizationInterface") 459 | 460 | This is an instance of `ZF\MvcAuth\Authorization\AclAuthorization`, which in turn is an extension 461 | of `Zend\Permissions\Acl\Acl`. 462 | 463 | 464 | #### ZF\MvcAuth\ApacheResolver 465 | 466 | This is an instance of `Zend\Authentication\Adapter\Http\ApacheResolver`. 467 | You can override the ApacheResolver with your own resolver by providing a custom factory. 468 | 469 | #### ZF\MvcAuth\FileResolver 470 | 471 | This is an instance of `Zend\Authentication\Adapter\Http\FileResolver`. 472 | You can override the FileResolver with your own resolver by providing a custom factory. 473 | 474 | ### Authentication Adapters 475 | 476 | - Since 1.1.0 477 | 478 | Authentication adapters provide the most direct means for adding custom 479 | authentication facilities to your APIs. Adapters implement 480 | `ZF\MvcAuth\Authentication\AdapterInterface`: 481 | 482 | ```php 483 | namespace ZF\MvcAuth\Authentication; 484 | 485 | use Zend\Http\Request; 486 | use Zend\Http\Response; 487 | use ZF\MvcAuth\Identity\IdentityInterface; 488 | use ZF\MvcAuth\MvcAuthEvent; 489 | 490 | interface AdapterInterface 491 | { 492 | /** 493 | * @return array Array of types this adapter can handle. 494 | */ 495 | public function provides(); 496 | 497 | /** 498 | * Attempt to match a requested authentication type 499 | * against what the adapter provides. 500 | * 501 | * @param string $type 502 | * @return bool 503 | */ 504 | public function matches($type); 505 | 506 | /** 507 | * Attempt to retrieve the authentication type based on the request. 508 | * 509 | * Allows an adapter to have custom logic for detecting if a request 510 | * might be providing credentials it's interested in. 511 | * 512 | * @param Request $request 513 | * @return false|string 514 | */ 515 | public function getTypeFromRequest(Request $request); 516 | 517 | /** 518 | * Perform pre-flight authentication operations. 519 | * 520 | * Use case would be for providing authentication challenge headers. 521 | * 522 | * @param Request $request 523 | * @param Response $response 524 | * @return void|Response 525 | */ 526 | public function preAuth(Request $request, Response $response); 527 | 528 | /** 529 | * Attempt to authenticate the current request. 530 | * 531 | * @param Request $request 532 | * @param Response $response 533 | * @param MvcAuthEvent $mvcAuthEvent 534 | * @return false|IdentityInterface False on failure, IdentityInterface 535 | * otherwise 536 | */ 537 | public function authenticate(Request $request, Response $response, MvcAuthEvent $mvcAuthEvent); 538 | } 539 | ``` 540 | 541 | The `provides()` method should return an array of strings, each an 542 | authentication "type" that this adapter provides; as an example, the provided 543 | `ZF\MvcAuth\Authentication\HttpAdapter` can provide `basic` and/or `digest`. 544 | 545 | The `matches($type)` should test the given `$type` against what the adapter 546 | provides to determine if it can handle an authentication request. Typically, 547 | this can be done with `return in_array($type, $this->provides(), true);` 548 | 549 | The `getTypeFromRequest()` method can be used to match an incoming request to 550 | the authentication type it resolves, if any. Examples might be deconstructing 551 | the `Authorization` header, or a custom header such as `X-Api-Token`. 552 | 553 | The `preAuth()` method can be used to provide client challenges; typically, 554 | this will only ever be used by the included `HttpAdapter`. 555 | 556 | Finally, the `authenticate()` method is used to attempt to authenticate an 557 | incoming request. I should return either a boolean `false`, indicating 558 | authentictaion failed, or an instance of 559 | `ZF\MvcAuth\Identity\IdentityInterface`; if the latter is returned, that 560 | identity will be used for the duration of the request. 561 | 562 | Adapters are attached to the `DefaultAuthenticationListener`. To attach your 563 | custom adapter, you will need to do one of the following: 564 | 565 | - Define named HTTP and/or OAuth2 adapters via configuration. 566 | - During an event listener, pull your adapter and the 567 | `DefaultAuthenticationListener` services, and attach your adapter to the 568 | latter. 569 | - Create a `DelegatorFactory` for the `DefaultAuthenticationListener` that 570 | attaches your custom adapter before returning the listener. 571 | 572 | #### Defining named HTTP and/or OAuth2 adapters 573 | 574 | Since HTTP and OAuth2 support is built-in, `zf-mvc-auth` provides a 575 | configuration-driven approach for creating named adapters of these types. Each 576 | requires a unique key under the `zf-mvc-auth.authentication.adapters` 577 | configuration, and each type has its own format. 578 | 579 | ```php 580 | return [ 581 | /* ... */ 582 | 'zf-mvc-auth' => [ 583 | 'authentication' => [ 584 | 'adapters' => [ 585 | 'api' => [ 586 | // This defines an HTTP adapter that can satisfy both 587 | // basic and digest. 588 | 'adapter' => 'ZF\MvcAuth\Authentication\HttpAdapter', 589 | 'options' => [ 590 | 'accept_schemes' => ['basic', 'digest'], 591 | 'realm' => 'api', 592 | 'digest_domains' => 'https://example.com', 593 | 'nonce_timeout' => 3600, 594 | 'htpasswd' => 'data/htpasswd', 595 | 'htdigest' => 'data/htdigest', 596 | ], 597 | ], 598 | 'user' => [ 599 | // This defines an OAuth2 adapter backed by PDO. 600 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 601 | 'storage' => [ 602 | 'adapter' => 'pdo', 603 | 'dsn' => 'mysql:host=localhost;dbname=oauth2', 604 | 'username' => 'username', 605 | 'password' => 'password', 606 | 'options' => [ 607 | 1002 => 'SET NAMES utf8', // PDO::MYSQL_ATTR_INIT_COMMAND 608 | ], 609 | ], 610 | ], 611 | 'client' => [ 612 | // This defines an OAuth2 adapter backed by Mongo. 613 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 614 | 'storage' => [ 615 | 'adapter' => 'mongo', 616 | 'locator_name' => 'SomeServiceName', // If provided, pulls the given service 617 | 'dsn' => 'mongodb://localhost', 618 | 'database' => 'oauth2', 619 | 'options' => [ 620 | 'username' => 'username', 621 | 'password' => 'password', 622 | 'connectTimeoutMS' => 500, 623 | ], 624 | ], 625 | ], 626 | ], 627 | /* ... */ 628 | ], 629 | /* ... */ 630 | ], 631 | /* ... */ 632 | ]; 633 | ``` 634 | 635 | The above configuration would provide the authentication types 636 | `['api-basic', 'api-digest', 'user', 'client']` to your application, which 637 | can each them be associated in the authentication type map. 638 | 639 | If you use `zf-apigility-admin`'s Admin API and/or the Apigility UI to 640 | configure authentication adapters, the above configuration will be created for 641 | you. 642 | 643 | #### Attaching an adapter during an event listener 644 | 645 | The best event to attach to in this circumstances is the "authentication" event. 646 | When doing so, you'll want to attach at a priority > 1 to ensure it executes 647 | before the `DefaultAuthenticationListener`. 648 | 649 | In the following example, we'll assume you've defined a service named 650 | `MyCustomAuthenticationAdapter` that returns an `AdapterInterface` 651 | implementation, and that the class is the `Module` class of your API or a module 652 | in your application. 653 | 654 | ```php 655 | class Module 656 | { 657 | public function onBootstrap($e) 658 | { 659 | $app = $e->getApplication(); 660 | $events = $app->getEventManager(); 661 | $services = $app->getServiceManager(); 662 | 663 | $events->attach( 664 | 'authentication', 665 | function ($e) use ($services) { 666 | $listener = $services->get('ZF\MvcAuth\Authentication\DefaultAuthenticationListener') 667 | $adapter = $services->get('MyCustomAuthenticationAdapter'); 668 | $listener->attach($adapter); 669 | }, 670 | 1000 671 | ); 672 | } 673 | } 674 | ``` 675 | 676 | By returning nothing, the `DefaultAuthenticationListener` will continue to 677 | execute, but will now also have the new adapter attached. 678 | 679 | #### Using a delegator factory 680 | 681 | Delegator Factories are a way to "decorate" an instance returned by the Zend 682 | Framework `ServiceManager` in order to provide pre-conditions or alter the 683 | instance normally returned. In our case, we want to attach an adapter after the 684 | instance is created, but before it's returned. 685 | 686 | In the following example, we'll assume you've defined a service named 687 | `MyCustomAuthenticationAdapter` that returns an `AdapterInterface` 688 | implementation. The following is a delegator factory for the `DefaultAuthenticationListener` that will inject our adapter. 689 | 690 | ```php 691 | use Zend\ServiceManager\DelegatorFactoryInterface; 692 | use Zend\ServiceManager\ServiceLocatorInterface; 693 | 694 | class CustomAuthenticationDelegatorFactory implements DelegatorFactoryInterface 695 | { 696 | public function createDelegatorWithName( 697 | ServiceLocatorInterface $services, 698 | $name, 699 | $requestedName, 700 | $callback 701 | ) { 702 | $listener = $callback(); 703 | $listener->attach($services->get('MyCustomAuthenticationAdapter'); 704 | return $listener; 705 | } 706 | } 707 | ``` 708 | 709 | We then need to tell the `ServiceManager` about the delegator factory; we do this in our module's `config/module.config.php`, or one of the `config/autoload/` configuration files: 710 | 711 | ```php 712 | return [ 713 | /* ... */ 714 | 'service_manager' => [ 715 | /* ... */ 716 | 'delegators' => [ 717 | 'ZF\MvcAuth\Authentication\DefaultAuthenticationListener' => [ 718 | 'CustomAuthenticationDelegatorFactory', 719 | ], 720 | ], 721 | ], 722 | /* ... */ 723 | ]; 724 | ``` 725 | 726 | Once configured, our adapter will be attached to every instance of the `DefaultAuthenticationListener` that is retrieved. 727 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfcampus/zf-mvc-auth", 3 | "description": "ZF2 Module providing Authentication and Authorization events and infrastructure", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zendframework", 7 | "zf", 8 | "zend", 9 | "module" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/zfcampus/zf-mvc-auth/issues", 13 | "source": "https://github.com/zfcampus/zf-mvc-auth", 14 | "rss": "https://github.com/zfcampus/zf-mvc-auth/releases.atom", 15 | "chat": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/apigility" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0", 20 | "zendframework/zend-authentication": "^2.5.3", 21 | "zendframework/zend-eventmanager": "^2.6.3 || ^3.0.1", 22 | "zendframework/zend-http": "^2.5.4", 23 | "zendframework/zend-mvc": "^2.7.9 || ^3.0.2", 24 | "zendframework/zend-permissions-acl": "^2.6", 25 | "zendframework/zend-permissions-rbac": "^2.5.1 || ^3.0", 26 | "zendframework/zend-servicemanager": "^2.7.6 || ^3.1", 27 | "zendframework/zend-stdlib": "^2.7.7 || ^3.0.1", 28 | "zfcampus/zf-api-problem": "^1.2.1", 29 | "zfcampus/zf-content-negotiation": "^1.2.1", 30 | "zfcampus/zf-oauth2": "^1.4" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 34 | "zendframework/zend-coding-standard": "~1.0.0", 35 | "zendframework/zend-session": "^2.8.5" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "ZF\\MvcAuth\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "ZFTest\\MvcAuth\\": "test/" 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "extra": { 51 | "branch-alias": { 52 | "dev-master": "1.5.x-dev", 53 | "dev-develop": "1.6.x-dev" 54 | }, 55 | "zf": { 56 | "module": "ZF\\MvcAuth" 57 | } 58 | }, 59 | "scripts": { 60 | "check": [ 61 | "@cs-check", 62 | "@test" 63 | ], 64 | "cs-check": "phpcs", 65 | "cs-fix": "phpcbf", 66 | "test": "phpunit --colors=always", 67 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'aliases' => [ 15 | 'getidentity' => Identity\IdentityPlugin::class, 16 | 'getIdentity' => Identity\IdentityPlugin::class, 17 | ], 18 | 'factories' => [ 19 | Identity\IdentityPlugin::class => InvokableFactory::class, 20 | ], 21 | ], 22 | 'service_manager' => [ 23 | 'aliases' => [ 24 | 'authentication' => 'ZF\MvcAuth\Authentication', 25 | 'authorization' => Authorization\AuthorizationInterface::class, 26 | Authorization\AuthorizationInterface::class => Authorization\AclAuthorization::class, 27 | ], 28 | 'delegators' => [ 29 | Authentication\DefaultAuthenticationListener::class => [ 30 | Factory\AuthenticationAdapterDelegatorFactory::class, 31 | ], 32 | ], 33 | // @codingStandardsIgnoreStart 34 | 'factories' => [ 35 | 'ZF\MvcAuth\Authentication' => Factory\AuthenticationServiceFactory::class, 36 | 'ZF\MvcAuth\ApacheResolver' => Factory\ApacheResolverFactory::class, 37 | 'ZF\MvcAuth\FileResolver' => Factory\FileResolverFactory::class, 38 | Authentication\DefaultAuthenticationListener::class => Factory\DefaultAuthenticationListenerFactory::class, 39 | Authentication\AuthHttpAdapter::class => Factory\DefaultAuthHttpAdapterFactory::class, 40 | Authorization\AclAuthorization::class => Factory\AclAuthorizationFactory::class, 41 | Authorization\DefaultAuthorizationListener::class => Factory\DefaultAuthorizationListenerFactory::class, 42 | Authorization\DefaultResourceResolverListener::class => Factory\DefaultResourceResolverListenerFactory::class, 43 | 'ZF\OAuth2\Service\OAuth2Server' => Factory\NamedOAuth2ServerFactory::class, 44 | NonPersistent::class => InvokableFactory::class, 45 | Authentication\DefaultAuthenticationPostListener::class => InvokableFactory::class, 46 | Authorization\DefaultAuthorizationPostListener::class => InvokableFactory::class, 47 | 48 | ], 49 | // @codingStandardsIgnoreEnd 50 | ], 51 | 'zf-mvc-auth' => [ 52 | 'authentication' => [ 53 | /* First, we define authentication configuration types. These have 54 | * the keys: 55 | * - http 56 | * - oauth2 57 | * 58 | * Note: as of 1.1, these are deprecated. 59 | * 60 | 'http' => [ 61 | 'accept_schemes' => ['basic', 'digest'], 62 | 'realm' => 'My Web Site', 63 | 'digest_domains' => '/', 64 | 'nonce_timeout' => 3600, 65 | // htpasswd tool generated: 66 | 'htpasswd' => APPLICATION_PATH . '/data/htpasswd', 67 | // @see http://www.askapache.com/online-tools/htpasswd-generator/ 68 | 'htdigest' => APPLICATION_PATH . '/data/htdigest', 69 | // If this is set, the htpasswd key is ignored - see below 70 | 'basic_resolver_factory' => 'ServiceManagerKeyToAsk', 71 | // If this is set, the htdigest key is ignored - see below: 72 | 'digest_resolver_factory' => 'ServiceManagerKeyToAsk', 73 | ], 74 | * 75 | * Starting in 1.1, we have an "adapters" key, which is a key/value 76 | * pair of adapter name -> adapter configuration information. Each 77 | * adapter should name the ZF\MvcAuth\Authentication\AdapterInterface 78 | * type in the 'adapter' key. 79 | * 80 | * For HttpAdapter cases, specify an 'options' key with the options 81 | * to use to create the Zend\Authentication\Adapter\Http instance. 82 | * 83 | * Starting in 1.2, you can specify a resolver implementing the 84 | * Zend\Authentication\Adapter\Http\ResolverInterface that is passed 85 | * into the Zend\Authentication\Adapter\Http as either basic or digest 86 | * resolver. This allows you to implement your own method of authentication 87 | * instead of having to rely on the two default methods (ApacheResolver 88 | * for basic authentication and FileResolver for digest authentication, 89 | * both based on files). 90 | * 91 | * When you want to use this feature, use the "basic_resolver_factory" 92 | * key to get your custom resolver instance from the Zend service manager. 93 | * If this key is set and pointing to a valid entry in the service manager, 94 | * the entry "htpasswd" is ignored (unless you use it in your custom 95 | * factory to build the resolver). 96 | * 97 | * Using the "digest_resolver_factory" ignores the "htdigest" key in 98 | * the same way. 99 | * 100 | * For OAuth2Adapter instances, specify a 'storage' key, with options 101 | * to use for matching the adapter and creating an OAuth2 storage 102 | * instance. The array MUST contain a `route' key, with the route 103 | * at which the specific adapter will match authentication requests. 104 | * To specify the storage instance, you may use one of two approaches: 105 | * 106 | * - Specify a "storage" subkey pointing to a named service or an array 107 | * of named services to use. 108 | * - Specify an "adapter" subkey with the value "pdo" or "mongo", and 109 | * include additional subkeys for configuring a ZF\OAuth2\Adapter\PdoAdapter 110 | * or ZF\OAuth2\Adapter\MongoAdapter, accordingly. See the zf-oauth2 111 | * documentation for details. 112 | * 113 | * This looks like the following for the HTTP basic/digest and OAuth2 114 | * adapters: 115 | 'adapters' => [ 116 | // HTTP adapter 117 | 'api' => [ 118 | 'adapter' => 'ZF\MvcAuth\Authentication\HttpAdapter', 119 | 'options' => [ 120 | 'accept_schemes' => ['basic', 'digest'], 121 | 'realm' => 'api', 122 | 'digest_domains' => 'https://example.com', 123 | 'nonce_timeout' => 3600, 124 | 'htpasswd' => 'data/htpasswd', 125 | 'htdigest' => 'data/htdigest', 126 | // If this is set, the htpasswd key is ignored: 127 | 'basic_resolver_factory' => 'ServiceManagerKeyToAsk', 128 | // If this is set, the htdigest key is ignored: 129 | 'digest_resolver_factory' => 'ServiceManagerKeyToAsk', 130 | ], 131 | ], 132 | // OAuth2 adapter, using an "adapter" type of "pdo" 133 | 'user' => [ 134 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 135 | 'storage' => [ 136 | 'adapter' => 'pdo', 137 | 'route' => '/user', 138 | 'dsn' => 'mysql:host=localhost;dbname=oauth2', 139 | 'username' => 'username', 140 | 'password' => 'password', 141 | 'options' => [ 142 | 1002 => 'SET NAMES utf8', // PDO::MYSQL_ATTR_INIT_COMMAND 143 | ], 144 | ], 145 | ], 146 | // OAuth2 adapter, using an "adapter" type of "mongo" 147 | 'client' => [ 148 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 149 | 'storage' => [ 150 | 'adapter' => 'mongo', 151 | 'route' => '/client', 152 | 'locator_name' => 'SomeServiceName', // If provided, pulls the given service 153 | 'dsn' => 'mongodb://localhost', 154 | 'database' => 'oauth2', 155 | 'options' => [ 156 | 'username' => 'username', 157 | 'password' => 'password', 158 | 'connectTimeoutMS' => 500, 159 | ], 160 | ], 161 | ], 162 | // OAuth2 adapter, using a named "storage" service 163 | 'named-storage' => [ 164 | 'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter', 165 | 'storage' => [ 166 | 'storage' => 'Name\Of\An\OAuth2\Storage\Service', 167 | 'route' => '/named-storage', 168 | ], 169 | ], 170 | ], 171 | * 172 | * Next, we also have a "map", which maps an API module (with 173 | * optional version) to a given authentication type (one of basic, 174 | * digest, or oauth2): 175 | 'map' => [ 176 | 'ApiModuleName' => 'oauth2', 177 | 'OtherApi\V2' => 'basic', 178 | 'AnotherApi\V1' => 'digest', 179 | ], 180 | * 181 | * We also allow you to specify custom authentication types that you 182 | * support via listeners; by adding them to the configuration, you 183 | * ensure that they will be available for mapping modules to 184 | * authentication types in the Admin. 185 | 'types' => [ 186 | 'token', 187 | 'key', 188 | 'etc', 189 | ] 190 | */ 191 | ], 192 | 'authorization' => [ 193 | // Toggle the following to true to change the ACL creation to 194 | // require an authenticated user by default, and thus selectively 195 | // allow unauthenticated users based on the rules. 196 | 'deny_by_default' => false, 197 | 198 | /* 199 | * Rules indicating what controllers are behind authentication. 200 | * 201 | * Keys are controller service names. 202 | * 203 | * Values are arrays with either the key "actions" and/or one or 204 | * more of the keys "collection" and "entity". 205 | * 206 | * The "actions" key will be a set of action name/method pairs. 207 | * The "collection" and "entity" keys will have method values. 208 | * 209 | * Method values are arrays of HTTP method/boolean pairs. By 210 | * default, if an HTTP method is not present in the list, it is 211 | * assumed to be open (i.e., not require authentication). The 212 | * special key "default" can be used to set the default flag for 213 | * all HTTP methods. 214 | * 215 | 'Controller\Service\Name' => [ 216 | 'actions' => [ 217 | 'action' => [ 218 | 'default' => boolean, 219 | 'GET' => boolean, 220 | 'POST' => boolean, 221 | // etc. 222 | ], 223 | ], 224 | 'collection' => [ 225 | 'default' => boolean, 226 | 'GET' => boolean, 227 | 'POST' => boolean, 228 | // etc. 229 | ], 230 | 'entity' => [ 231 | 'default' => boolean, 232 | 'GET' => boolean, 233 | 'POST' => boolean, 234 | // etc. 235 | ], 236 | ], 237 | */ 238 | ], 239 | ], 240 | ]; 241 | -------------------------------------------------------------------------------- /src/Authentication/AbstractAdapter.php: -------------------------------------------------------------------------------- 1 | getHeaders(); 30 | $authorization = $request->getHeader('Authorization'); 31 | if (! $authorization) { 32 | return false; 33 | } 34 | 35 | $authorization = trim($authorization->getFieldValue()); 36 | $type = $this->getTypeFromAuthorizationHeader($authorization); 37 | 38 | if (! in_array($type, $this->authorizationTokenTypes)) { 39 | return false; 40 | } 41 | 42 | return $type; 43 | } 44 | 45 | /** 46 | * Determine the authentication type from the authorization header contents 47 | * 48 | * @param string $header 49 | * @return false|string 50 | */ 51 | private function getTypeFromAuthorizationHeader($header) 52 | { 53 | // we only support headers in the format: Authorization: xxx yyyyy 54 | if (strpos($header, ' ') === false) { 55 | return false; 56 | } 57 | 58 | list($type, $credential) = preg_split('# #', $header, 2); 59 | 60 | return strtolower($type); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Authentication/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | adapters[] = $adapter; 65 | $this->authenticationTypes = array_unique(array_merge($this->authenticationTypes, $adapter->provides())); 66 | } 67 | 68 | /** 69 | * Add custom authentication types. 70 | * 71 | * This method allows specifiying additional authentication types, outside 72 | * of adapters, that your application supports. The values provided are 73 | * merged with any types already discovered. 74 | * 75 | * @param array $types 76 | */ 77 | public function addAuthenticationTypes(array $types) 78 | { 79 | $this->authenticationTypes = array_unique( 80 | array_merge( 81 | $this->authenticationTypes, 82 | $types 83 | ) 84 | ); 85 | } 86 | 87 | /** 88 | * Retrieve the supported authentication types 89 | * 90 | * @return array 91 | */ 92 | public function getAuthenticationTypes() 93 | { 94 | if ($this->httpAdapter instanceof HttpAuth) { 95 | // Legacy purposes only. We cannot merge the actual HttpAdapter instance 96 | // until we have the MvcAuthEvent (and thus the AuthenticationService), 97 | // so if an HttpAuth instance was directly attached, and this method is 98 | // queried before invocation, we report both basic and digest as being 99 | // available. 100 | return array_unique(array_merge( 101 | $this->authenticationTypes, 102 | ['basic', 'digest'] 103 | )); 104 | } 105 | return $this->authenticationTypes; 106 | } 107 | 108 | /** 109 | * Set the HTTP authentication adapter 110 | * 111 | * This method is deprecated; create and attach an HttpAdapter instead. 112 | * 113 | * @deprecated 114 | * @param HttpAuth $httpAdapter 115 | * @return self 116 | */ 117 | public function setHttpAdapter(HttpAuth $httpAdapter) 118 | { 119 | $this->httpAdapter = $httpAdapter; 120 | return $this; 121 | } 122 | 123 | /** 124 | * Set the OAuth2 server 125 | * 126 | * This method is deprecated; create and attach an OAuth2Adapter instead. 127 | * 128 | * @deprecated 129 | * @param OAuth2Server $oauth2Server 130 | * @return self 131 | */ 132 | public function setOauth2Server(OAuth2Server $oauth2Server) 133 | { 134 | $this->attach(new OAuth2Adapter($oauth2Server)); 135 | return $this; 136 | } 137 | 138 | /** 139 | * Set the API/version to authentication type map. 140 | * 141 | * @param array $map 142 | */ 143 | public function setAuthMap(array $map) 144 | { 145 | $this->authMap = $map; 146 | } 147 | 148 | /** 149 | * Listen to the authentication event 150 | * 151 | * @param MvcAuthEvent $mvcAuthEvent 152 | * @return null|Identity\IdentityInterface 153 | */ 154 | public function __invoke(MvcAuthEvent $mvcAuthEvent) 155 | { 156 | $this->attachHttpAdapter($mvcAuthEvent); 157 | 158 | $mvcEvent = $mvcAuthEvent->getMvcEvent(); 159 | $request = $mvcEvent->getRequest(); 160 | $response = $mvcEvent->getResponse(); 161 | 162 | if (! $request instanceof HttpRequest 163 | || $request->isOptions() 164 | ) { 165 | return; 166 | } 167 | 168 | $type = $this->getTypeFromMap($mvcEvent->getRouteMatch()); 169 | if (false === $type && count($this->adapters) > 1) { 170 | // Ambiguous situation; no matching type in map, but multiple 171 | // authentication adapters; return a guest identity. 172 | $identity = new Identity\GuestIdentity(); 173 | $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity); 174 | return $identity; 175 | } 176 | 177 | $type = $type ?: $this->getTypeFromRequest($request); 178 | if (false === $type) { 179 | // No authentication type known; trigger any pre-flight actions, 180 | // and return a guest identity. 181 | $this->triggerAdapterPreAuth($request, $response); 182 | $identity = new Identity\GuestIdentity(); 183 | $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity); 184 | return $identity; 185 | } 186 | 187 | // Authenticate against first matching adapter 188 | $identity = $this->authenticate($type, $request, $response, $mvcAuthEvent); 189 | 190 | // If the adapter returns a response instance, return it directly. 191 | if ($identity instanceof HttpResponse) { 192 | return $identity; 193 | } 194 | 195 | // If no identity returned, create a guest identity 196 | if (! $identity instanceof Identity\IdentityInterface) { 197 | $identity = new Identity\GuestIdentity(); 198 | } 199 | 200 | $mvcEvent->setParam('ZF\MvcAuth\Identity', $identity); 201 | return $identity; 202 | } 203 | 204 | /** 205 | * Match the controller to an authentication type, based on the API to 206 | * which the controller belongs. 207 | * 208 | * @param null|V2RouteMatch|RouteMatch $routeMatch 209 | * @return string|false 210 | */ 211 | private function getTypeFromMap($routeMatch = null) 212 | { 213 | if (null === $routeMatch) { 214 | return false; 215 | } 216 | 217 | if (! ($routeMatch instanceof RouteMatch || $routeMatch instanceof V2RouteMatch)) { 218 | throw new InvalidArgumentException(sprintf( 219 | '%s expected either a %s or %s; received %s', 220 | __METHOD__, 221 | RouteMatch::class, 222 | V2RouteMatch::class, 223 | (is_object($routeMatch) ? get_class($routeMatch) : gettype($routeMatch)) 224 | )); 225 | } 226 | 227 | $controller = $routeMatch->getParam('controller', false); 228 | if (false === $controller) { 229 | return false; 230 | } 231 | 232 | foreach ($this->authMap as $api => $type) { 233 | $api = rtrim($api, '\\') . '\\'; 234 | if (strlen($api) > strlen($controller)) { 235 | continue; 236 | } 237 | 238 | if (0 === strpos($controller, $api)) { 239 | return $type; 240 | } 241 | } 242 | 243 | return false; 244 | } 245 | 246 | /** 247 | * Determine the authentication type based on request information 248 | * 249 | * @param HttpRequest $request 250 | * @return false|string 251 | */ 252 | private function getTypeFromRequest(HttpRequest $request) 253 | { 254 | foreach ($this->adapters as $adapter) { 255 | $type = $adapter->getTypeFromRequest($request); 256 | if (false !== $type) { 257 | return $type; 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | /** 264 | * Trigger the preAuth routine of each adapter 265 | * 266 | * This method is triggered if no authentication type was discovered in the 267 | * request. 268 | * 269 | * @param HttpRequest $request 270 | * @param HttpResponse $response 271 | */ 272 | private function triggerAdapterPreAuth(HttpRequest $request, HttpResponse $response) 273 | { 274 | foreach ($this->adapters as $adapter) { 275 | $adapter->preAuth($request, $response); 276 | } 277 | } 278 | 279 | /** 280 | * Invoke the adapter matching the given $type in order to peform authentication 281 | * 282 | * @param string $type 283 | * @param HttpRequest $request 284 | * @param HttpResponse $response 285 | * @param MvcAuthEvent $mvcAuthEvent 286 | * @return false|Identity\IdentityInterface 287 | */ 288 | private function authenticate($type, HttpRequest $request, HttpResponse $response, MvcAuthEvent $mvcAuthEvent) 289 | { 290 | foreach ($this->adapters as $adapter) { 291 | if (! $adapter->matches($type)) { 292 | continue; 293 | } 294 | 295 | return $adapter->authenticate($request, $response, $mvcAuthEvent); 296 | } 297 | 298 | return false; 299 | } 300 | 301 | /** 302 | * Attach the $httpAdapter as a proper adapter 303 | * 304 | * This is to allow using the setHttpAdapter() method along with the 305 | * AdapterInterface system, and will be removed in a future version. 306 | * 307 | * @deprecated 308 | * @param MvcAuthEvent $mvcAuthEvent 309 | */ 310 | private function attachHttpAdapter(MvcAuthEvent $mvcAuthEvent) 311 | { 312 | if (! $this->httpAdapter instanceof HttpAuth) { 313 | return; 314 | } 315 | 316 | $this->attach(new HttpAdapter($this->httpAdapter, $mvcAuthEvent->getAuthenticationService())); 317 | $this->httpAdapter = null; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/Authentication/DefaultAuthenticationPostListener.php: -------------------------------------------------------------------------------- 1 | hasAuthenticationResult()) { 23 | return; 24 | } 25 | 26 | $authResult = $mvcAuthEvent->getAuthenticationResult(); 27 | if ($authResult->isValid()) { 28 | return; 29 | } 30 | 31 | $mvcEvent = $mvcAuthEvent->getMvcEvent(); 32 | $response = $mvcEvent->getResponse(); 33 | if (! $response instanceof HttpResponse) { 34 | return $response; 35 | } 36 | 37 | $response->setStatusCode(401); 38 | $response->setReasonPhrase('Unauthorized'); 39 | return $response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Authentication/HttpAdapter.php: -------------------------------------------------------------------------------- 1 | httpAuth = $httpAuth; 53 | $this->authenticationService = $authenticationService; 54 | 55 | if (is_string($providesBase) && ! empty($providesBase)) { 56 | $this->providesBase = $providesBase; 57 | } 58 | } 59 | 60 | /** 61 | * Returns the "types" this adapter can handle. 62 | * 63 | * If no $providesBase is present, returns "basic" and/or "digest" in the 64 | * array, based on what resolvers are present in the adapter; if 65 | * $providesBase is present, the same strings are returned, only with the 66 | * $providesBase prefixed, along with a "-" separator. 67 | * 68 | * @return array Array of types this adapter can handle. 69 | */ 70 | public function provides() 71 | { 72 | $providesBase = $this->providesBase ? $this->providesBase . '-' : ''; 73 | $provides = []; 74 | 75 | if ($this->httpAuth->getBasicResolver()) { 76 | $provides[] = $providesBase . 'basic'; 77 | } 78 | 79 | if ($this->httpAuth->getDigestResolver()) { 80 | $provides[] = $providesBase . 'digest'; 81 | } 82 | 83 | return $provides; 84 | } 85 | 86 | /** 87 | * Match the requested authentication type against what we provide. 88 | * 89 | * @param string $type 90 | * @return bool 91 | */ 92 | public function matches($type) 93 | { 94 | return ($this->providesBase === $type || in_array($type, $this->provides(), true)); 95 | } 96 | 97 | /** 98 | * Perform pre-flight authentication operations. 99 | * 100 | * If invoked, issues a client challenge. 101 | * 102 | * @param Request $request 103 | * @param Response $response 104 | */ 105 | public function preAuth(Request $request, Response $response) 106 | { 107 | $this->httpAuth->setRequest($request); 108 | $this->httpAuth->setResponse($response); 109 | $this->httpAuth->challengeClient(); 110 | } 111 | 112 | /** 113 | * Attempt to authenticate the current request. 114 | * 115 | * @param Request $request 116 | * @param Response $response 117 | * @param MvcAuthEvent $mvcAuthEvent 118 | * @return false|Identity\IdentityInterface False on failure, IdentityInterface 119 | * otherwise 120 | */ 121 | public function authenticate(Request $request, Response $response, MvcAuthEvent $mvcAuthEvent) 122 | { 123 | if (! $request->getHeader('Authorization', false)) { 124 | // No credentials were present at all, so we just return a guest identity. 125 | return new Identity\GuestIdentity(); 126 | } 127 | 128 | $this->httpAuth->setRequest($request); 129 | $this->httpAuth->setResponse($response); 130 | 131 | $result = $this->authenticationService->authenticate($this->httpAuth); 132 | $mvcAuthEvent->setAuthenticationResult($result); 133 | 134 | if (! $result->isValid()) { 135 | return false; 136 | } 137 | 138 | $resultIdentity = $result->getIdentity(); 139 | 140 | // Pass fully discovered identity to AuthenticatedIdentity instance 141 | $identity = new Identity\AuthenticatedIdentity($resultIdentity); 142 | 143 | // But determine the name separately 144 | $name = $resultIdentity; 145 | if (is_array($resultIdentity)) { 146 | $name = isset($resultIdentity['username']) 147 | ? $resultIdentity['username'] 148 | : (string) array_shift($resultIdentity); 149 | } 150 | $identity->setName($name); 151 | 152 | return $identity; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Authentication/OAuth2Adapter.php: -------------------------------------------------------------------------------- 1 | oauth2Server = $oauth2Server; 55 | 56 | if (is_string($types) && ! empty($types)) { 57 | $types = [$types]; 58 | } 59 | 60 | if (is_array($types)) { 61 | $this->providesTypes = $types; 62 | } 63 | } 64 | 65 | /** 66 | * @return array Array of types this adapter can handle. 67 | */ 68 | public function provides() 69 | { 70 | return $this->providesTypes; 71 | } 72 | 73 | /** 74 | * Attempt to match a requested authentication type 75 | * against what the adapter provides. 76 | * 77 | * @param string $type 78 | * @return bool 79 | */ 80 | public function matches($type) 81 | { 82 | return in_array($type, $this->providesTypes, true); 83 | } 84 | 85 | /** 86 | * Determine if the given request is a type (oauth2) that we recognize 87 | * 88 | * @param Request $request 89 | * @return false|string 90 | */ 91 | public function getTypeFromRequest(Request $request) 92 | { 93 | $type = parent::getTypeFromRequest($request); 94 | 95 | if (false !== $type) { 96 | return 'oauth2'; 97 | } 98 | 99 | if (! in_array($request->getMethod(), $this->requestsWithoutBodies) 100 | && $request->getHeaders()->has('Content-Type') 101 | && $request->getHeaders()->get('Content-Type')->match('application/x-www-form-urlencoded') 102 | && $request->getPost('access_token') 103 | ) { 104 | return 'oauth2'; 105 | } 106 | 107 | if (null !== $request->getQuery('access_token')) { 108 | return 'oauth2'; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | /** 115 | * Perform pre-flight authentication operations. 116 | * 117 | * Performs a no-op; nothing needs to happen for this adapter. 118 | * 119 | * @param Request $request 120 | * @param Response $response 121 | */ 122 | public function preAuth(Request $request, Response $response) 123 | { 124 | } 125 | 126 | /** 127 | * Attempt to authenticate the current request. 128 | * 129 | * @param Request $request 130 | * @param Response $response 131 | * @param MvcAuthEvent $mvcAuthEvent 132 | * @return false|Identity\IdentityInterface False on failure, IdentityInterface 133 | * otherwise 134 | */ 135 | public function authenticate(Request $request, Response $response, MvcAuthEvent $mvcAuthEvent) 136 | { 137 | $oauth2request = new OAuth2Request( 138 | $request->getQuery()->toArray(), 139 | $request->getPost()->toArray(), 140 | [], 141 | ($request->getCookie() ? $request->getCookie()->getArrayCopy() : []), 142 | ($request->getFiles() ? $request->getFiles()->toArray() : []), 143 | (method_exists($request, 'getServer') ? $request->getServer()->toArray() : $_SERVER), 144 | $request->getContent(), 145 | $request->getHeaders()->toArray() 146 | ); 147 | 148 | $token = $this->oauth2Server->getAccessTokenData($oauth2request); 149 | 150 | // Failure to validate 151 | if (! $token) { 152 | return $this->processInvalidToken($response); 153 | } 154 | 155 | $identity = new Identity\AuthenticatedIdentity($token); 156 | $identity->setName($token['user_id']); 157 | 158 | return $identity; 159 | } 160 | 161 | /** 162 | * Handle a invalid Token. 163 | * 164 | * @param $response 165 | * @return Response|Identity\GuestIdentity 166 | */ 167 | private function processInvalidToken($response) 168 | { 169 | $oauth2Response = $this->oauth2Server->getResponse(); 170 | $status = $oauth2Response->getStatusCode(); 171 | 172 | // 401 or 403 mean invalid credentials or unauthorized scopes; report those. 173 | if (in_array($status, [401, 403], true) && null !== $oauth2Response->getParameter('error')) { 174 | return $this->mergeOAuth2Response($status, $response, $oauth2Response); 175 | } 176 | 177 | // Merge in any headers; typically sets a WWW-Authenticate header. 178 | $this->mergeOAuth2ResponseHeaders($response, $oauth2Response->getHttpHeaders()); 179 | 180 | // Otherwise, no credentials were present at all, so we just return a guest identity. 181 | return new Identity\GuestIdentity(); 182 | } 183 | 184 | /** 185 | * Merge the OAuth2\Response instance's status and headers into the current Zend\Http\Response. 186 | * 187 | * @param int $status 188 | * @param Response $response 189 | * @param OAuth2Response $oauth2Response 190 | * @return Response 191 | */ 192 | private function mergeOAuth2Response($status, Response $response, OAuth2Response $oauth2Response) 193 | { 194 | $response->setStatusCode($status); 195 | return $this->mergeOAuth2ResponseHeaders($response, $oauth2Response->getHttpHeaders()); 196 | } 197 | 198 | /** 199 | * Merge the OAuth2\Response headers into the current Zend\Http\Response. 200 | * 201 | * @param Response $response 202 | * @param array $oauth2Headers 203 | * @return Response 204 | */ 205 | private function mergeOAuth2ResponseHeaders(Response $response, array $oauth2Headers) 206 | { 207 | if (empty($oauth2Headers)) { 208 | return $response; 209 | } 210 | 211 | $headers = $response->getHeaders(); 212 | foreach ($oauth2Headers as $header => $value) { 213 | $headers->addHeaderLine($header, $value); 214 | } 215 | 216 | return $response; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Authorization/AclAuthorization.php: -------------------------------------------------------------------------------- 1 | hasResource($resource))) { 30 | $this->addResource($resource); 31 | } 32 | 33 | if (! $this->hasRole($identity)) { 34 | $this->addRole($identity); 35 | } 36 | 37 | return $this->isAllowed($identity, $resource, $privilege); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Authorization/AclAuthorizationFactory.php: -------------------------------------------------------------------------------- 1 | addRole('guest'); 29 | $acl->allow(); 30 | 31 | $grant = 'deny'; 32 | if ($denyByDefault) { 33 | $acl->deny('guest', null, null); 34 | $grant = 'allow'; 35 | } 36 | 37 | if (! empty($config)) { 38 | return self::injectGrants($acl, $grant, $config); 39 | } 40 | 41 | return $acl; 42 | } 43 | 44 | /** 45 | * Inject the ACL with the grants specified in the collection of rules. 46 | * 47 | * @param AclAuthorization $acl 48 | * @param string $grantType Either "allow" or "deny". 49 | * @param array $rules 50 | * @return AclAuthorization 51 | */ 52 | private static function injectGrants(AclAuthorization $acl, $grantType, array $rules) 53 | { 54 | foreach ($rules as $set) { 55 | if (! is_array($set) || ! isset($set['resource'])) { 56 | continue; 57 | } 58 | 59 | self::injectGrant($acl, $grantType, $set); 60 | } 61 | 62 | return $acl; 63 | } 64 | 65 | /** 66 | * Inject the ACL with the grant specified by a single rule set. 67 | * 68 | * @param AclAuthorization $acl 69 | * @param string $grantType 70 | * @param array $ruleSet 71 | * @return void 72 | */ 73 | private static function injectGrant(AclAuthorization $acl, $grantType, array $ruleSet) 74 | { 75 | // Add new resource to ACL 76 | $resource = $ruleSet['resource']; 77 | $acl->addResource($ruleSet['resource']); 78 | 79 | // Deny guest specified privileges to resource 80 | $privileges = isset($ruleSet['privileges']) ? $ruleSet['privileges'] : null; 81 | 82 | // null privileges means no permissions were setup; nothing to do 83 | if (null === $privileges) { 84 | return; 85 | } 86 | 87 | $acl->$grantType('guest', $resource, $privileges); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Authorization/AuthorizationInterface.php: -------------------------------------------------------------------------------- 1 | authorization = $authorization; 29 | } 30 | 31 | /** 32 | * Attempt to authorize the discovered identity based on the ACLs present 33 | * 34 | * @param MvcAuthEvent $mvcAuthEvent 35 | * @return bool 36 | */ 37 | public function __invoke(MvcAuthEvent $mvcAuthEvent) 38 | { 39 | if ($mvcAuthEvent->isAuthorized()) { 40 | return; 41 | } 42 | 43 | $mvcEvent = $mvcAuthEvent->getMvcEvent(); 44 | 45 | $request = $mvcEvent->getRequest(); 46 | if (! $request instanceof Request) { 47 | return; 48 | } 49 | 50 | $response = $mvcEvent->getResponse(); 51 | if (! $response instanceof Response) { 52 | return; 53 | } 54 | 55 | $routeMatch = $mvcEvent->getRouteMatch(); 56 | if (! ($routeMatch instanceof RouteMatch || $routeMatch instanceof V2RouteMatch)) { 57 | return; 58 | } 59 | 60 | $identity = $mvcAuthEvent->getIdentity(); 61 | if (! $identity instanceof IdentityInterface) { 62 | return; 63 | } 64 | 65 | $resource = $mvcAuthEvent->getResource(); 66 | $identity = $mvcAuthEvent->getIdentity(); 67 | return $this->authorization->isAuthorized($identity, $resource, $request->getMethod()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Authorization/DefaultAuthorizationPostListener.php: -------------------------------------------------------------------------------- 1 | getMvcEvent(); 23 | $response = $mvcEvent->getResponse(); 24 | 25 | if ($mvcAuthEvent->isAuthorized()) { 26 | if ($response instanceof HttpResponse) { 27 | if ($response->getStatusCode() != 200) { 28 | $response->setStatusCode(200); 29 | } 30 | } 31 | return; 32 | } 33 | 34 | if (! $response instanceof HttpResponse) { 35 | return $response; 36 | } 37 | 38 | $response->setStatusCode(403); 39 | $response->setReasonPhrase('Forbidden'); 40 | return $response; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Authorization/DefaultResourceResolverListener.php: -------------------------------------------------------------------------------- 1 | restControllers = $restControllers; 28 | } 29 | 30 | /** 31 | * Attempt to determine the authorization resource based on the request 32 | * 33 | * Looks at the matched controller. 34 | * 35 | * If the controller is in the list of rest controllers, determines if we 36 | * have a collection or a resource, based on the presence of the named 37 | * identifier in the route matches or query string. 38 | * 39 | * Otherwise, looks for the presence of an "action" parameter in the route 40 | * matches. 41 | * 42 | * Once created, it is injected into the $mvcAuthEvent. 43 | * 44 | * @param MvcAuthEvent $mvcAuthEvent 45 | */ 46 | public function __invoke(MvcAuthEvent $mvcAuthEvent) 47 | { 48 | $mvcEvent = $mvcAuthEvent->getMvcEvent(); 49 | $request = $mvcEvent->getRequest(); 50 | $routeMatch = $mvcEvent->getRouteMatch(); 51 | 52 | $resource = $this->buildResourceString($routeMatch, $request); 53 | if (! $resource) { 54 | return; 55 | } 56 | 57 | $mvcAuthEvent->setResource($resource); 58 | } 59 | 60 | /** 61 | * Creates a resource string based on the controller service name and type 62 | * 63 | * For REST services (those passed to the constructor), it returns one of: 64 | * 65 | * - ::entity 66 | * - ::collection 67 | * 68 | * For all others, it uses the "action" route match parameter: 69 | * 70 | * - :: 71 | * 72 | * If it cannot resolve a controller service name, boolean false is returned. 73 | * 74 | * @param RouteMatch|V2RouteMatch $routeMatch 75 | * @param \Zend\Stdlib\RequestInterface $request 76 | * @return false|string 77 | */ 78 | public function buildResourceString($routeMatch, $request) 79 | { 80 | if (! ($routeMatch instanceof RouteMatch || $routeMatch instanceof V2RouteMatch)) { 81 | throw new InvalidArgumentException(sprintf( 82 | '%s expected either a %s or %s; received %s', 83 | __METHOD__, 84 | RouteMatch::class, 85 | V2RouteMatch::class, 86 | (is_object($routeMatch) ? get_class($routeMatch) : gettype($routeMatch)) 87 | )); 88 | } 89 | 90 | // Considerations: 91 | // - We want the controller service name 92 | $controller = $routeMatch->getParam('controller', false); 93 | if (! $controller) { 94 | return false; 95 | } 96 | 97 | // - Is this an RPC or a REST call? 98 | // - Basically, if it's not in the zf-rest configuration, we assume RPC 99 | if (! array_key_exists($controller, $this->restControllers)) { 100 | $action = $routeMatch->getParam('action', 'index'); 101 | return sprintf('%s::%s', $controller, $action); 102 | } 103 | 104 | // - If it is a REST controller, we need to know if we have a 105 | // resource or a controller. The way to determine that is if we have 106 | // an identifier. We find that info from the route parameters. 107 | $identifierName = $this->restControllers[$controller]; 108 | $id = $this->getIdentifier($identifierName, $routeMatch, $request); 109 | if ($id !== false) { 110 | return sprintf('%s::entity', $controller); 111 | } 112 | return sprintf('%s::collection', $controller); 113 | } 114 | 115 | /** 116 | * Attempt to retrieve the identifier for a given request 117 | * 118 | * Checks first if the $identifierName is in the route matches, and then 119 | * as a query string parameter. 120 | * 121 | * @param string $identifierName 122 | * @param RouteMatch|V2RouteMatch $routeMatch Validated by calling method. 123 | * @param \Zend\Stdlib\RequestInterface $request 124 | * @return false|mixed 125 | */ 126 | protected function getIdentifier($identifierName, $routeMatch, $request) 127 | { 128 | $id = $routeMatch->getParam($identifierName, false); 129 | if ($id !== false) { 130 | return $id; 131 | } 132 | 133 | if (! $request instanceof Request) { 134 | return false; 135 | } 136 | 137 | return $request->getQuery($identifierName, false); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Factory/AclAuthorizationFactory.php: -------------------------------------------------------------------------------- 1 | true, 26 | Request::METHOD_GET => true, 27 | Request::METHOD_PATCH => true, 28 | Request::METHOD_POST => true, 29 | Request::METHOD_PUT => true, 30 | ]; 31 | 32 | /** 33 | * Create and return an AclAuthorization instance. 34 | * 35 | * @param ContainerInterface $container 36 | * @param string $requestedName 37 | * @param null|array $options 38 | * @return AclAuthorization 39 | */ 40 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) 41 | { 42 | $config = $this->getConfigFromContainer($container); 43 | return $this->createAclFromConfig($config); 44 | } 45 | 46 | /** 47 | * Create the AclAuthorization (v2). 48 | * 49 | * Provided for backwards compatibility; proxies to __invoke(). 50 | * 51 | * @param ContainerInterface|ServiceLocatorInterface $container 52 | * @return AclAuthorization 53 | */ 54 | public function createService(ServiceLocatorInterface $container) 55 | { 56 | return $this($container, AclAuthorization::class); 57 | } 58 | 59 | /** 60 | * Generate the ACL instance based on the zf-mvc-auth "authorization" configuration 61 | * 62 | * Consumes the AclFactory in order to create the AclAuthorization instance. 63 | * 64 | * @param array $config 65 | * @return AclAuthorization 66 | */ 67 | protected function createAclFromConfig(array $config) 68 | { 69 | $aclConfig = []; 70 | $denyByDefault = false; 71 | 72 | if (array_key_exists('deny_by_default', $config)) { 73 | $denyByDefault = $aclConfig['deny_by_default'] = (bool) $config['deny_by_default']; 74 | unset($config['deny_by_default']); 75 | } 76 | 77 | foreach ($config as $controllerService => $privileges) { 78 | $this->createAclConfigFromPrivileges($controllerService, $privileges, $aclConfig, $denyByDefault); 79 | } 80 | 81 | return AclFactory::factory($aclConfig); 82 | } 83 | 84 | /** 85 | * Creates ACL configuration based on the privileges configured 86 | * 87 | * - Extracts a privilege per action 88 | * - Extracts privileges for each of "collection" and "entity" configured 89 | * 90 | * @param string $controllerService 91 | * @param array $privileges 92 | * @param array $aclConfig 93 | * @param bool $denyByDefault 94 | */ 95 | protected function createAclConfigFromPrivileges($controllerService, array $privileges, &$aclConfig, $denyByDefault) 96 | { 97 | // Normalize the controller service name. 98 | // zend-mvc will always pass the name using namespace seprators, but 99 | // the admin may write the name using dash seprators. 100 | $controllerService = strtr($controllerService, '-', '\\'); 101 | if (isset($privileges['actions'])) { 102 | foreach ($privileges['actions'] as $action => $methods) { 103 | $action = lcfirst($action); 104 | $aclConfig[] = [ 105 | 'resource' => sprintf('%s::%s', $controllerService, $action), 106 | 'privileges' => $this->createPrivilegesFromMethods($methods, $denyByDefault), 107 | ]; 108 | } 109 | } 110 | 111 | if (isset($privileges['collection'])) { 112 | $aclConfig[] = [ 113 | 'resource' => sprintf('%s::collection', $controllerService), 114 | 'privileges' => $this->createPrivilegesFromMethods($privileges['collection'], $denyByDefault), 115 | ]; 116 | } 117 | 118 | if (isset($privileges['entity'])) { 119 | $aclConfig[] = [ 120 | 'resource' => sprintf('%s::entity', $controllerService), 121 | 'privileges' => $this->createPrivilegesFromMethods($privileges['entity'], $denyByDefault), 122 | ]; 123 | } 124 | } 125 | 126 | /** 127 | * Create the list of HTTP methods defining privileges 128 | * 129 | * @param array $methods 130 | * @param bool $denyByDefault 131 | * @return array|null 132 | */ 133 | protected function createPrivilegesFromMethods(array $methods, $denyByDefault) 134 | { 135 | $privileges = []; 136 | 137 | if (isset($methods['default']) && $methods['default']) { 138 | $privileges = $this->httpMethods; 139 | unset($methods['default']); 140 | } 141 | 142 | foreach ($methods as $method => $flag) { 143 | // If the flag evaluates true and we're denying by default, OR 144 | // if the flag evaluates false and we're allowing by default, 145 | // THEN no rule needs to be added 146 | if (( $denyByDefault && $flag) 147 | || (! $denyByDefault && ! $flag) 148 | ) { 149 | if (isset($privileges[$method])) { 150 | unset($privileges[$method]); 151 | } 152 | continue; 153 | } 154 | 155 | // Otherwise, we need to add a rule 156 | $privileges[$method] = true; 157 | } 158 | 159 | if (empty($privileges)) { 160 | return null; 161 | } 162 | 163 | return array_keys($privileges); 164 | } 165 | 166 | /** 167 | * Retrieve configuration from the container. 168 | * 169 | * Attempts to pull the 'config' service, and, further, the 170 | * zf-mvc-auth.authorization segment. 171 | * 172 | * @param ContainerInterface $container 173 | * @return array 174 | */ 175 | private function getConfigFromContainer(ContainerInterface $container) 176 | { 177 | if (! $container->has('config')) { 178 | return []; 179 | } 180 | 181 | $config = $container->get('config'); 182 | 183 | if (! isset($config['zf-mvc-auth']['authorization'])) { 184 | return []; 185 | } 186 | 187 | return $config['zf-mvc-auth']['authorization']; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/Factory/ApacheResolverFactory.php: -------------------------------------------------------------------------------- 1 | has('config')) { 28 | return false; 29 | } 30 | 31 | $config = $container->get('config'); 32 | 33 | if (! isset($config['zf-mvc-auth']['authentication']['http']['htpasswd'])) { 34 | return false; 35 | } 36 | 37 | $htpasswd = $config['zf-mvc-auth']['authentication']['http']['htpasswd']; 38 | 39 | return new ApacheResolver($htpasswd); 40 | } 41 | 42 | /** 43 | * Create and return an ApacheResolve instance (v2). 44 | * 45 | * Exists for backwards compatibility only; proxies to __invoke(). 46 | * 47 | * @param ServiceLocatorInterface $container 48 | * @return false|ApacheResolver 49 | */ 50 | public function createService(ServiceLocatorInterface $container) 51 | { 52 | return $this($container, ApacheResolver::class); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Factory/AuthenticationAdapterDelegatorFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 33 | if (! isset($config['zf-mvc-auth']['authentication']['adapters']) 34 | || ! is_array($config['zf-mvc-auth']['authentication']['adapters']) 35 | ) { 36 | return $listener; 37 | } 38 | 39 | foreach ($config['zf-mvc-auth']['authentication']['adapters'] as $type => $data) { 40 | $this->attachAdapterOfType($type, $data, $container, $listener); 41 | } 42 | 43 | return $listener; 44 | } 45 | 46 | /** 47 | * Decorate the DefaultAuthenticationListener (v2) 48 | * 49 | * Provided for backwards compatibility; proxies to __invoke(). 50 | * 51 | * @param ServiceLocatorInterface $container 52 | * @param string $name 53 | * @param string $requestedName 54 | * @param callable $callback 55 | * @return DefaultAuthenticationListener 56 | */ 57 | public function createDelegatorWithName(ServiceLocatorInterface $container, $name, $requestedName, $callback) 58 | { 59 | return $this($container, $requestedName, $callback); 60 | } 61 | 62 | /** 63 | * Attach an adaper to the listener as described by $type and $data. 64 | * 65 | * @param string $type 66 | * @param array $adapterConfig 67 | * @param ContainerInterface $container 68 | * @param DefaultAuthenticationListener $listener 69 | */ 70 | private function attachAdapterOfType( 71 | $type, 72 | array $adapterConfig, 73 | ContainerInterface $container, 74 | DefaultAuthenticationListener $listener 75 | ) { 76 | if (! isset($adapterConfig['adapter']) 77 | || ! is_string($adapterConfig['adapter']) 78 | ) { 79 | return; 80 | } 81 | 82 | switch ($adapterConfig['adapter']) { 83 | case HttpAdapter::class: 84 | $adapter = AuthenticationHttpAdapterFactory::factory($type, $adapterConfig, $container); 85 | break; 86 | case OAuth2Adapter::class: 87 | $adapter = AuthenticationOAuth2AdapterFactory::factory($type, $adapterConfig, $container); 88 | break; 89 | default: 90 | $adapter = false; 91 | break; 92 | } 93 | 94 | if (! $adapter) { 95 | return; 96 | } 97 | 98 | $listener->attach($adapter); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Factory/AuthenticationHttpAdapterFactory.php: -------------------------------------------------------------------------------- 1 | has('authentication')) { 33 | throw new ServiceNotCreatedException( 34 | 'Cannot create HTTP authentication adapter; missing AuthenticationService' 35 | ); 36 | } 37 | 38 | if (! isset($config['options']) || ! is_array($config['options'])) { 39 | throw new ServiceNotCreatedException( 40 | 'Cannot create HTTP authentication adapter; missing options' 41 | ); 42 | } 43 | 44 | return new HttpAdapter( 45 | HttpAdapterFactory::factory($config['options'], $container), 46 | $container->get('authentication'), 47 | $type 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Factory/AuthenticationOAuth2AdapterFactory.php: -------------------------------------------------------------------------------- 1 | get(NonPersistent::class)); 28 | } 29 | 30 | /** 31 | * Create and return an AuthenticationService instance (v2). 32 | * 33 | * Provided for backwards compatibility; proxies to __invoke(). 34 | * 35 | * @param ServiceLocatorInterface $container 36 | * @return AuthenticationService 37 | */ 38 | public function createService(ServiceLocatorInterface $container) 39 | { 40 | return $this($container, AuthenticationService::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Factory/DefaultAuthHttpAdapterFactory.php: -------------------------------------------------------------------------------- 1 | has('config')) { 31 | return false; 32 | } 33 | 34 | $config = $container->get('config'); 35 | 36 | // If no HTTP adapter configuration present, nothing to create 37 | if (! isset($config['zf-mvc-auth']['authentication']['http'])) { 38 | return false; 39 | } 40 | 41 | return HttpAdapterFactory::factory($config['zf-mvc-auth']['authentication']['http'], $container); 42 | } 43 | 44 | /** 45 | * Create and return an HTTP authentication adapter instance (v2). 46 | * 47 | * Provided for backwards compatibility; proxies to __invoke(). 48 | * 49 | * @param ServiceLocatorInterface $container 50 | * @return HttpAuth 51 | */ 52 | public function createService(ServiceLocatorInterface $container) 53 | { 54 | return $this($container, HttpAuth::class); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Factory/DefaultAuthenticationListenerFactory.php: -------------------------------------------------------------------------------- 1 | retrieveHttpAdapter($container); 37 | if ($httpAdapter) { 38 | $listener->attach($httpAdapter); 39 | } 40 | 41 | $oauth2Server = $this->createOAuth2Server($container); 42 | if ($oauth2Server) { 43 | $listener->attach($oauth2Server); 44 | } 45 | 46 | $authenticationTypes = $this->getAuthenticationTypes($container); 47 | if ($authenticationTypes) { 48 | $listener->addAuthenticationTypes($authenticationTypes); 49 | } 50 | 51 | $listener->setAuthMap($this->getAuthenticationMap($container)); 52 | 53 | return $listener; 54 | } 55 | 56 | /** 57 | * Create and return a DefaultAuthenticationListener (v2). 58 | * 59 | * Provided for backwards compatibility; proxies to __invoke(). 60 | * 61 | * @param ServiceLocatorInterface $container 62 | * @return DefaultAuthenticationListener 63 | */ 64 | public function createService(ServiceLocatorInterface $container) 65 | { 66 | return $this($container, DefaultAuthenticationListener::class); 67 | } 68 | 69 | /** 70 | * @param ContainerInterface $services 71 | * @return false|HttpAdapter 72 | */ 73 | protected function retrieveHttpAdapter(ContainerInterface $container) 74 | { 75 | // Allow applications to provide their own AuthHttpAdapter service; if none provided, 76 | // or no HTTP adapter configuration provided to zf-mvc-auth, we can stop early. 77 | 78 | $httpAdapter = $container->get('ZF\MvcAuth\Authentication\AuthHttpAdapter'); 79 | 80 | if ($httpAdapter === false) { 81 | return false; 82 | } 83 | 84 | // We must abort if no resolver was provided 85 | if (! $httpAdapter->getBasicResolver() 86 | && ! $httpAdapter->getDigestResolver() 87 | ) { 88 | return false; 89 | } 90 | 91 | $authService = $container->get('authentication'); 92 | 93 | return new HttpAdapter($httpAdapter, $authService); 94 | } 95 | 96 | /** 97 | * Create an OAuth2 server by introspecting the config service 98 | * 99 | * @param ContainerInterface $container 100 | * @return false|OAuth2Adapter 101 | */ 102 | protected function createOAuth2Server(ContainerInterface $container) 103 | { 104 | if (! $container->has('config')) { 105 | // If we don't have configuration, we cannot create an OAuth2 server. 106 | return false; 107 | } 108 | 109 | $config = $container->get('config'); 110 | if (! isset($config['zf-oauth2']['storage']) 111 | || ! is_string($config['zf-oauth2']['storage']) 112 | || ! $container->has($config['zf-oauth2']['storage']) 113 | ) { 114 | return false; 115 | } 116 | 117 | if ($container->has('ZF\OAuth2\Service\OAuth2Server')) { 118 | // If the service locator already has a pre-configured OAuth2 server, use it. 119 | $factory = $container->get('ZF\OAuth2\Service\OAuth2Server'); 120 | 121 | return new OAuth2Adapter($factory()); 122 | } 123 | 124 | $factory = new ZFOAuth2ServerFactory(); 125 | 126 | try { 127 | $serverFactory = $factory($container); 128 | } catch (RuntimeException $e) { 129 | // These are exceptions specifically thrown from the 130 | // ZF\OAuth2\Factory\OAuth2ServerFactory when essential 131 | // configuration is missing. 132 | switch (true) { 133 | case strpos($e->getMessage(), 'missing'): 134 | return false; 135 | case strpos($e->getMessage(), 'string or array'): 136 | return false; 137 | default: 138 | // Any other RuntimeException at this point we don't know 139 | // about and need to re-throw. 140 | throw $e; 141 | } 142 | } 143 | 144 | return new OAuth2Adapter($serverFactory(null)); 145 | } 146 | 147 | /** 148 | * Retrieve custom authentication types 149 | * 150 | * @param ContainerInterface $container 151 | * @return array|false 152 | */ 153 | protected function getAuthenticationTypes(ContainerInterface $container) 154 | { 155 | if (! $container->has('config')) { 156 | return false; 157 | } 158 | 159 | $config = $container->get('config'); 160 | if (! isset($config['zf-mvc-auth']['authentication']['types']) 161 | || ! is_array($config['zf-mvc-auth']['authentication']['types']) 162 | ) { 163 | return false; 164 | } 165 | 166 | return $config['zf-mvc-auth']['authentication']['types']; 167 | } 168 | 169 | /** 170 | * @param ContainerInterface $container 171 | * @return array 172 | */ 173 | protected function getAuthenticationMap(ContainerInterface $container) 174 | { 175 | if (! $container->has('config')) { 176 | return []; 177 | } 178 | 179 | $config = $container->get('config'); 180 | if (! isset($config['zf-mvc-auth']['authentication']['map']) 181 | || ! is_array($config['zf-mvc-auth']['authentication']['map']) 182 | ) { 183 | return []; 184 | } 185 | 186 | return $config['zf-mvc-auth']['authentication']['map']; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Factory/DefaultAuthorizationListenerFactory.php: -------------------------------------------------------------------------------- 1 | has(AuthorizationInterface::class)) { 33 | throw new ServiceNotCreatedException(sprintf( 34 | 'Cannot create %s service; no %s service available!', 35 | DefaultAuthorizationListener::class, 36 | AuthorizationInterface::class 37 | )); 38 | } 39 | 40 | return new DefaultAuthorizationListener($container->get(AuthorizationInterface::class)); 41 | } 42 | 43 | /** 44 | * Create and return the default authorization listener (v2). 45 | * 46 | * Provided for backwards compatibility; proxies to __invoke(). 47 | * 48 | * @param ServiceLocatorInterface $container 49 | * @return DefaultAuthorizationListenerFactory 50 | */ 51 | public function createService(ServiceLocatorInterface $container) 52 | { 53 | return $this($container, DefaultAuthorizationListener::class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Factory/DefaultResourceResolverListenerFactory.php: -------------------------------------------------------------------------------- 1 | true, 22 | Request::METHOD_GET => true, 23 | Request::METHOD_PATCH => true, 24 | Request::METHOD_POST => true, 25 | Request::METHOD_PUT => true, 26 | ]; 27 | 28 | /** 29 | * Create and return a DefaultResourceResolverListener instance. 30 | * 31 | * @param ContainerInterface $container 32 | * @param string $requestedName 33 | * @param null|array $options 34 | * @return DefaultResourceResolverListener 35 | */ 36 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) 37 | { 38 | $config = $container->has('config') ? $container->get('config') : []; 39 | 40 | return new DefaultResourceResolverListener( 41 | $this->getRestServicesFromConfig($config) 42 | ); 43 | } 44 | 45 | /** 46 | * Create and return a DefaultResourceResolverListener instance (v2). 47 | * 48 | * Provided for backwards compatibility; proxies to __invoke(). 49 | * 50 | * @param ServiceLocatorInterface $container 51 | * @return DefaultResourceResolverListener 52 | */ 53 | public function createService(ServiceLocatorInterface $container) 54 | { 55 | return $this($container, DefaultResourceResolverListener::class); 56 | } 57 | 58 | /** 59 | * Generate the list of REST services for the listener 60 | * 61 | * Looks for zf-rest configuration, and creates a list of controller 62 | * service / identifier name pairs to pass to the listener. 63 | * 64 | * @param array $config 65 | * @return array 66 | */ 67 | protected function getRestServicesFromConfig(array $config) 68 | { 69 | $restServices = []; 70 | if (! isset($config['zf-rest'])) { 71 | return $restServices; 72 | } 73 | 74 | foreach ($config['zf-rest'] as $controllerService => $restConfig) { 75 | if (! isset($restConfig['route_identifier_name'])) { 76 | continue; 77 | } 78 | $restServices[$controllerService] = $restConfig['route_identifier_name']; 79 | } 80 | 81 | return $restServices; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Factory/FileResolverFactory.php: -------------------------------------------------------------------------------- 1 | has('config')) { 27 | return false; 28 | } 29 | 30 | $config = $container->get('config'); 31 | 32 | if (! isset($config['zf-mvc-auth']['authentication']['http']['htdigest'])) { 33 | return false; 34 | } 35 | 36 | $htdigest = $config['zf-mvc-auth']['authentication']['http']['htdigest']; 37 | 38 | return new FileResolver($htdigest); 39 | } 40 | 41 | /** 42 | * Create and return a FileResolver instance, if configured (v2). 43 | * 44 | * Provided for backwards compatibility; proxies to __invoke(). 45 | * 46 | * @param ServiceLocatorInterface $container 47 | * @return false|FileResolver 48 | */ 49 | public function createService(ServiceLocatorInterface $container) 50 | { 51 | return $this($container, FileResolver::class); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Factory/HttpAdapterFactory.php: -------------------------------------------------------------------------------- 1 | implode(' ', $config['accept_schemes']) 63 | ] 64 | )); 65 | 66 | if (in_array('basic', $config['accept_schemes'])) { 67 | if (isset($config['basic_resolver_factory']) 68 | && self::containerHasKey($container, $config['basic_resolver_factory']) 69 | ) { 70 | $httpAdapter->setBasicResolver($container->get($config['basic_resolver_factory'])); 71 | } elseif (isset($config['htpasswd'])) { 72 | $httpAdapter->setBasicResolver(new ApacheResolver($config['htpasswd'])); 73 | } 74 | } 75 | 76 | if (in_array('digest', $config['accept_schemes'])) { 77 | if (isset($config['digest_resolver_factory']) 78 | && self::containerHasKey($container, $config['digest_resolver_factory']) 79 | ) { 80 | $httpAdapter->setDigestResolver($container->get($config['digest_resolver_factory'])); 81 | } elseif (isset($config['htdigest'])) { 82 | $httpAdapter->setDigestResolver(new FileResolver($config['htdigest'])); 83 | } 84 | } 85 | 86 | return $httpAdapter; 87 | } 88 | 89 | /** 90 | * @param ContainerInterface $container 91 | * @param null $key 92 | * @return bool 93 | */ 94 | private static function containerHasKey(ContainerInterface $container = null, $key = null) 95 | { 96 | if (! $container instanceof ContainerInterface) { 97 | return false; 98 | } 99 | if (! is_string($key)) { 100 | return false; 101 | } 102 | return $container->has($key); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Factory/NamedOAuth2ServerFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 29 | 30 | $oauth2Config = isset($config['zf-oauth2']) ? $config['zf-oauth2'] : []; 31 | $mvcAuthConfig = isset($config['zf-mvc-auth']['authentication']['adapters']) 32 | ? $config['zf-mvc-auth']['authentication']['adapters'] 33 | : []; 34 | 35 | $servers = (object) ['application' => null, 'api' => []]; 36 | return function ($type = null) use ($oauth2Config, $mvcAuthConfig, $container, $servers) { 37 | // Empty type == legacy configuration. 38 | if (empty($type)) { 39 | if ($servers->application) { 40 | return $servers->application; 41 | } 42 | $factory = new OAuth2ServerInstanceFactory($oauth2Config, $container); 43 | return $servers->application = $factory(); 44 | } 45 | 46 | if (isset($servers->api[$type])) { 47 | return $servers->api[$type]; 48 | } 49 | 50 | foreach ($mvcAuthConfig as $name => $adapterConfig) { 51 | if (! isset($adapterConfig['storage']['route'])) { 52 | // Not a zf-oauth2 config 53 | continue; 54 | } 55 | 56 | if ($type !== $adapterConfig['storage']['route']) { 57 | continue; 58 | } 59 | 60 | // Found! 61 | return $servers->api[$type] = OAuth2ServerFactory::factory( 62 | $adapterConfig['storage'], 63 | $container 64 | ); 65 | } 66 | 67 | // At this point, a $type was specified, but no matching adapter 68 | // was found. Attempt to pull a global OAuth2 instance; if none is 69 | // present, this will raise an exception anyways. 70 | if ($servers->application) { 71 | return $servers->application; 72 | } 73 | $factory = new OAuth2ServerInstanceFactory($oauth2Config, $container); 74 | return $servers->application = $factory(); 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Factory/OAuth2ServerFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 40 | $oauth2Config = isset($allConfig['zf-oauth2']) ? $allConfig['zf-oauth2'] : []; 41 | $options = self::marshalOptions($oauth2Config); 42 | 43 | $oauth2Server = new OAuth2Server( 44 | self::createStorage(array_merge($oauth2Config, $config), $container), 45 | $options 46 | ); 47 | 48 | return self::injectGrantTypes($oauth2Server, $oauth2Config['grant_types'], $options); 49 | } 50 | 51 | /** 52 | * Create and return an OAuth2 storage adapter instance. 53 | * 54 | * @param array $config 55 | * @param ContainerInterface $container 56 | * @return array|MongoAdapter|PdoAdapter A PdoAdapter, MongoAdapter, or array of storage instances. 57 | */ 58 | private static function createStorage(array $config, ContainerInterface $container) 59 | { 60 | if (isset($config['adapter']) && is_string($config['adapter'])) { 61 | return self::createStorageFromAdapter($config['adapter'], $config, $container); 62 | } 63 | 64 | if (isset($config['storage']) 65 | && (is_string($config['storage']) || is_array($config['storage'])) 66 | ) { 67 | return self::createStorageFromServices($config['storage'], $container); 68 | } 69 | 70 | throw new ServiceNotCreatedException('Missing or invalid storage adapter information for OAuth2'); 71 | } 72 | 73 | /** 74 | * Create an OAuth2 storage instance based on the adapter specified. 75 | * 76 | * @param string $adapter One of "pdo" or "mongo". 77 | * @param array $config 78 | * @param ContainerInterface $container 79 | * @return MongoAdapter|PdoAdapter 80 | */ 81 | private static function createStorageFromAdapter($adapter, array $config, ContainerInterface $container) 82 | { 83 | switch (strtolower($adapter)) { 84 | case 'pdo': 85 | return self::createPdoAdapter($config); 86 | case 'mongo': 87 | return self::createMongoAdapter($config, $container); 88 | default: 89 | throw new ServiceNotCreatedException('Invalid storage adapter type for OAuth2'); 90 | } 91 | } 92 | 93 | /** 94 | * Creates the OAuth2 storage from services. 95 | * 96 | * @param string|string[] $storage A string or an array of strings; each MUST be a valid service. 97 | * @param ContainerInterface $container 98 | * @return array 99 | */ 100 | private static function createStorageFromServices($storage, ContainerInterface $container) 101 | { 102 | $storageServices = []; 103 | 104 | if (is_string($storage)) { 105 | $storageServices[] = $storage; 106 | } 107 | 108 | if (is_array($storage)) { 109 | $storageServices = $storage; 110 | } 111 | 112 | $storage = []; 113 | foreach ($storageServices as $key => $service) { 114 | $storage[$key] = $container->get($service); 115 | } 116 | return $storage; 117 | } 118 | 119 | /** 120 | * Create and return an OAuth2 PDO adapter. 121 | * 122 | * @param array $config 123 | * @return PdoAdapter 124 | */ 125 | private static function createPdoAdapter(array $config) 126 | { 127 | return new PdoAdapter( 128 | self::createPdoConfig($config), 129 | self::getOAuth2ServerConfig($config) 130 | ); 131 | } 132 | 133 | /** 134 | * Create and return an OAuth2 Mongo adapter. 135 | * 136 | * @param array $config 137 | * @param ContainerInterface $container 138 | * @return MongoAdapter 139 | */ 140 | private static function createMongoAdapter(array $config, ContainerInterface $container) 141 | { 142 | return new MongoAdapter( 143 | self::createMongoDatabase($config, $container), 144 | self::getOAuth2ServerConfig($config) 145 | ); 146 | } 147 | 148 | /** 149 | * Create and return the configuration needed to create a PDO instance. 150 | * 151 | * @param array $config 152 | * @return array 153 | */ 154 | private static function createPdoConfig(array $config) 155 | { 156 | if (! isset($config['dsn'])) { 157 | throw new ServiceNotCreatedException( 158 | 'Missing DSN for OAuth2 PDO adapter creation' 159 | ); 160 | } 161 | 162 | $username = isset($config['username']) ? $config['username'] : null; 163 | $password = isset($config['password']) ? $config['password'] : null; 164 | $options = isset($config['options']) ? $config['options'] : []; 165 | 166 | return [ 167 | 'dsn' => $config['dsn'], 168 | 'username' => $username, 169 | 'password' => $password, 170 | 'options' => $options, 171 | ]; 172 | } 173 | 174 | /** 175 | * Create and return a Mongo database instance. 176 | * 177 | * @param array $config 178 | * @param ContainerInterface $container 179 | * @return \MongoDB 180 | */ 181 | private static function createMongoDatabase(array $config, ContainerInterface $container) 182 | { 183 | $dbLocatorName = isset($config['locator_name']) 184 | ? $config['locator_name'] 185 | : 'MongoDB'; 186 | 187 | if ($container->has($dbLocatorName)) { 188 | return $container->get($dbLocatorName); 189 | } 190 | 191 | if (! isset($config['database'])) { 192 | throw new ServiceNotCreatedException( 193 | 'Missing OAuth2 Mongo database configuration' 194 | ); 195 | } 196 | 197 | $options = isset($config['options']) ? $config['options'] : []; 198 | $options['connect'] = false; 199 | $server = isset($config['dsn']) ? $config['dsn'] : null; 200 | $mongo = new MongoClient($server, $options); 201 | return $mongo->{$config['database']}; 202 | } 203 | 204 | /** 205 | * Retrieve oauth2-server-php storage settings configuration. 206 | * 207 | * @param $config 208 | * 209 | * @return array 210 | */ 211 | private static function getOAuth2ServerConfig($config) 212 | { 213 | $oauth2ServerConfig = []; 214 | if (isset($config['storage_settings']) && is_array($config['storage_settings'])) { 215 | $oauth2ServerConfig = $config['storage_settings']; 216 | } 217 | 218 | return $oauth2ServerConfig; 219 | } 220 | 221 | /** 222 | * Marshal OAuth2\Server options from zf-oauth2 configuration. 223 | * 224 | * @param array $config 225 | * @return array 226 | */ 227 | private static function marshalOptions(array $config) 228 | { 229 | $enforceState = array_key_exists('enforce_state', $config) 230 | ? $config['enforce_state'] 231 | : true; 232 | $allowImplicit = isset($config['allow_implicit']) 233 | ? $config['allow_implicit'] 234 | : false; 235 | $accessLifetime = isset($config['access_lifetime']) 236 | ? $config['access_lifetime'] 237 | : 3600; 238 | $audience = isset($config['audience']) 239 | ? $config['audience'] 240 | : ''; 241 | $options = isset($config['options']) 242 | ? $config['options'] 243 | : []; 244 | 245 | return array_merge([ 246 | 'access_lifetime' => $accessLifetime, 247 | 'allow_implicit' => $allowImplicit, 248 | 'audience' => $audience, 249 | 'enforce_state' => $enforceState, 250 | ], $options); 251 | } 252 | 253 | /** 254 | * Inject grant types into the OAuth2\Server instance, based on zf-oauth2 255 | * configuration. 256 | * 257 | * @param OAuth2Server $server 258 | * @param array $availableGrantTypes 259 | * @param array $options 260 | * @return OAuth2Server 261 | */ 262 | private static function injectGrantTypes(OAuth2Server $server, array $availableGrantTypes, array $options) 263 | { 264 | if (array_key_exists('client_credentials', $availableGrantTypes) 265 | && $availableGrantTypes['client_credentials'] === true 266 | ) { 267 | $clientOptions = []; 268 | if (isset($options['allow_credentials_in_request_body'])) { 269 | $clientOptions['allow_credentials_in_request_body'] = $options['allow_credentials_in_request_body']; 270 | } 271 | 272 | // Add the "Client Credentials" grant type (it is the simplest of the grant types) 273 | $server->addGrantType(new ClientCredentials($server->getStorage('client_credentials'), $clientOptions)); 274 | } 275 | 276 | if (array_key_exists('authorization_code', $availableGrantTypes) 277 | && $availableGrantTypes['authorization_code'] === true 278 | ) { 279 | $authCodeClass = array_key_exists('use_openid_connect', $options) && $options['use_openid_connect'] === true 280 | ? OpenIDAuthorizationCodeGrantType::class 281 | : AuthorizationCode::class; 282 | 283 | // Add the "Authorization Code" grant type (this is where the oauth magic happens) 284 | $server->addGrantType(new $authCodeClass($server->getStorage('authorization_code'))); 285 | } 286 | 287 | if (array_key_exists('password', $availableGrantTypes) && $availableGrantTypes['password'] === true) { 288 | // Add the "User Credentials" grant type 289 | $server->addGrantType(new UserCredentials($server->getStorage('user_credentials'))); 290 | } 291 | 292 | if (array_key_exists('jwt', $availableGrantTypes) && $availableGrantTypes['jwt'] === true) { 293 | // Add the "JWT Bearer" grant type 294 | $server->addGrantType(new JwtBearer($server->getStorage('jwt_bearer'), $options['audience'])); 295 | } 296 | 297 | if (array_key_exists('refresh_token', $availableGrantTypes) && $availableGrantTypes['refresh_token'] === true) { 298 | $refreshOptions = []; 299 | if (isset($options['always_issue_new_refresh_token'])) { 300 | $refreshOptions['always_issue_new_refresh_token'] = $options['always_issue_new_refresh_token']; 301 | } 302 | if (isset($options['refresh_token_lifetime'])) { 303 | $refreshOptions['refresh_token_lifetime'] = $options['refresh_token_lifetime']; 304 | } 305 | if (isset($options['unset_refresh_token_after_use'])) { 306 | $refreshOptions['unset_refresh_token_after_use'] = $options['unset_refresh_token_after_use']; 307 | } 308 | 309 | // Add the "Refresh Token" grant type 310 | $server->addGrantType(new RefreshToken($server->getStorage('refresh_token'), $refreshOptions)); 311 | } 312 | 313 | return $server; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/Identity/AuthenticatedIdentity.php: -------------------------------------------------------------------------------- 1 | identity = $identity; 18 | } 19 | 20 | public function getRoleId() 21 | { 22 | return $this->name; 23 | } 24 | 25 | public function getAuthenticationIdentity() 26 | { 27 | return $this->identity; 28 | } 29 | 30 | public function setName($name) 31 | { 32 | $this->name = $name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Identity/GuestIdentity.php: -------------------------------------------------------------------------------- 1 | getController(); 17 | if (! $controller instanceof InjectApplicationEventInterface) { 18 | return new GuestIdentity(); 19 | } 20 | 21 | $event = $controller->getEvent(); 22 | $identity = $event->getParam(__NAMESPACE__); 23 | 24 | if (! $identity instanceof IdentityInterface) { 25 | return new GuestIdentity(); 26 | } 27 | 28 | return $identity; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | getEventManager(); 38 | $events->attach(ModuleEvent::EVENT_MERGE_CONFIG, [$this, 'onMergeConfig']); 39 | } 40 | 41 | /** 42 | * Override ZF\OAuth2\Service\OAuth2Server service 43 | * 44 | * If the ZF\OAuth2\Service\OAuth2Server is defined, and set to the 45 | * default, override it with the NamedOAuth2ServerFactory. 46 | * 47 | * @param ModuleEvent $e 48 | */ 49 | public function onMergeConfig(ModuleEvent $e) 50 | { 51 | $configListener = $e->getConfigListener(); 52 | $config = $configListener->getMergedConfig(false); 53 | $service = 'ZF\OAuth2\Service\OAuth2Server'; 54 | $default = 'ZF\OAuth2\Factory\OAuth2ServerFactory'; 55 | 56 | if (! isset($config['service_manager']['factories'][$service]) 57 | || $config['service_manager']['factories'][$service] !== $default 58 | ) { 59 | return; 60 | } 61 | 62 | $config['service_manager']['factories'][$service] = __NAMESPACE__ . '\Factory\NamedOAuth2ServerFactory'; 63 | $configListener->setMergedConfig($config); 64 | } 65 | 66 | public function onBootstrap(MvcEvent $mvcEvent) 67 | { 68 | if (! $mvcEvent->getRequest() instanceof HttpRequest) { 69 | return; 70 | } 71 | 72 | $app = $mvcEvent->getApplication(); 73 | $events = $app->getEventManager(); 74 | $this->container = $app->getServiceManager(); 75 | 76 | $authentication = $this->container->get('authentication'); 77 | $mvcAuthEvent = new MvcAuthEvent( 78 | $mvcEvent, 79 | $authentication, 80 | $this->container->get('authorization') 81 | ); 82 | $this->mvcRouteListener = new MvcRouteListener($mvcAuthEvent, $events, $authentication); 83 | 84 | $events->attach( 85 | MvcAuthEvent::EVENT_AUTHENTICATION, 86 | $this->container->get('ZF\MvcAuth\Authentication\DefaultAuthenticationListener') 87 | ); 88 | $events->attach( 89 | MvcAuthEvent::EVENT_AUTHENTICATION_POST, 90 | $this->container->get('ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener') 91 | ); 92 | $events->attach( 93 | MvcAuthEvent::EVENT_AUTHORIZATION, 94 | $this->container->get('ZF\MvcAuth\Authorization\DefaultResourceResolverListener'), 95 | 1000 96 | ); 97 | $events->attach( 98 | MvcAuthEvent::EVENT_AUTHORIZATION, 99 | $this->container->get('ZF\MvcAuth\Authorization\DefaultAuthorizationListener') 100 | ); 101 | $events->attach( 102 | MvcAuthEvent::EVENT_AUTHORIZATION_POST, 103 | $this->container->get('ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener') 104 | ); 105 | 106 | $events->attach( 107 | MvcAuthEvent::EVENT_AUTHENTICATION_POST, 108 | [$this, 'onAuthenticationPost'], 109 | -1 110 | ); 111 | } 112 | 113 | public function onAuthenticationPost(MvcAuthEvent $e) 114 | { 115 | if ($this->container->has('api-identity')) { 116 | return; 117 | } 118 | 119 | $this->container->setService('api-identity', $e->getIdentity()); 120 | } 121 | 122 | /** 123 | * Retrieve the configured MvcRouteListener. 124 | * 125 | * @return null|MvcRouteListener 126 | */ 127 | public function getMvcRouteListener() 128 | { 129 | return $this->mvcRouteListener; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/MvcAuthEvent.php: -------------------------------------------------------------------------------- 1 | mvcEvent = $mvcEvent; 62 | $this->authentication = $authentication; 63 | $this->authorization = $authorization; 64 | } 65 | 66 | /** 67 | * @return mixed 68 | */ 69 | public function getAuthenticationService() 70 | { 71 | return $this->authentication; 72 | } 73 | 74 | /** 75 | * @return bool 76 | */ 77 | public function hasAuthenticationResult() 78 | { 79 | return ($this->authenticationResult !== null); 80 | } 81 | 82 | /** 83 | * @param Result $result 84 | * @return self 85 | */ 86 | public function setAuthenticationResult(Result $result) 87 | { 88 | $this->authenticationResult = $result; 89 | return $this; 90 | } 91 | 92 | /** 93 | * @return null|Result 94 | */ 95 | public function getAuthenticationResult() 96 | { 97 | return $this->authenticationResult; 98 | } 99 | 100 | /** 101 | * @return mixed 102 | */ 103 | public function getAuthorizationService() 104 | { 105 | return $this->authorization; 106 | } 107 | 108 | /** 109 | * @return MvcEvent 110 | */ 111 | public function getMvcEvent() 112 | { 113 | return $this->mvcEvent; 114 | } 115 | 116 | /** 117 | * @return mixed|null 118 | */ 119 | public function getIdentity() 120 | { 121 | return $this->authentication->getIdentity(); 122 | } 123 | 124 | /** 125 | * @param IdentityInterface $identity 126 | * @return $this 127 | */ 128 | public function setIdentity(IdentityInterface $identity) 129 | { 130 | $this->authentication->getStorage()->write($identity); 131 | return $this; 132 | } 133 | 134 | /** 135 | * @return mixed 136 | */ 137 | public function getResource() 138 | { 139 | return $this->resource; 140 | } 141 | 142 | /** 143 | * @param mixed $resource 144 | * @return self 145 | */ 146 | public function setResource($resource) 147 | { 148 | $this->resource = $resource; 149 | return $this; 150 | } 151 | 152 | /** 153 | * @return bool 154 | */ 155 | public function isAuthorized() 156 | { 157 | return $this->authorized; 158 | } 159 | 160 | /** 161 | * @param bool $flag 162 | * @return self 163 | */ 164 | public function setIsAuthorized($flag) 165 | { 166 | $this->authorized = (bool) $flag; 167 | return $this; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/MvcRouteListener.php: -------------------------------------------------------------------------------- 1 | attach($events); 45 | $mvcAuthEvent->setTarget($this); 46 | $this->mvcAuthEvent = $mvcAuthEvent; 47 | $this->events = $events; 48 | $this->authentication = $authentication; 49 | } 50 | 51 | /** 52 | * Attach listeners 53 | * 54 | * @param EventManagerInterface $events 55 | */ 56 | public function attach(EventManagerInterface $events, $priority = 1) 57 | { 58 | $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'authentication'], -50); 59 | $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'authenticationPost'], -51); 60 | $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'authorization'], -600); 61 | $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'authorizationPost'], -601); 62 | } 63 | 64 | /** 65 | * Trigger the authentication event 66 | * 67 | * @param MvcEvent $mvcEvent 68 | * @return null|Response 69 | */ 70 | public function authentication(MvcEvent $mvcEvent) 71 | { 72 | if (! $mvcEvent->getRequest() instanceof HttpRequest 73 | || $mvcEvent->getRequest()->isOptions() 74 | ) { 75 | return; 76 | } 77 | 78 | $mvcAuthEvent = $this->mvcAuthEvent; 79 | $mvcAuthEvent->setName($mvcAuthEvent::EVENT_AUTHENTICATION); 80 | $responses = $this->events->triggerEventUntil(function ($r) { 81 | return ($r instanceof Identity\IdentityInterface 82 | || $r instanceof Result 83 | || $r instanceof Response 84 | ); 85 | }, $mvcAuthEvent); 86 | 87 | $result = $responses->last(); 88 | $storage = $this->authentication->getStorage(); 89 | 90 | // If we have a response, return immediately 91 | if ($result instanceof Response) { 92 | return $result; 93 | } 94 | 95 | // Determine if the listener returned an identity 96 | if ($result instanceof Identity\IdentityInterface) { 97 | $storage->write($result); 98 | } 99 | 100 | // If we have a Result, we create an AuthenticatedIdentity from it 101 | if ($result instanceof Result 102 | && $result->isValid() 103 | ) { 104 | $mvcAuthEvent->setAuthenticationResult($result); 105 | $mvcAuthEvent->setIdentity(new Identity\AuthenticatedIdentity($result->getIdentity())); 106 | return; 107 | } 108 | 109 | $identity = $this->authentication->getIdentity(); 110 | if ($identity === null && ! $mvcAuthEvent->hasAuthenticationResult()) { 111 | // if there is no Authentication identity or result, safe to assume we have a guest 112 | $mvcAuthEvent->setIdentity(new Identity\GuestIdentity()); 113 | return; 114 | } 115 | 116 | if ($mvcAuthEvent->hasAuthenticationResult() 117 | && $mvcAuthEvent->getAuthenticationResult()->isValid() 118 | ) { 119 | $mvcAuthEvent->setIdentity( 120 | new Identity\AuthenticatedIdentity( 121 | $mvcAuthEvent->getAuthenticationResult()->getIdentity() 122 | ) 123 | ); 124 | } 125 | 126 | if ($identity instanceof Identity\IdentityInterface) { 127 | $mvcAuthEvent->setIdentity($identity); 128 | return; 129 | } 130 | 131 | if ($identity !== null) { 132 | // identity found in authentication; we can assume we're authenticated 133 | $mvcAuthEvent->setIdentity(new Identity\AuthenticatedIdentity($identity)); 134 | return; 135 | } 136 | } 137 | 138 | /** 139 | * Trigger the authentication.post event 140 | * 141 | * @param MvcEvent $mvcEvent 142 | * @return Response|mixed 143 | */ 144 | public function authenticationPost(MvcEvent $mvcEvent) 145 | { 146 | if (! $mvcEvent->getRequest() instanceof HttpRequest 147 | || $mvcEvent->getRequest()->isOptions() 148 | ) { 149 | return; 150 | } 151 | 152 | $mvcAuthEvent = $this->mvcAuthEvent; 153 | $mvcAuthEvent->setName($mvcAuthEvent::EVENT_AUTHENTICATION_POST); 154 | 155 | $responses = $this->events->triggerEventUntil(function ($r) { 156 | return ($r instanceof Response); 157 | }, $mvcAuthEvent); 158 | 159 | return $responses->last(); 160 | } 161 | 162 | /** 163 | * Trigger the authorization event 164 | * 165 | * @param MvcEvent $mvcEvent 166 | * @return null|Response 167 | */ 168 | public function authorization(MvcEvent $mvcEvent) 169 | { 170 | if (! $mvcEvent->getRequest() instanceof HttpRequest 171 | || $mvcEvent->getRequest()->isOptions() 172 | ) { 173 | return; 174 | } 175 | 176 | $mvcAuthEvent = $this->mvcAuthEvent; 177 | $mvcAuthEvent->setName($mvcAuthEvent::EVENT_AUTHORIZATION); 178 | 179 | $responses = $this->events->triggerEventUntil(function ($r) { 180 | return (is_bool($r) || $r instanceof Response); 181 | }, $mvcAuthEvent); 182 | 183 | $result = $responses->last(); 184 | 185 | if (is_bool($result)) { 186 | $mvcAuthEvent->setIsAuthorized($result); 187 | return; 188 | } 189 | 190 | if ($result instanceof Response) { 191 | return $result; 192 | } 193 | } 194 | 195 | /** 196 | * Trigger the authorization.post event 197 | * 198 | * @param MvcEvent $mvcEvent 199 | * @return null|Response 200 | */ 201 | public function authorizationPost(MvcEvent $mvcEvent) 202 | { 203 | if (! $mvcEvent->getRequest() instanceof HttpRequest 204 | || $mvcEvent->getRequest()->isOptions() 205 | ) { 206 | return; 207 | } 208 | 209 | $mvcAuthEvent = $this->mvcAuthEvent; 210 | $mvcAuthEvent->setName($mvcAuthEvent::EVENT_AUTHORIZATION_POST); 211 | 212 | $responses = $this->events->triggerEventUntil(function ($r) { 213 | return ($r instanceof Response); 214 | }, $mvcAuthEvent); 215 | 216 | return $responses->last(); 217 | } 218 | } 219 | --------------------------------------------------------------------------------