371 lines
13 KiB
PHP
371 lines
13 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace OAuth2\Storage;
|
||
|
|
||
|
/**
|
||
|
* Simple PDO storage for all storage types
|
||
|
*
|
||
|
* NOTE: This class is meant to get users started
|
||
|
* quickly. If your application requires further
|
||
|
* customization, extend this class or create your own.
|
||
|
*
|
||
|
* NOTE: Passwords are stored in plaintext, which is never
|
||
|
* a good idea. Be sure to override this for your application
|
||
|
*
|
||
|
* @author Brent Shaffer <bshafs at gmail dot com>
|
||
|
*/
|
||
|
class Pdo implements
|
||
|
AuthorizationCodeInterface,
|
||
|
AccessTokenInterface,
|
||
|
ClientCredentialsInterface,
|
||
|
RefreshTokenInterface,
|
||
|
ScopeInterface
|
||
|
{
|
||
|
protected $db;
|
||
|
protected $config;
|
||
|
|
||
|
public function __construct($connection, $config = array())
|
||
|
{
|
||
|
if (!$connection instanceof \PDO) {
|
||
|
if (is_string($connection)) {
|
||
|
$connection = array('dsn' => $connection);
|
||
|
}
|
||
|
if (!is_array($connection)) {
|
||
|
throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array');
|
||
|
}
|
||
|
if (!isset($connection['dsn'])) {
|
||
|
throw new \InvalidArgumentException('configuration array must contain "dsn"');
|
||
|
}
|
||
|
// merge optional parameters
|
||
|
$connection = array_merge(array(
|
||
|
'username' => null,
|
||
|
'password' => null,
|
||
|
'options' => array(),
|
||
|
), $connection);
|
||
|
$connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']);
|
||
|
}
|
||
|
$this->db = $connection;
|
||
|
|
||
|
// debugging
|
||
|
$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||
|
|
||
|
$this->config = array_merge(array(
|
||
|
'client_table' => 'oauth_clients',
|
||
|
'access_token_table' => 'oauth_access_tokens',
|
||
|
'refresh_token_table' => 'oauth_refresh_tokens',
|
||
|
'code_table' => 'oauth_authorization_codes',
|
||
|
'scope_table' => 'oauth_scopes',
|
||
|
'assoc_users_table' => 'users'
|
||
|
), $config);
|
||
|
}
|
||
|
|
||
|
/* OAuth2\Storage\ClientCredentialsInterface */
|
||
|
public function checkClientCredentials($client_id, $client_secret = null)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
|
||
|
$stmt->execute(compact('client_id'));
|
||
|
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||
|
|
||
|
// make this extensible
|
||
|
return $result && $result['client_secret'] == $client_secret;
|
||
|
}
|
||
|
|
||
|
public function isPublicClient($client_id)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
|
||
|
$stmt->execute(compact('client_id'));
|
||
|
|
||
|
if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return empty($result['client_secret']);
|
||
|
}
|
||
|
|
||
|
/* OAuth2\Storage\ClientInterface */
|
||
|
public function getClientDetails($client_id)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
|
||
|
$stmt->execute(compact('client_id'));
|
||
|
|
||
|
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
||
|
}
|
||
|
|
||
|
public function getClientScope($client_id)
|
||
|
{
|
||
|
if (!$clientDetails = $this->getClientDetails($client_id)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (isset($clientDetails['scope'])) {
|
||
|
return $clientDetails['scope'];
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public function checkRestrictedGrantType($client_id, $grant_type)
|
||
|
{
|
||
|
$details = $this->getClientDetails($client_id);
|
||
|
if (isset($details['grant_types'])) {
|
||
|
$grant_types = explode(' ', $details['grant_types']);
|
||
|
|
||
|
return in_array($grant_type, (array) $grant_types);
|
||
|
}
|
||
|
|
||
|
// if grant_types are not defined, then none are restricted
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* OAuth2\Storage\AccessTokenInterface */
|
||
|
public function getAccessToken($access_token)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table']));
|
||
|
|
||
|
$token = $stmt->execute(compact('access_token'));
|
||
|
if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
// convert date string back to timestamp
|
||
|
$token['expires'] = strtotime($token['expires']);
|
||
|
}
|
||
|
|
||
|
return $token;
|
||
|
}
|
||
|
|
||
|
public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
|
||
|
{
|
||
|
// convert expires to datestring
|
||
|
$expires = date('Y-m-d H:i:s', $expires);
|
||
|
|
||
|
// if it exists, update it.
|
||
|
if ($this->getAccessToken($access_token)) {
|
||
|
$stmt = $this->db->prepare(sprintf('UPDATE %s SET client_id=:client_id, expires=:expires, user_id=:user_id, scope=:scope where access_token=:access_token', $this->config['access_token_table']));
|
||
|
} else {
|
||
|
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table']));
|
||
|
}
|
||
|
|
||
|
return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope'));
|
||
|
}
|
||
|
|
||
|
public function unsetAccessToken($access_token)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table']));
|
||
|
|
||
|
$stmt->execute(compact('access_token'));
|
||
|
|
||
|
return $stmt->rowCount() > 0;
|
||
|
}
|
||
|
|
||
|
/* OAuth2\Storage\AuthorizationCodeInterface */
|
||
|
public function getAuthorizationCode($code)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table']));
|
||
|
$stmt->execute(compact('code'));
|
||
|
|
||
|
if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
// convert date string back to timestamp
|
||
|
$code['expires'] = strtotime($code['expires']);
|
||
|
}
|
||
|
|
||
|
return $code;
|
||
|
}
|
||
|
|
||
|
public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
|
||
|
{
|
||
|
// convert expires to datestring
|
||
|
$expires = date('Y-m-d H:i:s', $expires);
|
||
|
|
||
|
// if it exists, update it.
|
||
|
if ($this->getAuthorizationCode($code)) {
|
||
|
$stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table']));
|
||
|
} else {
|
||
|
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table']));
|
||
|
}
|
||
|
|
||
|
return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'));
|
||
|
}
|
||
|
|
||
|
public function expireAuthorizationCode($code)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table']));
|
||
|
|
||
|
return $stmt->execute(compact('code'));
|
||
|
}
|
||
|
|
||
|
|
||
|
/* OAuth2\Storage\RefreshTokenInterface */
|
||
|
public function getRefreshToken($refresh_token)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
|
||
|
|
||
|
$token = $stmt->execute(compact('refresh_token'));
|
||
|
if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
// convert expires to epoch time
|
||
|
$token['expires'] = strtotime($token['expires']);
|
||
|
}
|
||
|
|
||
|
return $token;
|
||
|
}
|
||
|
|
||
|
public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
|
||
|
{
|
||
|
// convert expires to datestring
|
||
|
$expires = date('Y-m-d H:i:s', $expires);
|
||
|
|
||
|
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (refresh_token, client_id, user_id, expires, scope) VALUES (:refresh_token, :client_id, :user_id, :expires, :scope)', $this->config['refresh_token_table']));
|
||
|
|
||
|
return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'));
|
||
|
}
|
||
|
|
||
|
public function unsetRefreshToken($refresh_token)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
|
||
|
|
||
|
$stmt->execute(compact('refresh_token'));
|
||
|
|
||
|
return $stmt->rowCount() > 0;
|
||
|
}
|
||
|
|
||
|
/* ScopeInterface */
|
||
|
public function scopeExists($scope)
|
||
|
{
|
||
|
$scope = explode(' ', $scope);
|
||
|
$whereIn = implode(',', array_fill(0, count($scope), '?'));
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn));
|
||
|
$stmt->execute($scope);
|
||
|
|
||
|
if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
return $result['count'] == count($scope);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public function getDefaultScope($client_id = null)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table']));
|
||
|
$stmt->execute(array('is_default' => true));
|
||
|
|
||
|
if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) {
|
||
|
$defaultScope = array_map(function ($row) {
|
||
|
return $row['scope'];
|
||
|
}, $result);
|
||
|
|
||
|
return implode(' ', $defaultScope);
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||
|
/**
|
||
|
* @author Denis CLAVIER <clavierd at gmail dot com>
|
||
|
*/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* get user id on Oauth2 server
|
||
|
*
|
||
|
* @param string $username
|
||
|
* Username of an LDAP user (often uid)
|
||
|
*
|
||
|
* @return
|
||
|
* The id associated to username in users table
|
||
|
* and FALSE if username is not in the users table
|
||
|
*/
|
||
|
public function getUsersID($username)
|
||
|
{
|
||
|
$stmt = $this->db->prepare($sql = sprintf('SELECT id FROM %s WHERE username=:username', $this->config['assoc_users_table']));
|
||
|
$stmt->execute(compact('username'));
|
||
|
|
||
|
// If user exist return his id
|
||
|
if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||
|
return $result['id'];
|
||
|
}
|
||
|
|
||
|
// If user does not exist, return false
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* set an id for username on Oauth2 server
|
||
|
*
|
||
|
* @param string $username
|
||
|
* Username of an LDAP user (often uid)
|
||
|
*
|
||
|
* @return
|
||
|
* TRUE if insertion has succeed
|
||
|
* and FALSE if is not
|
||
|
*
|
||
|
* An unique ID is linked to the username after this function
|
||
|
*/
|
||
|
public function setUsersID($username)
|
||
|
{
|
||
|
$stmt = $this->db->prepare(sprintf('INSERT INTO %s (username) VALUES (:username)', $this->config['assoc_users_table']));
|
||
|
return $stmt->execute(compact('username'));
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||
|
|
||
|
/**
|
||
|
* DDL to create OAuth2 database and tables for PDO storage
|
||
|
*
|
||
|
* @see https://github.com/dsquier/oauth2-server-php-mysql
|
||
|
*/
|
||
|
public function getBuildSql($dbName = 'oauth2_server_php')
|
||
|
{
|
||
|
$sql = "
|
||
|
CREATE TABLE {$this->config['client_table']} (
|
||
|
client_id VARCHAR(80) NOT NULL,
|
||
|
client_secret VARCHAR(80),
|
||
|
redirect_uri VARCHAR(2000),
|
||
|
grant_types VARCHAR(80),
|
||
|
scope VARCHAR(4000),
|
||
|
user_id VARCHAR(80),
|
||
|
PRIMARY KEY (client_id)
|
||
|
);
|
||
|
|
||
|
CREATE TABLE {$this->config['access_token_table']} (
|
||
|
access_token VARCHAR(40) NOT NULL,
|
||
|
client_id VARCHAR(80) NOT NULL,
|
||
|
user_id VARCHAR(80),
|
||
|
expires TIMESTAMP NOT NULL,
|
||
|
scope VARCHAR(4000),
|
||
|
PRIMARY KEY (access_token)
|
||
|
);
|
||
|
|
||
|
CREATE TABLE {$this->config['code_table']} (
|
||
|
authorization_code VARCHAR(40) NOT NULL,
|
||
|
client_id VARCHAR(80) NOT NULL,
|
||
|
user_id VARCHAR(80),
|
||
|
redirect_uri VARCHAR(2000),
|
||
|
expires TIMESTAMP NOT NULL,
|
||
|
scope VARCHAR(4000),
|
||
|
PRIMARY KEY (authorization_code)
|
||
|
);
|
||
|
|
||
|
CREATE TABLE {$this->config['refresh_token_table']} (
|
||
|
refresh_token VARCHAR(40) NOT NULL,
|
||
|
client_id VARCHAR(80) NOT NULL,
|
||
|
user_id VARCHAR(80),
|
||
|
expires TIMESTAMP NOT NULL,
|
||
|
scope VARCHAR(4000),
|
||
|
PRIMARY KEY (refresh_token)
|
||
|
);
|
||
|
|
||
|
CREATE TABLE {$this->config['user_table']} (
|
||
|
id SERIAL NOT NULL,
|
||
|
username VARCHAR(80) NOT NULL,
|
||
|
PRIMARY KEY (id)
|
||
|
);
|
||
|
|
||
|
CREATE TABLE {$this->config['scope_table']} (
|
||
|
scope VARCHAR(80) NOT NULL,
|
||
|
is_default BOOLEAN,
|
||
|
PRIMARY KEY (scope)
|
||
|
);
|
||
|
";
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
}
|