diff --git a/src/Authenticator/PrimaryKeySessionAuthenticator.php b/src/Authenticator/PrimaryKeySessionAuthenticator.php index a55e8bb6..a03f896f 100644 --- a/src/Authenticator/PrimaryKeySessionAuthenticator.php +++ b/src/Authenticator/PrimaryKeySessionAuthenticator.php @@ -4,6 +4,7 @@ namespace Authentication\Authenticator; use ArrayAccess; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; use Cake\Http\Exception\UnauthorizedException; use Psr\Http\Message\ResponseInterface; @@ -11,21 +12,87 @@ /** * Session Authenticator with only ID + * + * This authenticator stores only the user's primary key in the session, + * and looks up the full user record from the database on each request. + * + * By default, it uses a TokenIdentifier configured to look up users by + * their `id` field. This works out of the box for most applications: + * + * ```php + * $service->loadAuthenticator('Authentication.PrimaryKeySession'); + * ``` + * + * You can customize the identifier configuration if needed: + * + * ```php + * $service->loadAuthenticator('Authentication.PrimaryKeySession', [ + * 'identifier' => [ + * 'className' => 'Authentication.Token', + * 'tokenField' => 'uuid', + * 'dataField' => 'key', + * 'resolver' => [ + * 'className' => 'Authentication.Orm', + * 'userModel' => 'Members', + * ], + * ], + * ]); + * ``` */ class PrimaryKeySessionAuthenticator extends SessionAuthenticator { /** - * @param \Authentication\Identifier\IdentifierInterface|null $identifier - * @param array $config + * Default config for this object. + * + * - `identifierKey` The key used when passing the ID to the identifier. + * - `idField` The field on the user entity that contains the primary key. + * + * @var array + */ + protected array $_defaultConfig = [ + 'fields' => [], + 'sessionKey' => 'Auth', + 'impersonateSessionKey' => 'AuthImpersonate', + 'identify' => false, + 'identityAttribute' => 'identity', + 'identifierKey' => 'key', + 'idField' => 'id', + ]; + + /** + * Constructor + * + * Bypasses SessionAuthenticator's default PasswordIdentifier creation + * to allow lazy initialization of the TokenIdentifier in getIdentifier(). + * + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. + * @param array $config Configuration settings. */ public function __construct(?IdentifierInterface $identifier, array $config = []) { - $config += [ - 'identifierKey' => 'key', - 'idField' => 'id', - ]; + $this->_identifier = $identifier; + $this->setConfig($config); + } + + /** + * Gets the identifier. + * + * If no identifier was explicitly configured, creates a default TokenIdentifier + * configured to look up users by their primary key (`id` field). + * + * @return \Authentication\Identifier\IdentifierInterface + */ + public function getIdentifier(): IdentifierInterface + { + if ($this->_identifier === null) { + $this->_identifier = IdentifierFactory::create([ + 'className' => 'Authentication.Token', + 'tokenField' => $this->getConfig('idField'), + 'dataField' => $this->getConfig('identifierKey'), + ]); + } - parent::__construct($identifier, $config); + return $this->_identifier; } /** diff --git a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php index 5b6d8806..ce704aae 100644 --- a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php @@ -20,6 +20,7 @@ use Authentication\Authenticator\PrimaryKeySessionAuthenticator; use Authentication\Authenticator\Result; use Authentication\Identifier\IdentifierFactory; +use Authentication\Identifier\TokenIdentifier; use Cake\Http\Exception\UnauthorizedException; use Cake\Http\Response; use Cake\Http\ServerRequestFactory; @@ -67,7 +68,7 @@ public function setUp(): void } /** - * Test authentication + * Test authentication with explicit identifier * * @return void */ @@ -89,6 +90,63 @@ public function testAuthenticateSuccess() $this->assertSame(Result::SUCCESS, $result->getStatus()); } + /** + * Test authentication works with default identifier (no explicit configuration) + * + * @return void + */ + public function testAuthenticateSuccessWithDefaultIdentifier() + { + $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); + + $this->sessionMock->expects($this->once()) + ->method('read') + ->with('Auth') + ->willReturn(1); + + $request = $request->withAttribute('session', $this->sessionMock); + + // No identifier passed - should use the default TokenIdentifier + $authenticator = new PrimaryKeySessionAuthenticator(null); + $result = $authenticator->authenticate($request); + + $this->assertInstanceOf(Result::class, $result); + $this->assertSame(Result::SUCCESS, $result->getStatus()); + } + + /** + * Test getIdentifier returns default TokenIdentifier when none configured + * + * @return void + */ + public function testGetIdentifierReturnsDefaultWhenNotConfigured() + { + $authenticator = new PrimaryKeySessionAuthenticator(null); + $identifier = $authenticator->getIdentifier(); + + $this->assertInstanceOf(TokenIdentifier::class, $identifier); + $this->assertSame('id', $identifier->getConfig('tokenField')); + $this->assertSame('key', $identifier->getConfig('dataField')); + } + + /** + * Test custom idField/identifierKey config propagates to default identifier + * + * @return void + */ + public function testGetIdentifierUsesCustomConfig() + { + $authenticator = new PrimaryKeySessionAuthenticator(null, [ + 'idField' => 'uuid', + 'identifierKey' => 'token', + ]); + $identifier = $authenticator->getIdentifier(); + + $this->assertInstanceOf(TokenIdentifier::class, $identifier); + $this->assertSame('uuid', $identifier->getConfig('tokenField')); + $this->assertSame('token', $identifier->getConfig('dataField')); + } + /** * Test authentication *