diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 032c79a25..8035ff10e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['7.3', '7.4', '8.0', '8.1'] + php-version: ['8.1', '8.2'] db-type: [sqlite, mysql, pgsql] prefer-lowest: [''] @@ -59,7 +59,7 @@ jobs: fi - name: Setup problem matchers for PHPUnit - if: matrix.php-version == '7.4' && matrix.db-type == 'mysql' + if: matrix.php-version == '8.1' && matrix.db-type == 'mysql' run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Run PHPUnit @@ -67,14 +67,14 @@ jobs: if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp?encoding=utf8'; fi if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi - if [[ ${{ matrix.php-version }} == '7.4' ]]; then + if [[ ${{ matrix.php-version }} == '8.1' ]]; then export CODECOVERAGE=1 && vendor/bin/phpunit --verbose --coverage-clover=coverage.xml else vendor/bin/phpunit fi - name: Submit code coverage - if: matrix.php-version == '7.4' + if: matrix.php-version == '8.1' uses: codecov/codecov-action@v1 cs-stan: @@ -87,7 +87,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.3' + php-version: '8.1' extensions: mbstring, intl, apcu coverage: none diff --git a/Docs/Documentation/Migration/11.x-12.0.md b/Docs/Documentation/Migration/11.x-12.0.md new file mode 100644 index 000000000..b21674944 --- /dev/null +++ b/Docs/Documentation/Migration/11.x-12.0.md @@ -0,0 +1,35 @@ +Migration 11.x to 12.0 +====================== + +12.0 is compatible with CakePHP ^5.0 + +Requirements +------------ +- php >=8.1 +- CakePHP 5 +- cakephp/authentication 3 +- cakephp/authorization 3 + +Overview +-------- +- Removed the deprecated config key `'Auth.authenticate.all.contain'` you should use `'Auth.Profile.contain'` instead. +- UsersShell logic was migrated into commands classes. +- Security component was removed from CakePHP core, the usages in the plugin +were updated with FormProtection component, for more information about the component, +go to https://book.cakephp.org/5/en/controllers/components/form-protection.html +- +Webauthn Two-Factor Authentication +---------------------------------- +It's required the version 4.4 of web-auth/webauthn-lib to use webauthn +two-factor authentication. The main actions behave the same as before, +if you had extended the adapter please check the following changes: + - `Webauthn\Server` was removed from web-auth/webauthn-lib and the related + logic was migrated into our custom classes `\CakeDC\Users\Webauthn\AuthenticateAdapter` +and `\CakeDC\Users\Webauthn\RegisterAdapter` + - Removed protected method `\CakeDC\Users\Webauthn\AuthenticateAdapter::loadAndCheckAssertionResponse`, this method was +related to `Webauthn\Server` + - Removed protected method `\CakeDC\Users\Webauthn\RegisterAdapter::loadAndCheckAttestationResponse`, this method was + related to `Webauthn\Server` + - Added class `\CakeDC\Users\Webauthn\Base64Utility` to provide basic encoding and decoding compatible with previous versions. + - Added class `\CakeDC\Users\Webauthn\PublicKeyCredentialLoader` to correctly load credentials from basic +client (javascript) provided. diff --git a/composer.json b/composer.json index 54aab97aa..aef43248f 100644 --- a/composer.json +++ b/composer.json @@ -29,14 +29,14 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": ">=7.2.0", - "cakephp/cakephp": "^4.0", - "cakedc/auth": "^7.0", - "cakephp/authorization": "^2.0.0", - "cakephp/authentication": "^2.0.0" + "php": ">=8.1", + "cakephp/cakephp": "5.x-dev", + "cakedc/auth": "8.next-cake5-dev", + "cakephp/authorization": "3.x-dev", + "cakephp/authentication": "3.x-dev" }, "require-dev": { - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.5.16", "league/oauth2-facebook": "@stable", "league/oauth2-instagram": "@stable", "league/oauth2-google": "@stable", @@ -45,10 +45,9 @@ "google/recaptcha": "@stable", "robthree/twofactorauth": "^1.6", "yubico/u2flib-server": "^1.0", - "php-coveralls/php-coveralls": "^2.1", "league/oauth1-client": "^1.7", - "cakephp/cakephp-codesniffer": "^4.0", - "web-auth/webauthn-lib": "^3.3", + "cakephp/cakephp-codesniffer": "^5.0", + "web-auth/webauthn-lib": "^4.4", "thenetworg/oauth2-azure": "^2.1" }, "suggest": { @@ -85,12 +84,12 @@ "@test", "@analyse" ], - "cs-check": "phpcs -n -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests", - "cs-fix": "phpcbf --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests", + "cs-check": "phpcs -n -p ./src ./tests", + "cs-fix": "phpcbf ./src ./tests", "test": "phpunit --stderr", "stan": "phpstan analyse src/", "psalm": "php vendor/psalm/phar/psalm.phar --show-info=false src/ ", - "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:0.12.94 psalm/phar:~4.9.2 && mv composer.backup composer.json", + "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:~1.9.0 psalm/phar:~5.1.0 && mv composer.backup composer.json", "stan-rebuild-baseline": "phpstan analyse --configuration phpstan.neon --error-format baselineNeon src/ > phpstan-baseline.neon", "psalm-rebuild-baseline": "php vendor/psalm/phar/psalm.phar --show-info=false --set-baseline=psalm-baseline.xml src/", "rector": "rector process src/", diff --git a/config/users.php b/config/users.php index ebb64c76e..fd7c75c42 100644 --- a/config/users.php +++ b/config/users.php @@ -144,7 +144,7 @@ 'enabled' => false, 'appName' => null,//App must set a valid name here 'id' => null,//default value is the current domain - 'checker' => \CakeDC\Auth\Authentication\DefaultWebauthn2fAuthenticationChecker::class, + 'checker' => \CakeDC\Auth\Authentication\DefaultWebauthn2FAuthenticationChecker::class, ], // default configuration used to auto-load the Auth Component, override to change the way Auth works 'Auth' => [ diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 000000000..d1fb83821 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 52b966521..ae869cf3f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -65,16 +65,6 @@ parameters: count: 1 path: src/Controller/UsersController.php - - - message: "#^Parameter \\#1 \\$message of method Cake\\\\Controller\\\\Controller\\:\\:log\\(\\) expects string, Exception given\\.$#" - count: 1 - path: src/Controller/UsersController.php - - - - message: "#^Parameter \\#1 \\$object of method Cake\\\\Controller\\\\Controller\\:\\:paginate\\(\\) expects Cake\\\\ORM\\\\Query\\|Cake\\\\ORM\\\\Table\\|string\\|null, Cake\\\\Datasource\\\\RepositoryInterface given\\.$#" - count: 1 - path: src/Controller/UsersController.php - - message: "#^Parameter \\#2 \\$options of method Cake\\\\Controller\\\\Component\\\\FlashComponent\\:\\:error\\(\\) expects array, string given\\.$#" count: 1 @@ -190,31 +180,11 @@ parameters: count: 1 path: src/Model/Behavior/RegisterBehavior.php - - - message: "#^Cannot call method tokenExpired\\(\\) on array\\|Cake\\\\Datasource\\\\EntityInterface\\.$#" - count: 1 - path: src/Model/Behavior/RegisterBehavior.php - - message: "#^Access to an undefined property CakeDC\\\\Users\\\\Model\\\\Entity\\\\SocialAccount\\:\\:\\$active\\.$#" count: 1 path: src/Model/Behavior/SocialAccountBehavior.php - - - message: "#^Cannot access property \\$active on array\\|Cake\\\\Datasource\\\\EntityInterface\\.$#" - count: 2 - path: src/Model/Behavior/SocialAccountBehavior.php - - - - message: "#^Cannot access property \\$token on array\\|Cake\\\\Datasource\\\\EntityInterface\\.$#" - count: 1 - path: src/Model/Behavior/SocialAccountBehavior.php - - - - message: "#^Cannot access property \\$user on array\\|Cake\\\\Datasource\\\\EntityInterface\\.$#" - count: 1 - path: src/Model/Behavior/SocialAccountBehavior.php - - message: "#^Method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\SocialAccountBehavior\\:\\:resendValidation\\(\\) should return CakeDC\\\\Users\\\\Model\\\\Entity\\\\User but returns array\\.$#" count: 1 @@ -225,11 +195,6 @@ parameters: count: 1 path: src/Model/Behavior/SocialAccountBehavior.php - - - message: "#^Parameter \\#1 \\$socialAccount of method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\SocialAccountBehavior\\:\\:_activateAccount\\(\\) expects CakeDC\\\\Users\\\\Model\\\\Entity\\\\SocialAccount, array\\|Cake\\\\Datasource\\\\EntityInterface given\\.$#" - count: 1 - path: src/Model/Behavior/SocialAccountBehavior.php - - message: "#^Access to an undefined property Cake\\\\ORM\\\\Table\\:\\:\\$SocialAccounts\\.$#" count: 4 @@ -250,16 +215,6 @@ parameters: count: 1 path: src/Model/Entity/User.php - - - message: "#^Cannot access property \\$username on bool\\.$#" - count: 2 - path: src/Shell/UsersShell.php - - - - message: "#^Property CakeDC\\\\Users\\\\Shell\\\\UsersShell\\:\\:\\$Users \\(CakeDC\\\\Users\\\\Model\\\\Table\\\\UsersTable\\) does not accept Cake\\\\Datasource\\\\RepositoryInterface\\.$#" - count: 1 - path: src/Shell/UsersShell.php - - message: "#^Parameter \\#2 \\$usersTable of class CakeDC\\\\Users\\\\Webauthn\\\\Repository\\\\UserCredentialSourceRepository constructor expects CakeDC\\\\Users\\\\Model\\\\Table\\\\UsersTable\\|null, Cake\\\\ORM\\\\Table given\\.$#" count: 1 diff --git a/psalm.xml b/psalm.xml index 3bcbd74e4..1de17a837 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,7 +14,6 @@ - diff --git a/resources/locales/ar_OM/users.mo b/resources/locales/ar_OM/cake_d_c_users.mo similarity index 100% rename from resources/locales/ar_OM/users.mo rename to resources/locales/ar_OM/cake_d_c_users.mo diff --git a/resources/locales/ar_OM/users.po b/resources/locales/ar_OM/cake_d_c_users.po similarity index 100% rename from resources/locales/ar_OM/users.po rename to resources/locales/ar_OM/cake_d_c_users.po diff --git a/resources/locales/users.pot b/resources/locales/cake_d_c_users.pot similarity index 100% rename from resources/locales/users.pot rename to resources/locales/cake_d_c_users.pot diff --git a/resources/locales/de_DE/users.mo b/resources/locales/de_DE/cake_d_c_users.mo similarity index 100% rename from resources/locales/de_DE/users.mo rename to resources/locales/de_DE/cake_d_c_users.mo diff --git a/resources/locales/de_DE/users.po b/resources/locales/de_DE/cake_d_c_users.po similarity index 100% rename from resources/locales/de_DE/users.po rename to resources/locales/de_DE/cake_d_c_users.po diff --git a/resources/locales/es/users.mo b/resources/locales/es/cake_d_c_users.mo similarity index 100% rename from resources/locales/es/users.mo rename to resources/locales/es/cake_d_c_users.mo diff --git a/resources/locales/es/users.po b/resources/locales/es/cake_d_c_users.po similarity index 100% rename from resources/locales/es/users.po rename to resources/locales/es/cake_d_c_users.po diff --git a/resources/locales/fr_FR/users.mo b/resources/locales/fr_FR/cake_d_c_users.mo similarity index 100% rename from resources/locales/fr_FR/users.mo rename to resources/locales/fr_FR/cake_d_c_users.mo diff --git a/resources/locales/fr_FR/users.po b/resources/locales/fr_FR/cake_d_c_users.po similarity index 100% rename from resources/locales/fr_FR/users.po rename to resources/locales/fr_FR/cake_d_c_users.po diff --git a/resources/locales/hu_HU/users.mo b/resources/locales/hu_HU/cake_d_c_users.mo similarity index 100% rename from resources/locales/hu_HU/users.mo rename to resources/locales/hu_HU/cake_d_c_users.mo diff --git a/resources/locales/hu_HU/users.po b/resources/locales/hu_HU/cake_d_c_users.po similarity index 100% rename from resources/locales/hu_HU/users.po rename to resources/locales/hu_HU/cake_d_c_users.po diff --git a/resources/locales/it_IT/users.mo b/resources/locales/it_IT/cake_d_c_users.mo similarity index 100% rename from resources/locales/it_IT/users.mo rename to resources/locales/it_IT/cake_d_c_users.mo diff --git a/resources/locales/it_IT/users.po b/resources/locales/it_IT/cake_d_c_users.po similarity index 100% rename from resources/locales/it_IT/users.po rename to resources/locales/it_IT/cake_d_c_users.po diff --git a/resources/locales/pl/users.mo b/resources/locales/pl/cake_d_c_users.mo similarity index 100% rename from resources/locales/pl/users.mo rename to resources/locales/pl/cake_d_c_users.mo diff --git a/resources/locales/pl/users.po b/resources/locales/pl/cake_d_c_users.po similarity index 100% rename from resources/locales/pl/users.po rename to resources/locales/pl/cake_d_c_users.po diff --git a/resources/locales/pt_BR/users.mo b/resources/locales/pt_BR/cake_d_c_users.mo similarity index 100% rename from resources/locales/pt_BR/users.mo rename to resources/locales/pt_BR/cake_d_c_users.mo diff --git a/resources/locales/pt_BR/users.po b/resources/locales/pt_BR/cake_d_c_users.po similarity index 100% rename from resources/locales/pt_BR/users.po rename to resources/locales/pt_BR/cake_d_c_users.po diff --git a/resources/locales/sv/users.mo b/resources/locales/sv/cake_d_c_users.mo similarity index 100% rename from resources/locales/sv/users.mo rename to resources/locales/sv/cake_d_c_users.mo diff --git a/resources/locales/sv/users.po b/resources/locales/sv/cake_d_c_users.po similarity index 100% rename from resources/locales/sv/users.po rename to resources/locales/sv/cake_d_c_users.po diff --git a/resources/locales/tr_TR/users.mo b/resources/locales/tr_TR/cake_d_c_users.mo similarity index 100% rename from resources/locales/tr_TR/users.mo rename to resources/locales/tr_TR/cake_d_c_users.mo diff --git a/resources/locales/tr_TR/users.po b/resources/locales/tr_TR/cake_d_c_users.po similarity index 100% rename from resources/locales/tr_TR/users.po rename to resources/locales/tr_TR/cake_d_c_users.po diff --git a/resources/locales/uk/users.mo b/resources/locales/uk/cake_d_c_users.mo similarity index 100% rename from resources/locales/uk/users.mo rename to resources/locales/uk/cake_d_c_users.mo diff --git a/resources/locales/uk/users.po b/resources/locales/uk/cake_d_c_users.po similarity index 100% rename from resources/locales/uk/users.po rename to resources/locales/uk/cake_d_c_users.po diff --git a/src/Authenticator/SocialAuthTrait.php b/src/Authenticator/SocialAuthTrait.php index 9f3cef77c..962db7f8d 100644 --- a/src/Authenticator/SocialAuthTrait.php +++ b/src/Authenticator/SocialAuthTrait.php @@ -26,7 +26,7 @@ trait SocialAuthTrait * @param array $rawData social user raw data * @return \Authentication\Authenticator\Result */ - protected function identify($rawData) + protected function identify($rawData): Result { try { $user = $this->getIdentifier()->identify([SocialIdentifier::CREDENTIAL_KEY => $rawData]); diff --git a/src/Authenticator/SocialPendingEmailAuthenticator.php b/src/Authenticator/SocialPendingEmailAuthenticator.php index a62964b23..3ab276066 100644 --- a/src/Authenticator/SocialPendingEmailAuthenticator.php +++ b/src/Authenticator/SocialPendingEmailAuthenticator.php @@ -46,7 +46,7 @@ class SocialPendingEmailAuthenticator extends AbstractAuthenticator * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'loginUrl' => [ 'plugin' => 'CakeDC/Users', 'controller' => 'Users', diff --git a/src/Command/Logic/ChangeUserActiveTrait.php b/src/Command/Logic/ChangeUserActiveTrait.php new file mode 100644 index 000000000..be8dd41e1 --- /dev/null +++ b/src/Command/Logic/ChangeUserActiveTrait.php @@ -0,0 +1,33 @@ +getArgumentAt(0); + if (empty($username)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + $data = [ + 'active' => $active, + ]; + + return $this->_updateUser($io, $username, $data); + } +} diff --git a/src/Command/Logic/CreateUserTrait.php b/src/Command/Logic/CreateUserTrait.php new file mode 100644 index 000000000..a1ad1739d --- /dev/null +++ b/src/Command/Logic/CreateUserTrait.php @@ -0,0 +1,126 @@ +addOptions([ + 'username' => ['short' => 'u', 'help' => 'The username for the new user'], + 'password' => ['short' => 'p', 'help' => 'The password for the new user'], + 'email' => ['short' => 'e', 'help' => 'The email for the new user'], + 'role' => ['short' => 'r', 'help' => 'The role for the new user'], + ]); + } + + /** + * Create a new user or superuser + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @param array $template template with deafault user values + * @return void + */ + protected function _createUser(Arguments $args, ConsoleIo $io, $template) + { + /** + * @var \CakeDC\Users\Model\Table\UsersTable $UsersTable + */ + $UsersTable = $this->getTableLocator()->get('Users'); + $username = $args->getOption('username'); + $password = $args->getOption('password'); + $email = $args->getOption('email'); + $role = $args->getOption('role'); + if (empty($username)) { + $username = empty($template['username']) + ? $this->_generateRandomUsername() + : $template['username']; + } + + $password = $password ?: $this->_generateRandomPassword(); + $email = $email ?: $username . '@example.com'; + $role = $role ?: $template['role']; + + $user = [ + 'username' => $UsersTable->generateUniqueUsername($username), + 'email' => $email, + 'password' => $password, + 'active' => 1, + ]; + + $userEntity = $UsersTable->newEntity($user); + $userEntity->is_superuser = empty($template['is_superuser']) ? + false : $template['is_superuser']; + $userEntity->role = $role; + $savedUser = $UsersTable->save($userEntity); + + if (is_object($savedUser)) { + if ($savedUser->is_superuser) { + $io->out(__d('cake_d_c/users', 'Superuser added:')); + } else { + $io->out(__d('cake_d_c/users', 'User added:')); + } + $io->out(__d('cake_d_c/users', 'Id: {0}', $savedUser->id)); + $io->out(__d('cake_d_c/users', 'Username: {0}', $savedUser->username)); + $io->out(__d('cake_d_c/users', 'Email: {0}', $savedUser->email)); + $io->out(__d('cake_d_c/users', 'Role: {0}', $savedUser->role)); + $io->out(__d('cake_d_c/users', 'Password: {0}', $password)); + } else { + $io->out(__d('cake_d_c/users', 'User could not be added:')); + + collection($userEntity->getErrors())->each(function ($error, $field) use ($io) { + $io->out(__d('cake_d_c/users', 'Field: {0} Error: {1}', $field, implode(',', $error))); + }); + } + } + + /** + * Generates a random password. + * + * @return string + */ + protected function _generateRandomPassword() + { + return str_replace('-', '', Text::uuid()); + } + + /** + * Generates a random username based on a list of preexisting ones. + * + * @return string + */ + protected function _generateRandomUsername() + { + return $this->_usernameSeed[array_rand($this->_usernameSeed)]; + } +} diff --git a/src/Command/Logic/UpdateUserTrait.php b/src/Command/Logic/UpdateUserTrait.php new file mode 100644 index 000000000..719644d14 --- /dev/null +++ b/src/Command/Logic/UpdateUserTrait.php @@ -0,0 +1,43 @@ +getTableLocator()->get('Users'); + $user = $UsersTable->find()->where(['username' => $username])->first(); + if (!is_object($user)) { + $io->abort(__d('cake_d_c/users', 'The user was not found.')); + } + /** + * @var \Cake\Datasource\EntityInterface $user + */ + $user = $UsersTable->patchEntity($user, $data); + collection($data)->filter(function ($value, $field) use ($user) { + return !$user->isAccessible($field); + })->each(function ($value, $field) use (&$user) { + $user->{$field} = $value; + }); + + return $UsersTable->saveOrFail($user); + } +} diff --git a/src/Command/UsersActivateUserCommand.php b/src/Command/UsersActivateUserCommand.php new file mode 100644 index 000000000..1e6e0e7b8 --- /dev/null +++ b/src/Command/UsersActivateUserCommand.php @@ -0,0 +1,54 @@ +setDescription(__d('cake_d_c/users', 'Activate an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $user = $this->_changeUserActive($args, $io, true); + $io->out(__d('cake_d_c/users', 'User was activated: {0}', $user->username)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users activate_user'; + } +} diff --git a/src/Command/UsersAddSuperuserCommand.php b/src/Command/UsersAddSuperuserCommand.php new file mode 100644 index 000000000..041f60cad --- /dev/null +++ b/src/Command/UsersAddSuperuserCommand.php @@ -0,0 +1,41 @@ +_createUser($args, $io, [ + 'username' => 'superadmin', + 'role' => 'superuser', + 'is_superuser' => true, + ]); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users add_superuser'; + } +} diff --git a/src/Command/UsersAddUserCommand.php b/src/Command/UsersAddUserCommand.php new file mode 100644 index 000000000..e02955754 --- /dev/null +++ b/src/Command/UsersAddUserCommand.php @@ -0,0 +1,40 @@ +_createUser($args, $io, [ + 'role' => Configure::read('Users.Registration.defaultRole') ?: 'user', + ]); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users add_user'; + } +} diff --git a/src/Command/UsersChangeApiTokenCommand.php b/src/Command/UsersChangeApiTokenCommand.php new file mode 100644 index 000000000..727c0dbae --- /dev/null +++ b/src/Command/UsersChangeApiTokenCommand.php @@ -0,0 +1,69 @@ +setDescription(__d('cake_d_c/users', 'Change the api token for an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $username = $args->getArgumentAt(0); + $token = $args->getArgumentAt(1); + if (empty($username)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($token)) { + $io->abort(__d('cake_d_c/users', 'Please enter a token.')); + } + $data = [ + 'api_token' => $token, + ]; + $savedUser = $this->_updateUser($io, $username, $data); + /** + * @var \CakeDC\Users\Model\Entity\User $savedUser + */ + $io->out(__d('cake_d_c/users', 'Api token changed for user: {0}', $username)); + $io->out(__d('cake_d_c/users', 'New token: {0}', $savedUser->api_token)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users change_api_token'; + } +} diff --git a/src/Command/UsersChangeRoleCommand.php b/src/Command/UsersChangeRoleCommand.php new file mode 100644 index 000000000..a1351eca8 --- /dev/null +++ b/src/Command/UsersChangeRoleCommand.php @@ -0,0 +1,66 @@ +setDescription(__d('cake_d_c/users', 'Change the role for an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $username = $args->getArgumentAt(0); + $role = $args->getArgumentAt(1); + if (empty($username)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($role)) { + $io->abort(__d('cake_d_c/users', 'Please enter a role.')); + } + $data = [ + 'role' => $role, + ]; + $savedUser = $this->_updateUser($io, $username, $data); + $io->out(__d('cake_d_c/users', 'Role changed for user: {0}', $username)); + $io->out(__d('cake_d_c/users', 'New role: {0}', $savedUser->role)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users change_role'; + } +} diff --git a/src/Command/UsersDeactivateUserCommand.php b/src/Command/UsersDeactivateUserCommand.php new file mode 100644 index 000000000..d6ff40446 --- /dev/null +++ b/src/Command/UsersDeactivateUserCommand.php @@ -0,0 +1,54 @@ +setDescription(__d('cake_d_c/users', 'Deactivate an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $user = $this->_changeUserActive($args, $io, false); + $io->out(__d('cake_d_c/users', 'User was de-activated: {0}', $user->username)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users deactivate_user'; + } +} diff --git a/src/Command/UsersDeleteUserCommand.php b/src/Command/UsersDeleteUserCommand.php new file mode 100644 index 000000000..0c8304c88 --- /dev/null +++ b/src/Command/UsersDeleteUserCommand.php @@ -0,0 +1,69 @@ +setDescription(__d('cake_d_c/users', 'Delete an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $username = $args->getArgumentAt(0); + if (empty($username)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + /** + * @var \CakeDC\Users\Model\Table\UsersTable $UsersTable + */ + $UsersTable = $this->getTableLocator()->get('Users'); + /** + * @var \Cake\Datasource\EntityInterface $user + */ + $user = $UsersTable->find()->where(['username' => $username])->firstOrFail(); + if (isset($UsersTable->SocialAccounts)) { + $UsersTable->SocialAccounts->deleteAll(['user_id' => $user->id]); + } + $deleteUser = $UsersTable->delete($user); + if (!$deleteUser) { + $io->abort(__d('cake_d_c/users', 'The user {0} was not deleted. Please try again', $username)); + } + $io->out(__d('cake_d_c/users', 'The user {0} was deleted successfully', $username)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users delete_user'; + } +} diff --git a/src/Command/UsersPasswordEmailCommand.php b/src/Command/UsersPasswordEmailCommand.php new file mode 100644 index 000000000..712e2ad7a --- /dev/null +++ b/src/Command/UsersPasswordEmailCommand.php @@ -0,0 +1,76 @@ +setDescription(__d('cake_d_c/users', 'Reset the password via email')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $reference = $args->getArgumentAt(0); + if (empty($reference)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username or email.')); + } + /** + * @var \CakeDC\Users\Model\Table\UsersTable $UsersTable + */ + $UsersTable = $this->getTableLocator()->get('Users'); + $resetUser = $UsersTable->resetToken($reference, [ + 'expiration' => Configure::read('Users.Token.expiration'), + 'checkActive' => false, + 'sendEmail' => true, + ]); + if ($resetUser) { + $msg = __d( + 'cake_d_c/users', + 'Please ask the user to check the email to continue with password reset process' + ); + $io->out($msg); + } else { + $msg = __d( + 'cake_d_c/users', + 'The password token could not be generated. Please try again' + ); + $io->abort($msg); + } + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users password_email'; + } +} diff --git a/src/Command/UsersResetAllPasswordsCommand.php b/src/Command/UsersResetAllPasswordsCommand.php new file mode 100644 index 000000000..263ac5e4f --- /dev/null +++ b/src/Command/UsersResetAllPasswordsCommand.php @@ -0,0 +1,70 @@ +setDescription(__d('cake_d_c/users', 'Reset the password for all users')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return int|null|void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $password = $args->getArgumentAt(0); + + if (empty($password)) { + $io->abort(__d('cake_d_c/users', 'Please enter a password.')); + } + $hashedPassword = $this->_generatedHashedPassword($password); + $this->getTableLocator()->get('Users')->updateAll(['password' => $hashedPassword], ['id IS NOT NULL']); + $io->out(__d('cake_d_c/users', 'Password changed for all users')); + $io->out(__d('cake_d_c/users', 'New password: {0}', $password)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users reset_all_passwords'; + } + + /** + * Hash a password + * + * @param string $password password + * @return string + */ + protected function _generatedHashedPassword($password) + { + return (new User())->hashPassword($password); + } +} diff --git a/src/Command/UsersResetPasswordCommand.php b/src/Command/UsersResetPasswordCommand.php new file mode 100644 index 000000000..03a8e24de --- /dev/null +++ b/src/Command/UsersResetPasswordCommand.php @@ -0,0 +1,66 @@ +setDescription(__d('cake_d_c/users', 'Reset the password for an specific user')); + } + + /** + * Implement this method with your command's logic. + * + * @param \Cake\Console\Arguments $args The command arguments. + * @param \Cake\Console\ConsoleIo $io The console io + * @return void The exit code or null for success + */ + public function execute(Arguments $args, ConsoleIo $io) + { + $username = $args->getArgumentAt(0); + $password = $args->getArgumentAt(1); + if (empty($username)) { + $io->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($password)) { + $io->abort(__d('cake_d_c/users', 'Please enter a password.')); + } + $data = [ + 'password' => $password, + ]; + $this->_updateUser($io, $username, $data); + $io->out(__d('cake_d_c/users', 'Password changed for user: {0}', $username)); + $io->out(__d('cake_d_c/users', 'New password: {0}', $password)); + } + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'users reset_password'; + } +} diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 68c4951e2..f2a6011c9 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -28,7 +28,7 @@ class AppController extends BaseController public function initialize(): void { parent::initialize(); - $this->loadComponent('Security'); + $this->loadComponent('FormProtection'); if ($this->request->getParam('_csrfToken') === false) { $this->loadComponent('Csrf'); } diff --git a/src/Controller/Component/LoginComponent.php b/src/Controller/Component/LoginComponent.php index 76794abd1..28b97e429 100644 --- a/src/Controller/Component/LoginComponent.php +++ b/src/Controller/Component/LoginComponent.php @@ -32,11 +32,9 @@ class LoginComponent extends Component use IsAuthorizedTrait; /** - * Default configuration. - * - * @var array + * @inheritDoc */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'defaultMessage' => null, 'messages' => [], 'targetAuthenticator' => null, @@ -235,7 +233,7 @@ protected function checkSafeHost(?string $queryRedirect = null): bool */ protected function updateLastLogin($user) { - $now = \Cake\I18n\FrozenTime::now(); + $now = \Cake\I18n\DateTime::now(); $user->set('last_login', $now); $this->getController()->getUsersTable()->updateAll( ['last_login' => $now], diff --git a/src/Controller/Traits/CustomUsersTableTrait.php b/src/Controller/Traits/CustomUsersTableTrait.php index c003996ca..b02b65827 100644 --- a/src/Controller/Traits/CustomUsersTableTrait.php +++ b/src/Controller/Traits/CustomUsersTableTrait.php @@ -25,7 +25,7 @@ trait CustomUsersTableTrait /** * @var \Cake\ORM\Table|null */ - protected $_usersTable = null; + protected ?Table $_usersTable = null; /** * Gets the users table instance diff --git a/src/Controller/Traits/LinkSocialTrait.php b/src/Controller/Traits/LinkSocialTrait.php index a2fcf802f..335d0bd88 100644 --- a/src/Controller/Traits/LinkSocialTrait.php +++ b/src/Controller/Traits/LinkSocialTrait.php @@ -27,7 +27,7 @@ trait LinkSocialTrait * * @param string $alias of the provider. * @throws \Cake\Http\Exception\NotFoundException Quando o provider informado não existe - * @return \Cake\Http\Response Redirects on successful + * @return \Cake\Http\Response Redirects on successful */ public function linkSocial($alias = null) { @@ -49,7 +49,7 @@ public function linkSocial($alias = null) * * @param string $alias of the provider. * @throws \Cake\Http\Exception\NotFoundException Quando o provider informado não existe - * @return \Cake\Http\Response Redirects to profile if okay or error + * @return \Cake\Http\Response Redirects to profile if okay or error */ public function callbackLinkSocial($alias = null) { diff --git a/src/Controller/Traits/OneTimePasswordVerifyTrait.php b/src/Controller/Traits/OneTimePasswordVerifyTrait.php index 9538569a6..561802936 100644 --- a/src/Controller/Traits/OneTimePasswordVerifyTrait.php +++ b/src/Controller/Traits/OneTimePasswordVerifyTrait.php @@ -116,8 +116,8 @@ protected function onVerifyGetSecret($user) // catching sql exception in case of any sql inconsistencies try { - $query = $this->getUsersTable()->query(); - $query->update() + $query = $this->getUsersTable() + ->updateQuery() ->set(['secret' => $secret]) ->where(['id' => $user['id']]); $query->execute(); @@ -183,7 +183,8 @@ protected function onPostVerifyCodeOkay($loginAction, $user) unset($user['secret']); if (!$user['secret_verified']) { - $this->getUsersTable()->query()->update() + $this->getUsersTable() + ->updateQuery() ->set(['secret_verified' => true]) ->where(['id' => $user['id']]) ->execute(); diff --git a/src/Controller/Traits/PasswordManagementTrait.php b/src/Controller/Traits/PasswordManagementTrait.php index 09878ae2a..ad5f80b0f 100644 --- a/src/Controller/Traits/PasswordManagementTrait.php +++ b/src/Controller/Traits/PasswordManagementTrait.php @@ -35,7 +35,7 @@ trait PasswordManagementTrait * Can be used while logged in for own password, as a superuser on any user, or while not logged in for reset * reset password with session key (email token has already been validated) * - * @param int|string|null $id user_id, null for logged in user id + * @param string|int|null $id user_id, null for logged in user id * @return mixed */ public function changePassword($id = null) @@ -107,7 +107,8 @@ public function changePassword($id = null) $result = $this->getUsersTable()->changePassword($user); if ($result) { $event = $this->dispatchEvent(Plugin::EVENT_AFTER_CHANGE_PASSWORD, ['user' => $result]); - if (!empty($event) && is_array($event->getResult())) { + $eventResult = $event->getResult(); + if (!empty($eventResult) && is_array($eventResult)) { return $this->redirect($event->getResult()); } $this->Flash->success(__d('cake_d_c/users', 'Password has been changed successfully')); @@ -144,7 +145,7 @@ public function resetPassword($token = null) /** * Reset password * - * @return void|\Cake\Http\Response + * @return \Cake\Http\Response|void */ public function requestResetPassword() { @@ -195,8 +196,8 @@ public function resetOneTimePasswordAuthenticator($id = null) { if ($this->getRequest()->is('post')) { try { - $query = $this->getUsersTable()->query(); - $query->update() + $query = $this->getUsersTable() + ->updateQuery() ->set(['secret_verified' => false, 'secret' => null]) ->where(['id' => $id]); $query->execute(); @@ -208,6 +209,6 @@ public function resetOneTimePasswordAuthenticator($id = null) } } - return $this->redirect($this->getRequest()->referer()); + return $this->redirect($this->getRequest()->referer() ?? '/'); } } diff --git a/src/Controller/Traits/ProfileTrait.php b/src/Controller/Traits/ProfileTrait.php index a67ac991c..8b4b25cd4 100644 --- a/src/Controller/Traits/ProfileTrait.php +++ b/src/Controller/Traits/ProfileTrait.php @@ -13,7 +13,6 @@ namespace CakeDC\Users\Controller\Traits; -use Cake\Controller\Component\AuthComponent; use Cake\Core\Configure; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\Exception\RecordNotFoundException; @@ -41,10 +40,10 @@ public function profile($id = null) $id = $loggedUserId; } try { - $appContain = (array)Configure::read('Auth.authenticate.' . AuthComponent::ALL . '.contain'); + $appContain = (array)Configure::read('Auth.Profile.contain'); $socialContain = Configure::read('Users.Social.login') ? ['SocialAccounts'] : []; $user = $this->getUsersTable()->get($id, [ - 'contain' => array_merge((array)$appContain, (array)$socialContain), + 'contain' => array_merge($appContain, $socialContain), ]); $this->set('avatarPlaceholder', Configure::read('Users.Avatar.placeholder')); if ($user->id === $loggedUserId) { @@ -53,11 +52,11 @@ public function profile($id = null) } catch (RecordNotFoundException $ex) { $this->Flash->error(__d('cake_d_c/users', 'User was not found')); - return $this->redirect($this->getRequest()->referer()); + return $this->redirect($this->getRequest()->referer() ?? '/'); } catch (InvalidPrimaryKeyException $ex) { $this->Flash->error(__d('cake_d_c/users', 'Not authorized, please login first')); - return $this->redirect($this->getRequest()->referer()); + return $this->redirect($this->getRequest()->referer() ?? '/'); } $this->set(['user' => $user, 'isCurrentUser' => $isCurrentUser]); $this->set('_serialize', ['user', 'isCurrentUser']); diff --git a/src/Controller/Traits/SimpleCrudTrait.php b/src/Controller/Traits/SimpleCrudTrait.php index 33346d298..90eafb9f0 100644 --- a/src/Controller/Traits/SimpleCrudTrait.php +++ b/src/Controller/Traits/SimpleCrudTrait.php @@ -29,7 +29,7 @@ trait SimpleCrudTrait */ public function index() { - $table = $this->loadModel(); + $table = $this->fetchTable(); $tableAlias = $table->getAlias(); $this->set($tableAlias, $this->paginate($table)); $this->set('tableAlias', $tableAlias); @@ -45,7 +45,7 @@ public function index() */ public function view($id = null) { - $table = $this->loadModel(); + $table = $this->fetchTable(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ 'contain' => [], @@ -62,7 +62,7 @@ public function view($id = null) */ public function add() { - $table = $this->loadModel(); + $table = $this->fetchTable(); $tableAlias = $table->getAlias(); $entity = $table->newEmptyEntity(); $this->set($tableAlias, $entity); @@ -90,7 +90,7 @@ public function add() */ public function edit($id = null) { - $table = $this->loadModel(); + $table = $this->fetchTable(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ 'contain' => [], @@ -121,7 +121,7 @@ public function edit($id = null) public function delete($id = null) { $this->getRequest()->allowMethod(['post', 'delete']); - $table = $this->loadModel(); + $table = $this->fetchTable(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ 'contain' => [], diff --git a/src/Controller/Traits/UserValidationTrait.php b/src/Controller/Traits/UserValidationTrait.php index 16bcf90a0..63ec46621 100644 --- a/src/Controller/Traits/UserValidationTrait.php +++ b/src/Controller/Traits/UserValidationTrait.php @@ -43,8 +43,9 @@ public function validate($type = null, $token = null) $result = $this->getUsersTable()->validate($token, 'activateUser'); if ($result) { $event = $this->dispatchEvent(Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION, ['user' => $result]); - if (!empty($event) && is_array($event->getResult())) { - return $this->redirect($event->getResult()); + $eventResult = $event->getResult(); + if (!empty($eventResult) && is_array($eventResult)) { + return $this->redirect($eventResult); } $this->Flash->success(__d('cake_d_c/users', 'User account validated successfully')); } else { @@ -75,8 +76,9 @@ public function validate($type = null, $token = null) $this->Flash->error(__d('cake_d_c/users', 'Invalid token or user account already validated')); } catch (TokenExpiredException $ex) { $event = $this->dispatchEvent(Plugin::EVENT_ON_EXPIRED_TOKEN, ['type' => $type]); - if (!empty($event) && is_array($event->getResult())) { - return $this->redirect($event->getResult()); + $eventResult = $event->getResult(); + if (!empty($eventResult) && is_array($eventResult)) { + return $this->redirect($eventResult); } $this->Flash->error(__d('cake_d_c/users', 'Token already expired')); } @@ -108,7 +110,7 @@ public function resendTokenValidation() ) { $event = $this->dispatchEvent(Plugin::EVENT_AFTER_RESEND_TOKEN_VALIDATION); $result = $event->getResult(); - if (!empty($event) && is_array($result)) { + if (!empty($result) && is_array($result)) { return $this->redirect($result); } $this->Flash->success(__d( diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index 096bbc9c1..0a80a7d31 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -28,7 +28,7 @@ * Users Controller * * @property \CakeDC\Users\Model\Table\UsersTable $Users - * @property \Cake\Controller\Component\SecurityComponent $Security + * @property \Cake\Controller\Component\FormProtectionComponent|null $FormProtection */ class UsersController extends AppController { @@ -51,8 +51,8 @@ class UsersController extends AppController public function initialize(): void { parent::initialize(); - if ($this->components()->has('Security')) { - $this->Security->setConfig( + if ($this->components()->has('FormProtection')) { + $this->FormProtection->setConfig( 'unlockedActions', [ 'login', diff --git a/src/Exception/AccountNotActiveException.php b/src/Exception/AccountNotActiveException.php index a7d1f3741..bcd1dcf78 100644 --- a/src/Exception/AccountNotActiveException.php +++ b/src/Exception/AccountNotActiveException.php @@ -15,7 +15,7 @@ class AccountNotActiveException extends \Cake\Core\Exception\CakeException { - protected $_messageTemplate = '/a/validate/%s/%s'; + protected string $_messageTemplate = '/a/validate/%s/%s'; /** * AccountNotActiveException constructor. diff --git a/src/Exception/SocialAuthenticationException.php b/src/Exception/SocialAuthenticationException.php index bb9e03bf5..422ab39ba 100644 --- a/src/Exception/SocialAuthenticationException.php +++ b/src/Exception/SocialAuthenticationException.php @@ -14,6 +14,6 @@ class SocialAuthenticationException extends \Cake\Core\Exception\CakeException { - protected $_messageTemplate = 'Could not autheticate user'; - protected $_defaultCode = 400; + protected string $_messageTemplate = 'Could not autheticate user'; + protected int $_defaultCode = 400; } diff --git a/src/Identifier/SocialIdentifier.php b/src/Identifier/SocialIdentifier.php index 7a75ecdc4..ab8028b63 100644 --- a/src/Identifier/SocialIdentifier.php +++ b/src/Identifier/SocialIdentifier.php @@ -33,7 +33,7 @@ class SocialIdentifier extends AbstractIdentifier * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'authFinder' => 'active', ]; @@ -43,7 +43,7 @@ class SocialIdentifier extends AbstractIdentifier * @param array $credentials Authentication credentials * @return \ArrayAccess|array|null */ - public function identify(array $credentials) + public function identify(array $credentials): \ArrayAccess|array|null { if (!isset($credentials[self::CREDENTIAL_KEY])) { return null; @@ -66,7 +66,7 @@ public function identify(array $credentials) * Get query object for fetching user from database. * * @param \Cake\Datasource\EntityInterface $user The user. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ protected function findUser($user) { diff --git a/src/Loader/MiddlewareQueueLoader.php b/src/Loader/MiddlewareQueueLoader.php index bed495ff0..240cd094c 100644 --- a/src/Loader/MiddlewareQueueLoader.php +++ b/src/Loader/MiddlewareQueueLoader.php @@ -42,7 +42,7 @@ class MiddlewareQueueLoader * For 'Auth.Authorization.loadRbacMiddleware' load RbacMiddleware * * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update. - * @param \Authentication\AuthenticationServiceProviderInterface $authenticationServiceProvider Loads the auth service + * @param \Authentication\AuthenticationServiceProviderInterface $authenticationServiceProvider Loads the auth service * @param \Authorization\AuthorizationServiceProviderInterface $authorizationServiceProvider Loads the authorization service * @return \Cake\Http\MiddlewareQueue */ diff --git a/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php b/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php index 709a3fe43..9019dca98 100644 --- a/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php +++ b/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php @@ -33,7 +33,7 @@ class DefaultRedirectHandler extends CakeRedirectHandler /** * @inheritDoc */ - protected $defaultOptions = [ + protected array $defaultOptions = [ 'exceptions' => [ 'MissingIdentityException' => MissingIdentityException::class, 'ForbiddenException' => ForbiddenException::class, diff --git a/src/Model/Behavior/AuthFinderBehavior.php b/src/Model/Behavior/AuthFinderBehavior.php index 3dc19fe2b..0cac46186 100644 --- a/src/Model/Behavior/AuthFinderBehavior.php +++ b/src/Model/Behavior/AuthFinderBehavior.php @@ -14,7 +14,7 @@ namespace CakeDC\Users\Model\Behavior; use Cake\ORM\Behavior; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; /** * Implement finders used by Auth @@ -24,10 +24,10 @@ class AuthFinderBehavior extends Behavior /** * Custom finder to filter active users * - * @param \Cake\ORM\Query $query Query object to modify - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query Query object to modify + * @return \Cake\ORM\Query\SelectQuery */ - public function findActive(Query $query) + public function findActive(SelectQuery $query) { $query->where([$this->_table->aliasField('active') => 1]); @@ -37,12 +37,12 @@ public function findActive(Query $query) /** * Custom finder to log in users * - * @param \Cake\ORM\Query $query Query object to modify + * @param \Cake\ORM\Query\SelectQuery $query Query object to modify * @param array $options Query options - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException */ - public function findAuth(Query $query, array $options = []) + public function findAuth(SelectQuery $query, array $options = []) { $identifier = $options['username'] ?? null; if (empty($identifier)) { diff --git a/src/Model/Behavior/BaseTokenBehavior.php b/src/Model/Behavior/BaseTokenBehavior.php index fbbe3cc89..4428b6af2 100644 --- a/src/Model/Behavior/BaseTokenBehavior.php +++ b/src/Model/Behavior/BaseTokenBehavior.php @@ -14,7 +14,7 @@ namespace CakeDC\Users\Model\Behavior; use Cake\Datasource\EntityInterface; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\ORM\Behavior; /** @@ -38,7 +38,7 @@ protected function _updateActive(EntityInterface $user, $validateEmail, $tokenEx $user->updateToken($tokenExpiration); } else { $user['active'] = true; - $user['activation_date'] = new FrozenTime(); + $user['activation_date'] = new DateTime(); } return $user; diff --git a/src/Model/Behavior/LinkSocialBehavior.php b/src/Model/Behavior/LinkSocialBehavior.php index 14096acea..9f5c6ec0b 100644 --- a/src/Model/Behavior/LinkSocialBehavior.php +++ b/src/Model/Behavior/LinkSocialBehavior.php @@ -14,7 +14,7 @@ namespace CakeDC\Users\Model\Behavior; use Cake\Datasource\EntityInterface; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\ORM\Behavior; /** @@ -23,11 +23,9 @@ class LinkSocialBehavior extends Behavior { /** - * Default configuration. - * - * @var array + * @inheritDoc */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Link an user account with a social account (facebook, google) @@ -124,7 +122,7 @@ protected function populateSocialAccount($socialAccount, $data) $accountData['token_expires'] = null; $expires = $data['credentials']['expires'] ?? null; if (!empty($expires)) { - $expiresTime = new FrozenTime(); + $expiresTime = new DateTime(); $accountData['token_expires'] = $expiresTime->setTimestamp($expires)->format('Y-m-d H:i:s'); } diff --git a/src/Model/Behavior/RegisterBehavior.php b/src/Model/Behavior/RegisterBehavior.php index b2fb4fb10..2ca644465 100644 --- a/src/Model/Behavior/RegisterBehavior.php +++ b/src/Model/Behavior/RegisterBehavior.php @@ -31,11 +31,11 @@ class RegisterBehavior extends BaseTokenBehavior /** * @var bool */ - protected $validateEmail; + protected bool $validateEmail = true; /** * @var bool */ - protected $useTos; + protected bool $useTos = true; /** * Constructor hook method. @@ -56,11 +56,11 @@ public function initialize(array $config): void * @param \Cake\Datasource\EntityInterface $user User information * @param array $data User information * @param array $options ['tokenExpiration] - * @return bool|\Cake\Datasource\EntityInterface + * @return \Cake\Datasource\EntityInterface|bool */ public function register($user, $data, $options) { - $validateEmail = $options['validate_email'] ?? null; + $validateEmail = (bool)($options['validate_email'] ?? null); $tokenExpiration = $options['token_expiration'] ?? null; $validator = $options['validator'] ?? null; if (is_string($validator)) { @@ -161,7 +161,7 @@ public function buildValidator(\Cake\Event\EventInterface $event, Validator $val */ protected function _emailValidator(Validator $validator, $validateEmail) { - $this->validateEmail = $validateEmail; + $this->validateEmail = (bool)$validateEmail; $validator ->add('email', 'valid', ['rule' => 'email']) ->notBlank('email', __d('cake_d_c/users', 'This field is required'), function ($context) { diff --git a/src/Model/Behavior/SocialBehavior.php b/src/Model/Behavior/SocialBehavior.php index 7911ed802..14d2db492 100644 --- a/src/Model/Behavior/SocialBehavior.php +++ b/src/Model/Behavior/SocialBehavior.php @@ -38,7 +38,7 @@ class SocialBehavior extends BaseTokenBehavior * * @var string */ - protected $_username = 'username'; + protected string $_username = 'username'; /** * Initialize an action instance @@ -49,7 +49,7 @@ class SocialBehavior extends BaseTokenBehavior public function initialize(array $config): void { if (isset($config['username'])) { - $this->_username = $config['username']; + $this->_username = (string)$config['username']; } parent::initialize($config); @@ -63,7 +63,7 @@ public function initialize(array $config): void * @throws \InvalidArgumentException * @throws \CakeDC\Users\Exception\UserNotActiveException * @throws \CakeDC\Users\Exception\AccountNotActiveException - * @return bool|\Cake\Datasource\EntityInterface|mixed + * @return \Cake\Datasource\EntityInterface|mixed|bool */ public function socialLogin(array $data, array $options) { @@ -123,12 +123,12 @@ public function socialLogin(array $data, array $options) * @param array $data Array social user. * @param array $options Array option data. * @throws \CakeDC\Users\Exception\MissingEmailException - * @return bool|\Cake\Datasource\EntityInterface|mixed result of the save operation + * @return \Cake\Datasource\EntityInterface|mixed|bool result of the save operation */ protected function _createSocialUser($data, $options = []) { $useEmail = $options['use_email'] ?? null; - $validateEmail = $options['validate_email'] ?? null; + $validateEmail = (bool)($options['validate_email'] ?? null); $tokenExpiration = $options['token_expiration'] ?? null; $existingUser = null; $email = $data['email'] ?? null; @@ -159,9 +159,9 @@ protected function _createSocialUser($data, $options = []) * data to create a new one * * @param array $data Array social login. - * @param \Cake\Datasource\EntityInterface $existingUser user data. + * @param \Cake\Datasource\EntityInterface|null $existingUser user data. * @param string $useEmail email to use. - * @param string $validateEmail email to validate. + * @param bool $validateEmail email to validate. * @param string $tokenExpiration token_expires data. * @return \Cake\Datasource\EntityInterface * @todo refactor @@ -195,9 +195,9 @@ protected function _populateUser($data, $existingUser, $useEmail, $validateEmail $userData['username'] = Hash::get($email, 0); } else { $firstName = $userData['first_name'] ?? null; - $lastName = $userData['last_name'] ?? null; + $lastName = $userData['last_name']; $userData['username'] = strtolower($firstName . $lastName); - $userData['username'] = preg_replace('/[^A-Za-z0-9]/i', '', $userData['username'] ?? null); + $userData['username'] = preg_replace('/[^A-Za-z0-9]/i', '', $userData['username']); } } @@ -260,11 +260,11 @@ public function generateUniqueUsername($username) /** * Prepare a query to retrieve existing entity for social login * - * @param \Cake\ORM\Query $query The base query. + * @param \Cake\ORM\Query\SelectQuery $query The base query. * @param array $options Find options with email key. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function findExistingForSocialLogin(\Cake\ORM\Query $query, array $options) + public function findExistingForSocialLogin(\Cake\ORM\Query\SelectQuery $query, array $options) { return $query->where([ $this->_table->aliasField('email') => $options['email'], diff --git a/src/Model/Entity/SocialAccount.php b/src/Model/Entity/SocialAccount.php index c8429655f..e43205657 100644 --- a/src/Model/Entity/SocialAccount.php +++ b/src/Model/Entity/SocialAccount.php @@ -21,21 +21,17 @@ class SocialAccount extends Entity { /** - * Fields that can be mass assigned using newEntity() or patchEntity(). - * - * @var array + * @inheritDoc */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, ]; /** - * Fields that are excluded from JSON an array versions of the entity. - * - * @var array + * @inheritDoc */ - protected $_hidden = [ + protected array $_hidden = [ 'token', 'token_secret', 'token_expires', diff --git a/src/Model/Entity/User.php b/src/Model/Entity/User.php index e007fc912..fc7f1c2bf 100644 --- a/src/Model/Entity/User.php +++ b/src/Model/Entity/User.php @@ -13,8 +13,9 @@ namespace CakeDC\Users\Model\Entity; +use Authentication\PasswordHasher\DefaultPasswordHasher; use Cake\Core\Configure; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\ORM\Entity; use Cake\Utility\Security; @@ -25,20 +26,19 @@ * @property string $role * @property string $username * @property bool $is_superuser - * @property \Cake\I18n\Time|\Cake\I18n\FrozenTime $token_expires + * @property \Cake\I18n\Time|\Cake\I18n\DateTime $token_expires * @property string $token * @property string $api_token * @property array|string $additional_data * @property \CakeDC\Users\Model\Entity\SocialAccount[] $social_accounts + * @property string $password */ class User extends Entity { /** - * Fields that can be mass assigned using newEntity() or patchEntity(). - * - * @var array + * @inheritDoc */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'is_superuser' => false, @@ -46,11 +46,9 @@ class User extends Entity ]; /** - * Fields that are excluded from JSON an array versions of the entity. - * - * @var array + * @inheritDoc */ - protected $_hidden = [ + protected array $_hidden = [ 'password', 'token', 'token_expires', @@ -59,7 +57,7 @@ class User extends Entity /** * @param string $password password that will be set. - * @return bool|string + * @return string|bool */ protected function _setPassword($password) { @@ -68,7 +66,7 @@ protected function _setPassword($password) /** * @param string $password password that will be confirm. - * @return bool|string + * @return string|bool */ protected function _setConfirmPassword($password) { @@ -82,7 +80,7 @@ protected function _setConfirmPassword($password) protected function _setTos($tos) { if ((bool)$tos) { - $this->set('tos_date', FrozenTime::now()); + $this->set('tos_date', DateTime::now()); } return $tos; @@ -111,7 +109,7 @@ public function getPasswordHasher() { $passwordHasher = Configure::read('Users.passwordHasher'); if (!class_exists($passwordHasher)) { - $passwordHasher = \Cake\Auth\DefaultPasswordHasher::class; + $passwordHasher = DefaultPasswordHasher::class; } return new $passwordHasher(); @@ -142,7 +140,7 @@ public function tokenExpired() return true; } - return new FrozenTime($this->token_expires) < FrozenTime::now(); + return new DateTime($this->token_expires) < DateTime::now(); } /** @@ -186,7 +184,7 @@ protected function _getU2fRegistration() */ public function updateToken($tokenExpiration = 0) { - $expiration = new FrozenTime('now'); + $expiration = new DateTime('now'); $this->token_expires = $expiration->addSeconds($tokenExpiration); $this->token = bin2hex(Security::randomBytes(16)); } diff --git a/src/Model/Table/SocialAccountsTable.php b/src/Model/Table/SocialAccountsTable.php index 1d1578c19..f10397c09 100644 --- a/src/Model/Table/SocialAccountsTable.php +++ b/src/Model/Table/SocialAccountsTable.php @@ -13,7 +13,7 @@ namespace CakeDC\Users\Model\Table; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\RulesChecker; use Cake\ORM\Table; use Cake\Validation\Validator; @@ -123,10 +123,10 @@ public function buildRules(RulesChecker $rules): RulesChecker /** * Finder for active social accounts * - * @param \Cake\ORM\Query $query query - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query query + * @return \Cake\ORM\Query\SelectQuery */ - public function findActive(Query $query) + public function findActive(SelectQuery $query) { return $query->where([ $this->aliasField('active') => true, diff --git a/src/Model/Table/UsersTable.php b/src/Model/Table/UsersTable.php index 4e6954348..dfe788fa9 100644 --- a/src/Model/Table/UsersTable.php +++ b/src/Model/Table/UsersTable.php @@ -35,6 +35,7 @@ * @mixin \CakeDC\Users\Model\Behavior\RegisterBehavior * @mixin \CakeDC\Users\Model\Behavior\SocialAccountBehavior * @mixin \CakeDC\Users\Model\Behavior\SocialBehavior + * @property \CakeDC\Users\Model\Table\SocialAccountsTable $SocialAccounts */ class UsersTable extends Table { @@ -49,19 +50,19 @@ class UsersTable extends Table * * @var bool */ - public $isValidateEmail = false; + public bool $isValidateEmail = false; /** * Field additional_data is json * - * @param \Cake\Database\Schema\TableSchemaInterface $schema The table definition fetched from database. * @return \Cake\Database\Schema\TableSchemaInterface the altered schema */ - protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface + public function getSchema(): TableSchemaInterface { + $schema = parent::getSchema(); $schema->setColumnType('additional_data', 'json'); - return parent::_initializeSchema($schema); + return $schema; } /** diff --git a/src/Plugin.php b/src/Plugin.php index 1b9d08ece..aa4ef10ad 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -26,9 +26,10 @@ class Plugin extends BasePlugin /** * Plugin name. * - * @var string + * @var string|null */ - protected $name = 'CakeDC/Users'; + protected ?string $name = 'CakeDC/Users'; + public const EVENT_BEFORE_LOGIN = 'Users.Authentication.beforeLogin'; public const EVENT_AFTER_LOGIN = 'Users.Authentication.afterLogin'; public const EVENT_AFTER_LOGIN_FAILURE = 'Users.Authentication.afterLoginFailure'; diff --git a/src/Provider/AuthenticationServiceProvider.php b/src/Provider/AuthenticationServiceProvider.php index 1c4dcc3e4..18b14e484 100644 --- a/src/Provider/AuthenticationServiceProvider.php +++ b/src/Provider/AuthenticationServiceProvider.php @@ -27,7 +27,7 @@ class AuthenticationServiceProvider implements AuthenticationServiceProviderInte use ServiceProviderLoaderTrait; /** - * @param \Psr\Http\Message\ServerRequestInterface $request Http server request + * @param \Psr\Http\Message\ServerRequestInterface $request Http server request * @return \Authentication\AuthenticationServiceInterface */ public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface diff --git a/src/Provider/AuthorizationServiceProvider.php b/src/Provider/AuthorizationServiceProvider.php index 8c727f41a..cc91683d5 100644 --- a/src/Provider/AuthorizationServiceProvider.php +++ b/src/Provider/AuthorizationServiceProvider.php @@ -27,7 +27,7 @@ class AuthorizationServiceProvider implements AuthorizationServiceProviderInterf use ServiceProviderLoaderTrait; /** - * @param \Psr\Http\Message\ServerRequestInterface $request Http server request + * @param \Psr\Http\Message\ServerRequestInterface $request Http server request * @return \Authorization\AuthorizationService */ public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface diff --git a/src/Shell/UsersShell.php b/src/Shell/UsersShell.php deleted file mode 100644 index bd38e2e44..000000000 --- a/src/Shell/UsersShell.php +++ /dev/null @@ -1,452 +0,0 @@ -setDescription(__d('cake_d_c/users', 'Utilities for CakeDC Users Plugin')) - ->addSubcommand('activateUser', [ - 'help' => __d('cake_d_c/users', 'Activate an specific user'), - ]) - ->addSubcommand('addSuperuser', [ - 'help' => __d('cake_d_c/users', 'Add a new superadmin user for testing purposes'), - ]) - ->addSubcommand('addUser', [ - 'help' => __d('cake_d_c/users', 'Add a new user'), - ]) - ->addSubcommand('changeRole', [ - 'help' => __d('cake_d_c/users', 'Change the role for an specific user'), - ]) - ->addSubcommand('changeApiToken', [ - 'help' => __d('cake_d_c/users', 'Change the api token for an specific user'), - ]) - ->addSubcommand('deactivateUser', [ - 'help' => __d('cake_d_c/users', 'Deactivate an specific user'), - ]) - ->addSubcommand('deleteUser', [ - 'help' => __d('cake_d_c/users', 'Delete an specific user'), - ]) - ->addSubcommand('passwordEmail', [ - 'help' => __d('cake_d_c/users', 'Reset the password via email'), - ]) - ->addSubcommand('resetAllPasswords', [ - 'help' => __d('cake_d_c/users', 'Reset the password for all users'), - ]) - ->addSubcommand('resetPassword', [ - 'help' => __d('cake_d_c/users', 'Reset the password for an specific user'), - ]) - ->addOptions([ - 'username' => ['short' => 'u', 'help' => 'The username for the new user'], - 'password' => ['short' => 'p', 'help' => 'The password for the new user'], - 'email' => ['short' => 'e', 'help' => 'The email for the new user'], - 'role' => ['short' => 'r', 'help' => 'The role for the new user'], - ]); - - return $parser; - } - - /** - * initialize callback - * - * @return void - */ - public function initialize(): void - { - parent::initialize(); - $this->Users = $this->loadModel(Configure::read('Users.table')); - } - - /** - * Add a new user - * - * @return void - */ - public function addUser() - { - $this->_createUser(['role' => Configure::read('Users.Registration.defaultRole') ?: 'user']); - } - - /** - * Add a new superadmin user - * - * @return void - */ - public function addSuperuser() - { - $this->_createUser([ - 'username' => 'superadmin', - 'role' => 'superuser', - 'is_superuser' => true, - ]); - } - - /** - * Reset password for all user - * - * Arguments: - * - * - Password to be set - * - * @return void - */ - public function resetAllPasswords() - { - $password = Hash::get($this->args, 0); - if (empty($password)) { - $this->abort(__d('cake_d_c/users', 'Please enter a password.')); - } - $hashedPassword = $this->_generatedHashedPassword($password); - $this->Users->updateAll(['password' => $hashedPassword], ['id IS NOT NULL']); - $this->out(__d('cake_d_c/users', 'Password changed for all users')); - $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); - } - - /** - * Reset password for a user - * - * Arguments: - * - * - Username - * - Password to be set - * - * @return void - */ - public function resetPassword() - { - $username = Hash::get($this->args, 0); - $password = Hash::get($this->args, 1); - if (empty($username)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username.')); - } - if (empty($password)) { - $this->abort(__d('cake_d_c/users', 'Please enter a password.')); - } - $data = [ - 'password' => $password, - ]; - $this->_updateUser($username, $data); - $this->out(__d('cake_d_c/users', 'Password changed for user: {0}', $username)); - $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); - } - - /** - * Change role for a user - * - * Arguments: - * - * - Username - * - Role to be set - * - * @return void - */ - public function changeRole() - { - $username = Hash::get($this->args, 0); - $role = Hash::get($this->args, 1); - if (empty($username)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username.')); - } - if (empty($role)) { - $this->abort(__d('cake_d_c/users', 'Please enter a role.')); - } - $data = [ - 'role' => $role, - ]; - $savedUser = $this->_updateUser($username, $data); - $this->out(__d('cake_d_c/users', 'Role changed for user: {0}', $username)); - $this->out(__d('cake_d_c/users', 'New role: {0}', $savedUser->role)); - } - - /** - * Change api token for a user - * - * Arguments: - * - * - Username - * - Token to be set - * - * @return void - */ - public function changeApiToken() - { - $username = Hash::get($this->args, 0); - $token = Hash::get($this->args, 1); - if (empty($username)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username.')); - } - if (empty($token)) { - $this->abort(__d('cake_d_c/users', 'Please enter a token.')); - } - $data = [ - 'api_token' => $token, - ]; - $savedUser = $this->_updateUser($username, $data); - if (!$savedUser) { - $this->err(__d('cake_d_c/users', 'User was not saved, check validation errors')); - } - /** - * @var \CakeDC\Users\Model\Entity\User $savedUser - */ - $this->out(__d('cake_d_c/users', 'Api token changed for user: {0}', $username)); - $this->out(__d('cake_d_c/users', 'New token: {0}', $savedUser->api_token)); - } - - /** - * Activate an specific user - * - * Arguments: - * - * - Username - * - * @return void - */ - public function activateUser() - { - $user = $this->_changeUserActive(true); - $this->out(__d('cake_d_c/users', 'User was activated: {0}', $user->username)); - } - - /** - * De-activate an specific user - * - * Arguments: - * - * - Username - * - * @return void - */ - public function deactivateUser() - { - $user = $this->_changeUserActive(false); - $this->out(__d('cake_d_c/users', 'User was de-activated: {0}', $user->username)); - } - - /** - * Reset password via email for user - * - * @return void - */ - public function passwordEmail() - { - $reference = Hash::get($this->args, 0); - if (empty($reference)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username or email.')); - } - $resetUser = $this->Users->resetToken($reference, [ - 'expiration' => Configure::read('Users.Token.expiration'), - 'checkActive' => false, - 'sendEmail' => true, - ]); - if ($resetUser) { - $msg = __d( - 'cake_d_c/users', - 'Please ask the user to check the email to continue with password reset process' - ); - $this->out($msg); - } else { - $msg = __d( - 'cake_d_c/users', - 'The password token could not be generated. Please try again' - ); - $this->abort($msg); - } - } - - /** - * Change user active field - * - * @param bool $active active value - * @return bool - */ - protected function _changeUserActive($active) - { - $username = Hash::get($this->args, 0); - if (empty($username)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username.')); - } - $data = [ - 'active' => $active, - ]; - - return $this->_updateUser($username, $data); - } - - /** - * Create a new user or superuser - * - * @param array $template template with deafault user values - * @return void - */ - protected function _createUser($template) - { - if (!empty($this->params['username'])) { - $username = $this->params['username']; - } else { - $username = empty($template['username']) ? - $this->_generateRandomUsername() : $template['username']; - } - - $password = (empty($this->params['password']) ? - $this->_generateRandomPassword() : $this->params['password']); - $email = (empty($this->params['email']) ? - $username . '@example.com' : $this->params['email']); - $role = (empty($this->params['role']) ? - $template['role'] : $this->params['role']); - - $user = [ - 'username' => $this->Users->generateUniqueUsername($username), - 'email' => $email, - 'password' => $password, - 'active' => 1, - ]; - - $userEntity = $this->Users->newEntity($user); - $userEntity->is_superuser = empty($template['is_superuser']) ? - false : $template['is_superuser']; - $userEntity->role = $role; - $savedUser = $this->Users->save($userEntity); - - if (is_object($savedUser)) { - if ($savedUser->is_superuser) { - $this->out(__d('cake_d_c/users', 'Superuser added:')); - } else { - $this->out(__d('cake_d_c/users', 'User added:')); - } - $this->out(__d('cake_d_c/users', 'Id: {0}', $savedUser->id)); - $this->out(__d('cake_d_c/users', 'Username: {0}', $savedUser->username)); - $this->out(__d('cake_d_c/users', 'Email: {0}', $savedUser->email)); - $this->out(__d('cake_d_c/users', 'Role: {0}', $savedUser->role)); - $this->out(__d('cake_d_c/users', 'Password: {0}', $password)); - } else { - $this->out(__d('cake_d_c/users', 'User could not be added:')); - - collection($userEntity->getErrors())->each(function ($error, $field) { - $this->out(__d('cake_d_c/users', 'Field: {0} Error: {1}', $field, implode(',', $error))); - }); - } - } - - /** - * Update user by username - * - * @param string $username username - * @param array $data data - * @return \CakeDC\Users\Model\Entity\User|bool - */ - protected function _updateUser($username, $data) - { - $user = $this->Users->find()->where(['username' => $username])->first(); - if (!is_object($user)) { - $this->abort(__d('cake_d_c/users', 'The user was not found.')); - } - /** - * @var \Cake\Datasource\EntityInterface $user - */ - $user = $this->Users->patchEntity($user, $data); - collection($data)->filter(function ($value, $field) use ($user) { - return !$user->isAccessible($field); - })->each(function ($value, $field) use (&$user) { - $user->{$field} = $value; - }); - - return $this->Users->save($user); - } - - /** - * Delete an specific user and associated social accounts - * - * @return void - */ - public function deleteUser() - { - $username = Hash::get($this->args, 0); - if (empty($username)) { - $this->abort(__d('cake_d_c/users', 'Please enter a username.')); - } - /** - * @var \Cake\Datasource\EntityInterface $user - */ - $user = $this->Users->find()->where(['username' => $username])->firstOrFail(); - if (isset($this->Users->SocialAccounts)) { - $this->Users->SocialAccounts->deleteAll(['user_id' => $user->id]); - } - $deleteUser = $this->Users->delete($user); - if (!$deleteUser) { - $this->abort(__d('cake_d_c/users', 'The user {0} was not deleted. Please try again', $username)); - } - $this->out(__d('cake_d_c/users', 'The user {0} was deleted successfully', $username)); - } - - /** - * Generates a random password. - * - * @return string - */ - protected function _generateRandomPassword() - { - return str_replace('-', '', Text::uuid()); - } - - /** - * Generates a random username based on a list of preexisting ones. - * - * @return string - */ - protected function _generateRandomUsername() - { - return $this->_usernameSeed[array_rand($this->_usernameSeed)]; - } - - /** - * Hash a password - * - * @param string $password password - * @return string - */ - protected function _generatedHashedPassword($password) - { - return (new User())->hashPassword($password); - } - - //add filters LIKE in username and email to some tasks - // --force to ignore "you are about to do X to Y users" -} diff --git a/src/View/Helper/AuthLinkHelper.php b/src/View/Helper/AuthLinkHelper.php index 06274ae37..f61417a08 100644 --- a/src/View/Helper/AuthLinkHelper.php +++ b/src/View/Helper/AuthLinkHelper.php @@ -25,13 +25,13 @@ class AuthLinkHelper extends HtmlHelper { use IsAuthorizedTrait; - public $helpers = ['Url', 'Form']; + protected array $helpers = ['Url', 'Form']; /** * Generate a link if the target url is authorized for the logged in user * * @param string $title link's title. - * @param string|array|null $url url that the user is making request. + * @param array|string|null $url url that the user is making request. * @param array $options Array with option data. Extra options include * 'before' and 'after' to quickly inject some html code in the link, like icons etc * 'allowed' to manage if the link should be displayed, default is null to check isAuthorized @@ -60,7 +60,7 @@ public function link($title, $url = null, array $options = []): string * Write the link only if user is authorized. * * @param string $title Link's title - * @param string|array $url Link's url + * @param array|string $url Link's url * @param array $options Link's options * @return string Link as a string. */ diff --git a/src/View/Helper/UserHelper.php b/src/View/Helper/UserHelper.php index 00d32bd93..691ec0fce 100644 --- a/src/View/Helper/UserHelper.php +++ b/src/View/Helper/UserHelper.php @@ -28,14 +28,12 @@ */ class UserHelper extends Helper { - public $helpers = ['Html', 'Form', 'CakeDC/Users.AuthLink']; + protected array $helpers = ['Html', 'Form', 'CakeDC/Users.AuthLink']; /** - * Default configuration. - * - * @var array + * @inheritDoc */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Social login link @@ -56,7 +54,7 @@ public function socialLogin($name, $options = []) if (isset($options['title'])) { $providerTitle = $options['title']; } else { - $providerTitle = ($options['label'] ?? '') . ' ' . Inflector::camelize($name); + $providerTitle = $options['label'] . ' ' . Inflector::camelize($name); } $providerClass = 'btn btn-social btn-' . strtolower($name); @@ -103,7 +101,7 @@ public function socialLoginList(array $providerOptions = []) /** * Logout link * - * @param null $message logout message info. + * @param string|null $message logout message info. * @param array $options Array with option data. * @return string */ @@ -188,7 +186,7 @@ public function addReCaptcha() * * @deprecated Since 3.2.1. Use AuthLinkHelper::link() instead * @param string $title link's title. - * @param string|array|null $url url that the user is making request. + * @param array|string|null $url url that the user is making request. * @param array $options Array with option data. * @return string */ diff --git a/src/Webauthn/AuthenticateAdapter.php b/src/Webauthn/AuthenticateAdapter.php index 592c9590a..79b6cf2a5 100644 --- a/src/Webauthn/AuthenticateAdapter.php +++ b/src/Webauthn/AuthenticateAdapter.php @@ -13,6 +13,10 @@ namespace CakeDC\Users\Webauthn; +use Cake\Http\Exception\BadRequestException; +use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; +use Webauthn\AuthenticatorAssertionResponse; +use Webauthn\AuthenticatorAssertionResponseValidator; use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\PublicKeyCredentialSource; @@ -24,14 +28,16 @@ class AuthenticateAdapter extends BaseAdapter public function getOptions(): PublicKeyCredentialRequestOptions { $userEntity = $this->getUserEntity(); - $allowed = array_map(function (PublicKeyCredentialSource $credential) { + $allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) { return $credential->getPublicKeyCredentialDescriptor(); }, $this->repository->findAllForUserEntity($userEntity)); - $options = $this->server->generatePublicKeyCredentialRequestOptions( - PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value - $allowed - ); + $options = (new PublicKeyCredentialRequestOptions(random_bytes(32))) + ->setRpId($this->rpEntity->getId()) + ->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED) + ->allowCredentials(...$allowedCredentials) + ->setExtensions(new AuthenticationExtensionsClientInputs()); + $this->request->getSession()->write( 'Webauthn2fa.authenticateOptions', $options @@ -44,25 +50,41 @@ public function getOptions(): PublicKeyCredentialRequestOptions * Verify the registration response * * @return \Webauthn\PublicKeyCredentialSource + * @throws \Throwable */ public function verifyResponse(): \Webauthn\PublicKeyCredentialSource { $options = $this->request->getSession()->read('Webauthn2fa.authenticateOptions'); - return $this->loadAndCheckAssertionResponse($options); + $publicKeyCredentialLoader = $this->createPublicKeyCredentialLoader(); + + $publicKeyCredential = $publicKeyCredentialLoader->loadArray($this->request->getData()); + $authenticatorAssertionResponse = $publicKeyCredential->getResponse(); + if ($authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) { + $authenticatorAssertionResponseValidator = $this->createAssertionResponseValidator(); + + return $authenticatorAssertionResponseValidator->check( + $publicKeyCredential->getRawId(), + $authenticatorAssertionResponse, + $options, + $this->request, + $this->getUserEntity()->getId(), + ); + } + + throw new BadRequestException(__('Could not validate credential response for authentication')); } /** - * @param \Webauthn\PublicKeyCredentialRequestOptions $options request options - * @return \Webauthn\PublicKeyCredentialSource + * @return \Webauthn\AuthenticatorAssertionResponseValidator */ - protected function loadAndCheckAssertionResponse($options): PublicKeyCredentialSource + protected function createAssertionResponseValidator(): AuthenticatorAssertionResponseValidator { - return $this->server->loadAndCheckAssertionResponse( - json_encode($this->request->getData()), - $options, - $this->getUserEntity(), - $this->request + return new AuthenticatorAssertionResponseValidator( + $this->repository, + null, + $this->createExtensionOutputCheckerHandler(), + $this->getAlgorithmManager() ); } } diff --git a/src/Webauthn/Base64Utility.php b/src/Webauthn/Base64Utility.php new file mode 100644 index 000000000..a65d2ce83 --- /dev/null +++ b/src/Webauthn/Base64Utility.php @@ -0,0 +1,56 @@ +request = $request; - $rpEntity = new PublicKeyCredentialRpEntity( + $this->rpEntity = new PublicKeyCredentialRpEntity( Configure::read('Webauthn2fa.appName'), // The application name Configure::read('Webauthn2fa.id') ); @@ -53,11 +78,6 @@ public function __construct(ServerRequest $request, ?UsersTable $usersTable = nu $this->user, $usersTable ); - - $this->server = new Server( - $rpEntity, - $this->repository - ); } /** @@ -75,7 +95,7 @@ protected function getUserEntity(): PublicKeyCredentialUserEntity } /** - * @return array|mixed|null + * @return mixed|array|null */ public function getUser() { @@ -91,4 +111,73 @@ public function hasCredential(): bool $this->getUserEntity() ); } + + /** + * @param \Webauthn\AttestationStatement\AttestationStatementSupportManager $attestationStatementSupportManager + */ + public function setAttestationStatementSupportManager(AttestationStatementSupportManager $attestationStatementSupportManager): void + { + $this->attestationStatementSupportManager = $attestationStatementSupportManager; + } + + /** + * @return \Webauthn\AttestationStatement\AttestationStatementSupportManager + */ + protected function getAttestationStatementSupportManager(): AttestationStatementSupportManager + { + if ($this->attestationStatementSupportManager === null) { + $this->attestationStatementSupportManager = new AttestationStatementSupportManager(); + $this->attestationStatementSupportManager + ->add(new NoneAttestationStatementSupport()); + } + + return $this->attestationStatementSupportManager; + } + + /** + * @return \CakeDC\Users\Webauthn\PublicKeyCredentialLoader + */ + protected function createPublicKeyCredentialLoader(): PublicKeyCredentialLoader + { + $attestationObjectLoader = new AttestationObjectLoader( + $this->getAttestationStatementSupportManager() + ); + + return new PublicKeyCredentialLoader( + $attestationObjectLoader + ); + } + + /** + * @return \Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler + */ + protected function createExtensionOutputCheckerHandler(): ExtensionOutputCheckerHandler + { + return new ExtensionOutputCheckerHandler(); + } + + /** + * @return \Cose\Algorithm\Manager + */ + protected function getAlgorithmManager(): Manager + { + if ($this->algorithmManager === null) { + $this->algorithmManager = Manager::create()->add( + ES256::create(), + ES256K::create(), + ES384::create(), + ES512::create(), + RS256::create(), + RS384::create(), + RS512::create(), + PS256::create(), + PS384::create(), + PS512::create(), + Ed256::create(), + Ed512::create(), + ); + } + + return $this->algorithmManager; + } } diff --git a/src/Webauthn/PublicKeyCredentialLoader.php b/src/Webauthn/PublicKeyCredentialLoader.php new file mode 100644 index 000000000..a0cf8113d --- /dev/null +++ b/src/Webauthn/PublicKeyCredentialLoader.php @@ -0,0 +1,41 @@ +getUserEntity(); - $options = $this->server->generatePublicKeyCredentialCreationOptions( + $challenge = random_bytes(16); + + $options = PublicKeyCredentialCreationOptions::create( + $this->rpEntity, $userEntity, - PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, - [] + $challenge, + $this->getPubKeyCredParams(), ); + $options = $options + ->setAuthenticatorSelection(new AuthenticatorSelectionCriteria()) + ->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE) + ->setExtensions(new AuthenticationExtensionsClientInputs()); + $this->request->getSession()->write('Webauthn2fa.registerOptions', $options); $this->request->getSession()->write('Webauthn2fa.userEntity', $userEntity); @@ -42,24 +57,46 @@ public function getOptions(): PublicKeyCredentialCreationOptions public function verifyResponse(): \Webauthn\PublicKeyCredentialSource { $options = $this->request->getSession()->read('Webauthn2fa.registerOptions'); - $credential = $this->loadAndCheckAttestationResponse($options); - $this->repository->saveCredentialSource($credential); + $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); + $publicKeyCredentialLoader = $this->createPublicKeyCredentialLoader(); + + $publicKeyCredential = $publicKeyCredentialLoader->loadArray((array)$this->request->getData()); + + $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); + if ($authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { + $extensionOutputCheckerHandler = $this->createExtensionOutputCheckerHandler(); + $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( + $attestationStatementSupportManager, + $this->repository, + null, //Token binding is deprecated + $extensionOutputCheckerHandler + ); + $credential = $authenticatorAttestationResponseValidator->check( + $authenticatorAttestationResponse, + $options, + $this->request + ); - return $credential; + $this->repository->saveCredentialSource($credential); + + return $credential; + } + throw new BadRequestException(__('Could not credential response for registration')); } /** - * @param \Webauthn\PublicKeyCredentialCreationOptions $options creation options - * @return \Webauthn\PublicKeyCredentialSource + * @return array<\Webauthn\PublicKeyCredentialParameters> */ - protected function loadAndCheckAttestationResponse($options): \Webauthn\PublicKeyCredentialSource + protected function getPubKeyCredParams(): array { - $credential = $this->server->loadAndCheckAttestationResponse( - json_encode($this->request->getData()), - $options, - $this->request - ); + $list = []; + foreach ($this->getAlgorithmManager()->all() as $algorithm) { + $list[] = PublicKeyCredentialParameters::create( + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + $algorithm::identifier() + ); + } - return $credential; + return $list; } } diff --git a/src/Webauthn/Repository/UserCredentialSourceRepository.php b/src/Webauthn/Repository/UserCredentialSourceRepository.php index ecda3cbb0..5c1127a21 100644 --- a/src/Webauthn/Repository/UserCredentialSourceRepository.php +++ b/src/Webauthn/Repository/UserCredentialSourceRepository.php @@ -3,9 +3,9 @@ namespace CakeDC\Users\Webauthn\Repository; -use Base64Url\Base64Url; use Cake\Datasource\EntityInterface; use CakeDC\Users\Model\Table\UsersTable; +use CakeDC\Users\Webauthn\Base64Utility; use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialSourceRepository; use Webauthn\PublicKeyCredentialUserEntity; @@ -37,7 +37,7 @@ public function __construct(EntityInterface $user, ?UsersTable $usersTable = nul */ public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource { - $encodedId = Base64Url::encode($publicKeyCredentialId); + $encodedId = Base64Utility::basicEncode($publicKeyCredentialId); $credential = $this->user['additional_data']['webauthn_credentials'][$encodedId] ?? null; return $credential @@ -68,7 +68,7 @@ public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCre public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void { $credentials = $this->user['additional_data']['webauthn_credentials'] ?? []; - $id = Base64Url::encode($publicKeyCredentialSource->getPublicKeyCredentialId()); + $id = Base64Utility::basicEncode($publicKeyCredentialSource->getPublicKeyCredentialId()); $credentials[$id] = json_decode(json_encode($publicKeyCredentialSource), true); $this->user['additional_data'] = $this->user['additional_data'] ?? []; $this->user['additional_data']['webauthn_credentials'] = $credentials; diff --git a/tests/Fixture/PostsFixture.php b/tests/Fixture/PostsFixture.php index 4057cbbb8..88cc2f9b1 100644 --- a/tests/Fixture/PostsFixture.php +++ b/tests/Fixture/PostsFixture.php @@ -23,7 +23,7 @@ class PostsFixture extends TestFixture * * @var array */ - public $records = [ + public array $records = [ [ 'id' => '00000000-0000-0000-0000-000000000001', 'title' => 'post-1', diff --git a/tests/Fixture/PostsUsersFixture.php b/tests/Fixture/PostsUsersFixture.php index 5f5350e9e..0abc40496 100644 --- a/tests/Fixture/PostsUsersFixture.php +++ b/tests/Fixture/PostsUsersFixture.php @@ -23,7 +23,7 @@ class PostsUsersFixture extends TestFixture * * @var array */ - public $records = [ + public array $records = [ [ 'id' => '00000000-0000-0000-0000-000000000011', 'user_id' => '00000000-0000-0000-0000-000000000001', diff --git a/tests/Fixture/SocialAccountsFixture.php b/tests/Fixture/SocialAccountsFixture.php index 9789d2c45..e1ffe3f6b 100644 --- a/tests/Fixture/SocialAccountsFixture.php +++ b/tests/Fixture/SocialAccountsFixture.php @@ -23,7 +23,7 @@ class SocialAccountsFixture extends TestFixture * * @var array */ - public $records = [ + public array $records = [ [ 'id' => '00000000-0000-0000-0000-000000000001', 'user_id' => '00000000-0000-0000-0000-000000000001', diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php index d48f6dc55..f5ff54cf6 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -12,8 +12,8 @@ namespace CakeDC\Users\Test\Fixture; -use Base64Url\Base64Url; use Cake\TestSuite\Fixture\TestFixture; +use CakeDC\Users\Webauthn\Base64Utility; /** * UsersFixture @@ -64,8 +64,9 @@ public function init(): void 'type' => 'Webauthn\TrustPath\EmptyTrustPath', ], 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-ZZZZZZZZZZZ'), - 'userHandle' => Base64Url::encode('00000000-0000-0000-0000-000000000001'), + //add this when migrated webauthn + 'credentialPublicKey' => Base64Utility::basicEncode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-ZZZZZZZZZZZ'), + 'userHandle' => Base64Utility::basicEncode('00000000-0000-0000-0000-000000000001'), 'counter' => 190, 'otherUI' => null, ], diff --git a/tests/TestCase/Authenticator/SocialAuthenticatorTest.php b/tests/TestCase/Authenticator/SocialAuthenticatorTest.php index d20ba65fc..759a36651 100644 --- a/tests/TestCase/Authenticator/SocialAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/SocialAuthenticatorTest.php @@ -36,7 +36,7 @@ */ class SocialAuthenticatorTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php b/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php index 949153707..3309dcded 100644 --- a/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php @@ -25,7 +25,7 @@ class SocialPendingEmailAuthenticatorTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Command/UsersActivateUserCommandTest.php b/tests/TestCase/Command/UsersActivateUserCommandTest.php new file mode 100644 index 000000000..ec03a4682 --- /dev/null +++ b/tests/TestCase/Command/UsersActivateUserCommandTest.php @@ -0,0 +1,62 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 0])); + $this->exec('cake_d_c/users.users activate_user user-3'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/^User was activated: user-3$/'); + //Target must have changed + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 1])); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteNoUsername(): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 0])); + $this->exec('cake_d_c/users.users activate_user'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a username.'); + //Should not change + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 0])); + } +} diff --git a/tests/TestCase/Command/UsersAddSuperuserCommandTest.php b/tests/TestCase/Command/UsersAddSuperuserCommandTest.php new file mode 100644 index 000000000..197521fbf --- /dev/null +++ b/tests/TestCase/Command/UsersAddSuperuserCommandTest.php @@ -0,0 +1,71 @@ +get('CakeDC/Users.Users'); + $this->assertFalse($UsersTable->exists(['username' => $username])); + $this->exec('cake_d_c/users.users add_superuser --username=yeliparra.admin --password=123456 --email=yeli.parra.testing01@testing.com --role=admin-tester'); + $this->assertOutputRegExp('/^Superuser added:\n/'); + $this->assertOutputRegExp('/Username: yeliparra.admin\n/'); + $this->assertOutputRegExp('/Email: yeli.parra.testing01@testing.com\n/'); + $this->assertOutputRegExp('/Role: admin-tester\n/'); + $this->assertOutputRegExp('/Password: 123456$/'); + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->find()->where(['username' => $username])->firstOrFail(); + $this->assertSame('yeli.parra.testing01@testing.com', $user->email); + $this->assertSame('admin-tester', $user->role); + $this->assertTrue($user->is_superuser); + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('123456', $user->get('password'))); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersAddUserCommand::execute() + */ + public function testExecuteWithNoParams(): void + { + $this->exec('cake_d_c/users.users add_superuser'); + $this->assertOutputRegExp('/^Superuser added:\n/'); + $this->assertOutputRegExp('/Username: superadmin\n/'); + $this->assertOutputRegExp('/Email: superadmin@example.com\n/'); + $this->assertOutputRegExp('/Role: superuser\n/'); + $this->assertOutputRegExp('/Password: [a-z0-9]{32}$/'); + } +} diff --git a/tests/TestCase/Command/UsersAddUserCommandTest.php b/tests/TestCase/Command/UsersAddUserCommandTest.php new file mode 100644 index 000000000..7cab4901a --- /dev/null +++ b/tests/TestCase/Command/UsersAddUserCommandTest.php @@ -0,0 +1,139 @@ +get('CakeDC/Users.Users'); + $this->assertFalse($UsersTable->exists(['username' => 'yeliparra'])); + $this->exec('cake_d_c/users.users add_user --username=yeliparra --password=123 --email=yeli.parra@test.com --role=tester'); + $this->assertOutputRegExp('/^User added:\n/'); + $this->assertOutputRegExp('/Username: yeliparra\n/'); + $this->assertOutputRegExp('/Email: yeli.parra@test.com\n/'); + $this->assertOutputRegExp('/Role: tester\n/'); + $this->assertOutputRegExp('/Password: 123$/'); + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->find()->where(['username' => 'yeliparra'])->firstOrFail(); + $this->assertSame('yeli.parra@test.com', $user->email); + $this->assertSame('tester', $user->role); + $this->assertFalse($user->is_superuser); + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('123', $user->get('password'))); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersAddUserCommand::execute() + */ + public function testExecuteWithNoParams(): void + { + $this->exec('cake_d_c/users.users add_user'); + $username = '(aayla|admiral|anakin|chewbacca|darthvader|hansolo|luke|obiwan|leia|r2d2)'; + $this->assertOutputRegExp('/^User added:\n/'); + $this->assertOutputRegExp('/Username: ' . $username . '\n/'); + $this->assertOutputRegExp('/Email: ' . $username . '@example.com\n/'); + $this->assertOutputRegExp('/Role: user\n/'); + $this->assertOutputRegExp('/Password: [a-z0-9]{32}$/'); + } + + /** + * Test execute method + * + * @return void + */ + public function testExecuteCustomDefaultRole() + { + EventManager::instance()->on('Console.buildCommands', function () { + Configure::write('Users.Registration.defaultRole', 'emperor'); + }); + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $username = 'custom.user'; + $this->assertFalse($UsersTable->exists(['username' => $username])); + + $this->exec('cake_d_c/users.users add_user --username=custom.user --password=12345 --email=custom+user@example.com'); + $this->assertOutputRegExp('/^User added:\n/'); + $this->assertOutputRegExp('/Username: custom.user\n/'); + $this->assertOutputRegExp('/Email: custom\+user@example.com/'); + $this->assertOutputRegExp('/Role: emperor\n/'); + $this->assertOutputRegExp('/Password: 12345$/'); + + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->find()->where(['username' => $username])->firstOrFail(); + $this->assertSame('custom+user@example.com', $user->email); + $this->assertSame('emperor', $user->role); + $this->assertFalse($user->is_superuser); + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('12345', $user->get('password'))); + } + + /** + * Test execute method + * + * @return void + */ + public function testExecuteDefaultRole() + { + EventManager::instance()->on('Console.buildCommands', function () { + Configure::write('Users.Registration.defaultRole', false); + }); + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $username = 'custom.user'; + $this->assertFalse($UsersTable->exists(['username' => $username])); + + $this->exec('cake_d_c/users.users add_user --username=custom.user --password=12345 --email=custom+user@example.com'); + $this->assertOutputRegExp('/^User added:\n/'); + $this->assertOutputRegExp('/Username: custom.user\n/'); + $this->assertOutputRegExp('/Email: custom\+user@example.com/'); + $this->assertOutputRegExp('/Role: user\n/'); + $this->assertOutputRegExp('/Password: 12345$/'); + + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->find()->where(['username' => $username])->firstOrFail(); + $this->assertSame('custom+user@example.com', $user->email); + $this->assertSame('user', $user->role); + $this->assertFalse($user->is_superuser); + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('12345', $user->get('password'))); + } +} diff --git a/tests/TestCase/Command/UsersChangeApiTokenCommandTest.php b/tests/TestCase/Command/UsersChangeApiTokenCommandTest.php new file mode 100644 index 000000000..81449ba5e --- /dev/null +++ b/tests/TestCase/Command/UsersChangeApiTokenCommandTest.php @@ -0,0 +1,106 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000004'; + $userId006 = '00000000-0000-0000-0000-000000000006'; + $valueBefore = 'Lorem ipsum dolor sit amet'; + $userId001 = '00000000-0000-0000-0000-000000000001'; + $value001 = 'yyy'; + $value006 = ''; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'api_token' => $valueBefore])); + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'api_token' => $value001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'api_token' => $value006])); + + $this->exec('cake_d_c/users.users change_api_token user-4 test-execute-001'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/Api token changed for user: user-4\n/'); + $this->assertOutputRegExp('/New token: test-execute-001/'); + + //$userId 1 and 2 should not have change the password + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'api_token' => $value001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'api_token' => $value006])); + //Target must have changed + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'api_token' => 'test-execute-001'])); + } + + /** + * Test execute method + * + * @param string $command + * @param string $expectedMessage + * @return void + * @dataProvider dataProviderExecuteMissingInfo + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteMissingInfo(string $command, $expectedMessage): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000004'; + $userId006 = '00000000-0000-0000-0000-000000000006'; + $valueBefore = 'Lorem ipsum dolor sit amet'; + $userId001 = '00000000-0000-0000-0000-000000000001'; + $value001 = 'yyy'; + $value006 = ''; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'api_token' => $valueBefore])); + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'api_token' => $value001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'api_token' => $value006])); + + $this->exec($command); + $this->assertExitError(); + $this->assertErrorContains($expectedMessage); + + //should not have change the password + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'api_token' => $value001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'api_token' => $value006])); + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'api_token' => $valueBefore])); + } + + /** + * @return array + */ + public function dataProviderExecuteMissingInfo(): array + { + return [ + [ + 'cake_d_c/users.users change_api_token user-4', + 'Please enter a token.', + ], + [ + 'cake_d_c/users.users change_api_token', + 'Please enter a username.', + ], + ]; + } +} diff --git a/tests/TestCase/Command/UsersChangeRoleCommandTest.php b/tests/TestCase/Command/UsersChangeRoleCommandTest.php new file mode 100644 index 000000000..2e5d818c5 --- /dev/null +++ b/tests/TestCase/Command/UsersChangeRoleCommandTest.php @@ -0,0 +1,106 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000004'; + $userId006 = '00000000-0000-0000-0000-000000000006'; + $roleBefore = 'Lorem ipsum dolor sit amet'; + $userId001 = '00000000-0000-0000-0000-000000000001'; + $role001 = 'admin'; + $role006 = 'user'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'role' => $roleBefore])); + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'role' => $role001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'role' => $role006])); + + $this->exec('cake_d_c/users.users change_role user-4 test-execute-001'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/Role changed for user: user-4\n/'); + $this->assertOutputRegExp('/New role: test-execute-001/'); + + //$userId 1 and 2 should not have change the password + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'role' => $role001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'role' => $role006])); + //Target must have changed + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'role' => 'test-execute-001'])); + } + + /** + * Test execute method + * + * @param string $command + * @param string $expectedMessage + * @return void + * @dataProvider dataProviderExecuteMissingInfo + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteMissingInfo(string $command, $expectedMessage): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000004'; + $userId006 = '00000000-0000-0000-0000-000000000006'; + $roleBefore = 'Lorem ipsum dolor sit amet'; + $userId001 = '00000000-0000-0000-0000-000000000001'; + $role001 = 'admin'; + $role006 = 'user'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'role' => $roleBefore])); + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'role' => $role001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'role' => $role006])); + + $this->exec($command); + $this->assertExitError(); + $this->assertErrorContains($expectedMessage); + + //should not have change the password + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'role' => $role001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'role' => $role006])); + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'role' => $roleBefore])); + } + + /** + * @return array + */ + public function dataProviderExecuteMissingInfo(): array + { + return [ + [ + 'cake_d_c/users.users change_role user-4', + 'Please enter a role.', + ], + [ + 'cake_d_c/users.users change_role', + 'Please enter a username.', + ], + ]; + } +} diff --git a/tests/TestCase/Command/UsersDeactivateUserCommandTest.php b/tests/TestCase/Command/UsersDeactivateUserCommandTest.php new file mode 100644 index 000000000..03de010ba --- /dev/null +++ b/tests/TestCase/Command/UsersDeactivateUserCommandTest.php @@ -0,0 +1,62 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000002'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 1])); + $this->exec('cake_d_c/users.users deactivate_user user-2'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/^User was de-activated: user-2$/'); + //Target must have changed + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 0])); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteNoUsername(): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000002'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 1])); + $this->exec('cake_d_c/users.users deactivate_user'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a username.'); + //Should not change + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget, 'active' => 1])); + } +} diff --git a/tests/TestCase/Command/UsersDeleteUserCommandTest.php b/tests/TestCase/Command/UsersDeleteUserCommandTest.php new file mode 100644 index 000000000..e21f8ae4c --- /dev/null +++ b/tests/TestCase/Command/UsersDeleteUserCommandTest.php @@ -0,0 +1,72 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget])); + $this->assertTrue($UsersTable->SocialAccounts->exists(['user_id' => $userIdTarget])); + $this->exec('cake_d_c/users.users delete_user user-3'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/^The user user-3 was deleted successfully$/'); + //Target must have changed + $this->assertFalse($UsersTable->exists(['id' => $userIdTarget])); + $this->assertFalse($UsersTable->SocialAccounts->exists(['user_id' => $userIdTarget])); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteNoUsername(): void + { + /** + * @var \CakeDC\Users\Model\Table\UsersTable $UsersTable + */ + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget])); + $this->assertTrue($UsersTable->SocialAccounts->exists(['user_id' => $userIdTarget])); + $this->exec('cake_d_c/users.users delete_user'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a username.'); + //Target must have changed + $this->assertTrue($UsersTable->exists(['id' => $userIdTarget])); + $this->assertTrue($UsersTable->SocialAccounts->exists(['user_id' => $userIdTarget])); + } +} diff --git a/tests/TestCase/Command/UsersPasswordEmailCommandTest.php b/tests/TestCase/Command/UsersPasswordEmailCommandTest.php new file mode 100644 index 000000000..3228ea627 --- /dev/null +++ b/tests/TestCase/Command/UsersPasswordEmailCommandTest.php @@ -0,0 +1,74 @@ +get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $user = $UsersTable->get($userIdTarget); + $this->assertSame('token-3', $user->token); + + $this->exec('cake_d_c/users.users password_email user-3'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/^Please ask the user to check the email to continue with password reset process$/'); + //Target must have changed + $userAfter = $UsersTable->get($userIdTarget); + $this->assertNotEmpty($userAfter->token); + $this->assertNotEquals($user->token, $userAfter->token); + $this->assertNotEmpty($userAfter->token_expires); + $this->assertNotEquals($user->token_expires, $userAfter->token_expires); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersChangeRoleCommand::execute() + */ + public function testExecuteNoUsername(): void + { + /** + * @var \CakeDC\Users\Model\Table\UsersTable $UsersTable + */ + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userIdTarget = '00000000-0000-0000-0000-000000000003'; + $this->exec('cake_d_c/users.users password_email'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a username or email.'); + //Should not change + $user = $UsersTable->get($userIdTarget); + $this->assertSame('token-3', $user->token); + } +} diff --git a/tests/TestCase/Command/UsersResetAllPasswordsCommandTest.php b/tests/TestCase/Command/UsersResetAllPasswordsCommandTest.php new file mode 100644 index 000000000..ab4f56cbe --- /dev/null +++ b/tests/TestCase/Command/UsersResetAllPasswordsCommandTest.php @@ -0,0 +1,79 @@ +exec('cake_d_c/users.users reset_all_passwords myCustomNewPass002'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/Password changed for all users\n/'); + $this->assertOutputRegExp('/New password: myCustomNewPass002/'); + + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $users = $UsersTable->find()->all()->toArray(); + if (empty($users)) { + throw new \UnexpectedValueException(__('Test expect to have users')); + } + foreach ($users as $user) { + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('myCustomNewPass002', $user->get('password'))); + } + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersResetAllPasswordsCommand::execute() + */ + public function testExecuteNoPassword(): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->find()->firstOrFail(); + $this->assertNotEmpty($user->password); + $this->exec('cake_d_c/users.users reset_all_passwords'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a password.'); + $this->assertOutputNotContains('Password changed'); + /** + * @var \CakeDC\Users\Model\Entity\User $userAfter + */ + $userAfter = $UsersTable->get($user->id); + $this->assertSame($user->password, $userAfter->password); + } +} diff --git a/tests/TestCase/Command/UsersResetPasswordCommandTest.php b/tests/TestCase/Command/UsersResetPasswordCommandTest.php new file mode 100644 index 000000000..2b5ba24e4 --- /dev/null +++ b/tests/TestCase/Command/UsersResetPasswordCommandTest.php @@ -0,0 +1,105 @@ +get('CakeDC/Users.Users'); + $userId004 = '00000000-0000-0000-0000-000000000004'; + $userId006 = '00000000-0000-0000-0000-000000000006'; + $passwordBefore = '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde'; + $userId001 = '00000000-0000-0000-0000-000000000001'; + $password001 = '12345'; + $password006 = '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC'; + $this->assertTrue($UsersTable->exists(['id' => $userId004, 'password' => $passwordBefore])); + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'password' => $password001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'password' => $password006])); + + $this->exec('cake_d_c/users.users reset_password user-4 newPassTestOne234'); + $this->assertExitSuccess(); + $this->assertOutputRegExp('/Password changed for user: user-4\n/'); + $this->assertOutputRegExp('/New password: newPassTestOne234/'); + + //$userId 1 and 2 should not have change the password + $this->assertTrue($UsersTable->exists(['id' => $userId001, 'password' => $password001])); + $this->assertTrue($UsersTable->exists(['id' => $userId006, 'password' => $password006])); + /** + * @var \CakeDC\Users\Model\Entity\User $user + */ + $user = $UsersTable->get($userId004); + $this->assertNotEquals($passwordBefore, $user->password); + //Correct password? + $passwordHasher = $user->getPasswordHasher(); + $this->assertTrue($passwordHasher->check('newPassTestOne234', $user->get('password'))); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersResetPasswordCommand::execute() + */ + public function testExecuteNotPassword(): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userId004 = '00000000-0000-0000-0000-000000000004'; + $passwordBefore = '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde'; + $this->assertTrue($UsersTable->exists(['id' => $userId004, 'password' => $passwordBefore])); + + $this->exec('cake_d_c/users.users reset_password user-4'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a password.'); + + //Password not changed + $this->assertTrue($UsersTable->exists(['id' => $userId004, 'password' => $passwordBefore])); + } + + /** + * Test execute method + * + * @return void + * @uses \CakeDC\Users\Command\UsersResetPasswordCommand::execute() + */ + public function testExecuteNotUserName(): void + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userId004 = '00000000-0000-0000-0000-000000000004'; + $passwordBefore = '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde'; + $this->assertTrue($UsersTable->exists(['id' => $userId004, 'password' => $passwordBefore])); + + $this->exec('cake_d_c/users.users reset_password'); + $this->assertExitError(); + $this->assertErrorContains('Please enter a username.'); + + //Password not changed + $this->assertTrue($UsersTable->exists(['id' => $userId004, 'password' => $passwordBefore])); + } +} diff --git a/tests/TestCase/Controller/Component/SetupComponentTest.php b/tests/TestCase/Controller/Component/SetupComponentTest.php index d685949a2..790ed2075 100644 --- a/tests/TestCase/Controller/Component/SetupComponentTest.php +++ b/tests/TestCase/Controller/Component/SetupComponentTest.php @@ -46,7 +46,7 @@ class SetupComponentTest extends TestCase public function setUp(): void { parent::setUp(); - $this->Controller = new Controller(); + $this->Controller = new Controller(new \Cake\Http\ServerRequest()); } /** diff --git a/tests/TestCase/Controller/SocialAccountsControllerTest.php b/tests/TestCase/Controller/SocialAccountsControllerTest.php index 47a544d7e..165e18142 100644 --- a/tests/TestCase/Controller/SocialAccountsControllerTest.php +++ b/tests/TestCase/Controller/SocialAccountsControllerTest.php @@ -15,7 +15,7 @@ use Cake\Core\Configure; use Cake\Http\ServerRequest; -use Cake\Mailer\Email; +use Cake\Mailer\Mailer; use Cake\Mailer\TransportFactory; use Cake\TestSuite\TestCase; @@ -26,7 +26,7 @@ class SocialAccountsControllerTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; @@ -46,9 +46,9 @@ public function setUp(): void Configure::write('Users.RememberMe.active', false); TransportFactory::setConfig('test', ['className' => 'Debug']); - $this->configEmail = Email::getConfig('default'); - Email::drop('default'); - Email::setConfig('default', [ + $this->configEmail = Mailer::getConfig('default'); + Mailer::drop('default'); + Mailer::setConfig('default', [ 'transport' => 'test', 'from' => 'cakedc@example.com', ]); @@ -58,7 +58,7 @@ public function setUp(): void $this->Controller = $this->getMockBuilder('CakeDC\Users\Controller\SocialAccountsController') ->onlyMethods(['redirect', 'render']) - ->setConstructorArgs([$request, null, 'SocialAccounts']) + ->setConstructorArgs([$request, 'SocialAccounts']) ->getMock(); } @@ -69,9 +69,8 @@ public function setUp(): void */ public function tearDown(): void { - Email::drop('default'); + Mailer::drop('default'); TransportFactory::drop('test'); - //Email::setConfig('default', $this->configEmail); Configure::write('Opauth', $this->configOpauth); Configure::write('Users.RememberMe.active', $this->configRememberMe); diff --git a/tests/TestCase/Controller/Traits/BaseTraitTest.php b/tests/TestCase/Controller/Traits/BaseTraitTest.php index 693940e8a..bdeac3b49 100644 --- a/tests/TestCase/Controller/Traits/BaseTraitTest.php +++ b/tests/TestCase/Controller/Traits/BaseTraitTest.php @@ -21,14 +21,15 @@ use Cake\Controller\ComponentRegistry; use Cake\Controller\Controller; use Cake\Event\Event; -use Cake\Mailer\Email; +use Cake\Http\ServerRequest; +use Cake\Mailer\Mailer; use Cake\Mailer\TransportFactory; use Cake\ORM\Entity; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; use CakeDC\Auth\Authentication\AuthenticationService; use CakeDC\Users\Model\Entity\User; -use PHPUnit_Framework_MockObject_RuntimeException; +use PHPUnit\Framework\MockObject\RuntimeException; /** * Class BaseTraitTest @@ -42,7 +43,7 @@ abstract class BaseTraitTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -83,14 +84,16 @@ public function setUp(): void $traitMockMethods = array_unique(array_merge(['getUsersTable'], $this->traitMockMethods)); $this->table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); try { - $this->Trait = $this->getMockBuilder($this->traitClassName) - ->setMethods($traitMockMethods) - ->getMock(); + $buildTrait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods); + if (class_exists($this->traitClassName)) { + $buildTrait = $buildTrait->setConstructorArgs([new ServerRequest()]); + } + $this->Trait = $buildTrait->getMock(); $this->Trait->expects($this->any()) ->method('getUsersTable') ->will($this->returnValue($this->table)); - } catch (PHPUnit_Framework_MockObject_RuntimeException $ex) { - debug($ex); + } catch (RuntimeException $ex) { $this->fail('Unit tests extending BaseTraitTest should declare the trait class name in the $traitClassName variable before calling setUp()'); } @@ -98,9 +101,9 @@ public function setUp(): void TransportFactory::setConfig('test', [ 'className' => 'Debug', ]); - $this->configEmail = Email::getConfig('default'); - Email::drop('default'); - Email::setConfig('default', [ + $this->configEmail = Mailer::getConfig('default'); + Mailer::drop('default'); + Mailer::setConfig('default', [ 'transport' => 'test', 'from' => 'cakedc@example.com', ]); @@ -116,9 +119,8 @@ public function tearDown(): void { unset($this->table, $this->Trait); if ($this->mockDefaultEmail) { - Email::drop('default'); + Mailer::drop('default'); TransportFactory::drop('test'); - //Email::setConfig('default', $this->setConfigEmail); } parent::tearDown(); } diff --git a/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php index 8bad2e04b..ebe2b8c88 100644 --- a/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php +++ b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php @@ -13,6 +13,7 @@ namespace CakeDC\Users\Test\TestCase\Controller\Traits; +use Cake\Http\ServerRequest; use Cake\ORM\Table; use Cake\TestSuite\TestCase; @@ -23,6 +24,7 @@ public function setUp(): void parent::setUp(); $this->controller = $this->getMockBuilder('Cake\Controller\Controller') ->setMethods(['header', 'redirect', 'render', '_stop']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->controller->Trait = $this->getMockForTrait('CakeDC\Users\Controller\Traits\CustomUsersTableTrait'); } diff --git a/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php index 9bf8d74d4..7f9696ce8 100644 --- a/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php +++ b/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php @@ -28,7 +28,7 @@ class LoginTraitIntegrationTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -76,8 +76,8 @@ public function testLoginGetRequestNoSocialLogin() $this->assertResponseNotContains('Username or password is incorrect'); $this->assertResponseContains('
'); $this->assertResponseContains('Please enter your username and password'); - $this->assertResponseContains(''); - $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains('Register'); @@ -102,8 +102,8 @@ public function testLoginGetRequest() $this->assertResponseNotContains('Username or password is incorrect'); $this->assertResponseContains(''); $this->assertResponseContains('Please enter your username and password'); - $this->assertResponseContains(''); - $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains('Register'); @@ -131,8 +131,8 @@ public function testLoginPostRequestInvalidPassword() $this->assertResponseContains('Username or password is incorrect'); $this->assertResponseContains(''); $this->assertResponseContains('Please enter your username and password'); - $this->assertResponseContains(''); - $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains(''); } diff --git a/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php index 47a9e337d..be5df20f3 100644 --- a/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php +++ b/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php @@ -27,7 +27,7 @@ class PasswordManagementTraitIntegrationTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -41,7 +41,7 @@ public function testRequestResetPassword() $this->get('/users/request-reset-password'); $this->assertResponseOk(); $this->assertResponseContains('Please enter your email or username to reset your password'); - $this->assertResponseContains(''); + $this->assertResponseContains(''); } /** diff --git a/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php index ea555a89d..39a304eb5 100644 --- a/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php +++ b/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php @@ -26,7 +26,7 @@ class ProfileTraitIntegrationTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php index 8c7d4e852..0ce3da3ce 100644 --- a/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php +++ b/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php @@ -26,7 +26,7 @@ class RegisterTraitIntegrationTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -45,9 +45,9 @@ public function testRegister() $this->assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains(''); - $this->assertResponseContains(''); - $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains(''); } @@ -80,9 +80,9 @@ public function testRegisterPostWithErrors() $this->assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains(''); - $this->assertResponseContains(''); - $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); $this->assertResponseContains(''); $this->assertResponseContains(''); } diff --git a/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php index 9b606146e..6965f9c6a 100644 --- a/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php +++ b/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php @@ -29,7 +29,7 @@ class SimpleCrudTraitIntegrationTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php b/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php index d5229da54..3d507d442 100644 --- a/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php +++ b/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php @@ -18,7 +18,7 @@ use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\Http\ServerRequestFactory; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\ORM\TableRegistry; use Laminas\Diactoros\Uri; use League\OAuth2\Client\Provider\FacebookUser; @@ -30,7 +30,7 @@ class LinkSocialTraitTest extends BaseTraitTest * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; @@ -58,11 +58,7 @@ public function setUp(): void $request = new ServerRequest(); $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set']) - ->getMock(); - - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest($request); @@ -119,6 +115,7 @@ public function testLinkSocialHappy() $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); @@ -238,6 +235,7 @@ public function testCallbackLinkSocialHappy() $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); @@ -274,7 +272,7 @@ public function testCallbackLinkSocialHappy() $actual = $Table->SocialAccounts->find('all')->where(['reference' => '9999911112255'])->firstOrFail(); - $expiresTime = new FrozenTime(); + $expiresTime = new DateTime(); $tokenExpires = $expiresTime->setTimestamp($Token->getExpires())->format('Y-m-d H:i:s'); $expected = [ @@ -383,6 +381,7 @@ public function testCallbackLinkSocialWithValidationErrors() $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->expects($this->any()) @@ -446,6 +445,7 @@ public function testCallbackLinkSocialQueryHasErrors() $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); @@ -507,6 +507,7 @@ public function testCallbackLinkSocialUnknownProvider() $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); diff --git a/tests/TestCase/Controller/Traits/LoginTraitTest.php b/tests/TestCase/Controller/Traits/LoginTraitTest.php index 79d0a860b..0d35d26a1 100644 --- a/tests/TestCase/Controller/Traits/LoginTraitTest.php +++ b/tests/TestCase/Controller/Traits/LoginTraitTest.php @@ -17,7 +17,7 @@ use Authentication\Authenticator\SessionAuthenticator; use Authentication\Identifier\IdentifierCollection; use Authentication\Identifier\PasswordIdentifier; -use Cake\Auth\DefaultPasswordHasher; +use Authentication\PasswordHasher\DefaultPasswordHasher; use Cake\Controller\ComponentRegistry; use Cake\Event\Event; use Cake\Http\Response; @@ -43,6 +43,7 @@ public function setUp(): void $this->Trait->setRequest(new ServerRequest()); $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', 'loadComponent']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') @@ -102,7 +103,7 @@ public function testLoginHappy() ->with($this->successLoginRedirect) ->will($this->returnValue(new Response())); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $config = [ 'component' => 'CakeDC/Users.Login', 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), @@ -134,7 +135,7 @@ public function testLoginHappy() $this->assertSame($passwordBefore, $passwordAfter); $lastLoginAfter = $userAfter['last_login']; $this->assertNotEmpty($lastLoginAfter); - $now = \Cake\I18n\FrozenTime::now(); + $now = \Cake\I18n\DateTime::now(); $this->assertEqualsWithDelta($lastLoginAfter->timestamp, $now->timestamp, 2); } @@ -182,7 +183,7 @@ public function testLoginRehash() ->with($this->successLoginRedirect) ->will($this->returnValue(new Response())); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $config = [ 'component' => 'CakeDC/Users.Login', 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), @@ -246,7 +247,7 @@ public function testLoginGet() $this->_mockAuthentication(); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $config = [ 'component' => 'CakeDC/Users.Login', 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), @@ -421,7 +422,7 @@ public function testLogin($AuthClass, $resultStatus, $message, $method, $failure ->method('error') ->with($message); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $Login = $this->getMockBuilder(LoginComponent::class) ->setMethods(['getController']) ->setConstructorArgs([$registry, $failureConfig]) @@ -498,7 +499,7 @@ public function testSocialLoginSuccess() ->with($this->successLoginRedirect) ->will($this->returnValue(new Response())); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $config = [ 'component' => 'CakeDC/Users.Login', 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), diff --git a/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php index 683bfc0c7..7ca5af94b 100644 --- a/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php +++ b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php @@ -20,7 +20,7 @@ class OneTimePasswordVerifyTraitTest extends BaseTraitTest { - protected $loginPage = [ + protected array $loginPage = [ 'plugin' => 'CakeDC/Users', 'prefix' => false, 'controller' => 'users', @@ -41,6 +41,7 @@ public function setUp(): void $request = new ServerRequest(); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $this->Trait->setRequest($request); diff --git a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php index 9a3d8a6ad..0f6cb2fbb 100644 --- a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php +++ b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php @@ -13,7 +13,7 @@ namespace CakeDC\Users\Test\TestCase\Controller\Traits; -use Cake\Auth\PasswordHasherFactory; +use Authentication\PasswordHasher\PasswordHasherFactory; use Cake\Core\Configure; use Cake\Event\Event; use Cake\TestSuite\TestCase; @@ -72,7 +72,7 @@ public function testChangePasswordHappy() ->method('success') ->with('Password has been changed successfully'); $this->Trait->changePassword(); - $hasher = PasswordHasherFactory::build('Default'); + $hasher = PasswordHasherFactory::build('Authentication.Default'); $this->assertTrue($hasher->check('new', $this->table->get('00000000-0000-0000-0000-000000000001')->password)); } @@ -130,7 +130,7 @@ public function testChangePasswordWithAfterChangeEvent() ->method('success') ->with('Password has been changed successfully'); $this->Trait->changePassword(); - $hasher = PasswordHasherFactory::build('Default'); + $hasher = PasswordHasherFactory::build('Authentication.Default'); $this->assertTrue($hasher->check('new', $this->table->get('00000000-0000-0000-0000-000000000001')->password)); } @@ -456,18 +456,6 @@ public function testRequestGoogleAuthTokenResetWithValidUser($userId, $entityId, { $this->_mockRequestPost(); $this->_mockFlash(); - - $user = $this->table->get($userId); - - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'config']) - ->disableOriginalConstructor() - ->getMock(); - - $this->Trait->Auth->expects($this->any()) - ->method('user') - ->will($this->returnValue($user)); - $this->Trait->Flash->expects($this->any()) ->method($method) ->with($msg); diff --git a/tests/TestCase/Controller/Traits/ProfileTraitTest.php b/tests/TestCase/Controller/Traits/ProfileTraitTest.php index 24dd1912c..3051c344a 100644 --- a/tests/TestCase/Controller/Traits/ProfileTraitTest.php +++ b/tests/TestCase/Controller/Traits/ProfileTraitTest.php @@ -13,6 +13,9 @@ namespace CakeDC\Users\Test\TestCase\Controller\Traits; +/** + * @property \CakeDC\Users\Controller\Traits\ProfileTrait&\PHPUnit\Framework\MockObject\MockObject $Trait + */ class ProfileTraitTest extends BaseTraitTest { /** @@ -20,7 +23,7 @@ class ProfileTraitTest extends BaseTraitTest * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php index dc792ee49..6c4967bf5 100644 --- a/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php +++ b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php @@ -15,11 +15,14 @@ use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\Exception\RecordNotFoundException; +use Cake\Datasource\Paging\PaginatedResultSet; +use Cake\Datasource\ResultSetDecorator; /** * Class SimpleCrudTraitTest * * @package CakeDC\Users\Test\TestCase\Controller\Traits + * @property \CakeDC\Users\Controller\Traits\SimpleCrudTrait&\PHPUnit\Framework\MockObject\MockObject $Trait */ class SimpleCrudTraitTest extends BaseTraitTest { @@ -33,7 +36,7 @@ class SimpleCrudTraitTest extends BaseTraitTest public function setUp(): void { $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; - $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'loadModel', 'paginate']; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'fetchTable', 'paginate']; parent::setUp(); $viewVarsContainer = $this; $this->Trait->expects($this->any()) @@ -42,7 +45,7 @@ public function setUp(): void $viewVarsContainer->viewVars[$param1] = $param2; })); $this->Trait->expects($this->once()) - ->method('loadModel') + ->method('fetchTable') ->will($this->returnValue($this->table)); } @@ -64,20 +67,22 @@ public function tearDown(): void */ public function testIndex() { + $results = new ResultSetDecorator([]); + $paginated = new PaginatedResultSet($results, []); $this->Trait->expects($this->once()) ->method('paginate') ->with($this->table) - ->will($this->returnValue([])); + ->will($this->returnValue($paginated)); $this->Trait->index(); $expected = [ - 'Users' => [], + 'Users' => $paginated, 'tableAlias' => 'Users', '_serialize' => [ 'Users', 'tableAlias', ], ]; - $this->assertSame($expected, $this->viewVars); + $this->assertEquals($expected, $this->viewVars); } /** diff --git a/tests/TestCase/Controller/Traits/SocialTraitTest.php b/tests/TestCase/Controller/Traits/SocialTraitTest.php index b86d5a1a6..c9f2072c9 100644 --- a/tests/TestCase/Controller/Traits/SocialTraitTest.php +++ b/tests/TestCase/Controller/Traits/SocialTraitTest.php @@ -27,6 +27,16 @@ class SocialTraitTest extends BaseTraitTest { + /** + * Fixtures + * + * @var array + */ + public array $fixtures = [ + 'plugin.CakeDC/Users.SocialAccounts', + 'plugin.CakeDC/Users.Users', + ]; + /** * setup * @@ -41,9 +51,10 @@ public function setUp(): void $request = new ServerRequest(); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods(['dispatchEvent', 'redirect', 'set', 'loadComponent']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); - $this->Trait->request = $request; + $this->Trait->setRequest($request); } /** @@ -99,7 +110,7 @@ public function testSocialEmailSuccess() ->with($this->successLoginRedirect) ->will($this->returnValue(new Response())); - $registry = new ComponentRegistry(); + $registry = new ComponentRegistry(new \Cake\Controller\Controller(new \Cake\Http\ServerRequest())); $config = [ 'component' => 'CakeDC/Users.Login', 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), diff --git a/tests/TestCase/Controller/Traits/U2fTraitTest.php b/tests/TestCase/Controller/Traits/U2fTraitTest.php index 6d7db567e..6d3c71132 100644 --- a/tests/TestCase/Controller/Traits/U2fTraitTest.php +++ b/tests/TestCase/Controller/Traits/U2fTraitTest.php @@ -35,7 +35,7 @@ class U2fTraitTest extends BaseTraitTest * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php index e47f2c661..4edf2ac57 100644 --- a/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php +++ b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php @@ -13,7 +13,6 @@ namespace CakeDC\Users\Test\TestCase\Controller\Traits; -use Base64Url\Base64Url; use Cake\Core\Configure; use Cake\Http\Exception\BadRequestException; use Cake\Http\ServerRequest; @@ -21,6 +20,7 @@ use Cake\ORM\TableRegistry; use CakeDC\Users\Model\Entity\User; use CakeDC\Users\Webauthn\AuthenticateAdapter; +use CakeDC\Users\Webauthn\Base64Utility; use CakeDC\Users\Webauthn\RegisterAdapter; use Webauthn\PublicKeyCredentialSource; @@ -36,7 +36,7 @@ class Webauthn2faTraitTest extends BaseTraitTest * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -269,8 +269,8 @@ public function testWebauthn2faRegister() 'type' => 'Webauthn\TrustPath\EmptyTrustPath', ], 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), - 'userHandle' => Base64Url::encode($userId), + 'credentialPublicKey' => Base64Utility::basicEncode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Utility::basicEncode($userId), 'counter' => 191, 'otherUI' => null, ]; @@ -282,6 +282,7 @@ public function testWebauthn2faRegister() $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods($traitMockMethods) + ->setConstructorArgs([$request]) ->getMock(); $this->Trait->expects($this->once()) ->method('getWebauthn2faRegisterAdapter') @@ -324,6 +325,7 @@ public function testWebauthn2faRegisterError() $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods($traitMockMethods) + ->setConstructorArgs([$request]) ->getMock(); $this->Trait->expects($this->once()) ->method('getWebauthn2faRegisterAdapter') @@ -412,8 +414,8 @@ public function testWebauthn2faAuthenticate() 'type' => 'Webauthn\TrustPath\EmptyTrustPath', ], 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), - 'userHandle' => Base64Url::encode($userId), + 'credentialPublicKey' => Base64Utility::basicEncode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Utility::basicEncode($userId), 'counter' => 191, 'otherUI' => null, ]; @@ -425,6 +427,7 @@ public function testWebauthn2faAuthenticate() $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods($traitMockMethods) + ->setConstructorArgs([$request]) ->getMock(); $this->Trait->expects($this->once()) ->method('getWebauthn2faAuthenticateAdapter') @@ -478,6 +481,7 @@ public function testWebauthn2faAuthenticateError() $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); $this->Trait = $this->getMockBuilder($this->traitClassName) ->setMethods($traitMockMethods) + ->setConstructorArgs([$request]) ->getMock(); $this->Trait->expects($this->once()) ->method('getWebauthn2faAuthenticateAdapter') diff --git a/tests/TestCase/Controller/UsersControllerTest.php b/tests/TestCase/Controller/UsersControllerTest.php index 9cebd1f89..da283f8e8 100644 --- a/tests/TestCase/Controller/UsersControllerTest.php +++ b/tests/TestCase/Controller/UsersControllerTest.php @@ -28,7 +28,7 @@ class UsersControllerTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Identifier/SocialIdentifierTest.php b/tests/TestCase/Identifier/SocialIdentifierTest.php index bf93fd11d..ccab2a300 100644 --- a/tests/TestCase/Identifier/SocialIdentifierTest.php +++ b/tests/TestCase/Identifier/SocialIdentifierTest.php @@ -19,7 +19,7 @@ class SocialIdentifierTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Mailer/UsersMailerTest.php b/tests/TestCase/Mailer/UsersMailerTest.php index 294c245e9..3fd6bcb11 100644 --- a/tests/TestCase/Mailer/UsersMailerTest.php +++ b/tests/TestCase/Mailer/UsersMailerTest.php @@ -28,7 +28,7 @@ class UsersMailerTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php b/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php index c9889c527..57211c2fd 100644 --- a/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php +++ b/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php @@ -32,7 +32,7 @@ class SocialAuthMiddlewareTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php b/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php index 5232e736b..8b92d0296 100644 --- a/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php +++ b/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php @@ -26,7 +26,7 @@ class SocialEmailMiddlewareTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; diff --git a/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php b/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php index b98fa6dac..c7e5705ce 100644 --- a/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php @@ -28,7 +28,7 @@ class AuthFinderBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php index df6e5620e..c1a9a536b 100644 --- a/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php @@ -35,7 +35,7 @@ class LinkSocialBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php index 23b12f02f..47e693cd5 100644 --- a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php @@ -14,7 +14,6 @@ namespace CakeDC\Users\Test\TestCase\Model\Behavior; use Cake\Core\Configure; -use Cake\Mailer\Email; use Cake\Mailer\Mailer; use Cake\Mailer\TransportFactory; use Cake\ORM\TableRegistry; @@ -38,7 +37,7 @@ class PasswordBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -57,7 +56,6 @@ public function setUp(): void ->getMock(); TransportFactory::drop('test'); TransportFactory::setConfig('test', ['className' => 'Debug']); - //$this->configEmail = Email::getConfig('default'); Mailer::drop('default'); Mailer::setConfig('default', [ 'transport' => 'test', @@ -73,7 +71,7 @@ public function setUp(): void public function tearDown(): void { unset($this->table, $this->Behavior); - Email::drop('default'); + Mailer::drop('default'); TransportFactory::drop('test'); parent::tearDown(); } diff --git a/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php index 317cc6a80..09cefee54 100644 --- a/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php @@ -14,7 +14,7 @@ namespace CakeDC\Users\Test\TestCase\Model\Behavior; use Cake\Core\Configure; -use Cake\Mailer\Email; +use Cake\Mailer\Mailer; use Cake\Mailer\TransportFactory; use Cake\ORM\TableRegistry; use Cake\Routing\Router; @@ -33,7 +33,7 @@ class RegisterBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', ]; @@ -57,7 +57,7 @@ public function setUp(): void $this->Table = $table; $this->Behavior = $table->behaviors()->Register; TransportFactory::setConfig('test', ['className' => 'Debug']); - Email::setConfig('default', [ + Mailer::setConfig('default', [ 'transport' => 'test', 'from' => 'cakedc@example.com', ]); @@ -71,7 +71,7 @@ public function setUp(): void public function tearDown(): void { unset($this->Table, $this->Behavior); - Email::drop('default'); + Mailer::drop('default'); TransportFactory::drop('test'); parent::tearDown(); } diff --git a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php index 5e35cf818..a7c60db4a 100644 --- a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php @@ -30,7 +30,7 @@ class SocialAccountBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Model/Behavior/SocialBehaviorTest.php b/tests/TestCase/Model/Behavior/SocialBehaviorTest.php index 684f6c4db..948befd95 100644 --- a/tests/TestCase/Model/Behavior/SocialBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SocialBehaviorTest.php @@ -28,7 +28,7 @@ class SocialBehaviorTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Model/Entity/UserTest.php b/tests/TestCase/Model/Entity/UserTest.php index 4f0981f71..2b224b519 100644 --- a/tests/TestCase/Model/Entity/UserTest.php +++ b/tests/TestCase/Model/Entity/UserTest.php @@ -13,8 +13,8 @@ namespace CakeDC\Users\Test\TestCase\Model\Entity; -use Cake\Auth\DefaultPasswordHasher; -use Cake\I18n\FrozenTime; +use Authentication\PasswordHasher\DefaultPasswordHasher; +use Cake\I18n\DateTime; use Cake\I18n\I18n; use Cake\TestSuite\TestCase; use CakeDC\Users\Model\Entity\User; @@ -32,8 +32,8 @@ class UserTest extends TestCase public function setUp(): void { parent::setUp(); - $this->now = FrozenTime::now(); - FrozenTime::setTestNow($this->now); + $this->now = DateTime::now(); + DateTime::setTestNow($this->now); $this->User = new User(); } @@ -45,7 +45,7 @@ public function setUp(): void public function tearDown(): void { unset($this->User); - FrozenTime::setTestNow(); + DateTime::setTestNow(); parent::tearDown(); } diff --git a/tests/TestCase/Model/Table/SocialAccountsTableTest.php b/tests/TestCase/Model/Table/SocialAccountsTableTest.php index 04999b7f5..3328279e3 100644 --- a/tests/TestCase/Model/Table/SocialAccountsTableTest.php +++ b/tests/TestCase/Model/Table/SocialAccountsTableTest.php @@ -26,7 +26,7 @@ class SocialAccountsTableTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.SocialAccounts', 'plugin.CakeDC/Users.Users', ]; diff --git a/tests/TestCase/Model/Table/UsersTableTest.php b/tests/TestCase/Model/Table/UsersTableTest.php index 58b1e7fd6..b9995544f 100644 --- a/tests/TestCase/Model/Table/UsersTableTest.php +++ b/tests/TestCase/Model/Table/UsersTableTest.php @@ -13,7 +13,7 @@ namespace CakeDC\Users\Test\TestCase\Model\Table; -use Cake\Mailer\Email; +use Cake\Mailer\Mailer; use Cake\Mailer\TransportFactory; use Cake\ORM\TableRegistry; use Cake\Routing\Router; @@ -31,7 +31,7 @@ class UsersTableTest extends TestCase * * @var array */ - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; @@ -49,7 +49,7 @@ public function setUp(): void Router::fullBaseUrl('http://users.test'); TransportFactory::drop('test'); TransportFactory::setConfig('test', ['className' => 'Debug']); - Email::setConfig('default', [ + Mailer::setConfig('default', [ 'transport' => 'test', 'from' => 'cakedc@example.com', ]); @@ -64,7 +64,7 @@ public function tearDown(): void { unset($this->Users); Router::fullBaseUrl($this->fullBaseBackup); - Email::drop('default'); + Mailer::drop('default'); parent::tearDown(); } diff --git a/tests/TestCase/Provider/AuthenticationServiceProviderTest.php b/tests/TestCase/Provider/AuthenticationServiceProviderTest.php index a3ed282bf..d9ced290a 100644 --- a/tests/TestCase/Provider/AuthenticationServiceProviderTest.php +++ b/tests/TestCase/Provider/AuthenticationServiceProviderTest.php @@ -94,6 +94,7 @@ public function testGetAuthenticationService() 'identify' => true, 'identityAttribute' => 'identity', 'skipTwoFactorVerify' => true, + 'impersonateSessionKey' => 'AuthImpersonate', ], FormAuthenticator::class => [ 'loginUrl' => '/login', @@ -115,7 +116,6 @@ public function testGetAuthenticationService() $actual = []; foreach ($authenticators as $key => $value) { $config = $value->getConfig(); - unset($config['impersonateSessionKey']); $actual[get_class($value)] = $config; } $this->assertEquals($expected, $actual); @@ -137,6 +137,7 @@ public function testGetAuthenticationService() 'tokenField' => 'api_token', 'dataField' => 'token', 'resolver' => 'Authentication.Orm', + 'hashAlgorithm' => null, ], JwtSubjectIdentifier::class => [ 'tokenField' => 'id', @@ -147,7 +148,6 @@ public function testGetAuthenticationService() $actual = []; foreach ($identifiers as $key => $value) { $config = $value->getConfig(); - unset($config['impersonateSessionKey'], $config['hashAlgorithm']); $actual[get_class($value)] = $config; } $this->assertEquals($expected, $actual); @@ -192,6 +192,7 @@ public function testGetAuthenticationServiceWithoutOneTimePasswordAuthenticator( 'sessionKey' => 'CustomAuth', 'fields' => ['username' => 'email'], 'identify' => true, + 'impersonateSessionKey' => 'AuthImpersonate', ], 'Form' => [ 'className' => 'CakeDC/Auth.Form', @@ -231,6 +232,7 @@ public function testGetAuthenticationServiceWithoutOneTimePasswordAuthenticator( 'identify' => true, 'identityAttribute' => 'identity', 'skipTwoFactorVerify' => true, + 'impersonateSessionKey' => 'AuthImpersonate', ], FormAuthenticator::class => [ 'loginUrl' => '/login', @@ -247,7 +249,6 @@ public function testGetAuthenticationServiceWithoutOneTimePasswordAuthenticator( $actual = []; foreach ($authenticators as $key => $value) { $config = $value->getConfig(); - unset($config['impersonateSessionKey']); $actual[get_class($value)] = $config; } $this->assertEquals($expected, $actual); diff --git a/tests/TestCase/Shell/UsersShellTest.php b/tests/TestCase/Shell/UsersShellTest.php deleted file mode 100644 index 2ce3ea7c0..000000000 --- a/tests/TestCase/Shell/UsersShellTest.php +++ /dev/null @@ -1,420 +0,0 @@ -out = new ConsoleOutput(); - $this->io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock(); - $this->Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - - $this->Shell = $this->getMockBuilder('CakeDC\Users\Shell\UsersShell') - ->setMethods(['in', 'out', '_stop', 'clear', '_usernameSeed', '_generateRandomPassword', - '_generateRandomUsername', '_generatedHashedPassword', 'error', '_updateUser']) - ->setConstructorArgs([$this->io]) - ->getMock(); - - $this->Shell->Users = $this->getMockBuilder('CakeDC\Users\Model\Table\UsersTable') - ->setMethods(['generateUniqueUsername', 'newEntity', 'save', 'updateAll']) - ->getMock(); - - $this->Shell->Command = $this->getMockBuilder('Cake\Shell\Task\CommandTask') - ->setMethods(['in', '_stop', 'clear', 'out']) - ->setConstructorArgs([$this->io]) - ->getMock(); - } - - /** - * Tear Down - * - * @return void - */ - public function tearDown(): void - { - parent::tearDown(); - unset($this->Shell); - } - - /** - * Add user test - * Adding user with username, email, password and role - * - * @return void - */ - public function testAddUser() - { - $user = [ - 'username' => 'yeliparra', - 'password' => '123', - 'email' => 'yeli.parra@example.com', - 'active' => 1, - ]; - $role = 'tester'; - - $this->Shell->expects($this->never()) - ->method('_generateRandomUsername'); - - $this->Shell->expects($this->never()) - ->method('_generateRandomPassword'); - - $this->Shell->Users->expects($this->once()) - ->method('generateUniqueUsername') - ->with($user['username']) - ->will($this->returnValue($user['username'])); - - $entityUser = $this->Users->newEntity($user); - $entityUser->role = $role; - - $this->Shell->Users->expects($this->once()) - ->method('newEntity') - ->with($user) - ->will($this->returnValue($entityUser)); - - $userSaved = $entityUser; - $userSaved->id = 'my-id'; - - $this->Shell->Users->expects($this->once()) - ->method('save') - ->with($entityUser) - ->will($this->returnValue($userSaved)); - - $this->Shell->runCommand(['addUser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); - } - - /** - * Add user test - * Adding user passing no params - * - * @return void - */ - public function testAddUserWithNoParams() - { - $user = [ - 'username' => 'anakin', - 'password' => 'mypassword', - 'email' => 'anakin@example.com', - 'active' => 1, - ]; - - $this->Shell->Users->expects($this->once()) - ->method('generateUniqueUsername') - ->with($user['username']) - ->will($this->returnValue($user['username'])); - - $this->Shell->expects($this->once()) - ->method('_generateRandomPassword') - ->will($this->returnValue($user['password'])); - - $this->Shell->expects($this->once()) - ->method('_generateRandomUsername') - ->will($this->returnValue($user['username'])); - - $entityUser = $this->Users->newEntity($user); - $entityUser->role = 'user'; - - $this->Shell->Users->expects($this->once()) - ->method('newEntity') - ->with($user) - ->will($this->returnValue($entityUser)); - - $userSaved = $entityUser; - $userSaved->id = 'my-id'; - - $this->Shell->Users->expects($this->once()) - ->method('save') - ->with($entityUser) - ->will($this->returnValue($userSaved)); - - //TODO: Add assertions with 'out' - - $this->Shell->runCommand(['addUser']); - } - - /** - * Add user test - * Adding user with username, email, password and role - * - * @return void - */ - public function testAddSuperuser() - { - $user = [ - 'username' => 'yeliparra', - 'password' => '123', - 'email' => 'yeli.parra@example.com', - 'active' => 1, - ]; - $role = 'tester'; - - $this->Shell->expects($this->never()) - ->method('_generateRandomUsername'); - - $this->Shell->expects($this->never()) - ->method('_generateRandomPassword'); - - $this->Shell->Users->expects($this->once()) - ->method('generateUniqueUsername') - ->with($user['username']) - ->will($this->returnValue($user['username'])); - - $entityUser = $this->Users->newEntity($user); - $entityUser->role = $role; - - $this->Shell->Users->expects($this->once()) - ->method('newEntity') - ->with($user) - ->will($this->returnValue($entityUser)); - - $userSaved = $entityUser; - $userSaved->id = 'my-id'; - $userSaved->is_superuser = true; - - $this->Shell->Users->expects($this->once()) - ->method('save') - ->with($entityUser) - ->will($this->returnValue($userSaved)); - - $this->Shell->runCommand(['addSuperuser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); - } - - /** - * Add superadmin user - * - * @return void - */ - public function testAddSuperuserWithNoParams() - { - $this->Shell->Users->expects($this->once()) - ->method('generateUniqueUsername') - ->with('superadmin') - ->will($this->returnValue('superadmin')); - - $this->Shell->expects($this->once()) - ->method('_generateRandomPassword') - ->will($this->returnValue('password')); - - $user = [ - 'username' => 'superadmin', - 'password' => 'password', - 'email' => 'superadmin@example.com', - 'active' => 1, - ]; - $entityUser = $this->Users->newEntity($user); - - $this->Shell->Users->expects($this->once()) - ->method('newEntity') - ->with($user) - ->will($this->returnValue($entityUser)); - - $userSaved = $entityUser; - $userSaved->id = 'my-id'; - $userSaved->is_superuser = true; - $userSaved->role = 'superuser'; - - $this->Shell->Users->expects($this->once()) - ->method('save') - ->with($entityUser) - ->will($this->returnValue($userSaved)); - - $this->Shell->runCommand(['addSuperuser']); - } - - /** - * Reset all passwords - * - * @return void - */ - public function testResetAllPasswords() - { - $this->Shell->expects($this->once()) - ->method('_generatedHashedPassword') - ->will($this->returnValue('hashedPasssword')); - - $this->Shell->Users->expects($this->once()) - ->method('updateAll') - ->with(['password' => 'hashedPasssword'], ['id IS NOT NULL']); - - $this->Shell->runCommand(['resetAllPasswords', '123']); - } - - /** - * Reset all passwords - */ - public function testResetAllPasswordsNoPassingParams() - { - $this->expectException(StopException::class); - $this->expectExceptionMessage('Please enter a password.'); - $this->Shell->runCommand(['resetAllPasswords']); - } - - /** - * Reset password - * - * @return void - */ - public function testResetPassword() - { - $user = $this->Users->newEmptyEntity(); - $user->username = 'user-1'; - $user->password = 'password'; - - $this->Shell->expects($this->once()) - ->method('_updateUser') - ->will($this->returnValue($user)); - - $this->Shell->runCommand(['resetPassword', 'user-1', 'password']); - } - - /** - * Change role - * - * @return void - */ - public function testChangeRole() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); - $this->assertSame('admin', $user['role']); - $this->Shell->runCommand(['changeRole', 'user-1', 'another-role']); - $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); - $this->assertSame('another-role', $user['role']); - } - - /** - * Activate user - * - * @return void - */ - public function testActivateUser() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); - $this->assertFalse($user['active']); - $this->Shell->runCommand(['activateUser', 'user-1']); - $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); - $this->assertTrue($user['active']); - } - - /** - * Delete user - * - * @return void - * @expected - */ - public function testDeleteUser() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - - $this->assertNotEmpty($this->Users->findById('00000000-0000-0000-0000-000000000001')->first()); - $this->assertNotEmpty($this->Users->SocialAccounts->findByUserId('00000000-0000-0000-0000-000000000001')->toArray()); - $this->Shell->runCommand(['deleteUser', 'user-1']); - $this->assertEmpty($this->Users->findById('00000000-0000-0000-0000-000000000001')->first()); - $this->assertEmpty($this->Users->SocialAccounts->findByUserId('00000000-0000-0000-0000-000000000001')->toArray()); - - $this->assertNotEmpty($this->Users->findById('00000000-0000-0000-0000-000000000005')->first()); - $this->Shell->runCommand(['deleteUser', 'user-5']); - $this->assertEmpty($this->Users->findById('00000000-0000-0000-0000-000000000005')->first()); - } - - /** - * test - * - * @return void - */ - public function testAddUserCustomRole() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - $this->assertEmpty($this->Users->findByUsername('custom')->first()); - $this->Shell->runCommand([ - 'addUser', - '--username=custom', - '--password=12345678', - '--email=custom@example.com', - '--role=custom', - ]); - $user = $this->Users->findByUsername('custom')->first(); - $this->assertSame('custom', $user['role']); - } - - /** - * test - * - * @return void - */ - public function testAddUserDefaultRole() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - $this->assertEmpty($this->Users->findByUsername('custom')->first()); - Configure::write('Users.Registration.defaultRole', false); - $this->Shell->runCommand([ - 'addUser', - '--username=custom', - '--password=12345678', - '--email=custom@example.com', - ]); - $user = $this->Users->findByUsername('custom')->first(); - $this->assertSame('user', $user['role']); - } - - /** - * test - * - * @return void - */ - public function testAddUserCustomDefaultRole() - { - $this->Shell = new UsersShell($this->io); - $this->Shell->Users = $this->Users; - $this->assertEmpty($this->Users->findByUsername('custom')->first()); - Configure::write('Users.Registration.defaultRole', 'emperor'); - $this->Shell->runCommand([ - 'addUser', - '--username=custom', - '--password=12345678', - '--email=custom@example.com', - ]); - $user = $this->Users->findByUsername('custom')->first(); - $this->assertSame('emperor', $user['role']); - } -} diff --git a/tests/TestCase/Webauthn/AuthenticateAdapterTest.php b/tests/TestCase/Webauthn/AuthenticateAdapterTest.php index 70c101c90..e61b1d9a2 100644 --- a/tests/TestCase/Webauthn/AuthenticateAdapterTest.php +++ b/tests/TestCase/Webauthn/AuthenticateAdapterTest.php @@ -3,22 +3,30 @@ namespace CakeDC\Users\Test\TestCase\Webauthn; -use Base64Url\Base64Url; use Cake\Core\Configure; use Cake\Http\ServerRequestFactory; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; use CakeDC\Users\Webauthn\AuthenticateAdapter; use Webauthn\PublicKeyCredentialRequestOptions; -use Webauthn\PublicKeyCredentialSource; class AuthenticateAdapterTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; + /** + * @inheritDoc + */ + protected function setUp(): void + { + parent::setUp(); + Configure::write('Webauthn2fa.appName', 'ACME Webauthn Server'); + Configure::write('Webauthn2fa.id', 'localhost'); + } + /** * Test getRegisterOptions method * @@ -26,8 +34,6 @@ class AuthenticateAdapterTest extends TestCase */ public function testGetOptions() { - Configure::write('Webauthn2fa.appName', 'ACME Webauthn Server'); - Configure::write('Webauthn2fa.id', 'localhost'); $userId = '00000000-0000-0000-0000-000000000001'; $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $user = $UsersTable->get($userId); @@ -37,43 +43,5 @@ public function testGetOptions() $options = $adapter->getOptions(); $this->assertInstanceOf(PublicKeyCredentialRequestOptions::class, $options); $this->assertSame($options, $request->getSession()->read('Webauthn2fa.authenticateOptions')); - $data = json_decode('{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAA","signature":"MEYCIQCv7EqsBRtf2E4o_BjzZfBwNpP8fLjd5y6TUOLWt5l9DQIhANiYig9newAJZYTzG1i5lwP-YQk9uXFnnDaHnr2yCKXL","userHandle":"","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4ZGowQ0JmWDY5MnFzQVRweTBrTmM4NTMzSmR2ZExVcHFZUDh3RFRYX1pFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9"},"type":"public-key"}', true); - $request = $request->withParsedBody($data); - - $adapter = $this->getMockBuilder(AuthenticateAdapter::class) - ->onlyMethods(['loadAndCheckAssertionResponse']) - ->setConstructorArgs([$request]) - ->getMock(); - $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; - $userId = '00000000-0000-0000-0000-000000000001'; - $credentialData = [ - 'publicKeyCredentialId' => $publicKeyCredentialId, - 'type' => 'public-key', - 'transports' => [], - 'attestationType' => 'none', - 'trustPath' => [ - 'type' => 'Webauthn\TrustPath\EmptyTrustPath', - ], - 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), - 'userHandle' => Base64Url::encode($userId), - 'counter' => 191, - 'otherUI' => null, - ]; - $credential = PublicKeyCredentialSource::createFromArray($credentialData); - $adapter->expects($this->once()) - ->method('loadAndCheckAssertionResponse') - ->with( - $this->equalTo($options) - ) - ->willReturn($credential); - $actual = $adapter->verifyResponse(); - $this->assertEquals($credential, $actual); - - $adapter = new AuthenticateAdapter($request); - - $this->expectException(\Assert\InvalidArgumentException::class); - $this->getExpectedExceptionMessage('The credential ID is not allowed.'); - $adapter->verifyResponse(); } } diff --git a/tests/TestCase/Webauthn/Base64UtilityTest.php b/tests/TestCase/Webauthn/Base64UtilityTest.php new file mode 100644 index 000000000..52008d197 --- /dev/null +++ b/tests/TestCase/Webauthn/Base64UtilityTest.php @@ -0,0 +1,80 @@ +assertNotEmpty($encoded); + $this->assertStringEndsNotWith('=', $encoded); + $decoded = Base64Utility::basicDecode($encoded); + $this->assertSame($originalText, $decoded); + } + + /** + * @return \string[][] + */ + public function dataProviderBasicEncodeDecodeText(): array + { + return [ + ['00000000-0000-0000-0000-000000000002'], + ['$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde'], + ['First Name'], + ]; + } + + /** + * Test method complyEncodedNoPadding + * + * @param string $encodedTextWithPadding + * @param string $originalText + * @dataProvider dataProviderComplyEncodedNoPadding + * @return void + */ + public function testComplyEncodedNoPadding($encodedTextWithPadding, $originalText) + { + $encoded = Base64Utility::complyEncodedNoPadding($encodedTextWithPadding); + $this->assertNotEmpty($encoded); + $this->assertStringEndsNotWith('=', $encoded); + $decoded = Base64UrlSafe::decodeNoPadding($encoded); + $this->assertSame($originalText, $decoded); + } + + /** + * @return \string[][] + */ + public function dataProviderComplyEncodedNoPadding(): array + { + return [ + ['JDJ5JDEwJE52dTdpcFAuejh0aUlsNzVPZFV2dC44NnZ1RzZpS01vSElPYzdPN21ib0ZJODU=', '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85'], + ['MDAwMDAwMDAwMDM=', '00000000003'], + //does not end with = + ['MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAz', '00000000-0000-0000-0000-000000000003'], + //end with == + ['Rmlyc3QgTmFtZQ==', 'First Name'], + ]; + } +} diff --git a/tests/TestCase/Webauthn/RegisterAdapterTest.php b/tests/TestCase/Webauthn/RegisterAdapterTest.php index 3ce86a8e8..15dbb7f1f 100644 --- a/tests/TestCase/Webauthn/RegisterAdapterTest.php +++ b/tests/TestCase/Webauthn/RegisterAdapterTest.php @@ -1,24 +1,48 @@ get('CakeDC/Users.Users'); $user = $UsersTable->get($userId); @@ -38,53 +60,90 @@ public function testGetOptions() $options = $adapter->getOptions(); $this->assertInstanceOf(PublicKeyCredentialCreationOptions::class, $options); $this->assertSame($options, $request->getSession()->read('Webauthn2fa.registerOptions')); + } - $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + /** + * Test verifyResponse + * + * @return void + * @throws \Webauthn\Exception\InvalidDataException + */ + public function testVerifyResponse(): void + { + $options = PublicKeyCredentialCreationOptions + ::create( + new PublicKeyCredentialRpEntity('My Application'), + new PublicKeyCredentialUserEntity('test@foo.com', random_bytes( + 64 + ), 'Test PublicKeyCredentialUserEntity'), + base64_decode( + '9WqgpRIYvGMCUYiFT20o1U7hSD193k11zu4tKP7wRcrE26zs1zc4LHyPinvPGS86wu6bDvpwbt8Xp2bQ3VBRSQ==', + true + ), + [new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256)] + ); + + $data = '{"id":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB_MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1-RIuTF9DUtEJZEEK","type":"public-key","rawId":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB/MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1+RIuTF9DUtEJZEEK","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiI5V3FncFJJWXZHTUNVWWlGVDIwbzFVN2hTRDE5M2sxMXp1NHRLUDd3UmNyRTI2enMxemM0TEh5UGludlBHUzg2d3U2YkR2cHdidDhYcDJiUTNWQlJTUSIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjIobiMfS7pLMMQTjIzBw3+hADjTsu6nVoWkEO3TrVYkdnFQfzDW2cVEYtnL4ErykiC295iEnvZTzRvbGIKI7mOYjYp2DoOoUVcZptFbLLjRtqZtfkSLkxfQ1LRCWRBCqUBAgMmIAEhWCAcPxwKyHADVjTgTsat4R/Jax6PWte50A8ZasMm4w6RxCJYILt0FCiGwC6rBrh3ySNy0yiUjZpNGAhW+aM9YYyYnUTJ"}}'; + $userId = '00000000-0000-0000-0000-000000000002'; + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $user = $UsersTable->get($userId); + $request = ServerRequestFactory::fromGlobals(); $request = $request->withParsedBody( json_decode($data, true) ); - //Mock success response - $adapter = $this->getMockBuilder(RegisterAdapter::class) - ->onlyMethods(['loadAndCheckAttestationResponse']) - ->setConstructorArgs([$request]) - ->getMock(); - $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; - $userId = '00000000-0000-0000-0000-000000000002'; - $credentialData = [ - 'publicKeyCredentialId' => $publicKeyCredentialId, - 'type' => 'public-key', - 'transports' => [], - 'attestationType' => 'none', - 'trustPath' => [ - 'type' => 'Webauthn\TrustPath\EmptyTrustPath', - ], - 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), - 'userHandle' => Base64Url::encode($userId), - 'counter' => 191, - 'otherUI' => null, - ]; - $credential = PublicKeyCredentialSource::createFromArray($credentialData); - $adapter->expects($this->once()) - ->method('loadAndCheckAttestationResponse') - ->with( - $this->equalTo($options) - ) - ->willReturn($credential); + $request->getSession()->write('Webauthn2fa.User', $user); + $request->getSession()->write('Webauthn2fa.registerOptions', $options); + $adapter = new RegisterAdapter($request, $UsersTable); $credentialsList = $adapter->getUser()->additional_data['webauthn_credentials'] ?? []; $this->assertCount(0, $credentialsList); $actual = $adapter->verifyResponse(); - $this->assertEquals($credential, $actual); + $this->assertInstanceOf(PublicKeyCredentialSource::class, $actual); $credentialsList = $adapter->getUser()->additional_data['webauthn_credentials']; $this->assertCount(1, $credentialsList); $key = key($credentialsList); $this->assertIsString($key); $this->assertTrue(isset($credentialsList[$key]['publicKeyCredentialId'])); $this->assertTrue($adapter->hasCredential()); - //Invalid challenge without mock - $adapter = new RegisterAdapter($request); - $this->expectException(\Assert\InvalidArgumentException::class); - $this->getExpectedExceptionMessage('Invalid challenge.'); + } + + /** + * Test verifyResponse + * + * @return void + * @throws \Webauthn\Exception\InvalidDataException + */ + public function testVerifyResponseOptionsDoesNotMatch(): void + { + $options = PublicKeyCredentialCreationOptions + ::create( + new PublicKeyCredentialRpEntity('My Application'), + new PublicKeyCredentialUserEntity('test@foo.com', random_bytes( + 64 + ), 'Test PublicKeyCredentialUserEntity'), + base64_decode( + '9WqgpRIYvGMCUYiFT20o1U7hSD193k11zu4tKP7wRcrE26zs1zc4LHyPinvPGS86wu6bDvpwbt8Xp2bQ3VBRSQ==', + true + ), + [new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256)] + ); + + $data = '{"id":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB_MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1-RIuTF9DUtEJZEEK","type":"public-key","rawId":"mMihuIx9LukswxBOMjMHDf6EAONOy7qdWhaQQ7dOtViR2cVB/MNbZxURi2cvgSvKSILb3mISe9lPNG9sYgojuY5iNinYOg6hRVxmm0VssuNG2pm1+RIuTF9DUtEJZEEK","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJNREF3TURBd01EQXRNREF3TUMwd01EQXdMVEF3TURBdE1EQXdNREF3TURBd01EQXoiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2xvY2FsaG9zdDo4NDQzIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAYJjIobiMfS7pLMMQTjIzBw3+hADjTsu6nVoWkEO3TrVYkdnFQfzDW2cVEYtnL4ErykiC295iEnvZTzRvbGIKI7mOYjYp2DoOoUVcZptFbLLjRtqZtfkSLkxfQ1LRCWRBCqUBAgMmIAEhWCAcPxwKyHADVjTgTsat4R/Jax6PWte50A8ZasMm4w6RxCJYILt0FCiGwC6rBrh3ySNy0yiUjZpNGAhW+aM9YYyYnUTJ"}}'; + //Mock success response + $userId = '00000000-0000-0000-0000-000000000002'; + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $user = $UsersTable->get($userId); + $request = ServerRequestFactory::fromGlobals(); + $request = $request->withParsedBody( + json_decode($data, true) + ); + $request->getSession()->write('Webauthn2fa.User', $user); + $request->getSession()->write('Webauthn2fa.registerOptions', $options); + $adapter = new RegisterAdapter($request, $UsersTable); + + $credentialsList = $adapter->getUser()->additional_data['webauthn_credentials'] ?? []; + $this->assertCount(0, $credentialsList); + $this->expectException(AuthenticatorResponseVerificationException::class); + $this->expectExceptionMessage('Invalid challenge'); $adapter->verifyResponse(); } } diff --git a/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php b/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php index 021d08c8b..51cb23c61 100644 --- a/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php +++ b/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php @@ -3,16 +3,16 @@ namespace CakeDC\Users\Test\TestCase\Webauthn\Repository; -use Base64Url\Base64Url; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; +use CakeDC\Users\Webauthn\Base64Utility; use CakeDC\Users\Webauthn\Repository\UserCredentialSourceRepository; use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialUserEntity; class UserCredentialSourceRepositoryTest extends TestCase { - public $fixtures = [ + protected array $fixtures = [ 'plugin.CakeDC/Users.Users', 'plugin.CakeDC/Users.SocialAccounts', ]; @@ -87,8 +87,8 @@ public function testSaveCredentialSource() 'type' => 'Webauthn\TrustPath\EmptyTrustPath', ], 'aaguid' => '00000000-0000-0000-0000-000000000000', - 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), - 'userHandle' => Base64Url::encode($userId), + 'credentialPublicKey' => Base64Utility::basicEncode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Utility::basicEncode($userId), 'counter' => 191, 'otherUI' => null, ]; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f5424bd41..59b133ad5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -60,11 +60,16 @@ Cake\Core\Configure::write('debug', true); ini_set('intl.default_locale', 'en_US'); - -$TMP = new \Cake\Filesystem\Folder(TMP); -$TMP->create(TMP . 'cache/models', 0777); -$TMP->create(TMP . 'cache/persistent', 0777); -$TMP->create(TMP . 'cache/views', 0777); +$pathsCache = [ + TMP . 'cache/models', + TMP . 'cache/persistent', + TMP . 'cache/views', +]; +foreach ($pathsCache as $path) { + if (!file_exists($path)) { + mkdir($path, 0777, true); + } +} $cache = [ 'default' => [ @@ -108,6 +113,8 @@ 'timezone' => 'UTC', ]); +\Cake\Database\TypeFactory::map('json', 'Cake\Database\Type\JsonType'); + class_alias('TestApp\Controller\AppController', 'App\Controller\AppController'); \Cake\Core\Configure::write('App', [ diff --git a/tests/test_app/TestApp/Application.php b/tests/test_app/TestApp/Application.php index f34718450..b89fcea65 100644 --- a/tests/test_app/TestApp/Application.php +++ b/tests/test_app/TestApp/Application.php @@ -37,8 +37,8 @@ public function bootstrap(): void ], ]); } - if (!\Cake\Mailer\Email::getConfig('default')) { - \Cake\Mailer\Email::setConfig([ + if (!\Cake\Mailer\Mailer::getConfig('default')) { + \Cake\Mailer\Mailer::setConfig([ 'default' => [ 'transport' => 'default', 'from' => 'you@localhost', diff --git a/tests/test_app/TestApp/Controller/AppController.php b/tests/test_app/TestApp/Controller/AppController.php index db11ec36d..dee6ddc22 100644 --- a/tests/test_app/TestApp/Controller/AppController.php +++ b/tests/test_app/TestApp/Controller/AppController.php @@ -28,6 +28,5 @@ public function initialize(): void { parent::initialize(); $this->loadComponent('Flash'); - $this->loadComponent('RequestHandler'); } }