Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added User Credentials and Refresh Token Grants #2

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions sql/database.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ CREATE TABLE `client_endpoints` (
CREATE TABLE `oauth_sessions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`client_id` varchar(40) NOT NULL DEFAULT '',
`redirect_uri` varchar(250) NOT NULL DEFAULT '',
`redirect_uri` varchar(250) DEFAULT '',
`owner_type` enum('user','client') NOT NULL DEFAULT 'user',
`owner_id` varchar(255) DEFAULT NULL,
`auth_code` varchar(40) DEFAULT '',
`access_token` varchar(40) DEFAULT '',
`refresh_token` varchar(40) NOT NULL,
`access_token_expires` int(10) DEFAULT NULL,
`stage` enum('requested','granted') NOT NULL DEFAULT 'requested',
`first_requested` int(10) unsigned NOT NULL,
Expand Down Expand Up @@ -56,4 +57,4 @@ CREATE TABLE `oauth_session_scopes` (
KEY `scope` (`scope`),
CONSTRAINT `oauth_session_scopes_ibfk_3` FOREIGN KEY (`scope`) REFERENCES `scopes` (`scope`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `oauth_session_scopes_ibfk_4` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
22 changes: 22 additions & 0 deletions src/Oauth2/Authentication/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function newSession(
$typeId = null,
$authCode = null,
$accessToken = null,
$refreshToken = null,
$accessTokenExpire = null,
$stage = 'requested'
);
Expand All @@ -102,6 +103,7 @@ public function updateSession(
$sessionId,
$authCode = null,
$accessToken = null,
$refreshToken = null,
$accessTokenExpire = null,
$stage = 'requested'
);
Expand All @@ -125,6 +127,26 @@ public function deleteSession(
$typeId
);


/**
* Update the refresh token
*
* Database query:
*
* <code>
* UPDATE oauth_sessions SET access_token = $newAccessToken, refresh_token =
* $newRefreshToken, access_toke_expires = $accessTokenExpires, last_updated = UNIX_TIMESTAMP(NOW()) WHERE
* refresh_token = $currentRefreshToken
* </code>
*
* @param string $currentRefreshToken The session's current refresh token
* @param string $newAccessToken The new access token for this session
* @param string $newRefreshToken The new refresh token for the session
* @param int $accessTokenExpires The UNIX timestamp of when the new token expires
* @return bool Whether the $currentRefreshToken was valid or not.
*/
public function refreshToken($currentRefreshToken, $newAccessToken, $newRefreshToken, $accessTokenExpires);

/**
* Validate that an authorisation code is valid
*
Expand Down
202 changes: 189 additions & 13 deletions src/Oauth2/Authentication/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Server
* @var array
*/
private $_config = array(
'scope_delimeter' => ',',
'access_token_ttl' => null
'scope_delimeter' => ',',
'access_token_ttl' => 3600
);

/**
Expand All @@ -47,7 +47,9 @@ class Server
* @var array
*/
private $_grantTypes = array(
'authorization_code'
'authorization_code',
'user_credentials',
'refresh_token',
);

/**
Expand Down Expand Up @@ -75,16 +77,18 @@ class Server
* @var array
*/
public $errors = array(
'invalid_request' => 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the "%s" parameter.',
'unauthorized_client' => 'The client is not authorized to request an access token using this method.',
'access_denied' => 'The resource owner or authorization server denied the request.',
'invalid_request' => 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the "%s" parameter.',
'unauthorized_client' => 'The client is not authorized to request an access token using this method.',
'access_denied' => 'The resource owner or authorization server denied the request.',
'unsupported_response_type' => 'The authorization server does not support obtaining an access token using this method.',
'invalid_scope' => 'The requested scope is invalid, unknown, or malformed. Check the "%s" scope.',
'server_error' => 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.',
'invalid_scope' => 'The requested scope is invalid, unknown, or malformed. Check the "%s" scope.',
'server_error' => 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.',
'temporarily_unavailable' => 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.',
'unsupported_grant_type' => 'The authorization grant type is not supported by the authorization server',
'invalid_client' => 'Client authentication failed',
'invalid_grant' => 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. Check the "%s" parameter.'
'invalid_client' => 'Client authentication failed',
'invalid_grant' => 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. Check the "%s" parameter.',
'invalid_credentials' => 'Invalid Credentials',
'invalid_refresh' => 'Invalid Refresh Token',
);

/**
Expand Down Expand Up @@ -284,7 +288,7 @@ private function generateCode()
* @param string $accessToken The access token (default = null)
* @return string An authorisation code
*/
private function newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes = array(), $accessToken = null)
private function newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes = array(), $accessToken = null, $refreshToken = null)
{
$authCode = $this->generateCode();

Expand All @@ -299,6 +303,7 @@ private function newAuthCode($clientId, $type, $typeId, $redirectUri, $scopes =
$typeId,
$authCode,
$accessToken,
$refreshToken,
'requested'
);

Expand Down Expand Up @@ -374,9 +379,17 @@ public function issueAccessToken($authParams = null)
return $this->completeAuthCodeGrant($authParams, $params);
break;

case 'user_credentials':
return $this->completeUserCredentialsGrant($authParams, $params);
break;

case 'refresh_token': // Refresh token
return $this->completeRefreshTokenGrant($authParams, $params);
break;

case 'password': // Resource owner password credentials grant
case 'client_credentials': // Client credentials grant

default: // Unsupported
throw new ServerException($this->errors['server_error'] . 'Tried to process an unsuppported grant type.', 5);
break;
Expand Down Expand Up @@ -479,16 +492,19 @@ private function completeAuthCodeGrant($authParams = array(), $params = array())
// remove the authorisation code, change the stage to 'granted'

$accessToken = $this->generateCode();
$refreshToken = $this->generateCode();

$accessTokenExpires = ($this->_config['access_token_ttl'] === null) ?
null :
time() + $this->_config['access_token_ttl'];
$accessTokenExpiresIn = ($this->_config['access_token_ttl'] === null) ? 0 : $this->_config['access_token_ttl'];

$this->_dbCall(
'updateSession',
$session['id'],
null,
$accessToken,
$refreshToken,
$accessTokenExpires,
'granted'
);
Expand All @@ -497,17 +513,177 @@ private function completeAuthCodeGrant($authParams = array(), $params = array())
$this->_dbCall(
'updateSessionScopeAccessToken',
$session['id'],
$accessToken
$accessToken,
$refreshToken
);

return array(
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'bearer',
'expires_in' => $this->_config['access_token_ttl']
'expires' => $accessTokenExpires,
'expires_in' => $accessTokenExpiresIn
);
}
}

/**
* Complete the user credentials grant
*
* @access private
*
* @param array $authParams Array of parsed $_POST keys
* @param array $params Generated parameters from issueAccessToken()
*
* @return array Authorise request parameters
*/
private function completeUserCredentialsGrant($authParams = array(), $params = array())
{
$params = array();

if ( ! isset($authParams['user_auth_callback'])) {
throw new \InvalidArgumentException('You must set a user_auth_callback when using the user_credentials grant type.');
}

// Client ID
if ( ! isset($authParams['client_id']) && ! isset($_POST['client_id'])) {
throw new ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0);
} else {
$params['client_id'] = (isset($authParams['client_id'])) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here.

$authParams['client_id'] :
$_POST['client_id'];
}

// Client secret
if ( ! isset($authParams['client_secret']) && ! isset($_POST['client_secret'])) {
throw new ClientException(sprintf($this->errors['invalid_request'], 'client_secret'), 0);
} else {
$params['client_secret'] = (isset($authParams['client_secret'])) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

$authParams['client_secret'] :
$_POST['client_secret'];
}

// Validate client ID and redirect URI
$clientDetails = $this->_dbCall(
'validateClient',
$params['client_id'],
$params['client_secret'],
null
);

if ($clientDetails === false) {
throw new \Oauth2\Authentication\ClientException($this->errors['invalid_client'], 8);
}

// Check for grant
if ( ! isset($_POST['grant_type'])) {
throw new \Oauth2\Authentication\ClientException(sprintf($this->errors['invalid_request'], 'client_id'), 0);
} else {
$params['grant_type'] = $_POST['grant_type'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

}

if ($params['grant_type'] == 'user_credentials')
{
// Check if user's u+p are correct
$userId = call_user_func($authParams['user_auth_callback']);

if ($userId === false)
{
throw new \Oauth2\Authentication\ClientException($this->errors['invalid_credentials'], 0);
}

else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else could be removed.

{
// Generate an access token
$accessToken = $this->generateCode();
$refreshToken = $this->generateCode();

$accessTokenExpires = ($this->_config['access_token_ttl'] === null) ?
null :
time() + $this->_config['access_token_ttl'];
$accessTokenExpiresIn = ($this->_config['access_token_ttl'] === null) ? 0 : $this->_config['access_token_ttl'];

// Delete any existing sessions just to be sure
$this->_dbCall('deleteSession', $params['client_id'], 'user', $userId);

// Create a new session
$this->_dbCall('newSession', $params['client_id'], null, 'user', $userId, null, $accessToken, $refreshToken, $accessTokenExpires, 'granted');

return array(
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'bearer',
'expires' => $accessTokenExpires,
'expires_in' => $accessTokenExpiresIn
);
}
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you check for the negative first you can throw the exception instead of doing it in the if and throwing the exception in the else.

throw new \Oauth2\Authentication\ClientException($this->errors['unsupported_grant_type'], 7);
}

}

/**
* Complete the refresh token grant
*
* @access private
*
* @param array $authParams Array of parsed $_POST keys
* @param array $params Generated parameters from issueAccessToken()
*
* @return array Authorise request parameters
*/
private function completeRefreshTokenGrant($authParams = array(), $params = array())
{
$params = array();

// Check for grant
if ( ! isset($_POST['grant_type'])) {
throw new \Oauth2\Authentication\ClientException(sprintf($this->errors['invalid_request'], 'grant_type'), 0);
} else {
$params['grant_type'] = $_POST['grant_type'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else is not needed.

}

if ( ! isset($authParams['refresh_token']) && ! isset($_POST['refresh_token'])) {
throw new ClientException(sprintf($this->errors['invalid_request'], 'refresh_token'), 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

} else {
$params['refresh_token'] = (isset($authParams['refresh_token'])) ?
$authParams['refresh_token'] :
$_POST['refresh_token'];
}

if ($params['grant_type'] == 'refresh_token')
{
// Generate an access token
$accessToken = $this->generateCode();
$refreshToken = $this->generateCode();

$accessTokenExpires = ($this->_config['access_token_ttl'] === null) ?
null :
time() + $this->_config['access_token_ttl'];

$accessTokenExpiresIn = ($this->_config['access_token_ttl'] === null) ? 0 : $this->_config['access_token_ttl'];

// Delete any existing sessions just to be sure
$result = $this->_dbCall('refreshToken', $params['refresh_token'], $accessToken, $refreshToken, $accessTokenExpires);

if ( ! $result) {
throw new \Oauth2\Authentication\ClientException($this->errors['invalid_refresh'], 0);
}

return array(
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'bearer',
'expires' => $accessTokenExpires,
'expires_in' => $accessTokenExpiresIn
);
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception can also be moved up to eliminate the nesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FrenkyNet I am aware. I was just doing it for consistency in the class. Not how I would normally do it, but want to be consistent.

throw new \Oauth2\Authentication\ClientException($this->errors['unsupported_grant_type'], 7);
}

}

/**
* Generates the redirect uri with appended params
*
Expand Down