├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── Module.php ├── README.md ├── composer.json ├── config └── module.config.php ├── phpunit.xml ├── src ├── Authentication │ └── Adapter │ │ └── Db.php └── Storage │ ├── ZfcUserPdo.php │ ├── ZfcUserPdoFactory.php │ ├── ZfcUserStorageBridge.php │ └── ZfcUserStorageBridgeFactory.php └── tests ├── LdcZfcUserOAuth2Test ├── Authentication │ └── Adapter │ │ └── DbTest.php ├── Storage │ ├── ZfcUserPdoFactoryTest.php │ ├── ZfcUserPdoTest.php │ ├── ZfcUserStorageBridgeFactoryTest.php │ └── ZfcUserStorageBridgeTest.php └── TestCase.php ├── TestConfiguration.php.dist └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | ._* 3 | .~lock.* 4 | .buildpath 5 | .DS_Store 6 | .idea 7 | .project 8 | .settings 9 | composer.lock 10 | 11 | vendor/* 12 | 13 | vagrant/.vagrant 14 | 15 | tests/log/* 16 | tests/TestConfiguration.php 17 | 18 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - php 3 | 4 | tools: 5 | php_cpd: true 6 | php_pdepend: true 7 | external_code_coverage: true 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - "5.4" 5 | - "5.5" 6 | - "5.6" 7 | - "hhvm" 8 | 9 | install: 10 | - composer install --dev 11 | - composer show -i 12 | - wget https://scrutinizer-ci.com/ocular.phar 13 | 14 | before_script: 15 | 16 | script: 17 | - ./vendor/bin/php-cs-fixer fix -v --dry-run src; 18 | - ./vendor/bin/php-cs-fixer fix -v --dry-run tests; 19 | - ./vendor/bin/phpunit 20 | 21 | after_script: 22 | - php ocular.phar code-coverage:upload --format=php-clover tests/log/clover.xml 23 | 24 | matrix: 25 | fast_finish: true 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Adam Lundrigan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, 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, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of LdcOAuth2CryptoToken nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | getApplication()->getServiceManager(); 18 | $sm->get('ZfcUser\Authentication\Storage\Db')->setStorage($storage); 19 | $sm->get('ZfcUser\Authentication\Adapter\Db')->setStorage($storage); 20 | 21 | // Inject authenticated user from zf-mvc-auth into ZfcUser so it's 22 | // built-in session and user checking still function properly 23 | $zfcUserService = $sm->get('zfcuser_user_service'); 24 | $em = $e->getApplication()->getEventManager(); 25 | $em->attach(MvcAuthEvent::EVENT_AUTHENTICATION_POST, function (MvcAuthEvent $e) use ($zfcUserService, $storage) { 26 | $identity = $e->getIdentity(); 27 | if ( ! $identity instanceof AuthenticatedIdentity ) { 28 | return; 29 | } 30 | 31 | $token = $identity->getAuthenticationIdentity(); 32 | $uid = $token['user_id']; 33 | 34 | $user = $zfcUserService->getUserMapper()->findById($uid); 35 | if ( ! $user instanceof ZfcUserEntity ) { 36 | return; 37 | } 38 | 39 | $storage->write($user->getId()); 40 | }); 41 | } 42 | 43 | public function getConfig() 44 | { 45 | return include __DIR__ . '/config/module.config.php'; 46 | } 47 | 48 | public function getAutoloaderConfig() 49 | { 50 | return array( 51 | 'Zend\Loader\StandardAutoloader' => array( 52 | 'namespaces' => array( 53 | __NAMESPACE__ => __DIR__ . '/src' 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LdcZfcUserOAuth2 2 | 3 | ## What? 4 | 5 | An extension for [`zf-oauth2`](https://github.com/zfcampus/zf-oauth2) allowing use of ZfcUser as authentication source 6 | 7 | ---- 8 | 9 | [![Latest Stable Version](https://poser.pugx.org/adamlundrigan/ldc-zfc-user-oauth2/v/stable.svg)](https://packagist.org/packages/adamlundrigan/ldc-zfc-user-oauth2) [![License](https://poser.pugx.org/adamlundrigan/ldc-zfc-user-oauth2/license.svg)](https://packagist.org/packages/adamlundrigan/ldc-zfc-user-oauth2) [![Build Status](https://travis-ci.org/adamlundrigan/LdcZfcUserOAuth2.svg?branch=master)](https://travis-ci.org/adamlundrigan/LdcZfcUserOAuth2) [![Code Coverage](https://scrutinizer-ci.com/g/adamlundrigan/LdcZfcUserOAuth2/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/adamlundrigan/LdcZfcUserOAuth2/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/adamlundrigan/LdcZfcUserOAuth2/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/adamlundrigan/LdcZfcUserOAuth2/?branch=master) 10 | 11 | ---- 12 | 13 | ## How? 14 | 15 | 1. Install module using Composer 16 | 17 | ``` 18 | composer require adamlundrigan/ldc-zfc-user-oauth2: 19 | ``` 20 | 21 | 2. Enable required modules in your `application.config.php` file: 22 | 23 | - ZfcBase 24 | - ZfcUser 25 | - LdcZfcUserOAuth2 26 | 27 | 3. Configure ZfcUser 28 | 29 | 4. Override the `zf-ouath2` configuration to use the provided storage provider: 30 | 31 | ``` 32 | return array( 33 | 'zf-oauth2' => array( 34 | 'storage' => 'ldc-zfc-user-oauth2-storage-pdo', 35 | ), 36 | ); 37 | ``` 38 | 39 | 5. Override the authentication adapter used by ZfcUser. Locate the `auth_adapters` key in your `zfc-user.global.php` config file and replace it with this: 40 | 41 | ``` 42 | 'auth_adapters' => array( 100 => 'ldc-zfc-user-oauth2-authentication-adapter-db' ), 43 | ``` 44 | 45 | ## TODO 46 | 47 | - [x] Use ZfcUser's authentication mechanism in OAuth2 server 48 | - [x] Populate ZfcUser auth storage when OAuth2 server authentication succeeds 49 | - [ ] Some tests might be a good idea 50 | - [ ] Some documentation and an example might also be good ideas 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adamlundrigan/ldc-zfc-user-oauth2", 3 | "description": "OAuth2 server integration for ZfcUser", 4 | "keywords": [ "zfcuser", "apigility", "zf-oauth2" ], 5 | "license": "BSD-3-Clause", 6 | "authors": [ 7 | { 8 | "name": "Adam Lundrigan", 9 | "email": "adam@lundrigan.ca" 10 | } 11 | ], 12 | "require": { 13 | "zf-commons/zfc-user": "1.*", 14 | "zfcampus/zf-oauth2": "1.*" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": ">=4.3.0", 18 | "mockery/mockery": "0.*", 19 | "fabpot/php-cs-fixer": "@stable", 20 | "zendframework/zend-servicemanager": "2.*", 21 | "ext-pdo_sqlite": "*" 22 | }, 23 | "replace": { 24 | "adamlundrigan/ldc-zfc-user-apigility": "*" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "LdcZfcUserOAuth2\\": "src/" 29 | }, 30 | "files": ["Module.php"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'strategies' => array( 5 | 'ViewJsonStrategy', 6 | ), 7 | ), 8 | 'service_manager' => array( 9 | 'invokables' => array( 10 | 'ldc-zfc-user-oauth2-authentication-adapter-db' => 'LdcZfcUserOAuth2\Authentication\Adapter\Db', 11 | ), 12 | 'factories' => array( 13 | 'ldc-zfc-user-oauth2-storage-pdo' => 'LdcZfcUserOAuth2\Storage\ZfcUserPdoFactory', 14 | 'ldc-zfc-user-oauth2-storage-bridge' => 'LdcZfcUserOAuth2\Storage\ZfcUserStorageBridgeFactory', 15 | ), 16 | ), 17 | ); 18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./tests/ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Authentication/Adapter/Db.php: -------------------------------------------------------------------------------- 1 | getStorage()->clear(); 20 | } 21 | 22 | public function authenticate(AuthEvent $e) 23 | { 24 | if ($this->isSatisfied()) { 25 | $storage = $this->getStorage()->read(); 26 | $e->setIdentity($storage['identity']) 27 | ->setCode(AuthenticationResult::SUCCESS) 28 | ->setMessages(array('Authentication successful.')); 29 | 30 | return; 31 | } 32 | 33 | $identity = $e->getRequest()->getPost()->get('identity'); 34 | $credential = $e->getRequest()->getPost()->get('credential'); 35 | $credential = $this->preProcessCredential($credential); 36 | $userObject = null; 37 | 38 | // Cycle through the configured identity sources and test each 39 | $fields = $this->getOptions()->getAuthIdentityFields(); 40 | while (!is_object($userObject) && count($fields) > 0) { 41 | $mode = array_shift($fields); 42 | switch ($mode) { 43 | case 'username': 44 | $userObject = $this->getMapper()->findByUsername($identity); 45 | break; 46 | case 'email': 47 | $userObject = $this->getMapper()->findByEmail($identity); 48 | break; 49 | } 50 | } 51 | 52 | if (!$userObject) { 53 | $e->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND) 54 | ->setMessages(array('A record with the supplied identity could not be found.')); 55 | $this->setSatisfied(false); 56 | 57 | return false; 58 | } 59 | 60 | if ($this->getOptions()->getEnableUserState()) { 61 | // Don't allow user to login if state is not in allowed list 62 | if (!in_array($userObject->getState(), $this->getOptions()->getAllowedLoginStates())) { 63 | $e->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED) 64 | ->setMessages(array('A record with the supplied identity is not active.')); 65 | $this->setSatisfied(false); 66 | 67 | return false; 68 | } 69 | } 70 | 71 | $bcrypt = new Bcrypt(); 72 | $bcrypt->setCost($this->getOptions()->getPasswordCost()); 73 | if (!$bcrypt->verify($credential, $userObject->getPassword())) { 74 | // Password does not match 75 | $e->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID) 76 | ->setMessages(array('Supplied credential is invalid.')); 77 | $this->setSatisfied(false); 78 | 79 | return false; 80 | } 81 | 82 | // Success! 83 | $e->setIdentity($userObject->getId()); 84 | // Update user's password hash if the cost parameter has changed 85 | $this->updateUserPasswordHash($userObject, $credential, $bcrypt); 86 | $this->setSatisfied(true); 87 | $storage = $this->getStorage()->read(); 88 | $storage['identity'] = $e->getIdentity(); 89 | $this->getStorage()->write($storage); 90 | $e->setCode(AuthenticationResult::SUCCESS) 91 | ->setMessages(array('Authentication successful.')); 92 | } 93 | 94 | /** 95 | * Returns the storage handler 96 | * 97 | * Non-persistent storage is used by default unless a different storage adapter has been set. 98 | * 99 | * @return Storage\StorageInterface 100 | */ 101 | public function getStorage() 102 | { 103 | if (null === $this->storage) { 104 | $this->setStorage(new \Zend\Authentication\Storage\NonPersistent()); 105 | } 106 | 107 | return parent::getStorage(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Storage/ZfcUserPdo.php: -------------------------------------------------------------------------------- 1 | bridge = $bridge; 17 | } 18 | 19 | public function checkUserCredentials($username, $password) 20 | { 21 | return $this->bridge->checkUserCredentials($username, $password); 22 | } 23 | 24 | public function getUserDetails($username) 25 | { 26 | return $this->bridge->getUserDetails($username); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Storage/ZfcUserPdoFactory.php: -------------------------------------------------------------------------------- 1 | get('Config'); 13 | 14 | if (!isset($config['zf-oauth2']['db']) || empty($config['zf-oauth2']['db'])) { 15 | throw new Exception\RuntimeException( 16 | 'The database configuration [\'zf-oauth2\'][\'db\'] for OAuth2 is missing' 17 | ); 18 | } 19 | 20 | $username = isset($config['zf-oauth2']['db']['username']) ? $config['zf-oauth2']['db']['username'] : null; 21 | $password = isset($config['zf-oauth2']['db']['password']) ? $config['zf-oauth2']['db']['password'] : null; 22 | $options = isset($config['zf-oauth2']['db']['options']) ? $config['zf-oauth2']['db']['options'] : array(); 23 | 24 | $oauth2ServerConfig = array(); 25 | if (isset($config['zf-oauth2']['storage_settings']) && is_array($config['zf-oauth2']['storage_settings'])) { 26 | $oauth2ServerConfig = $config['zf-oauth2']['storage_settings']; 27 | } 28 | 29 | $obj = new ZfcUserPdo( 30 | array( 31 | 'dsn' => $config['zf-oauth2']['db']['dsn'], 32 | 'username' => $username, 33 | 'password' => $password, 34 | 'options' => $options, 35 | ), 36 | $oauth2ServerConfig, 37 | $serviceLocator->get('ldc-zfc-user-oauth2-storage-bridge') 38 | ); 39 | 40 | return $obj; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Storage/ZfcUserStorageBridge.php: -------------------------------------------------------------------------------- 1 | mapper = $mapper; 44 | $this->auth = $auth; 45 | $this->authAdapter = $adapter; 46 | $this->authOptions = $options; 47 | } 48 | 49 | /** 50 | * Delegate checking of user credentials to ZfcUser's onboard adapter chain 51 | * 52 | * @param string $username 53 | * @param string $password 54 | * @return boolean 55 | */ 56 | public function checkUserCredentials($username, $password) 57 | { 58 | $request = new \Zend\Http\Request(); 59 | $request->getPost()->set('identity', $username); 60 | $request->getPost()->set('credential', $password); 61 | 62 | $adapterResult = $this->authAdapter->prepareForAuthentication($request); 63 | if ($adapterResult instanceof \Zend\Stdlib\ResponseInterface) { 64 | return false; 65 | } 66 | 67 | $authResult = $this->auth->authenticate($this->authAdapter); 68 | if (! $authResult->isValid()) { 69 | $this->authAdapter->resetAdapters(); 70 | 71 | return false; 72 | } 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * Load user details based on configured authentication fields 79 | * 80 | * @param string $username 81 | * @return array|null 82 | */ 83 | public function getUserDetails($username) 84 | { 85 | $user = null; 86 | $fields = $this->authOptions->getAuthIdentityFields(); 87 | 88 | while (!is_object($user) && count($fields) > 0) { 89 | $mode = array_shift($fields); 90 | switch ($mode) { 91 | case 'username': 92 | $user = $this->mapper->findByUsername($username); 93 | break; 94 | case 'email': 95 | $user = $this->mapper->findByEmail($username); 96 | break; 97 | } 98 | } 99 | if (! $user instanceof UserInterface) { 100 | return NULL; 101 | } 102 | 103 | return array( 104 | 'user_id' => $user->getId(), 105 | 'username' => $user->getUsername(), 106 | 'email' => $user->getEmail(), 107 | 'display_name' => $user->getDisplayName(), 108 | 'state' => $user->getState(), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Storage/ZfcUserStorageBridgeFactory.php: -------------------------------------------------------------------------------- 1 | get('zfcuser_user_mapper'), 13 | $serviceLocator->get('zfcuser_auth_service'), 14 | $serviceLocator->get('ZfcUser\Authentication\Adapter\AdapterChain'), 15 | $serviceLocator->get('zfcuser_module_options') 16 | ); 17 | 18 | return $obj; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/Authentication/Adapter/DbTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\Zend\Authentication\Storage\NonPersistent', $db->getStorage()); 13 | } 14 | 15 | // Everything below here is taken from ZfcUserTest\Authentication\Adapter 16 | 17 | 18 | /** 19 | * The object to be tested. 20 | * 21 | * @var Db 22 | */ 23 | protected $db; 24 | 25 | /** 26 | * Mock of AuthEvent. 27 | * 28 | * @var authEvent 29 | */ 30 | protected $authEvent; 31 | 32 | /** 33 | * Mock of Storage. 34 | * 35 | * @var storage 36 | */ 37 | protected $storage; 38 | 39 | /** 40 | * Mock of Options. 41 | * 42 | * @var options 43 | */ 44 | protected $options; 45 | 46 | /** 47 | * Mock of Mapper. 48 | * 49 | * @var mapper 50 | */ 51 | protected $mapper; 52 | 53 | /** 54 | * Mock of User. 55 | * 56 | * @var user 57 | */ 58 | protected $user; 59 | 60 | protected function setUp() 61 | { 62 | $storage = $this->getMock('Zend\Authentication\Storage\Session'); 63 | $this->storage = $storage; 64 | 65 | $authEvent = $this->getMock('ZfcUser\Authentication\Adapter\AdapterChainEvent'); 66 | $this->authEvent = $authEvent; 67 | 68 | $options = $this->getMock('ZfcUser\Options\ModuleOptions'); 69 | $this->options = $options; 70 | 71 | $mapper = $this->getMock('ZfcUser\Mapper\User'); 72 | $this->mapper = $mapper; 73 | 74 | $user = $this->getMock('ZfcUser\Entity\User'); 75 | $this->user = $user; 76 | 77 | $this->db = new Db(); 78 | $this->db->setStorage($this->storage); 79 | } 80 | 81 | /** 82 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::logout 83 | */ 84 | public function testLogout() 85 | { 86 | $this->storage->expects($this->once()) 87 | ->method('clear'); 88 | 89 | $this->db->logout($this->authEvent); 90 | } 91 | 92 | /** 93 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 94 | */ 95 | public function testAuthenticateWhenSatisfies() 96 | { 97 | $this->authEvent->expects($this->once()) 98 | ->method('setIdentity') 99 | ->with('ZfcUser') 100 | ->will($this->returnValue($this->authEvent)); 101 | $this->authEvent->expects($this->once()) 102 | ->method('setCode') 103 | ->with(\Zend\Authentication\Result::SUCCESS) 104 | ->will($this->returnValue($this->authEvent)); 105 | $this->authEvent->expects($this->once()) 106 | ->method('setMessages') 107 | ->with(array('Authentication successful.')) 108 | ->will($this->returnValue($this->authEvent)); 109 | 110 | $this->storage->expects($this->at(0)) 111 | ->method('read') 112 | ->will($this->returnValue(array('is_satisfied' => true))); 113 | $this->storage->expects($this->at(1)) 114 | ->method('read') 115 | ->will($this->returnValue(array('identity' => 'ZfcUser'))); 116 | 117 | $result = $this->db->authenticate($this->authEvent); 118 | $this->assertNull($result); 119 | } 120 | 121 | /** 122 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 123 | */ 124 | public function testAuthenticateNoUserObject() 125 | { 126 | $this->setAuthenticationCredentials(); 127 | 128 | $this->options->expects($this->once()) 129 | ->method('getAuthIdentityFields') 130 | ->will($this->returnValue(array())); 131 | 132 | $this->authEvent->expects($this->once()) 133 | ->method('setCode') 134 | ->with(\Zend\Authentication\Result::FAILURE_IDENTITY_NOT_FOUND) 135 | ->will($this->returnValue($this->authEvent)); 136 | $this->authEvent->expects($this->once(1)) 137 | ->method('setMessages') 138 | ->with(array('A record with the supplied identity could not be found.')) 139 | ->will($this->returnValue($this->authEvent)); 140 | 141 | $this->db->setOptions($this->options); 142 | 143 | $result = $this->db->authenticate($this->authEvent); 144 | 145 | $this->assertFalse($result); 146 | $this->assertFalse($this->db->isSatisfied()); 147 | } 148 | 149 | /** 150 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 151 | */ 152 | public function testAuthenticationUserStateEnabledUserButUserStateNotInArray() 153 | { 154 | $this->setAuthenticationCredentials(); 155 | $this->setAuthenticationUser(); 156 | 157 | $this->options->expects($this->once()) 158 | ->method('getEnableUserState') 159 | ->will($this->returnValue(true)); 160 | $this->options->expects($this->once()) 161 | ->method('getAllowedLoginStates') 162 | ->will($this->returnValue(array(2, 3))); 163 | 164 | $this->authEvent->expects($this->once()) 165 | ->method('setCode') 166 | ->with(\Zend\Authentication\Result::FAILURE_UNCATEGORIZED) 167 | ->will($this->returnValue($this->authEvent)); 168 | $this->authEvent->expects($this->once()) 169 | ->method('setMessages') 170 | ->with(array('A record with the supplied identity is not active.')) 171 | ->will($this->returnValue($this->authEvent)); 172 | 173 | $this->user->expects($this->once()) 174 | ->method('getState') 175 | ->will($this->returnValue(1)); 176 | 177 | $this->db->setMapper($this->mapper); 178 | $this->db->setOptions($this->options); 179 | 180 | $result = $this->db->authenticate($this->authEvent); 181 | 182 | $this->assertFalse($result); 183 | $this->assertFalse($this->db->isSatisfied()); 184 | } 185 | 186 | /** 187 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 188 | */ 189 | public function testAuthenticateWithWrongPassword() 190 | { 191 | $this->setAuthenticationCredentials(); 192 | $this->setAuthenticationUser(); 193 | 194 | $this->options->expects($this->once()) 195 | ->method('getEnableUserState') 196 | ->will($this->returnValue(false)); 197 | 198 | // Set lowest possible to spent the least amount of resources/time 199 | $this->options->expects($this->once()) 200 | ->method('getPasswordCost') 201 | ->will($this->returnValue(4)); 202 | 203 | $this->authEvent->expects($this->once()) 204 | ->method('setCode') 205 | ->with(\Zend\Authentication\Result::FAILURE_CREDENTIAL_INVALID) 206 | ->will($this->returnValue($this->authEvent)); 207 | $this->authEvent->expects($this->once(1)) 208 | ->method('setMessages') 209 | ->with(array('Supplied credential is invalid.')); 210 | 211 | $this->db->setMapper($this->mapper); 212 | $this->db->setOptions($this->options); 213 | 214 | $result = $this->db->authenticate($this->authEvent); 215 | 216 | $this->assertFalse($result); 217 | $this->assertFalse($this->db->isSatisfied()); 218 | } 219 | 220 | /** 221 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 222 | */ 223 | public function testAuthenticationAuthenticatesWithEmail() 224 | { 225 | $this->setAuthenticationCredentials('zfc-user@zf-commons.io'); 226 | $this->setAuthenticationEmail(); 227 | 228 | $this->options->expects($this->once()) 229 | ->method('getEnableUserState') 230 | ->will($this->returnValue(false)); 231 | 232 | $this->options->expects($this->once()) 233 | ->method('getPasswordCost') 234 | ->will($this->returnValue(4)); 235 | 236 | $this->user->expects($this->exactly(2)) 237 | ->method('getPassword') 238 | ->will($this->returnValue('$2a$04$5kq1mnYWbww8X.rIj7eOVOHXtvGw/peefjIcm0lDGxRTEjm9LnOae')); 239 | $this->user->expects($this->once()) 240 | ->method('getId') 241 | ->will($this->returnValue(1)); 242 | 243 | $this->storage->expects($this->any()) 244 | ->method('getNameSpace') 245 | ->will($this->returnValue('test')); 246 | 247 | $this->authEvent->expects($this->once()) 248 | ->method('setIdentity') 249 | ->with(1) 250 | ->will($this->returnValue($this->authEvent)); 251 | $this->authEvent->expects($this->once()) 252 | ->method('setCode') 253 | ->with(\Zend\Authentication\Result::SUCCESS) 254 | ->will($this->returnValue($this->authEvent)); 255 | $this->authEvent->expects($this->once()) 256 | ->method('setMessages') 257 | ->with(array('Authentication successful.')) 258 | ->will($this->returnValue($this->authEvent)); 259 | 260 | $this->db->setMapper($this->mapper); 261 | $this->db->setOptions($this->options); 262 | 263 | $result = $this->db->authenticate($this->authEvent); 264 | } 265 | 266 | /** 267 | * @covers LdcZfcUserOAuth2\Authentication\Adapter\Db::Authenticate 268 | */ 269 | public function testAuthenticationAuthenticates() 270 | { 271 | $this->setAuthenticationCredentials(); 272 | $this->setAuthenticationUser(); 273 | 274 | $this->options->expects($this->once()) 275 | ->method('getEnableUserState') 276 | ->will($this->returnValue(true)); 277 | 278 | $this->options->expects($this->once()) 279 | ->method('getAllowedLoginStates') 280 | ->will($this->returnValue(array(1, 2, 3))); 281 | 282 | $this->options->expects($this->once()) 283 | ->method('getPasswordCost') 284 | ->will($this->returnValue(4)); 285 | 286 | $this->user->expects($this->exactly(2)) 287 | ->method('getPassword') 288 | ->will($this->returnValue('$2a$04$5kq1mnYWbww8X.rIj7eOVOHXtvGw/peefjIcm0lDGxRTEjm9LnOae')); 289 | $this->user->expects($this->once()) 290 | ->method('getId') 291 | ->will($this->returnValue(1)); 292 | $this->user->expects($this->once()) 293 | ->method('getState') 294 | ->will($this->returnValue(1)); 295 | 296 | $this->storage->expects($this->any()) 297 | ->method('getNameSpace') 298 | ->will($this->returnValue('test')); 299 | 300 | $this->authEvent->expects($this->once()) 301 | ->method('setIdentity') 302 | ->with(1) 303 | ->will($this->returnValue($this->authEvent)); 304 | $this->authEvent->expects($this->once()) 305 | ->method('setCode') 306 | ->with(\Zend\Authentication\Result::SUCCESS) 307 | ->will($this->returnValue($this->authEvent)); 308 | $this->authEvent->expects($this->once()) 309 | ->method('setMessages') 310 | ->with(array('Authentication successful.')) 311 | ->will($this->returnValue($this->authEvent)); 312 | 313 | $this->db->setMapper($this->mapper); 314 | $this->db->setOptions($this->options); 315 | 316 | $result = $this->db->authenticate($this->authEvent); 317 | } 318 | 319 | protected function setAuthenticationEmail() 320 | { 321 | $this->mapper->expects($this->once()) 322 | ->method('findByEmail') 323 | ->with('zfc-user@zf-commons.io') 324 | ->will($this->returnValue($this->user)); 325 | 326 | $this->options->expects($this->once()) 327 | ->method('getAuthIdentityFields') 328 | ->will($this->returnValue(array('email'))); 329 | } 330 | 331 | protected function setAuthenticationUser() 332 | { 333 | $this->mapper->expects($this->once()) 334 | ->method('findByUsername') 335 | ->with('ZfcUser') 336 | ->will($this->returnValue($this->user)); 337 | 338 | $this->options->expects($this->once()) 339 | ->method('getAuthIdentityFields') 340 | ->will($this->returnValue(array('username'))); 341 | } 342 | 343 | protected function setAuthenticationCredentials($identity = 'ZfcUser', $credential = 'ZfcUserPassword') 344 | { 345 | $this->storage->expects($this->at(0)) 346 | ->method('read') 347 | ->will($this->returnValue(array('is_satisfied' => false))); 348 | 349 | $post = $this->getMock('Zend\Stdlib\Parameters'); 350 | $post->expects($this->at(0)) 351 | ->method('get') 352 | ->with('identity') 353 | ->will($this->returnValue($identity)); 354 | $post->expects($this->at(1)) 355 | ->method('get') 356 | ->with('credential') 357 | ->will($this->returnValue($credential)); 358 | 359 | $request = $this->getMock('Zend\Http\Request'); 360 | $request->expects($this->exactly(2)) 361 | ->method('getPost') 362 | ->will($this->returnValue($post)); 363 | 364 | $this->authEvent->expects($this->exactly(2)) 365 | ->method('getRequest') 366 | ->will($this->returnValue($request)); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/Storage/ZfcUserPdoFactoryTest.php: -------------------------------------------------------------------------------- 1 | serviceManager = $this->getServiceManager(); 12 | $this->serviceManager->setAllowOverride(true); 13 | $this->serviceManager->setService('Config', []); 14 | } 15 | 16 | public function testCreateServiceThrowsExceptionIfConfigurationIsMissing() 17 | { 18 | $this->setExpectedException('ZF\OAuth2\Adapter\Exception\RuntimeException'); 19 | 20 | $factory = new ZfcUserPdoFactory(); 21 | $obj = $factory->createService($this->serviceManager); 22 | } 23 | 24 | public function testCreateServiceDependsOnStorageBridgeService() 25 | { 26 | $this->serviceManager->setService('Config', [ 27 | 'zf-oauth2' => [ 28 | 'db' => [ 29 | 'dsn' => '', 30 | 'username' => '', 31 | 'password' => '', 32 | 'options' => array(), 33 | ], 34 | ], 35 | ]); 36 | 37 | $this->setExpectedExceptionRegExp( 38 | 'Zend\ServiceManager\Exception\ServiceNotFoundException', 39 | '{ldc-zfc-user-oauth2-storage-bridge}is' 40 | ); 41 | 42 | $factory = new ZfcUserPdoFactory(); 43 | $obj = $factory->createService($this->serviceManager); 44 | } 45 | 46 | public function testCreateServiceWillInjectCustomStorageSettings() 47 | { 48 | $this->serviceManager->setService('Config', [ 49 | 'zf-oauth2' => [ 50 | 'db' => [ 51 | 'dsn' => 'sqlite::memory:', 52 | 'username' => '', 53 | 'password' => '', 54 | 'options' => array(), 55 | ], 56 | 'storage_settings' => [ 57 | 'client_table' => 'foobar', 58 | ], 59 | ], 60 | ]); 61 | $this->serviceManager->setService( 62 | 'ldc-zfc-user-oauth2-storage-bridge', 63 | \Mockery::mock('LdcZfcUserOAuth2\Storage\ZfcUserStorageBridge') 64 | ); 65 | 66 | $factory = new ZfcUserPdoFactory(); 67 | $obj = $factory->createService($this->serviceManager); 68 | 69 | $expected = array( 70 | 'client_table' => 'foobar', 71 | 'access_token_table' => 'oauth_access_tokens', 72 | 'refresh_token_table' => 'oauth_refresh_tokens', 73 | 'code_table' => 'oauth_authorization_codes', 74 | 'user_table' => 'oauth_users', 75 | 'jwt_table' => 'oauth_jwt', 76 | 'scope_table' => 'oauth_scopes', 77 | 'public_key_table' => 'oauth_public_keys', 78 | ); 79 | $this->assertAttributeEquals($expected, 'config', $obj); 80 | } 81 | 82 | public function testCreateServiceHappyCase() 83 | { 84 | $this->serviceManager->setService('Config', [ 85 | 'zf-oauth2' => [ 86 | 'db' => [ 87 | 'dsn' => 'sqlite::memory:', 88 | 'username' => '', 89 | 'password' => '', 90 | 'options' => array(), 91 | ], 92 | ], 93 | ]); 94 | $this->serviceManager->setService('ldc-zfc-user-oauth2-storage-bridge', \Mockery::mock('LdcZfcUserOAuth2\Storage\ZfcUserStorageBridge')); 95 | 96 | $factory = new ZfcUserPdoFactory(); 97 | $obj = $factory->createService($this->serviceManager); 98 | 99 | $this->assertInstanceOf('LdcZfcUserOAuth2\Storage\ZfcUserPdo', $obj); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/Storage/ZfcUserPdoTest.php: -------------------------------------------------------------------------------- 1 | pdo = \Mockery::mock('PDO'); 12 | $this->pdo->shouldIgnoreMissing(); 13 | $this->bridge = \Mockery::mock('LdcZfcUserOAuth2\Storage\ZfcUserStorageBridge'); 14 | 15 | $this->service = new ZfcUserPdo($this->pdo, [], $this->bridge); 16 | } 17 | 18 | public function testCheckUserCredentialsProxiesToBridge() 19 | { 20 | $this->bridge->shouldReceive('checkUserCredentials') 21 | ->withArgs(['foo', 'bar']) 22 | ->andReturn(false) 23 | ->once(); 24 | 25 | $this->assertFalse($this->service->checkUserCredentials('foo', 'bar')); 26 | } 27 | 28 | public function testGetUserDetailsProxiesToBridge() 29 | { 30 | $result = ['id' => 'foo']; 31 | 32 | $this->bridge->shouldReceive('getUserDetails') 33 | ->withArgs(['foo']) 34 | ->andReturn($result) 35 | ->once(); 36 | 37 | $this->assertEquals($result, $this->service->getUserDetails('foo')); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/Storage/ZfcUserStorageBridgeFactoryTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('get')->with('zfcuser_user_mapper')->once()->andReturn(\Mockery::mock('ZfcUser\Mapper\UserInterface')); 13 | $sm->shouldReceive('get')->with('zfcuser_auth_service')->once()->andReturn(\Mockery::mock('Zend\Authentication\AuthenticationServiceInterface')); 14 | $sm->shouldReceive('get')->with('ZfcUser\Authentication\Adapter\AdapterChain')->once()->andReturn(\Mockery::mock('ZfcUser\Authentication\Adapter\AdapterChain')); 15 | $sm->shouldReceive('get')->with('zfcuser_module_options')->once()->andReturn(\Mockery::mock('ZfcUser\Options\ModuleOptions')); 16 | 17 | $factory = new ZfcUserStorageBridgeFactory(); 18 | $obj = $factory->createService($sm); 19 | 20 | $this->assertInstanceOf('LdcZfcUserOAuth2\Storage\ZfcUserStorageBridge', $obj); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/Storage/ZfcUserStorageBridgeTest.php: -------------------------------------------------------------------------------- 1 | userMapper = \Mockery::mock('ZfcUser\Mapper\UserInterface'); 13 | $this->authService = \Mockery::mock('Zend\Authentication\AuthenticationServiceInterface'); 14 | $this->authAdapter = \Mockery::mock('ZfcUser\Authentication\Adapter\AdapterChain'); 15 | $this->authOptions = \Mockery::mock('ZfcUser\Options\AuthenticationOptionsInterface'); 16 | 17 | $this->mockUserData = ['user_id' => 42, 'email' => 'foo@bar.com', 'username' => 'foo', 'display_name' => '', 'state' => null]; 18 | $this->userHydrator = new \ZfcUser\Mapper\UserHydrator(); 19 | $this->mockUserObject = new \ZfcUser\Entity\User(); 20 | $this->userHydrator->hydrate($this->mockUserData, $this->mockUserObject); 21 | 22 | $this->service = new ZfcUserStorageBridge( 23 | $this->userMapper, 24 | $this->authService, 25 | $this->authAdapter, 26 | $this->authOptions 27 | ); 28 | } 29 | 30 | public function testCheckUserCredentialsHappyCaseAuthSuccess() 31 | { 32 | $this->authAdapter->shouldIgnoreMissing(); 33 | 34 | $authResult = new AuthResult(AuthResult::SUCCESS, []); 35 | $this->authService->shouldReceive('authenticate') 36 | ->once() 37 | ->with($this->authAdapter) 38 | ->andReturn($authResult); 39 | 40 | $this->assertTrue($this->service->checkUserCredentials('foo', 'bar')); 41 | } 42 | 43 | public function testCheckUserCredentialsShortCircuitsIfAuthPrepFails() 44 | { 45 | $this->authAdapter->shouldReceive('prepareForAuthentication') 46 | ->once() 47 | ->andReturn(new \Zend\Stdlib\Response()); 48 | 49 | $this->authService->shouldReceive('authenticate') 50 | ->never(); 51 | 52 | $this->assertFalse($this->service->checkUserCredentials('foo', 'bar')); 53 | } 54 | 55 | public function testCheckUserCredentialsHappyCaseAuthFailure() 56 | { 57 | $this->authAdapter->shouldIgnoreMissing(); 58 | 59 | $authResult = new AuthResult(AuthResult::FAILURE_UNCATEGORIZED, []); 60 | $this->authService->shouldReceive('authenticate') 61 | ->once() 62 | ->with($this->authAdapter) 63 | ->andReturn($authResult); 64 | 65 | $this->assertFalse($this->service->checkUserCredentials('foo', 'bar')); 66 | } 67 | 68 | public function testGetUserDetailsEmailHappyCase() 69 | { 70 | $this->authOptions->shouldReceive('getAuthIdentityFields') 71 | ->andReturn(['email']); 72 | 73 | $this->userMapper->shouldReceive('findByEmail') 74 | ->with($this->mockUserData['email']) 75 | ->once() 76 | ->andReturn($this->mockUserObject); 77 | 78 | $result = $this->service->getUserDetails($this->mockUserData['email']); 79 | $this->verifyUserResult($result); 80 | } 81 | 82 | public function testGetUserDetailsUsernameHappyCase() 83 | { 84 | $this->authOptions->shouldReceive('getAuthIdentityFields') 85 | ->andReturn(['username']); 86 | 87 | $this->userMapper->shouldReceive('findByUsername') 88 | ->with($this->mockUserData['username']) 89 | ->once() 90 | ->andReturn($this->mockUserObject); 91 | 92 | $result = $this->service->getUserDetails($this->mockUserData['username']); 93 | $this->verifyUserResult($result); 94 | } 95 | 96 | public function testGetUserDetailsRepectsOrderingOfIdentitfyFieldsOnSecondFieldSuccess() 97 | { 98 | $this->authOptions->shouldReceive('getAuthIdentityFields') 99 | ->andReturn(['username', 'email']); 100 | 101 | $this->userMapper->shouldReceive('findByUsername') 102 | ->once() 103 | ->andReturn(null); 104 | 105 | $this->userMapper->shouldReceive('findByEmail') 106 | ->once() 107 | ->andReturn($this->mockUserObject); 108 | 109 | $result = $this->service->getUserDetails($this->mockUserData['email']); 110 | $this->verifyUserResult($result); 111 | } 112 | 113 | public function testGetUserDetailsRepectsOrderingOfIdentitfyFieldsOnFirstFieldSuccess() 114 | { 115 | $this->authOptions->shouldReceive('getAuthIdentityFields') 116 | ->andReturn(['username', 'email']); 117 | 118 | $this->userMapper->shouldReceive('findByUsername') 119 | ->once() 120 | ->andReturn($this->mockUserObject); 121 | 122 | $this->userMapper->shouldReceive('findByEmail') 123 | ->never(); 124 | 125 | $result = $this->service->getUserDetails($this->mockUserData['username']); 126 | $this->verifyUserResult($result); 127 | } 128 | 129 | public function testGetUserDetailsRepectsOrderingOfIdentitfyFieldsUserNotFound() 130 | { 131 | $this->authOptions->shouldReceive('getAuthIdentityFields') 132 | ->andReturn(['username', 'email']); 133 | 134 | $this->userMapper->shouldReceive('findByUsername') 135 | ->once() 136 | ->andReturn(null); 137 | 138 | $this->userMapper->shouldReceive('findByEmail') 139 | ->once() 140 | ->andReturn(null); 141 | 142 | $result = $this->service->getUserDetails($this->mockUserData['username']); 143 | $this->assertNull($result); 144 | } 145 | 146 | protected function verifyUserResult($result) 147 | { 148 | $this->assertInternalType('array', $result); 149 | foreach ($this->mockUserData as $key => $value) { 150 | $this->assertArrayHasKey($key, $result); 151 | $this->assertEquals($value, $result[$key]); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/LdcZfcUserOAuth2Test/TestCase.php: -------------------------------------------------------------------------------- 1 | setService('ApplicationConfig', $configuration); 51 | $serviceManager->setFactory('ServiceListener', 'Zend\Mvc\Service\ServiceListenerFactory'); 52 | 53 | return $serviceManager; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/TestConfiguration.php.dist: -------------------------------------------------------------------------------- 1 | array( 4 | ), 5 | 'module_listener_options' => array( 6 | 'config_glob_paths' => array(), 7 | 'module_paths' => array( 8 | ), 9 | ), 10 | ); 11 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('LdcZfcUserOAuth2Test\\', __DIR__); 21 | 22 | if (file_exists(__DIR__.'/TestConfiguration.php')) { 23 | $config = require __DIR__.'/TestConfiguration.php'; 24 | } else { 25 | $config = require __DIR__.'/TestConfiguration.php.dist'; 26 | } 27 | 28 | TestCase::setConfiguration($config); 29 | unset($files, $file, $loader, $config); 30 | --------------------------------------------------------------------------------