Skip to content

Commit

Permalink
Switch global user by user authenticated by RESTful
Browse files Browse the repository at this point in the history
  • Loading branch information
amitaibu committed Mar 12, 2015
1 parent 7ea72a6 commit 29ed7bc
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 21 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ before_script:
- drush dl entity_validator --dev --yes

# Patch Entity API.
- curl -O https://www.drupal.org/files/issues/2086225-entity-access-check-node-create-3.patch
- patch -p1 /home/travis/build/restful/drupal/sites/all/modules/entity/modules/callbacks.inc < 2086225-entity-access-check-node-create-3.patch
- cd sites/all/modules/entity
- curl -O https://www.drupal.org/files/issues/2086225-entity-access-check-18.patch
- patch -p1 < 2086225-entity-access-check-18.patch
- cd -

# start a web server on port 8080, run in the background; wait for initialization
- drush runserver 127.0.0.1:8080 &
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ control.
## Module dependencies

* [Entity API](https://drupal.org/project/entity), with the following patch:
* [Prevent notice in entity_metadata_no_hook_node_access() when node is not saved](https://drupal.org/node/2086225#comment-8768373)
* [Prevent notice in entity_metadata_no_hook_node_access() when node is not saved](https://www.drupal.org/node/2086225#comment-9627407)

## Recipes
Read even more examples on how to use the RESTful module in the [module documentation
Expand Down
10 changes: 9 additions & 1 deletion modules/restful_token_auth/restful_token_auth.module
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,16 @@ function restful_token_auth_restful_parse_request_alter(&$request) {
$plugin = restful_get_authentication_plugin('token');
$param_name = $plugin['options']['param_name'];

$value = \RestfulManager::getRequestHttpHeader($param_name);
if (!$value && !empty($_GET[$param_name])) {
// If no access token found on the HTTP header, check if it's in the URL
// itself. This allows to do a POST request to for example:
// https://example.com/api/file-upload?access_token=foo
$value = $_GET[$param_name];
}

$request['__application'] += array(
$param_name => \RestfulManager::getRequestHttpHeader($param_name),
$param_name => $value,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function authenticate(array $request = array(), $method = \RestfulInterfa
if ($uid = user_authenticate($username, $password)) {
// Clear the user based flood control.
flood_clear_event('failed_login_attempt_user', $identifier);

return user_load($uid);
}
flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 3600), $identifier);
Expand Down
80 changes: 80 additions & 0 deletions plugins/authentication/RestfulAuthenticationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class RestfulAuthenticationManager extends \ArrayObject {
*/
protected $account;

/**
* The original user object and session.
*
* @var array
*/
protected $originalUserSession;


/**
* Determines if authentication is optional.
Expand Down Expand Up @@ -71,10 +78,12 @@ public function addAuthenticationProvider(RestfulAuthenticationInterface $provid
*/
public function getAccount(array $request = array(), $method = \RestfulInterface::GET, $cache = TRUE) {
global $user;

// Return the previously resolved user, if any.
if (!empty($this->account)) {
return $this->account;
}

// Resolve the user based on the providers in the manager.
$account = NULL;
foreach ($this as $provider) {
Expand Down Expand Up @@ -105,6 +114,7 @@ public function getAccount(array $request = array(), $method = \RestfulInterface
if ($cache) {
$this->setAccount($account);
}

return $account;
}

Expand All @@ -116,6 +126,76 @@ public function getAccount(array $request = array(), $method = \RestfulInterface
*/
public function setAccount(\stdClass $account) {
$this->account = $account;
$this->switchUser();
}

/**
* Switch the user to the user authenticated by RESTful.
*
* @link https://www.drupal.org/node/218104
*/
public function switchUser() {
global $user;

if (!restful_is_user_switched() && !$this->getOriginalUserSession()) {
// This is the first time a user switched, and there isn't an original
// user session.

$session = drupal_save_session();
$this->setOriginalUserSession(array(
'user' => $user,
'session' => $session,
));

// Don't allow a session to be saved. Provider that require a session to
// be saved, like the cookie provider, need to explicitly set
// drupal_save_session(TRUE).
// @see \RestfulUserLoginCookie::loginUser().
drupal_save_session(FALSE);
}

$account = $this->getAccount();
// Set the global user.
$user = $account;

}

/**
* Switch the user to the authenticated user, and back.
*
* This should be called only for an API call. It should not be used for calls
* via the menu system, as it might be a login request, so we avoid switching
* back to the anonymous user.
*/
public function switchUserBack() {
global $user;
if (!$user_state = $this->getOriginalUserSession()) {
return;
}

$user = $user_state['user'];
drupal_save_session($user_state['session']);
}

/**
* Set the original user object and session.
*
* @param array $original_user_session
* Array keyed by 'user' and 'session'.
*/
protected function setOriginalUserSession(array $original_user_session) {
$this->originalUserSession = $original_user_session;
}

/**
* Get the original user object and session.
*
* @return array
* Array keyed by 'user' and 'session'.
*/
protected function getOriginalUserSession() {
return $this->originalUserSession;
}


}
11 changes: 10 additions & 1 deletion plugins/restful/RestfulBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,16 @@ public function process($path = '', array $request = array(), $method = \Restful
$this->getRateLimitManager()->checkRateLimit($request);
}

return $this->{$method_name}($path);
$return = $this->{$method_name}($path);

if (empty($request['__application']['rest_call'])) {
// Switch back to the original user.
$this->getAuthenticationManager()->switchUserBack();
}


return $return;

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static function controllersInfo() {
public function loginAndRespondWithCookie() {
// Login the user.
$account = $this->getAccount();
$this->loginUser($account);
$this->loginUser();

$version = $this->getVersion();
$handler = restful_get_restful_handler('users', $version['major'], $version['minor']);
Expand All @@ -39,12 +39,18 @@ public function loginAndRespondWithCookie() {

/**
* Log the user.
*
* @param $account
* The user object that was retrieved by the \RestfulAuthenticationManager.
*/
public function loginUser($account) {
protected function loginUser() {
global $user;

$account = $this->getAccount();

// Explicitly allow a session to be saved, as it was disabled in
// \RestfulAuthenticationManager::getAccount. However this resource is a
// special one, in the sense that we want to keep the user authenticated
// after login.
drupal_save_session(TRUE);

// Override the global user.
$user = user_load($account->uid);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
if (variable_get('restful_enable_user_login_resource', TRUE)) {
$plugin = array(
'label' => t('Login'),
'description' => t('Login a user and return a JSON along with the authentication cookie..'),
'description' => t('Login a user and return a JSON along with the authentication cookie.'),
'resource' => 'login_cookie',
'class' => 'RestfulUserLoginCookie',
'entity_type' => 'user',
Expand Down
57 changes: 47 additions & 10 deletions restful.module
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,20 @@ function restful_menu_access_callback($resource_name, $version = NULL) {
return;
}

// Set the request and method on the handler, so access callbacks have full
// access to the request and account.
$path = func_get_args();
array_shift($path);
if (preg_match('/^v\d+(\.\d+)?$/', $version)) {
array_shift($path);
}
$path = implode('/', $path);

$request = restful_parse_request();
$handler->setPath($path);
$handler->setMethod($method);
$handler->setRequest($request);

return $handler->access();
}

Expand All @@ -445,11 +459,6 @@ function restful_menu_access_callback($resource_name, $version = NULL) {
* @see http://tools.ietf.org/html/draft-nottingham-http-problem-06
*/
function restful_menu_process_callback($resource_name, $version = NULL) {
$path = func_get_args();
array_shift($path);
if (preg_match('/^v\d+(\.\d+)?$/', $version)) {
array_shift($path);
}
list($major_version, $minor_version) = \RestfulBase::getVersionFromRequest();
$handler = restful_get_restful_handler($resource_name, $major_version, $minor_version);
if (!$handler instanceof \RestfulDataProviderInterface) {
Expand All @@ -472,15 +481,21 @@ function restful_menu_process_callback($resource_name, $version = NULL) {
$handler->setHttpHeaders('Access-Control-Allow-Origin', $allowed_origin);
}

$path = implode('/', $path);

$method = strtoupper($_SERVER['REQUEST_METHOD']);

if ($method == \RestfulInterface::POST && \RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override')) {
$method = strtoupper(\RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override'));
$method = \RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override');
}

$method = strtolower($method);

$path = func_get_args();
array_shift($path);
if (preg_match('/^v\d+(\.\d+)?$/', $version)) {
array_shift($path);
}
$path = implode('/', $path);

$request = restful_parse_request();

try {
Expand Down Expand Up @@ -527,7 +542,11 @@ function restful_menu_process_callback($resource_name, $version = NULL) {
* The request array.
*/
function restful_parse_request() {
$request = NULL;
$request = &drupal_static(__FUNCTION__, NULL);
if ($request) {
return $request;
}

$method = strtoupper($_SERVER['REQUEST_METHOD']);

if ($method == \RestfulInterface::GET) {
Expand Down Expand Up @@ -556,7 +575,7 @@ function restful_parse_request() {
'csrf_token' => \RestfulManager::getRequestHttpHeader('X-CSRF-Token'),
);

// Allow implemeting modules to alter the request.
// Allow implementing modules to alter the request.
drupal_alter('restful_parse_request', $request);

return $request;
Expand Down Expand Up @@ -749,3 +768,21 @@ function restful_date_time_format_element_validate($element, &$form_state) {
)));
}
}

/**
* Determine if this is the first time we try to switch the user.
*
* @return bool
* TRUE if this static function was already called.
*/
function restful_is_user_switched() {
$restful_switch_user = &drupal_static(__FUNCTION__, FALSE);

if (!$restful_switch_user) {
$restful_switch_user = TRUE;
return FALSE;
}

return TRUE;
}

18 changes: 18 additions & 0 deletions tests/RestfulAuthenticationTestCase.test
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,22 @@ class RestfulAuthenticationTestCase extends RestfulCurlBaseTestCase {
$result = $this->httpRequest('api/v1.0/users');
$this->assertEqual($result['code'], 200, 'Anonymous user got 200 when trying to access the "users" resource after permissions changed.');
}

/**
* Test switching the user in an API call.
*/
function testSwitchUser() {
global $user;
// We need to hijack the global user object in order to force it to be an
// anonymous user.
$user = drupal_anonymous_user();

$user1 = $this->drupalCreateUser();

$handler = restful_get_restful_handler('articles');
$handler->setAccount($user1);
$handler->get();

$this->assertEqual($user->uid, 0, 'Global user is the correct one after an API call that switches the user.');
}
}

0 comments on commit 29ed7bc

Please sign in to comment.