Skip to content

Commit e15ad7b

Browse files
authored
Merge pull request #737 from cakephp/3.next-defaulting
Fix up identifier defaulting.
1 parent 3437d40 commit e15ad7b

File tree

7 files changed

+223
-1
lines changed

7 files changed

+223
-1
lines changed

src/Authenticator/CookieAuthenticator.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
use ArrayAccess;
2020
use Authentication\Identifier\AbstractIdentifier;
21+
use Authentication\Identifier\IdentifierCollection;
22+
use Authentication\Identifier\IdentifierInterface;
2123
use Authentication\PasswordHasher\PasswordHasherTrait;
2224
use Authentication\UrlChecker\UrlCheckerTrait;
2325
use Cake\Http\Cookie\Cookie;
@@ -55,6 +57,29 @@ class CookieAuthenticator extends AbstractAuthenticator implements PersistenceIn
5557
'salt' => true,
5658
];
5759

60+
/**
61+
* Constructor
62+
*
63+
* @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection.
64+
* @param array<string, mixed> $config Configuration settings.
65+
*/
66+
public function __construct(IdentifierInterface $identifier, array $config = [])
67+
{
68+
// If no identifier is configured, set up a default Password identifier
69+
if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) {
70+
// Pass the authenticator's fields configuration to the identifier
71+
$identifierConfig = [];
72+
if (isset($config['fields'])) {
73+
$identifierConfig['fields'] = $config['fields'];
74+
}
75+
$identifier = new IdentifierCollection([
76+
'Authentication.Password' => $identifierConfig,
77+
]);
78+
}
79+
80+
parent::__construct($identifier, $config);
81+
}
82+
5883
/**
5984
* @inheritDoc
6085
*/

src/Authenticator/FormAuthenticator.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
namespace Authentication\Authenticator;
1818

1919
use Authentication\Identifier\AbstractIdentifier;
20+
use Authentication\Identifier\IdentifierCollection;
21+
use Authentication\Identifier\IdentifierInterface;
2022
use Authentication\UrlChecker\UrlCheckerTrait;
2123
use Cake\Routing\Router;
2224
use Psr\Http\Message\ServerRequestInterface;
@@ -47,6 +49,29 @@ class FormAuthenticator extends AbstractAuthenticator
4749
],
4850
];
4951

52+
/**
53+
* Constructor
54+
*
55+
* @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection.
56+
* @param array<string, mixed> $config Configuration settings.
57+
*/
58+
public function __construct(IdentifierInterface $identifier, array $config = [])
59+
{
60+
// If no identifier is configured, set up a default Password identifier
61+
if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) {
62+
// Pass the authenticator's fields configuration to the identifier
63+
$identifierConfig = [];
64+
if (isset($config['fields'])) {
65+
$identifierConfig['fields'] = $config['fields'];
66+
}
67+
$identifier = new IdentifierCollection([
68+
'Authentication.Password' => $identifierConfig,
69+
]);
70+
}
71+
72+
parent::__construct($identifier, $config);
73+
}
74+
5075
/**
5176
* Checks the fields to ensure they are supplied.
5277
*

src/Authenticator/HttpBasicAuthenticator.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
namespace Authentication\Authenticator;
1717

1818
use Authentication\Identifier\AbstractIdentifier;
19+
use Authentication\Identifier\IdentifierCollection;
20+
use Authentication\Identifier\IdentifierInterface;
1921
use Psr\Http\Message\ServerRequestInterface;
2022

2123
/**
@@ -41,6 +43,29 @@ class HttpBasicAuthenticator extends AbstractAuthenticator implements StatelessI
4143
'skipChallenge' => false,
4244
];
4345

46+
/**
47+
* Constructor
48+
*
49+
* @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection.
50+
* @param array<string, mixed> $config Configuration settings.
51+
*/
52+
public function __construct(IdentifierInterface $identifier, array $config = [])
53+
{
54+
// If no identifier is configured, set up a default Password identifier
55+
if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) {
56+
// Pass the authenticator's fields configuration to the identifier
57+
$identifierConfig = [];
58+
if (isset($config['fields'])) {
59+
$identifierConfig['fields'] = $config['fields'];
60+
}
61+
$identifier = new IdentifierCollection([
62+
'Authentication.Password' => $identifierConfig,
63+
]);
64+
}
65+
66+
parent::__construct($identifier, $config);
67+
}
68+
4469
/**
4570
* Authenticate a user using HTTP auth. Will use the configured User model and attempt a
4671
* login using HTTP auth.

src/Authenticator/JwtAuthenticator.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace Authentication\Authenticator;
1818

1919
use ArrayObject;
20+
use Authentication\Identifier\IdentifierCollection;
2021
use Authentication\Identifier\IdentifierInterface;
2122
use Authentication\Identifier\JwtSubjectIdentifier;
2223
use Cake\Utility\Security;
@@ -56,7 +57,13 @@ class JwtAuthenticator extends TokenAuthenticator
5657
*/
5758
public function __construct(IdentifierInterface $identifier, array $config = [])
5859
{
59-
parent::__construct($identifier, $config);
60+
// Override parent's default - JWT should use JwtSubject identifier
61+
if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) {
62+
$identifier = new IdentifierCollection(['Authentication.JwtSubject']);
63+
}
64+
65+
// Call TokenAuthenticator's constructor but skip its default
66+
AbstractAuthenticator::__construct($identifier, $config);
6067

