Skip to content
This repository has been archived by the owner on Sep 10, 2021. It is now read-only.

Commit

Permalink
ENH: refs #951. Add token endpoint testing
Browse files Browse the repository at this point in the history
  • Loading branch information
zachmullen committed Mar 13, 2013
1 parent 6b84fc5 commit d891447
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 16 deletions.
5 changes: 3 additions & 2 deletions modules/oauth/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public function requirePermissions($params)
/** Handle web API authentication with an OAuth token */
public function handleAuth($params)
{
if(array_key_exists('oauth_token', $params))
$apiArgs = $params['args'];
if(array_key_exists('oauth_token', $apiArgs))
{
if(Zend_Registry::isRegistered('oauthRequiredScopes'))
{
Expand All @@ -66,7 +67,7 @@ public function handleAuth($params)
$requiredScopes = array(MIDAS_API_PERMISSION_SCOPE_ALL);
}

$tokenDao = $this->Oauth_Token->getByToken($params['oauth_token']);
$tokenDao = $this->Oauth_Token->getByToken($apiArgs['oauth_token']);
if(!$tokenDao || $tokenDao->getType() != MIDAS_OAUTH_TOKEN_TYPE_ACCESS)
{
throw new Zend_Exception('Invalid OAuth access token', 400);
Expand Down
26 changes: 15 additions & 11 deletions modules/oauth/controllers/TokenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ function indexAction()
if(!$authHeader)
{
$this->_doOutput(array('error' => 'invalid_client',
'error_description' => 'Must pass client_secret parameter or pass client secret using Authentication header'));
'error_description' => 'Must pass client_secret parameter or pass client secret using Authorization header'));
return;
}
list($mode, $secret) = explode(' ', $authHeader);
if($mode !== 'Basic' || !$secret)
{
$this->_doOutput(array('error' => 'invalid_client',
'error_description' => 'Must use header form Authorization: Basic <client_secret>'));
return;
}
}

Expand Down Expand Up @@ -216,17 +217,20 @@ private function _refreshToken($secret)
*/
private function _doOutput($array)
{
header('Content-Type: application/json;charset=UTF-8');
header('Cache-Control: no-store');
header('Pragma: no-cache');
if(array_key_exists('error', $array))
{
$this->getResponse()->setHttpResponseCode(400);
$this->getLogger()->crit('Access token denied ('.$array['error_description'].') '.print_r($this->_getAllParams(), true));
}
else
if(!headers_sent())
{
$this->getResponse()->setHttpResponseCode(200);
header('Content-Type: application/json;charset=UTF-8');
header('Cache-Control: no-store');
header('Pragma: no-cache');
if(array_key_exists('error', $array))
{
$this->getResponse()->setHttpResponseCode(400);
$this->getLogger()->crit('Access token denied ('.$array['error_description'].') '.print_r($this->_getAllParams(), true));
}
else
{
$this->getResponse()->setHttpResponseCode(200);
}
}
echo JsonComponent::encode($array);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ private function _testParamsRequired($uri, $params, $userDao = null)
$this->params = $localParams;
$this->getRequest()->setMethod('GET');
$this->dispatchUri($uri, $userDao, true);
$this->assertEquals($this->getResponse()->getHttpResponseCode(), 400); //IETF spec dictates we must send BAD_REQUEST response
}
}

Expand Down
217 changes: 215 additions & 2 deletions modules/oauth/tests/controllers/TokenControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,227 @@ public function setUp()
$this->setupDatabase(array('default'), 'oauth');
$this->enabledModules = array('api', 'oauth');
$this->_models = array('User');
$this->_components = array('Json');

parent::setUp();
}

/**
* TODO stub
* Helper function for asserting error responses from the token endpoint
* @param errorName The error name as specified in the IETF spec draft
*/
public function testStub()
private function _assertErrorResponse($errorName)
{
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['error'], $errorName);
}

