Skip to content

Commit 29ed7bc

Browse files
committed
Switch global user by user authenticated by RESTful
1 parent 7ea72a6 commit 29ed7bc

File tree

10 files changed

+182
-21
lines changed

10 files changed

+182
-21
lines changed

.travis.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ before_script:
3434
- drush dl entity_validator --dev --yes
3535

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

4042
# start a web server on port 8080, run in the background; wait for initialization
4143
- drush runserver 127.0.0.1:8080 &

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ control.
2929
## Module dependencies
3030

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

3434
## Recipes
3535
Read even more examples on how to use the RESTful module in the [module documentation

modules/restful_token_auth/restful_token_auth.module

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,16 @@ function restful_token_auth_restful_parse_request_alter(&$request) {
4949
$plugin = restful_get_authentication_plugin('token');
5050
$param_name = $plugin['options']['param_name'];
5151

52+
$value = \RestfulManager::getRequestHttpHeader($param_name);
53+
if (!$value && !empty($_GET[$param_name])) {
54+
// If no access token found on the HTTP header, check if it's in the URL
55+
// itself. This allows to do a POST request to for example:
56+
// https://example.com/api/file-upload?access_token=foo
57+
$value = $_GET[$param_name];
58+
}
59+
5260
$request['__application'] += array(
53-
$param_name => \RestfulManager::getRequestHttpHeader($param_name),
61+
$param_name => $value,
5462
);
5563
}
5664

plugins/authentication/RestfulAuthenticationBasic.class.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function authenticate(array $request = array(), $method = \RestfulInterfa
6060
if ($uid = user_authenticate($username, $password)) {
6161
// Clear the user based flood control.
6262
flood_clear_event('failed_login_attempt_user', $identifier);
63+
6364
return user_load($uid);
6465
}
6566
flood_register_event('failed_login_attempt_user', variable_get('user_failed_login_user_window', 3600), $identifier);

plugins/authentication/RestfulAuthenticationManager.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ class RestfulAuthenticationManager extends \ArrayObject {
1414
*/
1515
protected $account;
1616

17+
/**
18+
* The original user object and session.
19+
*
20+
* @var array
21+
*/
22+
protected $originalUserSession;
23+
1724

1825
/**
1926
* Determines if authentication is optional.
@@ -71,10 +78,12 @@ public function addAuthenticationProvider(RestfulAuthenticationInterface $provid
7178
*/
7279
public function getAccount(array $request = array(), $method = \RestfulInterface::GET, $cache = TRUE) {
7380
global $user;
81+
7482
// Return the previously resolved user, if any.
7583
if (!empty($this->account)) {
7684
return $this->account;
7785
}
86+
7887
// Resolve the user based on the providers in the manager.
7988
$account = NULL;
8089
foreach ($this as $provider) {
@@ -105,6 +114,7 @@ public function getAccount(array $request = array(), $method = \RestfulInterface
105114
if ($cache) {
106115
$this->setAccount($account);
107116
}
117+
108118
return $account;
109119
}
110120

@@ -116,6 +126,76 @@ public function getAccount(array $request = array(), $method = \RestfulInterface
116126
*/
117127
public function setAccount(\stdClass $account) {
118128
$this->account = $account;
129+
$this->switchUser();
130+
}
131+
132+
/**
133+
* Switch the user to the user authenticated by RESTful.
134+
*
135+
* @link https://www.drupal.org/node/218104
136+
*/
137+
public function switchUser() {
138+
global $user;
139+
140+
if (!restful_is_user_switched() && !$this->getOriginalUserSession()) {
141+
// This is the first time a user switched, and there isn't an original
142+
// user session.
143+
144+
$session = drupal_save_session();
145+
$this->setOriginalUserSession(array(
146+
'user' => $user,
147+
'session' => $session,
148+
));
149+
150+
// Don't allow a session to be saved. Provider that require a session to
151+
// be saved, like the cookie provider, need to explicitly set
152+
// drupal_save_session(TRUE).
153+
// @see \RestfulUserLoginCookie::loginUser().
154+
drupal_save_session(FALSE);
155+
}
156+
157+
$account = $this->getAccount();
158+
// Set the global user.
159+
$user = $account;
160+
119161
}
120162

163+
/**
164+
* Switch the user to the authenticated user, and back.
165+
*
166+
* This should be called only for an API call. It should not be used for calls
167+
* via the menu system, as it might be a login request, so we avoid switching
168+
* back to the anonymous user.
169+
*/
170+
public function switchUserBack() {
171+
global $user;
172+
if (!$user_state = $this->getOriginalUserSession()) {
173+
return;
174+
}
175+
176+
$user = $user_state['user'];
177+
drupal_save_session($user_state['session']);
178+
}
179+
180+
/**
181+
* Set the original user object and session.
182+
*
183+
* @param array $original_user_session
184+
* Array keyed by 'user' and 'session'.
185+
*/
186+
protected function setOriginalUserSession(array $original_user_session) {
187+
$this->originalUserSession = $original_user_session;
188+
}
189+
190+
/**
191+
* Get the original user object and session.
192+
*
193+
* @return array
194+
* Array keyed by 'user' and 'session'.
195+
*/
196+
protected function getOriginalUserSession() {
197+
return $this->originalUserSession;
198+
}
199+
200+
121201
}

plugins/restful/RestfulBase.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,16 @@ public function process($path = '', array $request = array(), $method = \Restful
815815
$this->getRateLimitManager()->checkRateLimit($request);
816816
}
817817

818-
return $this->{$method_name}($path);
818+
$return = $this->{$method_name}($path);
819+
820+
if (empty($request['__application']['rest_call'])) {
821+
// Switch back to the original user.
822+
$this->getAuthenticationManager()->switchUserBack();
823+
}
824+
825+
826+
return $return;
827+
819828
}
820829

821830
/**

plugins/restful/user/login_cookie/1.0/RestfulUserLoginCookie.class.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static function controllersInfo() {
2727
public function loginAndRespondWithCookie() {
2828
// Login the user.
2929
$account = $this->getAccount();
30-
$this->loginUser($account);
30+
$this->loginUser();
3131

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

4040
/**
4141
* Log the user.
42-
*
43-
* @param $account
44-
* The user object that was retrieved by the \RestfulAuthenticationManager.
4542
*/
46-
public function loginUser($account) {
43+
protected function loginUser() {
4744
global $user;
45+
46+
$account = $this->getAccount();
47+
48+
// Explicitly allow a session to be saved, as it was disabled in
49+
// \RestfulAuthenticationManager::getAccount. However this resource is a
50+
// special one, in the sense that we want to keep the user authenticated
51+
// after login.
52+
drupal_save_session(TRUE);
53+
4854
// Override the global user.
4955
$user = user_load($account->uid);
5056

plugins/restful/user/login_cookie/1.0/login_cookie__1_0.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
if (variable_get('restful_enable_user_login_resource', TRUE)) {
44
$plugin = array(
55
'label' => t('Login'),
6-
'description' => t('Login a user and return a JSON along with the authentication cookie..'),
6+
'description' => t('Login a user and return a JSON along with the authentication cookie.'),
77
'resource' => 'login_cookie',
88
'class' => 'RestfulUserLoginCookie',
99
'entity_type' => 'user',

restful.module

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,20 @@ function restful_menu_access_callback($resource_name, $version = NULL) {
426426
return;
427427
}
428428

429+
// Set the request and method on the handler, so access callbacks have full
430+
// access to the request and account.
431+
$path = func_get_args();
432+
array_shift($path);
433+
if (preg_match('/^v\d+(\.\d+)?$/', $version)) {
434+
array_shift($path);
435+
}
436+
$path = implode('/', $path);
437+
438+
$request = restful_parse_request();
439+
$handler->setPath($path);
440+
$handler->setMethod($method);
441+
$handler->setRequest($request);
442+
429443
return $handler->access();
430444
}
431445

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

475-
$path = implode('/', $path);
476-
477484
$method = strtoupper($_SERVER['REQUEST_METHOD']);
478485

479486
if ($method == \RestfulInterface::POST && \RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override')) {
480-
$method = strtoupper(\RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override'));
487+
$method = \RestfulManager::getRequestHttpHeader('X-HTTP-Method-Override');
481488
}
482489

483490
$method = strtolower($method);
491+
492+
$path = func_get_args();
493+
array_shift($path);
494+
if (preg_match('/^v\d+(\.\d+)?$/', $version)) {
495+
array_shift($path);
496+
}
497+
$path = implode('/', $path);
498+
484499
$request = restful_parse_request();
485500

486501
try {
@@ -527,7 +542,11 @@ function restful_menu_process_callback($resource_name, $version = NULL) {
527542
* The request array.
528543
*/
529544
function restful_parse_request() {
530-
$request = NULL;
545+
$request = &drupal_static(__FUNCTION__, NULL);
546+
if ($request) {
547+
return $request;
548+
}
549+
531550
$method = strtoupper($_SERVER['REQUEST_METHOD']);
532551

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

559-
// Allow implemeting modules to alter the request.
578+
// Allow implementing modules to alter the request.
560579
drupal_alter('restful_parse_request', $request);
561580

562581
return $request;
@@ -749,3 +768,21 @@ function restful_date_time_format_element_validate($element, &$form_state) {
749768
)));
750769
}
751770
}
771+
772+
/**
773+
* Determine if this is the first time we try to switch the user.
774+
*
775+
* @return bool
776+
* TRUE if this static function was already called.
777+
*/
778+
function restful_is_user_switched() {
779+
$restful_switch_user = &drupal_static(__FUNCTION__, FALSE);
780+
781+
if (!$restful_switch_user) {
782+
$restful_switch_user = TRUE;
783+
return FALSE;
784+
}
785+
786+
return TRUE;
787+
}
788+

tests/RestfulAuthenticationTestCase.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,22 @@ class RestfulAuthenticationTestCase extends RestfulCurlBaseTestCase {
120120
$result = $this->httpRequest('api/v1.0/users');
121121
$this->assertEqual($result['code'], 200, 'Anonymous user got 200 when trying to access the "users" resource after permissions changed.');
122122
}
123+
124+
/**
125+
* Test switching the user in an API call.
126+
*/
127+
function testSwitchUser() {
128+
global $user;
129+
// We need to hijack the global user object in order to force it to be an
130+
// anonymous user.
131+
$user = drupal_anonymous_user();
132+
133+
$user1 = $this->drupalCreateUser();
134+
135+
$handler = restful_get_restful_handler('articles');
136+
$handler->setAccount($user1);
137+
$handler->get();
138+
139+
$this->assertEqual($user->uid, 0, 'Global user is the correct one after an API call that switches the user.');
140+
}
123141
}

0 commit comments

Comments
 (0)