From 2090aaab57699e103b72d1ecb7e66b07fcabc2fc Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Wed, 4 Nov 2015 17:37:38 +0200 Subject: [PATCH 1/7] Redis Client added --- composer.json | 3 +- config/autoload/auth.local.php.dist | 24 +++++++++++++ .../OAuth/Storage/Adapter/RedisAdapter.php | 8 +++++ .../Storage/Adapter/RedisAdapterFactory.php | 35 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 config/autoload/auth.local.php.dist create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapterFactory.php diff --git a/composer.json b/composer.json index 12f4b8d..031f427 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "zfcampus/zf-content-validation": "~1.0", "zfcampus/zf-hal": "~1.0", "zfcampus/zf-mvc-auth": "~1.0", - "zfcampus/zf-rest": "~1.0" + "zfcampus/zf-rest": "~1.0", + "predis/predis": "1.0.3" }, "require-dev": { diff --git a/config/autoload/auth.local.php.dist b/config/autoload/auth.local.php.dist new file mode 100644 index 0000000..68cc245 --- /dev/null +++ b/config/autoload/auth.local.php.dist @@ -0,0 +1,24 @@ + [ + // You can specify a general adapter for all data operations. + // Or you can specify one for each data class. + 'storage' => [ + 'client_credentials' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + 'user_credentials' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + 'access_token' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + 'scope' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + 'authorization_code' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + 'refresh_token' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, + ], + ], + 'service_manager' => [ + 'factories' => [ + \Api\OAuth\Storage\Adapter\RedisAdapter::class => \Api\OAuth\Storage\Adapter\RedisAdapterFactory::class, + ], + ], +]; diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php b/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php new file mode 100644 index 0000000..93dc91d --- /dev/null +++ b/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php @@ -0,0 +1,8 @@ + + */ +namespace Api\OAuth\Storage\Adapter\Factory; + +use Api\OAuth\Storage\Adapter\RedisAdapter; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +/** + * Authentication service factory + */ +class RedisAdapterFactory implements FactoryInterface +{ + /** + * Simply returns Redis Adapter Storage + * + * @return RedisAdapter + */ + public function createService(ServiceLocatorInterface $services) + { + $redisConfig = $sm->get('Config'); + if (!isset($redisConfig['caches']['core.cache.redis']['adapter']['options']['server'])) { + throw new \Exception('Missing redis configuration!'); + } + + $redisClient = new \Predis\Client($redisConfig['caches']['core.cache.redis']['adapter']['options']['server']) + + return new RedisAdapter($redisClient); + } +} From 419dc5f9827a9720e7351abf392e3c173b5aa16a Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Tue, 24 Nov 2015 23:16:58 +0200 Subject: [PATCH 2/7] Bshaffer OAuth2 implementation with Pdo adapter and Redis Adapter --- config/autoload/auth.local.php.dist | 15 --- module/Api/Module.php | 71 ++++++++++++ module/Api/config/module.config.php | 102 ++++++++++++++++-- .../Api/Exception/ApplicationException.php | 13 +++ .../Api/src/Api/Exception/AuthException.php | 13 +++ .../Api/src/Api/Exception/CacheException.php | 13 +++ .../src/Api/Exception/DatabaseException.php | 13 +++ .../Api/src/Api/OAuth/Storage/Adapter/Pdo.php | 85 +++++++++++++++ .../Api/OAuth/Storage/Adapter/PdoFactory.php | 34 ++++++ .../src/Api/OAuth/Storage/Adapter/Redis.php | 24 +++++ .../OAuth/Storage/Adapter/RedisAdapter.php | 8 -- .../Storage/Adapter/RedisAdapterFactory.php | 35 ------ .../OAuth/Storage/Adapter/RedisFactory.php | 40 +++++++ module/Core/src/Core/Fixture/BaseFixture.php | 2 +- .../src/Core/Service/RegistrationService.php | 28 ++++- 15 files changed, 423 insertions(+), 73 deletions(-) create mode 100644 module/Api/src/Api/Exception/ApplicationException.php create mode 100644 module/Api/src/Api/Exception/AuthException.php create mode 100644 module/Api/src/Api/Exception/CacheException.php create mode 100644 module/Api/src/Api/Exception/DatabaseException.php create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/Redis.php delete mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php delete mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapterFactory.php create mode 100644 module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php diff --git a/config/autoload/auth.local.php.dist b/config/autoload/auth.local.php.dist index 68cc245..c31e1a1 100644 --- a/config/autoload/auth.local.php.dist +++ b/config/autoload/auth.local.php.dist @@ -5,20 +5,5 @@ return [ 'zf-oauth2' => [ - // You can specify a general adapter for all data operations. - // Or you can specify one for each data class. - 'storage' => [ - 'client_credentials' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - 'user_credentials' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - 'access_token' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - 'scope' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - 'authorization_code' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - 'refresh_token' => \Api\OAuth\Storage\Adapter\RedisAdapter::class, - ], - ], - 'service_manager' => [ - 'factories' => [ - \Api\OAuth\Storage\Adapter\RedisAdapter::class => \Api\OAuth\Storage\Adapter\RedisAdapterFactory::class, - ], ], ]; diff --git a/module/Api/Module.php b/module/Api/Module.php index fc40123..5d5473e 100644 --- a/module/Api/Module.php +++ b/module/Api/Module.php @@ -1,6 +1,12 @@ getApplication()->getEventManager(); + $eventManager->attach( + MvcAuthEvent::EVENT_AUTHENTICATION_POST, + array($this, 'bindIdentityModel'), + 1 + ); + + $moduleRouteListener = new ModuleRouteListener(); + $moduleRouteListener->attach($eventManager); + + + $event->getApplication()->getEventManager()->attach( + MvcEvent::EVENT_DISPATCH_ERROR, + array($this, 'dispatchError'), + 9000 + ); + } + + public function bindIdentityModel(MvcAuthEvent $event) + { + $identity = $event->getIdentity(); + + if (!!$identity) { + // Manipulate the identity here... Switch it with or add your own model etc. + $event->setIdentity($identity); + } + + return true; + } + + public function dispatchError(MvcEvent $event) + { + $problem = null; + if ($event->isError()) { + $exception = $event->getParam("exception"); + + // There are some other errors like that : + // "error-controller-cannot-dispatch", + // "error-controller-invalid", + // "error-controller-not-found", + // "error-router-no-match", + if ($event->getError() === 'error-controller-not-found') { + $problem = new ApiProblem(404, "Endpoint controller not found!"); + } elseif ($event->getError() === 'error-router-no-match') { + $problem = new ApiProblem(404, "Not found!"); + } elseif ($exception instanceof \Exception) { + $className = explode('\\', get_class($exception)); + $problem = new ApiProblem($exception->getCode(), end($className) . ' error.'); + $logger = $event->getTarget()->getServiceLocator()->get('logger'); + $logger->err($exception->getMessage(), array( + 'controller' => $event->getControllerClass(), + )); + } + } else { + $problem = new ApiProblem(500, "Unknown Error!"); + } + + $response = new ApiProblemResponse($problem); + $event->stopPropagation(); + + return $response; + } } diff --git a/module/Api/config/module.config.php b/module/Api/config/module.config.php index 95818cc..81965d1 100644 --- a/module/Api/config/module.config.php +++ b/module/Api/config/module.config.php @@ -2,7 +2,10 @@ return [ 'service_manager' => [ 'factories' => [ - 'Api\V1\User\UserResource' => 'Api\V1\User\UserResourceFactory', + Api\V1\User\UserResource::class => Api\V1\User\UserResourceFactory::class, + Api\OAuth\Storage\Adapter\Redis::class => Api\OAuth\Storage\Adapter\RedisFactory::class, + Api\OAuth\Storage\Adapter\Pdo::class => Api\OAuth\Storage\Adapter\PdoFactory::class, + ZF\OAuth2\Service\OAuth2Server::class => ZF\MvcAuth\Factory\NamedOAuth2ServerFactory::class, ], ], 'router' => array( @@ -15,7 +18,37 @@ 'controller' => 'Api\V1\User\Controller', ), ), - ), + ), // end of api.rest.user + 'oauth' => array( + 'options' => array( + 'spec' => '%oauth%', + 'regex' => '(?P(/oauth))', + ), + 'type' => 'regex', // regex type will be remove. + 'child_routes' => array( + 'token' => array( + 'type' => 'Zend\Mvc\Router\Http\Literal', + 'options' => array( + 'route' => '/token', + 'defaults' => array( + 'action' => 'token', + ), + ), + ), + 'resource' => array( + 'type' => 'Zend\Mvc\Router\Http\Literal', + 'options' => array( + 'route' => '', + ), + ), + 'code' => array( + 'type' => 'Zend\Mvc\Router\Http\Literal', + 'options' => array( + 'route' => '', + ), + ), + ) + ), // end of oauth ), ), 'zf-versioning' => array( @@ -25,7 +58,7 @@ ), 'zf-rest' => array( 'Api\V1\User\Controller' => array( - 'listener' => 'Api\V1\User\UserResource', + 'listener' => Api\V1\User\UserResource::class, 'route_name' => 'api.rest.user', 'route_identifier_name' => 'user_id', 'collection_name' => 'user', @@ -38,8 +71,8 @@ 'collection_query_whitelist' => array(), 'page_size' => 25, 'page_size_param' => null, - 'entity_class' => 'Api\V1\User\UserEntity', - 'collection_class' => 'Api\V1\User\UserCollection', + 'entity_class' => Api\V1\User\UserEntity::class, + 'collection_class' => Api\V1\User\UserCollection::class, 'service_name' => 'User', ), ), @@ -67,7 +100,7 @@ 'entity_identifier_name' => 'id', 'route_name' => 'api.rest.user', 'route_identifier_name' => 'user_id', - 'hydrator' => 'Zend\Stdlib\Hydrator\ObjectProperty', + 'hydrator' => Zend\Stdlib\Hydrator\ObjectProperty::class, ), 'Api\V1\User\UserCollection' => array( 'entity_identifier_name' => 'id', @@ -87,8 +120,59 @@ ), ), - 'zf-mvc-auth' => array( - 'authorization' => array( + 'zf-mvc-auth' => [ + 'authorization' => [ + 'Api\V1\User\Controller' => [ + 'collection' => [ + 'GET' => true, + 'POST' => true, + 'PUT' => false, + 'PATCH' => false, + 'DELETE' => false, + ], + 'entity' => [ + 'GET' => true, + 'POST' => true, + 'PUT' => false, + 'PATCH' => false, + 'DELETE' => false, + ], + ], + ], + 'authentication' => [ + 'adapters' => [ + 'zingatOAuth2' => [ + 'adapter' => ZF\MvcAuth\Authentication\OAuth2Adapter::class, + 'storage' => [] + ], + ], + 'map' => [ + 'Api\V1' => 'zingatOAuth2', + ], + 'access_lifetime' => 7200, + ], + 'deny_by_default' => true, + ], + 'zf-oauth2' => [ + 'storage' => [ + 'client_credentials' => Api\OAuth\Storage\Adapter\Pdo::class, + 'user_credentials' => Api\OAuth\Storage\Adapter\Pdo::class, + 'access_token' => \Api\OAuth\Storage\Adapter\Redis::class, + 'scope' => Api\OAuth\Storage\Adapter\Pdo::class, + 'authorization_code' => \Api\OAuth\Storage\Adapter\Redis::class, + 'refresh_token' => \Api\OAuth\Storage\Adapter\Redis::class, + ], + 'grant_types' => [ + 'client_credentials' => true, // Default Value + 'authorization_code' => true, // Default Value + 'password' => true, // Default Value + 'refresh_token' => true, // Default Value + 'jwt' => false, + ], + 'allow_implicit' => false, + 'options' => array( + 'always_issue_new_refresh_token' => true, ), - ), + 'access_lifetime' => 7200, + ], ]; diff --git a/module/Api/src/Api/Exception/ApplicationException.php b/module/Api/src/Api/Exception/ApplicationException.php new file mode 100644 index 0000000..3708cba --- /dev/null +++ b/module/Api/src/Api/Exception/ApplicationException.php @@ -0,0 +1,13 @@ + + */ +namespace Api\Exception; + +class ApplicationException extends \Exception +{ + // Maybe in future, in here, we should create some static factory methods +} diff --git a/module/Api/src/Api/Exception/AuthException.php b/module/Api/src/Api/Exception/AuthException.php new file mode 100644 index 0000000..03d922d --- /dev/null +++ b/module/Api/src/Api/Exception/AuthException.php @@ -0,0 +1,13 @@ + + */ +namespace Api\Exception; + +class AuthException extends \Exception +{ + // Maybe in future, in here, we should create some static factory methods +} diff --git a/module/Api/src/Api/Exception/CacheException.php b/module/Api/src/Api/Exception/CacheException.php new file mode 100644 index 0000000..9c909c4 --- /dev/null +++ b/module/Api/src/Api/Exception/CacheException.php @@ -0,0 +1,13 @@ + + */ +namespace Api\Exception; + +class CacheException extends \Exception +{ + // Maybe in future, in here, we should create some static factory methods +} diff --git a/module/Api/src/Api/Exception/DatabaseException.php b/module/Api/src/Api/Exception/DatabaseException.php new file mode 100644 index 0000000..46926d4 --- /dev/null +++ b/module/Api/src/Api/Exception/DatabaseException.php @@ -0,0 +1,13 @@ + + */ +namespace Api\Exception; + +class DatabaseException extends \Exception +{ + // Maybe in future, in here, we should create some static factory methods +} diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php new file mode 100644 index 0000000..2aaf397 --- /dev/null +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php @@ -0,0 +1,85 @@ + + */ +namespace Api\OAuth\Storage\Adapter; + +use OAuth2\Storage\Pdo as PdoStorage; + +/** + * Following grant_types implemented + * + * - user_credentials + * - scope + * + * Database Table Scripts: + * CREATE TABLE oauth_scopes (scope TEXT, is_default BOOLEAN); + * CREATE TABLE oauth_clients (client_id VARCHAR(80) NOT NULL, client_secret VARCHAR(80), redirect_uri VARCHAR(2000) NOT NULL, grant_types VARCHAR(80), scope VARCHAR(100), user_id VARCHAR(80), CONSTRAINT clients_client_id_pk PRIMARY KEY (client_id)); + * + * Example Data: + * + * INSERT INTO oauth_scopes (scope, is_default) VALUES ('default', 1); + * INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, scope, user_id) + * VALUES ('TestClient', 'TestSecret', 'http://api.boilerplate.local', 'password client_credentials, refresh_token', 'default', 'admin@boilerplate.local'); + * + * Redis Data Example Run on Command Line Interface: + * + * SET oauth_clients:TestClient '{"client_id":"TestClient","client_secret":"TestSecret","username":"oytun","password":"tez"}' + * + */ +class Pdo extends PdoStorage +{ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function checkPassword($user, $password) + { + return \Core\Service\RegistrationService::verifyRawPassword($user['password'], $password); + } + + /** + * {@inheritdoc} + */ + public function getUser($username) + { + $stmt = $this->db->prepare('SELECT * from users where email = :username'); + $stmt->execute(array('username' => $username)); + + if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username + ), $userInfo); + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + $firstNameSurname = (!empty($firstName) ? $firstName : '') . (!empty($lastName) ? ' ' .$lastName : ''); + // do not store in plaintext + $password = sha1($password); + + // if it exists, update it. + if ($this->getUser($username)) { + $stmt = $this->db->prepare('UPDATE users SET password=:password, name_surname=:firstNameSurname where email = :username'); + } else { + $stmt = $this->db->prepare('INSERT INTO users (username, password, name_surname) VALUES (:username, :password, :firstNameSurname)'); + } + + return $stmt->execute(compact('username', 'password', 'firstNameSurname')); + } +} diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php b/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php new file mode 100644 index 0000000..8036ef8 --- /dev/null +++ b/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php @@ -0,0 +1,34 @@ + + */ +namespace Api\OAuth\Storage\Adapter; + +use Api\Exception\DatabaseException; +use Api\Oauth\Storage\Adapter\Pdo as PdoAdapter; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +/** + * Pdo Adapter Factory + */ +class PdoFactory implements FactoryInterface +{ + /** + * Simply returns Pdo Adapter Storage + * + * @throws DatabaseException; + * @return PdoAdapter + */ + public function createService(ServiceLocatorInterface $services) + { + try { + return new PdoAdapter($services->get('doctrine.entitymanager.orm_default')->getConnection()->getWrappedConnection()); + } catch (\Exception $e) { + throw new DatabaseException('Database connection did not created!', 500, $e); + } + } +} diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php b/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php new file mode 100644 index 0000000..11d4888 --- /dev/null +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php @@ -0,0 +1,24 @@ + + */ +namespace Api\OAuth\Storage\Adapter; + +use OAuth2\Storage\Redis as RedisStorage; + +/** + * Redis Adapter Class + * We use Redis currently following grant_types: + * - access_token, + * - authorization_code, + * - refresh_token, + * + * @see Api/config/module.config.php > $['zf-oauth2']['storage'] + * + */ +class Redis extends RedisStorage +{ +} diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php b/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php deleted file mode 100644 index 93dc91d..0000000 --- a/module/Api/src/Api/OAuth/Storage/Adapter/RedisAdapter.php +++ /dev/null @@ -1,8 +0,0 @@ - - */ -namespace Api\OAuth\Storage\Adapter\Factory; - -use Api\OAuth\Storage\Adapter\RedisAdapter; -use Zend\ServiceManager\FactoryInterface; -use Zend\ServiceManager\ServiceLocatorInterface; - -/** - * Authentication service factory - */ -class RedisAdapterFactory implements FactoryInterface -{ - /** - * Simply returns Redis Adapter Storage - * - * @return RedisAdapter - */ - public function createService(ServiceLocatorInterface $services) - { - $redisConfig = $sm->get('Config'); - if (!isset($redisConfig['caches']['core.cache.redis']['adapter']['options']['server'])) { - throw new \Exception('Missing redis configuration!'); - } - - $redisClient = new \Predis\Client($redisConfig['caches']['core.cache.redis']['adapter']['options']['server']) - - return new RedisAdapter($redisClient); - } -} diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php b/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php new file mode 100644 index 0000000..54ff301 --- /dev/null +++ b/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php @@ -0,0 +1,40 @@ + + */ +namespace Api\OAuth\Storage\Adapter; + +use Api\Exception\CacheException; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +/** + * Redis Adapter Service Factory + */ +class RedisFactory implements FactoryInterface +{ + /** + * Simply returns Redis Adapter Storage + * + * @throws CacheException + * @return Redis + */ + public function createService(ServiceLocatorInterface $services) + { + $config = $services->get('Config'); + if (!isset($config['caches']['core.cache.redis']['adapter']['options']['server'])) { + throw new CacheException('Missing redis configuration!'); + } + + try { + $redisClient = new \Predis\Client($config['caches']['core.cache.redis']['adapter']['options']['server']); + + return new Redis($redisClient); + } catch (\Exception $e) { + throw new CacheException('Cache client exception', 500, $e); + } + } +} diff --git a/module/Core/src/Core/Fixture/BaseFixture.php b/module/Core/src/Core/Fixture/BaseFixture.php index 5508f01..4f49de7 100644 --- a/module/Core/src/Core/Fixture/BaseFixture.php +++ b/module/Core/src/Core/Fixture/BaseFixture.php @@ -54,7 +54,7 @@ public function getOrder() * Overrided by all derived childs. * * @param ObjectManager $manager - * @return voşid + * @return void */ public function load(ObjectManager $manager) { diff --git a/module/Core/src/Core/Service/RegistrationService.php b/module/Core/src/Core/Service/RegistrationService.php index f5a737f..d1c57b4 100644 --- a/module/Core/src/Core/Service/RegistrationService.php +++ b/module/Core/src/Core/Service/RegistrationService.php @@ -8,9 +8,9 @@ namespace Core\Service; use Core\Entity\User as UserEntity; -use Zend\Authentication\AuthenticationService; -use DoctrineModule\Persistence\ObjectManagerAwareInterface; use Core\Traits\ObjectManagerAwareTrait; +use DoctrineModule\Persistence\ObjectManagerAwareInterface; +use Zend\Authentication\AuthenticationService; class RegistrationService extends AbstractService implements ObjectManagerAwareInterface { @@ -62,14 +62,14 @@ public function login($email, $password, $rememberMe = false) * * @static * - * @param UserEntity $user + * @param string $passwordHashed * @param string $passwordGiven * * @return boolean */ - public static function verifyPassword(UserEntity $user, $passwordGiven) + public static function verifyRawPassword($passwordHashed, $passwordGiven) { - $verified = password_verify($passwordGiven, $user->getPassword()); + $verified = password_verify($passwordGiven, $passwordHashed); if ($verified) { // You may also want to check user status here. @@ -80,6 +80,24 @@ public static function verifyPassword(UserEntity $user, $passwordGiven) return false; } + /** + * Verifies given password by given user credentials (using password salt) + * when user trying to login the system first time. + * + * Called by doctrinemodule's authentication configuration on login. + * + * @static + * + * @param UserEntity $user + * @param string $passwordGiven + * + * @return boolean + */ + public static function verifyPassword(UserEntity $user, $passwordGiven) + { + return self::verifyRawPassword($user->getPassword(), $passwordGiven); + } + /** * Properly hashes password using bcrypt. * From 3fbe501d25e88c912e38e2623b85299ee7840214 Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Wed, 25 Nov 2015 16:09:42 +0200 Subject: [PATCH 3/7] removed unused setUser method from Pdo adapter Update module.config.php --- module/Api/config/module.config.php | 6 +++--- module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php | 15 ++------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/module/Api/config/module.config.php b/module/Api/config/module.config.php index 81965d1..a2b0bed 100644 --- a/module/Api/config/module.config.php +++ b/module/Api/config/module.config.php @@ -155,10 +155,10 @@ ], 'zf-oauth2' => [ 'storage' => [ - 'client_credentials' => Api\OAuth\Storage\Adapter\Pdo::class, - 'user_credentials' => Api\OAuth\Storage\Adapter\Pdo::class, + 'client_credentials' => \Api\OAuth\Storage\Adapter\Pdo::class, + 'user_credentials' => \Api\OAuth\Storage\Adapter\Pdo::class, 'access_token' => \Api\OAuth\Storage\Adapter\Redis::class, - 'scope' => Api\OAuth\Storage\Adapter\Pdo::class, + 'scope' => \Api\OAuth\Storage\Adapter\Pdo::class, 'authorization_code' => \Api\OAuth\Storage\Adapter\Redis::class, 'refresh_token' => \Api\OAuth\Storage\Adapter\Redis::class, ], diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php index 2aaf397..82edae9 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php @@ -54,7 +54,7 @@ protected function checkPassword($user, $password) */ public function getUser($username) { - $stmt = $this->db->prepare('SELECT * from users where email = :username'); + $stmt = $this->db->prepare('SELECT id, email, language from users where email = :username'); $stmt->execute(array('username' => $username)); if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { @@ -69,17 +69,6 @@ public function getUser($username) public function setUser($username, $password, $firstName = null, $lastName = null) { - $firstNameSurname = (!empty($firstName) ? $firstName : '') . (!empty($lastName) ? ' ' .$lastName : ''); - // do not store in plaintext - $password = sha1($password); - - // if it exists, update it. - if ($this->getUser($username)) { - $stmt = $this->db->prepare('UPDATE users SET password=:password, name_surname=:firstNameSurname where email = :username'); - } else { - $stmt = $this->db->prepare('INSERT INTO users (username, password, name_surname) VALUES (:username, :password, :firstNameSurname)'); - } - - return $stmt->execute(compact('username', 'password', 'firstNameSurname')); + throw new \Exception('You can not create user this way.'); } } From 0f37865b779ba2408a4907665ad4313644b549be Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Fri, 27 Nov 2015 10:02:58 +0200 Subject: [PATCH 4/7] bindIdentityModel method name changed deny_by_default config parameter fixed Pdo adaper bug fix --- module/Api/Module.php | 4 ++-- module/Api/config/module.config.php | 21 ++++++------------- .../Api/src/Api/OAuth/Storage/Adapter/Pdo.php | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/module/Api/Module.php b/module/Api/Module.php index 5d5473e..fc77e3c 100644 --- a/module/Api/Module.php +++ b/module/Api/Module.php @@ -30,7 +30,7 @@ public function onBootstrap(MvcEvent $event) $eventManager = $event->getApplication()->getEventManager(); $eventManager->attach( MvcAuthEvent::EVENT_AUTHENTICATION_POST, - array($this, 'bindIdentityModel'), + array($this, 'eventAuthenticationPost'), 1 ); @@ -45,7 +45,7 @@ public function onBootstrap(MvcEvent $event) ); } - public function bindIdentityModel(MvcAuthEvent $event) + public function eventAuthenticationPost(MvcAuthEvent $event) { $identity = $event->getIdentity(); diff --git a/module/Api/config/module.config.php b/module/Api/config/module.config.php index a2b0bed..a2b3005 100644 --- a/module/Api/config/module.config.php +++ b/module/Api/config/module.config.php @@ -122,20 +122,12 @@ ), 'zf-mvc-auth' => [ 'authorization' => [ - 'Api\V1\User\Controller' => [ - 'collection' => [ - 'GET' => true, - 'POST' => true, - 'PUT' => false, - 'PATCH' => false, - 'DELETE' => false, - ], - 'entity' => [ - 'GET' => true, - 'POST' => true, - 'PUT' => false, - 'PATCH' => false, - 'DELETE' => false, + 'deny_by_default' => true, + 'ZF\OAuth2\Controller\Auth' => [ + 'actions' => [ + 'token' => [ + 'POST' => false, + ], ], ], ], @@ -151,7 +143,6 @@ ], 'access_lifetime' => 7200, ], - 'deny_by_default' => true, ], 'zf-oauth2' => [ 'storage' => [ diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php index 82edae9..ffa85db 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php @@ -54,7 +54,7 @@ protected function checkPassword($user, $password) */ public function getUser($username) { - $stmt = $this->db->prepare('SELECT id, email, language from users where email = :username'); + $stmt = $this->db->prepare('SELECT id, email, password, language from users where email = :username'); $stmt->execute(array('username' => $username)); if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { From a1a82cc007ed9700c6441ff0401866269bf77d99 Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Mon, 30 Nov 2015 18:05:53 +0200 Subject: [PATCH 5/7] inject identity information to Resources. --- module/Api/Module.php | 20 +++++++---- .../Api/src/Api/OAuth/Storage/Adapter/Pdo.php | 33 +++++++++++++++++-- .../Api/OAuth/Storage/Adapter/PdoFactory.php | 5 ++- .../src/Api/OAuth/Storage/Adapter/Redis.php | 14 ++++++++ .../OAuth/Storage/Adapter/RedisFactory.php | 2 +- module/Api/src/Api/V1/User/UserResource.php | 4 +++ 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/module/Api/Module.php b/module/Api/Module.php index fc77e3c..7403842 100644 --- a/module/Api/Module.php +++ b/module/Api/Module.php @@ -28,16 +28,16 @@ public function getAutoloaderConfig() public function onBootstrap(MvcEvent $event) { $eventManager = $event->getApplication()->getEventManager(); + $eventManager->attach( MvcAuthEvent::EVENT_AUTHENTICATION_POST, array($this, 'eventAuthenticationPost'), - 1 + 900 ); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); - $event->getApplication()->getEventManager()->attach( MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'dispatchError'), @@ -47,14 +47,22 @@ public function onBootstrap(MvcEvent $event) public function eventAuthenticationPost(MvcAuthEvent $event) { - $identity = $event->getIdentity(); + $identity = $event->getIdentity(); + $oauth2Closure = $event->getMvcEvent() + ->getApplication() + ->getServiceManager() + ->get(\ZF\OAuth2\Service\OAuth2Server::class); if (!!$identity) { - // Manipulate the identity here... Switch it with or add your own model etc. + $userData = $oauth2Closure()->getStorage('user_credentials')->getUser($identity->getName()); + + $identity = new \ZF\MvcAuth\Identity\AuthenticatedIdentity($userData); $event->setIdentity($identity); + //MvcEvent did not understand when manipulated MvcAuthEvent identity + $event->getMvcEvent()->setParam('ZF\MvcAuth\Identity', $identity); } - return true; + return $event; } public function dispatchError(MvcEvent $event) @@ -75,7 +83,7 @@ public function dispatchError(MvcEvent $event) } elseif ($exception instanceof \Exception) { $className = explode('\\', get_class($exception)); $problem = new ApiProblem($exception->getCode(), end($className) . ' error.'); - $logger = $event->getTarget()->getServiceLocator()->get('logger'); + $logger = $event->getTarget()->getServiceManager()->get('logger'); $logger->err($exception->getMessage(), array( 'controller' => $event->getControllerClass(), )); diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php index ffa85db..6717bdc 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Pdo.php @@ -7,6 +7,7 @@ */ namespace Api\OAuth\Storage\Adapter; +use Api\OAuth\Storage\Adapter\Redis as RedisAdapter; use OAuth2\Storage\Pdo as PdoStorage; /** @@ -27,11 +28,27 @@ * * Redis Data Example Run on Command Line Interface: * - * SET oauth_clients:TestClient '{"client_id":"TestClient","client_secret":"TestSecret","username":"oytun","password":"tez"}' + * SET oauth_clients:TestClient '{"client_id":"TestClient","client_secret":"TestSecret","username":"test","password":"pass"}' * */ class Pdo extends PdoStorage { + /** + * + * @var RedisAdapter Redis Adapter + */ + protected $redis; + + public function setRedis($redis) + { + $this->redis = $redis; + } + + public function getRedis() + { + return $this->redis; + } + public function checkUserCredentials($username, $password) { if ($user = $this->getUser($username)) { @@ -54,14 +71,26 @@ protected function checkPassword($user, $password) */ public function getUser($username) { - $stmt = $this->db->prepare('SELECT id, email, password, language from users where email = :username'); + $userInfo = $this->getRedis()->getUserData($username); + if ($userInfo) { + $userInfo['from_cache'] = true; + + return array_merge(array( + 'user_id' => $username + ), $userInfo); + } + + $stmt = $this->db->prepare('SELECT id, email, password, language, registration_date from users where email = :username'); $stmt->execute(array('username' => $username)); if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { return false; } + $this->getRedis()->setUserData($username, $userInfo); // the default behavior is to use "username" as the user_id + $userInfo['from_cache'] = false; + return array_merge(array( 'user_id' => $username ), $userInfo); diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php b/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php index 8036ef8..dcb1d28 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/PdoFactory.php @@ -26,7 +26,10 @@ class PdoFactory implements FactoryInterface public function createService(ServiceLocatorInterface $services) { try { - return new PdoAdapter($services->get('doctrine.entitymanager.orm_default')->getConnection()->getWrappedConnection()); + $pdoAdapter = new PdoAdapter($services->get('doctrine.entitymanager.orm_default')->getConnection()->getWrappedConnection()); + $pdoAdapter->setRedis($services->get(\Api\OAuth\Storage\Adapter\Redis::class)); + + return $pdoAdapter; } catch (\Exception $e) { throw new DatabaseException('Database connection did not created!', 500, $e); } diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php b/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php index 11d4888..728d565 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/Redis.php @@ -21,4 +21,18 @@ */ class Redis extends RedisStorage { + public function getUserData($username) + { + return $this->getValue($this->config['user_data_cache_key'].$username); + } + + public function setUserData($username, $userInfo) + { + return $this->setValue($this->config['user_data_cache_key'].$username, $userInfo); + } + + public function expireUserData($username) + { + return $this->expireValue($this->config['user_data_cache_key'].$username); + } } diff --git a/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php b/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php index 54ff301..c80976d 100644 --- a/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php +++ b/module/Api/src/Api/OAuth/Storage/Adapter/RedisFactory.php @@ -32,7 +32,7 @@ public function createService(ServiceLocatorInterface $services) try { $redisClient = new \Predis\Client($config['caches']['core.cache.redis']['adapter']['options']['server']); - return new Redis($redisClient); + return new Redis($redisClient, ['user_data_cache_key' => 'user_data_cache:']); } catch (\Exception $e) { throw new CacheException('Cache client exception', 500, $e); } diff --git a/module/Api/src/Api/V1/User/UserResource.php b/module/Api/src/Api/V1/User/UserResource.php index f00f29c..f503c25 100644 --- a/module/Api/src/Api/V1/User/UserResource.php +++ b/module/Api/src/Api/V1/User/UserResource.php @@ -38,6 +38,10 @@ public function fetch($id) */ public function fetchAll($params = []) { + // Example usage identity information + // $identity = $this->getIdentity()->getAuthenticationIdentity(); + + return [['foo' => 'bar']]; } } From d4cd09d5104fe7f9ec37ea012d71255ac9539b4d Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Fri, 4 Dec 2015 15:13:24 +0200 Subject: [PATCH 6/7] Prevent serviceManager method not found error. GuestIdentity bug fix --- module/Api/Module.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/module/Api/Module.php b/module/Api/Module.php index 7403842..a103f58 100644 --- a/module/Api/Module.php +++ b/module/Api/Module.php @@ -47,6 +47,7 @@ public function onBootstrap(MvcEvent $event) public function eventAuthenticationPost(MvcAuthEvent $event) { + // Manupilating Identity Data $identity = $event->getIdentity(); $oauth2Closure = $event->getMvcEvent() ->getApplication() @@ -54,10 +55,14 @@ public function eventAuthenticationPost(MvcAuthEvent $event) ->get(\ZF\OAuth2\Service\OAuth2Server::class); if (!!$identity) { - $userData = $oauth2Closure()->getStorage('user_credentials')->getUser($identity->getName()); - - $identity = new \ZF\MvcAuth\Identity\AuthenticatedIdentity($userData); - $event->setIdentity($identity); + if ($identity instanceof \ZF\MvcAuth\Identity\AuthenticatedIdentity) { + $userData = $oauth2Closure()->getStorage('user_credentials')->getUser($identity->getName()); + if (is_array($identity->getAuthenticationIdentity())) { + $userData = array_merge($userData, $identity->getAuthenticationIdentity()); + } + $identity = new \ZF\MvcAuth\Identity\AuthenticatedIdentity($userData); + $event->setIdentity($identity); + } //MvcEvent did not understand when manipulated MvcAuthEvent identity $event->getMvcEvent()->setParam('ZF\MvcAuth\Identity', $identity); } @@ -83,6 +88,13 @@ public function dispatchError(MvcEvent $event) } elseif ($exception instanceof \Exception) { $className = explode('\\', get_class($exception)); $problem = new ApiProblem($exception->getCode(), end($className) . ' error.'); + + if ($event->getTarget() instanceof ServiceLocatorAwareInterface) { + $logger = $event->getTarget()->getServiceLocator()->get('logger'); + } else { + $logger = $event->getTarget()->getServiceManager()->get('logger'); + } + $logger = $event->getTarget()->getServiceManager()->get('logger'); $logger->err($exception->getMessage(), array( 'controller' => $event->getControllerClass(), From 9096bf1d4e1a3f6db986a4fcacce53aeda1aee3a Mon Sep 17 00:00:00 2001 From: Haydar KULEKCI Date: Sat, 5 Dec 2015 07:49:13 +0200 Subject: [PATCH 7/7] regex route removed --- module/Api/config/module.config.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/module/Api/config/module.config.php b/module/Api/config/module.config.php index a2b3005..f67a4a9 100644 --- a/module/Api/config/module.config.php +++ b/module/Api/config/module.config.php @@ -21,10 +21,9 @@ ), // end of api.rest.user 'oauth' => array( 'options' => array( - 'spec' => '%oauth%', - 'regex' => '(?P(/oauth))', + 'route' => '/oauth', ), - 'type' => 'regex', // regex type will be remove. + 'type' => 'Segment', // regex type will be remove. 'child_routes' => array( 'token' => array( 'type' => 'Zend\Mvc\Router\Http\Literal', @@ -128,6 +127,10 @@ 'token' => [ 'POST' => false, ], + 'authorize' => [ + 'GET' => false, + 'POST' => false, + ], ], ], ],