diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index 6c16053f..8e2836ef 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,29 @@ 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()) { + // 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); + } + /** * @inheritDoc */ diff --git a/src/Authenticator/FormAuthenticator.php b/src/Authenticator/FormAuthenticator.php index 07bbe9d7..f784c483 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,29 @@ 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()) { + // 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); + } + /** * Checks the fields to ensure they are supplied. * diff --git a/src/Authenticator/HttpBasicAuthenticator.php b/src/Authenticator/HttpBasicAuthenticator.php index e9878243..715e002f 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,29 @@ 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()) { + // 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); + } + /** * 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..ff1acd89 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..d02e26b5 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 * 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..e316e004 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -544,4 +544,101 @@ 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([ + 'Password' => [ + 'className' => '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()); + $this->assertSame($identifiers, $identifier, 'Identifier collection should be the same.'); + $this->assertSame($identifiers->get('Password'), $identifier->get('Password'), 'Identifier should be the same.'); + } + + /** + * 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')); + } }