diff --git a/docs/en/upgrade-3-to-4.rst b/docs/en/upgrade-3-to-4.rst new file mode 100644 index 00000000..e631c1b0 --- /dev/null +++ b/docs/en/upgrade-3-to-4.rst @@ -0,0 +1,314 @@ +Upgrade Guide 3.x to 4.x +######################### + +Version 4.0 is a major release with several breaking changes focused on +simplifying the API and removing deprecated code. + +Breaking Changes +================ + +IdentifierCollection Removed +----------------------------- + +The deprecated ``IdentifierCollection`` has been removed. Authenticators now +accept a nullable ``IdentifierInterface`` directly. + +**Before (3.x):** + +.. code-block:: php + + use Authentication\Identifier\IdentifierCollection; + + $identifiers = new IdentifierCollection([ + 'Authentication.Password', + ]); + + $authenticator = new FormAuthenticator($identifiers); + +**After (4.x):** + +.. code-block:: php + + use Authentication\Identifier\IdentifierFactory; + + // Option 1: Pass identifier directly + $identifier = IdentifierFactory::create('Authentication.Password'); + $authenticator = new FormAuthenticator($identifier); + + // Option 2: Pass null and let authenticator create default + $authenticator = new FormAuthenticator(null); + + // Option 3: Configure identifier in authenticator config + $service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', + ]); + +AuthenticationService Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``loadIdentifier()`` method has been removed from ``AuthenticationService``. +Identifiers are now managed by individual authenticators. + +**Before (3.x):** + +.. code-block:: php + + $service = new AuthenticationService(); + $service->loadIdentifier('Authentication.Password'); + $service->loadAuthenticator('Authentication.Form'); + +**After (4.x):** + +.. code-block:: php + + $service = new AuthenticationService(); + $service->loadAuthenticator('Authentication.Form', [ + 'identifier' => 'Authentication.Password', + ]); + +CREDENTIAL Constants Moved +--------------------------- + +The ``CREDENTIAL_USERNAME`` and ``CREDENTIAL_PASSWORD`` constants have been +moved from ``AbstractIdentifier`` to specific identifier implementations. + +**Before (3.x):** + +.. code-block:: php + + use Authentication\Identifier\AbstractIdentifier; + + $fields = [ + AbstractIdentifier::CREDENTIAL_USERNAME => 'email', + AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + ]; + +**After (4.x):** + +.. code-block:: php + + use Authentication\Identifier\PasswordIdentifier; + + $fields = [ + PasswordIdentifier::CREDENTIAL_USERNAME => 'email', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', + ]; + +For LDAP authentication: + +.. code-block:: php + + use Authentication\Identifier\LdapIdentifier; + + $fields = [ + LdapIdentifier::CREDENTIAL_USERNAME => 'uid', + LdapIdentifier::CREDENTIAL_PASSWORD => 'password', + ]; + +URL Checker Renamed and Restructured +------------------------------------- + +URL checkers have been completely restructured: + +- ``CakeRouterUrlChecker`` has been renamed to ``DefaultUrlChecker`` +- The old ``DefaultUrlChecker`` has been renamed to ``StringUrlChecker`` +- Auto-detection has been removed - ``DefaultUrlChecker`` is now hardcoded + +**Before (3.x):** + +.. code-block:: php + + // Using CakeRouterUrlChecker explicitly + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.CakeRouter', + 'loginUrl' => [ + 'controller' => 'Users', + 'action' => 'login', + ], + ]); + + // Using DefaultUrlChecker explicitly + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Default', + 'loginUrl' => '/users/login', + ]); + + // Auto-detection (picks CakeRouter if available, otherwise Default) + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', + ]); + +**After (4.x):** + +.. code-block:: php + + // DefaultUrlChecker is now hardcoded (formerly CakeRouterUrlChecker) + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + 'controller' => 'Users', + 'action' => 'login', + ], + ]); + + // For string-only URL checking, explicitly use StringUrlChecker + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', + ]); + +Simplified URL Checker API +--------------------------- + +URL checkers now accept a single URL in either string or array format. +For multiple URLs, you must explicitly use ``MultiUrlChecker``. + +**Multiple URLs - Before (3.x):** + +.. code-block:: php + + // This would auto-select the appropriate checker + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', + ], + ]); + +**Multiple URLs - After (4.x):** + +.. code-block:: php + + // Must explicitly configure MultiUrlChecker + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', + ], + ]); + +Single URLs work the same in both versions: + +.. code-block:: php + + // String URL + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', + ]); + + // Array URL (CakePHP route) + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => ['controller' => 'Users', 'action' => 'login'], + ]); + +Auto-Detection Removed +---------------------- + +URL Checkers +^^^^^^^^^^^^ + +**Important:** Auto-detection has been removed. ``DefaultUrlChecker`` is now hardcoded. + +- **4.x default:** Always uses ``DefaultUrlChecker`` (formerly ``CakeUrlChecker``) +- **String URLs only:** Must explicitly configure ``StringUrlChecker`` +- **Multiple URLs:** Must explicitly configure ``MultiUrlChecker`` + +DefaultUrlChecker is Now CakePHP-Based +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``DefaultUrlChecker`` is now the CakePHP checker (formerly ``CakeRouterUrlChecker``). +It requires CakePHP Router and supports both string and array URLs. + +The 3.x ``DefaultUrlChecker`` has been renamed to ``StringUrlChecker``. + +.. code-block:: php + + // DefaultUrlChecker now requires CakePHP Router + $checker = new DefaultUrlChecker(); + $checker->check($request, ['controller' => 'Users', 'action' => 'login']); // Works + $checker->check($request, '/users/login'); // Also works + + // For string URL only usage: + $checker = new StringUrlChecker(); + $checker->check($request, '/users/login'); // Works + $checker->check($request, ['controller' => 'Users']); // Throws exception + +New Features +============ + +IdentifierFactory +----------------- + +New factory class for creating identifiers from configuration: + +.. code-block:: php + + use Authentication\Identifier\IdentifierFactory; + + // Create from string + $identifier = IdentifierFactory::create('Authentication.Password'); + + // Create with config + $identifier = IdentifierFactory::create('Authentication.Password', [ + 'fields' => [ + 'username' => 'email', + 'password' => 'password', + ], + ]); + + // Pass existing instance (returns as-is) + $identifier = IdentifierFactory::create($existingIdentifier); + +MultiUrlChecker +--------------- + +New dedicated checker for multiple login URLs: + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/login', + '/de/login', + ['lang' => 'fr', 'controller' => 'Users', 'action' => 'login'], + ], + ]); + +Migration Tips +============== + +1. **Search and Replace**: + + - ``AbstractIdentifier::CREDENTIAL_`` → ``PasswordIdentifier::CREDENTIAL_`` + - ``IdentifierCollection`` → ``IdentifierFactory`` + - ``'Authentication.CakeRouter'`` → Remove (no longer needed, default is now CakePHP-based) + - ``CakeRouterUrlChecker`` → ``DefaultUrlChecker`` + - Old 3.x ``DefaultUrlChecker`` → ``StringUrlChecker`` + +2. **String URL Checking**: + + If you want to use string-only URL checking, explicitly configure + ``StringUrlChecker``: + + .. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', + ]); + +3. **Multiple Login URLs**: + + If you have multiple login URLs, add ``'urlChecker' => 'Authentication.Multi'`` + to your authenticator configuration. + +4. **Custom Identifier Setup**: + + If you were passing ``IdentifierCollection`` to authenticators, switch to + either passing a single identifier or null (to use defaults). + +5. **Test Thoroughly**: + + The changes to identifier management and URL checking are significant. + Test all authentication flows after upgrading. diff --git a/docs/en/url-checkers.rst b/docs/en/url-checkers.rst index f27a8532..fc6a33e2 100644 --- a/docs/en/url-checkers.rst +++ b/docs/en/url-checkers.rst @@ -1,9 +1,11 @@ URL Checkers ############ -To provide an abstract and framework agnostic solution there are URL -checkers implemented that allow you to customize the comparison of the -current URL if needed. For example to another frameworks routing. +There are URL checkers implemented that allow you to customize the comparison +of the current URL if needed. + +All checkers support single URLs in either string or array format (like ``Router::url()``). +For multiple login URLs, use ``MultiUrlChecker``. Included Checkers ================= @@ -11,41 +13,103 @@ Included Checkers DefaultUrlChecker ----------------- -The default checker allows you to compare an URL by regex or string -URLs. +The default URL checker. Supports both string URLs and CakePHP's array-based +routing notation. Uses CakePHP Router and works with named routes. + +Single URL (string): + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => '/users/login', + ]); + +Single URL (CakePHP route array): + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'loginUrl' => [ + 'prefix' => false, + 'plugin' => false, + 'controller' => 'Users', + 'action' => 'login', + ], + ]); + +Options: + +- **checkFullUrl**: To compare the full URL, including protocol, host + and port or not. Default is ``false``. + +StringUrlChecker +----------------- + +Checker for string URLs. Supports regex matching. + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.String', + 'loginUrl' => '/users/login', + ]); + +Using regex: + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => [ + 'className' => 'Authentication.String', + 'useRegex' => true, + ], + 'loginUrl' => '%^/[a-z]{2}/users/login/?$%', + ]); Options: - **checkFullUrl**: To compare the full URL, including protocol, host and port or not. Default is ``false`` - **useRegex**: Compares the URL by a regular expression provided in - the ``$loginUrls`` argument of the checker. + the ``loginUrl`` configuration. + +MultiUrlChecker +--------------- + +Use this checker when you need to support multiple login URLs (e.g., for multi-language sites). +You must explicitly configure this checker - it is not auto-detected. -CakeRouterUrlChecker --------------------- +Multiple string URLs: -Use this checker if you want to use the array notation of CakePHPs -routing system. The checker also works with named routes. +.. code-block:: php $service->loadAuthenticator('Authentication.Form', [ - 'urlChecker' => 'Authentication.CakeRouter', - 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + 'urlChecker' => 'Authentication.Multi', + 'loginUrl' => [ + '/en/users/login', + '/de/users/login', ], + ]); + +Multiple CakePHP route arrays: + +.. code-block:: php + + $service->loadAuthenticator('Authentication.Form', [ + 'urlChecker' => 'Authentication.Multi', 'loginUrl' => [ - 'prefix' => false, - 'plugin' => false, - 'controller' => 'Users', - 'action' => 'login', + ['lang' => 'en', 'controller' => 'Users', 'action' => 'login'], + ['lang' => 'de', 'controller' => 'Users', 'action' => 'login'], ], ]); Options: + - **checkFullUrl**: To compare the full URL, including protocol, host and port or not. Default is ``false`` +- **useRegex**: Compares URLs by regular expressions. Default is ``false`` Implementing your own Checker ----------------------------- -An URL checker **must** implement the ``UrlCheckerInterface``. +An URL checkers **must** implement the ``UrlCheckerInterface``. diff --git a/src/AuthenticationService.php b/src/AuthenticationService.php index c9d03513..2f526337 100644 --- a/src/AuthenticationService.php +++ b/src/AuthenticationService.php @@ -23,7 +23,6 @@ use Authentication\Authenticator\PersistenceInterface; use Authentication\Authenticator\ResultInterface; use Authentication\Authenticator\StatelessInterface; -use Authentication\Identifier\IdentifierCollection; use Authentication\Identifier\IdentifierInterface; use Cake\Core\InstanceConfigTrait; use Cake\Routing\Router; @@ -31,7 +30,6 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; -use function Cake\Core\deprecationWarning; /** * Authentication Service @@ -47,13 +45,6 @@ class AuthenticationService implements AuthenticationServiceInterface, Impersona */ protected ?AuthenticatorCollection $_authenticators = null; - /** - * Identifier collection - * - * @var \Authentication\Identifier\IdentifierCollection|null - */ - protected ?IdentifierCollection $_identifiers = null; - /** * Authenticator that successfully authenticated the identity. * @@ -73,10 +64,7 @@ class AuthenticationService implements AuthenticationServiceInterface, Impersona * * - `authenticators` - An array of authentication objects to use for authenticating users. * You can configure multiple adapters and they will be checked sequentially - * when users are identified. - * - `identifiers` - An array of identifiers. The identifiers are constructed by the service - * and then passed to the authenticators that will pass the credentials to them and get the - * user data. + * when users are identified. Each authenticator config can specify its own `identifier`. * - `identityClass` - The class name of identity or a callable identity builder. * - `identityAttribute` - The request attribute used to store the identity. Default to `identity`. * - `unauthenticatedRedirect` - The URL to redirect unauthenticated errors to. See @@ -112,7 +100,6 @@ class AuthenticationService implements AuthenticationServiceInterface, Impersona */ protected array $_defaultConfig = [ 'authenticators' => [], - 'identifiers' => [], 'identityClass' => Identity::class, 'identityAttribute' => 'identity', 'queryParam' => null, @@ -135,20 +122,6 @@ public function __construct(array $config = []) $this->setConfig($config); } - /** - * Access the identifier collection - * - * @return \Authentication\Identifier\IdentifierCollection - */ - public function identifiers(): IdentifierCollection - { - if ($this->_identifiers === null) { - $this->_identifiers = new IdentifierCollection($this->getConfig('identifiers')); - } - - return $this->_identifiers; - } - /** * Access the authenticator collection * @@ -157,9 +130,8 @@ public function identifiers(): IdentifierCollection public function authenticators(): AuthenticatorCollection { if ($this->_authenticators === null) { - $identifiers = $this->identifiers(); $authenticators = $this->getConfig('authenticators'); - $this->_authenticators = new AuthenticatorCollection($identifiers, $authenticators); + $this->_authenticators = new AuthenticatorCollection($authenticators); } return $this->_authenticators; @@ -177,24 +149,6 @@ public function loadAuthenticator(string $name, array $config = []): Authenticat return $this->authenticators()->load($name, $config); } - /** - * Loads an identifier. - * - * @param string $name Name or class name. - * @param array $config Identifier configuration. - * @return \Authentication\Identifier\IdentifierInterface Identifier instance - * @deprecated 3.3.0: loadIdentifier() usage is deprecated. Directly pass Identifier to Authenticator. - */ - public function loadIdentifier(string $name, array $config = []): IdentifierInterface - { - deprecationWarning( - '3.3.0', - 'loadIdentifier() usage is deprecated. Directly pass `\'identifier\'` config to the Authenticator.', - ); - - return $this->identifiers()->load($name, $config); - } - /** * {@inheritDoc} * @@ -309,12 +263,7 @@ public function getIdentificationProvider(): ?IdentifierInterface return null; } - $identifier = $this->_successfulAuthenticator->getIdentifier(); - if ($identifier instanceof IdentifierCollection) { - return $identifier->getIdentificationProvider(); - } - - return $identifier; + return $this->_successfulAuthenticator->getIdentifier(); } /** diff --git a/src/AuthenticationServiceInterface.php b/src/AuthenticationServiceInterface.php index ade15c28..570253ae 100644 --- a/src/AuthenticationServiceInterface.php +++ b/src/AuthenticationServiceInterface.php @@ -19,7 +19,6 @@ use Authentication\Authenticator\AuthenticatorInterface; use Authentication\Authenticator\PersistenceInterface; use Authentication\Authenticator\ResultInterface; -use Authentication\Identifier\IdentifierInterface; use Psr\Http\Message\ServerRequestInterface; interface AuthenticationServiceInterface extends PersistenceInterface @@ -33,16 +32,6 @@ interface AuthenticationServiceInterface extends PersistenceInterface */ public function loadAuthenticator(string $name, array $config = []): AuthenticatorInterface; - /** - * Loads an identifier. - * - * @param string $name Name or class name. - * @param array $config Identifier configuration. - * @return \Authentication\Identifier\IdentifierInterface - * @deprecated 3.3.0: loadIdentifier() usage is deprecated. Directly pass Identifier to Authenticator. - */ - public function loadIdentifier(string $name, array $config = []): IdentifierInterface; - /** * Authenticate the request against the configured authentication adapters. * diff --git a/src/Authenticator/AbstractAuthenticator.php b/src/Authenticator/AbstractAuthenticator.php index ad53b741..a384bcb4 100644 --- a/src/Authenticator/AbstractAuthenticator.php +++ b/src/Authenticator/AbstractAuthenticator.php @@ -16,10 +16,11 @@ */ namespace Authentication\Authenticator; -use Authentication\Identifier\AbstractIdentifier; use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Cake\Core\InstanceConfigTrait; use Psr\Http\Message\ServerRequestInterface; +use RuntimeException; abstract class AbstractAuthenticator implements AuthenticatorInterface { @@ -33,25 +34,25 @@ abstract class AbstractAuthenticator implements AuthenticatorInterface */ protected array $_defaultConfig = [ 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'username', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'username', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ], ]; /** - * Identifier or identifiers collection. + * Identifier instance. * - * @var \Authentication\Identifier\IdentifierInterface + * @var \Authentication\Identifier\IdentifierInterface|null */ - protected IdentifierInterface $_identifier; + protected ?IdentifierInterface $_identifier = null; /** * Constructor * - * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier or identifiers collection. + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. * @param array $config Configuration settings. */ - public function __construct(IdentifierInterface $identifier, array $config = []) + public function __construct(?IdentifierInterface $identifier, array $config = []) { $this->_identifier = $identifier; $this->setConfig($config); @@ -60,10 +61,23 @@ public function __construct(IdentifierInterface $identifier, array $config = []) /** * Gets the identifier. * + * Subclasses can override this method to provide a default identifier + * when none was configured, enabling lazy initialization. + * * @return \Authentication\Identifier\IdentifierInterface + * @throws \RuntimeException When identifier is null. */ public function getIdentifier(): IdentifierInterface { + if ($this->_identifier === null) { + throw new RuntimeException( + sprintf( + 'Identifier is required for `%s`. Please provide an identifier instance.', + static::class, + ), + ); + } + return $this->_identifier; } diff --git a/src/Authenticator/AuthenticatorCollection.php b/src/Authenticator/AuthenticatorCollection.php index 5e4249d0..461a6600 100644 --- a/src/Authenticator/AuthenticatorCollection.php +++ b/src/Authenticator/AuthenticatorCollection.php @@ -17,42 +17,15 @@ namespace Authentication\Authenticator; use Authentication\AbstractCollection; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Cake\Core\App; use RuntimeException; -use function Cake\Core\deprecationWarning; /** * @extends \Authentication\AbstractCollection<\Authentication\Authenticator\AuthenticatorInterface> */ class AuthenticatorCollection extends AbstractCollection { - /** - * Identifier collection. - * - * @var \Authentication\Identifier\IdentifierCollection - */ - protected IdentifierCollection $_identifiers; - - /** - * Constructor. - * - * @param \Authentication\Identifier\IdentifierCollection $identifiers Identifiers collection. - * @param array $config Config array. - */ - public function __construct(IdentifierCollection $identifiers, array $config = []) - { - $this->_identifiers = $identifiers; - if ($identifiers->count() > 0) { - deprecationWarning( - '3.3.0', - 'loadIdentifier() usage is deprecated. Directly pass `\'identifier\'` config to the Authenticator.', - ); - } - - parent::__construct($config); - } - /** * Creates authenticator instance. * @@ -65,11 +38,12 @@ public function __construct(IdentifierCollection $identifiers, array $config = [ protected function _create(object|string $class, string $alias, array $config): AuthenticatorInterface { if (is_string($class)) { + $identifier = null; if (!empty($config['identifier'])) { - $this->_identifiers = new IdentifierCollection((array)$config['identifier']); + $identifier = IdentifierFactory::create($config['identifier']); } - return new $class($this->_identifiers, $config); + return new $class($identifier, $config); } return $class; diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index 580aec6b..a95a9ab9 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -17,9 +17,9 @@ namespace Authentication\Authenticator; use ArrayAccess; -use Authentication\Identifier\AbstractIdentifier; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Authentication\PasswordHasher\PasswordHasherTrait; use Authentication\UrlChecker\UrlCheckerTrait; use Cake\Http\Cookie\Cookie; @@ -47,8 +47,8 @@ class CookieAuthenticator extends AbstractAuthenticator implements PersistenceIn 'urlChecker' => 'Authentication.Default', 'rememberMeField' => 'remember_me', 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'username', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'username', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ], 'cookie' => [ 'name' => 'CookieAuth', @@ -60,18 +60,18 @@ class CookieAuthenticator extends AbstractAuthenticator implements PersistenceIn /** * Gets the identifier, loading a default Password identifier if none configured. * - * This is done lazily to allow loadIdentifier() to be called after loadAuthenticator(). + * This is done lazily to allow configuration to be fully set before creating the identifier. * * @return \Authentication\Identifier\IdentifierInterface */ public function getIdentifier(): IdentifierInterface { - if ($this->_identifier instanceof IdentifierCollection && $this->_identifier->isEmpty()) { + if ($this->_identifier === null) { $identifierConfig = []; if ($this->getConfig('fields')) { $identifierConfig['fields'] = $this->getConfig('fields'); } - $this->_identifier->load('Authentication.Password', $identifierConfig); + $this->_identifier = IdentifierFactory::create('Authentication.Password', $identifierConfig); } return $this->_identifier; diff --git a/src/Authenticator/EnvironmentAuthenticator.php b/src/Authenticator/EnvironmentAuthenticator.php index 40959a00..d0b0f370 100644 --- a/src/Authenticator/EnvironmentAuthenticator.php +++ b/src/Authenticator/EnvironmentAuthenticator.php @@ -16,6 +16,8 @@ */ namespace Authentication\Authenticator; +use Authentication\Identifier\IdentifierFactory; +use Authentication\Identifier\IdentifierInterface; use Authentication\UrlChecker\UrlCheckerTrait; use Cake\Routing\Router; use Psr\Http\Message\ServerRequestInterface; @@ -45,6 +47,19 @@ class EnvironmentAuthenticator extends AbstractAuthenticator 'optionalFields' => [], ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. + * @param array $config Configuration settings. + */ + public function __construct(?IdentifierInterface $identifier, array $config = []) + { + $identifier ??= IdentifierFactory::create('Authentication.Callback'); + + parent::__construct($identifier, $config); + } + /** * Get values from the environment variables configured by `fields`. * @@ -153,10 +168,11 @@ public function authenticate(ServerRequestInterface $request): ResultInterface $data = array_merge($this->_getOptionalData($request), $data); - $user = $this->_identifier->identify($data); + $identifier = $this->getIdentifier(); + $user = $identifier->identify($data); if (!$user) { - return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND, $this->_identifier->getErrors()); + return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND, $identifier->getErrors()); } return new Result($user, Result::SUCCESS); diff --git a/src/Authenticator/FormAuthenticator.php b/src/Authenticator/FormAuthenticator.php index 781b4e70..0c48a0b9 100644 --- a/src/Authenticator/FormAuthenticator.php +++ b/src/Authenticator/FormAuthenticator.php @@ -16,9 +16,9 @@ */ namespace Authentication\Authenticator; -use Authentication\Identifier\AbstractIdentifier; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Authentication\UrlChecker\UrlCheckerTrait; use Cake\Routing\Router; use Psr\Http\Message\ServerRequestInterface; @@ -42,28 +42,28 @@ class FormAuthenticator extends AbstractAuthenticator */ protected array $_defaultConfig = [ 'loginUrl' => null, - 'urlChecker' => 'Authentication.Default', + 'urlChecker' => null, 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'username', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'username', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ], ]; /** * Gets the identifier, loading a default Password identifier if none configured. * - * This is done lazily to allow loadIdentifier() to be called after loadAuthenticator(). + * This is done lazily to allow configuration to be fully set before creating the identifier. * * @return \Authentication\Identifier\IdentifierInterface */ public function getIdentifier(): IdentifierInterface { - if ($this->_identifier instanceof IdentifierCollection && $this->_identifier->isEmpty()) { + if ($this->_identifier === null) { $identifierConfig = []; if ($this->getConfig('fields')) { $identifierConfig['fields'] = $this->getConfig('fields'); } - $this->_identifier->load('Authentication.Password', $identifierConfig); + $this->_identifier = IdentifierFactory::create('Authentication.Password', $identifierConfig); } return $this->_identifier; diff --git a/src/Authenticator/HttpBasicAuthenticator.php b/src/Authenticator/HttpBasicAuthenticator.php index 7d5c2b8d..ccda9ded 100644 --- a/src/Authenticator/HttpBasicAuthenticator.php +++ b/src/Authenticator/HttpBasicAuthenticator.php @@ -15,9 +15,9 @@ */ namespace Authentication\Authenticator; -use Authentication\Identifier\AbstractIdentifier; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Psr\Http\Message\ServerRequestInterface; /** @@ -37,8 +37,8 @@ class HttpBasicAuthenticator extends AbstractAuthenticator implements StatelessI */ protected array $_defaultConfig = [ 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'username', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'username', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ], 'skipChallenge' => false, ]; @@ -46,18 +46,18 @@ class HttpBasicAuthenticator extends AbstractAuthenticator implements StatelessI /** * Gets the identifier, loading a default Password identifier if none configured. * - * This is done lazily to allow loadIdentifier() to be called after loadAuthenticator(). + * This is done lazily to allow configuration to be fully set before creating the identifier. * * @return \Authentication\Identifier\IdentifierInterface */ public function getIdentifier(): IdentifierInterface { - if ($this->_identifier instanceof IdentifierCollection && $this->_identifier->isEmpty()) { + if ($this->_identifier === null) { $identifierConfig = []; if ($this->getConfig('fields')) { $identifierConfig['fields'] = $this->getConfig('fields'); } - $this->_identifier->load('Authentication.Password', $identifierConfig); + $this->_identifier = IdentifierFactory::create('Authentication.Password', $identifierConfig); } return $this->_identifier; @@ -81,8 +81,8 @@ public function authenticate(ServerRequestInterface $request): ResultInterface } $user = $this->getIdentifier()->identify([ - AbstractIdentifier::CREDENTIAL_USERNAME => $username, - AbstractIdentifier::CREDENTIAL_PASSWORD => $password, + PasswordIdentifier::CREDENTIAL_USERNAME => $username, + PasswordIdentifier::CREDENTIAL_PASSWORD => $password, ]); if ($user === null) { diff --git a/src/Authenticator/HttpDigestAuthenticator.php b/src/Authenticator/HttpDigestAuthenticator.php index 0647b8ea..340c2cb7 100644 --- a/src/Authenticator/HttpDigestAuthenticator.php +++ b/src/Authenticator/HttpDigestAuthenticator.php @@ -15,8 +15,8 @@ */ namespace Authentication\Authenticator; -use Authentication\Identifier\AbstractIdentifier; use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Cake\Utility\Security; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; @@ -55,10 +55,10 @@ class HttpDigestAuthenticator extends HttpBasicAuthenticator * - `opaque` A string that must be returned unchanged by clients. * Defaults to `md5($config['realm'])` * - * @param \Authentication\Identifier\IdentifierInterface $identifier Identifier instance. + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. * @param array $config Configuration settings. */ - public function __construct(IdentifierInterface $identifier, array $config = []) + public function __construct(?IdentifierInterface $identifier, array $config = []) { $secret = ''; if (class_exists(Security::class)) { @@ -94,8 +94,8 @@ public function authenticate(ServerRequestInterface $request): ResultInterface return new Result(null, Result::FAILURE_CREDENTIALS_MISSING); } - $user = $this->_identifier->identify([ - AbstractIdentifier::CREDENTIAL_USERNAME => $digest['username'], + $user = $this->getIdentifier()->identify([ + PasswordIdentifier::CREDENTIAL_USERNAME => $digest['username'], ]); if (!$user) { @@ -106,7 +106,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface return new Result(null, Result::FAILURE_CREDENTIALS_INVALID); } - $field = $this->_config['fields'][AbstractIdentifier::CREDENTIAL_PASSWORD]; + $field = $this->_config['fields'][PasswordIdentifier::CREDENTIAL_PASSWORD]; $password = $user[$field]; $server = $request->getServerParams(); diff --git a/src/Authenticator/JwtAuthenticator.php b/src/Authenticator/JwtAuthenticator.php index 3ce212e3..56b0e8d9 100644 --- a/src/Authenticator/JwtAuthenticator.php +++ b/src/Authenticator/JwtAuthenticator.php @@ -17,7 +17,7 @@ namespace Authentication\Authenticator; use ArrayObject; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; use Authentication\Identifier\JwtSubjectIdentifier; use Cake\Utility\Security; @@ -55,7 +55,7 @@ class JwtAuthenticator extends TokenAuthenticator /** * @inheritDoc */ - public function __construct(IdentifierInterface $identifier, array $config = []) + public function __construct(?IdentifierInterface $identifier, array $config = []) { parent::__construct($identifier, $config); @@ -70,17 +70,13 @@ public function __construct(IdentifierInterface $identifier, array $config = []) /** * Gets the identifier, loading a default JwtSubject identifier if none configured. * - * This is done lazily to allow loadIdentifier() to be called after loadAuthenticator(). + * This is done lazily to allow configuration to be fully set before creating the identifier. * * @return \Authentication\Identifier\IdentifierInterface */ public function getIdentifier(): IdentifierInterface { - if ($this->_identifier instanceof IdentifierCollection && $this->_identifier->isEmpty()) { - $this->_identifier->load('Authentication.JwtSubject'); - } - - return $this->_identifier; + return $this->_identifier ??= IdentifierFactory::create('Authentication.JwtSubject'); } /** @@ -109,8 +105,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface return new Result(null, Result::FAILURE_CREDENTIALS_INVALID); } - /** @phpstan-ignore-next-line */ - $result = json_decode(json_encode($result), true); + $result = json_decode((string)json_encode($result), true); $subjectKey = $this->getConfig('subjectKey'); if (empty($result[$subjectKey])) { diff --git a/src/Authenticator/PrimaryKeySessionAuthenticator.php b/src/Authenticator/PrimaryKeySessionAuthenticator.php index 0de92fbd..a55e8bb6 100644 --- a/src/Authenticator/PrimaryKeySessionAuthenticator.php +++ b/src/Authenticator/PrimaryKeySessionAuthenticator.php @@ -15,10 +15,10 @@ class PrimaryKeySessionAuthenticator extends SessionAuthenticator { /** - * @param \Authentication\Identifier\IdentifierInterface $identifier + * @param \Authentication\Identifier\IdentifierInterface|null $identifier * @param array $config */ - public function __construct(IdentifierInterface $identifier, array $config = []) + public function __construct(?IdentifierInterface $identifier, array $config = []) { $config += [ 'identifierKey' => 'key', @@ -45,7 +45,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND); } - $user = $this->_identifier->identify([$this->getConfig('identifierKey') => $userId]); + $user = $this->getIdentifier()->identify([$this->getConfig('identifierKey') => $userId]); if (!$user) { return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND); } diff --git a/src/Authenticator/SessionAuthenticator.php b/src/Authenticator/SessionAuthenticator.php index a6c98dea..e5b573ee 100644 --- a/src/Authenticator/SessionAuthenticator.php +++ b/src/Authenticator/SessionAuthenticator.php @@ -17,7 +17,9 @@ use ArrayAccess; use ArrayObject; -use Authentication\Identifier\AbstractIdentifier; +use Authentication\Identifier\IdentifierFactory; +use Authentication\Identifier\IdentifierInterface; +use Authentication\Identifier\PasswordIdentifier; use Cake\Http\Exception\UnauthorizedException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -39,7 +41,7 @@ class SessionAuthenticator extends AbstractAuthenticator implements PersistenceI */ protected array $_defaultConfig = [ 'fields' => [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'username', + PasswordIdentifier::CREDENTIAL_USERNAME => 'username', ], 'sessionKey' => 'Auth', 'impersonateSessionKey' => 'AuthImpersonate', @@ -47,6 +49,25 @@ class SessionAuthenticator extends AbstractAuthenticator implements PersistenceI 'identityAttribute' => 'identity', ]; + /** + * Constructor + * + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. + * @param array $config Configuration settings. + */ + public function __construct(?IdentifierInterface $identifier, array $config = []) + { + if ($identifier === null) { + $identifierConfig = []; + if (isset($config['fields'])) { + $identifierConfig['fields'] = $config['fields']; + } + $identifier = IdentifierFactory::create('Authentication.Password', $identifierConfig); + } + + parent::__construct($identifier, $config); + } + /** * Authenticate a user using session data. * @@ -69,7 +90,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface foreach ($this->getConfig('fields') as $key => $field) { $credentials[$key] = $user[$field]; } - $user = $this->_identifier->identify($credentials); + $user = $this->getIdentifier()->identify($credentials); if (!$user) { return new Result(null, Result::FAILURE_CREDENTIALS_INVALID); diff --git a/src/Authenticator/TokenAuthenticator.php b/src/Authenticator/TokenAuthenticator.php index 3751ac82..b2afbc36 100644 --- a/src/Authenticator/TokenAuthenticator.php +++ b/src/Authenticator/TokenAuthenticator.php @@ -16,7 +16,7 @@ */ namespace Authentication\Authenticator; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; use Authentication\Identifier\TokenIdentifier; use Psr\Http\Message\ServerRequestInterface; @@ -40,17 +40,13 @@ class TokenAuthenticator extends AbstractAuthenticator implements StatelessInter /** * Gets the identifier, loading a default Token identifier if none configured. * - * This is done lazily to allow loadIdentifier() to be called after loadAuthenticator(). + * This is done lazily to allow configuration to be fully set before creating the identifier. * * @return \Authentication\Identifier\IdentifierInterface */ public function getIdentifier(): IdentifierInterface { - if ($this->_identifier instanceof IdentifierCollection && $this->_identifier->isEmpty()) { - $this->_identifier->load('Authentication.Token'); - } - - return $this->_identifier; + return $this->_identifier ??= IdentifierFactory::create('Authentication.Token'); } /** diff --git a/src/Identifier/AbstractIdentifier.php b/src/Identifier/AbstractIdentifier.php index e2e36e36..f8aa9c79 100644 --- a/src/Identifier/AbstractIdentifier.php +++ b/src/Identifier/AbstractIdentifier.php @@ -22,10 +22,6 @@ abstract class AbstractIdentifier implements IdentifierInterface { use InstanceConfigTrait; - public const CREDENTIAL_USERNAME = 'username'; - - public const CREDENTIAL_PASSWORD = 'password'; - /** * Default configuration * diff --git a/src/Identifier/IdentifierCollection.php b/src/Identifier/IdentifierCollection.php deleted file mode 100644 index 257b25d8..00000000 --- a/src/Identifier/IdentifierCollection.php +++ /dev/null @@ -1,132 +0,0 @@ - - */ -class IdentifierCollection extends AbstractCollection implements IdentifierInterface -{ - /** - * Errors - * - * @var array - */ - protected array $_errors = []; - - /** - * Identifier that successfully Identified the identity. - * - * @var \Authentication\Identifier\IdentifierInterface|null - */ - protected ?IdentifierInterface $_successfulIdentifier = null; - - /** - * Identifies an user or service by the passed credentials - * - * @param array $credentials Authentication credentials - * @return \ArrayAccess|array|null - */ - public function identify(array $credentials): ArrayAccess|array|null - { - /** @var \Authentication\Identifier\IdentifierInterface $identifier */ - foreach ($this->_loaded as $name => $identifier) { - $result = $identifier->identify($credentials); - if ($result) { - $this->_successfulIdentifier = $identifier; - - return $result; - } - - $errors = $identifier->getErrors(); - if ($errors) { - $this->_errors[$name] = $identifier->getErrors(); - } - } - - $this->_successfulIdentifier = null; - - return null; - } - - /** - * Creates identifier instance. - * - * @param \Authentication\Identifier\IdentifierInterface|class-string<\Authentication\Identifier\IdentifierInterface> $class Identifier class. - * @param string $alias Identifier alias. - * @param array $config Config array. - * @return \Authentication\Identifier\IdentifierInterface - * @throws \RuntimeException - */ - protected function _create(object|string $class, string $alias, array $config): IdentifierInterface - { - if (is_object($class)) { - return $class; - } - - return new $class($config); - } - - /** - * Get errors - * - * @return array - */ - public function getErrors(): array - { - return $this->_errors; - } - - /** - * Resolves identifier class name. - * - * @param string $class Class name to be resolved. - * @return class-string<\Authentication\Identifier\IdentifierInterface>|null - */ - protected function _resolveClassName(string $class): ?string - { - /** @var class-string<\Authentication\Identifier\IdentifierInterface>|null */ - return App::className($class, 'Identifier', 'Identifier'); - } - - /** - * @param string $class Missing class. - * @param string $plugin Class plugin. - * @return void - * @throws \RuntimeException - */ - protected function _throwMissingClassError(string $class, ?string $plugin): void - { - $message = sprintf('Identifier class `%s` was not found.', $class); - throw new RuntimeException($message); - } - - /** - * Gets the successful identifier instance if one was successful after calling identify. - * - * @return \Authentication\Identifier\IdentifierInterface|null - */ - public function getIdentificationProvider(): ?IdentifierInterface - { - return $this->_successfulIdentifier; - } -} diff --git a/src/Identifier/IdentifierFactory.php b/src/Identifier/IdentifierFactory.php new file mode 100644 index 00000000..83161dfc --- /dev/null +++ b/src/Identifier/IdentifierFactory.php @@ -0,0 +1,66 @@ +|string $config Identifier configuration. + * Can be a class name string, an instance, or an array with 'className' key. + * @param array $defaultConfig Default configuration to merge. + * @return \Authentication\Identifier\IdentifierInterface + * @throws \RuntimeException When the identifier class cannot be found or created. + */ + public static function create( + string|array|IdentifierInterface $config, + array $defaultConfig = [], + ): IdentifierInterface { + if ($config instanceof IdentifierInterface) { + return $config; + } + + if (is_string($config)) { + $className = $config; + $config = []; + } else { + $className = $config['className'] ?? ''; + unset($config['className']); + } + + if (empty($className)) { + throw new RuntimeException('Identifier configuration must specify a class name.'); + } + + $config += $defaultConfig; + + /** @var class-string<\Authentication\Identifier\IdentifierInterface>|null $class */ + $class = App::className($className, 'Identifier', 'Identifier'); + if ($class === null) { + throw new RuntimeException(sprintf('Identifier class `%s` was not found.', $className)); + } + + return new $class($config); + } +} diff --git a/src/Identifier/IdentifierInterface.php b/src/Identifier/IdentifierInterface.php index adf29c22..7883fb72 100644 --- a/src/Identifier/IdentifierInterface.php +++ b/src/Identifier/IdentifierInterface.php @@ -21,7 +21,7 @@ interface IdentifierInterface { /** - * Identifies an user or service by the passed credentials + * Identifies a user or service by the passed credentials * * @param array $credentials Authentication credentials * @return \ArrayAccess|array|null diff --git a/src/Identifier/LdapIdentifier.php b/src/Identifier/LdapIdentifier.php index 70398149..c80076d2 100644 --- a/src/Identifier/LdapIdentifier.php +++ b/src/Identifier/LdapIdentifier.php @@ -46,6 +46,10 @@ */ class LdapIdentifier extends AbstractIdentifier { + public const CREDENTIAL_USERNAME = 'username'; + + public const CREDENTIAL_PASSWORD = 'password'; + /** * Default configuration * diff --git a/src/Identifier/PasswordIdentifier.php b/src/Identifier/PasswordIdentifier.php index 45c39353..964f352e 100644 --- a/src/Identifier/PasswordIdentifier.php +++ b/src/Identifier/PasswordIdentifier.php @@ -47,6 +47,10 @@ class PasswordIdentifier extends AbstractIdentifier } use ResolverAwareTrait; + public const CREDENTIAL_USERNAME = 'username'; + + public const CREDENTIAL_PASSWORD = 'password'; + /** * Default configuration. * - `fields` The fields to use to identify a user by: diff --git a/src/Plugin.php b/src/Plugin.php index 40c4da55..6c4d5ea3 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -16,7 +16,9 @@ namespace Authentication; /** - * @deprecated 3.3.4 Use AuthenticationPlugin instead + * Backwards-compatible alias for AuthenticationPlugin. + * + * @deprecated 4.0.0 Use AuthenticationPlugin instead. */ class Plugin extends AuthenticationPlugin { diff --git a/src/UrlChecker/CakeRouterUrlChecker.php b/src/UrlChecker/CakeRouterUrlChecker.php deleted file mode 100644 index 401c85f2..00000000 --- a/src/UrlChecker/CakeRouterUrlChecker.php +++ /dev/null @@ -1,66 +0,0 @@ - false, - ]; - - /** - * @inheritDoc - */ - public function check(ServerRequestInterface $request, $loginUrls, array $options = []): bool - { - $options = $this->_mergeDefaultOptions($options); - $url = $this->_getUrlFromRequest($request, $options['checkFullUrl']); - - if (!is_array($loginUrls) || empty($loginUrls)) { - throw new InvalidArgumentException('The $loginUrls parameter is empty or not of type array.'); - } - - // If it's a single route array add to another - if (!is_numeric(key($loginUrls))) { - $loginUrls = [$loginUrls]; - } - - foreach ($loginUrls as $validUrl) { - $validUrl = Router::url($validUrl, $options['checkFullUrl']); - - if ($validUrl === $url) { - return true; - } - } - - return false; - } -} diff --git a/src/UrlChecker/DefaultUrlChecker.php b/src/UrlChecker/DefaultUrlChecker.php index f148a02f..97e7dec9 100644 --- a/src/UrlChecker/DefaultUrlChecker.php +++ b/src/UrlChecker/DefaultUrlChecker.php @@ -16,83 +16,50 @@ */ namespace Authentication\UrlChecker; +use Cake\Routing\Router; use Psr\Http\Message\ServerRequestInterface; /** - * Checks if a request object contains a valid URL + * Default URL checker for CakePHP applications. Uses CakePHP Router. */ class DefaultUrlChecker implements UrlCheckerInterface { /** * Default Options * - * - `urlChecker` Whether to use `loginUrl` as regular expression(s). * - `checkFullUrl` Whether to check the full request URI. * - * @var array + * @var array */ protected array $_defaultOptions = [ - 'useRegex' => false, 'checkFullUrl' => false, ]; /** * @inheritDoc */ - public function check(ServerRequestInterface $request, $loginUrls, array $options = []): bool + public function check(ServerRequestInterface $request, array|string $loginUrls, array $options = []): bool { $options = $this->_mergeDefaultOptions($options); - - $urls = (array)$loginUrls; - if (!$urls) { - return true; - } - - $checker = $this->_getChecker($options); - $url = $this->_getUrlFromRequest($request, $options['checkFullUrl']); - foreach ($urls as $validUrl) { - if ($checker($validUrl, $url)) { - return true; - } - } + // Support both string URLs and array-based routes (like Router::url()) + $validUrl = Router::url($loginUrls, $options['checkFullUrl']); - return false; + return $validUrl === $url; } /** * Merges given options with the defaults. * - * The reason this method exists is that it makes it easy to override the - * method and inject additional options without the need to use the - * MergeVarsTrait. - * * @param array $options Options to merge in - * @return array + * @return array */ protected function _mergeDefaultOptions(array $options): array { return $options + $this->_defaultOptions; } - /** - * Gets the checker function name or a callback - * - * @param array $options Array of options - * @return callable - */ - protected function _getChecker(array $options): callable - { - if (!empty($options['useRegex'])) { - return 'preg_match'; - } - - return function ($validUrl, $url) { - return $validUrl === $url; - }; - } - /** * Returns current url. * diff --git a/src/UrlChecker/MultiUrlChecker.php b/src/UrlChecker/MultiUrlChecker.php new file mode 100644 index 00000000..ea525fdd --- /dev/null +++ b/src/UrlChecker/MultiUrlChecker.php @@ -0,0 +1,118 @@ + + */ + protected array $_defaultOptions = [ + 'useRegex' => false, + 'checkFullUrl' => false, + ]; + + /** + * @inheritDoc + */ + public function check(ServerRequestInterface $request, array|string $loginUrls, array $options = []): bool + { + $options = $this->_mergeDefaultOptions($options); + + // For a single URL (string or array route), convert to array + if (is_string($loginUrls) || $this->_isSingleRoute($loginUrls)) { + $urls = [$loginUrls]; + } else { + $urls = $loginUrls; + } + + if (!$urls) { + return true; + } + + foreach ($urls as $url) { + if ($this->_checkSingleUrl($request, $url, $options)) { + return true; + } + } + + return false; + } + + /** + * Check if the array is a single CakePHP route (not an array of routes) + * + * @param array|string $value The value to check + * @return bool + */ + protected function _isSingleRoute(array|string $value): bool + { + if (!is_array($value)) { + return false; + } + + if (!$value) { + return false; + } + + // A single route has string keys like ['controller' => 'Users'] + // An array of routes has numeric keys [0 => '/login', 1 => '/signin'] + reset($value); + $firstKey = key($value); + + return !is_int($firstKey); + } + + /** + * Check a single URL + * + * @param \Psr\Http\Message\ServerRequestInterface $request The request. + * @param array|string $url The URL to check (can be string or array). + * @param array $options Options array. + * @return bool + */ + protected function _checkSingleUrl(ServerRequestInterface $request, array|string $url, array $options): bool + { + $checker = new DefaultUrlChecker(); + + return $checker->check($request, $url, $options); + } + + /** + * Merge default options with provided options + * + * @param array $options The options to merge. + * @return array + */ + protected function _mergeDefaultOptions(array $options): array + { + return $options + $this->_defaultOptions; + } +} diff --git a/src/UrlChecker/StringUrlChecker.php b/src/UrlChecker/StringUrlChecker.php new file mode 100644 index 00000000..15f3ac9c --- /dev/null +++ b/src/UrlChecker/StringUrlChecker.php @@ -0,0 +1,112 @@ + + */ + protected array $_defaultOptions = [ + 'useRegex' => false, + 'checkFullUrl' => false, + ]; + + /** + * @inheritDoc + */ + public function check(ServerRequestInterface $request, array|string $loginUrls, array $options = []): bool + { + if (is_array($loginUrls)) { + throw new RuntimeException( + 'Array-based login URLs require CakePHP Router and DefaultUrlChecker.', + ); + } + + $options = $this->_mergeDefaultOptions($options); + $checker = $this->_getChecker($options); + $url = $this->_getUrlFromRequest($request, $options['checkFullUrl']); + + return (bool)$checker($loginUrls, $url); + } + + /** + * Merges given options with the defaults. + * + * The reason this method exists is that it makes it easy to override the + * method and inject additional options without the need to use the + * MergeVarsTrait. + * + * @param array $options Options to merge in + * @return array + */ + protected function _mergeDefaultOptions(array $options): array + { + return $options + $this->_defaultOptions; + } + + /** + * Gets the checker function name or a callback + * + * @param array $options Array of options + * @return callable + */ + protected function _getChecker(array $options): callable + { + if (!empty($options['useRegex'])) { + return 'preg_match'; + } + + return function ($validUrl, $url) { + return $validUrl === $url; + }; + } + + /** + * Returns current url. + * + * @param \Psr\Http\Message\ServerRequestInterface $request Server Request + * @param bool $getFullUrl Get the full URL or just the path + * @return string + */ + protected function _getUrlFromRequest(ServerRequestInterface $request, bool $getFullUrl = false): string + { + $uri = $request->getUri(); + + $requestBase = $request->getAttribute('base'); + if ($requestBase) { + $uri = $uri->withPath($requestBase . $uri->getPath()); + } + + if ($getFullUrl) { + return (string)$uri; + } + + return $uri->getPath(); + } +} diff --git a/src/UrlChecker/UrlCheckerTrait.php b/src/UrlChecker/UrlCheckerTrait.php index 36fe7be8..d6d6758f 100644 --- a/src/UrlChecker/UrlCheckerTrait.php +++ b/src/UrlChecker/UrlCheckerTrait.php @@ -33,9 +33,14 @@ trait UrlCheckerTrait */ protected function _checkUrl(ServerRequestInterface $request): bool { + $loginUrl = $this->getConfig('loginUrl'); + if ($loginUrl === null) { + return true; + } + return $this->_getUrlChecker()->check( $request, - $this->getConfig('loginUrl'), + $loginUrl, (array)$this->getConfig('urlChecker'), ); } @@ -48,12 +53,15 @@ protected function _checkUrl(ServerRequestInterface $request): bool protected function _getUrlChecker(): UrlCheckerInterface { $options = $this->getConfig('urlChecker'); + if (!is_array($options)) { $options = [ 'className' => $options, ]; } - if (!isset($options['className'])) { + + // If no explicit className is set (or it's null/empty), use DefaultUrlChecker + if (empty($options['className'])) { $options['className'] = DefaultUrlChecker::class; } diff --git a/tests/TestCase/AuthenticationServiceTest.php b/tests/TestCase/AuthenticationServiceTest.php index b6e9713e..960af304 100644 --- a/tests/TestCase/AuthenticationServiceTest.php +++ b/tests/TestCase/AuthenticationServiceTest.php @@ -22,7 +22,6 @@ use Authentication\Authenticator\AuthenticatorInterface; use Authentication\Authenticator\FormAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; use Authentication\Identifier\PasswordIdentifier; use Authentication\Identity; use Authentication\IdentityInterface; @@ -34,7 +33,6 @@ use Cake\I18n\DateTime; use Cake\Routing\Router; use InvalidArgumentException; -use PHPUnit\Runner\Version; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -226,37 +224,6 @@ public function testLoadAuthenticatorException() $service->loadAuthenticator('does-not-exist'); } - /** - * testLoadIdentifier - * - * @return void - */ - public function testLoadIdentifier() - { - $this->skipIf( - version_compare(Version::id(), '11.0', '<'), - 'For some reason PHPUnit doesn\'t pick up the deprecation on v10', - ); - - $this->deprecated(function () { - $service = new AuthenticationService(); - $result = $service->loadIdentifier('Authentication.Password'); - $this->assertInstanceOf(PasswordIdentifier::class, $result); - }); - } - - /** - * testIdentifiers - * - * @return void - */ - public function testIdentifiers() - { - $service = new AuthenticationService(); - $result = $service->identifiers(); - $this->assertInstanceOf(IdentifierCollection::class, $result); - } - /** * testClearIdentity * @@ -1373,62 +1340,4 @@ public function testFormAuthenticatorDefaultIdentifier() $authenticator = $service->getAuthenticationProvider(); $this->assertInstanceOf(FormAuthenticator::class, $authenticator); } - - /** - * Test that loadIdentifier called after loadAuthenticator still works. - * - * This is a regression test for https://github.com/cakephp/authentication/issues/754 - * When loadAuthenticator was called before loadIdentifier, the authenticator would - * create its own default identifier collection and ignore the later loadIdentifier call. - * - * @deprecated Note that this test will be removed in 4.x as this is only to keep BC in 3.x. - * @return void - */ - public function testLoadIdentifierAfterLoadAuthenticator() - { - $this->skipIf( - version_compare(Version::id(), '11.0', '<'), - 'For some reason PHPUnit doesn\'t pick up the deprecation on v10', - ); - - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'], - [], - ['username' => 'mariano', 'password' => 'password'], - ); - - $service = new AuthenticationService(); - - // Load authenticator FIRST - $service->loadAuthenticator('Authentication.Form'); - - // Then load identifier with custom resolver config - $this->deprecated(function () use ($service) { - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'AuthUsers', - 'finder' => 'auth', - ], - ]); - }); - - // The authenticator should use the identifier we loaded, not its default - $formAuth = $service->authenticators()->get('Form'); - $identifierCollection = $formAuth->getIdentifier(); - $this->assertInstanceOf(IdentifierCollection::class, $identifierCollection); - - $passwordIdentifier = $identifierCollection->get('Password'); - $this->assertInstanceOf(PasswordIdentifier::class, $passwordIdentifier); - - // Verify the resolver config was applied - $resolverConfig = $passwordIdentifier->getConfig('resolver'); - $this->assertIsArray($resolverConfig); - $this->assertSame('AuthUsers', $resolverConfig['userModel']); - $this->assertSame('auth', $resolverConfig['finder']); - - // Verify authentication actually works with the custom config - $result = $service->authenticate($request); - $this->assertTrue($result->isValid()); - } } diff --git a/tests/TestCase/Authenticator/AuthenticatorCollectionTest.php b/tests/TestCase/Authenticator/AuthenticatorCollectionTest.php index c1565955..cadafabb 100644 --- a/tests/TestCase/Authenticator/AuthenticatorCollectionTest.php +++ b/tests/TestCase/Authenticator/AuthenticatorCollectionTest.php @@ -19,7 +19,6 @@ use Authentication\Authenticator\AuthenticatorCollection; use Authentication\Authenticator\AuthenticatorInterface; use Authentication\Authenticator\FormAuthenticator; -use Authentication\Identifier\IdentifierCollection; use Cake\TestSuite\TestCase; class AuthenticatorCollectionTest extends TestCase @@ -31,8 +30,7 @@ class AuthenticatorCollectionTest extends TestCase */ public function testConstruct() { - $identifiers = $this->createMock(IdentifierCollection::class); - $collection = new AuthenticatorCollection($identifiers, [ + $collection = new AuthenticatorCollection([ 'Authentication.Form' => [ 'identifier' => 'Authentication.Password', ], @@ -48,8 +46,7 @@ public function testConstruct() */ public function testLoad() { - $identifiers = $this->createMock(IdentifierCollection::class); - $collection = new AuthenticatorCollection($identifiers); + $collection = new AuthenticatorCollection(); $result = $collection->load('Authentication.Form', [ 'identifier' => 'Authentication.Password', ]); @@ -63,10 +60,9 @@ public function testLoad() */ public function testSet() { - $identifiers = $this->createMock(IdentifierCollection::class); $authenticator = $this->createMock(AuthenticatorInterface::class); - $collection = new AuthenticatorCollection($identifiers); + $collection = new AuthenticatorCollection(); $collection->set('Form', $authenticator); $this->assertSame($authenticator, $collection->get('Form')); } @@ -75,8 +71,7 @@ public function testLoadException() { $this->expectException('RuntimeException'); $this->expectExceptionMessage('Authenticator class `Does-not-exist` was not found.'); - $identifiers = $this->createMock(IdentifierCollection::class); - $collection = new AuthenticatorCollection($identifiers); + $collection = new AuthenticatorCollection(); $collection->load('Does-not-exist'); } @@ -87,8 +82,7 @@ public function testLoadException() */ public function testIsEmpty() { - $identifiers = $this->createMock(IdentifierCollection::class); - $collection = new AuthenticatorCollection($identifiers); + $collection = new AuthenticatorCollection(); $this->assertTrue($collection->isEmpty()); $collection->load('Authentication.Form', [ @@ -104,10 +98,9 @@ public function testIsEmpty() */ public function testIterator() { - $identifiers = $this->createMock(IdentifierCollection::class); $authenticator = $this->createMock(AuthenticatorInterface::class); - $collection = new AuthenticatorCollection($identifiers); + $collection = new AuthenticatorCollection(); $collection->set('Form', $authenticator); $this->assertContains($authenticator, $collection); diff --git a/tests/TestCase/Authenticator/CookieAuthenticatorTest.php b/tests/TestCase/Authenticator/CookieAuthenticatorTest.php index a6e468e8..92cc5223 100644 --- a/tests/TestCase/Authenticator/CookieAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/CookieAuthenticatorTest.php @@ -18,7 +18,7 @@ use ArrayObject; use Authentication\Authenticator\CookieAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Cake\Core\Configure; use Cake\Http\Cookie\Cookie; use Cake\Http\Response; @@ -59,9 +59,7 @@ public function setUp(): void */ public function testAuthenticateInvalidTokenMissingUsername() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -72,7 +70,7 @@ public function testAuthenticateInvalidTokenMissingUsername() ], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -86,9 +84,7 @@ public function testAuthenticateInvalidTokenMissingUsername() */ public function testAuthenticateSuccess() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -100,7 +96,7 @@ public function testAuthenticateSuccess() ], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -114,9 +110,7 @@ public function testAuthenticateSuccess() */ public function testAuthenticateExpandedCookie() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -127,7 +121,7 @@ public function testAuthenticateExpandedCookie() ], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -143,9 +137,7 @@ public function testAuthenticateNoSalt() { Configure::delete('Security.salt'); - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -157,7 +149,7 @@ public function testAuthenticateNoSalt() ], ); - $authenticator = new CookieAuthenticator($identifiers, ['salt' => false]); + $authenticator = new CookieAuthenticator($identifier, ['salt' => false]); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -171,9 +163,7 @@ public function testAuthenticateNoSalt() */ public function testAuthenticateInvalidSalt() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -184,7 +174,7 @@ public function testAuthenticateInvalidSalt() ], ); - $authenticator = new CookieAuthenticator($identifiers, ['salt' => '']); + $authenticator = new CookieAuthenticator($identifier, ['salt' => '']); $this->expectException(InvalidArgumentException::class); $authenticator->authenticate($request); @@ -197,9 +187,7 @@ public function testAuthenticateInvalidSalt() */ public function testAuthenticateUnknownUser() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -210,7 +198,7 @@ public function testAuthenticateUnknownUser() ], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -224,15 +212,13 @@ public function testAuthenticateUnknownUser() */ public function testCredentialsNotPresent() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -246,9 +232,7 @@ public function testCredentialsNotPresent() */ public function testAuthenticateInvalidToken() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -259,7 +243,7 @@ public function testAuthenticateInvalidToken() ], ); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -273,9 +257,7 @@ public function testAuthenticateInvalidToken() */ public function testPersistIdentity() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -286,7 +268,7 @@ public function testPersistIdentity() $response = new Response(); Cookie::setDefaults(['samesite' => 'None']); - $authenticator = new CookieAuthenticator($identifiers, [ + $authenticator = new CookieAuthenticator($identifier, [ 'cookie' => ['expires' => '2030-01-01 00:00:00'], ]); @@ -332,7 +314,7 @@ public function testPersistIdentity() $request = $request->withParsedBody([ 'other_field' => 1, ]); - $authenticator = new CookieAuthenticator($identifiers, [ + $authenticator = new CookieAuthenticator($identifier, [ 'rememberMeField' => 'other_field', ]); $result = $authenticator->persistIdentity($request, $response, $identity); @@ -349,9 +331,7 @@ public function testPersistIdentity() */ public function testPersistIdentityLoginUrlMismatch() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -361,7 +341,7 @@ public function testPersistIdentityLoginUrlMismatch() ]); $response = new Response(); - $authenticator = new CookieAuthenticator($identifiers, [ + $authenticator = new CookieAuthenticator($identifier, [ 'loginUrl' => '/users/login', ]); @@ -387,9 +367,7 @@ public function testPersistIdentityLoginUrlMismatch() */ public function testPersistIdentityInvalidConfig() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -399,7 +377,7 @@ public function testPersistIdentityInvalidConfig() ]); $response = new Response(); - $authenticator = new CookieAuthenticator($identifiers, [ + $authenticator = new CookieAuthenticator($identifier, [ 'loginUrl' => '/users/login', ]); @@ -420,16 +398,14 @@ public function testPersistIdentityInvalidConfig() */ public function testClearIdentity() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], ); $response = new Response(); - $authenticator = new CookieAuthenticator($identifiers); + $authenticator = new CookieAuthenticator($identifier); $result = $authenticator->clearIdentity($request, $response); $this->assertIsArray($result); diff --git a/tests/TestCase/Authenticator/EnvironmentAuthenticatorTest.php b/tests/TestCase/Authenticator/EnvironmentAuthenticatorTest.php index a5eb2d4d..464e835b 100644 --- a/tests/TestCase/Authenticator/EnvironmentAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/EnvironmentAuthenticatorTest.php @@ -18,7 +18,7 @@ use Authentication\Authenticator\EnvironmentAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\ServerRequestFactory; use Cake\Routing\Router; @@ -39,9 +39,9 @@ class EnvironmentAuthenticatorTest extends TestCase /** * Identifiers * - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\IdentifierInterface */ - public $identifiers; + public $identifier; /** * @inheritDoc @@ -50,11 +50,9 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Token' => [ - 'tokenField' => 'username', - 'dataField' => 'USER_ID', - ], + $this->identifier = IdentifierFactory::create('Authentication.Token', [ + 'tokenField' => 'username', + 'dataField' => 'USER_ID', ]); } @@ -65,18 +63,16 @@ public function setUp(): void */ public function testAuthenticate() { - $identifiers = new IdentifierCollection([ - 'Authentication.Callback' => [ - 'callback' => function ($data) { - if (isset($data['USER_ID']) && isset($data['ATTRIBUTE'])) { - return new Result($data, RESULT::SUCCESS); - } - - return null; - }, - ], + $identifier = IdentifierFactory::create('Authentication.Callback', [ + 'callback' => function ($data) { + if (isset($data['USER_ID']) && isset($data['ATTRIBUTE'])) { + return new Result($data, RESULT::SUCCESS); + } + + return null; + }, ]); - $envAuth = new EnvironmentAuthenticator($identifiers, [ + $envAuth = new EnvironmentAuthenticator($identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -102,7 +98,7 @@ public function testAuthenticate() */ public function testFailedAuthentication() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -128,7 +124,7 @@ public function testFailedAuthentication() */ public function testWithoutFieldConfig() { - $envAuth = new EnvironmentAuthenticator($this->identifiers); + $envAuth = new EnvironmentAuthenticator($this->identifier); $result = $envAuth->authenticate(ServerRequestFactory::fromGlobals()); $this->assertInstanceOf(Result::class, $result); @@ -142,7 +138,7 @@ public function testWithoutFieldConfig() */ public function testWithIncorrectFieldConfig() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'INCORRECT_USER_ID', @@ -168,7 +164,7 @@ public function testWithIncorrectFieldConfig() */ public function testCredentialsEmpty() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -194,18 +190,16 @@ public function testCredentialsEmpty() */ public function testOptionalFields() { - $identifiers = new IdentifierCollection([ - 'Authentication.Callback' => [ - 'callback' => function ($data) { + $identifier = IdentifierFactory::create('Authentication.Callback', [ + 'callback' => function ($data) { if (isset($data['USER_ID']) && isset($data['OPTIONAL_FIELD'])) { return new Result($data, RESULT::SUCCESS); } - return null; - }, - ], + return null; + }, ]); - $envAuth = new EnvironmentAuthenticator($identifiers, [ + $envAuth = new EnvironmentAuthenticator($identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -235,7 +229,7 @@ public function testOptionalFields() */ public function testSingleLoginUrlMismatch() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -265,7 +259,8 @@ public function testMultipleLoginUrlMismatch() Router::createRouteBuilder('/') ->connect('/{lang}/secure', ['controller' => 'Users', 'action' => 'login']); - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ + 'urlChecker' => 'Authentication.Multi', 'loginUrl' => [ ['lang' => 'en', 'controller' => 'Users', 'action' => 'login'], ['lang' => 'de', 'controller' => 'Users', 'action' => 'login'], @@ -295,7 +290,7 @@ public function testMultipleLoginUrlMismatch() */ public function testSingleLoginUrlSuccess() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/en/secure', 'fields' => [ 'USER_ID', @@ -321,7 +316,8 @@ public function testSingleLoginUrlSuccess() */ public function testMultipleLoginUrlSuccess() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ + 'urlChecker' => 'Authentication.Multi', 'loginUrl' => [ '/en/secure', '/de/secure', @@ -351,7 +347,7 @@ public function testMultipleLoginUrlSuccess() */ public function testLoginUrlSuccessWithBase() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/base/fr/secure', 'fields' => [ 'USER_ID', @@ -379,9 +375,10 @@ public function testLoginUrlSuccessWithBase() */ public function testRegexLoginUrlSuccess() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '%^/[a-z]{2}/users/secure/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, ], 'fields' => [ @@ -409,9 +406,10 @@ public function testRegexLoginUrlSuccess() */ public function testFullRegexLoginUrlFailure() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '%auth\.localhost/[a-z]{2}/users/secure/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, 'checkFullUrl' => true, ], @@ -440,9 +438,10 @@ public function testFullRegexLoginUrlFailure() */ public function testFullRegexLoginUrlSuccess() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '%auth\.localhost/[a-z]{2}/users/secure/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, 'checkFullUrl' => true, ], @@ -472,7 +471,7 @@ public function testFullRegexLoginUrlSuccess() */ public function testFullLoginUrlFailureWithoutCheckFullUrlOption() { - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => 'http://localhost/secure', 'fields' => [ 'USER_ID', @@ -499,8 +498,7 @@ public function testFullLoginUrlFailureWithoutCheckFullUrlOption() */ public function testAuthenticateMissingChecker() { - $this->createMock(IdentifierCollection::class); - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', @@ -527,8 +525,7 @@ public function testAuthenticateMissingChecker() */ public function testAuthenticateInvalidChecker() { - $this->createMock(IdentifierCollection::class); - $envAuth = new EnvironmentAuthenticator($this->identifiers, [ + $envAuth = new EnvironmentAuthenticator($this->identifier, [ 'loginUrl' => '/secure', 'fields' => [ 'USER_ID', diff --git a/tests/TestCase/Authenticator/FormAuthenticatorTest.php b/tests/TestCase/Authenticator/FormAuthenticatorTest.php index 1c00a414..96103f52 100644 --- a/tests/TestCase/Authenticator/FormAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/FormAuthenticatorTest.php @@ -18,7 +18,8 @@ use Authentication\Authenticator\FormAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; +use Authentication\Identifier\IdentifierInterface; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\ServerRequestFactory; use Cake\Routing\Router; @@ -43,9 +44,7 @@ class FormAuthenticatorTest extends TestCase */ public function testAuthenticate() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/testpath'], @@ -53,7 +52,7 @@ public function testAuthenticate() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers); + $form = new FormAuthenticator($identifier); $result = $form->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -67,9 +66,7 @@ public function testAuthenticate() */ public function testCredentialsNotPresent() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/does-not-match'], @@ -77,7 +74,7 @@ public function testCredentialsNotPresent() [], ); - $form = new FormAuthenticator($identifiers); + $form = new FormAuthenticator($identifier); $result = $form->authenticate($request); @@ -93,9 +90,7 @@ public function testCredentialsNotPresent() */ public function testCredentialsEmpty() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/does-not-match'], @@ -103,7 +98,7 @@ public function testCredentialsEmpty() ['username' => '', 'password' => ''], ); - $form = new FormAuthenticator($identifiers); + $form = new FormAuthenticator($identifier); $result = $form->authenticate($request); @@ -114,9 +109,7 @@ public function testCredentialsEmpty() public function testIdentityNotFound() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/does-not-match'], @@ -124,7 +117,7 @@ public function testIdentityNotFound() ['username' => 'non-existent', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers); + $form = new FormAuthenticator($identifier); $result = $form->authenticate($request); @@ -140,9 +133,7 @@ public function testIdentityNotFound() */ public function testSingleLoginUrlMismatch() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/does-not-match'], @@ -150,7 +141,7 @@ public function testSingleLoginUrlMismatch() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', ]); @@ -168,9 +159,7 @@ public function testSingleLoginUrlMismatch() */ public function testMultipleLoginUrlMismatch() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/does-not-match'], @@ -181,8 +170,8 @@ public function testMultipleLoginUrlMismatch() Router::createRouteBuilder('/') ->connect('/{lang}/users/login', ['controller' => 'Users', 'action' => 'login']); - $form = new FormAuthenticator($identifiers, [ - 'urlChecker' => 'Authentication.CakeRouter', + $form = new FormAuthenticator($identifier, [ + 'urlChecker' => 'Authentication.Multi', 'loginUrl' => [ ['lang' => 'en', 'controller' => 'Users', 'action' => 'login'], ['lang' => 'de', 'controller' => 'Users', 'action' => 'login'], @@ -203,9 +192,7 @@ public function testMultipleLoginUrlMismatch() */ public function testLoginUrlMismatchWithBase() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -214,7 +201,7 @@ public function testLoginUrlMismatchWithBase() ); $request = $request->withAttribute('base', '/base'); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', ]); @@ -232,9 +219,7 @@ public function testLoginUrlMismatchWithBase() */ public function testSingleLoginUrlSuccess() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/Users/login'], @@ -242,7 +227,7 @@ public function testSingleLoginUrlSuccess() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/Users/login', ]); @@ -260,9 +245,7 @@ public function testSingleLoginUrlSuccess() */ public function testMultipleLoginUrlSuccess() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/de/users/login'], @@ -270,11 +253,12 @@ public function testMultipleLoginUrlSuccess() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => [ '/en/users/login', '/de/users/login', ], + 'urlChecker' => 'Authentication.Multi', ]); $result = $form->authenticate($request); @@ -291,9 +275,7 @@ public function testMultipleLoginUrlSuccess() */ public function testLoginUrlSuccessWithBase() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -302,7 +284,7 @@ public function testLoginUrlSuccessWithBase() ); $request = $request->withAttribute('base', '/base'); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/base/users/login', ]); @@ -320,9 +302,7 @@ public function testLoginUrlSuccessWithBase() */ public function testRegexLoginUrlSuccess() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/de/users/login'], @@ -330,9 +310,10 @@ public function testRegexLoginUrlSuccess() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '%^/[a-z]{2}/users/login/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, ], ]); @@ -351,9 +332,7 @@ public function testRegexLoginUrlSuccess() */ public function testFullRegexLoginUrlFailure() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( [ @@ -363,9 +342,10 @@ public function testFullRegexLoginUrlFailure() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '%auth\.localhost/[a-z]{2}/users/login/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, 'checkFullUrl' => true, ], @@ -385,9 +365,7 @@ public function testFullRegexLoginUrlFailure() */ public function testFullRegexLoginUrlSuccess() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( [ @@ -398,9 +376,10 @@ public function testFullRegexLoginUrlSuccess() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '%auth\.localhost/[a-z]{2}/users/login/?$%', 'urlChecker' => [ + 'className' => 'Authentication.String', 'useRegex' => true, 'checkFullUrl' => true, ], @@ -420,9 +399,7 @@ public function testFullRegexLoginUrlSuccess() */ public function testFullLoginUrlFailureWithoutCheckFullUrlOption() { - $identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $identifier = IdentifierFactory::create('Authentication.Password'); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -430,8 +407,11 @@ public function testFullLoginUrlFailureWithoutCheckFullUrlOption() ['username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => 'http://localhost/users/login', + 'urlChecker' => [ + 'className' => 'Authentication.String', + ], ]); $result = $form->authenticate($request); @@ -448,7 +428,7 @@ public function testFullLoginUrlFailureWithoutCheckFullUrlOption() */ public function testAuthenticateCustomFields() { - $identifiers = $this->createMock(IdentifierCollection::class); + $identifier = $this->createMock(IdentifierInterface::class); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -456,7 +436,7 @@ public function testAuthenticateCustomFields() ['email' => 'mariano@cakephp.org', 'secret' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', 'fields' => [ 'username' => 'email', @@ -464,7 +444,7 @@ public function testAuthenticateCustomFields() ], ]); - $identifiers->expects($this->once()) + $identifier->expects($this->once()) ->method('identify') ->with([ 'username' => 'mariano@cakephp.org', @@ -485,7 +465,7 @@ public function testAuthenticateCustomFields() */ public function testAuthenticateValidData() { - $identifiers = $this->createMock(IdentifierCollection::class); + $identifier = $this->createMock(IdentifierInterface::class); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -493,11 +473,11 @@ public function testAuthenticateValidData() ['id' => 1, 'username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', ]); - $identifiers->expects($this->once()) + $identifier->expects($this->once()) ->method('identify') ->with([ 'username' => 'mariano', @@ -518,7 +498,7 @@ public function testAuthenticateValidData() */ public function testAuthenticateMissingChecker() { - $identifiers = $this->createMock(IdentifierCollection::class); + $identifier = $this->createMock(IdentifierInterface::class); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -526,7 +506,7 @@ public function testAuthenticateMissingChecker() ['id' => 1, 'username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', 'urlChecker' => 'Foo', ]); @@ -544,7 +524,7 @@ public function testAuthenticateMissingChecker() */ public function testAuthenticateInvalidChecker() { - $identifiers = $this->createMock(IdentifierCollection::class); + $identifier = $this->createMock(IdentifierInterface::class); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], @@ -552,7 +532,7 @@ public function testAuthenticateInvalidChecker() ['id' => 1, 'username' => 'mariano', 'password' => 'password'], ); - $form = new FormAuthenticator($identifiers, [ + $form = new FormAuthenticator($identifier, [ 'loginUrl' => '/users/login', 'urlChecker' => self::class, ]); @@ -573,26 +553,22 @@ public function testAuthenticateInvalidChecker() */ 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); + // FormAuthenticator should automatically configure a Password identifier when null is passed + $form = new FormAuthenticator(null); $result = $form->authenticate($request); $this->assertInstanceOf(Result::class, $result); $this->assertSame(Result::SUCCESS, $result->getStatus()); - // Verify the identifier collection now has the Password identifier + // Verify the identifier was lazily created $identifier = $form->getIdentifier(); - $this->assertInstanceOf(IdentifierCollection::class, $identifier); - $this->assertFalse($identifier->isEmpty()); + $this->assertInstanceOf(IdentifierInterface::class, $identifier); } /** @@ -602,32 +578,19 @@ public function testDefaultPasswordIdentifier() */ public function testExplicitIdentifierNotOverridden() { - // Create an IdentifierCollection with a specific identifier - $identifiers = new IdentifierCollection([ - 'Password' => [ - 'className' => 'Authentication.Password', - 'fields' => [ - 'username' => 'email', - 'password' => 'password', - ], + // Create an identifier explicitly + $identifier = IdentifierFactory::create('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); + $form = new FormAuthenticator($identifier); // 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.'); + $this->assertSame($identifier, $form->getIdentifier(), 'Identifier should be the same.'); } /** @@ -637,9 +600,6 @@ public function testExplicitIdentifierNotOverridden() */ public function testDefaultIdentifierInheritsFieldsConfig() { - // Create an empty IdentifierCollection - $identifiers = new IdentifierCollection(); - // Configure authenticator with custom fields mapping // Also set a loginUrl that won't match, so authenticate() returns early // without actually trying to identify (which would require database access) @@ -652,8 +612,8 @@ public function testDefaultIdentifierInheritsFieldsConfig() ]; // FormAuthenticator should create default identifier with inherited fields - // The default identifier is loaded lazily when authenticate() is called - $form = new FormAuthenticator($identifiers, $config); + // The default identifier is loaded lazily when authenticate() or getIdentifier() is called + $form = new FormAuthenticator(null, $config); // Trigger the lazy loading by calling authenticate on a non-matching URL $request = ServerRequestFactory::fromGlobals( @@ -665,12 +625,10 @@ public function testDefaultIdentifierInheritsFieldsConfig() // Verify the identifier was created with the correct configuration $identifier = $form->getIdentifier(); - $this->assertInstanceOf(IdentifierCollection::class, $identifier); - $this->assertFalse($identifier->isEmpty()); + $this->assertInstanceOf(IdentifierInterface::class, $identifier); // Verify the fields are properly configured on the identifier - $passwordIdentifier = $identifier->get('Password'); - $this->assertEquals('user_name', $passwordIdentifier->getConfig('fields.username')); - $this->assertEquals('pass_word', $passwordIdentifier->getConfig('fields.password')); + $this->assertEquals('user_name', $identifier->getConfig('fields.username')); + $this->assertEquals('pass_word', $identifier->getConfig('fields.password')); } } diff --git a/tests/TestCase/Authenticator/HttpBasicAuthenticatorTest.php b/tests/TestCase/Authenticator/HttpBasicAuthenticatorTest.php index dff7e06e..71ddd053 100644 --- a/tests/TestCase/Authenticator/HttpBasicAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/HttpBasicAuthenticatorTest.php @@ -19,7 +19,7 @@ use Authentication\Authenticator\AuthenticationRequiredException; use Authentication\Authenticator\HttpBasicAuthenticator; use Authentication\Authenticator\ResultInterface; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\ServerRequestFactory; use Cake\I18n\DateTime; @@ -38,9 +38,9 @@ class HttpBasicAuthenticatorTest extends TestCase ]; /** - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\IdentifierInterface */ - protected $identifiers; + protected $identifier; /** * @var \Authentication\Authenticator\HttpBasicAuthenticator @@ -54,11 +54,9 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $this->identifier = IdentifierFactory::create('Authentication.Password'); - $this->auth = new HttpBasicAuthenticator($this->identifiers); + $this->auth = new HttpBasicAuthenticator($this->identifier); } /** @@ -68,7 +66,7 @@ public function setUp(): void */ public function testConstructor() { - $object = new HttpBasicAuthenticator($this->identifiers, [ + $object = new HttpBasicAuthenticator($this->identifier, [ 'userModel' => 'AuthUser', 'fields' => [ 'username' => 'user', diff --git a/tests/TestCase/Authenticator/HttpDigestAuthenticatorTest.php b/tests/TestCase/Authenticator/HttpDigestAuthenticatorTest.php index b5224d9c..2eedcb77 100644 --- a/tests/TestCase/Authenticator/HttpDigestAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/HttpDigestAuthenticatorTest.php @@ -21,7 +21,7 @@ use Authentication\Authenticator\HttpDigestAuthenticator; use Authentication\Authenticator\Result; use Authentication\Authenticator\StatelessInterface; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Cake\Http\ServerRequestFactory; use Cake\I18n\DateTime; use Cake\ORM\TableRegistry; @@ -44,9 +44,9 @@ class HttpDigestAuthenticatorTest extends TestCase ]; /** - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\IdentifierInterface */ - protected $identifiers; + protected $identifier; /** * @var \Authentication\Authenticator\HttpDigestAuthenticator @@ -62,11 +62,9 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $this->identifier = IdentifierFactory::create('Authentication.Password'); - $this->auth = new HttpDigestAuthenticator($this->identifiers, [ + $this->auth = new HttpDigestAuthenticator($this->identifier, [ 'realm' => 'localhost', 'nonce' => 123, 'opaque' => '123abc', @@ -85,7 +83,7 @@ public function setUp(): void */ public function testConstructor() { - $object = new HttpDigestAuthenticator($this->identifiers, [ + $object = new HttpDigestAuthenticator($this->identifier, [ 'userModel' => 'AuthUser', 'fields' => ['username' => 'user', 'password' => 'pass'], 'nonce' => 123456, diff --git a/tests/TestCase/Authenticator/JwtAuthenticatorTest.php b/tests/TestCase/Authenticator/JwtAuthenticatorTest.php index df8531a4..c2b36c4c 100644 --- a/tests/TestCase/Authenticator/JwtAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/JwtAuthenticatorTest.php @@ -20,7 +20,7 @@ use ArrayObject; use Authentication\Authenticator\JwtAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierInterface; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\ServerRequestFactory; use Exception; @@ -56,9 +56,9 @@ class JwtAuthenticatorTest extends TestCase /** * Identifier Collection * - * @var \Authentication\Identifier\IdentifierCollection; + * @var \Authentication\Identifier\IdentifierInterface; */ - public $identifiers; + public $identifier; /** * @var \Cake\Http\ServerRequest @@ -84,7 +84,7 @@ public function setUp(): void $privKey1 = file_get_contents(__DIR__ . '/../../data/rsa1-private.pem'); $this->tokenRS256 = JWT::encode($data, $privKey1, 'RS256', 'jwk1'); - $this->identifiers = new IdentifierCollection([]); + $this->identifier = null; } /** @@ -99,7 +99,7 @@ public function testAuthenticateViaHeaderToken() ); $this->request = $this->request->withAddedHeader('Authorization', 'Bearer ' . $this->tokenHS256); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'secretKey' => 'secretKey', 'subjectKey' => 'subjectId', ]); @@ -122,7 +122,7 @@ public function testAuthenticateViaQueryParamToken() ['token' => $this->tokenHS256], ); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'secretKey' => 'secretKey', 'subjectKey' => 'subjectId', ]); @@ -145,8 +145,8 @@ public function testAuthenticationViaIdentifierAndSubject() ['token' => $this->tokenHS256], ); - $this->identifiers = $this->createMock(IdentifierCollection::class); - $this->identifiers->expects($this->once()) + $this->identifier = $this->createMock(IdentifierInterface::class); + $this->identifier->expects($this->once()) ->method('identify') ->with([ 'subjectId' => 3, @@ -158,7 +158,7 @@ public function testAuthenticationViaIdentifierAndSubject() 'firstname' => 'larry', ])); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'secretKey' => 'secretKey', 'returnPayload' => false, 'subjectKey' => 'subjectId', @@ -186,7 +186,7 @@ public function testAuthenticateInvalidPayloadNotAnObject() $authenticator = $this->getMockBuilder(JwtAuthenticator::class) ->setConstructorArgs([ - $this->identifiers, + $this->identifier, ]) ->onlyMethods([ 'getPayLoad', @@ -217,7 +217,7 @@ public function testAuthenticateInvalidPayloadEmpty() $authenticator = $this->getMockBuilder(JwtAuthenticator::class) ->setConstructorArgs([ - $this->identifiers, + $this->identifier, ]) ->onlyMethods([ 'getPayLoad', @@ -241,7 +241,7 @@ public function testInvalidToken() ['token' => 'should cause an exception'], ); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'secretKey' => 'secretKey', ]); @@ -267,7 +267,7 @@ public function testGetPayloadHS256() ['token' => $this->tokenHS256], ); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'secretKey' => 'secretKey', ]); @@ -299,7 +299,7 @@ public function testGetPayloadRS256() ['token' => $this->tokenRS256], ); - $authenticator = new JwtAuthenticator($this->identifiers, [ + $authenticator = new JwtAuthenticator($this->identifier, [ 'jwks' => json_decode(file_get_contents(__DIR__ . '/../../data/rsa-jwkset.json'), true), ]); diff --git a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php index 1330900d..5b6d8806 100644 --- a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php @@ -19,7 +19,7 @@ use ArrayObject; use Authentication\Authenticator\PrimaryKeySessionAuthenticator; use Authentication\Authenticator\Result; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Cake\Http\Exception\UnauthorizedException; use Cake\Http\Response; use Cake\Http\ServerRequestFactory; @@ -35,12 +35,13 @@ class PrimaryKeySessionAuthenticatorTest extends TestCase */ protected array $fixtures = [ 'core.AuthUsers', + 'core.Users', ]; /** - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\IdentifierInterface */ - protected $identifiers; + protected $identifier; /** * @var \Cake\Http\Session&\PHPUnit\Framework\MockObject\MockObject @@ -54,9 +55,9 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Password' => [ - ], + $this->identifier = IdentifierFactory::create('Authentication.Token', [ + 'tokenField' => 'id', + 'dataField' => 'key', ]); $this->sessionMock = $this->getMockBuilder(Session::class) @@ -81,18 +82,7 @@ public function testAuthenticateSuccess() $request = $request->withAttribute('session', $this->sessionMock); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Token' => [ - 'tokenField' => 'id', - 'dataField' => 'key', - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'AuthUsers', - ], - ], - ]); - - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -118,20 +108,17 @@ public function testAuthenticateSuccessCustomFinder() $request = $request->withAttribute('session', $this->sessionMock); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Token' => [ - 'tokenField' => 'id', - 'dataField' => 'key', - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'AuthUsers', - 'finder' => 'auth', - ], + $this->identifier = IdentifierFactory::create('Authentication.Token', [ + 'tokenField' => 'id', + 'dataField' => 'key', + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'userModel' => 'AuthUsers', + 'finder' => 'auth', ], ]); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers, [ - ]); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -157,7 +144,7 @@ public function testAuthenticateFailure() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -180,7 +167,7 @@ public function testVerifyByDatabaseFailure() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers, [ + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier, [ ]); $result = $authenticator->authenticate($request); @@ -198,7 +185,7 @@ public function testPersistIdentity() $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $data = new ArrayObject(['id' => 1]); @@ -241,7 +228,7 @@ public function testClearIdentity() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('delete') @@ -270,7 +257,7 @@ public function testImpersonate() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $usersTable = $this->fetchTable('Users'); $impersonator = $usersTable->newEntity([ 'username' => 'mariano', @@ -311,7 +298,7 @@ public function testImpersonateAlreadyImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $impersonator = new ArrayObject([ 'username' => 'mariano', 'password' => 'password', @@ -345,7 +332,7 @@ public function testStopImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $impersonator = new ArrayObject([ 'username' => 'mariano', @@ -392,7 +379,7 @@ public function testStopImpersonatingNotImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('check') @@ -429,7 +416,7 @@ public function testIsImpersonating() $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new PrimaryKeySessionAuthenticator($this->identifiers); + $authenticator = new PrimaryKeySessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('check') diff --git a/tests/TestCase/Authenticator/SessionAuthenticatorTest.php b/tests/TestCase/Authenticator/SessionAuthenticatorTest.php index bd22d5ea..ce5014f5 100644 --- a/tests/TestCase/Authenticator/SessionAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/SessionAuthenticatorTest.php @@ -19,7 +19,7 @@ use ArrayObject; use Authentication\Authenticator\Result; use Authentication\Authenticator\SessionAuthenticator; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\PasswordIdentifier; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\Exception\UnauthorizedException; @@ -43,9 +43,9 @@ class SessionAuthenticatorTest extends TestCase ]; /** - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\IdentifierInterface */ - protected $identifiers; + protected $identifier; protected $sessionMock; @@ -56,9 +56,7 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Password', - ]); + $this->identifier = IdentifierFactory::create('Authentication.Password'); $this->sessionMock = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() @@ -85,7 +83,7 @@ public function testAuthenticateSuccess() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -111,7 +109,7 @@ public function testAuthenticateSuccessWithoutCollection() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator(new IdentifierCollection(), [ + $authenticator = new SessionAuthenticator(null, [ 'identifier' => 'Authentication.Password', ]); $result = $authenticator->authenticate($request); @@ -139,7 +137,7 @@ public function testAuthenticateSuccessWithoutCollectionButObject() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator(new IdentifierCollection(), [ + $authenticator = new SessionAuthenticator(null, [ 'identifier' => new PasswordIdentifier(), ]); $result = $authenticator->authenticate($request); @@ -167,8 +165,8 @@ public function testAuthenticateSuccessWithDirectCollection() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator(new IdentifierCollection(), [ - 'identifier' => new IdentifierCollection(['Authentication.Password']), + $authenticator = new SessionAuthenticator(null, [ + 'identifier' => IdentifierFactory::create('Authentication.Password'), ]); $result = $authenticator->authenticate($request); @@ -192,7 +190,7 @@ public function testAuthenticateFailure() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $result = $authenticator->authenticate($request); $this->assertInstanceOf(Result::class, $result); @@ -218,7 +216,7 @@ public function testVerifyByDatabaseSuccess() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator($this->identifiers, [ + $authenticator = new SessionAuthenticator($this->identifier, [ 'identify' => true, ]); $result = $authenticator->authenticate($request); @@ -246,7 +244,7 @@ public function testVerifyByDatabaseFailure() $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator($this->identifiers, [ + $authenticator = new SessionAuthenticator($this->identifier, [ 'identify' => true, ]); $result = $authenticator->authenticate($request); @@ -265,7 +263,7 @@ public function testPersistIdentity() $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $data = new ArrayObject(['username' => 'florian']); @@ -308,7 +306,7 @@ public function testClearIdentity() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('delete') @@ -337,7 +335,7 @@ public function testImpersonate() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $AuthUsers = TableRegistry::getTableLocator()->get('AuthUsers'); $impersonator = $AuthUsers->newEntity([ 'username' => 'mariano', @@ -376,7 +374,7 @@ public function testImpersonateAlreadyImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $impersonator = new ArrayObject([ 'username' => 'mariano', 'password' => 'password', @@ -410,7 +408,7 @@ public function testStopImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $impersonator = new ArrayObject([ 'username' => 'mariano', @@ -457,7 +455,7 @@ public function testStopImpersonatingNotImpersonating() $request = $request->withAttribute('session', $this->sessionMock); $response = new Response(); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('check') @@ -494,7 +492,7 @@ public function testIsImpersonating() $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); $request = $request->withAttribute('session', $this->sessionMock); - $authenticator = new SessionAuthenticator($this->identifiers); + $authenticator = new SessionAuthenticator($this->identifier); $this->sessionMock->expects($this->once()) ->method('check') diff --git a/tests/TestCase/Authenticator/TokenAuthenticatorTest.php b/tests/TestCase/Authenticator/TokenAuthenticatorTest.php index f471fb38..8b2a4725 100644 --- a/tests/TestCase/Authenticator/TokenAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/TokenAuthenticatorTest.php @@ -18,7 +18,7 @@ use Authentication\Authenticator\Result; use Authentication\Authenticator\TokenAuthenticator; -use Authentication\Identifier\IdentifierCollection; +use Authentication\Identifier\IdentifierFactory; use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Cake\Http\ServerRequestFactory; @@ -35,9 +35,9 @@ class TokenAuthenticatorTest extends TestCase ]; /** - * @var \Authentication\Identifier\IdentifierCollection + * @var \Authentication\Identifier\TokenIdentifier */ - protected $identifiers; + protected $identifier; /** * @var \Cake\Http\ServerRequest @@ -51,10 +51,8 @@ public function setUp(): void { parent::setUp(); - $this->identifiers = new IdentifierCollection([ - 'Authentication.Token' => [ - 'tokenField' => 'username', - ], + $this->identifier = IdentifierFactory::create('Authentication.Token', [ + 'tokenField' => 'username', ]); $this->request = ServerRequestFactory::fromGlobals( @@ -72,7 +70,7 @@ public function setUp(): void public function testAuthenticateViaHeaderToken() { // Test without token - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'queryParam' => 'token', ]); $result = $tokenAuth->authenticate($this->request); @@ -81,7 +79,7 @@ public function testAuthenticateViaHeaderToken() // Test header token $requestWithHeaders = $this->request->withAddedHeader('Token', 'mariano'); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'Token', ]); $result = $tokenAuth->authenticate($requestWithHeaders); @@ -98,7 +96,7 @@ public function testViaQueryParamToken() { // Test with query param token $requestWithParams = $this->request->withQueryParams(['token' => 'mariano']); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'queryParam' => 'token', ]); $result = $tokenAuth->authenticate($requestWithParams); @@ -107,7 +105,7 @@ public function testViaQueryParamToken() // Test with valid query param but invalid token $requestWithParams = $this->request->withQueryParams(['token' => 'does-not-exist']); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'queryParam' => 'token', ]); $result = $tokenAuth->authenticate($requestWithParams); @@ -124,7 +122,7 @@ public function testTokenPrefix() { //valid prefix $requestWithHeaders = $this->request->withAddedHeader('Token', 'identity mariano'); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'Token', 'tokenPrefix' => 'identity', ]); @@ -133,7 +131,7 @@ public function testTokenPrefix() $this->assertSame(Result::SUCCESS, $result->getStatus()); $requestWithHeaders = $this->request->withAddedHeader('X-Dipper-Auth', 'dipper_mariano'); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'X-Dipper-Auth', 'tokenPrefix' => 'dipper_', ]); @@ -143,7 +141,7 @@ public function testTokenPrefix() //invalid prefix $requestWithHeaders = $this->request->withAddedHeader('Token', 'bearer mariano'); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'Token', 'tokenPrefix' => 'identity', ]); @@ -153,7 +151,7 @@ public function testTokenPrefix() // should not remove prefix from token $requestWithHeaders = $this->request->withAddedHeader('X-Dipper-Auth', 'mari mariano'); - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'X-Dipper-Auth', 'tokenPrefix' => 'mari', ]); @@ -169,7 +167,7 @@ public function testTokenPrefix() */ public function testWithoutQueryParamConfig() { - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'header' => 'Token', ]); @@ -185,7 +183,7 @@ public function testWithoutQueryParamConfig() */ public function testWithoutHeaderConfig() { - $tokenAuth = new TokenAuthenticator($this->identifiers, [ + $tokenAuth = new TokenAuthenticator($this->identifier, [ 'queryParam' => 'token', ]); @@ -201,7 +199,7 @@ public function testWithoutHeaderConfig() */ public function testWithoutAnyConfig() { - $tokenAuth = new TokenAuthenticator($this->identifiers); + $tokenAuth = new TokenAuthenticator($this->identifier); $result = $tokenAuth->authenticate(ServerRequestFactory::fromGlobals()); $this->assertInstanceOf(Result::class, $result); diff --git a/tests/TestCase/Identifier/IdentifierCollectionTest.php b/tests/TestCase/Identifier/IdentifierCollectionTest.php deleted file mode 100644 index b296c4b5..00000000 --- a/tests/TestCase/Identifier/IdentifierCollectionTest.php +++ /dev/null @@ -1,121 +0,0 @@ -get('Password'); - $this->assertInstanceOf('\Authentication\Identifier\PasswordIdentifier', $result); - } - - /** - * testLoad - * - * @return void - */ - public function testLoad() - { - $collection = new IdentifierCollection(); - $result = $collection->load('Authentication.Password'); - $this->assertInstanceOf('\Authentication\Identifier\PasswordIdentifier', $result); - } - - /** - * testSet - * - * @return void - */ - public function testSet() - { - $identifier = $this->createMock(IdentifierInterface::class); - $collection = new IdentifierCollection(); - $collection->set('Password', $identifier); - $this->assertSame($identifier, $collection->get('Password')); - } - - public function testLoadException() - { - $this->expectException('RuntimeException'); - $this->expectExceptionMessage('Identifier class `Does-not-exist` was not found.'); - $collection = new IdentifierCollection(); - $collection->load('Does-not-exist'); - } - - /** - * testIsEmpty - * - * @return void - */ - public function testIsEmpty() - { - $collection = new IdentifierCollection(); - $this->assertTrue($collection->isEmpty()); - - $collection->load('Authentication.Password'); - $this->assertFalse($collection->isEmpty()); - } - - /** - * testIterator - * - * @return void - */ - public function testIterator() - { - $identifier = $this->createMock(IdentifierInterface::class); - $collection = new IdentifierCollection(); - $collection->set('Password', $identifier); - - $this->assertContains($identifier, $collection); - } - - /** - * testIdentify - * - * @return void - */ - public function testIdentify() - { - $collection = new IdentifierCollection([ - 'Authentication.Password', - ]); - - $result = $collection->identify([ - 'username' => 'mariano', - 'password' => 'password', - ]); - - $this->assertInstanceOf('\ArrayAccess', $result); - $this->assertInstanceOf(PasswordIdentifier::class, $collection->getIdentificationProvider()); - - $collection->identify([ - 'username' => 'mariano', - 'password' => 'invalid password', - ]); - $this->assertNull($collection->getIdentificationProvider()); - } -} diff --git a/tests/TestCase/UrlChecker/CakeRouterUrlCheckerTest.php b/tests/TestCase/UrlChecker/CakeRouterUrlCheckerTest.php deleted file mode 100644 index f8ec480e..00000000 --- a/tests/TestCase/UrlChecker/CakeRouterUrlCheckerTest.php +++ /dev/null @@ -1,230 +0,0 @@ -connect( - '/login', - ['controller' => 'Users', 'action' => 'login'], - ['_name' => 'login'], - ); - $builder->connect('/{controller}/{action}'); - $builder->connect( - '/login', - ['controller' => 'Users', 'action' => 'login'], - [ - '_host' => 'auth.localhost', - '_name' => 'secureLogin', - ], - ); - } - - /** - * testCheckSimple - * - * @return void - */ - public function testCheckSimple() - { - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/invalid'], - ); - $result = $checker->check($request, [ - 'controller' => 'Users', - 'action' => 'login', - ]); - $this->assertFalse($result); - } - - /** - * checkFullUrls - * - * @return void - */ - public function testCheckFullUrls() - { - $url = [ - 'controller' => 'users', - 'action' => 'login', - ]; - - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], - ); - $result = $checker->check($request, $url, [ - 'checkFullUrl' => true, - ]); - $this->assertTrue($result); - - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/invalid'], - ); - $result = $checker->check($request, $url, [ - 'checkFullUrl' => true, - ]); - $this->assertFalse($result); - - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/login'], - ); - $result = $checker->check($request, ['_name' => 'secureLogin'], [ - 'checkFullUrl' => true, - ]); - $this->assertFalse($result); - - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - [ - 'REQUEST_URI' => '/login', - 'SERVER_NAME' => 'auth.localhost', - ], - ); - $result = $checker->check($request, ['_name' => 'secureLogin'], [ - 'checkFullUrl' => true, - ]); - $this->assertTrue($result); - } - - /** - * testEmptyUrl - * - * @return void - */ - public function testEmptyUrl() - { - $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('The $loginUrls parameter is empty or not of type array.'); - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], - ); - $result = $checker->check($request, []); - $this->assertFalse($result); - } - - /** - * testEmptyUrl - * - * @return void - */ - public function testStringUrl() - { - $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('The $loginUrls parameter is empty or not of type array.'); - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], - ); - $result = $checker->check($request, '/users/login'); - $this->assertFalse($result); - } - - /** - * testNamedRoute - * - * @return void - */ - public function testNamedRoute() - { - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/login'], - ); - $result = $checker->check($request, ['_name' => 'login']); - $this->assertTrue($result); - } - - /** - * testInvalidNamedRoute - */ - public function testInvalidNamedRoute() - { - $this->expectException('Cake\Routing\Exception\MissingRouteException'); - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/login'], - ); - $checker->check($request, ['_name' => 'login-does-not-exist']); - } - - /** - * testMultipleUrls - * - * @return void - */ - public function testMultipleUrls() - { - $url = [ - [ - 'controller' => 'users', - 'action' => 'login', - ], - [ - 'controller' => 'admins', - 'action' => 'login', - ], - ]; - - $checker = new CakeRouterUrlChecker(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], - ); - $result = $checker->check($request, $url, [ - 'checkFullUrl' => true, - ]); - $this->assertTrue($result); - - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/admins/login'], - ); - $result = $checker->check($request, $url, [ - 'checkFullUrl' => true, - ]); - $this->assertTrue($result); - - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/invalid'], - ); - $result = $checker->check($request, $url, [ - 'checkFullUrl' => true, - ]); - $this->assertFalse($result); - } -} diff --git a/tests/TestCase/UrlChecker/DefaultUrlCheckerTest.php b/tests/TestCase/UrlChecker/DefaultUrlCheckerTest.php index e0a4e4cb..a0be5877 100644 --- a/tests/TestCase/UrlChecker/DefaultUrlCheckerTest.php +++ b/tests/TestCase/UrlChecker/DefaultUrlCheckerTest.php @@ -19,27 +19,37 @@ use Authentication\Test\TestCase\AuthenticationTestCase as TestCase; use Authentication\UrlChecker\DefaultUrlChecker; use Cake\Http\ServerRequestFactory; +use Cake\Routing\Router; /** - * DefaultUrlCheckerTest + * DefaultUrlChecker Test */ class DefaultUrlCheckerTest extends TestCase { /** - * testCheckFailure - * - * @return void + * @inheritDoc */ - public function testCheckFailure() + public function setUp(): void { - $checker = new DefaultUrlChecker(); + parent::setUp(); - $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/does-not-match'], - ); + Router::fullBaseUrl('http://localhost'); - $result = $checker->check($request, '/users/login'); - $this->assertFalse($result); + $builder = Router::createRouteBuilder('/'); + $builder->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'], + ); + $builder->connect('/{controller}/{action}'); + $builder->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + [ + '_host' => 'auth.localhost', + '_name' => 'secureLogin', + ], + ); } /** @@ -51,89 +61,113 @@ public function testCheckSimple() { $checker = new DefaultUrlChecker(); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], + ['REQUEST_URI' => '/users/invalid'], ); - $result = $checker->check($request, '/users/login'); - $this->assertTrue($result); - $result = $checker->check($request, [ - '/users/login', - '/admin/login', + 'controller' => 'Users', + 'action' => 'login', ]); - $this->assertTrue($result); + $this->assertFalse($result); } /** - * testCheckArray + * checkFullUrls * * @return void */ - public function testCheckArray() + public function testCheckFullUrls() { + $url = [ + 'controller' => 'users', + 'action' => 'login', + ]; + $checker = new DefaultUrlChecker(); $request = ServerRequestFactory::fromGlobals( ['REQUEST_URI' => '/users/login'], ); + $result = $checker->check($request, $url, [ + 'checkFullUrl' => true, + ]); + $this->assertTrue($result); - $result = $checker->check($request, [ - '/users/login', - '/admin/login', + $checker = new DefaultUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/invalid'], + ); + $result = $checker->check($request, $url, [ + 'checkFullUrl' => true, + ]); + $this->assertFalse($result); + + $checker = new DefaultUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/login'], + ); + $result = $checker->check($request, ['_name' => 'secureLogin'], [ + 'checkFullUrl' => true, + ]); + $this->assertFalse($result); + + $checker = new DefaultUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + [ + 'REQUEST_URI' => '/login', + 'SERVER_NAME' => 'auth.localhost', + ], + ); + $result = $checker->check($request, ['_name' => 'secureLogin'], [ + 'checkFullUrl' => true, ]); $this->assertTrue($result); } /** - * testCheckRegexp + * testStringUrl - CakeUrlChecker now accepts strings too * * @return void */ - public function testCheckRegexp() + public function testStringUrl() { $checker = new DefaultUrlChecker(); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/en/users/login'], + ['REQUEST_URI' => '/users/login'], ); - - $result = $checker->check($request, '%^/[a-z]{2}/users/login/?$%', [ - 'useRegex' => true, - ]); + $result = $checker->check($request, '/users/login'); $this->assertTrue($result); + + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/different/url'], + ); + $result = $checker->check($request, '/users/login'); + $this->assertFalse($result); } /** - * testCheckFull + * testNamedRoute * * @return void */ - public function testCheckFull() + public function testNamedRoute() { $checker = new DefaultUrlChecker(); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], + ['REQUEST_URI' => '/login'], ); - - $result = $checker->check($request, 'http://localhost/users/login', [ - 'checkFullUrl' => true, - ]); + $result = $checker->check($request, ['_name' => 'login']); $this->assertTrue($result); } /** - * testCheckBase - * - * @return void + * testInvalidNamedRoute */ - public function testCheckBase() + public function testInvalidNamedRoute() { + $this->expectException('Cake\Routing\Exception\MissingRouteException'); $checker = new DefaultUrlChecker(); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/users/login'], + ['REQUEST_URI' => '/login'], ); - $request = $request->withAttribute('base', '/base'); - - $result = $checker->check($request, 'http://localhost/base/users/login', [ - 'checkFullUrl' => true, - ]); - $this->assertTrue($result); + $checker->check($request, ['_name' => 'login-does-not-exist']); } } diff --git a/tests/TestCase/UrlChecker/StringUrlCheckerTest.php b/tests/TestCase/UrlChecker/StringUrlCheckerTest.php new file mode 100644 index 00000000..f865bc27 --- /dev/null +++ b/tests/TestCase/UrlChecker/StringUrlCheckerTest.php @@ -0,0 +1,117 @@ + '/users/does-not-match'], + ); + + $result = $checker->check($request, '/users/login'); + $this->assertFalse($result); + } + + /** + * testCheckSimple + * + * @return void + */ + public function testCheckSimple() + { + $checker = new StringUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/login'], + ); + $result = $checker->check($request, '/users/login'); + $this->assertTrue($result); + + $result = $checker->check($request, '/different/url'); + $this->assertFalse($result); + } + + /** + * testCheckRegexp + * + * @return void + */ + public function testCheckRegexp() + { + $checker = new StringUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/en/users/login'], + ); + + $result = $checker->check($request, '%^/[a-z]{2}/users/login/?$%', [ + 'useRegex' => true, + ]); + $this->assertTrue($result); + } + + /** + * testCheckFull + * + * @return void + */ + public function testCheckFull() + { + $checker = new StringUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/login'], + ); + + $result = $checker->check($request, 'http://localhost/users/login', [ + 'checkFullUrl' => true, + ]); + $this->assertTrue($result); + } + + /** + * testCheckBase + * + * @return void + */ + public function testCheckBase() + { + $checker = new StringUrlChecker(); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/login'], + ); + $request = $request->withAttribute('base', '/base'); + + $result = $checker->check($request, 'http://localhost/base/users/login', [ + 'checkFullUrl' => true, + ]); + $this->assertTrue($result); + } +}