579 lines
16 KiB
Plaintext
579 lines
16 KiB
Plaintext
<?php
|
|
|
|
define('OAUTH_COMMON_CODE_BRANCH', '6.x-3.x');
|
|
|
|
define('OAUTH_COMMON_TOKEN_TYPE_REQUEST', 0);
|
|
define('OAUTH_COMMON_TOKEN_TYPE_ACCESS', 1);
|
|
|
|
define('OAUTH_COMMON_VERSION_1', 1); // The original 1.0 spec
|
|
define('OAUTH_COMMON_VERSION_1_RFC', 2); // The RFC 5849 1.0 spec
|
|
|
|
//TODO: Don't act as a provider by default.
|
|
//TODO: Check for other functions with breaking changes
|
|
//TODO: Move admin pages to more regular places
|
|
//TODO: Add watchdog messages about deprecated methods?
|
|
//TODO: Move provider ui related pages to provider ui
|
|
|
|
/**
|
|
* Implements hook_permission().
|
|
*/
|
|
function oauth_common_permission() {
|
|
$permissions = array(
|
|
'oauth authorize any consumers' => array(
|
|
'title' => t('Authorize any OAuth consumers'),
|
|
'restrict access' => TRUE,
|
|
),
|
|
'oauth register any consumers' => array(
|
|
'title' => t('Register any OAuth consumers'),
|
|
'restrict access' => TRUE,
|
|
),
|
|
'administer oauth' => array(
|
|
'title' => t('Administer OAuth'),
|
|
'restrict access' => TRUE,
|
|
),
|
|
'administer consumers' => array(
|
|
'title' => t('Administer OAuth consumers'),
|
|
'restrict access' => TRUE,
|
|
),
|
|
);
|
|
|
|
// Add seperate permissions for creating and
|
|
// authorizing consumers in each context.
|
|
foreach (oauth_common_context_list() as $name => $title) {
|
|
$permissions[sprintf('oauth register consumers in %s', $name)] = array(
|
|
'title' => t('Register OAuth consumers in %context', array('%context' => $title)),
|
|
);
|
|
$permissions[sprintf('oauth authorize consumers in %s', $name)] = array(
|
|
'title' => t('Authorize OAuth consumers in %context', array('%context' => $title)),
|
|
);
|
|
}
|
|
|
|
return $permissions;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_menu().
|
|
*/
|
|
function oauth_common_menu() {
|
|
$menu = array();
|
|
|
|
$admin_base = array(
|
|
'access arguments' => array('administer oauth'),
|
|
'file' => 'oauth_common.admin.inc',
|
|
);
|
|
|
|
$menu['admin/config/services/oauth'] = array(
|
|
'title' => 'OAuth',
|
|
'description' => 'Settings for OAuth',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('_oauth_common_admin'),
|
|
'type' => MENU_NORMAL_ITEM,
|
|
) + $admin_base;
|
|
|
|
$menu['admin/config/services/oauth/settings'] = array(
|
|
'title' => 'Settings',
|
|
'description' => 'Settings for OAuth',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('_oauth_common_admin'),
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
|
'weight' => 0,
|
|
) + $admin_base;
|
|
|
|
// OAuth doesn't need different endpoints for the different context as all
|
|
// actions are done with a specific consumer, which in itself is associated
|
|
// with a context.
|
|
|
|
$provider_base = array(
|
|
'access callback' => 'oauth_commmon_is_provider',
|
|
'file' => 'oauth_common.pages.inc',
|
|
'type' => MENU_CALLBACK,
|
|
);
|
|
|
|
// The endpoint that consumers use to get a request token.
|
|
$menu['oauth/request_token'] = array(
|
|
'page callback' => 'oauth_common_callback_request_token',
|
|
) + $provider_base;
|
|
|
|
// The page a user gets sent to to authorize a request token.
|
|
$menu['oauth/authorize'] = array(
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('oauth_common_form_authorize'),
|
|
) + $provider_base;
|
|
|
|
// The endpoint that consumers use to get a access token.
|
|
$menu['oauth/access_token'] = array(
|
|
'page callback' => 'oauth_common_callback_access_token',
|
|
) + $provider_base;
|
|
|
|
// This page is used both in consumer and provider mode. For consumers it is
|
|
// the callback url and triggers hook_oauth_common_authorized(). For
|
|
// providers it is the page where users end up if no callback url exists.
|
|
$menu['oauth/authorized'] = array(
|
|
'title' => 'Authorization finished',
|
|
'page callback' => 'oauth_common_page_authorized',
|
|
'access arguments' => array('access content'),
|
|
'file' => 'oauth_common.pages.inc',
|
|
'type' => MENU_CALLBACK,
|
|
);
|
|
|
|
// TODO: Different structures makes sense depending on whether oauth_common is
|
|
// acting as a provider or as a consumer.
|
|
|
|
$menu['oauth/test/valid-consumer'] = array(
|
|
'file' => 'oauth_common.pages.inc',
|
|
'page callback' => '_oauth_common_validate_request_callback',
|
|
'page arguments' => array('consumer'),
|
|
'access callback' => 'oauth_commmon_is_provider',
|
|
'type' => MENU_CALLBACK,
|
|
);
|
|
|
|
$menu['oauth/test/valid-access-token'] = array(
|
|
'file' => 'oauth_common.pages.inc',
|
|
'page callback' => '_oauth_common_validate_request_callback',
|
|
'page arguments' => array('access token'),
|
|
'access callback' => 'oauth_commmon_is_provider',
|
|
'type' => MENU_CALLBACK,
|
|
);
|
|
|
|
return $menu;
|
|
}
|
|
|
|
/**
|
|
* Menu system wildcard loader for provider consumers.
|
|
*
|
|
* @param string $key
|
|
*/
|
|
function oauth_common_consumer_load($csid) {
|
|
$consumer = DrupalOAuthConsumer::loadById($csid, TRUE);
|
|
if (!$consumer) {
|
|
$consumer = FALSE;
|
|
}
|
|
return $consumer;
|
|
}
|
|
|
|
/**
|
|
* Menu system wildcard loader for provider tokens.
|
|
*
|
|
* @param string $key
|
|
*/
|
|
function oauth_common_provider_token_load($tid) {
|
|
$token = DrupalOAuthToken::loadByID($tid);
|
|
if (!$token) {
|
|
$token = FALSE;
|
|
}
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_cron().
|
|
*/
|
|
function oauth_common_cron() {
|
|
$token_condition = db_and()->condition('expires', 0, '<>')->condition('expires', REQUEST_TIME, '<=');
|
|
|
|
db_delete('oauth_common_provider_token')
|
|
->condition('tid', db_select('oauth_common_token', 't')->condition($token_condition)->fields('t', array('tid')), 'IN')
|
|
->execute();
|
|
|
|
db_delete('oauth_common_token')
|
|
->condition($token_condition)
|
|
->execute();
|
|
|
|
// Should 300 be overriden in DrupalOAuthServer and made configurable?
|
|
db_delete('oauth_common_nonce')
|
|
->condition('timestamp', REQUEST_TIME - 300, '<')
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Implements hook_oauth_default_contexts().
|
|
*/
|
|
function oauth_common_default_oauth_common_context() {
|
|
$contexts = array();
|
|
|
|
$context = new stdClass;
|
|
$context->disabled = FALSE; /* Edit this to true to make a default context disabled initially */
|
|
$context->name = 'default';
|
|
$context->title = 'Default context';
|
|
$context->authorization_options = array();
|
|
$context->authorization_levels = array(
|
|
'*' => array(
|
|
'title' => 'Full access',
|
|
'description' => 'This will give @appname the same permissions that you normally have and will allow it to access the full range of services that @sitename provides.',
|
|
),
|
|
'read' => array(
|
|
'title' => 'Read access',
|
|
'description' => 'This will allow @appname to fetch content that you have access to on @sitename.',
|
|
),
|
|
'update' => array(
|
|
'title' => 'Update access',
|
|
'description' => 'This will allow @appname to update content that you have permissions to edit.',
|
|
),
|
|
'create' => array(
|
|
'title' => 'Create access',
|
|
'description' => 'This will allow @appname to create new content on @sitename.',
|
|
),
|
|
'delete' => array(
|
|
'title' => 'Delete access',
|
|
'description' => 'This will allow @appname to delete content from @sitename.',
|
|
),
|
|
);
|
|
$contexts[$context->name] = $context;
|
|
|
|
return $contexts;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_user_delete().
|
|
*/
|
|
function oauth_common_user_delete($account) {
|
|
// Delete all tokens and consumers related to a user
|
|
module_load_include('inc', 'oauth_common');
|
|
|
|
$consumer_condition = db_select('oauth_common_provider_consumer', 'c')->condition('uid', $account->uid)->fields('c', array('csid'));
|
|
$token_condition = db_or()->condition('uid', $account->uid)->condition('csid', $consumer_condition, 'IN');
|
|
|
|
db_delete('oauth_common_provider_token')
|
|
->condition('tid', db_select('oauth_common_token', 't')->condition($token_condition)->fields('t', array('tid')), 'IN')
|
|
->execute();
|
|
|
|
db_delete('oauth_common_token')
|
|
->condition($token_condition)
|
|
->execute();
|
|
|
|
db_delete('oauth_common_consumer')
|
|
->condition('csid', $consumer_condition, 'IN')
|
|
->execute();
|
|
|
|
db_delete('oauth_common_provider_consumer')
|
|
->condition('uid', $account->uid)
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Implements hook_xrds().
|
|
*/
|
|
function services_oauth_xrds() {
|
|
$xrds = array();
|
|
$xrds['oauth'] = array(
|
|
'services' => array(
|
|
array(
|
|
'data' => array(
|
|
'Type' => array('http://oauth.net/discovery/1.0'),
|
|
'URI' => array('#main'),
|
|
),
|
|
),
|
|
array(
|
|
'data' => array(
|
|
'Type' => array(
|
|
'http://oauth.net/core/1.0/endpoint/request',
|
|
'http://oauth.net/core/1.0/parameters/auth-header',
|
|
'http://oauth.net/core/1.0/parameters/uri-query',
|
|
'http://oauth.net/core/1.0/signature/HMAC-SHA1',
|
|
),
|
|
'URI' => array(url('oauth/request_token', array('absolute' => TRUE))),
|
|
),
|
|
),
|
|
array(
|
|
'data' => array(
|
|
'Type' => array(
|
|
'http://oauth.net/core/1.0/endpoint/authorize',
|
|
'http://oauth.net/core/1.0/parameters/uri-query',
|
|
),
|
|
'URI' => array(url('oauth/authorize', array('absolute' => TRUE))),
|
|
),
|
|
),
|
|
array(
|
|
'data' => array(
|
|
'Type' => array(
|
|
'http://oauth.net/core/1.0/endpoint/access',
|
|
'http://oauth.net/core/1.0/parameters/auth-header',
|
|
'http://oauth.net/core/1.0/parameters/uri-query',
|
|
'http://oauth.net/core/1.0/signature/HMAC-SHA1',
|
|
),
|
|
'URI' => array(url('oauth/access_token', array('absolute' => TRUE))),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return $xrds;
|
|
}
|
|
|
|
/**
|
|
* Access callback function used by several menu items.
|
|
*
|
|
* @param stdClass $user
|
|
* A user object.
|
|
* @param string $permission
|
|
* The permission that is needed in addition to edit access on the $user.
|
|
*/
|
|
function _oauth_common_user_access($user, $permission = NULL) {
|
|
return user_edit_access($user) && (empty($permission) || user_access($permission));
|
|
}
|
|
|
|
/**
|
|
* Checks if the user has permission to edit the consumer. Edit access is
|
|
* granted if the user has the 'administer consumers' permission or may
|
|
* edit the account connected to the consumer.
|
|
*
|
|
* @param DrupalOAuthConsumer $consumer
|
|
* @return bool
|
|
*/
|
|
function oauth_common_can_edit_consumer($consumer) {
|
|
$may_edit = user_access('administer consumers');
|
|
|
|
// If the user doesn't have consumer admin privileges, check for account
|
|
// edit access.
|
|
if (!$may_edit && $consumer->uid) {
|
|
$user = user_load($consumer->uid);
|
|
$may_edit = user_edit_access($user);
|
|
}
|
|
|
|
return $may_edit;
|
|
}
|
|
|
|
/**
|
|
* Deterines if a user has the necessary permissions to create consumers.
|
|
*
|
|
* @param object $account
|
|
* The user account to check permissions for. Defaults to the currently
|
|
* logged in user.
|
|
* @return bool
|
|
*/
|
|
function oauth_common_can_create_consumers($account = NULL) {
|
|
global $user;
|
|
if (!$account) {
|
|
$account = $user;
|
|
}
|
|
|
|
$can_register_consumers = user_access('oauth register any consumers', $account);
|
|
if (!$can_register_consumers) {
|
|
foreach (oauth_common_context_list() as $context => $title) {
|
|
$can_register_consumers = $can_register_consumers || user_access(sprintf('oauth register consumers in %s', $context), $account);
|
|
}
|
|
}
|
|
return $can_register_consumers;
|
|
}
|
|
|
|
/**
|
|
* This function is used as a access callback
|
|
* when the authentication of the request shouldn't be
|
|
* done by the menu system.
|
|
*
|
|
* @return bool
|
|
* Always returns TRUE
|
|
*/
|
|
function _oauth_common_always_true() {
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Access callback that checks if a user may create authorizations in the
|
|
* consumers context.
|
|
*
|
|
* @param DrupalOAuthConsumer $consumer
|
|
* @return bool
|
|
*/
|
|
function oauth_common_can_authorize_consumer($consumer) {
|
|
return user_access(sprintf('oauth authorize consumers in %s', $consumer->context));
|
|
}
|
|
|
|
/**
|
|
* Check if oauth_common is acting as a provider.
|
|
*/
|
|
function oauth_commmon_is_provider() {
|
|
return variable_get('oauth_common_enable_provider', TRUE);
|
|
}
|
|
|
|
/**
|
|
* Gets a request token from a oauth provider and returns the authorization
|
|
* url. The request token is saved in the database.
|
|
*
|
|
* @param OAuthToken $consumer_token
|
|
* The consumer token to use
|
|
* @param string $request_endpoint
|
|
* Optional. Pass a custom endpoint if needed. Defaults to '/oauth/request_token'.
|
|
* @param string $authorize_endpoint
|
|
* Optional. Pass a custom endpoint if needed. Defaults to '/oauth/authorize'.
|
|
* @return string
|
|
* The url that the client should be redirected to to authorize
|
|
* the request token.
|
|
*/
|
|
function oauth_common_get_request_token($consumer_token, $request_endpoint = '/oauth/request_token', $authorize_endpoint = '/oauth/authorize') {
|
|
$client = new DrupalOAuthClient($consumer_token);
|
|
$request_token = $client->getRequestToken($request_endpoint);
|
|
$request_token->write();
|
|
return $client->getAuthorizationUrl($authorize_endpoint);
|
|
}
|
|
|
|
/**
|
|
* Gets the tokens for a user.
|
|
*
|
|
* @param string $uid
|
|
* @param string $type
|
|
* @return array
|
|
*/
|
|
function oauth_common_get_user_provider_tokens($uid) {
|
|
$res = db_query("SELECT t.*, pt.created, pt.changed, pt.services, pt.authorized FROM {oauth_common_token} t
|
|
INNER JOIN {oauth_common_provider_token} pt WHERE t.uid = :uid AND t.type = :type", array(
|
|
':uid' => $uid,
|
|
':type' => OAUTH_COMMON_TOKEN_TYPE_ACCESS,
|
|
));
|
|
$tokens = array();
|
|
while ($token = DrupalOAuthToken::fromResult($res)) {
|
|
$tokens[] = $token;
|
|
}
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* Create a new context with defaults appropriately set from schema.
|
|
*
|
|
* @return stdClass
|
|
* An context initialized with the default values.
|
|
*/
|
|
function oauth_common_context_new() {
|
|
if (!module_exists('ctools')) {
|
|
return FALSE;
|
|
}
|
|
ctools_include('export');
|
|
return ctools_export_new_object('oauth_common_context');
|
|
}
|
|
|
|
/**
|
|
* Load a single context.
|
|
*
|
|
* @param string $name
|
|
* The name of the context.
|
|
* @return stdClass
|
|
* The context configuration.
|
|
*/
|
|
function oauth_common_context_load($name) {
|
|
if (!module_exists('ctools')) {
|
|
return FALSE;
|
|
}
|
|
ctools_include('export');
|
|
$result = ctools_export_load_object('oauth_common_context', 'names', array($name));
|
|
if (isset($result[$name])) {
|
|
return $result[$name];
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the context for a request.
|
|
*
|
|
* @param OAuthRequest $request
|
|
* @return object
|
|
* The context configuration.
|
|
*/
|
|
function oauth_common_context_from_request($request) {
|
|
$context = NULL;
|
|
$consumer_key = $request->get_parameter('oauth_consumer_key');
|
|
$token_key = $request->get_parameter('oauth_token');
|
|
|
|
if (empty($consumer_key) && !empty($token_key)) {
|
|
$token = DrupalOAuthToken::loadByKey($token_key, FALSE, OAUTH_COMMON_TOKEN_TYPE_REQUEST);
|
|
if ($token) {
|
|
$consumer = $token->consumer;
|
|
}
|
|
}
|
|
|
|
if (!empty($consumer_key)) {
|
|
$consumer = DrupalOAuthConsumer::loadProviderByKey($consumer_key);
|
|
}
|
|
|
|
if (!empty($consumer)) {
|
|
$context = oauth_common_context_load($consumer->context);
|
|
}
|
|
|
|
return $context;
|
|
}
|
|
|
|
/**
|
|
* Load all contexts.
|
|
*
|
|
* @return array
|
|
* Array of context objects keyed by context names.
|
|
*/
|
|
function oauth_common_context_load_all() {
|
|
if (!module_exists('ctools')) {
|
|
return FALSE;
|
|
}
|
|
ctools_include('export');
|
|
return ctools_export_load_object('oauth_common_context');
|
|
}
|
|
|
|
/**
|
|
* Saves an context in the database.
|
|
*
|
|
* @return void
|
|
*/
|
|
function oauth_common_context_save($context) {
|
|
$update = (isset($context->cid)) ? array('cid') : array();
|
|
drupal_write_record('oauth_common_context', $context, $update);
|
|
}
|
|
|
|
/**
|
|
* Remove an context.
|
|
*
|
|
* @return void
|
|
*/
|
|
function oauth_common_context_delete($context) {
|
|
db_delete('oauth_common_context')
|
|
->condition('name', $context->name)
|
|
->condition('cid', $context->cid)
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Export an context.
|
|
*
|
|
* @return string
|
|
*/
|
|
function oauth_common_context_export($context, $indent = '') {
|
|
if (!module_exists('ctools')) {
|
|
return FALSE;
|
|
}
|
|
ctools_include('export');
|
|
$output = ctools_export_object('oauth_common_context', $context, $indent);
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Lists all available contexts.
|
|
*
|
|
* @return array
|
|
*/
|
|
function oauth_common_context_list() {
|
|
$return = array();
|
|
$contexts = oauth_common_context_load_all();
|
|
if ($contexts) {
|
|
foreach ($contexts as $context) {
|
|
$return[$context->name] = $context->title;
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Finds the current version of the OAuth module, used in eg. user agents
|
|
*
|
|
* @return string
|
|
*/
|
|
function _oauth_common_version() {
|
|
static $version;
|
|
if (!isset($version)) {
|
|
$info = db_query("SELECT info FROM {system} WHERE name = 'oauth_common'")->fetchField();
|
|
$info = $info ? unserialize($info) : FALSE;
|
|
if (!$info || empty($info['version'])) {
|
|
$version = OAUTH_COMMON_CODE_BRANCH;
|
|
}
|
|
else {
|
|
$version = $info['version'];
|
|
}
|
|
}
|
|
return $version;
|
|
}
|