6168
if (empty($this->_config['secretKey'])) {
6269
if (!class_exists(Security::class)) {

src/Authenticator/TokenAuthenticator.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
namespace Authentication\Authenticator;
1818

19+
use Authentication\Identifier\IdentifierCollection;
20+
use Authentication\Identifier\IdentifierInterface;
1921
use Authentication\Identifier\TokenIdentifier;
2022
use Psr\Http\Message\ServerRequestInterface;
2123

@@ -35,6 +37,22 @@ class TokenAuthenticator extends AbstractAuthenticator implements StatelessInter
3537
'tokenPrefix' => null,
3638
];
3739

40+
/**
41+
* Constructor
42+
*
43+
* @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection.
44+
* @param array<string, mixed> $config Configuration settings.
45+
*/
46+
public function __construct(IdentifierInterface $identifier, array $config = [])
47+
{
48+
// If no identifier is configured, set up a default Token identifier
49+
if ($identifier instanceof IdentifierCollection && $identifier->isEmpty()) {
50+
$identifier = new IdentifierCollection(['Authentication.Token']);
51+
}
52+
53+
parent::__construct($identifier, $config);
54+
}
55+
3856
/**
3957
* Checks if the token is in the headers or a request parameter
4058
*

tests/TestCase/AuthenticationServiceTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,4 +1172,29 @@ public function testIsImpersonatingWrongProvider()
11721172
$this->expectExceptionMessage('The Authentication\Authenticator\FormAuthenticator Provider must implement ImpersonationInterface in order to use impersonation.');
11731173
$service->isImpersonating($request);
11741174
}
1175+
1176+
/**
1177+
* Test that FormAuthenticator works with default Password identifier
1178+
*
1179+
* @return void
1180+
*/
1181+
public function testFormAuthenticatorDefaultIdentifier()
1182+
{
1183+
$request = ServerRequestFactory::fromGlobals(
1184+
['REQUEST_URI' => '/testpath'],
1185+
[],
1186+
['username' => 'mariano', 'password' => 'password'],
1187+
);
1188+
1189+
// Test loading FormAuthenticator without specifying an identifier
1190+
$service = new AuthenticationService();
1191+
$service->loadAuthenticator('Authentication.Form');
1192+
1193+
$result = $service->authenticate($request);
1194+
$this->assertInstanceOf(Result::class, $result);
1195+
$this->assertTrue($result->isValid());
1196+
1197+
$authenticator = $service->getAuthenticationProvider();
1198+
$this->assertInstanceOf(FormAuthenticator::class, $authenticator);
1199+
}
11751200
}