/**
* This tests the token endpoint, used by clients for
* 1. Exchanging an authorization code for access/refresh tokens
* 2. Using a refresh token to get a new access token
*/
public function testIndexAction()
{
$clientModel = MidasLoader::loadModel('Client', 'oauth');
$codeModel = MidasLoader::loadModel('Code', 'oauth');

// I. Test exchanging a code for tokens
$client = $clientModel->load(1000);
$user = $this->User->load(1);
$codeDao = $codeModel->create($user, $client, array(1, 2, 3));
$otherClient = $clientModel->create($user, 'other client');

// 1. Test failure conditions
// a. No secret passed
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_client');
$this->resetAll();
// b. Correct secret passed, but missing grant type
$this->params['client_secret'] = $client->getSecret();
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('unsupported_grant_type');
$this->resetAll();
// c. Missing client_id
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_request');
$this->resetAll();
// d. Missing code
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_request');
$this->resetAll();
// e. Incorrect secret passed
$this->params['client_secret'] = 'wrong';
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->params['code'] = $codeDao->getCode();
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_client');
$this->resetAll();
// f. Incorrect code
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->params['code'] = 'wrong';
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_grant');
$this->resetAll();
// g. Expired code
$codeDao->setExpirationDate(date('c', strtotime('-1 hour')));
$codeModel->save($codeDao);
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->params['code'] = $codeDao->getCode();
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_grant');
$codeDao->setExpirationDate(date('c', strtotime('+1 hour')));
$codeModel->save($codeDao);
$this->resetAll();
// h. Client id mismatch
$this->params['client_secret'] = $otherClient->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $otherClient->getKey();
$this->params['code'] = $codeDao->getCode();
$this->dispatchUri('/oauth/token');
$this->assertNotEquals($otherClient->getKey(), $client->getKey());
$this->_assertErrorResponse('invalid_grant');
$this->resetAll();

// 2. Test success conditions
// a. With secret passed in Authorization header
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->params['code'] = $codeDao->getCode();
$this->dispatchUri('/oauth/token');
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['token_type'], 'bearer');
$this->assertNotEmpty($json['access_token']);
$this->assertNotEmpty($json['refresh_token']);
$this->assertNotEmpty($json['expires_in']);
$this->assertTrue(is_numeric($json['expires_in']));
$this->resetAll();
// b. With secret passed in params
$codeDao = $codeModel->create($user, $client, array(1, 2, 3)); // have to re-create the code since last call deleted it
$this->getRequest()->setHeader('Authorization', 'Basic '.$client->getSecret());
$this->params['grant_type'] = 'authorization_code';
$this->params['client_id'] = $client->getKey();
$this->params['code'] = $codeDao->getCode();
$this->dispatchUri('/oauth/token');
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['token_type'], 'bearer');
$this->assertNotEmpty($json['access_token']);
$this->assertNotEmpty($json['refresh_token']);
$this->assertNotEmpty($json['expires_in']);
$this->assertTrue(is_numeric($json['expires_in']));
$this->resetAll();

// II. Test refreshing an access token with a refresh token
$refreshToken = $json['refresh_token'];
$accessToken = $json['access_token'];
// 1. Test failure conditions
// a. Trying to refresh using an access token
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'refresh_token';
$this->params['refresh_token'] = $accessToken;
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_grant');
$this->resetAll();
// b. Trying to refresh as wrong client
$this->params['client_secret'] = $otherClient->getSecret();
$this->params['grant_type'] = 'refresh_token';
$this->params['refresh_token'] = $refreshToken;
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_client');
$this->resetAll();
// c. Trying to refresh using something that isn't a token at all
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'refresh_token';
$this->params['refresh_token'] = 'not_a_real_token';
$this->dispatchUri('/oauth/token');
$this->_assertErrorResponse('invalid_grant');
$this->resetAll();

// 2. Test success conditions
$this->params['client_secret'] = $client->getSecret();
$this->params['grant_type'] = 'refresh_token';
$this->params['refresh_token'] = $refreshToken;
$this->dispatchUri('/oauth/token');
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['token_type'], 'bearer');
$this->assertNotEmpty($json['access_token']);
$this->assertNotEmpty($json['expires_in']);
$this->assertTrue(is_numeric($json['expires_in']));
$this->resetAll();
}

/**
* Test actually using access tokens to authenticate when calling API methods
*/
public function testApiAccess()
{
$adminUser = $this->User->load(3);
$clientModel = MidasLoader::loadModel('Client', 'oauth');
$codeModel = MidasLoader::loadModel('Code', 'oauth');
$tokenModel = MidasLoader::loadModel('Token', 'oauth');
$client = $clientModel->load(1000);
$codeDao = $codeModel->create($adminUser, $client, array(1, 2, 3));

// Create an expired access token
$accessToken = $tokenModel->createAccessToken($codeDao, '-1 hour');

// Calling community.create without authentication should fail since admin is required
$uri = '/api/json?method=midas.community.create&name=hello';
$this->dispatchUri($uri);
$this->_assertApiFailure();
$this->resetAll();

// Calling with an expired token should fail
$uri .= '&oauth_token='.urlencode($accessToken->getToken());
$this->dispatchUri($uri);
$this->_assertApiFailure();
$this->resetAll();

// Test with valid token but incorrect scope
$accessToken->setExpirationDate(date('c', strtotime('+1 hour')));
$tokenModel->save($accessToken);
$this->dispatchUri($uri);
$this->_assertApiFailure();
$this->resetAll();

// Set scope to ALL; should now work
$accessToken->setScopes(JsonComponent::encode(array(0)));
$tokenModel->save($accessToken);
$this->dispatchUri($uri);
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['stat'], 'ok');
$this->assertEquals($json['code'], 0);
$this->assertNotEmpty($json['data']);
$this->assertNotEmpty($json['data']['community_id']);
$this->assertEquals($json['data']['name'], 'Hello');
}

/**
* Helper method to make sure that a web api call failed
*/
private function _assertApiFailure()
{
$json = JsonComponent::decode($this->getBody());
$this->assertEquals($json['stat'], 'fail');
$this->assertTrue($json['code'] != 0);
}

/**
* Test deletion of tokens (done via the OAuth user settings tab)
*/
public function testDeauthorize()
{
// TODO stub
}
}

0 comments on commit d891447

Please sign in to comment.