mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00: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,
|
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.
|
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
|
## API Documentation
|
||||||
|
|
||||||
|
@ -111,9 +111,11 @@ class AssetControlExtension extends DataExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if canView permits anonymous viewers
|
// Check if canView permits anonymous viewers
|
||||||
return $record->canView(Member::create())
|
return Member::actAs(null, function () use ($record) {
|
||||||
? AssetManipulationList::STATE_PUBLIC
|
return $record->canView()
|
||||||
: AssetManipulationList::STATE_PROTECTED;
|
? 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
|
* Get the ID of the current logged in user
|
||||||
*
|
*
|
||||||
@ -878,6 +912,10 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
*/
|
*/
|
||||||
public static function currentUserID()
|
public static function currentUserID()
|
||||||
{
|
{
|
||||||
|
if (isset(static::$overrideID)) {
|
||||||
|
return static::$overrideID;
|
||||||
|
}
|
||||||
|
|
||||||
$id = Session::get("loggedInAs");
|
$id = Session::get("loggedInAs");
|
||||||
if (!$id && !self::$_already_tried_to_auto_log_in) {
|
if (!$id && !self::$_already_tried_to_auto_log_in) {
|
||||||
self::autoLogin();
|
self::autoLogin();
|
||||||
@ -886,6 +924,7 @@ class Member extends DataObject implements TemplateGlobalProvider
|
|||||||
|
|
||||||
return is_numeric($id) ? $id : 0;
|
return is_numeric($id) ? $id : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static $_already_tried_to_auto_log_in = false;
|
private static $_already_tried_to_auto_log_in = false;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1402,4 +1402,66 @@ class MemberTest extends FunctionalTest
|
|||||||
$userFromSession = Member::currentUser();
|
$userFromSession = Member::currentUser();
|
||||||
$this->assertEquals($adminMember->ID, $userFromSession->ID);
|
$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…
x
Reference in New Issue
Block a user