Skip to content
This repository has been archived by the owner on May 29, 2020. It is now read-only.

Commit

Permalink
Merge 26653fd into 83fe6c0
Browse files Browse the repository at this point in the history
  • Loading branch information
ADmad committed Sep 13, 2014
2 parents 83fe6c0 + 26653fd commit cbc06dc
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"require": {
"php": ">=5.4.0",
"cakephp/cakephp": "3.0.*-dev",
"cakephp/plugin-installer": "dev-master"
"cakephp/plugin-installer": "dev-master",
"firebase/php-jwt": "*"
},
"require-dev": {
"phpunit/phpunit": "4.1.*"
Expand Down
139 changes: 139 additions & 0 deletions src/Auth/JwtAuthenticate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
namespace FOC\Authenticate\Auth;

use Cake\Auth\BaseAuthenticate;
use Cake\Controller\ComponentRegistry;
use Cake\Network\Request;
use Cake\Network\Response;
use Cake\ORM\TableRegistry;
use Cake\Utility\Security;
use \JWT;

/**
* An authentication adapter for authenticating using JSON Web Tokens.
*
* {{{
* $this->Auth->config('authenticate', [
* 'FOC/Authenticate.Jwt' => [
* 'parameter' => '_token',
* 'userModel' => 'Users',
* 'scope' => ['User.active' => 1]
* 'fields' => [
* 'id' => 'id',
* ],
* ]
* ]);
* }}}
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
*
* @see http://jwt.io
* @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token
*/
class JwtAuthenticate extends BaseAuthenticate {

/**
* Constructor.
*
* Settings for this object.
*
* - `parameter` - The url parameter name of the token. Defaults to `_token`.
* First $_SERVER['HTTP_BEARER'] is checked for token value. If empty this
* query string paramater is checked.
* - `userModel` - The model name of the User, defaults to `Users`.
* - `fields` - Has key `id` whose value contains primary key field name.
* Defaults to ['id' => 'id'].
* - `scope` - Additional conditions to use when looking up and authenticating
* users, i.e. `['Users.is_active' => 1].`
* - `contain` - Extra models to contain.
*
* @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->config([
'parameter' => '_token',
'fields' => ['id' => 'id'],
]);

parent::__construct($registry, $config);
}

/**
* Unused since this is a stateless authentication provider.
*
* @param Request $request The request object.
* @param Response $response response object.
* @return boolean Always false.
*/
public function authenticate(Request $request, Response $response) {
return false;
}

/**
* Get token information from the request.
*
* @param \Cake\Network\Request $request Request object.
* @return bool|array Either false or an array of user information
*/
public function getUser(Request $request) {
$token = $request->env('HTTP_BEARER');
if ($token) {
return $this->_findUser($token);
}

if (!empty($this->_config['parameter']) &&
$token = $request->query($this->_config['parameter'])
) {
return $this->_findUser($token);
}

return false;
}

/**
* Find a user record.
*
* @param string $token The token identifier.
* @param string $password Unused password.
* @return bool|array Either false on failure, or an array of user data.
*/
protected function _findUser($token, $password = null) {
$token = JWT::decode($token, Security::salt());

// Token has full user record.
if (isset($token->record)) {
// Trick to convert object of stdClass to array. Typecasting to
// array doesn't convert property values which are themselves objects.
return json_decode(json_encode($token->record), true);
}

$userModel = $this->_config['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->_config['fields'];

$conditions = [$model . '.' . $fields['id'] => $token->id];
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($contain);
}

$result = $table
->where($conditions)
->hydrate(false)
->first();

if (empty($result)) {
return false;
}

unset($result[$fields['password']]);
return $result;
}

}
113 changes: 113 additions & 0 deletions tests/TestCase/Auth/JwtAuthenticateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
namespace FOC\Authenticate\Auth\Test\TestCase\Auth;

use Cake\Controller\Component\AuthComponent;
use Cake\Controller\Controller;
use Cake\I18n\Time;
use Cake\Network\Request;
use Cake\Network\Response;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
use Cake\Utility\Security;
use FOC\Authenticate\Auth\JwtAuthenticate;
use \JWT;

/**
* Test case for JwtAuthentication
*/
class JwtAuthenticateTest extends TestCase {

public $fixtures = ['plugin.FOC\Authenticate.multi_user'];

/**
* setup
*
* @return void
*/
public function setUp() {
parent::setUp();

$this->Registry = $this->getMock('Cake\Controller\ComponentRegistry');
$this->auth = new JwtAuthenticate($this->Registry, [
'userModel' => 'MultiUsers'
]);

$this->token = JWT::encode(['id' => 1], Security::salt());

$this->response = $this->getMock('Cake\Network\Response');
}

/**
* test authenticate token as query parameter
*
* @return void
*/
public function testAuthenticateTokenParameter() {
$request = new Request('posts/index');

$result = $this->auth->getUser($request, $this->response);
$this->assertFalse($result);

$expected = array(
'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=' . $this->token);
$result = $this->auth->getUser($request, $this->response);
$this->assertEquals($expected, $result);

$this->auth->config('parameter', 'tokenname');
$request = new Request('posts/index?tokenname=' . $this->token);
$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 = array(
'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_BEARER', $this->token);
$result = $this->auth->getUser($request, $this->response);
$this->assertEquals($expected, $result);

$this->setExpectedException('UnexpectedValueException');
$request->env('HTTP_BEARER', '66666');
$result = $this->auth->getUser($request, $this->response);
$this->assertFalse($result);
}

/**
* test authenticate token with user record
*
* @return void
*/
public function testAuthenticateWithUserRecord() {
$request = new Request('posts/index');

$expected = [
'id' => 99,
'username' => 'ADmad',
'group' => ['name' => 'admin']
];
$request->env('HTTP_BEARER', JWT::encode(['record' => $expected], Security::salt()));
$result = $this->auth->getUser($request, $this->response);
$this->assertEquals($expected, $result);
}

}

0 comments on commit cbc06dc

Please sign in to comment.