mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Allow users to act-as another
This commit is contained in:
parent
5d4ab7f255
commit
7d67c5b9bd
@ -134,6 +134,39 @@ will be created and associated with the device used during authentication. When
|
||||
for all devices will be revoked, unless `[api:RememberLoginHash::$logout_across_devices] is set to false. For extra security,
|
||||
single tokens can be enforced by setting `[api:RememberLoginHash::$force_single_token] to true.
|
||||
|
||||
## Acting as another user ##
|
||||
|
||||
Occasionally, it may be necessary not only to check permissions of a particular member, but also to
|
||||
temporarily assume the identity of another user for certain tasks. E.g. when running a CLI task,
|
||||
it may be necessary to log in as an administrator to perform write operations.
|
||||
|
||||
You can use `Member::actAs()` method, which takes a member or member id to act as, and a callback
|
||||
within which the current user will be assigned the given member. After this method returns
|
||||
the current state will be restored to whichever current user (if any) was logged in.
|
||||
|
||||
If you pass in null as a first argument, you can also mock being logged out, without modifying
|
||||
the current user.
|
||||
|
||||
Note: Take care not to invoke this method to perform any operation the current user should not
|
||||
reasonably be expected to be allowed to do.
|
||||
|
||||
E.g.
|
||||
|
||||
|
||||
:::php
|
||||
class CleanRecordsTask extends BuildTask
|
||||
{
|
||||
public function run($request)
|
||||
{
|
||||
if (!Director::is_cli()) {
|
||||
throw new BadMethodCallException('This task only runs on CLI');
|
||||
}
|
||||
$admin = Security::findAnAdministrator();
|
||||
Member::actAs($admin, function() {
|
||||
DataRecord::get()->filter('Dirty', true)->removeAll();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
## API Documentation
|
||||
|
||||
|
@ -111,9 +111,11 @@ class AssetControlExtension extends DataExtension
|
||||
}
|
||||
|
||||
// Check if canView permits anonymous viewers
|
||||
return $record->canView(Member::create())
|
||||
? AssetManipulationList::STATE_PUBLIC
|
||||
: AssetManipulationList::STATE_PROTECTED;
|
||||
return Member::actAs(null, function () use ($record) {
|
||||
return $record->canView()
|
||||
? AssetManipulationList::STATE_PUBLIC
|
||||
: AssetManipulationList::STATE_PROTECTED;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -871,6 +871,40 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow override of the current user ID
|
||||
*
|
||||
* @var int|null Set to null to fallback to session, or an explicit ID
|
||||
*/
|
||||
protected static $overrideID = null;
|
||||
|
||||
/**
|
||||
* Temporarily act as the specified user, limited to a $callback, but
|
||||
* without logging in as that user.
|
||||
*
|
||||
* E.g.
|
||||
* <code>
|
||||
* Member::logInAs(Security::findAnAdministrator(), function() {
|
||||
* $record->write();
|
||||
* });
|
||||
* </code>
|
||||
*
|
||||
* @param Member|null|int $member Member or member ID to log in as.
|
||||
* Set to null or 0 to act as a logged out user.
|
||||
* @param $callback
|
||||
*/
|
||||
public static function actAs($member, $callback)
|
||||
{
|
||||
$id = ($member instanceof Member ? $member->ID : $member) ?: 0;
|
||||
$previousID = static::$overrideID;
|
||||
static::$overrideID = $id;
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::$overrideID = $previousID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the current logged in user
|
||||
*
|
||||
@ -878,6 +912,10 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function currentUserID()
|
||||
{
|
||||
if (isset(static::$overrideID)) {
|
||||
return static::$overrideID;
|
||||
}
|
||||
|
||||
$id = Session::get("loggedInAs");
|
||||
if (!$id && !self::$_already_tried_to_auto_log_in) {
|
||||
self::autoLogin();
|
||||
@ -886,6 +924,7 @@ class Member extends DataObject implements TemplateGlobalProvider
|
||||
|
||||
return is_numeric($id) ? $id : 0;
|
||||
}
|
||||
|
||||
private static $_already_tried_to_auto_log_in = false;
|
||||
|
||||
|
||||
|
@ -1402,4 +1402,66 @@ class MemberTest extends FunctionalTest
|
||||
$userFromSession = Member::currentUser();
|
||||
$this->assertEquals($adminMember->ID, $userFromSession->ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \SilverStripe\Security\Member::actAs()
|
||||
*/
|
||||
public function testActAsUserPermissions()
|
||||
{
|
||||
$this->assertNull(Member::currentUser());
|
||||
|
||||
/** @var Member $adminMember */
|
||||
$adminMember = $this->objFromFixture(Member::class, 'admin');
|
||||
|
||||
// Check acting as admin when not logged in
|
||||
$checkAdmin = Member::actAs($adminMember, function () {
|
||||
return Permission::check('ADMIN');
|
||||
});
|
||||
$this->assertTrue($checkAdmin);
|
||||
|
||||
// Check nesting
|
||||
$checkAdmin = Member::actAs($adminMember, function () {
|
||||
return Member::actAs(null, function () {
|
||||
return Permission::check('ADMIN');
|
||||
});
|
||||
});
|
||||
$this->assertFalse($checkAdmin);
|
||||
|
||||
// Check logging in as non-admin user
|
||||
$this->logInWithPermission('TEST_PERMISSION');
|
||||
|
||||
$hasPerm = Member::actAs(null, function () {
|
||||
return Permission::check('TEST_PERMISSION');
|
||||
});
|
||||
$this->assertFalse($hasPerm);
|
||||
|
||||
// Check permissions can be promoted
|
||||
$checkAdmin = Member::actAs($adminMember, function () {
|
||||
return Permission::check('ADMIN');
|
||||
});
|
||||
$this->assertTrue($checkAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \SilverStripe\Security\Member::actAs()
|
||||
*/
|
||||
public function testActAsUser()
|
||||
{
|
||||
$this->assertNull(Member::currentUser());
|
||||
|
||||
/** @var Member $adminMember */
|
||||
$adminMember = $this->objFromFixture(Member::class, 'admin');
|
||||
$memberID = Member::actAs($adminMember, function () {
|
||||
return Member::currentUserID();
|
||||
});
|
||||
$this->assertEquals($adminMember->ID, $memberID);
|
||||
|
||||
// Check nesting
|
||||
$memberID = Member::actAs($adminMember, function () {
|
||||
return Member::actAs(null, function () {
|
||||
return Member::currentUserID();
|
||||
});
|
||||
});
|
||||
$this->assertEmpty($memberID);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user