diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc959c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/composer.lock +/phpunit.xml +/vendor diff --git a/.travis.yml b/.travis.yml index d2f705a..0d41c0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,51 @@ +#This Travis config template file was taken from https://github.com/FriendsOfCake/travis language: php php: - - 5.3 - 5.4 - 5.5 - 5.6 -env: - global: - - REPO_NAME=Authenticate - - PLUGIN_NAME=Authenticate - - REQUIRE="" +sudo: false +env: matrix: - - DB=mysql CAKE_VERSION=2.6 - - DB=mysql CAKE_VERSION=2.7 + - DB=mysql db_dsn='mysql://travis@0.0.0.0/cakephp_test' + - DB=pgsql db_dsn='postgres://travis@127.0.0.1/cakephp_test' + - DB=sqlite db_dsn='sqlite:///:memory:' + + global: + - DEFAULT=1 matrix: + fast_finish: true + include: - - php: 5.4 - env: - - DB=mysql CAKE_VERSION=2.6 COVERALLS=1 - - php: 5.4 - env: - - DB=mysql CAKE_VERSION=2.6 PHPCS=1 + - php: 5.4 + env: PHPCS=1 DEFAULT=0 + + - php: 5.4 + env: COVERALLS=1 DEFAULT=0 DB=mysql db_dsn='mysql://travis@0.0.0.0/cakephp_test' + +install: + - composer self-update + - composer install --prefer-dist --no-interaction --dev before_script: - - git clone -b master https://github.com/FriendsOfCake/travis.git --depth 1 ../travis - - ../travis/before_script.sh + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" + - sh -c "if [ '$PHPCS' = '1' ]; then composer require cakephp/cakephp-codesniffer:dev-master; fi" + - sh -c "if [ '$COVERALLS' = '1' ]; then composer require --dev satooshi/php-coveralls:dev-master; fi" + - sh -c "if [ '$COVERALLS' = '1' ]; then mkdir -p build/logs; fi" -script: - - ../travis/script.sh + - phpenv rehash + - set +H -after_success: - - ../travis/after_success.sh +script: + - sh -c "if [ '$DEFAULT' = '1' ]; then phpunit --stderr; fi" + - sh -c "if [ '$PHPCS' = '1' ]; then ./vendor/bin/phpcs -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests; fi" + - sh -c "if [ '$COVERALLS' = '1' ]; then phpunit --stderr --coverage-clover build/logs/clover.xml; fi" + - sh -c "if [ '$COVERALLS' = '1' ]; then php vendor/bin/coveralls -c .coveralls.yml -v; fi" notifications: email: false diff --git a/Controller/Component/Auth/CookieAuthenticate.php b/Controller/Component/Auth/CookieAuthenticate.php deleted file mode 100644 index 3f5135e..0000000 --- a/Controller/Component/Auth/CookieAuthenticate.php +++ /dev/null @@ -1,98 +0,0 @@ -Auth->authenticate = array( - * 'Authenticate.Cookie' => array( - * 'fields' => array( - * 'username' => 'username', - * 'password' => 'password' - * ), - * 'userModel' => 'User', - * 'scope' => array('User.active' => 1), - * 'crypt' => 'rijndael', // Defaults to rijndael(safest), optionally set to 'cipher' if required - * ) - * ) - * }}} - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - */ -class CookieAuthenticate extends BaseAuthenticate { - -/** - * Constructor - * - * @param ComponentCollection $collection Components collection. - * @param array $settings Settings - */ - public function __construct(ComponentCollection $collection, $settings) { - $this->settings['crypt'] = 'rijndael'; - parent::__construct($collection, $settings); - } - -/** - * Authenticates the identity contained in the cookie. Will use the - * `settings.userModel`, and `settings.fields` to find COOKIE data that is used - * to find a matching record in the `settings.userModel`. Will return false if - * there is no cookie data, either username or password is missing, of if the - * scope conditions have not been met. - * - * @param CakeRequest $request The unused request object - * @return mixed False on login failure. An array of User data on success. - * @throws CakeException - */ - public function getUser(CakeRequest $request) { - if (!isset($this->_Collection->Cookie) || !$this->_Collection->Cookie instanceof CookieComponent) { - throw new CakeException('CookieComponent is not loaded'); - } - - $this->_Collection->Cookie->type($this->settings['crypt']); - list(, $model) = pluginSplit($this->settings['userModel']); - - $data = $this->_Collection->Cookie->read($model); - if (empty($data)) { - return false; - } - - extract($this->settings['fields']); - if (empty($data[$username]) || empty($data[$password])) { - return false; - } - - $user = $this->_findUser($data[$username], $data[$password]); - if ($user) { - $this->_Collection->Session->write(AuthComponent::$sessionKey, $user); - return $user; - } - return false; - } - -/** - * Authenticate user - * - * @param CakeRequest $request Request object. - * @param CakeResponse $response Response object. - * @return array|bool Array of user info on success, false on failure. - */ - public function authenticate(CakeRequest $request, CakeResponse $response) { - return $this->getUser($request); - } - -/** - * Called from AuthComponent::logout() - * - * @param array $user User record - * @return void - */ - public function logout($user) { - $this->_Collection->Cookie->destroy(); - } - -} diff --git a/Controller/Component/Auth/MultiColumnAuthenticate.php b/Controller/Component/Auth/MultiColumnAuthenticate.php deleted file mode 100644 index ca90e8a..0000000 --- a/Controller/Component/Auth/MultiColumnAuthenticate.php +++ /dev/null @@ -1,88 +0,0 @@ -Auth->authenticate = array( - * 'Authenticate.MultiColumn' => array( - * 'fields' => array( - * 'username' => 'username', - * 'password' => 'password' - * ), - * 'columns' => array('username', 'email'), - * 'userModel' => 'User', - * 'scope' => array('User.active' => 1) - * ) - * ) - * }}} - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - */ -class MultiColumnAuthenticate extends FormAuthenticate { - -/** - * Settings for this object. - * - * - `fields` The fields to use to identify a user by. - * - 'columns' array of columns to check username form input against - * - `userModel` The model name of the User, defaults to User. - * - `scope` Additional conditions to use when looking up and authenticating users, - * i.e. `array('User.is_active' => 1).` - * - * @var array - */ - public $settings = array( - 'fields' => array( - 'username' => 'username', - 'password' => 'password' - ), - 'columns' => array(), - 'userModel' => 'User', - 'scope' => array(), - 'contain' => null - ); - -/** - * Find a user record using the standard options. - * - * @param string $username The username/identifier. - * @param string $password The unhashed password. - * @return Mixed Either false on failure, or an array of user data. - */ - protected function _findUser($username, $password = null) { - $userModel = $this->settings['userModel']; - list($plugin, $model) = pluginSplit($userModel); - $fields = $this->settings['fields']; - $conditions = array($model . '.' . $fields['username'] => $username); - if ($this->settings['columns'] && is_array($this->settings['columns'])) { - $columns = array(); - foreach ($this->settings['columns'] as $column) { - $columns[] = array($model . '.' . $column => $username); - } - $conditions = array('OR' => $columns); - } - $conditions = array_merge($conditions, array($model . '.' . $fields['password'] => $this->_password($password))); - if (!empty($this->settings['scope'])) { - $conditions = array_merge($conditions, $this->settings['scope']); - } - $result = ClassRegistry::init($userModel)->find('first', array( - 'conditions' => $conditions, - 'recursive' => 0, - 'contain' => $this->settings['contain'], - )); - if (empty($result) || empty($result[$model])) { - return false; - } - $user = $result[$model]; - unset($user[$fields['password']]); - unset($result[$model]); - return array_merge($user, $result); - } - -} diff --git a/Controller/Component/Auth/TokenAuthenticate.php b/Controller/Component/Auth/TokenAuthenticate.php deleted file mode 100644 index 1d02541..0000000 --- a/Controller/Component/Auth/TokenAuthenticate.php +++ /dev/null @@ -1,163 +0,0 @@ -Auth->authenticate = array( - * 'Authenticate.Token' => array( - * 'parameter' => '_token', - * 'header' => 'X-MyApiTokenHeader', - * 'userModel' => 'User', - * 'scope' => array('User.active' => 1) - * 'fields' => array( - * 'username' => 'username', - * 'password' => 'password', - * 'token' => 'public_key', - * ), - * 'continue' => true - * ) - * ) - * }}} - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - */ -class TokenAuthenticate extends BaseAuthenticate { - -/** - * Settings for this object. - * - * - `parameter` The url parameter name of the token. - * - `header` The token header value. - * - `userModel` The model name of the User, defaults to User. - * - `fields` The fields to use to identify a user by. Make sure `'token'` has been added to the array - * - `scope` Additional conditions to use when looking up and authenticating users, - * i.e. `array('User.is_active' => 1).` - * - `recursive` The value of the recursive key passed to find(). Defaults to 0. - * - `contain` Extra models to contain and store in session. - * - `continue` Continue after trying token authentication or just throw the `unauthorized` exception. - * - `unauthorized` Exception name to throw or a status code as an integer. - * - * @var array - */ - public $settings = array( - 'parameter' => '_token', - 'header' => 'X-ApiToken', - - 'userModel' => 'User', - 'fields' => array( - 'username' => 'username', - 'password' => 'password', - 'token' => 'token', - ), - 'scope' => array(), - 'recursive' => 0, - 'contain' => null, - - 'continue' => false, - 'unauthorized' => 'BadRequestException' - ); - -/** - * Constructor - * - * @param ComponentCollection $collection The Component collection used on this request. - * @param array $settings Array of settings to use. - * @throws CakeException - */ - public function __construct(ComponentCollection $collection, $settings) { - parent::__construct($collection, $settings); - if (empty($this->settings['parameter']) && empty($this->settings['header'])) { - throw new CakeException(__d('authenticate', 'You need to specify token parameter and/or header')); - } - } - -/** - * Implemented because CakePHP forces you to. - * - * @param CakeRequest $request The request object. - * @param CakeResponse $response response object. - * @return bool Always false. - */ - public function authenticate(CakeRequest $request, CakeResponse $response) { - return false; - } - -/** - * If unauthenticated, try to authenticate and respond. - * - * @param CakeRequest $request The request object. - * @param CakeResponse $response The response object. - * @return bool False on failure, user on success. - * @throws HttpException or the one specified using $settings['unauthorized'] - */ - public function unauthenticated(CakeRequest $request, CakeResponse $response) { - if ($this->settings['continue']) { - return false; - } - if (is_string($this->settings['unauthorized'])) { - // @codingStandardsIgnoreStart - throw new $this->settings['unauthorized']; - // @codingStandardsIgnoreEnd - } - $message = __d('authenticate', 'You are not authenticated.'); - throw new HttpException($message, $this->settings['unauthorized']); - } - -/** - * Get token information from the request. - * - * @param CakeRequest $request Request object. - * @return mixed Either false or an array of user information - */ - public function getUser(CakeRequest $request) { - if (!empty($this->settings['header'])) { - $token = $request->header($this->settings['header']); - if ($token) { - return $this->_findUser($token, null); - } - } - if (!empty($this->settings['parameter']) && !empty($request->query[$this->settings['parameter']])) { - $token = $request->query[$this->settings['parameter']]; - return $this->_findUser($token); - } - return false; - } - -/** - * Find a user record. - * - * @param string $username The token identifier. - * @param string $password Unused password. - * @return Mixed Either false on failure, or an array of user data. - */ - protected function _findUser($username, $password = null) { - $userModel = $this->settings['userModel']; - list($plugin, $model) = pluginSplit($userModel); - $fields = $this->settings['fields']; - - $conditions = array( - $model . '.' . $fields['token'] => $username, - ); - if (!empty($this->settings['scope'])) { - $conditions = array_merge($conditions, $this->settings['scope']); - } - $result = ClassRegistry::init($userModel)->find('first', array( - 'conditions' => $conditions, - 'recursive' => (int)$this->settings['recursive'], - 'contain' => $this->settings['contain'], - )); - if (empty($result) || empty($result[$model])) { - return false; - } - $user = $result[$model]; - unset($user[$fields['password']]); - unset($result[$model]); - return array_merge($user, $result); - } - -} diff --git a/Test/Case/AllAuthenticateTest.php b/Test/Case/AllAuthenticateTest.php deleted file mode 100644 index 3f08db0..0000000 --- a/Test/Case/AllAuthenticateTest.php +++ /dev/null @@ -1,22 +0,0 @@ -addTestDirectoryRecursive($path); - - return $suite; - } -} diff --git a/Test/Case/Controller/Component/Auth/CookieAuthenticateTest.php b/Test/Case/Controller/Component/Auth/CookieAuthenticateTest.php deleted file mode 100644 index 7a0ce62..0000000 --- a/Test/Case/Controller/Component/Auth/CookieAuthenticateTest.php +++ /dev/null @@ -1,92 +0,0 @@ -request = new CakeRequest('posts/index', false); - Router::setRequestInfo($this->request); - $this->Collection = new ComponentCollection(); - $this->Collection->load('Cookie'); - $this->Collection->load('Session'); - $this->auth = new CookieAuthenticate($this->Collection, array( - 'fields' => array('username' => 'user', 'password' => 'password'), - 'userModel' => 'MultiUser', - )); - $password = Security::hash('password', null, true); - $User = ClassRegistry::init('MultiUser'); - $User->updateAll(array('password' => $User->getDataSource()->value($password))); - $this->response = $this->getMock('CakeResponse'); - } - -/** - * tearDown - * - * @return void - */ - public function tearDown() { - parent::tearDown(); - $this->Collection->Cookie->destroy(); - } - -/** - * test authenticate email or username - * - * @return void - */ - public function testAuthenticate() { - $expected = array( - 'id' => 1, - 'user' => 'mariano', - 'email' => 'mariano@example.com', - 'token' => '12345', - 'created' => '2007-03-17 01:16:23', - 'updated' => '2007-03-17 01:18:31' - ); - - $result = $this->auth->authenticate($this->request, $this->response); - $this->assertFalse($result); - - $this->Collection->Cookie->write('MultiUser', array('user' => 'mariano', 'password' => 'password')); - $result = $this->auth->authenticate($this->request, $this->response); - $this->assertEquals($expected, $result); - } -} diff --git a/Test/Case/Controller/Component/Auth/MultiColumnAuthenticateTest.php b/Test/Case/Controller/Component/Auth/MultiColumnAuthenticateTest.php deleted file mode 100644 index 57a0a54..0000000 --- a/Test/Case/Controller/Component/Auth/MultiColumnAuthenticateTest.php +++ /dev/null @@ -1,152 +0,0 @@ -Collection = $this->getMock('ComponentCollection'); - $this->auth = new MultiColumnAuthenticate($this->Collection, array( - 'fields' => array('username' => 'user', 'password' => 'password'), - 'userModel' => 'MultiUser', - 'columns' => array('user', 'email') - )); - $password = Security::hash('password', null, true); - $User = ClassRegistry::init('MultiUser'); - $User->updateAll(array('password' => $User->getDataSource()->value($password))); - $this->response = $this->getMock('CakeResponse'); - } - -/** - * test authenticate email or username - * - * @return void - */ - public function testAuthenticateEmailOrUsername() { - $request = new CakeRequest('posts/index', false); - $expected = array( - 'id' => 1, - 'user' => 'mariano', - 'email' => 'mariano@example.com', - 'token' => '12345', - 'created' => '2007-03-17 01:16:23', - 'updated' => '2007-03-17 01:18:31' - ); - - $request->data = array('MultiUser' => array( - 'user' => 'mariano', - 'password' => 'password' - )); - $result = $this->auth->authenticate($request, $this->response); - $this->assertEquals($expected, $result); - - $request->data = array('MultiUser' => array( - 'user' => 'mariano@example.com', - 'password' => 'password' - )); - $result = $this->auth->authenticate($request, $this->response); - $this->assertEquals($expected, $result); - } - -/** - * test the authenticate method - * - * @return void - */ - public function testAuthenticateNoData() { - $request = new CakeRequest('posts/index', false); - $request->data = array(); - $this->assertFalse($this->auth->authenticate($request, $this->response)); - } - -/** - * test the authenticate method - * - * @return void - */ - public function testAuthenticateNoUsername() { - $request = new CakeRequest('posts/index', false); - $request->data = array('MultiUser' => array('password' => 'foobar')); - $this->assertFalse($this->auth->authenticate($request, $this->response)); - } - -/** - * test the authenticate method - * - * @return void - */ - public function testAuthenticateNoPassword() { - $request = new CakeRequest('posts/index', false); - $request->data = array('MultiUser' => array('user' => 'mariano')); - $this->assertFalse($this->auth->authenticate($request, $this->response)); - - $request->data = array('MultiUser' => array('user' => 'mariano@example.com')); - $this->assertFalse($this->auth->authenticate($request, $this->response)); - } - -/** - * test the authenticate method - * - * @return void - */ - public function testAuthenticateInjection() { - $request = new CakeRequest('posts/index', false); - $request->data = array( - 'MultiUser' => array( - 'user' => '> 1', - 'password' => "' OR 1 = 1" - )); - $this->assertFalse($this->auth->authenticate($request, $this->response)); - } - -/** - * test scope failure. - * - * @return void - */ - public function testAuthenticateScopeFail() { - $this->auth->settings['scope'] = array('user' => 'nate'); - $request = new CakeRequest('posts/index', false); - $request->data = array('User' => array( - 'user' => 'mariano', - 'password' => 'password' - )); - - $this->assertFalse($this->auth->authenticate($request, $this->response)); - } - -} diff --git a/Test/Case/Controller/Component/Auth/TokenAuthenticateTest.php b/Test/Case/Controller/Component/Auth/TokenAuthenticateTest.php deleted file mode 100644 index cf0f7a1..0000000 --- a/Test/Case/Controller/Component/Auth/TokenAuthenticateTest.php +++ /dev/null @@ -1,113 +0,0 @@ -Collection = $this->getMock('ComponentCollection'); - $this->auth = new TokenAuthenticate($this->Collection, array( - 'fields' => array( - 'username' => 'user', - 'password' => 'password', - 'token' => 'token' - ), - 'userModel' => 'MultiUser', - )); - $password = Security::hash('password', null, true); - $User = ClassRegistry::init('MultiUser'); - $User->updateAll(array('password' => $User->getDataSource()->value($password))); - $this->response = $this->getMock('CakeResponse'); - } - -/** - * test authenticate token as query parameter - * - * @return void - */ - public function testAuthenticateTokenParameter() { - $this->auth->settings['_parameter'] = 'token'; - $request = new CakeRequest('posts/index?_token=54321'); - - $result = $this->auth->getUser($request, $this->response); - $this->assertFalse($result); - - $expected = array( - 'id' => '1', - 'user' => 'mariano', - 'email' => 'mariano@example.com', - 'token' => '12345', - 'created' => '2007-03-17 01:16:23', - 'updated' => '2007-03-17 01:18:31' - ); - $request = new CakeRequest('posts/index?_token=12345'); - $result = $this->auth->getUser($request, $this->response); - $this->assertEquals($expected, $result); - - $this->auth->settings['parameter'] = 'tokenname'; - $request = new CakeRequest('posts/index?tokenname=12345'); - $result = $this->auth->getUser($request, $this->response); - $this->assertEquals($expected, $result); - } - -/** - * test authenticate token as request header - * - * @return void - */ - public function testAuthenticateTokenHeader() { - $_SERVER['HTTP_X_APITOKEN'] = '54321'; - $request = new CakeRequest('posts/index', false); - - $result = $this->auth->getUser($request, $this->response); - $this->assertFalse($result); - - $expected = array( - 'id' => '1', - 'user' => 'mariano', - 'email' => 'mariano@example.com', - 'token' => '12345', - 'created' => '2007-03-17 01:16:23', - 'updated' => '2007-03-17 01:18:31' - ); - $_SERVER['HTTP_X_APITOKEN'] = '12345'; - $result = $this->auth->getUser($request, $this->response); - $this->assertEquals($expected, $result); - } - -} diff --git a/Test/Fixture/MultiUserFixture.php b/Test/Fixture/MultiUserFixture.php deleted file mode 100644 index 88c781b..0000000 --- a/Test/Fixture/MultiUserFixture.php +++ /dev/null @@ -1,39 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'user' => array('type' => 'string', 'null' => false), - 'email' => array('type' => 'string', 'null' => false), - 'password' => array('type' => 'string', 'null' => false), - 'token' => array('type' => 'string', 'null' => false), - 'created' => 'datetime', - 'updated' => 'datetime' - ); - -/** - * records property - * - * @var array - */ - public $records = array( - array('user' => 'mariano', 'email' => 'mariano@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'token' => '12345', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), - array('user' => 'nate', 'email' => 'nate@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'token' => '23456', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), - array('user' => 'larry', 'email' => 'larry@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'token' => '34567', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), - array('user' => 'garrett', 'email' => 'garrett@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'token' => '45678', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), - array('user' => 'chartjes', 'email' => 'chartjes@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'token' => '56789', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), - - ); -} diff --git a/composer.json b/composer.json index 4d593b0..2954dea 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,11 @@ { "name":"Ceeram", "role":"Author" + }, + { + "name":"ADmad", + "role":"Developer", + "homepage":"https://github.com/ADmad" } ], "license": "MIT", @@ -20,7 +25,20 @@ "irc":"irc://irc.freenode.org/friendsofcake" }, "require": { - "php": ">=5.3.0", - "composer/installers": "*" + "php": ">=5.4.0", + "cakephp/cakephp": "~3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.1.*" + }, + "autoload": { + "psr-4": { + "FOC\\Authenticate\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "FOC\\Authenticate\\Test\\": "tests" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..fbad239 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,47 @@ + + + + + + + + + + ./tests/TestCase + + + + + + + + + + + + + + + + + + diff --git a/readme.md b/readme.md index 17c97ac..9475099 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # Authenticate plugin -[![Build Status](https://travis-ci.org/FriendsOfCake/Authenticate.png?branch=master)](https://travis-ci.org/FriendsOfCake/Authenticate) +[![Build Status](https://travis-ci.org/FriendsOfCake/Authenticate.png?branch=cake3)](https://travis-ci.org/FriendsOfCake/Authenticate) [![Coverage Status](https://coveralls.io/repos/FriendsOfCake/Authenticate/badge.png)](https://coveralls.io/r/FriendsOfCake/Authenticate) Plugin containing some authenticate classes for AuthComponent. @@ -11,43 +11,21 @@ Current classes: * CookieAuthenticate, login with a cookie * TokenAuthenticate, login with a token as url parameter or header -GoogleAuthenticate is moved to separate repo: https://github.com/ceeram/GoogleAuthenticate - ## Requirements -* PHP 5.3 -* CakePHP 2.x +* CakePHP 3.0 ## Installation _[Composer]_ -run: `composer require friendsofcake/authenticate` or add `friendsofcake/authenticate` to `require` in your applications `composer.json` - -_[Manual]_ - -* Download this: http://github.com/FriendsOfCake/Authenticate/zipball/master -* Unzip that download. -* Copy the resulting folder to app/Plugin -* Rename the folder you just copied to Authenticate - -_[GIT Submodule]_ - -In your app directory type: -``` -git submodule add git://github.com/FriendsOfCake/Authenticate.git Plugin/Authenticate -git submodule init -git submodule update -``` - -_[GIT Clone]_ - -In your plugin directory type -`git clone git://github.com/FriendsOfCake/Authenticate.git Authenticate` +run: `composer require friendsofcake/authenticate:dev-cake3` or +add `"friendsofcake/authenticate":"dev-cake3"` to `require` section in your +application's `composer.json`. ## Usage -In `app/Config/bootstrap.php` add: `CakePlugin::load('Authenticate')`; +In your app's `config/bootstrap.php` add: `Plugin::load('FOC/Authenticate');` ## Configuration: @@ -57,64 +35,65 @@ Setup the authentication class settings ```php //in $components - public $components = array( - 'Auth' => array( - 'authenticate' => array( - 'Authenticate.MultiColumn' => array( - 'fields' => array( + public $components = [ + 'Auth' => [ + 'authenticate' => [ + 'FOC/Authenticate.MultiColumn' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'columns' => array('username', 'email'), - 'userModel' => 'User', - 'scope' => array('User.active' => 1) - ) - ) - ) - ); - //Or in beforeFilter() - $this->Auth->authenticate = array( - 'Authenticate.MultiColumn' => array( - 'fields' => array( + ], + 'columns' => ['username', 'email'], + 'userModel' => 'Users', + 'scope' => ['Users.active' => 1] + ] + ] + ] + ]; + + // Or in beforeFilter() + $this->Auth->config('authenticate', [ + 'FOC/Authenticate.MultiColumn' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'columns' => array('username', 'email'), - 'userModel' => 'User', - 'scope' => array('User.active' => 1) - ) - ); + ], + 'columns' => ['username', 'email'], + 'userModel' => 'Users', + 'scope' => ['Users.active' => 1] + ] + ]); ``` ### CookieAuthenticate: ```php //in $components - public $components = array( - 'Auth' => array( - 'authenticate' => array( - 'Authenticate.Cookie' => array( - 'fields' => array( + public $components = [ + 'Auth' => [ + 'authenticate' => [ + 'FOC/Authenticate.Cookie' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'userModel' => 'SomePlugin.User', - 'scope' => array('User.active' => 1) - ) - ) - ) - ); + ], + 'userModel' => 'SomePlugin.Users', + 'scope' => ['User.active' => 1] + ] + ] + ] + ]; //Or in beforeFilter() - $this->Auth->authenticate = array( - 'Authenticate.Cookie' => array( - 'fields' => array( + $this->Auth->authenticate = [ + 'FOC/Authenticate.Cookie' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'userModel' => 'SomePlugin.User', - 'scope' => array('User.active' => 1) - ) - ); + ], + 'userModel' => 'SomePlugin.Users', + 'scope' => ['Users.active' => 1] + ] + ]; ``` ### Setup both: @@ -122,40 +101,29 @@ Setup the authentication class settings It will first try to read the cookie, if that fails will try with form data: ```php //in $components - public $components = array( - 'Auth' => array( - 'authenticate' => array( - 'Authenticate.Cookie' => array( - 'fields' => array( + public $components = [ + 'Auth' => [ + 'authenticate' => [ + 'FOC/Authenticate.Cookie' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'userModel' => 'SomePlugin.User', - 'scope' => array('User.active' => 1) - ), - 'Authenticate.MultiColumn' => array( - 'fields' => array( + ], + 'userModel' => 'SomePlugin.Users', + 'scope' => ['User.active' => 1] + ], + 'FOC/Authenticate.MultiColumn' => [ + 'fields' => [ 'username' => 'login', 'password' => 'password' - ), - 'columns' => array('username', 'email'), - 'userModel' => 'User', - 'scope' => array('User.active' => 1) - ) - ) - ) - ); -``` - -### Security - -For enhanced security, make sure you add this code to your `AppController::beforeFilter()` if you intend to use Cookie -authentication: - -```php -public function beforeFilter() { - $this->Cookie->type('rijndael'); //Enable AES symetric encryption of cookie -} + ], + 'columns' => ['username', 'email'], + 'userModel' => 'Users', + 'scope' => ['Users.active' => 1] + ] + ] + ] + ]; ``` ### Setting the cookie @@ -171,36 +139,32 @@ App::uses('AppController', 'Controller'); */ class UsersController extends AppController { - public $components = array('Cookie'); + public $components = ['Cookie']; - public function beforeFilter() { - $this->Cookie->type('rijndael'); - } - - public function login() { - if ($this->Auth->loggedIn() || $this->Auth->login()) { - $this->_setCookie(); - $this->redirect($this->Auth->redirect()); - } - } + public function login() { + if ($this->request->is('post')) { + $user = $this->Auth->identify(); + if ($user) { + $this->Auth->setUser($user); + $this->_setCookie(); + return $this->redirect($this->Auth->redirectUrl()); + } + $this->Flash->error(__('Invalid username or password, try again')); + } + } protected function _setCookie() { - if (!$this->request->data('User.remember_me')) { + if (!$this->request->data('remember_me')) { return false; } - $data = array( - 'username' => $this->request->data('User.username'), - 'password' => $this->request->data('User.password') - ); - $this->Cookie->write('User', $data, true, '+1 week'); + $data = [ + 'username' => $this->request->data('username'), + 'password' => $this->request->data('password') + ]; + $this->Cookie->write('RememberMe', $data, true, '+1 week'); return true; } - public function logout() { - $this->Auth->logout(); - $this->Session->setFlash('Logged out'); - $this->redirect($this->Auth->redirect('/')); - } } ``` @@ -208,37 +172,37 @@ class UsersController extends AppController { ```php //in $components - public $components = array( - 'Auth' => array( - 'authenticate' => array( - 'Authenticate.Token' => array( + public $components = [ + 'Auth' => [ + 'authenticate' => [ + 'FOC/Authenticate.Token' => [ 'parameter' => '_token', 'header' => 'X-MyApiTokenHeader', - 'userModel' => 'User', - 'scope' => array('User.active' => 1), - 'fields' => array( + 'userModel' => 'Users', + 'scope' => ['Users.active' => 1], + 'fields' => [ 'username' => 'username', 'password' => 'password', 'token' => 'public_key', - ), + ], 'continue' => true - ) - ) - ) - ); + ] + ] + ] + ]; //Or in beforeFilter() - $this->Auth->authenticate = array( - 'Authenticate.Token' => array( + $this->Auth->config('authenticate', [ + 'FOC/Authenticate.Token' => [ 'parameter' => '_token', 'header' => 'X-MyApiTokenHeader', - 'userModel' => 'User', - 'scope' => array('User.active' => 1), - 'fields' => array( + 'userModel' => 'Users', + 'scope' => ['Users.active' => 1], + 'fields' => [ 'username' => 'username', 'password' => 'password', 'token' => 'public_key', - ), + ], 'continue' => true - ) - ); + ] + ]); ``` diff --git a/src/Auth/CookieAuthenticate.php b/src/Auth/CookieAuthenticate.php new file mode 100644 index 0000000..5e5d962 --- /dev/null +++ b/src/Auth/CookieAuthenticate.php @@ -0,0 +1,132 @@ +Auth->config('authenticate', [ + * 'Authenticate.Cookie' => [ + * 'fields' => [ + * 'username' => 'username', + * 'password' => 'password' + * ], + * 'userModel' => 'Users', + * 'scope' => ['Users.active' => 1], + * 'crypt' => 'aes', + * 'cookie' => [ + * 'name' => 'RememberMe', + * 'time' => '+2 weeks', + * ] + * ] + * ]); + * ``` + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + */ +class CookieAuthenticate extends BaseAuthenticate +{ + + /** + * Constructor + * + * @param \Cake\Controller\ComponentRegistry $registry The Component registry + * used on this request. + * @param array $config Array of config to use. + */ + public function __construct(ComponentRegistry $registry, $config) + { + $this->_registry = $registry; + + $this->config([ + 'cookie' => [ + 'name' => 'RememberMe', + 'expires' => '+2 weeks' + ], + 'crypt' => 'aes' + ]); + + $this->config($config); + } + + /** + * Authenticates the identity contained in the cookie. Will use the + * `userModel` config, and `fields` config to find COOKIE data that is used + * to find a matching record in the model specified by `userModel`. Will return + * false if there is no cookie data, either username or password is missing, + * or if the scope conditions have not been met. + * + * @param Request $request The unused request object. + * @return mixed False on login failure. An array of User data on success. + * @throws \RuntimeException If CookieComponent is not loaded. + */ + public function getUser(Request $request) + { + if (!isset($this->_registry->Cookie) || + !$this->_registry->Cookie instanceof CookieComponent + ) { + throw new \RuntimeException('CookieComponent is not loaded'); + } + + $cookieConfig = $this->_config['cookie']; + $cookieName = $this->_config['cookie']['name']; + unset($cookieConfig['name']); + $this->_registry->Cookie->configKey($cookieName, $cookieConfig); + + $data = $this->_registry->Cookie->read($cookieName); + if (empty($data)) { + return false; + } + + extract($this->_config['fields']); + if (empty($data[$username]) || empty($data[$password])) { + return false; + } + + $user = $this->_findUser($data[$username], $data[$password]); + if ($user) { + $request->session()->write( + $this->_registry->Auth->sessionKey, + $user + ); + return $user; + } + + return false; + } + + /** + * Authenticate user + * + * @param Request $request Request object. + * @param Response $response Response object. + * @return array|bool Array of user info on success, false on falure. + */ + public function authenticate(Request $request, Response $response) + { + return $this->getUser($request); + } + + /** + * Called from AuthComponent::logout() + * + * @param \Cake\Event\Event $event The dispatched Auth.logout event. + * @param array $user User record. + * @return void + */ + public function logout(Event $event, array $user) + { + $this->_registry->Cookie->delete($this->_config['cookie']['name']); + } +} diff --git a/src/Auth/MultiColumnAuthenticate.php b/src/Auth/MultiColumnAuthenticate.php new file mode 100644 index 0000000..697018c --- /dev/null +++ b/src/Auth/MultiColumnAuthenticate.php @@ -0,0 +1,108 @@ +Auth->config('authenticate', [ + * 'Authenticate.MultiColumn' => [ + * 'fields' => [ + * 'username' => 'username', + * 'password' => 'password' + * ], + * 'columns' => ['username', 'email'], + * 'userModel' => 'Users', + * 'scope' => ['User.active' => 1] + * ] + * ]); + * ``` + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + */ +class MultiColumnAuthenticate extends FormAuthenticate +{ + /** + * Constructor + * + * Besides the keys specified in BaseAuthenticate::$_defaultConfig, + * MultiColumnAuthenticate uses the following extra keys: + * + * - 'columns' Array of columns to check username form input against + * + * @param \Cake\Controller\ComponentRegistry $registry The Component registry + * used on this request. + * @param array $config Array of config to use. + */ + public function __construct(ComponentRegistry $registry, $config) + { + $this->_registry = $registry; + + $this->config([ + 'columns' => [], + ]); + + $this->config($config); + } + + /** + * Find a user record using the standard options. + * + * @param string $username The username/identifier. + * @param string $password The password, if not provide password checking is + * skipped and result of find is returned. + * @return bool|array Either false on failure, or an array of user data. + */ + protected function _findUser($username, $password = null) + { + $userModel = $this->_config['userModel']; + list($plugin, $model) = pluginSplit($userModel); + $fields = $this->_config['fields']; + $conditions = [$model . '.' . $fields['username'] => $username]; + + $columns = []; + foreach ($this->_config['columns'] as $column) { + $columns[] = [$model . '.' . $column => $username]; + } + $conditions = ['OR' => $columns]; + + if (!empty($this->_config['scope'])) { + $conditions = array_merge($conditions, $this->_config['scope']); + } + + $table = TableRegistry::get($userModel)->find('all'); + if ($this->_config['contain']) { + $table = $table->contain($this->_config['contain']); + } + + $result = $table + ->where($conditions) + ->hydrate(false) + ->first(); + + if (empty($result)) { + return false; + } + + if ($password !== null) { + $hasher = $this->passwordHasher(); + $hashedPassword = $result[$fields['password']]; + if (!$hasher->check($password, $hashedPassword)) { + return false; + } + + $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword); + unset($result[$fields['password']]); + } + + return $result; + } +} diff --git a/src/Auth/TokenAuthenticate.php b/src/Auth/TokenAuthenticate.php new file mode 100644 index 0000000..e43d3dd --- /dev/null +++ b/src/Auth/TokenAuthenticate.php @@ -0,0 +1,175 @@ +Auth->config('authenticate', [ + * 'FOC/Authenticate.Token' => [ + * 'parameter' => '_token', + * 'header' => 'X-MyApiTokenHeader', + * 'userModel' => 'Users', + * 'scope' => ['User.active' => 1] + * 'fields' => [ + * 'token' => 'public_key', + * ], + * 'continue' => true + * ] + * ]); + * ``` + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + */ +class TokenAuthenticate extends BaseAuthenticate +{ + + /** + * Constructor. + * + * Settings for this object. + * + * - `parameter` The url parameter name of the token. + * - `header` The token header value. + * - `userModel` The model name of the User, defaults to Users. + * - `fields` The fields to use to identify a user by. Make sure `'token'` has + * been added to the array + * - `scope` Additional conditions to use when looking up and authenticating users, + * i.e. `['Users.is_active' => 1].` + * - `contain` Extra models to contain. + * - `continue` Continue after trying token authentication or just throw the + * `unauthorized` exception. + * - `unauthorized` Exception name to throw or a status code as an integer. + * + * @param \Cake\Controller\ComponentRegistry $registry The Component registry + * used on this request. + * @param array $config Array of config to use. + * @throws Cake\Error\Exception If header is not present. + */ + public function __construct(ComponentRegistry $registry, $config) + { + $this->_registry = $registry; + + $this->config([ + 'parameter' => '_token', + 'header' => 'X-ApiToken', + 'fields' => ['token' => 'token', 'password' => 'password'], + 'continue' => false, + 'unauthorized' => 'Cake\Network\Exception\BadRequestException' + ]); + + $this->config($config); + + if (empty($this->_config['parameter']) && + empty($this->_config['header']) + ) { + throw new Exception(__d( + 'authenticate', + 'You need to specify token parameter and/or header' + )); + } + } + + /** + * Implemented because CakePHP forces you to. + * + * @param Request $request The request object. + * @param Response $response The response object. + * @return bool Always false. + */ + public function authenticate(Request $request, Response $response) + { + return false; + } + + /** + * If unauthenticated, try to authenticate and respond. + * + * @param Request $request The request object. + * @param Response $response The response object. + * @return bool False on failure, user on success. + * @throws HttpException Or the one specified using $settings['unauthorized']. + */ + public function unauthenticated(Request $request, Response $response) + { + if ($this->_config['continue']) { + return false; + } + if (is_string($this->_config['unauthorized'])) { + // @codingStandardsIgnoreStart + throw new $this->_config['unauthorized']; + // @codingStandardsIgnoreEnd + } + $message = __d('authenticate', 'You are not authenticated.'); + throw new HttpException($message, $this->_config['unauthorized']); + } + + /** + * Get token information from the request. + * + * @param Request $request Request object. + * @return mixed Either false or an array of user information + */ + public function getUser(Request $request) + { + if (!empty($this->_config['header'])) { + $token = $request->header($this->_config['header']); + if ($token) { + return $this->_findUser($token); + } + } + if (!empty($this->_config['parameter']) && + !empty($request->query[$this->_config['parameter']]) + ) { + $token = $request->query[$this->_config['parameter']]; + return $this->_findUser($token); + } + return false; + } + + /** + * Find a user record. + * + * @param string $username The token identifier. + * @param string $password Unused password. + * @return Mixed Either false on failure, or an array of user data. + */ + protected function _findUser($username, $password = null) + { + $userModel = $this->_config['userModel']; + list($plugin, $model) = pluginSplit($userModel); + $fields = $this->_config['fields']; + + $conditions = [$model . '.' . $fields['token'] => $username]; + if (!empty($this->_config['scope'])) { + $conditions = array_merge($conditions, $this->_config['scope']); + } + $table = TableRegistry::get($userModel)->find('all'); + if ($this->_config['contain']) { + $table = $table->contain($this->_config['contain']); + } + + $result = $table + ->where($conditions) + ->hydrate(false) + ->first(); + + if (empty($result)) { + return false; + } + + unset($result[$fields['password']]); + + return $result; + } +} diff --git a/tests/Fixture/MultiUsersFixture.php b/tests/Fixture/MultiUsersFixture.php new file mode 100644 index 0000000..efdc6f7 --- /dev/null +++ b/tests/Fixture/MultiUsersFixture.php @@ -0,0 +1,70 @@ + ['type' => 'integer'], + 'user_name' => ['type' => 'string', 'null' => false], + 'email' => ['type' => 'string', 'null' => false], + 'password' => ['type' => 'string', 'null' => false], + 'token' => ['type' => 'string', 'null' => false], + 'created' => 'datetime', + 'updated' => 'datetime', + '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] + ]; + + /** + * records property + * + * @var array + */ + public $records = [ + [ + 'user_name' => 'mariano', + 'email' => 'mariano@example.com', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'token' => '12345', 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ], + [ + 'user_name' => 'nate', + 'email' => 'nate@example.com', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'token' => '23456', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ], + [ + 'user_name' => 'larry', + 'email' => 'larry@example.com', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'token' => '34567', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ], + [ + 'user_name' => 'garrett', + 'email' => 'garrett@example.com', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'token' => '45678', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ], + [ + 'user_name' => 'chartjes', + 'email' => 'chartjes@example.com', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'token' => '56789', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ] + ]; +} diff --git a/tests/TestCase/Auth/CookieAuthenticateTest.php b/tests/TestCase/Auth/CookieAuthenticateTest.php new file mode 100644 index 0000000..64199c4 --- /dev/null +++ b/tests/TestCase/Auth/CookieAuthenticateTest.php @@ -0,0 +1,92 @@ +request = new Request('posts/index'); + Router::setRequestInfo($this->request); + $this->response = $this->getMock('Cake\Network\Response'); + + Security::salt('somerandomhaskeysomerandomhaskey'); + $this->Registry = new ComponentRegistry(new Controller($this->request, $this->response)); + $this->Registry->load('Cookie'); + $this->Registry->load('Auth'); + $this->auth = new CookieAuthenticate($this->Registry, [ + 'fields' => ['username' => 'user_name', 'password' => 'password'], + 'userModel' => 'MultiUsers', + ]); + + $password = password_hash('password', PASSWORD_DEFAULT); + $MultiUsers = TableRegistry::get('MultiUsers'); + $MultiUsers->updateAll(['password' => $password], []); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() + { + parent::tearDown(); + $this->Registry->Cookie->delete('MultiUsers'); + } + + /** + * test authenticate email or username + * + * @return void + */ + public function testAuthenticate() + { + $expected = [ + 'id' => 1, + 'user_name' => 'mariano', + 'email' => 'mariano@example.com', + 'token' => '12345', + 'created' => new Time('2007-03-17 01:16:23'), + 'updated' => new Time('2007-03-17 01:18:31') + ]; + + $result = $this->auth->authenticate($this->request, $this->response); + $this->assertFalse($result); + + $this->Registry->Cookie->write( + 'RememberMe', + [ + 'user_name' => 'mariano', + 'password' => 'password' + ] + ); + $result = $this->auth->authenticate($this->request, $this->response); + $this->assertEquals($expected, $result); + } +} diff --git a/tests/TestCase/Auth/MultiColumnAuthenticateTest.php b/tests/TestCase/Auth/MultiColumnAuthenticateTest.php new file mode 100644 index 0000000..f7d31fa --- /dev/null +++ b/tests/TestCase/Auth/MultiColumnAuthenticateTest.php @@ -0,0 +1,147 @@ +Registry = $this->getMock('Cake\Controller\ComponentRegistry'); + $this->auth = new MultiColumnAuthenticate($this->Registry, [ + 'fields' => ['username' => 'user_name', 'password' => 'password'], + 'userModel' => 'MultiUsers', + 'columns' => ['user_name', 'email'] + ]); + + $password = password_hash('password', PASSWORD_DEFAULT); + $MultiUsers = TableRegistry::get('MultiUsers'); + $MultiUsers->updateAll(['password' => $password], []); + + $this->response = $this->getMock('Cake\Network\Response'); + } + + /** + * test authenticate email or username + * + * @return void + */ + public function testAuthenticateEmailOrUsername() + { + $request = new Request('posts/index'); + $expected = [ + 'id' => 1, + 'user_name' => 'mariano', + 'email' => 'mariano@example.com', + 'token' => '12345', + 'created' => new Time('2007-03-17 01:16:23'), + 'updated' => new Time('2007-03-17 01:18:31') + ]; + + $request->data = [ + 'user_name' => 'mariano', + 'password' => 'password' + ]; + $result = $this->auth->authenticate($request, $this->response); + $this->assertEquals($expected, $result); + + $request->data = [ + 'user_name' => 'mariano@example.com', + 'password' => 'password' + ]; + $result = $this->auth->authenticate($request, $this->response); + $this->assertEquals($expected, $result); + } + + /** + * test the authenticate method + * + * @return void + */ + public function testAuthenticateNoData() + { + $request = new Request('posts/index'); + $request->data = []; + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + + /** + * test the authenticate method + * + * @return void + */ + public function testAuthenticateNoUsername() + { + $request = new Request('posts/index'); + $request->data = ['password' => 'foobar']; + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + + /** + * test the authenticate method + * + * @return void + */ + public function testAuthenticateNoPassword() + { + $request = new Request('posts/index'); + $request->data = ['user_name' => 'mariano']; + $this->assertFalse($this->auth->authenticate($request, $this->response)); + + $request->data = ['user_name' => 'mariano@example.com']; + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + + /** + * test the authenticate method + * + * @return void + */ + public function testAuthenticateInjection() + { + $request = new Request('posts/index'); + $request->data = [ + 'user_name' => '> 1', + 'password' => "' OR 1 = 1" + ]; + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + + /** + * test scope failure. + * + * @return void + */ + public function testAuthenticateScopeFail() + { + $this->auth->config('scope', ['user_name' => 'nate']); + $request = new Request('posts/index'); + $request->data = [ + 'user_name' => 'mariano', + 'password' => 'password' + ]; + + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } +} diff --git a/tests/TestCase/Auth/TokenAuthenticateTest.php b/tests/TestCase/Auth/TokenAuthenticateTest.php new file mode 100644 index 0000000..20e1fc3 --- /dev/null +++ b/tests/TestCase/Auth/TokenAuthenticateTest.php @@ -0,0 +1,103 @@ +Registry = $this->getMock('Cake\Controller\ComponentRegistry'); + $this->auth = new TokenAuthenticate($this->Registry, [ + 'fields' => [ + 'username' => 'user_name', + 'password' => 'password', + 'token' => 'token' + ], + 'userModel' => 'MultiUsers' + ]); + + $password = password_hash('password', PASSWORD_DEFAULT); + $MultiUsers = TableRegistry::get('MultiUsers'); + $MultiUsers->updateAll(['password' => $password], []); + + $this->response = $this->getMock('Cake\Network\Response'); + } + + /** + * test authenticate token as query parameter + * + * @return void + */ + public function testAuthenticateTokenParameter() + { + $this->auth->config('_parameter', 'token'); + $request = new Request('posts/index?_token=54321'); + + $result = $this->auth->getUser($request, $this->response); + $this->assertFalse($result); + + $expected = [ + 'id' => 1, + 'user_name' => 'mariano', + 'email' => 'mariano@example.com', + 'token' => '12345', + 'created' => new Time('2007-03-17 01:16:23'), + 'updated' => new Time('2007-03-17 01:18:31') + ]; + $request = new Request('posts/index?_token=12345'); + $result = $this->auth->getUser($request, $this->response); + $this->assertEquals($expected, $result); + + $this->auth->config('parameter', 'tokenname'); + $request = new Request('posts/index?tokenname=12345'); + $result = $this->auth->getUser($request, $this->response); + $this->assertEquals($expected, $result); + } + + /** + * test authenticate token as request header + * + * @return void + */ + public function testAuthenticateTokenHeader() + { + $request = new Request('posts/index'); + + $expected = [ + 'id' => 1, + 'user_name' => 'mariano', + 'email' => 'mariano@example.com', + 'token' => '12345', + 'created' => new Time('2007-03-17 01:16:23'), + 'updated' => new Time('2007-03-17 01:18:31') + ]; + $request->env('HTTP_X_APITOKEN', '12345'); + $result = $this->auth->getUser($request, $this->response); + $this->assertEquals($expected, $result); + + $request->env('HTTP_X_APITOKEN', '66666'); + $result = $this->auth->getUser($request, $this->response); + $this->assertFalse($result); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..ba4284a --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,101 @@ +register(); + +$loader->addNamespace('TestApp', APP); +$loader->addNamespace('AuthenticateTestPlugin', APP . 'Plugin' . DS . 'TestPlugin' . DS . 'src'); + +require_once CORE_PATH . 'config/bootstrap.php'; + +date_default_timezone_set('UTC'); +mb_internal_encoding('UTF-8'); + +Configure::write('debug', true); +Configure::write('App', [ + 'namespace' => 'App', + 'encoding' => 'UTF-8', + 'base' => false, + 'baseUrl' => false, + 'dir' => 'src', + 'webroot' => 'webroot', + 'www_root' => APP . 'webroot', + 'fullBaseUrl' => 'http://localhost', + 'imageBaseUrl' => 'img/', + 'jsBaseUrl' => 'js/', + 'cssBaseUrl' => 'css/', + 'paths' => [ + 'plugins' => [APP . 'Plugin' . DS], + 'templates' => [APP . 'Template' . DS] + ] +]); +Configure::write('Session', [ + 'defaults' => 'php' +]); + +Cache::config([ + '_cake_core_' => [ + 'engine' => 'File', + 'prefix' => 'cake_core_', + 'serialize' => true + ], + '_cake_model_' => [ + 'engine' => 'File', + 'prefix' => 'cake_model_', + 'serialize' => true + ], + 'default' => [ + 'engine' => 'File', + 'prefix' => 'default_', + 'serialize' => true + ] +]); + +// Ensure default test connection is defined +if (!getenv('db_class')) { + putenv('db_class=Cake\Database\Driver\Sqlite'); + putenv('db_dsn=sqlite::memory:'); +} + +ConnectionManager::config('test', [ + 'className' => 'Cake\Database\Connection', + 'driver' => getenv('db_class'), + 'dsn' => getenv('db_dsn'), + 'database' => getenv('db_database'), + 'username' => getenv('db_login'), + 'password' => getenv('db_password'), + 'timezone' => 'UTC' +]); + +Log::config([ + 'debug' => [ + 'engine' => 'Cake\Log\Engine\FileLog', + 'levels' => ['notice', 'info', 'debug'], + 'file' => 'debug', + ], + 'error' => [ + 'engine' => 'Cake\Log\Engine\FileLog', + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + 'file' => 'error', + ] +]); + +Plugin::load('FOC/Authenticate', ['path' => ROOT]);