Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/AuthenticationServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
namespace Authentication;

use ArrayAccess;
use Authentication\Authenticator\AuthenticatorInterface;
use Authentication\Authenticator\PersistenceInterface;
use Authentication\Authenticator\ResultInterface;
Expand Down Expand Up @@ -69,6 +70,17 @@ public function getResult(): ?ResultInterface;
*/
public function getIdentityAttribute(): string;

/**
* Build an identity object from raw identity data.
*
* If the supplied data is already an `IdentityInterface`, it is returned
* unchanged.
*
* @param \ArrayAccess<string, mixed>|array<string, mixed> $identityData Identity data.
* @return \Authentication\IdentityInterface
*/
public function buildIdentity(ArrayAccess|array $identityData): IdentityInterface;
Comment thread
dereuromark marked this conversation as resolved.
Outdated

/**
* Return the URL to redirect unauthenticated users to.
*
Expand Down
32 changes: 32 additions & 0 deletions src/Controller/Component/AuthenticationComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,38 @@ public function setIdentity(ArrayAccess|array $identity)
return $this;
}

/**
* Replace the in-request identity object without persisting it.
*
* Use this when you only need to swap the identity attribute on the
* current request - for example, to attach eager-loaded associations
* or computed flags to the active user for the rest of the request -
* without going through `clearIdentity()` and `persistIdentity()`.
*
* Unlike `setIdentity()`, this does not touch the session and does not
* end an active impersonation, because no authenticator's
* `clearIdentity()` is invoked.
*
* @param \ArrayAccess|array $identity Identity data or an identity object.
* @return $this
*/
public function replaceIdentity(ArrayAccess|array $identity)
{
$controller = $this->getController();
$service = $this->getAuthenticationService();

$identity = $service->buildIdentity($identity);

$controller->setRequest(
$controller->getRequest()->withAttribute(
$service->getIdentityAttribute(),
$identity,
),
);

return $this;
}

/**
* Log a user out.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,96 @@ public function testSetIdentityOverwrite(): void
);
}

/**
* Ensure replaceIdentity() swaps the request attribute without
* touching the session.
*
* @return void
*/
public function testReplaceIdentity(): void
{
$request = $this->request->withAttribute('authentication', $this->service);

$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$component->replaceIdentity($this->identityData);

$result = $component->getIdentity();
$this->assertInstanceOf(IdentityInterface::class, $result);
$this->assertSame($this->identityData, $result->getOriginalData());
$this->assertNull(
$controller->getRequest()->getSession()->read('Auth'),
'Session must not be written by replaceIdentity().',
);
}

/**
* Test that replaceIdentity() called with an identity instance keeps the
* exact instance as the request attribute.
*
* @return void
*/
public function testReplaceIdentityInstance(): void
{
$request = $this->request->withAttribute('authentication', $this->service);

$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$identity = new Identity($this->identityData);
$component->replaceIdentity($identity);

$this->assertSame($identity, $component->getIdentity());
}

/**
* Ensure replaceIdentity() does not end an active impersonation,
* unlike setIdentity() which clears identity first.
*
* @return void
*/
public function testReplaceIdentityKeepsImpersonation(): void
{
$impersonator = new ArrayObject(['username' => 'mariano']);
$impersonated = new ArrayObject(['username' => 'larry']);
$this->request->getSession()->write('Auth', $impersonator);
$this->service->authenticate($this->request);
$identity = new Identity($impersonator);
$request = $this->request
->withAttribute('identity', $identity)
->withAttribute('authentication', $this->service);
$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$component->impersonate($impersonated);
$this->assertEquals($impersonated, $controller->getRequest()->getSession()->read('Auth'));
$this->assertEquals($impersonator, $controller->getRequest()->getSession()->read('AuthImpersonate'));

$reloaded = new ArrayObject(['username' => 'larry', 'profile' => 'loaded']);
$component->replaceIdentity($reloaded);

$this->assertSame(
$reloaded,
$component->getIdentity()->getOriginalData(),
'Request identity should reflect the reloaded user.',
);
$this->assertEquals(
$impersonated,
$controller->getRequest()->getSession()->read('Auth'),
'Session Auth slot must be untouched by replaceIdentity().',
);
$this->assertEquals(
$impersonator,
$controller->getRequest()->getSession()->read('AuthImpersonate'),
'Impersonation must survive replaceIdentity().',
);
$this->assertTrue($component->isImpersonating());
}

/**
* testGetIdentity
*
Expand Down
Loading