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('