Skip to content

Commit

Permalink
Add ability to temporarily override logged in user
Browse files Browse the repository at this point in the history
Allows api4 to specify a different acting user
  • Loading branch information
colemanw committed Aug 28, 2019
1 parent 087101e commit 7d3ce29
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 5 deletions.
10 changes: 5 additions & 5 deletions CRM/ACL/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,18 @@ public static function whereClause(
}
}

if (!$contactID) {
$contactID = CRM_Core_Session::getLoggedInContactID();
}
$contactID = (int) $contactID;

// first see if the contact has edit / view all permission
if (CRM_Core_Permission::check('edit all contacts', $contactID) ||
($type == self::VIEW && CRM_Core_Permission::check('view all contacts', $contactID))
) {
return $deleteClause;
}

if (is_null($contactID)) {
$contactID = CRM_Core_Session::getLoggedInContactID();
}
$contactID = (int) $contactID;

$whereClause = CRM_ACL_BAO_ACL::whereClause($type,
$tables,
$whereTables,
Expand Down
2 changes: 2 additions & 0 deletions CRM/Core/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public static function getPermission() {
*/
public static function check($permissions, $contactId = NULL) {
$permissions = (array) $permissions;
// If the logged in contact id is being overridden, use the substitute contactId
$contactId = $contactId ?? CRM_Core_Session::getOverriddenUser();
$userId = CRM_Core_BAO_UFMatch::getUFId($contactId);
// If contact has no associated user, set to 0 for anonymous (logged-out)
if ($contactId === 0 || ($contactId && !$userId)) {
Expand Down
68 changes: 68 additions & 0 deletions CRM/Core/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
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;
}

}
40 changes: 40 additions & 0 deletions tests/phpunit/E2E/Core/UserOverrideTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?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()) {
}
}

public function testOverride() {
$session = \CRM_Core_Session::singleton();
$originalUser = $session::getLoggedInContactID();
$o1 = $session->overrideCurrentUser(2);
$this->assertEquals(2, $session::getLoggedInContactID());
$o2 = $session->overrideCurrentUser(3);
$this->assertEquals(3, $session::getLoggedInContactID());
$o3 = $session->overrideCurrentUser(4);
$session->restoreCurrentUser($o2);
$this->assertEquals(4, $session::getLoggedInContactID());
$session->restoreCurrentUser();
$this->assertEquals(2, $session::getLoggedInContactID());
$session->restoreCurrentUser();
$this->assertEquals($originalUser, $session::getLoggedInContactID());
}

}

0 comments on commit 7d3ce29

Please sign in to comment.