-
-
Notifications
You must be signed in to change notification settings - Fork 825
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
Add ability to temporarily override logged in user #15151
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,13 @@ class CRM_Core_Session { | |
*/ | ||
static private $_singleton = NULL; | ||
|
||
/** | ||
* Stack of contact ids when overriding current user | ||
* | ||
* @var array | ||
*/ | ||
static private $userOverride = []; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
|
@@ -250,6 +257,11 @@ public function get($name, $prefix = NULL) { | |
// create session scope | ||
$this->createScope($prefix, TRUE); | ||
|
||
if (!$prefix && $name == 'userID' && self::$userOverride) { | ||
end(self::$userOverride); | ||
return current(self::$userOverride); | ||
} | ||
|
||
if (empty($this->_session) || empty($this->_session[$this->_key])) { | ||
return NULL; | ||
} | ||
|
@@ -573,4 +585,60 @@ public function isEmpty() { | |
return empty($_SESSION); | ||
} | ||
|
||
/** | ||
* Temporarily masquerade as a different user. | ||
* | ||
* Note: the override is stored in a static variable so does not persist past this page request. | ||
* | ||
* Returns a handle to identify this override so you can revert it later. | ||
* | ||
* @param int $contactId | ||
* Contact id to spoof as current user. | ||
* @param string|int $handle | ||
* Specify your own handle if you wish. | ||
* @return bool|string|int | ||
* Handle if anything was done, FALSE otherwise | ||
*/ | ||
public function overrideCurrentUser($contactId, $handle = NULL) { | ||
if ($contactId == $this->get('userID')) { | ||
// Current user is already $contactId; nothing to do | ||
return FALSE; | ||
} | ||
$handle = $handle ?? uniqid(); | ||
self::$userOverride[$handle] = $contactId; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH, part of me thinks that the whole of However, I'm very much on the fence about that - just want to verbalize to see what others think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only one I've looked at is the contact ACL cache, which is on a per-user basis so it works fine with these overrides. But you're right there may be other spots in the system where things are cached assuming the logged in user id won't change. My original use case for this is pretty limited though. I wanted an anonymous user, authenticated with a checksum, to be able to submit an afform as that contact. So the afform.submit call internally authenticates the checksum and then sets "acting_user" param on subsequent api calls. So that's not nearly as demanding a scenario as actually "masquerading" as a different user and clicking around the site. |
||
return $handle; | ||
} | ||
|
||
/** | ||
* Reverts $this->overrideCurrentUser | ||
* | ||
* @param string|int $handle | ||
* Optionally specify the override to revert, else it will just | ||
* pop the latest override off the stack. | ||
* | ||
* @return bool | ||
* Whether or not anything was done. | ||
*/ | ||
public function restoreCurrentUser($handle = NULL) { | ||
if (self::$userOverride) { | ||
$handle = $handle ?? array_reverse(array_keys(self::$userOverride))[0]; | ||
if (array_key_exists($handle, self::$userOverride)) { | ||
unset(self::$userOverride[$handle]); | ||
return TRUE; | ||
} | ||
} | ||
return FALSE; | ||
} | ||
|
||
/** | ||
* If the contact id of the logged-in user has been overridden, return it. | ||
* | ||
* Otherwise return null to indicate the current user is not being overridden. | ||
* | ||
* @return int|null | ||
*/ | ||
public static function getOverriddenUser() { | ||
return self::$userOverride ? self::getLoggedInContactID() : NULL; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
namespace E2E\Core; | ||
|
||
/** | ||
* Class UserOverrideTest | ||
* | ||
* Check that overriding session user behaves as expected. | ||
* | ||
* @package E2E\Core | ||
* @group e2e | ||
*/ | ||
class UserOverrideTest extends \CiviEndToEndTestCase { | ||
|
||
protected function setUp() { | ||
parent::setUp(); | ||
} | ||
|
||
protected function tearDown() { | ||
while (\CRM_Core_Session::singleton()->restoreCurrentUser()) { | ||
// Loop until all overrides are cleared | ||
} | ||
} | ||
|
||
public function testOverride() { | ||
$session = \CRM_Core_Session::singleton(); | ||
$originalUser = $session::getLoggedInContactID(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little surprised PHP 7 doesn't complain about dispatching a static-function on an object. Learn something new... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not really different from doing |
||
// Set user CID to 2 | ||
$o1 = $session->overrideCurrentUser(2); | ||
$this->assertEquals(2, $session::getLoggedInContactID()); | ||
// Set user CID to 3 | ||
$o2 = $session->overrideCurrentUser(3); | ||
$this->assertEquals(3, $session::getLoggedInContactID()); | ||
// Set user CID to 4 | ||
$o3 = $session->overrideCurrentUser(4); | ||
// Clear the second override | ||
$this->assertTrue($session->restoreCurrentUser($o2)); | ||
// Latest override should still stand | ||
$this->assertEquals(4, $session::getLoggedInContactID()); | ||
// Clear the last override, should revert to the one remaining override | ||
$this->assertTrue($session->restoreCurrentUser()); | ||
$this->assertEquals(2, $session::getLoggedInContactID()); | ||
$this->assertEquals(2, $session->getOverriddenUser()); | ||
// Clear the final override | ||
$this->assertTrue($session->restoreCurrentUser()); | ||
$this->assertEquals($originalUser, $session::getLoggedInContactID()); | ||
// Assert there are no overrides left to clear | ||
$this->assertNull($session->getOverriddenUser()); | ||
$this->assertFalse($session->restoreCurrentUser()); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this change basically permits 0 to be passed in - why is it moved below the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton the problem was that this function wasn't differentiating
NULL
from0
and they are very different.0
= no usernull
= current userI fixed that and then moved the block down because
CRM_Core_Permission::check
already handlesNULL
and0
correctly so there was no need to mess with the$contactID
value before calling that function.