From 6f580715bed67ca0fc29b37dedf5779b3ab905c3 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 27 Aug 2025 00:45:42 +0200 Subject: [PATCH 1/6] Fix up identifier defaulting. --- src/Authenticator/FormAuthenticator.php | 18 ++++++ tests/TestCase/AuthenticationServiceTest.php | 25 ++++++++ .../Authenticator/FormAuthenticatorTest.php | 61 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/src/Authenticator/FormAuthenticator.php b/src/Authenticator/FormAuthenticator.php index 07bbe9d7..c5a807f7 100644 --- a/src/Authenticator/FormAuthenticator.php +++ b/src/Authenticator/FormAuthenticator.php @@ -17,6 +17,8 @@ namespace Authentication\Authenticator; use Authentication\Identifier\AbstractIdentifier; +use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierInterface; use Authentication\UrlChecker\UrlCheckerTrait; use Cake\Routing\Router; use Psr\Http\Message\ServerRequestInterface; @@ -47,6 +49,22 @@ class FormAuthenticator extends AbstractAuthenticator ], ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection. + * @param array $config Configuration settings. + */ + public function __construct(IdentifierInterface $identifier, array $config = []) + { + // If no identifier is configured, set up a default Password identifier + if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { + $identifier = new IdentifierCollection(['Authentication.Password']); + } + + parent::__construct($identifier, $config); + } + /** * Checks the fields to ensure they are supplied. * diff --git a/tests/TestCase/AuthenticationServiceTest.php b/tests/TestCase/AuthenticationServiceTest.php index a551e543..602cd9eb 100644 --- a/tests/TestCase/AuthenticationServiceTest.php +++ b/tests/TestCase/AuthenticationServiceTest.php @@ -1172,4 +1172,29 @@ public function testIsImpersonatingWrongProvider() $this->expectExceptionMessage('The Authentication\Authenticator\FormAuthenticator Provider must implement ImpersonationInterface in order to use impersonation.'); $service->isImpersonating($request); } + + /** + * Test that FormAuthenticator works with default Password identifier + * + * @return void + */ + public function testFormAuthenticatorDefaultIdentifier() + { + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/testpath'], + [], + ['username' => 'mariano', 'password' => 'password'], + ); + + // Test loading FormAuthenticator without specifying an identifier + $service = new AuthenticationService(); + $service->loadAuthenticator('Authentication.Form'); + + $result = $service->authenticate($request); + $this->assertInstanceOf(Result::class, $result); + $this->assertTrue($result->isValid()); + + $authenticator = $service->getAuthenticationProvider(); + $this->assertInstanceOf(FormAuthenticator::class, $authenticator); + } } diff --git a/tests/TestCase/Authenticator/FormAuthenticatorTest.php b/tests/TestCase/Authenticator/FormAuthenticatorTest.php index 3287f961..556f0dd6 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -544,4 +544,65 @@ public function testAuthenticateInvalidChecker() $form->authenticate($request); } + + /** + * Test that FormAuthenticator uses default Password identifier when none is provided. + * + * @return void + */ + public function testDefaultPasswordIdentifier() + { + // Create an empty IdentifierCollection (simulating no explicit identifier configuration) + $identifiers = new IdentifierCollection(); + + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/testpath'], + [], + ['username' => 'mariano', 'password' => 'password'], + ); + + // FormAuthenticator should automatically configure a Password identifier + $form = new FormAuthenticator($identifiers); + $result = $form->authenticate($request); + + $this->assertInstanceOf(Result::class, $result); + $this->assertSame(Result::SUCCESS, $result->getStatus()); + + // Verify the identifier collection now has the Password identifier + $identifier = $form->getIdentifier(); + $this->assertInstanceOf(IdentifierCollection::class, $identifier); + $this->assertFalse($identifier->isEmpty()); + } + + /** + * Test that FormAuthenticator respects explicitly configured identifier. + * + * @return void + */ + public function testExplicitIdentifierNotOverridden() + { + // Create an IdentifierCollection with a specific identifier + $identifiers = new IdentifierCollection([ + 'Authentication.Password' => [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password', + ], + ], + ]); + + ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/testpath'], + [], + ['email' => 'mariano@example.com', 'password' => 'password'], + ); + + // FormAuthenticator should use the provided identifier + $form = new FormAuthenticator($identifiers); + + // The identifier should remain as configured + $identifier = $form->getIdentifier(); + $this->assertInstanceOf(IdentifierCollection::class, $identifier); + $this->assertFalse($identifier->isEmpty()); + } } From cf5a6551e5380219e59acb0763855c685f68422b Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 27 Aug 2025 00:51:45 +0200 Subject: [PATCH 2/6] Fix up identifier defaulting. --- src/Authenticator/CookieAuthenticator.php | 18 ++++++++++++++++++ src/Authenticator/HttpBasicAuthenticator.php | 18 ++++++++++++++++++ src/Authenticator/JwtAuthenticator.php | 9 ++++++++- src/Authenticator/TokenAuthenticator.php | 18 ++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index 6c16053f..a5f717e1 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -18,6 +18,8 @@ use ArrayAccess; use Authentication\Identifier\AbstractIdentifier; +use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierInterface; use Authentication\PasswordHasher\PasswordHasherTrait; use Authentication\UrlChecker\UrlCheckerTrait; use Cake\Http\Cookie\Cookie; @@ -55,6 +57,22 @@ class CookieAuthenticator extends AbstractAuthenticator implements PersistenceIn 'salt' => true, ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection. + * @param array $config Configuration settings. + */ + public function __construct(IdentifierInterface $identifier, array $config = []) + { + // If no identifier is configured, set up a default Password identifier + if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { + $identifier = new IdentifierCollection(['Authentication.Password']); + } + + parent::__construct($identifier, $config); + } + /** * @inheritDoc */ diff --git a/src/Authenticator/HttpBasicAuthenticator.php b/src/Authenticator/HttpBasicAuthenticator.php index e9878243..689367c7 100644 --- a/src/Authenticator/HttpBasicAuthenticator.php +++ b/src/Authenticator/HttpBasicAuthenticator.php @@ -16,6 +16,8 @@ namespace Authentication\Authenticator; use Authentication\Identifier\AbstractIdentifier; +use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierInterface; use Psr\Http\Message\ServerRequestInterface; /** @@ -41,6 +43,22 @@ class HttpBasicAuthenticator extends AbstractAuthenticator implements StatelessI 'skipChallenge' => false, ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection. + * @param array $config Configuration settings. + */ + public function __construct(IdentifierInterface $identifier, array $config = []) + { + // If no identifier is configured, set up a default Password identifier + if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { + $identifier = new IdentifierCollection(['Authentication.Password']); + } + + parent::__construct($identifier, $config); + } + /** * Authenticate a user using HTTP auth. Will use the configured User model and attempt a * login using HTTP auth. diff --git a/src/Authenticator/JwtAuthenticator.php b/src/Authenticator/JwtAuthenticator.php index 2afa82fe..e62002be 100644 --- a/src/Authenticator/JwtAuthenticator.php +++ b/src/Authenticator/JwtAuthenticator.php @@ -17,6 +17,7 @@ namespace Authentication\Authenticator; use ArrayObject; +use Authentication\Identifier\IdentifierCollection; use Authentication\Identifier\IdentifierInterface; use Authentication\Identifier\JwtSubjectIdentifier; use Cake\Utility\Security; @@ -56,7 +57,13 @@ class JwtAuthenticator extends TokenAuthenticator */ public function __construct(IdentifierInterface $identifier, array $config = []) { - parent::__construct($identifier, $config); + // Override parent's default - JWT should use JwtSubject identifier + if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { + $identifier = new IdentifierCollection(['Authentication.JwtSubject']); + } + + // Call TokenAuthenticator's constructor but skip its default + AbstractAuthenticator::__construct($identifier, $config); if (empty($this->_config['secretKey'])) { if (!class_exists(Security::class)) { diff --git a/src/Authenticator/TokenAuthenticator.php b/src/Authenticator/TokenAuthenticator.php index 385a9c5c..5b8baed6 100644 --- a/src/Authenticator/TokenAuthenticator.php +++ b/src/Authenticator/TokenAuthenticator.php @@ -16,6 +16,8 @@ */ namespace Authentication\Authenticator; +use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierInterface; use Authentication\Identifier\TokenIdentifier; use Psr\Http\Message\ServerRequestInterface; @@ -35,6 +37,22 @@ class TokenAuthenticator extends AbstractAuthenticator implements StatelessInter 'tokenPrefix' => null, ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection. + * @param array $config Configuration settings. + */ + public function __construct(IdentifierInterface $identifier, array $config = []) + { + // If no identifier is configured, set up a default Token identifier + if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { + $identifier = new IdentifierCollection(['Authentication.Token']); + } + + parent::__construct($identifier, $config); + } + /** * Checks if the token is in the headers or a request parameter * From 0fd6e3d4e888927a366d936a0a3584750ccb4635 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 27 Aug 2025 00:52:50 +0200 Subject: [PATCH 3/6] Fix up identifier defaulting. --- src/Authenticator/CookieAuthenticator.php | 2 +- src/Authenticator/HttpBasicAuthenticator.php | 2 +- src/Authenticator/JwtAuthenticator.php | 2 +- src/Authenticator/TokenAuthenticator.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index a5f717e1..d507666f 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -69,7 +69,7 @@ public function __construct(IdentifierInterface $identifier, array $config = []) if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { $identifier = new IdentifierCollection(['Authentication.Password']); } - + parent::__construct($identifier, $config); } diff --git a/src/Authenticator/HttpBasicAuthenticator.php b/src/Authenticator/HttpBasicAuthenticator.php index 689367c7..6e0ec4c1 100644 --- a/src/Authenticator/HttpBasicAuthenticator.php +++ b/src/Authenticator/HttpBasicAuthenticator.php @@ -55,7 +55,7 @@ public function __construct(IdentifierInterface $identifier, array $config = []) if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { $identifier = new IdentifierCollection(['Authentication.Password']); } - + parent::__construct($identifier, $config); } diff --git a/src/Authenticator/JwtAuthenticator.php b/src/Authenticator/JwtAuthenticator.php index e62002be..ff1acd89 100644 --- a/src/Authenticator/JwtAuthenticator.php +++ b/src/Authenticator/JwtAuthenticator.php @@ -61,7 +61,7 @@ public function __construct(IdentifierInterface $identifier, array $config = []) if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { $identifier = new IdentifierCollection(['Authentication.JwtSubject']); } - + // Call TokenAuthenticator's constructor but skip its default AbstractAuthenticator::__construct($identifier, $config); diff --git a/src/Authenticator/TokenAuthenticator.php b/src/Authenticator/TokenAuthenticator.php index 5b8baed6..d02e26b5 100644 --- a/src/Authenticator/TokenAuthenticator.php +++ b/src/Authenticator/TokenAuthenticator.php @@ -49,7 +49,7 @@ public function __construct(IdentifierInterface $identifier, array $config = []) if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { $identifier = new IdentifierCollection(['Authentication.Token']); } - + parent::__construct($identifier, $config); } From a20d315fb706cee9e2d5d579a1248f9fefe95c1e Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 1 Sep 2025 16:30:39 +0200 Subject: [PATCH 4/6] Fix up identifier defaulting. --- src/Authenticator/CookieAuthenticator.php | 9 ++++- src/Authenticator/FormAuthenticator.php | 9 ++++- src/Authenticator/HttpBasicAuthenticator.php | 9 ++++- .../Authenticator/FormAuthenticatorTest.php | 33 +++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index d507666f..8e2836ef 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -67,7 +67,14 @@ public function __construct(IdentifierInterface $identifier, array $config = []) { // If no identifier is configured, set up a default Password identifier if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { - $identifier = new IdentifierCollection(['Authentication.Password']); + // Pass the authenticator's fields configuration to the identifier + $identifierConfig = []; + if (isset($config['fields'])) { + $identifierConfig['fields'] = $config['fields']; + } + $identifier = new IdentifierCollection([ + 'Authentication.Password' => $identifierConfig, + ]); } parent::__construct($identifier, $config); diff --git a/src/Authenticator/FormAuthenticator.php b/src/Authenticator/FormAuthenticator.php index c5a807f7..f784c483 100644 --- a/src/Authenticator/FormAuthenticator.php +++ b/src/Authenticator/FormAuthenticator.php @@ -59,7 +59,14 @@ public function __construct(IdentifierInterface $identifier, array $config = []) { // If no identifier is configured, set up a default Password identifier if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { - $identifier = new IdentifierCollection(['Authentication.Password']); + // Pass the authenticator's fields configuration to the identifier + $identifierConfig = []; + if (isset($config['fields'])) { + $identifierConfig['fields'] = $config['fields']; + } + $identifier = new IdentifierCollection([ + 'Authentication.Password' => $identifierConfig, + ]); } parent::__construct($identifier, $config); diff --git a/src/Authenticator/HttpBasicAuthenticator.php b/src/Authenticator/HttpBasicAuthenticator.php index 6e0ec4c1..715e002f 100644 --- a/src/Authenticator/HttpBasicAuthenticator.php +++ b/src/Authenticator/HttpBasicAuthenticator.php @@ -53,7 +53,14 @@ public function __construct(IdentifierInterface $identifier, array $config = []) { // If no identifier is configured, set up a default Password identifier if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) { - $identifier = new IdentifierCollection(['Authentication.Password']); + // Pass the authenticator's fields configuration to the identifier + $identifierConfig = []; + if (isset($config['fields'])) { + $identifierConfig['fields'] = $config['fields']; + } + $identifier = new IdentifierCollection([ + 'Authentication.Password' => $identifierConfig, + ]); } parent::__construct($identifier, $config); diff --git a/tests/TestCase/Authenticator/FormAuthenticatorTest.php b/tests/TestCase/Authenticator/FormAuthenticatorTest.php index 556f0dd6..cf1b5a20 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -605,4 +605,37 @@ public function testExplicitIdentifierNotOverridden() $this->assertInstanceOf(IdentifierCollection::class, $identifier); $this->assertFalse($identifier->isEmpty()); } + + /** + * Test that default identifier inherits fields configuration from authenticator. + * + * @return void + */ + public function testDefaultIdentifierInheritsFieldsConfig() + { + // Create an empty IdentifierCollection + $identifiers = new IdentifierCollection(); + + // Configure authenticator with custom fields mapping + $config = [ + 'fields' => [ + 'username' => 'user_name', + 'password' => 'pass_word', + ], + ]; + + // FormAuthenticator should create default identifier with inherited fields + $form = new FormAuthenticator($identifiers, $config); + + // Verify the identifier was created with the correct configuration + $identifier = $form->getIdentifier(); + $this->assertInstanceOf(IdentifierCollection::class, $identifier); + $this->assertFalse($identifier->isEmpty()); + + // Verify the fields are properly configured + // We can't directly access the internal configuration, but we can verify + // the FormAuthenticator has the expected configuration + $this->assertEquals('user_name', $form->getConfig('fields.username')); + $this->assertEquals('pass_word', $form->getConfig('fields.password')); + } } From a19631f8ca3261dabf6e3178190b5935f2355e6c Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 4 Sep 2025 00:38:02 +0200 Subject: [PATCH 5/6] Fix up identifier defaulting. --- tests/TestCase/Authenticator/FormAuthenticatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Authenticator/FormAuthenticatorTest.php b/tests/TestCase/Authenticator/FormAuthenticatorTest.php index cf1b5a20..25dfd6c7 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -631,7 +631,7 @@ public function testDefaultIdentifierInheritsFieldsConfig() $identifier = $form->getIdentifier(); $this->assertInstanceOf(IdentifierCollection::class, $identifier); $this->assertFalse($identifier->isEmpty()); - + // Verify the fields are properly configured // We can't directly access the internal configuration, but we can verify // the FormAuthenticator has the expected configuration From fb829c195625a02851ec0ebb3bc4b115b0fa77ee Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 8 Sep 2025 22:31:21 -0400 Subject: [PATCH 6/6] Add more assertions --- tests/TestCase/Authenticator/FormAuthenticatorTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/TestCase/Authenticator/FormAuthenticatorTest.php b/tests/TestCase/Authenticator/FormAuthenticatorTest.php index 25dfd6c7..e316e004 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -583,7 +583,8 @@ public function testExplicitIdentifierNotOverridden() { // Create an IdentifierCollection with a specific identifier $identifiers = new IdentifierCollection([ - 'Authentication.Password' => [ + 'Password' => [ + 'className' => 'Authentication.Password', 'fields' => [ 'username' => 'email', 'password' => 'password', @@ -604,6 +605,8 @@ public function testExplicitIdentifierNotOverridden() $identifier = $form->getIdentifier(); $this->assertInstanceOf(IdentifierCollection::class, $identifier); $this->assertFalse($identifier->isEmpty()); + $this->assertSame($identifiers, $identifier, 'Identifier collection should be the same.'); + $this->assertSame($identifiers->get('Password'), $identifier->get('Password'), 'Identifier should be the same.'); } /**