tests/TestCase/Authenticator/FormAuthenticatorTest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,4 +565,101 @@ public function testAuthenticateInvalidChecker()
565565

566566
$form->authenticate($request);
567567
}
568+
569+
/**
570+
* Test that FormAuthenticator uses default Password identifier when none is provided.
571+
*
572+
* @return void
573+
*/
574+
public function testDefaultPasswordIdentifier()
575+
{
576+
// Create an empty IdentifierCollection (simulating no explicit identifier configuration)
577+
$identifiers = new IdentifierCollection();
578+
579+
$request = ServerRequestFactory::fromGlobals(
580+
['REQUEST_URI' => '/testpath'],
581+
[],
582+
['username' => 'mariano', 'password' => 'password'],
583+
);
584+
585+
// FormAuthenticator should automatically configure a Password identifier
586+
$form = new FormAuthenticator($identifiers);
587+
$result = $form->authenticate($request);
588+
589+
$this->assertInstanceOf(Result::class, $result);
590+
$this->assertSame(Result::SUCCESS, $result->getStatus());
591+
592+
// Verify the identifier collection now has the Password identifier
593+
$identifier = $form->getIdentifier();
594+
$this->assertInstanceOf(IdentifierCollection::class, $identifier);
595+
$this->assertFalse($identifier->isEmpty());
596+
}
597+
598+
/**
599+
* Test that FormAuthenticator respects explicitly configured identifier.
600+
*
601+
* @return void
602+
*/
603+
public function testExplicitIdentifierNotOverridden()
604+
{
605+
// Create an IdentifierCollection with a specific identifier
606+
$identifiers = new IdentifierCollection([
607+
'Password' => [
608+
'className' => 'Authentication.Password',
609+
'fields' => [
610+
'username' => 'email',
611+
'password' => 'password',
612+
],
613+
],
614+
]);
615+
616+
ServerRequestFactory::fromGlobals(
617+
['REQUEST_URI' => '/testpath'],
618+
[],
619+
['email' => 'mariano@example.com', 'password' => 'password'],
620+
);
621+
622+
// FormAuthenticator should use the provided identifier
623+
$form = new FormAuthenticator($identifiers);
624+
625+
// The identifier should remain as configured
626+
$identifier = $form->getIdentifier();
627+
$this->assertInstanceOf(IdentifierCollection::class, $identifier);
628+
$this->assertFalse($identifier->isEmpty());
629+
$this->assertSame($identifiers, $identifier, 'Identifier collection should be the same.');
630+
$this->assertSame($identifiers->get('Password'), $identifier->get('Password'), 'Identifier should be the same.');
631+
}
632+
633+
/**
634+
* Test that default identifier inherits fields configuration from authenticator.
635+
*
636+
* @return void
637+
*/
638+
public function testDefaultIdentifierInheritsFieldsConfig()
639+
{
640+
// Create an empty IdentifierCollection
641+
$identifiers = new IdentifierCollection();
642+
643+
// Configure authenticator with custom fields mapping
644+
$config = [
645+
'fields' => [
646+
'username' => 'user_name',
647+
'password' => 'pass_word',
648+
],
649+
];
650+
651+
// FormAuthenticator should create default identifier with inherited fields
652+
$form = new FormAuthenticator($identifiers, $config);
653+
654+
// Verify the identifier was created with the correct configuration
655+
$identifier = $form->getIdentifier();
656+
$this->assertInstanceOf(IdentifierCollection::class, $identifier);
657+
$this->assertFalse($identifier->isEmpty());
658+
659+
// Verify the fields are properly configured
660+
// We can't directly access the internal configuration, but we can verify
661+
// the FormAuthenticator has the expected configuration
662+
$this->assertEquals('user_name', $form->getConfig('fields.username'));
663+
$this->assertEquals('pass_word', $form->getConfig('fields.password'));
664+
}
568665
}

0 commit comments

Comments
 (0)