diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..b1c838831 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Remove files for archives generated using `git archive` +.travis.yml export-ignore +.gitattributes export-ignore +phpunit.xml.dist export-ignore diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..87fe2a21f --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,7 @@ +codecov: + require_ci_to_pass: yes + +coverage: + range: "90...100" + +comment: false \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..032c79a25 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,120 @@ +name: CI + +on: + push: + + pull_request: + branches: + - '*' + +jobs: + testsuite: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + php-version: ['7.3', '7.4', '8.0', '8.1'] + db-type: [sqlite, mysql, pgsql] + prefer-lowest: [''] + + steps: + - name: Setup MySQL latest + if: matrix.db-type == 'mysql' + run: docker run --rm --name=mysqld -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=cakephp -p 3306:3306 -d mysql --default-authentication-plugin=mysql_native_password --disable-log-bin + + - name: Setup PostgreSQL latest + if: matrix.db-type == 'pgsql' + run: docker run --rm --name=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=cakephp -p 5432:5432 -d postgres + + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, intl, apcu, sqlite, pdo_sqlite, pdo_${{ matrix.db-type }}, ${{ matrix.db-type }} + ini-values: apc.enable_cli = 1 + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Get date part for cache key + id: key-date + run: echo "::set-output name=date::$(date +'%Y-%m')" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }} + + - name: composer install + run: | + if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then + composer update --prefer-lowest --prefer-stable + else + composer update + fi + + - name: Setup problem matchers for PHPUnit + if: matrix.php-version == '7.4' && matrix.db-type == 'mysql' + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Run PHPUnit + run: | + 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 + 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' + uses: codecov/codecov-action@v1 + + cs-stan: + name: Coding Standard & Static Analysis + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.3' + extensions: mbstring, intl, apcu + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Get date part for cache key + id: key-date + run: echo "::set-output name=date::$(date +'%Y-%m')" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.prefer-lowest }} + + - name: composer install + run: composer stan-setup + + - name: Run PHP CodeSniffer + run: composer cs-check + +# - name: Run psalm +# if: success() || failure() +# run: vendor/bin/psalm.phar --output-format=github + + - name: Run phpstan + if: success() || failure() + run: composer stan \ No newline at end of file diff --git a/.gitignore b/.gitignore index c89d5a392..f696b2eed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock .php_cs* /coverage +.phpunit.result.cache diff --git a/.semver b/.semver index d42bf6c44..726c4d95e 100644 --- a/.semver +++ b/.semver @@ -1,5 +1,5 @@ --- -:major: 7 +:major: 11 :minor: 0 :patch: 0 :special: '' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e71ec741f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -language: php - -php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 - -sudo: false - -env: - matrix: - - DB=mysql db_dsn='mysql://travis@0.0.0.0/cakephp_test' - - DB=pgsql db_dsn='postgres://postgres@127.0.0.1/cakephp_test' - - DB=sqlite db_dsn='sqlite:///:memory:' - - global: - - DEFAULT=1 - -matrix: - fast_finish: true - - include: - - php: 7.1 - env: PHPCS=1 DEFAULT=0 - - - php: 7.1 - env: CODECOVERAGE=1 DEFAULT=0 DB=mysql db_dsn='mysql://travis@0.0.0.0/cakephp_test' - -before_script: - - composer self-update - - composer install --prefer-dist --no-interaction - - - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test; GRANT ALL PRIVILEGES ON cakephp_test.* TO travis@localhost;'; fi" - - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" - - - sh -c "if [ '$PHPCS' = '1' ]; then composer require 'cakephp/cakephp-codesniffer:@stable'; fi" - - sh -c "if [ '$COVERALLS' = '1' ]; then composer require --dev 'satooshi/php-coveralls:^2.0'; fi" - - - sh -c "if [ '$COVERALLS' = '1' ]; then mkdir -p build/logs; fi" - -script: - - sh -c "if [ '$DEFAULT' = '1' ]; then ./vendor/bin/phpunit --stderr; fi" - - sh -c "if [ '$PHPCS' = '1' ]; then ./vendor/bin/phpcs -p -n --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests; fi" - - sh -c "if [ '$CODECOVERAGE' = '1' ]; then phpunit --coverage-clover=clover.xml || true; fi" - - sh -c "if [ '$CODECOVERAGE' = '1' ]; then wget -O codecov.sh https://codecov.io/bash; fi" - - sh -c "if [ '$CODECOVERAGE' = '1' ]; then bash codecov.sh; fi" - -notifications: - email: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce6ea612..78c464692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,106 @@ Changelog ========= +Releases for CakePHP 4.3 + +* 11.0.0 + * Switched tests to new cakephp schema + * Update to PHPUnit 9.5 + + +Releases for CakePHP 4 +------------- +* 9.2.0 + * Switch to github actions + * New event AfterEmailTokenValidation + * Remove deprecations + +* 9.0.5 + * Added change api token shell command + +* 9.0.4 + * Fixed deprecations and stan issues + * Improved docs + * Fixed issue where RememberMe cookie + * Fixed deprecated UserHelper::isAuthorized + +* 9.0.3 + * Ukrainian (uk) by @yarkm13 + * Docs improvements + * Fix DebugKit permissions issues + +* 9.0.2 + * Added a custom Unauthorized Handler + * If logged user access unauthorized url he is redirected to referer url or '/' if no referer url + * If not logged user access unauthorized url he is redirected to configured url (default to login) + * on login we only use the redirect url from querystring 'redirect' if user can access the target url + * App can configure a callable for 'url' option to define a custom logic to retrieve the url for unauthorized redirect + * Added postLink method to AuthLinkHelper + * UserHelper::welcome now works with request's attribute 'identity' + +* 9.0.1 + * Improved routes + * Improved integration tests + * Fixed warnings related to arguments in function calls + +* 9.0.0 + * Migration to CakePHP 4 + * Compatible with cakephp/authentication + * Compatible with cakephp/authorization + * Added/removed/changed some configurations to work with new authentication/authorization plugins, [please check Migration guide for more info](https://github.com/CakeDC/users/blob/9.next/Docs/Documentation/Migration/8.x-9.0.md). + * Events constants were moved/removed from AuthComponent to Plugin class and their values was also updated, [please check Migration guide for more info](https://github.com/CakeDC/users/blob/9.next/Docs/Documentation/Migration/8.x-9.0.md). + * Migrated usage of AuthComponent to Authorization/Authentication plugins. Releases for CakePHP 3 ------------- +* 8.5.1 + * Added new `UsersAuthComponent::EVENT_SOCIAL_LOGIN_EXISTING_ACCOUNT` +* 8.5.0 + * Added new `UsersAuthComponent::EVENT_BEFORE_SOCIAL_LOGIN_REDIRECT` + * Added finder to get existing social account + * Improved social login to updated social account when account already exists + * Improved URLs in template to avoid issue in prefixed routes + +* 8.4.0 + * Rehash password if needed at login + +* 8.3.0 + * Bootstrap don't need to listen for EVENT_FAILED_SOCIAL_LOGIN + +* 8.2.1 + * Fix scope in facebook social login + +* 8.2 + * Removed deprecations for CakePHP 3.7 + +* 8.1 + * Added Yubico U2F Authentication + +* 8.0.3 + * Updated to latest version of Google OAuth + * Added plugin object + * Fixed action changePassword to work with post or put request + +* 8.0.2 + * Add default role for users registered via social login + +* 8.0.1 + * Fixed 2fa link preserve querystring + +* 8.0.0 + * Added new events `Users.Component.UsersAuth.onExpiredToken` and `Users.Component.UsersAuth.afterResendTokenValidation` + * Added 2 factor authentication checkers to allow customization + * Added Mapper classes to social auth services as a way to generalize url/avatar retrieval + * Fix issues with recent changes in Facebook API + * Added new translations + * Improved customization options for recaptcha integration + +* 7.0.2 + * Fixed an issue with 2FA only working on the second try + +* 7.0.1 + * Fixed a security issue in 2 factor authentication, reported by @ndm2 + * Updated to cakedc/auth ^3.0 + * Documentation fixes * 7.0.0 * Removed deprecations for CakePHP 3.6 @@ -16,7 +114,7 @@ Releases for CakePHP 3 * Updated Facebook Graph version to 2.8 * Fixed flash error messages on logic * Added link social account feature for twitter - * Switched to codecov + * Switched to codecov * 5.2.0 * Compatible with 3.5, deprecations will be removed in next major version of the plugin @@ -68,7 +166,7 @@ Releases for CakePHP 3 * 4.1.2 * Fix RememberMe redirect * Fix AuthLink rendering inside Cells - + * 4.1.1 * Add missing password field in add user @@ -85,7 +183,7 @@ Releases for CakePHP 3 * Fixed RegisterBehavior api, make getRegisterValidators public. * 3.2.3 - * Added compatibility with CakePHP 3.3+ + * Added compatibility with CakePHP 3.3+ * Fixed several bugs, including regression issue with Facebook login & improvements * 3.2.2 @@ -98,7 +196,7 @@ Releases for CakePHP 3 * Improved registration and reset password user already logged in logic * Several bugfixes * AuthLinkHelper added to render links if user is allowed only - + * 3.1.5 * SocialAuthenticate improvements * Authorize Rules. Owner rule diff --git a/Docs/Documentation/AuthLinkHelper.md b/Docs/Documentation/AuthLinkHelper.md new file mode 100644 index 000000000..ace2bdeb5 --- /dev/null +++ b/Docs/Documentation/AuthLinkHelper.md @@ -0,0 +1,55 @@ +AuthLinkHelper +============= + +The AuthLink Helper has some methods that may be needed if you want to improve your templates and add features to your app in an easy way. +This helper provide two methods that allow you to hide or display links and postLinks based on the permissions file. +No more permissions check in your views ! If the permissions file is update, you do not have to replicate the permissions logic in the views. + +Setup +--------------- + +Enable the Helper in `src/view/AppView.php`: +```php +class AppView extends View +{ + public function initialize() + { + parent::initialize(); + $this->loadHelper('CakeDC/Users.AuthLink'); + } +} +``` + +Link +----------------- + +You can use this helper like the initial [cakePhp HtmlHelper link method](https://book.cakephp.org/4/en/views/helpers/html.html#creating-links) : + +In templates +```diff +- echo $this->Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ++ echo $this->AuthLink->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) +``` + +PostLink +----------------- + +You can use this helper like the initial [cakePhp FormHelper postLink method](https://book.cakephp.org/4/en/views/helpers/form.html#creating-post-links) : + +In templates +```diff +- echo $this->Form->postLink(__d('cake_d_c/users', 'Delete'), ['action' => 'delete', $user->id], ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $user->id)]) ++ echo $this->AuthLink->postLink(__d('cake_d_c/users', 'Delete'), ['action' => 'delete', $user->id], ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $user->id)]) +``` + +Before and After +----------------- + +The link method allow you to add two additional parameters in the options array. +Those two parameters are `before` and `after` to quickly inject some html code in the link, like icons etc + +```php +echo $this->AuthLink->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index', 'before' => '']); +``` + +Before and After are only implemented for the link method. diff --git a/Docs/Documentation/Authentication.md b/Docs/Documentation/Authentication.md new file mode 100644 index 000000000..9d692dd21 --- /dev/null +++ b/Docs/Documentation/Authentication.md @@ -0,0 +1,209 @@ +Authentication +============== +This plugin uses the new authentication plugin [cakephp/authentication](https://github.com/cakephp/authentication/) +instead of CakePHP Authentication component, but don't worry, the default configuration should be enough for your +projects. + +We've tried to simplify configuration as much as possible using defaults, but keep the ability to override them when needed. + +Authentication Component +------------------------ + +The default behavior is to load the authentication component at UsersController, +defining the default urls for loginAction, loginRedirect, logoutRedirect but not requiring +the request to have a identity. + +If you prefer to load the component yourself you can set 'Auth.AuthenticationComponent.load': + +``` +Configure:write('Auth.AuthenticationComponent.load', false); +``` + +And load the component at any controller: + +``` +$authenticationConfig = Configure::read('Auth.AuthenticationComponent'); +$this->loadComponent('Authentication.Authentication', $authenticationConfig); +$userId = $this->Authentication->getIdentity()->getIdentifier(); +$user = $this->Authentication->getIdentity()->getOriginalData(); +``` +The default configuration for Auth.AuthenticationComponent is: + +```php +[ + 'load' => true, + 'loginRedirect' => '/', + 'requireIdentity' => false +] +``` + +[Check the component options at the it's source code for more infomation](https://github.com/cakephp/authentication/blob/master/src/Controller/Component/AuthenticationComponent.php#L38) + +Authenticators +-------------- + +The cakephp/authentication plugin provides the main structure for the authenticators used in this plugin, +we also use some custom authenticators to work with social providers, reCaptcha and cookie. The default +list of authenticators includes: + +- 'Authentication.Session' +- 'CakeDC/Auth.Form' +- 'Authentication.Token' +- 'CakeDC/Auth.Cookie' +- 'CakeDC/Users.Social'//Works with SocialAuthMiddleware +- 'CakeDC/Users.SocialPendingEmail' + +**If you enable 'OneTimePasswordAuthenticator.login' we also load the CakeDC/Auth.TwoFactor** + +These authenticators should be enough for your application, but you easily customize it +setting the Auth.Authenticators config key. + +For example if you add JWT authenticator you must add this to your config/users.php file: + +```php +'Auth.Authenticators.Jwt' => [ + 'queryParam' => 'token', + 'skipTwoFactorVerify' => true, + 'className' => 'Authentication.Jwt', +], +``` + +**You may have noticed the 'skipTwoFactorVerify' option, this option is used to identify if a authenticator should skip +the two factor flow** + +The authenticators are loaded by \CakeDC\Users\Loader\AuthenticationServiceLoader class at load authentication +service method from plugin object. + +See the full Auth.Authenticators at config/users.php + +Identifiers +----------- +The identifies are defined to work correctly with the default authenticators, we are using these identifiers: + +- Authentication.Password, for Form authenticator +- CakeDC/Users.Social, for Social and SocialPendingEmail authenticators +- Authentication.Token, for TokenAuthenticator + +As you add more authenticators you may need to add identifiers, please check identifiers available at +[official documentation](https://github.com/cakephp/authentication/blob/master/docs/Identifiers.md) + +The default value for Auth.Identifiers is: + +```php +[ + 'Password' => [ + 'className' => 'Authentication.Password', + 'fields' => [ + 'username' => ['username', 'email'], + 'password' => 'password' + ], + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'finder' => 'active' + ], + ], + "Social" => [ + 'className' => 'CakeDC/Users.Social', + 'authFinder' => 'active' + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'tokenField' => 'api_token', + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'finder' => 'active' + ], + ] +] +``` +The identifiers are loaded by \CakeDC\Users\Loader\AuthenticationServiceLoader class at load authentication +service method from plugin object. + + +Handling Login Result +--------------------- +For both form login and social login we use a base component 'CakeDC/Users.Login' to handle login, +it check the result of authentication service to redirect user to a internal page or show an authentication +error. It provide some error messages for specific authentication result status, please check the config/users.php file. + +To use a custom component to handle the login you should update your config/users.php file with: + +```php +'Auth.SocialLoginFailure.component' => 'MyLoginA', +'Auth.FormLoginFailure.component' => 'MyLoginB', +``` + +The default configuration are: +```php +[ + ... + 'Auth' => [ + ... + 'SocialLoginFailure' => [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + 'FAILURE_USER_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + 'FAILURE_ACCOUNT_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ) + ], + 'targetAuthenticator' => 'CakeDC\Users\Authenticator\SocialAuthenticator' + ], + 'FormLoginFailure' => [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + 'FAILURE_INVALID_RECAPTCHA' => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => 'CakeDC\Auth\Authenticator\FormAuthenticator' + ] + ... + ] +] +``` + +Authentication Service Loader +----------------------------- +To make the integration with cakephp/authentication easier we load the authenticators and identifiers +defined at Auth configuration and other components to work with social provider, two-factor authentication. + +If the configuration is not enough for your project you may create a custom loader extending the +default provided. + +- Create file src/Loader/AppAuthenticationServiceLoader.php + +```php +loadAuthenticator('MyCustom', []); + } + } +} +``` +- Add this to your config/users.php file to change the authentication service loader: + +```php +'Auth.Authentication.serviceLoader' => \App\Loader\AppAuthenticationServiceLoader::class, +``` diff --git a/Docs/Documentation/Authorization.md b/Docs/Documentation/Authorization.md new file mode 100644 index 000000000..58809f524 --- /dev/null +++ b/Docs/Documentation/Authorization.md @@ -0,0 +1,138 @@ +Authorization +============= +This plugin uses the new plugin for authorization [cakephp/authorization](https://github.com/cakephp/authorization/) +instead of CakePHP Authorization component, but don't worry, the default configuration should be enough for your +projects. We tried to allow you to start quickly without the need to configure a lot of things and also +allow you to configure as much as possible. + + +If you don't want the plugin to autoload setup authorization, you can disable +in your config/users.php with: + +```php +'Auth.Authorization.enabled' => false, +``` + +Authorization Middleware +------------------------ +We load the RequestAuthorization and Authorization middleware with OrmResolver and RbacProvider(work with RequestAuthorizationMiddleware). + +The middleware accepts some additional configurations, you can update in your +config/users.php file: +```php +'Auth.AuthorizationMiddleware' => $config, +``` + +The default configuration for authorization middleware is: +```php +[ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', + ] +], +``` + +You can check the configuration options available for authorization middleware at the +[official documentation](https://github.com/cakephp/authorization/blob/master/docs/en/middleware.rst). + +The `CakeDC/Users.DefaultRedirect` offers additional behavior and config: + * If logged user access unauthorized url he is redirected to referer url or '/' if no referer url + * If not logged user access unauthorized url he is redirected to configured url (default to login) + * on login we only use the redirect url from querystring 'redirect' if user can access the target url + * App can configure a callable for 'url' option to define a custom logic to retrieve the url for unauthorized redirect + * App can configure a flash message + +You could do the following to set a custom url and flash message: + +```php +[ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', + 'url' => [ + 'plugin' => false, + 'prefix' => false, + 'controller' => 'Pages', + 'action' => 'home' + ], + 'flash' => [ + 'message' => 'My custom message', + 'key' => 'flash', + 'element' => 'flash/error', + 'params' => [], + ], + ] +], +``` +OR +```php +[ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', + 'url' => function($request, $options) { + //custom logic + + return $url; + }, + 'flash' => [ + 'message' => 'My custom message', + 'key' => 'flash', + 'element' => 'flash/error', + 'params' => [], + ], + ] +], +``` +Authorization Component +----------------------- +We autoload the authorization component at users controller using the default configuration, +if you don't want the plugin to autoload it, you can add this to your config/users.php file: + +```php +'Auth.AuthorizationComponent.enabled' => false, +``` + +You can check the configuration options available for authorization component at the +[official documentation](https://github.com/cakephp/authorization/blob/master/docs/Component.md) + +Authorization Service Loader +----------------------------- +To make the integration with cakephp/authorization easier we load the resolvers OrmResolver and MapResolver. +The MapResolver resolves ServerRequest request object to check access permission using Superuser and Rbac policies. + +If the configuration is not enough for your project you may create a custom loader extending the +default provided. + +- Create file src/Loader/AppAuthorizationServiceLoader.php + +```php + \App\Loader\AppAuthorizationServiceLoader::class, +``` diff --git a/Docs/Documentation/Configuration.md b/Docs/Documentation/Configuration.md index f52015acb..a7118a99b 100644 --- a/Docs/Documentation/Configuration.md +++ b/Docs/Documentation/Configuration.md @@ -6,12 +6,11 @@ Overriding the default configuration For easier configuration, you can specify an array of config files to override the default plugin keys this way: -config/bootstrap.php +Make sure you loaded the plugin and is using a custom config/users.php file at Application::bootstrap ``` // The following configuration setting must be set before loading the Users plugin +$this->addPlugin(\CakeDC\Users\Plugin::class); Configure::write('Users.config', ['users']); -Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); -Configure::write('Users.Social.login', true); //to enable social login ``` Configuration for social login @@ -28,13 +27,14 @@ $ composer require league/oauth1-client:@stable NOTE: twitter uses league/oauth1-client package -config/bootstrap.php -``` -Configure::write('OAuth.providers.facebook.options.clientId', 'YOUR APP ID'); -Configure::write('OAuth.providers.facebook.options.clientSecret', 'YOUR APP SECRET'); +And update your config/users.php file: -Configure::write('OAuth.providers.twitter.options.clientId', 'YOUR APP ID'); -Configure::write('OAuth.providers.twitter.options.clientSecret', 'YOUR APP SECRET'); +```php +'Users.Social.login' => true, +'OAuth.providers.facebook.options.clientId' => 'YOUR APP ID', +'OAuth.providers.facebook.options.clientSecret' => 'YOUR APP SECRET', +'OAuth.providers.twitter.options.clientId' => 'YOUR APP ID', +'OAuth.providers.twitter.options.clientSecret' => 'YOUR APP SECRET', ``` Or use the config override option when loading the plugin (see above) @@ -44,15 +44,18 @@ Additionally you will see you can configure two more keys for each provider: * linkSocialUri (default: /link-social/**provider**), * callbackLinkSocialUri(default: /callback-link-social/**provider**) -Those keys are needed to link an existing user account to a third-party account. **Remember to add the callback to your thrid-party app** +Those keys are needed to link an existing user account to a third-party account. **Remember to add the callback to your thrid-party app** Configuration for reCaptcha --------------------- -``` -Configure::write('Users.reCaptcha.key', 'YOUR RECAPTCHA KEY'); -Configure::write('Users.reCaptcha.secret', 'YOUR RECAPTCHA SECRET'); -Configure::write('Users.reCaptcha.registration', true); //enable on registration -Configure::write('Users.reCaptcha.login', true); //enable on login +To enable reCaptcha you need to register your site at google reCaptcha console +and add this to your config/users.php file: + +```php +'Users.reCaptcha.key' => 'YOUR RECAPTCHA KEY', +'Users.reCaptcha.secret' => 'YOUR RECAPTCHA SECRET', +'Users.reCaptcha.registration' => true, //enable on registration +'Users.reCaptcha.login' => true, //enable on login ``` @@ -62,16 +65,22 @@ Configuration options The plugin is configured via the Configure class. Check the `vendor/cakedc/users/config/users.php` for a complete list of all the configuration keys. -Loading the UsersAuthComponent and using the right configuration values will setup the Users plugin, -the AuthComponent and the OAuth component for your application. +Loading the plugin and using the right configuration values will setup the Users plugin, +with authentication service, authorization service, and the OAuth components for your application. + +This plugin uses by default the new [cakephp/authentication](https://github.com/cakephp/authentication) +and [cakephp/authorization](https://github.com/cakephp/authorization) plugins we suggest you to take a look +into their documentation for more information. + +Most authentication/authorization configuration is defined at 'Auth' key, for example +if you don't want the plugin to autoload the authorization service, you could add this +to your config/users.php file: -If you prefer to setup AuthComponent by yourself, you'll need to load AuthComponent before UsersAuthComponent -and set ``` -Configure::write('Users.auth', false); +'Auth.Authorization.enable' => false, ``` -Interesting UsersAuthComponent options and defaults +Interesting Users options and defaults NOTE: SOME keys were hidden in this doc page, please refer to `vendor/cakedc/users/config/users.php` for the complete list @@ -81,8 +90,6 @@ NOTE: SOME keys were hidden in this doc page, please refer to `vendor/cakedc/use 'table' => 'CakeDC/Users.Users', // Controller used to manage users plugin features & actions 'controller' => 'CakeDC/Users.Users', - // configure Auth component - 'auth' => true, 'Email' => [ // determines if the user should include email 'required' => true, @@ -98,6 +105,8 @@ NOTE: SOME keys were hidden in this doc page, please refer to `vendor/cakedc/use 'ensureActive' => false, // default role name used in registration 'defaultRole' => 'user', + // show verbose error to users + 'showVerboseError' => false, ], 'Tos' => [ // determines if the user should include tos accepted @@ -113,54 +122,70 @@ NOTE: SOME keys were hidden in this doc page, please refer to `vendor/cakedc/use // configure Remember Me component 'active' => true, ], + 'Superuser' => ['allowedToChangePasswords' => false], // able to reset any users password ], -// default configuration used to auto-load the Auth Component, override to change the way Auth works + //Default authentication/authorization setup 'Auth' => [ - 'authenticate' => [ - 'all' => [ - 'finder' => 'active', - ], - 'CakeDC/Auth.RememberMe', - 'Form', + 'Authentication' => [ + 'serviceLoader' => \CakeDC\Users\Loader\AuthenticationServiceLoader::class ], - 'authorize' => [ - 'CakeDC/Auth.Superuser', - 'CakeDC/Auth.SimpleRbac', + 'AuthenticationComponent' => [...], + 'Authenticators' => [...], + 'Identifiers' => [...], + "Authorization" => [ + 'enable' => true, + 'serviceLoader' => \CakeDC\Users\Loader\AuthorizationServiceLoader::class ], + 'AuthorizationMiddleware' => [...], + 'AuthorizationComponent' => [...], + 'SocialLoginFailure' => [...], + 'FormLoginFailure' => [...], + 'RbacPolicy' => [ + 'adapter' => [ + 'role_field' => 'group_name', + ... + ] + ] ], + 'SocialAuthMiddleware' => [...], + 'OAuth' => [...] ]; ``` -Default Authenticate and Authorize Objects used ------------------------- +Authentication and Authorization +-------------------------------- -Using the UsersAuthComponent default initialization, the component will load the following objects into AuthComponent: -* Authenticate - * 'Form' - * 'CakeDC/Users.Social' check [SocialAuthenticate](SocialAuthenticate.md) for configuration options - * 'CakeDC/Auth.RememberMe' check [RememberMeAuthenticate](https://github.com/CakeDC/auth/blob/master/src/RememberMeAuthenticate.php) for configuration options - * 'CakeDC/Auth.ApiKey' check [ApiKeyAuthenticate](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/ApiKeyAuthenticate.md) for configuration options -* Authorize - * 'CakeDC/Auth.Superuser' check [SuperuserAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SuperuserAuthorize.md) for configuration options - * 'CakeDC/Auth.SimpleRbac' check [SimpleRbacAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SimpleRbacAuthorize.md) for configuration options +This plugin uses the two new plugins cakephp/authentication and cakephp/authorization instead of +CakePHP Authentication component, but don't worry, the default configuration should be enough for your +projects. We tried to allow you to start quickly without the need to configure a lot of thing and also +allow you to configure as much as possible. + +To learn more about it please check the configurations for [Authentication](Authentication.md) and [Authorization](Authorization.md) ## Using the user's email to login -You need to configure 2 things: -* Change the Auth.authenticate.Form.fields configuration to let AuthComponent use the email instead of the username for user identify. Add this line to your bootstrap.php file, after CakeDC/Users Plugin is loaded +You need to configure 2 things (version 9.0.4): + +* Change the Password identifier fields and the Authenticator for Forms +configuration to let it use the email instead of the username for +user identify. Add this to your config/users.php: ```php -Configure::write('Auth.authenticate.Form.fields.username', 'email'); +'Auth.Identifiers.Password.fields.username' => 'email', +'Auth.Authenticators.Form.fields.username' => 'email', +'Auth.Authenticators.Cookie.fields.username' => 'email', ``` -* Override the login.ctp template to change the Form->control to "email". Add (or copy from the https://github.com/CakeDC/users/blob/master/src/Template/Users/login.ctp) the file login.ctp to path /src/Template/Plugin/CakeDC/Users/Users/login.ctp and ensure it has the following content +* Override the login.php template to change the Form->control to "email". +Add (or copy from the [/templates/Users/login.php](../../templates/Users/login.php)) the file login.php to path /templates/plugin/CakeDC/Users/Users/login.php +and ensure it has the following content ```php // ... inside the Form Form->control('email', ['required' => true]) ?> Form->control('password', ['required' => true]) ?> - // ... rest of your login.ctp code + // ... rest of your login.php code ``` @@ -172,24 +197,58 @@ Email Templates To modify the templates as needed copy them to your application ``` -cp -r vendor/cakedc/users/src/Template/Email/* src/Template/Plugin/CakeDC/Users/Email/ +cp -r vendor/cakedc/users/templates/email/ templates/plugin/CakeDC/Users/email/ ``` -Then customize the email templates as you need under the src/Template/Plugin/CakeDC/Users/Email/ directory +Then customize the email templates as you need under the templates/Plugin/CakeDC/Users/email/ directory Plugin Templates --------------- Similar to Email Templates customization, follow the CakePHP conventions to put your new templates under -src/Template/Plugin/CakeDC/Users/[Controller]/[view].ctp +templates/plugin/CakeDC/Users/[Controller]/[view].php -Check http://book.cakephp.org/3.0/en/plugins.html#overriding-plugin-templates-from-inside-your-application +Check https://book.cakephp.org/4/en/plugins.html#overriding-plugin-templates-from-inside-your-application Flash Messages --------------- To modify the flash messages, use the standard PO file provided by the plugin and customize the messages -Check http://book.cakephp.org/3.0/en/core-libraries/internationalization-and-localization.html#setting-up-translations +Check https://book.cakephp.org/4/en/core-libraries/internationalization-and-localization.html#setting-up-translations for more details about how the PO files should be managed in your application. We've included an updated POT file with all the `Users` domain keys for your customization. + +Password Hasher customization +----------------------------- + +Override the `Auth.Identifiers.Password` key in configuration adding a `passwordHasher` key https://book.cakephp.org/authentication/2/en/password-hashers.html#upgrading-hashing-algorithms + +For example: + +```php + 'Auth.Identifiers' => [ + 'Password' => [ + 'className' => 'Authentication.Password', + 'fields' => [ + 'username' => ['username', 'email'], + 'password' => 'password', + ], + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'finder' => 'active', + ], + 'passwordHasher' => [ + 'className' => 'Authentication.Fallback', + 'hashers' => [ + 'Authentication.Default', + [ + 'className' => 'Authentication.Legacy', + 'hashType' => 'md5', + 'salt' => false, // turn off default usage of salt + ], + ], + ], + ], + ], +``` diff --git a/Docs/Documentation/Events.md b/Docs/Documentation/Events.md index 13e810165..61fcee79d 100644 --- a/Docs/Documentation/Events.md +++ b/Docs/Documentation/Events.md @@ -1,55 +1,568 @@ Events ====== -The events in this plugin follow these conventions ...: +The events in this plugin follow these conventions `.`: -* 'Users.Component.UsersAuth.isAuthorized' -* 'Users.Component.UsersAuth.beforeLogin' -* 'Users.Component.UsersAuth.afterLogin' -* 'Users.Component.UsersAuth.afterCookieLogin' -* 'Users.Component.UsersAuth.beforeRegister' -* 'Users.Component.UsersAuth.afterRegister' -* 'Users.Component.UsersAuth.beforeLogout' -* 'Users.Component.UsersAuth.afterLogout' +* `Users.Authentication.afterLogin` +* `Users.Authentication.beforeLogout` +* `Users.Authentication.afterLogout` +* `Users.Global.beforeRegister` +* `Users.Global.afterRegister` +* `Users.Global.beforeSocialLoginUserCreate` +* `Users.Global.afterResetPassword` +* `Users.Global.onExpiredToken` +* `Users.Global.afterResendTokenValidation` The events allow you to inject data into the plugin on the before* plugins and use the data for your -own business login in the after* events, for example +own business. + +I want to add custom logic before user logout +--------------------------------------------- +When adding a custom logic to execute before user logout you +have access to user data and the controller object. The main logout +logic will be performed if you don't assign an array to result, but if +you set it we will use as redirect url. + +- Create or update file src/Event/UsersListener.php: +```php + 'beforeLogout', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function beforeLogout(\Cake\Event\Event $event) + { + $user = $event->getData('user'); + $controller = $event->getSubject(); + + //your custom logic + Cache::delete('dashboard_data_user_' . $user['id']); + $controller->Flash->succes(__('Some message if you want')); + + //If you want to ignore the logout logic you can, just set an url array as result to use as redirect + //$event->setResult(['plugin' => false, 'controller' => 'Page', 'action' => 'seeYouSoon']); + } +} +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic before user register +--------------------------------------------- +When adding a custom logic to execute before user register you +have access to 'usersTable', 'userEntity' and 'options' keys in the event +object and the controller object. +You can also populate the new user entity or stop the register process. + +- Create or update file src/Event/UsersListener.php: +```php + 'beforeRegister', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function beforeRegister(\Cake\Event\Event $event) + { + $controller = $event->getSubject(); + $user = $event->getData('userEntity'); + $table = $event->getData('usersTable'); + $options = $event->getData('options'); + + //Or custom logic + $controller->Flash->succes(__('Some message if you want')); + + //When you set an entity as result a part of register logic is skipped (ex: reCaptcha) + $newUser = $table->newEntity([ + 'username' => 'forceEventRegister', + 'email' => 'eventregister@example.com', + 'password' => 'password', + 'active' => true, + 'tos' => true, + ]); + // + $event->setResult($newUser); + //If you want to stop registration use + //$event->stopPropagation(); + //$event->setResult(['plugin' => false, 'controller' => 'Somewhere', 'action' => 'toRedirect']); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic before linking social account +-------------------------------------------------------- +When adding a custom logic to execute before linking social account you +have access to 'location' and 'request' keys in the event object and +the controller object. + +- Create or update file src/Event/UsersListener.php: +```php + 'beforeSocialLoginRedirect', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function beforeSocialLoginRedirect(\Cake\Event\Event $event) + { + $controller = $event->getSubject(); + $location = $event->getData('location'); + $request = $event->getData('request'); + + //your custom logic + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic before creating social account +--------------------------------------------------------- +When adding a custom logic to execute before creating social account you +have access to 'userEntity' and 'data' keys in the event object and +the social behavior object. + +You can also set a new user entity object as result. + +- Create or update file src/Event/UsersListener.php: +```php + 'beforeSocialLoginRedirect', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function beforeSocialLoginRedirect(\Cake\Event\Event $event) + { + $userEntity = $event->getData('userEntity'); + $socialData = $event->getData('data'); + + //your custom logic + + //If you want to use another entity use this + //$event->setResult($anotherUserEntity); + } +} ``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user login +------------------------------------------- +When adding a custom logic to execute after user login you +have access to user data. You can also set an array as result to +perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php + 'afterLogin', + ]; + } + /** - * Forced login using a beforeLogin event + * @param \Cake\Event\Event $event */ - public function eventLogin() + public function afterLogin(\Cake\Event\Event $event) { - $this->eventManager()->on(UsersAuthComponent::EVENT_BEFORE_LOGIN, function () { - //the callback function should return the user data array to force login - return [ - 'id' => 1337, - 'username' => 'forceLogin', - 'email' => 'event@example.com', - 'active' => true, - ]; - }); - $this->login(); - $this->render('login'); + $user = $event->getData('user'); + //your custom logic + //$this->loadModel('SomeOptionalUserLogs')->newLogin($user); + + //If you want to use a custom redirect + $event->setResult([ + 'plugin' => false, + 'controller' => 'Dashboard', + 'action' => 'home', + ]); } +} +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user logout +--------------------------------------------- +When adding a custom logic to execute after user logout you +have access to user data and the controller object. You can also +set an array as result to perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php +eventManager()->on(UsersAuthComponent::EVENT_BEFORE_REGISTER, function ($event) { - //the callback function should return the user data array to force register - return $event->data['usersTable']->newEntity([ - 'username' => 'forceEventRegister', - 'email' => 'eventregister@example.com', - 'password' => 'password', - 'active' => true, - 'tos' => true, - ]); - }); - $this->register(); - $this->render('register'); + return [ + \CakeDC\Users\Plugin::EVENT_AFTER_LOGOUT => 'afterLogout', + ]; } -``` \ No newline at end of file + + /** + * @param \Cake\Event\Event $event + */ + public function afterLogout(\Cake\Event\Event $event) + { + $user = $event->getData('user'); + $controller = $event->getSubject(); + //your custom logic + + //If you want to use a custom redirect + $event->setResult([ + 'plugin' => false, + 'controller' => 'Pages', + 'action' => 'thankYou', + ]); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user register +---------------------------------------------- +When adding a custom logic to execute after user register you +have access to user data and the controller object. You can also +set a custom http response as result to render a different content +or perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php + 'afterRegister', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterRegister(\Cake\Event\Event $event) + { + $user = $event->getData('user'); + $controller = $event->getSubject(); + //your custom logic + + //If you want to use a custom response to render a json. + $response = $controller->getResponse()->withStringBody(json_encode(['success' => true, 'id' => $user['id']])); + $event->setResult($response); + + //or if you want to use a custom redirect. + $response = $controller->getResponse()->withLocation("/some/page"); + $event->setResult($response); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user changed the password +---------------------------------------------------------- +When adding a custom logic to execute after user change the password +you have access to some user data and the controller object. You can also +set an array as result to perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php + 'afterChangePassword', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterChangePassword(\Cake\Event\Event $event) + { + $user = $event->getData('user'); + $controller = $event->getSubject(); + //your custom logic + + //If you want to use a custom redirect + $event->setResult([ + 'plugin' => false, + 'controller' => 'Pages', + 'action' => 'infoPassword', + ]); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + + +I want to add custom logic after sending the token for user validation +---------------------------------------------------------------------- +When adding a custom logic to execute after sending the token for user +validation you can also set an array as result to perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php + 'afterResendTokenValidation', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterResendTokenValidation(\Cake\Event\Event $event) + { + $controller = $event->getSubject(); + //your custom logic + + //If you want to use a custom redirect + $event->setResult([ + 'plugin' => false, + 'controller' => 'Pages', + 'action' => 'infoValidation', + ]); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user email is validated +-------------------------------------------------------- +When adding a custom logic to execute after user email is validate +you have access to some user data and the controller object. You can also +set an array as result to perform a custom redirect. + +- Create or update file src/Event/UsersListener.php: +```php + 'afterEmailTokenValidation', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterEmailTokenValidation(\Cake\Event\Event $event) + { + $user = $event->getData('user'); + $controller = $event->getSubject(); + //your custom logic + + //If you want to use a custom redirect + $event->setResult([ + 'plugin' => false, + 'controller' => 'Pages', + 'action' => 'infoPassword', + ]); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` + +I want to add custom logic after user email is validated to autologin user +-------------------------------------------------------------------------- +This is how you can autologin the user after email is validate: + +- Create or update file src/Event/UsersListener.php: +```php + 'afterEmailTokenValidation', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterEmailTokenValidation(\Cake\Event\Event $event) + { + $table = $this->loadModel('Users'); + $userData = $event->getData('user'); + $user = $table->get($userData['id']); + $this->Authentication->setIdentity($user); + } +} + +``` +- Add this at the end of your method Application::bootstrap if you have NOT done before. +```php +$this->getEventManager()->on(new \App\Event\UsersListener()); +``` diff --git a/Docs/Documentation/Extending-the-Plugin.md b/Docs/Documentation/Extending-the-Plugin.md index fec58928a..4f8cfe77e 100644 --- a/Docs/Documentation/Extending-the-Plugin.md +++ b/Docs/Documentation/Extending-the-Plugin.md @@ -7,7 +7,7 @@ Extending the Model (Table/Entity) Create a new Table and Entity in your app, matching the table you want to use for storing the users data. Check the initial users migration to know the default columns expected in the table. If your column names doesn't match the columns in your current table, you could use the Entity to -match the colums using accessors & mutators as described here http://book.cakephp.org/3.0/en/orm/entities.html#accessors-mutators +match the colums using accessors & mutators as described here https://book.cakephp.org/4/en/orm/entities.html#accessors-mutators Example: we are going to use a custom table ```my_users``` in our application , which has a field named ``is_active`` instead of the default ``active``. * Create a new Table under src/Model/Table/MyUsersTable.php @@ -116,7 +116,6 @@ namespace App\Controller; use App\Controller\AppController; use App\Model\Table\MyUsersTable; use Cake\Event\Event; -use CakeDC\Users\Controller\Component\UsersAuthComponent; use CakeDC\Users\Controller\Traits\LoginTrait; use CakeDC\Users\Controller\Traits\RegisterTrait; @@ -125,11 +124,29 @@ class MyUsersController extends AppController use LoginTrait; use RegisterTrait; -//add your new actions, override, etc here + /** + * Initialize + * + * @return void + */ + public function initialize() + { + parent::initialize(); + $this->loadComponent('CakeDC/Users.Setup'); + if ($this->components()->has('Security')) { + $this->Security->setConfig( + 'unlockedActions', + ['login', 'u2fRegister', 'u2fRegisterFinish', 'u2fAuthenticate', 'u2fAuthenticateFinish'] + ); + } + } + + //add your new actions, override, etc here } ``` -Don't forget to update the `Users.controller` configuration in `users.php` +Don't forget to update the `Users.controller` configuration in `users.php` this is +needed to setup correct url/route for authentication. ```php 'Users' => [ @@ -139,7 +156,25 @@ Don't forget to update the `Users.controller` configuration in `users.php` // ... ``` -Note you'll need to **copy the Plugin templates** you need into your project src/Template/MyUsers/[action].ctp +**You also need to update permissions rules in your file config/permissions.php +to match the new controller.** + +Note you'll need to **copy the Plugin templates** you need into your project templates/MyUsers/[action].php + +You may also need to load some helpers in your AppView: + +```php + /** + * Initialization hook method. + * + * @return void + */ + public function initialize() + { + $this->loadHelper('CakeDC/Users.AuthLink'); + $this->loadHelper('CakeDC/Users.User'); + } +``` Extending the Features in your controller ----------------------------- @@ -159,18 +194,13 @@ use Cake\Http\Exception\NotFoundException; trait ImpersonateTrait { /** - * Adding a new feature as an example: Impersonate another user + * Adding a new feature as an example: Review user * - * @param type $userId + * @param string $userId */ - public function impersonate($userId) + public function review($userId) { - $user = $this->getUsersTable()->find() - ->where(['id' => $userId]) - ->hydrate(false) - ->first(); - $this->Auth->setUser($user); - return $this->redirect('/'); + //Your review logic } } ``` @@ -178,14 +208,14 @@ Updating the Templates ------------------- Use the standard CakePHP conventions to override Plugin views using your application views -http://book.cakephp.org/3.0/en/plugins.html#overriding-plugin-templates-from-inside-your-application +https://book.cakephp.org/4/en/plugins.html#overriding-plugin-templates-from-inside-your-application -`{project_dir}/src/Template/Plugin/CakeDC/Users/Users/{templates_in_here}` +`{project_dir}/templates/plugin/CakeDC/Users/Users/{templates_in_here}` Updating the Emails ------------------- -Extend the `\CakeDC\Users\Mailer\UsersMailer` class and override the email configuration to change the way the +Extend the `\CakeDC\Users\Mailer\UsersMailer` class and override the email configuration to change the way the emails are sent by the Plugin. We currently have: * validation, sent with a link to validate new users registered * resetPassword, sent with a link to access the reset password feature @@ -209,10 +239,13 @@ class MyUsersMailer extends UsersMailer } } ``` -* Configure the Plugin to use this new mailer class in bootstrap or users.php -`Configure::write('Users.Email.mailerClass', \App\Mailer\MyUsersMailer::class);` +* Configure the plugin to use this new mailer class, add this in your config/users.php file: + +```php + 'Users.Email.mailerClass' => \App\Mailer\MyUsersMailer::class, +``` -* Create the file `src/Template/Email/text/custom_template_in_app_namespace.ctp` +* Create the file `templates/email/text/custom_template_in_app_namespace.php` with your custom contents. Note you can also prepare an html version of the file, change the template, or do any other customization in the `MyUsersMailer` method. diff --git a/Docs/Documentation/Google-Two-Factor-Authenticator.md b/Docs/Documentation/Google-Two-Factor-Authenticator.md deleted file mode 100644 index 99245ce76..000000000 --- a/Docs/Documentation/Google-Two-Factor-Authenticator.md +++ /dev/null @@ -1,36 +0,0 @@ -Google Two Factor Authenticator -=============================== - -Installation ------------- -To enable this feature you need to - -``` -composer require robthree/twofactorauth -``` - -Setup ------ - -Enable google authenticator in your bootstrap.php file: - -Config/bootstrap.php -``` -Configure::write('Users.GoogleAuthenticator.login', true); -``` - -How does it work ----------------- -When the user log-in, he is requested (image 1) to inform the current validation -code for your site in Google Authentation app (image 2), if this is the first -time he access he need to add your site to Google Authentation by reading -the QR code shown (image 1). - -1) Validation code page - - - -2) Google Authentation app - - - diff --git a/Docs/Documentation/Installation.md b/Docs/Documentation/Installation.md index 18b244230..f9d8d8e8d 100644 --- a/Docs/Documentation/Installation.md +++ b/Docs/Documentation/Installation.md @@ -2,7 +2,7 @@ Installation ============ Composer ------- +-------- ``` composer require cakedc/users @@ -18,13 +18,9 @@ composer require league/oauth2-linkedin:@stable composer require league/oauth1-client:@stable ``` -NOTE: you'll need to enable social login in your bootstrap.php file if you want to use it, social +NOTE: you'll need to enable social login if you want to use it, social login is disabled by default. Check the [Configuration](Configuration.md#configuration-for-social-login) page for more details. -``` -Configure::write('Users.Social.login', true); //to enable social login -``` - If you want to use reCaptcha features... ``` @@ -40,12 +36,61 @@ If you want to use Google Authenticator features... composer require robthree/twofactorauth:"^1.5.2" ``` -NOTE: you'll need to enable `Users.GoogleAuthenticator.login` +NOTE: you'll need to enable `OneTimePasswordAuthenticator.login` in your config/users.php file: +```php +'OneTimePasswordAuthenticator.login' => true, ``` -Configure::write('Users.GoogleAuthenticator.login', true); + +Load the Plugin +--------------- + +Ensure the Users Plugin is loaded in your src/Application.php file + +``` + /** + * {@inheritdoc} + */ + public function bootstrap() + { + parent::bootstrap(); + + $this->addPlugin(\CakeDC\Users\Plugin::class); + // Uncomment the line below to load your custom users.php config file + //Configure::write('Users.config', ['users']); + } ``` +**Important note: The plugin loads authentication and authorization plugin and +uses RequestAuthorizationMiddleware with Rbac|Superuser policy you +should not load then manually** + +Creating config files +--------------------- +You need to create the file config/users.php to configure the plugin. This documentation +assumes that you will create this file. + +Example config/users.php + +```php + true, +]; +``` + +***The plugin loads authentication and authorization plugins by default, +to be able to access your pages you NEED to have defined rules at the +file config/permissions.php. +You can copy the one from the plugin and add your permissions rules.*** + +```shell +cd {project_dir} +cp vendor/cakedc/users/config/permissions.php config/permissions.php +``` +[Go to permission documentation for more information.](./Permissions.md) + + Creating Required Tables ------------------------ If you want to use the Users tables to store your users and social accounts: @@ -58,28 +103,26 @@ Note you don't need to use the provided tables, you could customize the table na application and then use the plugin configuration to use your own tables instead. Please refer to the [Extending the Plugin](Extending-the-Plugin.md) section to check all the customization options -Load the Plugin ------------ - -Ensure the Users Plugin is loaded in your config/bootstrap.php file +You can create the first user, the super user by issuing the following command ``` -Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); +bin/cake users addSuperuser ``` Customization ----------- +------------- -config/bootstrap.php +First, make sure to set the config `Users.config` at Application::bootstrap ``` +$this->addPlugin(\CakeDC\Users\Plugin::class); Configure::write('Users.config', ['users']); -Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); -Configure::write('Users.Social.login', true); //to enable social login ``` -If you want to use social login, then in your config/users.php -``` +And update your config/users.php file, for example if you want to use social login: +```php + true, 'OAuth.providers.facebook.options.clientId' => 'YOUR APP ID', 'OAuth.providers.facebook.options.clientSecret' => 'YOUR APP SECRET', 'OAuth.providers.twitter.options.clientId' => 'YOUR APP ID', @@ -87,7 +130,7 @@ return [ //etc ]; ``` -IMPORTANT: Remember you'll need to configure your social login application **callback url** to use the provider specific endpoint, for example: +IMPORTANT: Remember you'll need to configure your social login application **callback url** to use the provider specific endpoint, for example: * Facebook App Callback URL --> `http://yourdomain.com/auth/facebook` * Twitter App Callback URL --> `http://yourdomain.com/auth/twitter` * Google App Callback URL --> `http://yourdomain.com/auth/google` @@ -96,20 +139,3 @@ IMPORTANT: Remember you'll need to configure your social login application **cal Note: using social authentication is not required. For more details, check the Configuration doc page - -Load the UsersAuth Component ---------------------- - -Load the Component in your src/Controller/AppController.php, and use the passed Component configuration to customize the Users Plugin: - -``` - public function initialize() - { - parent::initialize(); - - // Important: add the 'enableBeforeRedirect' config or or disable deprecation warnings - $this->loadComponent('RequestHandler', ['enableBeforeRedirect' => false]); - $this->loadComponent('Flash'); - $this->loadComponent('CakeDC/Users.UsersAuth'); - } -``` diff --git a/Docs/Documentation/InterceptLoginAction.md b/Docs/Documentation/InterceptLoginAction.md new file mode 100644 index 000000000..2e27b868d --- /dev/null +++ b/Docs/Documentation/InterceptLoginAction.md @@ -0,0 +1,48 @@ +Intercept Login Action +====================== + +There is a moment when you may want to intercept the login action to perform +some specific login, like redirect user to another url or set user data. +A simple way to intercept the login action is by creating a custom middleware, the following +example shows how to set user data and redirect to anothe url. + +```php +checkActionOnRequest('login', $request)) { + return $next($request, $response); + } + + if (!$request->getAttribute('session')->read('Auth')) { + //do some logic + //do more logic + $request->getAttribute('session')->write('Auth', $userIdentity); + + $response = $response->withHeader('Location', '/pages/33'); + + return $response; + } + + return $next($request, $response); + } + +} +``` + diff --git a/Docs/Documentation/Migration/8.x-9.0.md b/Docs/Documentation/Migration/8.x-9.0.md new file mode 100644 index 000000000..c375d89cd --- /dev/null +++ b/Docs/Documentation/Migration/8.x-9.0.md @@ -0,0 +1,131 @@ +Migration 8.x to 9.0 +====================== + +The 9.0 version uses the new plugins cakephp/authentication[(read more)](../Authentication.md) and cakephp/authorization[(read more)](../Authorization.md) instead of CakePHP +Authentication component. This version is compatible with CakePHP 4.x and the plugin +code was updated to remove all deprecations. + +Configuration file users.php +---------------------------- +We have added/removed/changed some configurations to work with new authentication/authorization +plugins, if you created a custom config file, please compare your file with the default +config/users.php from this plugin. + +* Users.middlewareQueueLoader was added; + +* Users.auth was removed since AuthComponent is not used; + +* Users.Social.authenticator was removed in favor of Auth.Authenticators.Social and Auth.Identifiers.Social; + +* Users.GoogleAuthenticator was renamed to OneTimePasswordAuthenticator; + +* GoogleAuthenticator was renamed to OneTimePasswordAuthenticator; + +* Auth.authenticate was removed in favor of Auth.Authentication, Auth.AuthenticationComponent, +Auth.Authenticators, Auth.Identifiers + +* Auth.authorize was removed in favor of Auth.Authorization, Auth.AuthorizationMiddleware, +Auth.AuthorizationComponent + +* Added Auth.SocialLoginFailure to handle social login + +* Added Auth.FormLoginFailure to handle form login + +* CakeDC/Auth was upgraded and now has a better way to handle social login. +Oauth providers config like OAuth.providers.facebook requires two new config keys, 'service' and 'mapper'. + +* Users.controller config now is also used in mapping users routes (/login, register). +You need to give permissions to your new controller actions. + +* Users controller rely on CakeDC/Users.Setup Component, when using a custom users controller make sure to load it. +```php + + /** + * Initialize + * + * @return void + */ + public function initialize() + { + parent::initialize(); + $this->loadComponent('CakeDC/Users.Setup'); + if ($this->components()->has('Security')) { + $this->Security->setConfig( + 'unlockedActions', + ['login', 'u2fRegister', 'u2fRegisterFinish', 'u2fAuthenticate', 'u2fAuthenticateFinish'] + ); + } + } +``` + +Loading the Plugin +------------------ +In this version you need to load the plugin in your [Application class](https://github.com/cakephp/app/blob/master/src/Application.php). +``` + /** + * {@inheritDoc} + */ + public function bootstrap() + { + // Call parent to load bootstrap from files. + parent::bootstrap(); + + //your code + + $this->addPlugin(\CakeDC\Users\Plugin::class); + } +``` + +Renamed Events +-------------- +All events constants were moved from AuthComponent to Plugin class and +their values was also updated. + +* `Users.Component.UsersAuth.afterLogin` renamed to `Users.Authentication.afterLogin` +* `Users.Component.UsersAuth.beforeLogout` renamed to `Users.Authentication.beforeLogout` +* `Users.Component.UsersAuth.afterLogout` renamed to `Users.Authentication.afterLogout` +* `Users.Component.UsersAuth.beforeRegister` renamed to `Users.Global.beforeRegister` +* `Users.Component.UsersAuth.afterRegister` renamed to `Users.Global.afterRegister` +* `Users.Component.UsersAuth.afterResetPassword` renamed to `Users.Global.afterResetPassword` +* `Users.Component.UsersAuth.beforeSocialLoginUserCreate` renamed to `Users.Global.beforeSocialLoginUserCreate` +* `Users.Component.UsersAuth.onExpiredToken` renamed to `Users.Global.onExpiredToken` +* `Users.Component.UsersAuth.afterResendTokenValidation` renamed to `Users.Global.afterResendTokenValidation` + +Removed Events +-------------- +* `Users.Component.UsersAuth.isAuthorized` use cakephp\authorization components or \CakeDC\Auth\Traits\IsAuthorizedTrait instead. +* `Users.Component.UsersAuth.beforeLogin` implement a middleware before authetication middleware. +* `Users.Component.UsersAuth.failedSocialLogin` +* `Users.Component.UsersAuth.afterCookieLogin` + +Getting Authenticated User +-------------------------- + +In previous version you usually used AuthComponent to get the authenticated +user, now you should use the provided 'identity' attribute, you can do this +in your controller. + +``` +$user = $this->getRequest()->getAttribute('identity'); +if ($user) { + //Do stuff +} +``` + +Settting user data in session for unit tests +-------------------------------------------- + +Authentication process read and store user data at session key 'Auth' not 'Auth.User' and the +value should be an User object. + +In your integration test class replace this: + +``` +$this->session(['Auth.User' => $userData]); +``` + +with + +``` +$this->session(['Auth' => new \CakeDC\Users\Model\Entity\User($userData)]); +``` \ No newline at end of file diff --git a/Docs/Documentation/GoogleAuthenticator/App.png b/Docs/Documentation/OneTimePasswordAuthenticator/App.png similarity index 100% rename from Docs/Documentation/GoogleAuthenticator/App.png rename to Docs/Documentation/OneTimePasswordAuthenticator/App.png diff --git a/Docs/Documentation/GoogleAuthenticator/FirstLogin.png b/Docs/Documentation/OneTimePasswordAuthenticator/FirstLogin.png similarity index 100% rename from Docs/Documentation/GoogleAuthenticator/FirstLogin.png rename to Docs/Documentation/OneTimePasswordAuthenticator/FirstLogin.png diff --git a/Docs/Documentation/Permissions.md b/Docs/Documentation/Permissions.md new file mode 100644 index 000000000..d2b55f20f --- /dev/null +++ b/Docs/Documentation/Permissions.md @@ -0,0 +1,183 @@ +Permissions +=========== +The plugin is setup to perform permissions check for all requests using +Superuser and Rbac policies. + +Superuser policy allow the superuser to access any page. + +The Rbac policy allows you to define a list of rules at config/permissions.php +to perform checks based on request information (prefix, plugin, controller, action, etc) +and user data. + +You can find the permission rule syntax at [CakeDC/auth documentation.](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/Rbac.md#permission-rules-syntax) + +I want to allow access to public actions (non-logged user) +---------------------------------------------------------- +To allow access to public actions (that does not requires a looged) we need to include a new rule at config/permissions.php +using the 'bypassAuth' key. + +```php + [ + //...... all other rules + [ + 'controller' => 'Pages', + 'action' => ['home', 'contact', 'projects'] + 'bypassAuth' => true, + ], + ], +]; +``` + +I want to allow access to one specific action +--------------------------------------------- +To allow access to specific action we need to include a new rule at config/permissions.php + +- Path: /{controler}/{action} +```php + [ + //...... all other rules + [ + //Allow user, manager and author roles to access /books + 'role' => ['user', 'manager', 'author'], + 'controller' => 'Books', + 'action' => 'index', + ], + [ + //Allow user to access /dashboard/home + 'role' => 'user', + 'controller' => 'Dashbord', + 'action' => 'home', + ], + [ + //Allow user to access /articles, /articles/add and /article/edit + 'role' => ['manager'], + 'controller' => 'Articles', + 'action' => ['index', 'add', 'edit'], + ], + ], +]; +``` + +- Path: /{plugin}/{prefix}/{controler}/{action} +```php + [ + //...... all other rules + [ + //Allow user to access /reports/admin/categories + 'plugin' => 'Reports', + 'prefix' => 'Admin', + 'role' => ['manager'], + 'controller' => 'Categories', + 'action' => ['index'], + ], + ], +]; +``` + +I want to allow access to all actions from one controller +--------------------------------------------------------- +To allow access to specific all actions from one controller we need to include a new rule at config/permissions.php +using the value '*' for 'action' key. + +```php + [ + //...... all other rules + [ + //Allow user, manager and author roles to access any action from books controller + 'role' => ['user', 'manager', 'author'], + 'controller' => 'Books', + 'action' => '*', + ], + ] +]; +``` + +I want to allow access to all controllers from one prefix +--------------------------------------------- +To allow access to specific to all pages from one prefix we need to include a new rule at config/permissions.php +using the value '*' for 'plugin', 'controller' and 'action' keys. + +```php + [ + //...... all other rules + [ + //Allow user, manager and author roles to access any action from books controller + 'role' => ['user', 'manager', 'author'], + 'plugin' => '*', + 'prefix' => 'Admin', + 'controller' => '*', + 'action' => '*', + ], + ], +]; +``` + +I want to allow access to entity owned by the user +-------------------------------------------------- +To allow access to entity owned by the user we need to include a new rule +at config/permissions.php using the 'allowed' key. + +```php + [ + //...... all other rules + [ + // + 'role' => 'user', + 'controller' => 'Articles', + 'action' => ['edit'] + 'allowed' => new \CakeDC\Auth\Rbac\Rules\Owner([ + 'table' => 'Articles', + 'id' => 'id', + 'ownerForeignKey' => 'owner_id' + ]), + ], + ], +]; +``` + +[For more information check owner rule documentation](https://github.com/CakeDC/auth/blob/6.next-cake4/Docs/Documentation/OwnerRule.md) + + +I want to allow access to action using a custom logic +---------------------------------------------------- +Permission rule can have a custom callback. Adde the rule at config/permissions.php using the 'allowed' key. + +```php + [ + //...... all other rules + [ + // + 'role' => 'user', + 'controller' => 'Posts', + 'action' => ['edit'] + 'allowed' => function (array $user, $role, \Cake\Http\ServerRequest $request) { + $postId = \Cake\Utility\Hash::get($request->params, 'pass.0'); + $post = \Cake\ORM\TableRegistry::get('Posts')->get($postId); + $userId = $user['id']; + if (!empty($post->user_id) && !empty($userId)) { + return $post->user_id === $userId; + } + return false; + } + ], + ], +]; +``` + +[For more information check CakeDC/Auth documentation](https://github.com/CakeDC/auth/blob/6.next-cake4/Docs/Documentation/Rbac.md#permission-callbacks) + + diff --git a/Docs/Documentation/SocialAuthenticate.md b/Docs/Documentation/SocialAuthenticate.md deleted file mode 100644 index 937039366..000000000 --- a/Docs/Documentation/SocialAuthenticate.md +++ /dev/null @@ -1,97 +0,0 @@ -SocialAuthenticate -============= - -We currently support the following providers to perform login as well as to link an existing account: - -* Facebook -* Twitter -* Google -* LinkedIn -* Instagram -* Amazon - -Please [contact us](https://cakedc.com/contact) if you need to support another provider. - -Setup ---------------------- - -Create the Facebook/Twitter applications you want to use and setup the configuration like this: - -Config/bootstrap.php -``` -Configure::write('OAuth.providers.facebook.options.clientId', 'YOUR APP ID'); -Configure::write('OAuth.providers.facebook.options.clientSecret', 'YOUR APP SECRET'); - -Configure::write('OAuth.providers.twitter.options.clientId', 'YOUR APP ID'); -Configure::write('OAuth.providers.twitter.options.clientSecret', 'YOUR APP SECRET'); -``` - -You can also change the default settings for social authenticate: - -``` -Configure::write('Users', [ - 'Email' => [ - //determines if the user should include email - 'required' => true, - //determines if registration workflow includes email validation - 'validate' => true, - ], - 'Social' => [ - //enable social login - 'login' => false, - ], - 'Key' => [ - 'Session' => [ - //session key to store the social auth data - 'social' => 'Users.social', - ], - //form key to store the social auth data - 'Form' => [ - 'social' => 'social' - ], - 'Data' => [ - //data key to store email coming from social networks - 'socialEmail' => 'info.email', - ], - ], -]); -``` - -If email is required and the social network does not return the user email then the user will be required to input the email. Additionally, validation could be enabled, in that case the user will be asked to validate the email before be able to login. There are some cases where the email address already exists onto database, if so, the user will receive an email and will be asked to validate the social account in the app. It is important to take into account that the user account itself will remain active and accessible by other ways (other social network account or username/password). - -In most situations you would not need to change any Oauth setting besides applications details. - -For new facebook aps you must use the graphApiVersion 2.8 or greater: - -``` -Configure::write('OAuth.providers.facebook.options.graphApiVersion', 'v2.8'); -``` - -User Helper ---------------------- - -You can use the helper included with the plugin to create Facebook/Twitter buttons: - -In templates -``` -$this->User->facebookLogin(); - -$this->User->twitterLogin(); -``` - -We recommend the use of [Bootstrap Social](http://lipis.github.io/bootstrap-social/) in order to automatically apply styles to buttons. Anyway you can always add your own style to the buttons. - -Social Authentication was inspired by [UseMuffin/OAuth2](https://github.com/UseMuffin/OAuth2) library. - -Custom username field ---------------------- - -In your customized users table, add the SocialBehavior with the following configuration: - -```php -$this->addBehavior('CakeDC.Users/Social', [ - 'username' => 'email' -]); -``` - -By default it will use `username` field. diff --git a/Docs/Documentation/SocialAuthentication.md b/Docs/Documentation/SocialAuthentication.md new file mode 100644 index 000000000..f74b36e7a --- /dev/null +++ b/Docs/Documentation/SocialAuthentication.md @@ -0,0 +1,171 @@ +Social Authentication +===================== + +We currently support the following providers to perform login as well as to link an existing account: + +* Facebook +* Twitter +* Google +* LinkedIn +* Instagram +* Amazon + +Please [contact us](https://cakedc.com/contact) if you need to support another provider. + +The main source code for social integration is provided by ['CakeDC/auth' plugin](https://github.com/cakedc/auth) + +Setup +----- +By default social login is disabled, to enable you need to create the +Facebook/Twitter applications you want to use and update your file config/users.php with: + +```php +//This enable social login (authentication) +'Users.Social.login' => true, +//This is the required config to setup facebook. +'OAuth.providers.facebook.options.clientId', 'YOUR APP ID'; +'OAuth.providers.facebook.options.clientSecret', 'YOUR APP SECRET'; +//This is the required config to setup twitter +'OAuth.providers.twitter.options.clientId', 'YOUR APP ID'; +'OAuth.providers.twitter.options.clientSecret', 'YOUR APP SECRET'; +``` +Check optional configs at [config/users.php](./../../config/users.php) inside 'OAuth' key + + +You can also change the default settings for social authenticate in your config/users.php file: + +```php + 'Users.Email' => [ + //determines if the user should include email + 'required' => true, + //determines if registration workflow includes email validation + 'validate' => true, + ], + 'Users.Social' => [ + //enable social login + 'login' => false, + ], + 'Users.Key' => [ + 'Session' => [ + //session key to store the social auth data + 'social' => 'Users.social', + ], + //form key to store the social auth data + 'Form' => [ + 'social' => 'social' + ], + 'Data' => [ + //data key to store email coming from social networks + 'socialEmail' => 'info.email', + ], + ], +``` + +If email is required and the social network does not return the user email then the user will be required to input the email. Additionally, validation could be enabled, in that case the user will be asked to validate the email before be able to login. There are some cases where the email address already exists onto database, if so, the user will receive an email and will be asked to validate the social account in the app. It is important to take into account that the user account itself will remain active and accessible by other ways (other social network account or username/password). + +In most situations you would not need to change any Oauth setting besides applications details. + +For new facebook aps you must use the graphApiVersion 2.8 or greater: + +```php +'OAuth.providers.facebook.options.graphApiVersion' => 'v2.8', +``` + +User Helper +----------- + +You can use the helper included with the plugin to create Facebook/Twitter buttons: + +In templates +```php +$this->User->facebookLogin(); + +$this->User->twitterLogin(); +``` + +We recommend the use of [Bootstrap Social](http://lipis.github.io/bootstrap-social/) in order to automatically apply styles to buttons. Anyway you can always add your own style to the buttons. + +Social Authentication was inspired by [UseMuffin/OAuth2](https://github.com/UseMuffin/OAuth2) library. + +Custom username field +--------------------- + +In your customized users table, add the SocialBehavior with the following configuration: + +```php +$this->addBehavior('CakeDC/Users.Social', [ + 'username' => 'email' +]); +``` +Or if you extend the users table, the behavior is already loaded, so just configure it with: +```php +$this->behaviors()->get('Social')->config(['username' => 'email']); +``` + +By default it will use `username` field. + + +Social Middlewares +------------------ +We provide two middleware to help us the integration with social providers, the SocialAuthMiddleware is +the main one, it is responsible to redirect the user to the social provider site and setup information +needed by the CakeDC/Users.Social authenticator. The second one SocialEmailMiddleware is used when social provider does +not returns user email. + +Social Authenticators +--------------------- +The social authentication works with cakephp/authentication, we have two authenticators they work +in combination with the two social middlewares: + - CakeDC/Users.Social, works with SocialAuthMiddleware + - CakeDC/Users.SocialPendingEmai, works with SocialEmailMiddleware + + +Social Indentifier +------------------ +The social identifier "CakeDC/Users.Social", works with data provider by both social authenticator, +it is responsible of finding or creating a user registry for the social user data request. +By default, it'll fetch user data with finder 'all', but you can use a custom one. Add this to your +config/users.php: + +```php +'Auth.Identifiers.Social.authFinder' => 'customSocialAuth', +``` + + +Handling Social Login Result +---------------------------- +We use a base component 'CakeDC/Users.Login' to handle login, it checks the result of authentication +service to redirects user to an internal page or show an authentication error. It provide some error messages for social login. +There are two custom messages (Auth.SocialLoginFailure.messages) and one default message (Auth.SocialLoginFailure.defaultMessage). + + +To use a custom component to handle the login add this to your config/users.php file: +```php +'Auth.SocialLoginFailure.component' => 'MyLoginA', +``` + +The default configuration is: +```php +[ + ... + 'Auth' => [ + ... + 'SocialLoginFailure' => [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + 'FAILURE_USER_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + 'FAILURE_ACCOUNT_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ) + ], + 'targetAuthenticator' => 'CakeDC\Users\Authenticator\SocialAuthenticator' + ], + ... + ] +] +``` diff --git a/Docs/Documentation/Translations.md b/Docs/Documentation/Translations.md index 41abc4e08..d9cc74def 100644 --- a/Docs/Documentation/Translations.md +++ b/Docs/Documentation/Translations.md @@ -10,8 +10,9 @@ The Plugin is translated into several languages: * Polish (pl) by @joulbex * Hungarian (hu_HU) by @rrd108 * Italian (it) by @arturmamedov -* Turkish (tr_TR) by @sayinserdar +* Turkish (tr_TR) by @sayinserdar +* Ukrainian (uk) by @yarkm13 -**Note:** To overwrite the plugin translations, create a file inside your project 'src/Locale/{$lang}/' folder, with the name 'Users.po' and add the strings with the new translations. +**Note:** To overwrite the plugin translations, create a file inside your project 'resources/locales//{$lang}/' folder, with the name 'users.po' and add the strings with the new translations. Remember to clean the translations cache! diff --git a/Docs/Documentation/Two-Factor-Authenticator.md b/Docs/Documentation/Two-Factor-Authenticator.md new file mode 100644 index 000000000..022445a01 --- /dev/null +++ b/Docs/Documentation/Two-Factor-Authenticator.md @@ -0,0 +1,54 @@ +Two Factor Authenticator +=============================== +The plugin offers an easy way to integrate OTP Two-Factor authentication +in the users login flow of your application. + + +Installation Requirement +------------------------ +Before you enable the feature you need to run + +``` +composer require robthree/twofactorauth +``` + +By default the feature is disabled. + +Enabling +-------- + +First install robthree/twofactorauth using composer: + +``` +composer require robthree/twofactorauth +``` + +Then add this in your config/users.php file: + +```php + 'OneTimePasswordAuthenticator.login' => true, +``` + +Disabling +--------- +You can disable it by adding this in your config/users.php file: + +```php + 'OneTimePasswordAuthenticator.login' => false, +``` + +How does it work +---------------- +When the user log-in, he is requested (image 1) to inform the current validation +code for your site in Google Authentation app (image 2), if this is the first +time he access he need to add your site to Google Authentation by reading +the QR code shown (image 1). + +1) Validation code page + + + +2) Google Authentation app + + + diff --git a/Docs/Documentation/Yubico-U2F.md b/Docs/Documentation/Yubico-U2F.md new file mode 100644 index 000000000..73511f16c --- /dev/null +++ b/Docs/Documentation/Yubico-U2F.md @@ -0,0 +1,37 @@ +YubicoKey U2F +============= + +The plugin offers an easy way to integrate U2F in the users login flow +of your application. + +Enabling +-------- + +First install yubico/u2flib-server using composer: + +``` +composer require yubico/u2flib-server:^1.0 +``` + +Then add this in your config/users.php file: + +```php + 'U2f.enabled' => true, +``` + +Disabling +--------- +You can disable it by adding this in your config/users.php file: + +```php + 'U2f.enabled' => false, +``` + +How does it work +---------------- +When the user log-in, he is requested to insert and tap his registered yubico key, +if this is the first time he access he need to register the yubico key. + +Please check the yubico site for more information about U2F +https://developers.yubico.com/U2F/ + diff --git a/Docs/Home.md b/Docs/Home.md index 378d61db9..4223b0e82 100644 --- a/Docs/Home.md +++ b/Docs/Home.md @@ -13,20 +13,236 @@ Documentation * [Overview](Documentation/Overview.md) * [Installation](Documentation/Installation.md) * [Configuration](Documentation/Configuration.md) +* [Authentication](Documentation/Authentication.md) +* [Authorization](Documentation/Authorization.md) * [SimpleRbacAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SimpleRbacAuthorize.md) * [SuperuserAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SuperuserAuthorize.md) -* [ApiKeyAuthenticate](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/ApiKeyAuthenticate.md) -* [SocialAuthenticate](Documentation/SocialAuthenticate.md) -* [Google Authenticator](Documentation/Google-Two-Factor-Authenticator.md) +* [Intercept Login Action](Documentation/InterceptLoginAction.md) +* [Social Authentication](Documentation/SocialAuthentication.md) +* [Google Authenticator](Documentation/Two-Factor-Authenticator.md) +* [Yubico U2F](Documentation/Yubico-U2F.md) * [UserHelper](Documentation/UserHelper.md) +* [AuthLinkHelper](Documentation/AuthLinkHelper.md) * [Events](Documentation/Events.md) * [Extending the Plugin](Documentation/Extending-the-Plugin.md) * [Translations](Documentation/Translations.md) +I want to +--------- +* extend the + * [model](Documentation/Extending-the-Plugin.md#extending-the-model-tableentity) + * [controller](Documentation/Extending-the-Plugin.md#extending-the-controller) + * [templates](Documentation/Extending-the-Plugin.md#updating-the-templates) + +* enable or disable + *
+ email validation + + Add this to your config/users.php file to disable email validation + + ```php + 'Users.Email.validate' => false, + ``` + or this to enable (default) + + ```php + 'Users.Email.validate' => true, + ``` +
+ *
+ registration + + Add this to your config/users.php file to disable registration + + ```php + 'Users.Registration.active' => false, + ``` + or this to enable (default) + + ```php + 'Users.Registration.active' => true, + ``` +
+ *
+ reCaptcha on registration + + To enable reCaptcha you need to register your site at google reCaptcha console + and add this to your config/users.php file to enable on registration: + + ```php + 'Users.reCaptcha.key' => 'YOUR RECAPTCHA KEY', + 'Users.reCaptcha.secret' => 'YOUR RECAPTCHA SECRET', + 'Users.reCaptcha.registration' => true, + ``` + To disable (default) add this to your config/users.php + + ```php + 'Users.reCaptcha.registration' => false, + ``` +
+ *
+ reCaptcha on login + + To enable reCaptcha you need to register your site at google reCaptcha console + and add this to your config/users.php file to enable on login: + + ```php + 'Users.reCaptcha.key' => 'YOUR RECAPTCHA KEY', + 'Users.reCaptcha.secret' => 'YOUR RECAPTCHA SECRET', + 'Users.reCaptcha.login' => true, + ``` + To disable (default) add this to your config/users.php + + ```php + 'Users.reCaptcha.login' => false, + ``` +
+ * [social login](./Documentation/SocialAuthentication.md#setup) + * [OTP Two-factor authenticator](./Documentation/Two-Factor-Authenticator.md) + * [Yubico Key U2F Two-factor authenticator](./Documentation/Yubico-U2F.md) + *
+ Authentication component + + Add this to your config/users.php file to autoload the component (default): + + ```php + 'Auth.AuthenticationComponent.load' => true, + ``` + + To not autoload add this to your config/users.php + + ```php + 'Auth.AuthenticationComponent.load' => false, + ``` +
+ *
+ Authorization component + + Add this to your config/users.php file to autoload the component (default): + + ```php + 'Auth.AuthorizationComponent.enabled' => true, + ``` + + To not autoload add this to your config/users.php + + ```php + 'Auth.AuthorizationComponent.enabled' => false, + ``` +
+ + *
+ TOS validation + + Add this to your config/users.php file to enable (default): + + ```php + 'Users.Tos.required' => true, + ``` + + To disable add this to your config/users.php + + ```php + 'Users.Tos.required' => false, + ``` +
+ + *
+ remember me + + Add this to your config/users.php file to enable (default): + + ```php + 'Users.RememberMe.active' => true, + ``` + + To disable add this to your config/users.php + + ```php + 'Users.RememberMe.active' => false, + ``` +
+ +- allow access to + - [public actions (non-logged user)](./Documentation/Permissions.md#i-want-to-allow-access-to-public-actions-non-logged-user) + - [one specific action](./Documentation/Permissions.md#i-want-to-allow-access-to-one-specific-action) + - [all actions from one controller](./Documentation/Permissions.md#i-want-to-allow-access-to-all-actions-from-one-controller) + - [all controllers from one prefix](./Documentation/Permissions.md#i-want-to-allow-access-to-all-controllers-from-one-prefix) + - [entity owned by the user](./Documentation/Permissions.md#i-want-to-allow-access-to-entity-owned-by-the-user) + - [action using a custom logic](./Documentation/Permissions.md#i-want-to-allow-access-to-action-using-a-custom-logic) + +- customize my login page to + -
+ use my template + Copy the login file from `{project_dir}/vendor/cakedc/users/templates/Users/` + to `{project_dir}/templates/plugin/CakeDC/Users/Users`. +
+ + -
+ use a custom finder + First add this to your config/users.php: + + ``` + 'Auth.Identifiers.Password.resolver.finder' => 'myFinderName', + 'Auth.Identifiers.Social.authFinder' => 'myFinderName', + 'Auth.Identifiers.Token.resolver.finder' => 'myFinderName', + ``` + Important: You must have extended the model, see how to at [Extending the Plugin](Documentation/Extending-the-Plugin.md) +
+ + -
+ use a custom redirect url + To use a custom redirect url on login add this to your config/users.php: + + ``` + 'Auth.AuthenticationComponent.loginRedirect' => '/some/url/', + ``` + or + ``` + 'Auth.AuthenticationComponent.loginRedirect' => ['plugin' => false, 'controller' => 'Example', 'action' => 'home'], + ``` + Important: when using array you should pass `'plugin' => false,` to match your app controller. +
+ + -
+ enable|disable reCaptcha + + To enable reCaptcha you need to register your site at google reCaptcha console + and add this to your config/users.php file to enable on login: + + ```php + 'Users.reCaptcha.login' => true, + 'Users.reCaptcha.key' => 'YOUR RECAPTCHA KEY', + 'Users.reCaptcha.secret' => 'YOUR RECAPTCHA SECRET', + ``` + To disable (default) add this to your config/users.php + ```php + 'Users.reCaptcha.login' => false, + ``` +
+ - [use user's email to login](./Documentation/Configuration.md#using-the-users-email-to-login) + - [override the password hasher](./Documentation/Configuration.md#password-hasher-customization) + +- add custom logic before + - [user logout](./Documentation/Events.md#i-want-to-add-custom-logic-before-user-logout) + - [user register](./Documentation/Events.md#i-want-to-add-custom-logic-before-user-register) + - [linking social account](./Documentation/Events.md#i-want-to-add-custom-logic-before-linking-social-account) + - [creating social account](./Documentation/Events.md#i-want-to-add-custom-logic-before-creating-social-account) + +- add custom logic after + - [user login](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-login) + - [user logout](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-logout) + - [user register](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-register) + - [user changed the password](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-changed-the-password) + - [sending the token for user validation](./Documentation/Events.md#i-want-to-add-custom-logic-after-sending-the-token-for-user-validation) + - [user email is validated](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-email-is-validated) + - [user email is validated to autologin user](./Documentation/Events.md#i-want-to-add-custom-logic-after-user-email-is-validated-to-autologin-user) + - [intercept login action](./Documentation/InterceptLoginAction.md) + + Migration guides ---------------- * [4.x to 5.0](Documentation/Migration/4.x-5.0.md) * [6.x to 7.0](Documentation/Migration/6.x-7.0.md) - - +* [8.x to 9.0](Documentation/Migration/8.x-9.0.md) diff --git a/README.md b/README.md index 1c213f6a7..fbd5af5f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CakeDC Users Plugin =================== -[![Build Status](https://secure.travis-ci.org/CakeDC/users.png?branch=master)](http://travis-ci.org/CakeDC/users) +[![Build Status](https://img.shields.io/github/workflow/status/CakeDC/users/CI/master?style=flat-square)](https://github.com/CakeDC/users/actions?query=workflow%3ACI+branch%3Amaster) [![Coverage Status](https://img.shields.io/codecov/c/gh/CakeDC/users.svg?style=flat-square)](https://codecov.io/gh/CakeDC/users) [![Downloads](https://poser.pugx.org/CakeDC/users/d/total.png)](https://packagist.org/packages/CakeDC/users) [![Latest Version](https://poser.pugx.org/CakeDC/users/v/stable.png)](https://packagist.org/packages/CakeDC/users) @@ -12,16 +12,19 @@ Versions and branches | CakePHP | CakeDC Users Plugin | Tag | Notes | | :-------------: | :------------------------: | :--: | :---- | -| ^3.6 | [master](https://github.com/cakedc/users/tree/master) | 7.0.0 | stable | -| ^3.6 | [develop](https://github.com/cakedc/users/tree/develop) | - | unstable | -| 3.5 | [6.x](https://github.com/cakedc/users/tree/6.x) | 6.0.1 | stable | -| 3.4 | [5.x](https://github.com/cakedc/users/tree/5.x) | 5.2.0 | stable | -| 3.3 | [4.x](https://github.com/cakedc/users/tree/4.x) | 4.2.0 | stable | -| 2.x | [2.x](https://github.com/cakedc/users/tree/2.x) | 2.2.0 |stable | +| ^4.3 | [master](https://github.com/cakedc/users/tree/master) | 11.0.0 | stable | +| ^4.3 | [11.0](https://github.com/cakedc/users/tree/11.next-cake4) | 11.0.0 | stable | +| ^4.0 | [9.0](https://github.com/cakedc/users/tree/9.next) | 9.0.5 | stable | +| ^3.7 <4.0 | [8.5](https://github.com/cakedc/users/tree/8.next) | 8.5.1 | stable | +| ^3.7 <4.0 | [develop](https://github.com/cakedc/users/tree/develop) | - | unstable | +| 3.6 | [8.1](https://github.com/cakedc/users/tree/8.1.0) | 8.1.0 | stable | +| 3.5 | [6.x](https://github.com/cakedc/users/tree/6.x) | 6.0.1 | stable | +| 3.4 | [5.x](https://github.com/cakedc/users/tree/5.x) | 5.2.0 | stable | +| >=3.2.9 <3.4.0 | [4.x](https://github.com/cakedc/users/tree/4.x) | 4.2.1 | stable | +| ^2.10 | [2.x](https://github.com/cakedc/users/tree/2.x) | 2.2.0 |stable | + +The **Users** plugin covers the following features: -The **Users** plugin is back! - -It covers the following features: * User registration * Login/logout * Social login (Facebook, Twitter, Instagram, Google, Linkedin, etc) @@ -29,8 +32,11 @@ It covers the following features: * Remember me (Cookie) via https://github.com/CakeDC/auth * Manage user's profile * Admin management +* Yubico U2F for Two-Factor Authentication +* One-Time Password for Two-Factor Authentication The plugin is here to provide users related features following 2 approaches: + * Quick drop-in working solution for users login/registration. Get users working in 5 minutes. * Extensible solution for a bigger/custom application. You'll be able to extend: * UsersAuth Component @@ -46,8 +52,8 @@ Another decision made was limiting the plugin dependencies on other packages as Requirements ------------ -* CakePHP 3.6.0+ -* PHP 5.6+ +* CakePHP 4.0+ +* PHP 7.2+ Documentation ------------- @@ -69,6 +75,6 @@ This repository follows the [CakeDC Plugin Standard](https://www.cakedc.com/plug License ------- -Copyright 2017 Cake Development Corporation (CakeDC). All rights reserved. +Copyright 2019 Cake Development Corporation (CakeDC). All rights reserved. Licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php) License. Redistributions of the source code included in this repository must retain the copyright notice found in each file. diff --git a/composer.json b/composer.json index 93d1a1dbd..3a94372d7 100644 --- a/composer.json +++ b/composer.json @@ -26,19 +26,29 @@ "issues": "https://github.com/CakeDC/users/issues", "source": "https://github.com/CakeDC/users" }, + "minimum-stability": "dev", + "prefer-stable": true, "require": { - "cakephp/cakephp": "^3.6", - "cakedc/auth": "^2.0" + "php": ">=7.2.0", + "cakephp/cakephp": "^4.0", + "cakedc/auth": "^7.0", + "cakephp/authorization": "^2.0.0", + "cakephp/authentication": "^2.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.0", + "phpunit/phpunit": "^9.5", "league/oauth2-facebook": "@stable", "league/oauth2-instagram": "@stable", "league/oauth2-google": "@stable", "league/oauth2-linkedin": "@stable", "luchianenco/oauth2-amazon": "^1.1", "google/recaptcha": "@stable", - "robthree/twofactorauth": "~1.6.0" + "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": "v3.3.x-dev" }, "suggest": { "league/oauth1-client": "Provides Social Authentication with Twitter", @@ -48,7 +58,8 @@ "luchianenco/oauth2-amazon": "Provides Social Authentication with Amazon", "league/oauth2-linkedin": "Provides Social Authentication with LinkedIn", "google/recaptcha": "Provides reCAPTCHA validation for registration form", - "robthree/twofactorauth": "Provides Google Authenticator functionality" + "robthree/twofactorauth": "Provides Google Authenticator functionality", + "cakephp/authorization": "Provide authorization for users" }, "autoload": { "psr-4": { @@ -58,7 +69,30 @@ "autoload-dev": { "psr-4": { "CakeDC\\Users\\Test\\": "tests", - "CakeDC\\Users\\Test\\Fixture\\": "tests" + "CakeDC\\Users\\Test\\Fixture\\": "tests", + "TestApp\\": "tests/test_app/TestApp/" } + }, + "scripts": { + "analyse": [ + "@stan", + "@psalm" + ], + "check": [ + "@cs-check", + "@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", + "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-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/", + "rector-setup": "cp composer.json composer.backup && composer require --dev rector/rector:^0.11.2 && mv composer.backup composer.json", + "coverage-test": "phpunit --stderr --coverage-clover=clover.xml" } } diff --git a/config/Migrations/20150513201111_initial.php b/config/Migrations/20150513201111_initial.php index 55b00f094..2379afa67 100644 --- a/config/Migrations/20150513201111_initial.php +++ b/config/Migrations/20150513201111_initial.php @@ -1,11 +1,11 @@ table('users'); + $table->addColumn('additional_data', 'text', [ + 'default' => null, + 'null' => true, + ]); + $table->update(); + } +} diff --git a/config/Migrations/20210929202041_AddLastLoginToUsers.php b/config/Migrations/20210929202041_AddLastLoginToUsers.php new file mode 100644 index 000000000..5a533c250 --- /dev/null +++ b/config/Migrations/20210929202041_AddLastLoginToUsers.php @@ -0,0 +1,24 @@ +table('users'); + $table->addColumn('last_login', 'datetime', [ + 'default' => null, + 'null' => true, + ]); + $table->update(); + } +} diff --git a/config/Migrations/schema-dump-default.lock b/config/Migrations/schema-dump-default.lock index f4f542a73..9c437b3c1 100644 Binary files a/config/Migrations/schema-dump-default.lock and b/config/Migrations/schema-dump-default.lock differ diff --git a/config/bootstrap.php b/config/bootstrap.php index 42d2bb0c2..d78acc8b7 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -1,19 +1,16 @@ each(function ($file) { Configure::load($file); }); +UsersUrl::setupConfigUrls(); -TableRegistry::getTableLocator()->setConfig('Users', ['className' => Configure::read('Users.table')]); -TableRegistry::getTableLocator()->setConfig('CakeDC/Users.Users', ['className' => Configure::read('Users.table')]); - -if (Configure::check('Users.auth')) { - Configure::write('Auth.authenticate.all.userModel', Configure::read('Users.table')); -} - -if (Configure::read('Users.Social.login') && php_sapi_name() != 'cli') { - try { - EventManager::instance()->on(\CakeDC\Users\Controller\Component\UsersAuthComponent::EVENT_FAILED_SOCIAL_LOGIN, [new \CakeDC\Users\Controller\UsersController(), 'failedSocialLoginListener']); - } catch (MissingPluginException $e) { - Log::error($e->getMessage()); +$locator = TableRegistry::getTableLocator(); +foreach (['Users', 'CakeDC/Users.Users'] as $modelKey) { + if (!$locator->exists($modelKey)) { + $locator->setConfig($modelKey, ['className' => Configure::read('Users.table')]); } } - -$oauthPath = Configure::read('OAuth.path'); -if (is_array($oauthPath)) { - Router::scope('/auth', function ($routes) use ($oauthPath) { - $routes->connect( - '/:provider', - $oauthPath, - ['provider' => implode('|', array_keys(Configure::read('OAuth.providers')))] - ); - }); +$oldConfigs = [ + 'Users.auth', + 'Users.Social.authenticator', + 'Users.GoogleAuthenticator', + 'GoogleAuthenticator', + 'Auth.authenticate', + 'Auth.authorize', +]; +foreach ($oldConfigs as $configKey) { + if (Configure::check($configKey)) { + trigger_error(__("Users plugin configuration key \"{0}\" was removed, please check migration guide https://github.com/CakeDC/users/blob/master/Docs/Documentation/Migration/8.x-9.0.md", $configKey)); + } } diff --git a/config/permissions.php b/config/permissions.php index 79a4ae957..5c52467fb 100644 --- a/config/permissions.php +++ b/config/permissions.php @@ -1,11 +1,11 @@ [ + //all bypass + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => [ + // LoginTrait + 'socialLogin', + 'login', + 'logout', + 'socialEmail', + 'verify', + // RegisterTrait + 'register', + 'validateEmail', + // PasswordManagementTrait used in RegisterTrait + 'changePassword', + 'resetPassword', + 'requestResetPassword', + // UserValidationTrait used in PasswordManagementTrait + 'resendTokenValidation', + 'linkSocial', + //U2F actions + 'u2f', + 'u2fRegister', + 'u2fRegisterFinish', + 'u2fAuthenticate', + 'u2fAuthenticateFinish', + 'webauthn2fa', + 'webauthn2faRegister', + 'webauthn2faRegisterOptions', + 'webauthn2faAuthenticate', + 'webauthn2faAuthenticateOptions', + ], + 'bypassAuth' => true, + ], + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => [ + 'validateAccount', + 'resendValidation', + ], + 'bypassAuth' => true, + ], //admin role allowed to all the things [ 'role' => 'admin', @@ -71,7 +117,7 @@ 'role' => '*', 'plugin' => 'CakeDC/Users', 'controller' => 'Users', - 'action' => 'resetGoogleAuthenticator', + 'action' => 'resetOneTimePasswordAuthenticator', 'allowed' => function (array $user, $role, \Cake\Http\ServerRequest $request) { $userId = \Cake\Utility\Hash::get($request->getAttribute('params'), 'pass.0'); if (!empty($userId) && !empty($user)) { @@ -87,5 +133,12 @@ 'controller' => 'Pages', 'action' => 'display', ], + [ + 'role' => '*', + 'plugin' => 'DebugKit', + 'controller' => '*', + 'action' => '*', + 'bypassAuth' => true, + ], ] ]; diff --git a/config/routes.php b/config/routes.php index f19f356a3..81ac70cc1 100644 --- a/config/routes.php +++ b/config/routes.php @@ -1,52 +1,52 @@ '/users'], function ($routes) { - $routes->fallbacks('DashedRoute'); -}); +use Cake\Routing\RouteBuilder; +use CakeDC\Users\Utility\UsersUrl; -Router::connect('/auth/twitter', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'twitterLogin', - 'provider' => 'twitter' -]); -Router::connect('/accounts/validate/*', [ +$routes->connect('/accounts/validate/*', [ 'plugin' => 'CakeDC/Users', 'controller' => 'SocialAccounts', 'action' => 'validate' ]); // Google Authenticator related routes -if (Configure::read('Users.GoogleAuthenticator.login')) { - Router::connect('/verify', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'verify']); +if (Configure::read('OneTimePasswordAuthenticator.login')) { + $routes->connect('/verify', UsersUrl::actionRouteParams('verify')); - Router::connect('/resetGoogleAuthenticator', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'resetGoogleAuthenticator' - ]); + $routes->connect('/resetOneTimePasswordAuthenticator', UsersUrl::actionRouteParams('resetOneTimePasswordAuthenticator')); } -Router::connect('/profile/*', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']); -Router::connect('/login', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); -Router::connect('/logout', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout']); -Router::connect('/link-social/*', [ - 'controller' => 'Users', - 'action' => 'linkSocial', - 'plugin' => 'CakeDC/Users', -]); -Router::connect('/callback-link-social/*', [ - 'controller' => 'Users', - 'action' => 'callbackLinkSocial', +$routes->connect('/profile/*', UsersUrl::actionRouteParams('profile')); +$routes->connect('/login', UsersUrl::actionRouteParams('login')); +$routes->connect('/logout', UsersUrl::actionRouteParams('logout')); +$routes->connect('/link-social/*', UsersUrl::actionRouteParams('linkSocial')); +$routes->connect('/callback-link-social/*', UsersUrl::actionRouteParams('callbackLinkSocial')); +$routes->connect('/register', UsersUrl::actionRouteParams('register')); + +$oauthPath = Configure::read('OAuth.path'); +if (is_array($oauthPath)) { + $routes->scope('/auth', function (RouteBuilder $routes) use ($oauthPath) { + $routes->connect( + '/{provider}', + $oauthPath, + ['provider' => implode('|', array_keys(Configure::read('OAuth.providers')))] + ); + }); +} + +$routes->connect('/social-accounts/:action/*', [ 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', ]); +$routes->connect('/users/{action}/*', UsersUrl::actionRouteParams(null)); diff --git a/config/users.php b/config/users.php index 52bba11e8..23dfeb9d1 100644 --- a/config/users.php +++ b/config/users.php @@ -1,16 +1,34 @@ getHost(); + if ($fullBaseHost) { + $allowedRedirectHosts[] = $fullBaseHost; + } + } catch (Exception $ex) { + Log::warning('Invalid host from App.fullBasedUrl in CakeDC/Users configuration: ' . $ex->getMessage()); + } +} $config = [ 'Users' => [ @@ -18,10 +36,9 @@ 'table' => 'CakeDC/Users.Users', // Controller used to manage users plugin features & actions 'controller' => 'CakeDC/Users.Users', - // configure Auth component - 'auth' => true, // Password Hasher 'passwordHasher' => '\Cake\Auth\DefaultPasswordHasher', + 'middlewareQueueLoader' => \CakeDC\Users\Loader\MiddlewareQueueLoader::class, // token expiration, 1 hour 'Token' => ['expiration' => 3600], 'Email' => [ @@ -41,6 +58,8 @@ 'ensureActive' => false, // default role name used in registration 'defaultRole' => 'user', + // show verbose error to users + 'showVerboseError' => false, ], 'reCaptcha' => [ // reCaptcha key goes here @@ -59,28 +78,10 @@ 'Social' => [ // enable social login 'login' => false, - // enable social login - 'authenticator' => 'CakeDC/Users.Social', - ], - 'GoogleAuthenticator' => [ - // enable Google Authenticator - 'login' => false, - 'issuer' => null, - // The number of digits the resulting codes will be - 'digits' => 6, - // The number of seconds a code will be valid - 'period' => 30, - // The algorithm used - 'algorithm' => 'sha1', - // QR-code provider (more on this later) - 'qrcodeprovider' => null, - // Random Number Generator provider (more on this later) - 'rngprovider' => null ], 'Profile' => [ // Allow view other users profiles 'viewOthers' => true, - 'route' => ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], ], 'Key' => [ 'Session' => [ @@ -91,7 +92,7 @@ ], // form key to store the social auth data 'Form' => [ - 'social' => 'social' + 'social' => 'social', ], 'Data' => [ // data key to store the users email @@ -111,95 +112,206 @@ 'Cookie' => [ 'name' => 'remember_me', 'Config' => [ - 'expires' => '1 month', - 'httpOnly' => true, - ] - ] + 'expires' => new \DateTime('+1 month'), + 'httponly' => true, + ], + ], ], + 'Superuser' => ['allowedToChangePasswords' => false], // able to reset any users password + // list of valid hosts to allow redirects after valid login via the `redirect` query param + 'AllowedRedirectHosts' => $allowedRedirectHosts, ], - 'GoogleAuthenticator' => [ - 'verifyAction' => [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'verify', - 'prefix' => false, - ], + 'OneTimePasswordAuthenticator' => [ + 'checker' => \CakeDC\Auth\Authentication\DefaultOneTimePasswordAuthenticationChecker::class, + 'login' => false, + 'issuer' => null, + // The number of digits the resulting codes will be + 'digits' => 6, + // The number of seconds a code will be valid + 'period' => 30, + // The algorithm used + 'algorithm' => 'sha1', + // QR-code provider (more on this later) + 'qrcodeprovider' => null, + // Random Number Generator provider (more on this later) + 'rngprovider' => null, + ], + 'U2f' => [ + 'enabled' => false, + 'checker' => \CakeDC\Auth\Authentication\DefaultU2fAuthenticationChecker::class, + ], + 'Webauthn2fa' => [ + '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, ], // default configuration used to auto-load the Auth Component, override to change the way Auth works 'Auth' => [ - 'loginAction' => [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'login', - 'prefix' => false + 'Authentication' => [ + 'serviceLoader' => \CakeDC\Users\Loader\AuthenticationServiceLoader::class, ], - 'authenticate' => [ - 'all' => [ - 'finder' => 'auth', + 'AuthenticationComponent' => [ + 'load' => true, + 'loginRedirect' => '/', + 'requireIdentity' => false, + ], + 'Authenticators' => [ + 'Session' => [ + 'className' => 'Authentication.Session', + 'skipTwoFactorVerify' => true, + 'sessionKey' => 'Auth', + ], + 'Form' => [ + 'className' => 'CakeDC/Auth.Form', + 'urlChecker' => 'Authentication.CakeRouter', + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'skipTwoFactorVerify' => true, + 'header' => null, + 'queryParam' => 'api_key', + 'tokenPrefix' => null, + ], + 'Cookie' => [ + 'className' => 'CakeDC/Auth.Cookie', + 'skipTwoFactorVerify' => true, + 'rememberMeField' => 'remember_me', + 'cookie' => [ + 'expires' => new \DateTime('+1 month'), + 'httponly' => true, + ], + 'urlChecker' => 'Authentication.CakeRouter', + ], + 'Social' => [ + 'className' => 'CakeDC/Users.Social', + 'skipTwoFactorVerify' => true, + ], + 'SocialPendingEmail' => [ + 'className' => 'CakeDC/Users.SocialPendingEmail', + 'skipTwoFactorVerify' => true, + ], + ], + 'Identifiers' => [ + 'Password' => [ + 'className' => 'Authentication.Password', + 'fields' => [ + 'username' => ['username', 'email'], + 'password' => 'password', + ], + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'finder' => 'active', + ], + ], + 'Social' => [ + 'className' => 'CakeDC/Users.Social', + 'authFinder' => 'active', + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'tokenField' => 'api_token', + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'finder' => 'active', + ], ], - 'CakeDC/Auth.ApiKey', - 'CakeDC/Auth.RememberMe', - 'Form', ], - 'authorize' => [ - 'CakeDC/Auth.Superuser', - 'CakeDC/Auth.SimpleRbac', + 'Authorization' => [ + 'enable' => true, + 'serviceLoader' => \CakeDC\Users\Loader\AuthorizationServiceLoader::class, + ], + 'AuthorizationMiddleware' => [ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', + ], + ], + 'AuthorizationComponent' => [ + 'enabled' => true, + ], + 'RbacPolicy' => [], + 'PasswordRehash' => [ + 'identifiers' => ['Password'], ], ], 'OAuth' => [ - 'path' => ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'prefix' => null], 'providers' => [ 'facebook' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', 'className' => 'League\OAuth2\Client\Provider\Facebook', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'authParams' => ['scope' => ['public_profile', 'email', 'user_birthday', 'user_gender', 'user_link']], 'options' => [ 'graphApiVersion' => 'v2.8', //bio field was deprecated on >= v2.8 'redirectUri' => Router::fullBaseUrl() . '/auth/facebook', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/facebook', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/facebook', - ] + ], ], 'twitter' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth1Service', + 'className' => 'League\OAuth1\Client\Server\Twitter', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Twitter', 'options' => [ 'redirectUri' => Router::fullBaseUrl() . '/auth/twitter', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/twitter', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/twitter', - ] + ], ], 'linkedIn' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', 'className' => 'League\OAuth2\Client\Provider\LinkedIn', + 'mapper' => 'CakeDC\Auth\Social\Mapper\LinkedIn', 'options' => [ 'redirectUri' => Router::fullBaseUrl() . '/auth/linkedIn', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedIn', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedIn', - ] + ], ], 'instagram' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', 'className' => 'League\OAuth2\Client\Provider\Instagram', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Instagram', 'options' => [ 'redirectUri' => Router::fullBaseUrl() . '/auth/instagram', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/instagram', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/instagram', - ] + ], ], 'google' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', 'className' => 'League\OAuth2\Client\Provider\Google', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Google', 'options' => [ 'userFields' => ['url', 'aboutMe'], 'redirectUri' => Router::fullBaseUrl() . '/auth/google', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/google', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/google', - ] + ], ], 'amazon' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', 'className' => 'Luchianenco\OAuth2\Client\Provider\Amazon', - 'options' => [ + 'mapper' => 'CakeDC\Auth\Social\Mapper\Amazon', + 'options' => [ 'redirectUri' => Router::fullBaseUrl() . '/auth/amazon', 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/amazon', 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/amazon', - ] + ], + ], + 'cognito' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'CakeDC\OAuth2\Client\Provider\Cognito', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Cognito', + 'options' => [ + 'redirectUri' => Router::fullBaseUrl() . '/auth/cognito', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/cognito', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/cognito', + 'scope' => 'email openid', + ], ], ], - ] + ], ]; return $config; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..52b966521 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,267 @@ +parameters: + ignoreErrors: + - + message: "#^Access to an undefined property Cake\\\\Controller\\\\Controller\\:\\:\\$Authentication\\.$#" + count: 1 + path: src/Controller/Component/LoginComponent.php + + - + message: "#^Call to an undefined method Cake\\\\Controller\\\\Controller\\:\\:getUsersTable\\(\\)\\.$#" + count: 2 + path: src/Controller/Component/LoginComponent.php + + - + message: "#^Access to an undefined property CakeDC\\\\Users\\\\Controller\\\\UsersController\\:\\:\\$Authentication\\.$#" + count: 2 + path: src/Controller/UsersController.php + + - + message: "#^Access to an undefined property CakeDC\\\\Users\\\\Controller\\\\UsersController\\:\\:\\$OneTimePasswordAuthenticator\\.$#" + count: 3 + path: src/Controller/UsersController.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$u2f_registration\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\Controller\\\\Component\\:\\:handleLogin\\(\\)\\.$#" + count: 3 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:changePassword\\(\\)\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:linkSocialAccount\\(\\)\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:register\\(\\)\\.$#" + count: 2 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:resetToken\\(\\)\\.$#" + count: 2 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:validate\\(\\)\\.$#" + count: 2 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:validationCurrentPassword\\(\\)\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:validationPasswordConfirm\\(\\)\\.$#" + 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 + path: src/Controller/UsersController.php + + - + message: "#^Parameter \\#2 \\$options of method Cake\\\\Controller\\\\Component\\\\FlashComponent\\:\\:success\\(\\) expects array, string given\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Parameter \\#2 \\$usersTable of class CakeDC\\\\Users\\\\Webauthn\\\\RegisterAdapter constructor expects CakeDC\\\\Users\\\\Model\\\\Table\\\\UsersTable\\|null, Cake\\\\ORM\\\\Table given\\.$#" + count: 1 + path: src/Controller/UsersController.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:socialLogin\\(\\)\\.$#" + count: 1 + path: src/Identifier/SocialIdentifier.php + + - + message: "#^Parameter \\#1 \\$request of method CakeDC\\\\Users\\\\Middleware\\\\SocialAuthMiddleware\\:\\:goNext\\(\\) expects Cake\\\\Http\\\\ServerRequest, Psr\\\\Http\\\\Message\\\\ServerRequestInterface given\\.$#" + count: 1 + path: src/Middleware/SocialAuthMiddleware.php + + - + message: "#^Parameter \\#2 \\$request of method CakeDC\\\\Users\\\\Utility\\\\UsersUrl\\:\\:checkActionOnRequest\\(\\) expects Cake\\\\Http\\\\ServerRequest, Psr\\\\Http\\\\Message\\\\ServerRequestInterface given\\.$#" + count: 1 + path: src/Middleware/SocialAuthMiddleware.php + + - + message: "#^Parameter \\#1 \\$request of method CakeDC\\\\Users\\\\Middleware\\\\SocialAuthMiddleware\\:\\:goNext\\(\\) expects Cake\\\\Http\\\\ServerRequest, Psr\\\\Http\\\\Message\\\\ServerRequestInterface given\\.$#" + count: 1 + path: src/Middleware/SocialEmailMiddleware.php + + - + message: "#^Parameter \\#2 \\$request of method CakeDC\\\\Users\\\\Utility\\\\UsersUrl\\:\\:checkActionOnRequest\\(\\) expects Cake\\\\Http\\\\ServerRequest, Psr\\\\Http\\\\Message\\\\ServerRequestInterface given\\.$#" + count: 1 + path: src/Middleware/SocialEmailMiddleware.php + + - + message: "#^Call to an undefined method Cake\\\\Datasource\\\\EntityInterface\\:\\:updateToken\\(\\)\\.$#" + count: 1 + path: src/Model/Behavior/BaseTokenBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$social_accounts\\.$#" + count: 2 + path: src/Model/Behavior/LinkSocialBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\ORM\\\\Table\\:\\:\\$SocialAccounts\\.$#" + count: 5 + path: src/Model/Behavior/LinkSocialBehavior.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/Model/Behavior/LinkSocialBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$password\\.$#" + count: 1 + path: src/Model/Behavior/PasswordBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$password_confirm\\.$#" + count: 1 + path: src/Model/Behavior/PasswordBehavior.php + + - + message: "#^Call to an undefined method Cake\\\\Datasource\\\\EntityInterface\\:\\:checkPassword\\(\\)\\.$#" + count: 1 + path: src/Model/Behavior/PasswordBehavior.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:findByUsernameOrEmail\\(\\)\\.$#" + count: 1 + path: src/Model/Behavior/PasswordBehavior.php + + - + message: "#^Method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\PasswordBehavior\\:\\:resetToken\\(\\) should return string but returns Cake\\\\Datasource\\\\EntityInterface\\|false\\.$#" + count: 1 + path: src/Model/Behavior/PasswordBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$activation_date\\.$#" + count: 1 + path: src/Model/Behavior/RegisterBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$active\\.$#" + count: 1 + path: src/Model/Behavior/RegisterBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$token_expires\\.$#" + count: 1 + path: src/Model/Behavior/RegisterBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\Datasource\\\\EntityInterface\\:\\:\\$validated\\.$#" + count: 1 + path: src/Model/Behavior/RegisterBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\ORM\\\\Table\\:\\:\\$isValidateEmail\\.$#" + count: 1 + path: src/Model/Behavior/RegisterBehavior.php + + - + message: "#^Call to an undefined method Cake\\\\ORM\\\\Table\\:\\:validationRegister\\(\\)\\.$#" + 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 + path: src/Model/Behavior/SocialAccountBehavior.php + + - + message: "#^Method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\SocialAccountBehavior\\:\\:validateAccount\\(\\) should return CakeDC\\\\Users\\\\Model\\\\Entity\\\\User but returns Cake\\\\Datasource\\\\EntityInterface\\.$#" + 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 + path: src/Model/Behavior/SocialBehavior.php + + - + message: "#^Access to an undefined property Cake\\\\ORM\\\\Table\\:\\:\\$isValidateEmail\\.$#" + count: 1 + path: src/Model/Behavior/SocialBehavior.php + + - + message: "#^Parameter \\#3 \\$tokenExpiration of method CakeDC\\\\Users\\\\Model\\\\Behavior\\\\BaseTokenBehavior\\:\\:_updateActive\\(\\) expects int, string given\\.$#" + count: 1 + path: src/Model/Behavior/SocialBehavior.php + + - + message: "#^Method CakeDC\\\\Users\\\\Model\\\\Entity\\\\User\\:\\:_setTos\\(\\) should return bool but returns string\\.$#" + 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 + path: src/Webauthn/BaseAdapter.php + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..f6dbf5f0b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,16 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 6 + checkMissingIterableValueType: false + checkGenericClassInNonGenericObjectType: false + bootstrapFiles: + - tests/bootstrap.php + excludes_analyse: + - src/Controller/SocialAccountsController.php + - src/Controller/UsersAccountsController.php + - src/Controller/AppAccountsController.php + ignoreErrors: + +services: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 500c19975..3a1684f67 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,36 +1,33 @@ - - - - - + + + + + ./src + + + + + + + + + + + ./tests/TestCase + + + + + + - - - - ./tests/TestCase - - - - - ./src - - - - - - - - - - - diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..305a69fbb --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,292 @@ + + + + + BaseController + + + + + getConfig + + + $checker + $checker + + + getConfig + + + + + AppController + + + + + public function resetOneTimePasswordAuthenticator($id = null) + + + + + \ReCaptcha\ReCaptcha + + + + + AppController + + + + + SocialIdentifier + + + + + $exception->getPrevious() + + + + + $exp + + + function ($exp) use ($identifier, $where) { + + + + + $result + + + \Cake\Datasource\EntityInterface + + + updateToken + + + + + $user->id + $socialAccount->user_id + $user->id + $user->social_accounts + $socialAccount->id + $user->social_accounts + + + $socialAccount + + + $accountData['avatar'] + + + + + $saveResult + + + string + + + $user->id + $user->current_password + $currentUser->password + $user->password_confirm + + + checkPassword + + + + + $user + + + $context + + + $user->validated + $user->active + $user->activation_date + $user->token_expires + $user->active + + + tokenExpired + + + $validateEmail + $tokenExpiration + + + tokenExpired + + + $this->_table->isValidateEmail + + + $this->validateEmail + $this->useTos + $this->validateEmail + + + $this->validateEmail + + + + + $result + + + $socialAccount + + + \Cake\Datasource\EntityInterface + + + $this->sendSocialValidationEmail($socialAccount, $socialAccount->user) + + + \CakeDC\Users\Model\Entity\User + + + $this->_activateAccount($socialAccount) + + + \CakeDC\Users\Model\Entity\User + + + $socialAccount->token + $socialAccount->active + $socialAccount->active + $socialAccount->user + + + $user + $socialAccount + + + + + $tokenExpiration + + + $existingAccount->user + + + $existingUser + + + $useEmail + $validateEmail + $tokenExpiration + $userData['username'] ?? null + $userData['username'] ?? null + $accountData['avatar'] + + + $userData + $userData + + + SocialBehavior + + + $this->_table->isValidateEmail + + + + + $tos + + + bool + + + + + SocialAccountsTable + + + + + \CakeDC\Users\Model\Entity\User|bool + + + UsersTable + + + + + Plugin + + + + + Shell + parent::getOptionParser() + parent::initialize() + + + $user->username + $user->username + + + $this->_updateUser($username, $data) + + + bool + + + $error + $field + $value + $field + $value + $field + + + $user->id + + + $savedUser->role + + + UsersShell + + + $this->Users + + + + + $title + + + AuthLinkHelper + + + + + $options['options'] + $options['options'] + + + + + $this->name + + + + + \Cake\Mailer\Mailer + + + + + $indexed + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 000000000..3bcbd74e4 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/resources/locales/ar_OM/users.mo b/resources/locales/ar_OM/users.mo new file mode 100644 index 000000000..e0114d118 Binary files /dev/null and b/resources/locales/ar_OM/users.mo differ diff --git a/resources/locales/ar_OM/users.po b/resources/locales/ar_OM/users.po new file mode 100644 index 000000000..72becda4f --- /dev/null +++ b/resources/locales/ar_OM/users.po @@ -0,0 +1,807 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 12:52-0200\n" +"Language-Team: CakeDC \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: ar_OM\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "تم التحقق من صحة الحساب بنجاح" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "تعذر التحقق من صحة الحساب" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "رمز Token غير صالح و/ أو الحساب غير صحيح" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "تم تنشيط الحساب مسبقاً" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "تعذر التحقق من صحة الحساب " + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "تم إرسال البريد الالكتروني بنجاح " + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "تعذر إرسال البريد الكتروني" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "حساب غير صالح" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "لا يمكن ارسال البريد الكتروني " + +#: Controller/Traits/LinkSocialTrait.php:52 +msgid "Could not associate account, please try again." +msgstr "تعذر ربط الحساب ، الرجاء المحاولة مره أخرى." + +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "تم ربط الحساب مسبقاً" + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "لقد قمت بتسجيل الخروج بنجاح" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "الرجاء تمكين أداه مصادقه Google أولا." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "تعذر أضافه المستخدم:" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "تعذر ربط الحساب ، الرجاء المحاولة مره أخرى." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "رمز التحقق غير صالح. حاول مرة أخرى" + +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 +msgid "User was not found" +msgstr "لم يتم العثور علي المستخدم" + +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 +msgid "Password could not be changed" +msgstr "تعذر تغيير كلمه المرور" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "تم تغيير كلمه المرور بنجاح" + +#: Controller/Traits/PasswordManagementTrait.php:137 +msgid "Please check your email to continue with password reset process" +msgstr "" +"يرجى التحقق من البريد الكتروني الخاص بك لمتابعه عمليه أعاده تعيين كلمه المرور" + +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 +msgid "The password token could not be generated. Please try again" +msgstr "تعذر إنشاء الرمز المميز لكلمه المرور. الرجاء المحاولة مره أخرى" + +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "لم يتم العثور علي المستخدم {0}" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "المستخدم غير نشط" + +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 +msgid "Token could not be reset" +msgstr "تعذر أعاده تعيين الرمز المميز" + +#: Controller/Traits/PasswordManagementTrait.php:174 +msgid "Google Authenticator token was successfully reset" +msgstr "تمت أعاده تعيين رمز مصادقه Google بنجاح" + +#: Controller/Traits/ProfileTrait.php:57 +msgid "Not authorized, please login first" +msgstr "غير مصرح به ، الرجاء تسجيل الدخول أولا" + +#: Controller/Traits/RegisterTrait.php:46 +msgid "You must log out to register a new user account" +msgstr "يجب تسجيل الخروج لتسجيل حساب مستخدم جديد" + +#: Controller/Traits/RegisterTrait.php:75;99 +msgid "The user could not be saved" +msgstr "تعذر حفظ المستخدم" + +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "reCaptcha غير صالحه" + +#: Controller/Traits/RegisterTrait.php:133 +msgid "You have registered successfully, please log in" +msgstr "لقد قمت بالتسجيل بنجاح ، الرجاء تسجيل الدخول" + +#: Controller/Traits/RegisterTrait.php:135 +msgid "Please validate your account before log in" +msgstr "الرجاء تنشيط حسابك قبل تسجيل الدخول" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "تم حفظ {0}" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "تعذر حفظ {0}" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "تم حذف {0}" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "تعذر حذف {0}" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "تم التحقق من صحة حساب المستخدم بنجاح" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "تعذر التحقق من صحة حساب المستخدم" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "المستخدم نشط بالفعل" + +#: Controller/Traits/UserValidationTrait.php:55 +msgid "Reset password token was validated successfully" +msgstr "تم التحقق من صحة رمز أعاده تعيين كلمه المرور بنجاح" + +#: Controller/Traits/UserValidationTrait.php:63 +msgid "Reset password token could not be validated" +msgstr "تعذر التحقق من صحة الرمز المميز لأعاده تعيين كلمه المرور" + +#: Controller/Traits/UserValidationTrait.php:67 +msgid "Invalid validation type" +msgstr "وسيلة التحقق غير صالحة" + +#: Controller/Traits/UserValidationTrait.php:70 +msgid "Invalid token or user account already validated" +msgstr "الرمز (token) غير صحيح او تم استخدامه من قبل" + +#: Controller/Traits/UserValidationTrait.php:76 +msgid "Token already expired" +msgstr "الرمز token انتهت صلاحيته " + +#: Controller/Traits/UserValidationTrait.php:106 +msgid "Token has been reset successfully. Please check your email." +msgstr "تم أعاده تعيين token بنجاح. يرجى التحقق من بريدك الكتروني." + +#: Controller/Traits/UserValidationTrait.php:118 +msgid "User {0} is already active" +msgstr "المستخدم {0} نشط بالفعل" + +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "اسم المستخدم أو كلمه المرور غير صحيحه" + +#: Loader/LoginComponentLoader.php:51 +#, fuzzy +msgid "Could not proceed with social account. Please try again" +msgstr "تعذر ربط الحساب ، الرجاء المحاولة مره أخرى." + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"لم يتم التحقق من صحة المستخدم الخاص بك بعد. الرجاء التحقق من البريد الوارد " +"للحصول علي تعليمات" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"لم يتم التحقق من صحة حسابك الاجتماعي بعد. الرجاء التحقق من البريد الوارد " +"للحصول علي تعليمات" + +#: Mailer/UsersMailer.php:33 +msgid "Your account validation link" +msgstr "رابط التحقق من الحسابك" + +#: Mailer/UsersMailer.php:51 +msgid "{0}Your reset password link" +msgstr "{0} رابط أعاده تعيين كلمه المرور" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} رابط التحقق من صحة الحساب" + +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "الرجاء إدخال بريدك الكتروني" + +#: Middleware/SocialAuthMiddleware.php:75 +#, fuzzy +msgid "Could not identify your account, please try again" +msgstr "تعذر ربط الحساب ، الرجاء المحاولة مره أخرى." + +#: Model/Behavior/AuthFinderBehavior.php:48 +msgid "Missing 'username' in options data" +msgstr "لم يتم العثور على اسم المستخدم" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "الحساب مرتبط بالفعل بمستخدم آخر" + +#: Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "لا يمكن ان يكون المرجع فارغا" + +#: Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "لا يمكن ترك حقل انتهاء صلاحية Token فارغاً " + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "لم يتم العثور علي المستخدم" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "تم التحقق من صحة حساب المستخدم" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "المستخدم غير نشط" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "لا تتطابق كلمه المرور الحالية" + +#: Model/Behavior/PasswordBehavior.php:146 +msgid "You cannot use the current password as the new one" +msgstr "لا يمكنك استخدام كلمه المرور الحالية كـ كلمة سر جديدة" + +#: Model/Behavior/RegisterBehavior.php:90 +msgid "User not found for the given token and email." +msgstr "لم يتم العثور على المستخدم لهذا الرمز و البريد الالكتروني " + +#: Model/Behavior/RegisterBehavior.php:93 +msgid "Token has already expired user with no token" +msgstr "الرمز Token انتهت صلاحيته, المستخدم بدون Token الآن " + +#: Model/Behavior/RegisterBehavior.php:151 +#, fuzzy +msgid "This field is required" +msgstr "الحقل: {0} خطا: {1}" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 +msgid "Account already validated" +msgstr "تم التحقق من صحة الحساب" + +#: Model/Behavior/SocialAccountBehavior.php:105;132 +msgid "Account not found for the given token and email." +msgstr "لم يتم العثور على المستخدم لهذا الرمز و البريد الالكتروني " + +#: Model/Behavior/SocialBehavior.php:83 +msgid "Unable to login user with reference {0}" +msgstr "غير قادر علي تسجيل دخول المستخدم مع المرجع {0}" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "البريد الكتروني غير موجود" + +#: Model/Table/UsersTable.php:79 +msgid "Your password does not match your confirm password. Please try again" +msgstr "" +"لا تتطابق كلمه المرور الخاصة بك مع تاكيد كلمه المرور. الرجاء المحاولة مره " +"أخرى" + +#: Model/Table/UsersTable.php:171 +msgid "Username already exists" +msgstr "اسم المستخدم موجود بالفعل" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "البريد الالكتروني موجود بالفعل" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "الأدوات المساعدة لإضافة CakeDC/Users " + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "تنشيط مستخدم معين" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "أضافه مستخدم جديد super admin لأغراض الاختبار" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "أضافه مستخدم جديد" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "تغيير دور مستخدم معين" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "إلغاء تنشيط مستخدم معين" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "حذف مستخدم معين" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "أعاده تعيين كلمه المرور عبر البريد الكتروني" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "أعاده تعيين كلمه المرور لكافة المستخدمين" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "أعاده تعيين كلمه المرور لمستخدم معين" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "الرجاء إدخال كلمة مرور" + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "تغيير كلمه المرور لكافة المستخدمين" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "كلمه المرور الجديدة: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "الرجاء ادخال اسم المستخدم." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "تم تغيير كلمه المرور للمستخدم: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "الرجاء إدخال دور." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "تم تغيير الدور للمستخدم: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "دور جديد: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "تم تنشيط المستخدم: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "تم إلغاء تنشيط المستخدم: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "الرجاء إدخال اسم المستخدم أو البريد الكتروني." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"من فضلك اطلب من المستخدم التحقق من البريد الكتروني لمتابعه عمليه أعاده تعيين " +"كلمه المرور" + +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "تم إضافة SuperUser" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "تم إضافة المستخدم" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "المعرف: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "اسم المستخدم: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "البريد الكتروني: {0}" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "الدور: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "كلمه المرور: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "تعذر أضافه المستخدم:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "الحقل: {0} خطا: {1}" + +#: Shell/UsersShell.php:337 +msgid "The user was not found." +msgstr "لم يتم العثور علي المستخدم." + +#: Shell/UsersShell.php:367 +msgid "The user {0} was not deleted. Please try again" +msgstr "لم يتم حذف المستخدم {0}. الرجاء المحاولة مره أخرى" + +#: Shell/UsersShell.php:369 +msgid "The user {0} was deleted successfully" +msgstr "تم حذف المستخدم {0} بنجاح" + +#: Template/Email/html/reset_password.ctp:21 +#: Template/Email/html/social_account_validation.ctp:14 +#: Template/Email/html/validation.ctp:21 +#: Template/Email/text/reset_password.ctp:20 +#: Template/Email/text/social_account_validation.ctp:22 +#: Template/Email/text/validation.ctp:20 +msgid "Hi {0}" +msgstr "مرحبا {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +msgstr "أعد تعيين كلمه المرور من هنا" + +#: Template/Email/html/reset_password.ctp:27 +#: Template/Email/html/social_account_validation.ctp:32 +#: Template/Email/html/validation.ctp:27 +msgid "" +"If the link is not correctly displayed, please copy the following address in " +"your web browser {0}" +msgstr "" +"إذا لم يتم عرض الرابط بشكل صحيح ، الرجاء نسخ العنوان التالي في مستعرض ويب " +"{0}" + +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 +msgid "Thank you" +msgstr "شكراً لك" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "تفعيل تسجيل الدخول باستخدام شبكات التواصل الاجتماعيا" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "تفعيل حسابك من هنا" + +#: Template/Email/text/reset_password.ctp:22 +#: Template/Email/text/validation.ctp:22 +msgid "Please copy the following address in your web browser {0}" +msgstr "الرجاء نسخ العنوان التالي في مستعرض ويب {0}" + +#: Template/Email/text/social_account_validation.ctp:24 +msgid "" +"Please copy the following address in your web browser to activate your " +"social login {0}" +msgstr "الرجاء نسخ العنوان التالي في متصفح الويب الخاص بك لتنشيط حسابك {0}" + +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 +msgid "Actions" +msgstr "الاجراءات" + +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "قائمة المستخدمين" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "إضافة مستخدم" + +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 +msgid "Username" +msgstr "اسم المستخدم" + +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 +msgid "Email" +msgstr "البريد الالكتروني" + +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "كلمه المرور" + +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 +msgid "First name" +msgstr "الاسم الأول" + +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 +msgid "Last name" +msgstr "الاسم الأخير" + +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 +#: Template/Users/view.ctp:49;74 +msgid "Active" +msgstr "فعال" + +#: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 +#: Template/Users/request_reset_password.ctp:8 +#: Template/Users/resend_token_validation.ctp:20 +#: Template/Users/social_email.ctp:19 +msgid "Submit" +msgstr "إرسال" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "الرجاء إدخال كلمه المرور الجديدة" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "كلمه السر الحالية" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "كلمه السر الجديدة" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "تاكيد كلمه المرور" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "حذف" + +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 +msgid "Are you sure you want to delete # {0}?" +msgstr "هل تريد بالتاكيد حذف # {0} ؟" + +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "تعديل المستخدم" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "الرمز Token" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "انتهاء صلاحيه الرمز Token" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "API token" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "تاريخ التفعيل" + +#: Template/Users/edit.ctp:51 +msgid "TOS date" +msgstr "تاريخ الموافقة على شروط الاستخدام" + +#: Template/Users/edit.ctp:64 +msgid "Reset Google Authenticator Token" +msgstr "أعاده تعيين رمز مصادقه Google" + +#: Template/Users/edit.ctp:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "هل تريد بالتاكيد أعاده تعيين Token للمستخدم \"{0}\" ؟" + +#: Template/Users/index.ctp:15 +msgid "New {0}" +msgstr "{0} جديد" + +#: Template/Users/index.ctp:37 +msgid "View" +msgstr "عرض" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "تغيير كلمة المرور" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "تحرير" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "السابق" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "التالي" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "الرجاء إدخال اسم المستخدم وكلمه المرور" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "تذكرني" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "التسجيل" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "إعادة تعيين كلمة المرور" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "تسجيل الدخول" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "تغيير كلمه المرور" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "حسابات التواصل الاجتماعي" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "الصورة الشخصية" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "المزود" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "رابط" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "الارتباط ب {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "هل تقبل شروط الاستخدام ؟" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "الرجاء إدخال بريدك الكتروني لأعاده تعيين كلمه المرور" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "أعادة تنشيط الحساب عبر البريد الالكتروني" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "البريد الكتروني أو اسم المستخدم" + +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "رمز التحقق" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" " +"تأكيد التحقق" + +#: Template/Users/view.ctp:19 +msgid "Delete User" +msgstr "حذف المستخدم" + +#: Template/Users/view.ctp:24 +msgid "New User" +msgstr "مستخدم جديد" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "المعرف" + +#: Template/Users/view.ctp:37 +msgid "First Name" +msgstr "الاسم الاول" + +#: Template/Users/view.ctp:39 +msgid "Last Name" +msgstr "الاسم الاخير" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "الدور" + +#: Template/Users/view.ctp:45 +#, fuzzy +msgid "Api Token" +msgstr "API token" + +#: Template/Users/view.ctp:53 +msgid "Token Expires" +msgstr "Token Expires" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "تاريخ التفعيل" + +#: Template/Users/view.ctp:57 +#, fuzzy +msgid "Tos Date" +msgstr "تاريخ الموافقة على شروط الاستخدام" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "تاريخ الإتشاء" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "اخر تعديل" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "دخول باستخدام" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "تسجيل الخروج" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "مرحبا ، {0}" + +#: View/Helper/UserHelper.php:151 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "لم يتم ضبط اعدادات reCaptcha! الرجاء إضافة اعدادات Users.reCaptcha.key" + +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "متصل ب {0}" + +#: View/Helper/UserHelper.php:220 +msgid "Connect with {0}" +msgstr "الاتصال ب {0}" diff --git a/resources/locales/de_DE/users.mo b/resources/locales/de_DE/users.mo new file mode 100644 index 000000000..8408ca51f Binary files /dev/null and b/resources/locales/de_DE/users.mo differ diff --git a/resources/locales/de_DE/users.po b/resources/locales/de_DE/users.po new file mode 100644 index 000000000..7e9a16bce --- /dev/null +++ b/resources/locales/de_DE/users.po @@ -0,0 +1,941 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2022-03-11 13:46+0100\n" +"PO-Revision-Date: 2022-03-11 14:19+0100\n" +"Last-Translator: \n" +"Language-Team: LANGUAGE \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.0.1\n" + +#: src/Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Das Konto wurde erfolgreich bestätigt" + +#: src/Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Das Konto konnte nicht bestätigt werden" + +#: src/Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Ungültiger Token und/oder Social Account" + +#: src/Controller/SocialAccountsController.php:57 +#: src/Controller/SocialAccountsController.php:85 +msgid "Social Account already active" +msgstr "Social Account ist bereits aktiviert" + +#: src/Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Social Account konnte nicht bestätigt werden" + +#: src/Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "Email wurde erfolgreich versendet" + +#: src/Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Email konnte nicht versendet werden" + +#: src/Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Ungültiges Konto" + +#: src/Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Email konnte nicht versendet werden" + +#: src/Controller/Traits/LinkSocialTrait.php:56 +msgid "Could not associate account, please try again." +msgstr "" +"Wir konnten das Konto leider nicht verbinden, bitte versuchen Sie es erneut." + +#: src/Controller/Traits/LinkSocialTrait.php:80 +msgid "Social account was associated." +msgstr "Social Account wurde verbunden." + +#: src/Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Sie wurden erfolgreich abgemeldet" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:75 +msgid "Please enable Google Authenticator first." +msgstr "Bitte aktivieren Sie zuerst den Google Authenticator." + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:90 +msgid "Could not find user data" +msgstr "Es konnten keine Benutzer-Daten gefunden werden" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:129 +msgid "Could not verify, please try again" +msgstr "Überprüfung ist fehlgeschlagen, bitte versuchen Sie es erneut" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:161 +msgid "Verification code is invalid. Try again" +msgstr "Überprüfungs-Code ist ungültig. Bitte versuchen Sie erneut" + +#: src/Controller/Traits/PasswordManagementTrait.php:63 +msgid "Changing another user's password is not allowed" +msgstr "Sie dürfen nicht das Passwort von einem anderen Benutzer ändern" + +#: src/Controller/Traits/PasswordManagementTrait.php:77 +#: src/Controller/Traits/PasswordManagementTrait.php:121 +#: src/Controller/Traits/ProfileTrait.php:54 +msgid "User was not found" +msgstr "Der Benutzer konnte nicht gefunden werden" + +#: src/Controller/Traits/PasswordManagementTrait.php:105 +#: src/Controller/Traits/PasswordManagementTrait.php:117 +#: src/Controller/Traits/PasswordManagementTrait.php:125 +msgid "Password could not be changed" +msgstr "Das Passwort konnte nicht geändert werden" + +#: src/Controller/Traits/PasswordManagementTrait.php:113 +msgid "Password has been changed successfully" +msgstr "Das Passwort wurde erfolgreich geändert" + +#: src/Controller/Traits/PasswordManagementTrait.php:167 +msgid "Please check your email to continue with password reset process" +msgstr "" +"Sie erhalten in Kürze eine Email mit Anweisungen wie Sie das Passwort " +"zurücksetzen können" + +#: src/Controller/Traits/PasswordManagementTrait.php:170 +#: src/Shell/UsersShell.php:286 +msgid "The password token could not be generated. Please try again" +msgstr "" +"Der Token für das Zurücksetzen des Passworts konnte nicht generiert werden. " +"Bitte versuchen Sie es erneut" + +#: src/Controller/Traits/PasswordManagementTrait.php:176 +#: src/Controller/Traits/UserValidationTrait.php:124 +msgid "User {0} was not found" +msgstr "Benutzer {0} konnte nicht gefunden werden" + +#: src/Controller/Traits/PasswordManagementTrait.php:178 +msgid "The user is not active" +msgstr "Der Benutzer ist inaktiv" + +#: src/Controller/Traits/PasswordManagementTrait.php:180 +#: src/Controller/Traits/UserValidationTrait.php:119 +#: src/Controller/Traits/UserValidationTrait.php:128 +msgid "Token could not be reset" +msgstr "Der Token konnte nicht zurückgesetzt werden" + +#: src/Controller/Traits/PasswordManagementTrait.php:204 +msgid "Google Authenticator token was successfully reset" +msgstr "Der Google Authenticator Token wurde erfolgreich zurückgesetzt" + +#: src/Controller/Traits/PasswordManagementTrait.php:207 +msgid "Could not reset Google Authenticator" +msgstr "Google Authenticator Token konnte nicht zurückgesetzt werden" + +#: src/Controller/Traits/ProfileTrait.php:58 +msgid "Not authorized, please login first" +msgstr "" +"Sie sind nicht berechtigt diese Seite aufzurufen. Bitte melden Sie sich " +"zuerst an" + +#: src/Controller/Traits/RegisterTrait.php:47 +msgid "You must log out to register a new user account" +msgstr "Sie müssen sich ausloggen um ein neues Konto zu erstellen" + +#: src/Controller/Traits/RegisterTrait.php:86 +#: src/Controller/Traits/RegisterTrait.php:117 +msgid "The user could not be saved" +msgstr "Der Benutzer konnte nicht gespeichert werden" + +#: src/Controller/Traits/RegisterTrait.php:103 +#: src/Loader/LoginComponentLoader.php:33 +msgid "Invalid reCaptcha" +msgstr "Ungültiger reCaptcha" + +#: src/Controller/Traits/RegisterTrait.php:151 +msgid "You have registered successfully, please log in" +msgstr "Sie haben sich erfolgreich registriert. Bitte melden Sie sich an" + +#: src/Controller/Traits/RegisterTrait.php:153 +msgid "Please validate your account before log in" +msgstr "Bitte bestätigen Sie ihr Konto bevor Sie sich anmelden" + +#: src/Controller/Traits/SimpleCrudTrait.php:77 +#: src/Controller/Traits/SimpleCrudTrait.php:107 +msgid "The {0} has been saved" +msgstr "Der {0} wurde gespeichert" + +#: src/Controller/Traits/SimpleCrudTrait.php:81 +#: src/Controller/Traits/SimpleCrudTrait.php:111 +msgid "The {0} could not be saved" +msgstr "Der {0} konnte nicht gespeichert werden" + +#: src/Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "Der {0} wurde gelöscht" + +#: src/Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "Der {0} konnte nicht gelöscht werden" + +#: src/Controller/Traits/U2fTrait.php:216 +msgid "U2F requires SSL." +msgstr "U2F setzt SSL voraus." + +#: src/Controller/Traits/UserValidationTrait.php:49 +msgid "User account validated successfully" +msgstr "Sie haben Ihren Benutzer erfolgreich bestätigt" + +#: src/Controller/Traits/UserValidationTrait.php:51 +msgid "User account could not be validated" +msgstr "Ihr Benutzer konnte nicht bestätigt werden" + +#: src/Controller/Traits/UserValidationTrait.php:54 +msgid "User already active" +msgstr "Dieser Benutzer ist bereits aktiv" + +#: src/Controller/Traits/UserValidationTrait.php:60 +msgid "Reset password token was validated successfully" +msgstr "Der angegebenen Token für das Zurücksetzen des Passworts ist gültig" + +#: src/Controller/Traits/UserValidationTrait.php:68 +msgid "Reset password token could not be validated" +msgstr "Der angegebenen Token für das Zurücksetzen des Passworts ist unültig" + +#: src/Controller/Traits/UserValidationTrait.php:72 +msgid "Invalid validation type" +msgstr "Ungültiger Validierungstyp" + +#: src/Controller/Traits/UserValidationTrait.php:75 +msgid "Invalid token or user account already validated" +msgstr "Ungültiger Token oder Benutzer ist bereits validiert" + +#: src/Controller/Traits/UserValidationTrait.php:81 +msgid "Token already expired" +msgstr "Token ist bereits abgelaufen" + +#: src/Controller/Traits/UserValidationTrait.php:114 +msgid "Token has been reset successfully. Please check your email." +msgstr "" +"Der Token wurde erfolgreich zurückgesetzt. Bitte überprüfen Sie ihren " +"Posteingang." + +#: src/Controller/Traits/UserValidationTrait.php:126 +msgid "User {0} is already active" +msgstr "Benutzer {0} ist bereits aktiv" + +#: src/Controller/Traits/Webauthn2faTrait.php:53 +#: src/Controller/Traits/Webauthn2faTrait.php:73 +msgid "User already has configured webauthn2fa" +msgstr "Dieser Benutzer hat bereis Webauthn2fa aktiv" + +#: src/Controller/Traits/Webauthn2faTrait.php:77 +#: src/Controller/Traits/Webauthn2faTrait.php:122 +msgid "Register error with webauthn for user id: {0}" +msgstr "" +"Es trat ein Fehler bei der Registrierung des Webauthn2fa für die Benutzer " +"ID: {0} auf" + +#: src/Loader/AuthenticationServiceLoader.php:109 +msgid "Property {0}.className should be defined" +msgstr "Property {0}.className sollte definiert sein" + +#: src/Loader/LoginComponentLoader.php:31 +msgid "Username or password is incorrect" +msgstr "Benutzername oder Passwort ist nicht korrekt" + +#: src/Loader/LoginComponentLoader.php:52 +msgid "Could not proceed with social account. Please try again" +msgstr "" +"Es konnte keine Verbindung zum Social Account hergestellt werden. Bitte " +"versuchen Sie es erneut" + +#: src/Loader/LoginComponentLoader.php:54 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Ihr Benutzer wurde noch nicht bestätigt. Bitte überprüfen Sie ihren " +"Posteingang für weitere Anweisungen" + +#: src/Loader/LoginComponentLoader.php:58 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Ihr Social Account wurde noch nicht bestätigt. Bitte überprüfen Sie ihren " +"Posteingang für weitere Anweisungen" + +#: src/Mailer/UsersMailer.php:36 +msgid "Your account validation link" +msgstr "Ihr Konto Validierungs-Link" + +#: src/Mailer/UsersMailer.php:63 +msgid "{0}Your reset password link" +msgstr "{0}Ihr Passwort Zurücksetzten Link" + +#: src/Mailer/UsersMailer.php:95 +msgid "{0}Your social account validation link" +msgstr "{0}Ihr Social Account Bestätigung Link" + +#: src/Middleware/SocialAuthMiddleware.php:47 +#: templates/Users/social_email.php:16 +msgid "Please enter your email" +msgstr "Bitte geben Sie ihre E-Mail Adresse ein" + +#: src/Middleware/SocialAuthMiddleware.php:57 +msgid "Could not identify your account, please try again" +msgstr "" +"Wir konnten Ihr Konto nicht identifizieren. Bitte versuchen Sie es erneut" + +#: src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php:112 +msgid "You are not authorized to access that location." +msgstr "Sie sind nicht berechtigt diese Seite aufzurufen." + +#: src/Model/Behavior/AuthFinderBehavior.php:49 +msgid "Missing 'username' in options data" +msgstr "Fehlender Parameter ‘username’ in angegeben Optionen" + +#: src/Model/Behavior/LinkSocialBehavior.php:52 +msgid "Social account already associated to another user" +msgstr "Dieser Social Account ist bereits mit einem anderen Benutzer verknüpft" + +#: src/Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "Referenz darf nicht leer sein" + +#: src/Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "Token Ablaufdatum darf nicht leer sein" + +#: src/Model/Behavior/PasswordBehavior.php:56 +#: src/Model/Behavior/PasswordBehavior.php:136 +msgid "User not found" +msgstr "Benutzer konnte nicht gefunden werden" + +#: src/Model/Behavior/PasswordBehavior.php:60 +#: src/Model/Behavior/RegisterBehavior.php:129 +msgid "User account already validated" +msgstr "Benutzer ist bereits bestätigt" + +#: src/Model/Behavior/PasswordBehavior.php:66 +msgid "User not active" +msgstr "Benutzer ist nicht aktiv" + +#: src/Model/Behavior/PasswordBehavior.php:141 +msgid "The current password does not match" +msgstr "Das aktuelle Passwort stimmt nicht überein" + +#: src/Model/Behavior/PasswordBehavior.php:144 +msgid "You cannot use the current password as the new one" +msgstr "Sie dürfen nicht das aktuelle Passwort als neues Passwort verwenden" + +#: src/Model/Behavior/RegisterBehavior.php:107 +msgid "User not found for the given token and email." +msgstr "" +"Es konnte kein Benutzer mit dem angegebenen Token oder der angegebenen Email " +"gefunden werden." + +#: src/Model/Behavior/RegisterBehavior.php:110 +msgid "Token has already expired user with no token" +msgstr "Der Token ist bereits abgelaufen oder der Benutzer hat keinen Token" + +#: src/Model/Behavior/RegisterBehavior.php:167 +msgid "This field is required" +msgstr "Dieses Feld muss ausgefüllt werden" + +#: src/Model/Behavior/SocialAccountBehavior.php:100 +#: src/Model/Behavior/SocialAccountBehavior.php:130 +msgid "Account already validated" +msgstr "Konto ist bereits bestätigt" + +#: src/Model/Behavior/SocialAccountBehavior.php:104 +#: src/Model/Behavior/SocialAccountBehavior.php:135 +msgid "Account not found for the given token and email." +msgstr "" +"Es konnte kein Konto für den angegebenen Token oder die angegebene Email " +"gefunden werden." + +#: src/Model/Behavior/SocialBehavior.php:85 +msgid "Unable to login user with reference {0}" +msgstr "Der Login über die Referenz {0} konnte nicht durchgeführt werden" + +#: src/Model/Behavior/SocialBehavior.php:136 +msgid "Email not present" +msgstr "Email nicht vorhanden" + +#: src/Model/Table/UsersTable.php:107 +msgid "Your password does not match your confirm password. Please try again" +msgstr "Bitte bestätigen Sie ihr Passwort und versuchen Sie es erneut" + +#: src/Model/Table/UsersTable.php:201 +msgid "Username already exists" +msgstr "Benutzername ist bereits vorhanden" + +#: src/Model/Table/UsersTable.php:207 +msgid "Email already exists" +msgstr "Email ist bereits vorhanden" + +#: src/Shell/UsersShell.php:46 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Tools für das CakeDC Users Plugin" + +#: src/Shell/UsersShell.php:48 +msgid "Activate an specific user" +msgstr "Aktiviere einen Benutzer" + +#: src/Shell/UsersShell.php:51 +msgid "Add a new superadmin user for testing purposes" +msgstr "Füge einen neuen Superadmin Benutzer für Testzwecke hinzu" + +#: src/Shell/UsersShell.php:54 +msgid "Add a new user" +msgstr "Füge einen neuen Benutzer hinzu" + +#: src/Shell/UsersShell.php:57 +msgid "Change the role for an specific user" +msgstr "Ändere die Rolle für einen Benutzer" + +#: src/Shell/UsersShell.php:60 +msgid "Change the api token for an specific user" +msgstr "Ändere den API Token für einen Benutzer" + +#: src/Shell/UsersShell.php:63 +msgid "Deactivate an specific user" +msgstr "Deaktiviere einen Benutzer" + +#: src/Shell/UsersShell.php:66 +msgid "Delete an specific user" +msgstr "Lösche einen Benutzer" + +#: src/Shell/UsersShell.php:69 +msgid "Reset the password via email" +msgstr "Sende Passwort-Zurücksetzen E-Mail für eine E-Mail Adresse" + +#: src/Shell/UsersShell.php:72 +msgid "Reset the password for all users" +msgstr "Setze das Passwort für alle Benutzer" + +#: src/Shell/UsersShell.php:75 +msgid "Reset the password for an specific user" +msgstr "Setze das Passwort für einen Benutzer" + +#: src/Shell/UsersShell.php:135 src/Shell/UsersShell.php:161 +msgid "Please enter a password." +msgstr "Bitte geben Sie das Passwort ein." + +#: src/Shell/UsersShell.php:139 +msgid "Password changed for all users" +msgstr "Das Passwort wurde für alle Benutzer geändert" + +#: src/Shell/UsersShell.php:140 src/Shell/UsersShell.php:168 +msgid "New password: {0}" +msgstr "Neues Passwort: {0}" + +#: src/Shell/UsersShell.php:158 src/Shell/UsersShell.php:186 +#: src/Shell/UsersShell.php:214 src/Shell/UsersShell.php:304 +#: src/Shell/UsersShell.php:403 +msgid "Please enter a username." +msgstr "Bitte geben Sie einen Benutzernamen ein." + +#: src/Shell/UsersShell.php:167 +msgid "Password changed for user: {0}" +msgstr "Das Passwort wurde für den Benutzer: {0} geändert" + +#: src/Shell/UsersShell.php:189 +msgid "Please enter a role." +msgstr "Bitte geben Sie eine Rolle ein." + +#: src/Shell/UsersShell.php:195 +msgid "Role changed for user: {0}" +msgstr "Die Rolle wurde für den Benutzer: {0} geändert" + +#: src/Shell/UsersShell.php:196 +msgid "New role: {0}" +msgstr "Neue Rolle: {0}" + +#: src/Shell/UsersShell.php:217 +msgid "Please enter a token." +msgstr "Bitte geben Sie einen Token ein." + +#: src/Shell/UsersShell.php:224 +msgid "User was not saved, check validation errors" +msgstr "" +"Der Benutzer konnte nicht gespeichert werden. Bitte überprüfe die " +"Validierungsfehlermeldungen" + +#: src/Shell/UsersShell.php:229 +msgid "Api token changed for user: {0}" +msgstr "API Token für Benutzer: {0} wurde geändert" + +#: src/Shell/UsersShell.php:230 +msgid "New token: {0}" +msgstr "Neuer Token: {0}" + +#: src/Shell/UsersShell.php:245 +msgid "User was activated: {0}" +msgstr "Benutzer wurde aktiviert: {0}" + +#: src/Shell/UsersShell.php:260 +msgid "User was de-activated: {0}" +msgstr "Benutzer wurde deaktiviert: {0}" + +#: src/Shell/UsersShell.php:272 +msgid "Please enter a username or email." +msgstr "Bitte geben Sie einen Benutzername oder eine Email ein." + +#: src/Shell/UsersShell.php:280 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Bitte Fragen Sie den Benutzer den Posteingang zu überprüfen für die weitere " +"Vorgehensweise" + +#: src/Shell/UsersShell.php:350 +msgid "Superuser added:" +msgstr "Superuser hinzugefügt:" + +#: src/Shell/UsersShell.php:352 +msgid "User added:" +msgstr "Benutzer hinzugefügt:" + +#: src/Shell/UsersShell.php:354 +msgid "Id: {0}" +msgstr "ID: {0}" + +#: src/Shell/UsersShell.php:355 +msgid "Username: {0}" +msgstr "Benutzername: {0}" + +#: src/Shell/UsersShell.php:356 +msgid "Email: {0}" +msgstr "Email: {0}" + +#: src/Shell/UsersShell.php:357 +msgid "Role: {0}" +msgstr "Rolle: {0}" + +#: src/Shell/UsersShell.php:358 +msgid "Password: {0}" +msgstr "Passwort: {0}" + +#: src/Shell/UsersShell.php:360 +msgid "User could not be added:" +msgstr "Benutzer konnte nicht hinzugefügt werden:" + +#: src/Shell/UsersShell.php:363 +msgid "Field: {0} Error: {1}" +msgstr "Feld: {0} Fehler: {0}" + +#: src/Shell/UsersShell.php:379 +msgid "The user was not found." +msgstr "Der Benutzer konnte nicht gefunden werden." + +#: src/Shell/UsersShell.php:414 +msgid "The user {0} was not deleted. Please try again" +msgstr "" +"Der Benutzer {0} konnte nicht gelöscht werden. Bitte versuchen Sie es erneut" + +#: src/Shell/UsersShell.php:416 +msgid "The user {0} was deleted successfully" +msgstr "Der Benutzer {0} wurde gelöscht" + +#: src/View/Helper/UserHelper.php:50 +msgid "Sign in with" +msgstr "Anmelden mit" + +#: src/View/Helper/UserHelper.php:113 +msgid "Logout" +msgstr "Abmelden" + +#: src/View/Helper/UserHelper.php:134 +msgid "Welcome, {0}" +msgstr "Willkommen, {0}" + +#: src/View/Helper/UserHelper.php:165 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "" +"reCaptcha ist nicht konfiguriert! Bitte setzten Sie Users.reCaptcha.key" + +#: src/View/Helper/UserHelper.php:218 +msgid "Connected with {0}" +msgstr "Mit {0} verbunden" + +#: src/View/Helper/UserHelper.php:223 +msgid "Connect with {0}" +msgstr "Mit {0} verbinden" + +#: templates/Users/add.php:13 templates/Users/edit.php:17 +#: templates/Users/index.php:13 templates/Users/index.php:26 +#: templates/Users/view.php:15 +msgid "Actions" +msgstr "Aktionen" + +#: templates/Users/add.php:15 templates/Users/edit.php:28 +#: templates/Users/view.php:23 +msgid "List Users" +msgstr "Benutzer auflisten" + +#: templates/Users/add.php:21 templates/Users/register.php:18 +msgid "Add User" +msgstr "Benutzer hinzufügen" + +#: templates/Users/add.php:23 templates/Users/edit.php:36 +#: templates/Users/index.php:22 templates/Users/login.php:20 +#: templates/Users/profile.php:30 templates/Users/register.php:20 +#: templates/Users/view.php:33 +msgid "Username" +msgstr "Benutzername" + +#: templates/Users/add.php:24 templates/Users/edit.php:37 +#: templates/Users/index.php:23 templates/Users/profile.php:32 +#: templates/Users/register.php:21 templates/Users/view.php:35 +msgid "Email" +msgstr "Email" + +#: templates/Users/add.php:25 templates/Users/login.php:21 +#: templates/Users/register.php:22 +msgid "Password" +msgstr "Passwort" + +#: templates/Users/add.php:26 templates/Users/edit.php:38 +#: templates/Users/index.php:24 templates/Users/register.php:28 +msgid "First name" +msgstr "Vorname" + +#: templates/Users/add.php:27 templates/Users/edit.php:39 +#: templates/Users/index.php:25 templates/Users/register.php:29 +msgid "Last name" +msgstr "Nachname" + +#: templates/Users/add.php:30 templates/Users/edit.php:54 +#: templates/Users/view.php:49 templates/Users/view.php:74 +msgid "Active" +msgstr "Aktiv" + +#: templates/Users/add.php:34 templates/Users/change_password.php:25 +#: templates/Users/edit.php:58 templates/Users/register.php:38 +#: templates/Users/request_reset_password.php:23 +#: templates/Users/resend_token_validation.php:20 +#: templates/Users/social_email.php:19 +msgid "Submit" +msgstr "Absenden" + +#: templates/Users/change_password.php:5 +msgid "Please enter the new password" +msgstr "Bitte geben Sie ihr neues Passwort ein" + +#: templates/Users/change_password.php:10 +msgid "Current password" +msgstr "Aktuelles Passwort" + +#: templates/Users/change_password.php:16 +msgid "New password" +msgstr "Neues Passwort" + +#: templates/Users/change_password.php:21 templates/Users/register.php:26 +msgid "Confirm password" +msgstr "Neues Passwort bestätigen" + +#: templates/Users/edit.php:22 templates/Users/index.php:40 +msgid "Delete" +msgstr "Löschen" + +#: templates/Users/edit.php:24 templates/Users/index.php:40 +#: templates/Users/view.php:21 +msgid "Are you sure you want to delete # {0}?" +msgstr "Sind Sie sicher, dass Sie # {0} löschen wollen?" + +#: templates/Users/edit.php:34 templates/Users/view.php:17 +msgid "Edit User" +msgstr "Benutzer bearbeiten" + +#: templates/Users/edit.php:40 templates/Users/view.php:43 +msgid "Token" +msgstr "Token" + +#: templates/Users/edit.php:42 +msgid "Token expires" +msgstr "Token Ablaufdatum" + +#: templates/Users/edit.php:45 +msgid "API token" +msgstr "API Token" + +#: templates/Users/edit.php:48 +msgid "Activation date" +msgstr "Aktivierungsdatum" + +#: templates/Users/edit.php:51 +msgid "TOS date" +msgstr "AGB Datum" + +#: templates/Users/edit.php:64 +msgid "Reset Google Authenticator Token" +msgstr "Google Authenticator Token zurücksetzen" + +#: templates/Users/edit.php:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" +"Sind Sie sicher, dass Sie den Token für den Benutzer # {0} zurücksetzen " +"wollen?" + +#: templates/Users/index.php:15 +msgid "New {0}" +msgstr "Neuer {0}" + +#: templates/Users/index.php:37 +msgid "View" +msgstr "Ansicht" + +#: templates/Users/index.php:38 +msgid "Change password" +msgstr "Passwort ändern" + +#: templates/Users/index.php:39 +msgid "Edit" +msgstr "Bearbeiten" + +#: templates/Users/index.php:49 +msgid "previous" +msgstr "vorherig" + +#: templates/Users/index.php:51 +msgid "next" +msgstr "nächste" + +#: templates/Users/login.php:19 +msgid "Please enter your username and password" +msgstr "Bitte geben Sie ihren Benutzernamen und Passwort ein" + +#: templates/Users/login.php:29 +msgid "Remember me" +msgstr "Angemeldet bleiben" + +#: templates/Users/login.php:37 +msgid "Register" +msgstr "Registrieren" + +#: templates/Users/login.php:43 +msgid "Reset Password" +msgstr "Passwort zurücksetzen" + +#: templates/Users/login.php:48 +msgid "Login" +msgstr "Anmelden" + +#: templates/Users/profile.php:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: templates/Users/profile.php:27 +msgid "Change Password" +msgstr "Passwort ändern" + +#: templates/Users/profile.php:38 templates/Users/view.php:68 +msgid "Social Accounts" +msgstr "Social Accounts" + +#: templates/Users/profile.php:42 templates/Users/view.php:73 +msgid "Avatar" +msgstr "Avatar" + +#: templates/Users/profile.php:43 templates/Users/view.php:72 +msgid "Provider" +msgstr "Provider" + +#: templates/Users/profile.php:44 +msgid "Link" +msgstr "Link" + +#: templates/Users/profile.php:51 +msgid "Link to {0}" +msgstr "Link zu {0}" + +#: templates/Users/register.php:31 +msgid "Accept TOS conditions?" +msgstr "AGBs akzeptieren?" + +#: templates/Users/request_reset_password.php:20 +msgid "Please enter your email or username to reset your password" +msgstr "" +"Bitte geben Sie ihre Email Adresse oder Benutzernamen ein um ihr Passwort " +"zurückzusetzen" + +#: templates/Users/resend_token_validation.php:15 +msgid "Resend Validation email" +msgstr "Validierungs-Email erneut versenden" + +#: templates/Users/resend_token_validation.php:17 +msgid "Email or username" +msgstr "Email oder Benutzername" + +#: templates/Users/u2f_authenticate.php:22 templates/Users/webauthn2fa.php:25 +msgid "Verify your registered yubico key" +msgstr "Bestätige deinen registrierten Yubico Key" + +#: templates/Users/u2f_authenticate.php:23 templates/Users/u2f_register.php:23 +#: templates/Users/webauthn2fa.php:20 templates/Users/webauthn2fa.php:26 +msgid "Please insert and tap your yubico key" +msgstr "Bitte Yubico Key anstecken und antippen" + +#: templates/Users/u2f_authenticate.php:24 templates/Users/webauthn2fa.php:27 +msgid "" +"You can now finish the authentication process using the registered device." +msgstr "" +"Sie können nun den Authentifizierungs-Prozess mit dem registrierten Gerät " +"fertigstellen." + +#: templates/Users/u2f_authenticate.php:25 templates/Users/webauthn2fa.php:22 +#: templates/Users/webauthn2fa.php:28 +msgid "" +"When the YubiKey starts blinking, press the golden disc to activate it. " +"Depending on the web browser you might need to confirm the use of extended " +"information from the YubiKey." +msgstr "" +"Wenn der YubiKey anfängt zu blinken drücken Sie bitte die goldene Diskette " +"um ihn zu aktivieren. Abhängig vom Web-Browser könnten Berechtigungs-Popups " +"erscheinen um den YubiKey zu verwenden." + +#: templates/Users/u2f_authenticate.php:27 templates/Users/u2f_register.php:27 +#: templates/Users/webauthn2fa.php:31 +msgid "Reload" +msgstr "Neu laden" + +#: templates/Users/u2f_authenticate.php:51 templates/Users/u2f_register.php:56 +msgid "Yubico key check has failed, please try again" +msgstr "Yubico Key Überprüfung fehlgeschlagen. Bitte versuchen Sie es erneut" + +#: templates/Users/u2f_register.php:22 templates/Users/webauthn2fa.php:19 +msgid "Registering your yubico key" +msgstr "Yubico Key registrieren" + +#: templates/Users/verify.php:13 +msgid "Verification Code" +msgstr "Bestätigungscode" + +#: templates/Users/verify.php:15 +msgid "" +" " +"Verify" +msgstr "" +" " +"Bestätigen" + +#: templates/Users/view.php:19 +msgid "Delete User" +msgstr "Benutzer löschen" + +#: templates/Users/view.php:24 +msgid "New User" +msgstr "Benutzer hinzufügen" + +#: templates/Users/view.php:31 +msgid "Id" +msgstr "ID" + +#: templates/Users/view.php:37 +msgid "First Name" +msgstr "Vorname" + +#: templates/Users/view.php:39 +msgid "Last Name" +msgstr "Nachname" + +#: templates/Users/view.php:41 +msgid "Role" +msgstr "Rolle" + +#: templates/Users/view.php:45 +msgid "Api Token" +msgstr "API Token" + +#: templates/Users/view.php:53 +msgid "Token Expires" +msgstr "Token Ablaufdatum" + +#: templates/Users/view.php:55 +msgid "Activation Date" +msgstr "Aktivierungsdatum" + +#: templates/Users/view.php:57 +msgid "Tos Date" +msgstr "AGB Datum" + +#: templates/Users/view.php:59 templates/Users/view.php:75 +msgid "Created" +msgstr "erstellt am" + +#: templates/Users/view.php:61 templates/Users/view.php:76 +msgid "Modified" +msgstr "zuletzt aktualisiert" + +#: templates/Users/webauthn2fa.php:8 +msgid "Two-factor authentication" +msgstr "Two-Factor Authentifizierung" + +#: templates/Users/webauthn2fa.php:21 +msgid "" +"In order to enable your YubiKey the first step is to perform a registration." +msgstr "" +"Damit Sie ihren YubiKey verwenden können müssen Sie diesen zuerst " +"registrieren." + +#: templates/email/html/reset_password.php:14 +#: templates/email/html/social_account_validation.php:14 +#: templates/email/html/validation.php:13 +#: templates/email/text/reset_password.php:12 +#: templates/email/text/social_account_validation.php:12 +#: templates/email/text/validation.php:12 +msgid "Hi {0}" +msgstr "Hallo {0}" + +#: templates/email/html/reset_password.php:17 +msgid "Reset your password here" +msgstr "Passwort jetzt zurücksetzen" + +#: templates/email/html/reset_password.php:20 +#: templates/email/html/social_account_validation.php:23 +#: templates/email/html/validation.php:19 +msgid "" +"If the link is not correctly displayed, please copy the following address in " +"your web browser {0}" +msgstr "" +"Falls der Link nicht korrekt dargestellt wird kopieren Sie bitte die " +"folgende Adresse in ihren Web Browser {0}" + +#: templates/email/html/reset_password.php:27 +#: templates/email/html/social_account_validation.php:30 +#: templates/email/html/validation.php:26 +#: templates/email/text/reset_password.php:20 +#: templates/email/text/social_account_validation.php:20 +#: templates/email/text/validation.php:20 +msgid "Thank you" +msgstr "Vielen Dank" + +#: templates/email/html/social_account_validation.php:18 +msgid "Activate your social login here" +msgstr "Social Login aktivieren" + +#: templates/email/html/validation.php:16 +msgid "Activate your account here" +msgstr "Konto aktivieren" + +#: templates/email/text/reset_password.php:14 +#: templates/email/text/validation.php:14 +msgid "Please copy the following address in your web browser {0}" +msgstr "Bitte kopieren Sie die folgende Adresse in Ihren Web Browser {0}" + +#: templates/email/text/social_account_validation.php:14 +msgid "" +"Please copy the following address in your web browser to activate your " +"social login {0}" +msgstr "" +"Bitte kopieren Sie die folgende Adresse in Ihren Web Browser um den Social " +"Login zu aktivieren {0}" diff --git a/resources/locales/es/users.mo b/resources/locales/es/users.mo new file mode 100644 index 000000000..9487e26de Binary files /dev/null and b/resources/locales/es/users.mo differ diff --git a/src/Locale/es/Users.po b/resources/locales/es/users.po similarity index 63% rename from src/Locale/es/Users.po rename to resources/locales/es/users.po index 16f4de825..96df31fdd 100644 --- a/src/Locale/es/Users.po +++ b/resources/locales/es/users.po @@ -1,60 +1,24 @@ # LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2015, Cake Development Corporation (http://cakedc.com) +# Copyright YEAR NAME # msgid "" msgstr "" "Project-Id-Version: CakeDC Users\n" -"POT-Creation-Date: 2016-10-26 09:23+0000\n" -"PO-Revision-Date: 2017-05-29 16:50+0100\n" -"Last-Translator: Bernat Arlandis \n" +"POT-Creation-Date: 2017-10-14 23:45+0000\n" +"PO-Revision-Date: 2019-01-10 13:01-0200\n" "Language-Team: CakeDC \n" -"Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.10\n" -"X-Poedit-SourceCharset: UTF-8\n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: es\n" -#: Auth/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "El tipo {0} no es válido" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "El tipo {0} no tiene un invocable asociado" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL requerido para autenticación por ApiKey" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "" -"Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" -"Falta el archivo de configuración: \"config/{0}.php\". Utilizando permisos " -"predeterminados" - -#: Auth/SocialAuthenticate.php:432 +#: Auth/SocialAuthenticate.php:456 msgid "Provider cannot be empty" msgstr "El proveedor no puede ser vacío" -#: Auth/Rules/AbstractRule.php:78 -msgid "" -"Table alias is empty, please define a table alias, we could not extract a " -"default table from the request" -msgstr "" -"El alias de la tabla está vacío, por favor define un alias para la tabla ya " -"que no podemos establecer una tabla predeterminada de la petición" - -#: Auth/Rules/Owner.php:67;70 -msgid "" -"Missing column {0} in table {1} while checking ownership permissions for " -"user {2}" -msgstr "" -"La columna {0} de la tabla {1} no existe, estábamos comprobando el permiso " -"de propietario para el usuario {2}" - #: Controller/SocialAccountsController.php:52 msgid "Account validated successfully" msgstr "Cuenta validada correctamente" @@ -91,25 +55,36 @@ msgstr "Cuenta inválida" msgid "Email could not be resent" msgstr "No se puede reenviar el Email" -#: Controller/Component/RememberMeComponent.php:69 +#: Controller/Component/RememberMeComponent.php:68 msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" msgstr "App salt inválido, debe contener al menos 256 bits (32 bytes)" -#: Controller/Component/UsersAuthComponent.php:178 +#: Controller/Component/UsersAuthComponent.php:204 msgid "You can't enable email validation workflow if use_email is false" msgstr "" "No es posible activar el flujo de trabajo para la validación de email si " "use_mail es falso" -#: Controller/Traits/LoginTrait.php:96 +#: Controller/Traits/LinkSocialTrait.php:54 +#, fuzzy +msgid "Could not associate account, please try again." +msgstr "" +"No se pudo generar el token de contraseña. Por favor, inténtalo de nuevo" + +#: Controller/Traits/LinkSocialTrait.php:77 +#, fuzzy +msgid "Social account was associated." +msgstr "Cuenta social ya activa" + +#: Controller/Traits/LoginTrait.php:104 msgid "Issues trying to log in with your social account" msgstr "Hubo un problema al iniciar sesión con tu cuenta social" -#: Controller/Traits/LoginTrait.php:101 Template/Users/social_email.ctp:16 +#: Controller/Traits/LoginTrait.php:109 Template/Users/social_email.ctp:16 msgid "Please enter your email" msgstr "Por favor, introduce tu email" -#: Controller/Traits/LoginTrait.php:108 +#: Controller/Traits/LoginTrait.php:120 msgid "" "Your user has not been validated yet. Please check your inbox for " "instructions" @@ -117,7 +92,7 @@ msgstr "" "El usuario no se ha validado todavía. Instrucciones enviadas a tu bandeja de " "entrada" -#: Controller/Traits/LoginTrait.php:110 +#: Controller/Traits/LoginTrait.php:125 msgid "" "Your social account has not been validated yet. Please check your inbox for " "instructions" @@ -125,346 +100,331 @@ msgstr "" "La cuenta social no se ha validado todavía. Instrucciones enviadas a tu " "bandeja de entrada" -#: Controller/Traits/LoginTrait.php:161 Controller/Traits/RegisterTrait.php:81 +#: Controller/Traits/LoginTrait.php:180 Controller/Traits/RegisterTrait.php:82 msgid "Invalid reCaptcha" msgstr "El código reCaptcha no es válido" -#: Controller/Traits/LoginTrait.php:171 +#: Controller/Traits/LoginTrait.php:191 msgid "You are already logged in" msgstr "Ya has iniciado sesión" -#: Controller/Traits/LoginTrait.php:217 +#: Controller/Traits/LoginTrait.php:212 +msgid "Please enable Google Authenticator first." +msgstr "" + +#: Controller/Traits/LoginTrait.php:287 +msgid "Verification code is invalid. Try again" +msgstr "" + +#: Controller/Traits/LoginTrait.php:340 msgid "Username or password is incorrect" msgstr "Usuario o contraseña incorrecta" -#: Controller/Traits/LoginTrait.php:238 +#: Controller/Traits/LoginTrait.php:363 msgid "You've successfully logged out" msgstr "Sesión finalizada correctamente" -#: Controller/Traits/PasswordManagementTrait.php:47;76 -#: Controller/Traits/ProfileTrait.php:49 +#: Controller/Traits/PasswordManagementTrait.php:49;82 +#: Controller/Traits/ProfileTrait.php:50 msgid "User was not found" msgstr "Usuario no encontrado" -#: Controller/Traits/PasswordManagementTrait.php:64;72;80 +#: Controller/Traits/PasswordManagementTrait.php:70;78;86 msgid "Password could not be changed" msgstr "No es posible cambiar la contraseña" -#: Controller/Traits/PasswordManagementTrait.php:68 +#: Controller/Traits/PasswordManagementTrait.php:74 msgid "Password has been changed successfully" msgstr "Contraseña cambiada correctamente" -#: Controller/Traits/PasswordManagementTrait.php:78 +#: Controller/Traits/PasswordManagementTrait.php:84 msgid "{0}" msgstr "{0}" -#: Controller/Traits/PasswordManagementTrait.php:120 +#: Controller/Traits/PasswordManagementTrait.php:127 msgid "Please check your email to continue with password reset process" msgstr "" "Por favor, comprueba tu correo para continuar con el proceso de " "restablecimiento de contraseña" -#: Controller/Traits/PasswordManagementTrait.php:123 Shell/UsersShell.php:267 +#: Controller/Traits/PasswordManagementTrait.php:130 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "" "No se pudo generar el token de contraseña. Por favor, inténtalo de nuevo" -#: Controller/Traits/PasswordManagementTrait.php:129 -#: Controller/Traits/UserValidationTrait.php:100 +#: Controller/Traits/PasswordManagementTrait.php:136 +#: Controller/Traits/UserValidationTrait.php:107 msgid "User {0} was not found" msgstr "Usuario {0} no encontrado" -#: Controller/Traits/PasswordManagementTrait.php:131 +#: Controller/Traits/PasswordManagementTrait.php:138 msgid "The user is not active" msgstr "El usuario no está activo" -#: Controller/Traits/PasswordManagementTrait.php:133 -#: Controller/Traits/UserValidationTrait.php:95;104 +#: Controller/Traits/PasswordManagementTrait.php:140 +#: Controller/Traits/UserValidationTrait.php:102;111 msgid "Token could not be reset" msgstr "No se puede restablecer el token" -#: Controller/Traits/ProfileTrait.php:53 +#: Controller/Traits/PasswordManagementTrait.php:164 +#, fuzzy +msgid "Google Authenticator token was successfully reset" +msgstr "Restablecimiento del token de contraseña validado correctamente" + +#: Controller/Traits/ProfileTrait.php:54 msgid "Not authorized, please login first" msgstr "" "No estás autorizado para realizar esta acción, por favor inicia sesión " "primero" -#: Controller/Traits/RegisterTrait.php:42 +#: Controller/Traits/RegisterTrait.php:43 msgid "You must log out to register a new user account" msgstr "Debes finalizar la sesión para registrar una nueva cuenta de usuario" -#: Controller/Traits/RegisterTrait.php:88 +#: Controller/Traits/RegisterTrait.php:89 msgid "The user could not be saved" msgstr "No se ha podido guardar el usuario" -#: Controller/Traits/RegisterTrait.php:122 +#: Controller/Traits/RegisterTrait.php:123 msgid "You have registered successfully, please log in" msgstr "Registrado correctamente, por favor inicia sesión" -#: Controller/Traits/RegisterTrait.php:124 +#: Controller/Traits/RegisterTrait.php:125 msgid "Please validate your account before log in" msgstr "Por favor, valida tu cuenta antes de iniciar sesión" -#: Controller/Traits/SimpleCrudTrait.php:76;106 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "El {0} ha sido guardado" -#: Controller/Traits/SimpleCrudTrait.php:80;110 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "El {0} no ha podido guardarse" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "El {0} ha sido eliminado" -#: Controller/Traits/SimpleCrudTrait.php:132 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "El {0} no ha podido eliminarse" -#: Controller/Traits/SocialTrait.php:39 +#: Controller/Traits/SocialTrait.php:40 msgid "The reCaptcha could not be validated" msgstr "El código reCaptcha no se pudo validar" -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:43 msgid "User account validated successfully" msgstr "Cuenta de usuario validada correctamente" -#: Controller/Traits/UserValidationTrait.php:44 +#: Controller/Traits/UserValidationTrait.php:45 msgid "User account could not be validated" msgstr "No se pudo validar la cuenta de usuario" -#: Controller/Traits/UserValidationTrait.php:47 +#: Controller/Traits/UserValidationTrait.php:48 msgid "User already active" msgstr "Usuario ya activo" -#: Controller/Traits/UserValidationTrait.php:53 +#: Controller/Traits/UserValidationTrait.php:54 msgid "Reset password token was validated successfully" msgstr "Restablecimiento del token de contraseña validado correctamente" -#: Controller/Traits/UserValidationTrait.php:58 +#: Controller/Traits/UserValidationTrait.php:62 msgid "Reset password token could not be validated" msgstr "Restablecimiento del token de contraseña no pudo validarse" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:66 msgid "Invalid validation type" msgstr "Tipo de validación inválido" -#: Controller/Traits/UserValidationTrait.php:65 +#: Controller/Traits/UserValidationTrait.php:69 msgid "Invalid token or user account already validated" msgstr "Token inválido, o tu cuenta ya había sido validada anteriormente" -#: Controller/Traits/UserValidationTrait.php:67 +#: Controller/Traits/UserValidationTrait.php:71 msgid "Token already expired" msgstr "Token ya expirado" -#: Controller/Traits/UserValidationTrait.php:93 +#: Controller/Traits/UserValidationTrait.php:97 msgid "Token has been reset successfully. Please check your email." msgstr "" "Se ha restablecido el token correctamente. Por favor comprueba tu email." -#: Controller/Traits/UserValidationTrait.php:102 +#: Controller/Traits/UserValidationTrait.php:109 msgid "User {0} is already active" msgstr "El usuario {0} ya está activo" -#: Email/EmailSender.php:39 +#: Mailer/UsersMailer.php:34 msgid "Your account validation link" msgstr "Enlace para validar tu cuenta" -#: Mailer/UsersMailer.php:55 +#: Mailer/UsersMailer.php:52 msgid "{0}Your reset password link" msgstr "{0} Enlace para restablecer tu contraseña" -#: Mailer/UsersMailer.php:78 +#: Mailer/UsersMailer.php:75 msgid "{0}Your social account validation link" msgstr "{0} Enlace de validación de tu cuenta social" -#: Model/Behavior/PasswordBehavior.php:56 +#: Model/Behavior/AuthFinderBehavior.php:49 +msgid "Missing 'username' in options data" +msgstr "Falta 'username' en las opciones" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "" + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "La referencia no puede estar vacía" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "La fecha de expiración del Token no puede estar vacía" -#: Model/Behavior/PasswordBehavior.php:67;116 +#: Model/Behavior/PasswordBehavior.php:56;117 msgid "User not found" msgstr "Usuario no encontrado" -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:111 +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112;205 msgid "User account already validated" msgstr "Tu usuario ya se había validado antes" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "El usuario no está activo" -#: Model/Behavior/PasswordBehavior.php:121 +#: Model/Behavior/PasswordBehavior.php:122 msgid "The current password does not match" msgstr "La contraseña actual no coincide" -#: Model/Behavior/PasswordBehavior.php:124 +#: Model/Behavior/PasswordBehavior.php:125 msgid "You cannot use the current password as the new one" msgstr "No puedes usar tu contraseña actual como nueva contraseña" -#: Model/Behavior/RegisterBehavior.php:89 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Usuario no encontrado para el token y email proporcionado" -#: Model/Behavior/RegisterBehavior.php:92 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "El token ha expirado usuario sin token" -#: Model/Behavior/SocialAccountBehavior.php:102;129 +#: Model/Behavior/SocialAccountBehavior.php:103;130 msgid "Account already validated" msgstr "Cuenta ya activada" -#: Model/Behavior/SocialAccountBehavior.php:105;132 +#: Model/Behavior/SocialAccountBehavior.php:106;133 msgid "Account not found for the given token and email." msgstr "Cuenta no encontrada para el token y email proporcionado" -#: Model/Behavior/SocialBehavior.php:56 +#: Model/Behavior/SocialBehavior.php:82 msgid "Unable to login user with reference {0}" msgstr "No se puede iniciar sesión con el usuario con referencia {0}" -#: Model/Behavior/SocialBehavior.php:98 +#: Model/Behavior/SocialBehavior.php:121 msgid "Email not present" msgstr "No se encuentra el email" -#: Model/Table/UsersTable.php:82 +#: Model/Table/UsersTable.php:81 msgid "Your password does not match your confirm password. Please try again" msgstr "" "La contraseña y la comprobación no concuerdan. Por favor inténtalo de nuevo" -#: Model/Table/UsersTable.php:175 +#: Model/Table/UsersTable.php:173 msgid "Username already exists" msgstr "Nombre de usuario ya existente" -#: Model/Table/UsersTable.php:181 +#: Model/Table/UsersTable.php:179 msgid "Email already exists" msgstr "Email ya existente" -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "Falta 'username' en las opciones" - -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Utilidades para CakeDC Users Plugin" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Activar un usuario específico" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "Añadir un nuevo superadmin" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Añadir un nuevo usuario" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" msgstr "Cambiar el rol de un usuario específico" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Desactivar un usuario" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Borrar un usuario" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Restablecer la contraseña vía email" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Restablecer la contraseña de todos los usuarios" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Restablecer la contraseña de un usuario concreto" -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "Usuario añadido:" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "Id: {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "Nombre de usuario: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "Email: {0}" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "Contraseña: {0}" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "Superusuario añadido:" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "No se pudo añadir un superusuario:" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "Campo: {0} Error: {1}" - -#: Shell/UsersShell.php:153;179 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Por favor, introduce una contraseña." -#: Shell/UsersShell.php:157 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Contraseña cambiada para todos los usuarios" -#: Shell/UsersShell.php:158;186 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nueva contraseña: {0}" -#: Shell/UsersShell.php:176;204;282;324 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Por favor introduce el nombre de usuario." -#: Shell/UsersShell.php:185 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Contraseña cambiada para el usuario: {0}" -#: Shell/UsersShell.php:207 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Por favor introduce el rol." -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Rol cambiado para el usuario: {0}" -#: Shell/UsersShell.php:214 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Nuevo rol: {0}" -#: Shell/UsersShell.php:229 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "Usuario activado: {0}" -#: Shell/UsersShell.php:244 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "Usuario desactivado: {0}" -#: Shell/UsersShell.php:256 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Por favor introduce el nombre de usuario o email." -#: Shell/UsersShell.php:264 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" @@ -472,15 +432,53 @@ msgstr "" "Por favor, pide al usuario que mire su buzón de correo para continuar con el " "proceso de restablecimiento de su contraseña" -#: Shell/UsersShell.php:302 +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superusuario añadido:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Usuario añadido:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Nombre de usuario: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Email: {0}" + +#: Shell/UsersShell.php:315 +#, fuzzy +msgid "Role: {0}" +msgstr "Nuevo rol: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Contraseña: {0}" + +#: Shell/UsersShell.php:318 +#, fuzzy +msgid "User could not be added:" +msgstr "No se pudo añadir un superusuario:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Campo: {0} Error: {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "Usuario no encontrado." -#: Shell/UsersShell.php:332 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "El usuario {0} no ha sido borrado. Inténtalo de nuevo por favor" -#: Shell/UsersShell.php:334 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "El usuario {0} se borró correctamente" @@ -500,19 +498,20 @@ msgstr "Restablece tu contraseña aquí" #: Template/Email/html/reset_password.ctp:27 #: Template/Email/html/social_account_validation.ctp:32 #: Template/Email/html/validation.ctp:27 +#, fuzzy msgid "" -"If the link is not correcly displayed, please copy the following address in " +"If the link is not correctly displayed, please copy the following address in " "your web browser {0}" msgstr "" "Por favor, copia la siguiente dirección en tu navegador si el enlace no se " "ve correctamente {0}" -#: Template/Email/html/reset_password.ctp:30 -#: Template/Email/html/social_account_validation.ctp:35 -#: Template/Email/html/validation.ctp:30 -#: Template/Email/text/reset_password.ctp:24 -#: Template/Email/text/social_account_validation.ctp:26 -#: Template/Email/text/validation.ctp:24 +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 msgid "Thank you" msgstr "Gracias" @@ -537,54 +536,53 @@ msgstr "" "Por favor copia la siguiente dirección en tu navegador para activar tu " "acceso social {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:15 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15;79 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:16 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Acciones" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:26 -#: Template/Users/view.ctp:19 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:27 +#: Template/Users/view.ctp:23 msgid "List Users" msgstr "Listar Usuarios" -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "Listar Cuentas" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 msgid "Add User" msgstr "Añadir Usuario" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:27 -#: Template/Users/register.ctp:18 Template/Users/view.ctp:30;70 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:35 +#: Template/Users/index.ctp:22 Template/Users/profile.ctp:30 +#: Template/Users/register.ctp:19 Template/Users/view.ctp:33 msgid "Username" msgstr "Usuario" -#: Template/Users/add.ctp:25 Template/Users/edit.ctp:36 -#: Template/Users/index.ctp:23 Template/Users/profile.ctp:29 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:32 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:20 Template/Users/view.ctp:35 msgid "Email" msgstr "Email" +#: Template/Users/add.ctp:25 Template/Users/register.ctp:21 +msgid "Password" +msgstr "Contraseña" + #: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:25 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:26 msgid "First name" msgstr "Nombre" #: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:26 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:27 msgid "Last name" msgstr "Apellidos" #: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 -#: Template/Users/view.ctp:44;75 +#: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Activo" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:35 +#: Template/Users/edit.ctp:57 Template/Users/register.ctp:36 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -603,17 +601,16 @@ msgstr "Contraseña actual" msgid "New password" msgstr "Nueva contraseña" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:23 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 msgid "Confirm password" msgstr "Confirmar contraseña" -#: Template/Users/edit.ctp:20 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:101 +#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 msgid "Delete" msgstr "Eliminar" -#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:18;101 +#: Template/Users/edit.ctp:23 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "¿Está seguro de que quieres eliminar # {0}?" @@ -621,7 +618,7 @@ msgstr "¿Está seguro de que quieres eliminar # {0}?" msgid "Edit User" msgstr "Editar Usuario" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:38;73 +#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 msgid "Token" msgstr "Token" @@ -641,11 +638,20 @@ msgstr "Fecha de activación" msgid "TOS date" msgstr "Fecha TOS" +#: Template/Users/edit.ctp:63 +#, fuzzy +msgid "Reset Google Authenticator Token" +msgstr "Restablecimiento del token de contraseña validado correctamente" + +#: Template/Users/edit.ctp:69 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Nuevo {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Ver" @@ -653,7 +659,7 @@ msgstr "Ver" msgid "Change password" msgstr "Cambiar contraseña" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:99 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Editar" @@ -685,39 +691,36 @@ msgstr "Cambiar contraseña" msgid "Login" msgstr "Iniciar sesión" -#: Template/Users/profile.ctp:18 View/Helper/UserHelper.php:51 +#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 msgid "{0} {1}" msgstr "{0} {1}" -#: Template/Users/profile.ctp:24 +#: Template/Users/profile.ctp:27 +#, fuzzy msgid "Change Password" msgstr "Cambiar contraseña" -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Cuentas sociales" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:72 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Avatar" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:69 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Proveedor" -#: Template/Users/profile.ctp:40 +#: Template/Users/profile.ctp:44 msgid "Link" msgstr "Enlace" -#: Template/Users/profile.ctp:47 +#: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Enlace a {0}" -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "Contraseña" - -#: Template/Users/register.ctp:28 +#: Template/Users/register.ctp:29 msgid "Accept TOS conditions?" msgstr "¿Aceptas las condiciones del servicios?" @@ -733,122 +736,104 @@ msgstr "Reenviar email de validación" msgid "Email or username" msgstr "Email o usuario" -#: Template/Users/view.ctp:18 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Eliminar Usuario" -#: Template/Users/view.ctp:20 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Nuevo Usuario" -#: Template/Users/view.ctp:28;67 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Id" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Nombre" -#: Template/Users/view.ctp:36 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Apellidos" -#: Template/Users/view.ctp:40 +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nuevo rol: {0}" + +#: Template/Users/view.ctp:45 +#, fuzzy msgid "Api Token" msgstr "Api Token" -#: Template/Users/view.ctp:48;74 +#: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Token caduca" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Fecha de activación" -#: Template/Users/view.ctp:52 +#: Template/Users/view.ctp:57 msgid "Tos Date" msgstr "Fecha Tos" -#: Template/Users/view.ctp:54;77 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Creado" -#: Template/Users/view.ctp:56;78 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Modificado" -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "Cuentas relacionadas" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "Id Usuario" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "Referencia" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "Datos" - -#: View/Helper/UserHelper.php:46 +#: View/Helper/UserHelper.php:45 msgid "Sign in with" msgstr "Iniciar sesión con" -#: View/Helper/UserHelper.php:49 +#: View/Helper/UserHelper.php:48 msgid "fa fa-{0}" msgstr "fa fa-{0}" -#: View/Helper/UserHelper.php:52 +#: View/Helper/UserHelper.php:57 msgid "btn btn-social btn-{0} " msgstr "btn btn-social btn-{0}" -#: View/Helper/UserHelper.php:91 +#: View/Helper/UserHelper.php:106 msgid "Logout" msgstr "Salir" -#: View/Helper/UserHelper.php:108 +#: View/Helper/UserHelper.php:123 msgid "Welcome, {0}" msgstr "Bienvenido/a, {0}" -#: View/Helper/UserHelper.php:131 +#: View/Helper/UserHelper.php:146 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "reCaptcha no se ha configurado, por favor configura Users.reCaptcha.key" -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "Este campo es requerido" - -#~ msgid "The old password does not match" -#~ msgstr "La antigua contraseña no coincide" - -#~ msgid "SocialAccount already active" -#~ msgstr "Cuenta social ya activa" - -#~ msgid "" -#~ "The social account is not active. Please check your email for " -#~ "instructions. {0}" -#~ msgstr "" -#~ "La cuenta social está inactiva. Por favor, compruebe las instrucciones en " -#~ "su correo. {0}" - -#~ msgid "There was an error associating your social network account" -#~ msgstr "Hubo un error al asociar su cuenta de la red social" - -#~ msgid "Invalid token and/or email" -#~ msgstr "Token y/o email inválido" - -#~ msgid "The \"tos\" property is not present" -#~ msgstr "La propiedad \"tos\" no está presente" - -#~ msgid "+ {0} secs" -#~ msgstr "+ {0} secs" +#: View/Helper/UserHelper.php:205 +#, fuzzy +msgid "btn btn-social btn-{0}" +msgstr "btn btn-social btn-{0}" -#~ msgid "Sign in with Facebook" -#~ msgstr "Login con Facebook" +#: View/Helper/UserHelper.php:211 +msgid "Connected with {0}" +msgstr "Conectado con {0}" -#~ msgid "Sign in with Twitter" -#~ msgstr "Login con Twitter" +#: View/Helper/UserHelper.php:216 +msgid "Connect with {0}" +msgstr "Conectar con {0}" diff --git a/resources/locales/fr_FR/users.mo b/resources/locales/fr_FR/users.mo new file mode 100644 index 000000000..7dbb2ec43 Binary files /dev/null and b/resources/locales/fr_FR/users.mo differ diff --git a/src/Locale/fr_FR/Users.po b/resources/locales/fr_FR/users.po similarity index 61% rename from src/Locale/fr_FR/Users.po rename to resources/locales/fr_FR/users.po index 6a88a9bdf..ff98d4585 100644 --- a/src/Locale/fr_FR/Users.po +++ b/resources/locales/fr_FR/users.po @@ -1,322 +1,306 @@ # LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2016, Cake Development Corporation (http://cakedc.com) +# Copyright YEAR NAME # msgid "" msgstr "" "Project-Id-Version: CakeDC Users\n" -"POT-Creation-Date: 2017-03-18 16:49+0100\n" -"PO-Revision-Date: 2017-03-18 16:52+0100\n" -"Last-Translator: Jean Traullé \n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:09-0200\n" "Language-Team: CakeDC \n" -"Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 1.8.11\n" - -#: Auth/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "Le type {0} est invalide" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "Le type {0} n'a aucune fonction de rappel associée" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL est requis pour ApiKey Authentication" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "" -"Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" -"Fichier de configuration manquant : \"config/{0}.php\". Utilisation des " -"permissions par défaut" - -#: Auth/SocialAuthenticate.php:432 -msgid "Provider cannot be empty" -msgstr "Le fournisseur ne peut pas être vide" - -#: Auth/Rules/AbstractRule.php:78 -msgid "" -"Table alias is empty, please define a table alias, we could not extract a " -"default table from the request" -msgstr "" -"L'alias de table est vide, merci de définir un alias de table, nous ne " -"pouvons pas extraire une table par défaut depuis la requête" - -#: Auth/Rules/Owner.php:67;70 -msgid "" -"Missing column {0} in table {1} while checking ownership permissions for " -"user {2}" -msgstr "" -"Colonne {0} manquante dans la table {1} lors de la vérification des " -"autorisations de propriété pour l'utilisateur {2}" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: fr_FR\n" -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Le compte a été validé avec succès" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "Le compte n'a pas pu être validé" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Jeton et/ou compte de réseau social invalide" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Compte de réseau social déjà actif" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "Le compte de réseau social n'a pas pu être validé" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Courriel envoyé avec succès" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "Le courriel n'a pas pu être envoyé" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Compte invalide" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "Le courriel n'a pas pu être réenvoyé" -#: Controller/Component/RememberMeComponent.php:69 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Clé de salage de l'application invalide, la clé de salage de l'application " -"doit-être d'une longueur minimale de 256 bits (32 octets)" - -#: Controller/Component/UsersAuthComponent.php:178 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" -"Vous ne pouvez pas activer le processus de validation par courriel si " -"use_email est défini à false" - -#: Controller/Traits/LoginTrait.php:96 -msgid "Issues trying to log in with your social account" -msgstr "" -"Un problème est survu lors de la tentative d'identification avec votre " -"compte de réseau social" +#: Controller/Traits/LinkSocialTrait.php:52 +#, fuzzy +msgid "Could not associate account, please try again." +msgstr "Le jeton du mot de passe n'a pas pu être généré. Veuillez réessayer" -#: Controller/Traits/LoginTrait.php:101 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Merci d'entrer votre email" +#: Controller/Traits/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Compte de réseau social déjà actif" -#: Controller/Traits/LoginTrait.php:108 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Votre compte n'a pas encore été validé. Merci de vérifier votre boîte de " -"réception pour obtenir les instructions" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Vous avez été correctement déconnecté" -#: Controller/Traits/LoginTrait.php:110 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." msgstr "" -"Votre compte de réseau social n'a pas encore été validé. Merci de vérifier " -"votre boîte de réception pour obtenir les instructions" - -#: Controller/Traits/LoginTrait.php:161 Controller/Traits/RegisterTrait.php:81 -msgid "Invalid reCaptcha" -msgstr "La validation reCaptcha a échoué" -#: Controller/Traits/LoginTrait.php:171 -msgid "You are already logged in" -msgstr "Vous êtes déjà identifié" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "L'utilisateur n'a pas pu être sauvegardé" -#: Controller/Traits/LoginTrait.php:217 -msgid "Username or password is incorrect" -msgstr "Le nom d'utilisateur ou le mot de passe est incorrect" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Le jeton du mot de passe n'a pas pu être généré. Veuillez réessayer" -#: Controller/Traits/LoginTrait.php:238 -msgid "You've successfully logged out" -msgstr "Vous avez été correctement déconnecté" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "" -#: Controller/Traits/PasswordManagementTrait.php:47;76 -#: Controller/Traits/ProfileTrait.php:49 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "L'utilisateur n'a pas été trouvé" -#: Controller/Traits/PasswordManagementTrait.php:64;72;80 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Le mot de passe n'a pas pu être changé" -#: Controller/Traits/PasswordManagementTrait.php:68 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Le mot de passe a été changé avec succès" -#: Controller/Traits/PasswordManagementTrait.php:78 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:120 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "" "Merci de vérifier vos courriels pour poursuivre la procédure de " "réinitialisation du mot de passe" -#: Controller/Traits/PasswordManagementTrait.php:123 Shell/UsersShell.php:267 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "Le jeton du mot de passe n'a pas pu être généré. Veuillez réessayer" -#: Controller/Traits/PasswordManagementTrait.php:129 -#: Controller/Traits/UserValidationTrait.php:100 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "L'utilisateur {0} n'a pas été trouvé" -#: Controller/Traits/PasswordManagementTrait.php:131 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "L'utilisateur n'est pas actif" -#: Controller/Traits/PasswordManagementTrait.php:133 -#: Controller/Traits/UserValidationTrait.php:95;104 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "Le jeton n'a pas pu être réinitialisé" -#: Controller/Traits/ProfileTrait.php:53 +#: Controller/Traits/PasswordManagementTrait.php:174 +#, fuzzy +msgid "Google Authenticator token was successfully reset" +msgstr "Le jeton de réinitialisation du mot de passe a été validé avec succès" + +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Non autorisé, merci de vous identifier d'abord" -#: Controller/Traits/RegisterTrait.php:42 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "" "Vous devez vous déconnecter pour enregistrer un nouveau compte utilisateur" -#: Controller/Traits/RegisterTrait.php:88 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "L'utilisateur n'a pas pu être sauvegardé" -#: Controller/Traits/RegisterTrait.php:122 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "La validation reCaptcha a échoué" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Vous êtes désormais enregistré, merci de vous identifier" -#: Controller/Traits/RegisterTrait.php:124 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Merci de valider votre compte avant de vous identifier" -#: Controller/Traits/SimpleCrudTrait.php:76;106 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "Le {0} a été sauvegardé" -#: Controller/Traits/SimpleCrudTrait.php:80;110 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "Le {0} n'a pas pu être sauvegardé" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "Le {0} a été supprimé" -#: Controller/Traits/SimpleCrudTrait.php:132 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "Le {0} n'a pas pu être supprimé" -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "Le reCaptcha n'a pas pu être validé" - -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Compte utilisateur validé avec succès" -#: Controller/Traits/UserValidationTrait.php:44 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Le compte utilisateur n'a pas pu être validé" -#: Controller/Traits/UserValidationTrait.php:47 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Utilisateur déjà actif" -#: Controller/Traits/UserValidationTrait.php:53 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Le jeton de réinitialisation du mot de passe a été validé avec succès" -#: Controller/Traits/UserValidationTrait.php:58 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "Le jeton de réinitialisation du mot de passe n'a pas pu être validé" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Type de validation invalide" -#: Controller/Traits/UserValidationTrait.php:65 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Jeton invalide ou compte utilisateur déjà activé" -#: Controller/Traits/UserValidationTrait.php:67 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Le jeton a déjà expiré" -#: Controller/Traits/UserValidationTrait.php:93 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "" "Le jeton a été réinitialisé avec succès. Veuillez consulter vos courriels." -#: Controller/Traits/UserValidationTrait.php:102 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "L'utilisateur {0} est déjà actif" -#: Email/EmailSender.php:39 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Le nom d'utilisateur ou le mot de passe est incorrect" + +#: Loader/LoginComponentLoader.php:51 +msgid "Could not proceed with social account. Please try again" +msgstr "" + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Votre compte n'a pas encore été validé. Merci de vérifier votre boîte de " +"réception pour obtenir les instructions" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Votre compte de réseau social n'a pas encore été validé. Merci de vérifier " +"votre boîte de réception pour obtenir les instructions" + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Votre lien de validation de compte" -#: Mailer/UsersMailer.php:55 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0}Votre lien de réinitialisation de mot de passe" -#: Mailer/UsersMailer.php:78 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0}Votre lien de validation de compte social" -#: Model/Behavior/PasswordBehavior.php:56 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Merci d'entrer votre email" + +#: Middleware/SocialAuthMiddleware.php:75 +msgid "Could not identify your account, please try again" +msgstr "" + +#: Model/Behavior/AuthFinderBehavior.php:48 +msgid "Missing 'username' in options data" +msgstr "'username' manquant dans les données d'options" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "" + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "La référence ne peut pas être nulle" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "L'expiration du jeton ne peut pas être vide" -#: Model/Behavior/PasswordBehavior.php:67;116 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Utilisateur non trouvé" -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:111 +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Compte utilisateur déjà validé" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "Utilisateur non activé" -#: Model/Behavior/PasswordBehavior.php:121 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "Le mot de passe actuel ne correspond pas" -#: Model/Behavior/PasswordBehavior.php:124 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "" "Vous ne pouvez pas utiliser le mot de passe actuel comme nouveau mot de passe" -#: Model/Behavior/RegisterBehavior.php:89 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Utilisateur non trouvé pour le jeton et le courriel spécifiés" -#: Model/Behavior/RegisterBehavior.php:92 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "Le jeton a déjà expiré utilisateur sans jeton" +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Ce champ est requis" + #: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "Compte déjà validé" @@ -325,150 +309,114 @@ msgstr "Compte déjà validé" msgid "Account not found for the given token and email." msgstr "Compte non trouvé pour le jeton et le courriel spécifiés" -#: Model/Behavior/SocialBehavior.php:56 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Impossible d'identifier l'utilisateur avec la référence {0}" -#: Model/Behavior/SocialBehavior.php:98 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Courriel non présent" -#: Model/Table/UsersTable.php:82 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "" "Votre mot de passe ne correspond pas à la confirmation de mot de passe. " "Veuillez réessayer" -#: Model/Table/UsersTable.php:175 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Le nom d'utilisateur existe déjà" -#: Model/Table/UsersTable.php:181 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Le courriel existe déjà" -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "'username' manquant dans les données d'options" - -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Utilitaires pour le Plugin CakeDC Users" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Activer un utilisateur spécifique" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "" "Ajouter un nouvel utilisateur super-administrateur pour des besoins de tests" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Ajouter un nouvel utilisateur" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" msgstr "Changer le rôle d'un utilisateur spécifique" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Désactiver un utilisateur spécifique" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Supprimer un utilisateur spécifique" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Réinitialiser le mot de passe par courriel" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Réinitialiser le mot de passe de tous les utilisateurs" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Réinitialiser le mot de passe pour un utilisateur spécifique" -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "Utilisateur ajouté :" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "Identifiant : {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "Nom d'utilisateur : {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "Courriel : {0}" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "Mot de passe : {0}" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "Super-utilisateur ajouté :" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "Le super-utilisateur n'a pas pu être ajouté :" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "Champ : {0} Erreur : {1}" - -#: Shell/UsersShell.php:153;179 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Veuillez saisir un mot de passe." -#: Shell/UsersShell.php:157 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Mot de passe changé pour tous les utilisateurs" -#: Shell/UsersShell.php:158;186 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nouveau mot de passe : {0}" -#: Shell/UsersShell.php:176;204;282;324 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Veuillez saisir un nom d'utilisateur." -#: Shell/UsersShell.php:185 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Mot de passe changé pour l'utilisateur : {0}" -#: Shell/UsersShell.php:207 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Veuillez saisir un rôle." -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Rôle changé pour l'utilisateur : {0}" -#: Shell/UsersShell.php:214 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Nouveau rôle : {0}" -#: Shell/UsersShell.php:229 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "L'utilisateur a été activé : {0}" -#: Shell/UsersShell.php:244 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "L'utilisateur a été désactivé : {0]" -#: Shell/UsersShell.php:256 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Veuillez saisir un nom d'utilisateur ou un courriel." -#: Shell/UsersShell.php:264 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" @@ -476,15 +424,53 @@ msgstr "" "Veuillez demander à l'utilisateur de vérifier ses courriels pour poursuivre " "la procédure de réinitialisation du mot de passe." -#: Shell/UsersShell.php:302 +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Super-utilisateur ajouté :" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Utilisateur ajouté :" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Identifiant : {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Nom d'utilisateur : {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Courriel : {0}" + +#: Shell/UsersShell.php:315 +#, fuzzy +msgid "Role: {0}" +msgstr "Nouveau rôle : {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Mot de passe : {0}" + +#: Shell/UsersShell.php:318 +#, fuzzy +msgid "User could not be added:" +msgstr "Le super-utilisateur n'a pas pu être ajouté :" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Champ : {0} Erreur : {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "L'utilisateur n'a pas été trouvé." -#: Shell/UsersShell.php:332 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "L'utilisateur {0} n'a pas été supprimé. Veuillez réessayer" -#: Shell/UsersShell.php:334 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "L'utilisateur {0} a été supprimé avec succès" @@ -504,19 +490,20 @@ msgstr "Réinitialisez votre mot de passe ici" #: Template/Email/html/reset_password.ctp:27 #: Template/Email/html/social_account_validation.ctp:32 #: Template/Email/html/validation.ctp:27 +#, fuzzy msgid "" -"If the link is not correcly displayed, please copy the following address in " +"If the link is not correctly displayed, please copy the following address in " "your web browser {0}" msgstr "" "Si le lien n'est pas correctement affiché, veuillez copier l'adresse " "suivante dans votre navigateur web {0}" -#: Template/Email/html/reset_password.ctp:30 -#: Template/Email/html/social_account_validation.ctp:35 -#: Template/Email/html/validation.ctp:30 -#: Template/Email/text/reset_password.ctp:24 -#: Template/Email/text/social_account_validation.ctp:26 -#: Template/Email/text/validation.ctp:24 +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 msgid "Thank you" msgstr "Merci" @@ -541,54 +528,55 @@ msgstr "" "Veuillez copier l'adresse suivante dans votre navigateur web pour activer " "votre identification sociale {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:15 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15;79 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Actions" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:26 -#: Template/Users/view.ctp:19 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 +#: Template/Users/view.ctp:23 msgid "List Users" msgstr "Lister les utilisateurs" -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "Lister les comptes" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Ajouter un utilisateur" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:27 -#: Template/Users/register.ctp:18 Template/Users/view.ctp:30;70 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Nom d'utilisateur" -#: Template/Users/add.ctp:25 Template/Users/edit.ctp:36 -#: Template/Users/index.ctp:23 Template/Users/profile.ctp:29 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:32 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "Courriel" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:25 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "Mot de passe" + +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Prénom" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Nom" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 -#: Template/Users/view.ctp:44;75 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 +#: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Actif" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:35 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -607,49 +595,57 @@ msgstr "Mot de passe actuel" msgid "New password" msgstr "Nouveau mot de passe" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:23 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Confirmer le mot de passe" -#: Template/Users/edit.ctp:20 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:101 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Supprimer" -#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:18;101 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Êtes vous sûr(e) de vouloir supprimler # {0} ?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Modifier l'utilisateur" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:38;73 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Jeton" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Expiration du jeton" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "Jeton d'API" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Date d'activiation" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "Date CGU" +#: Template/Users/edit.ctp:64 +#, fuzzy +msgid "Reset Google Authenticator Token" +msgstr "Le jeton de réinitialisation du mot de passe a été validé avec succès" + +#: Template/Users/edit.ctp:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Nouveau {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Voir" @@ -657,7 +653,7 @@ msgstr "Voir" msgid "Change password" msgstr "Changer le mot de passe" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:99 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Modifier" @@ -689,39 +685,36 @@ msgstr "Réinitialiser le mot de passe" msgid "Login" msgstr "S'identifier" -#: Template/Users/profile.ctp:18 View/Helper/UserHelper.php:51 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" -#: Template/Users/profile.ctp:24 +#: Template/Users/profile.ctp:27 +#, fuzzy msgid "Change Password" msgstr "Changer le mot de passe" -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Comptes sociaux" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:72 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Photo de profil" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:69 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Fournisseur" -#: Template/Users/profile.ctp:40 +#: Template/Users/profile.ctp:44 msgid "Link" msgstr "Lien" -#: Template/Users/profile.ctp:47 +#: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Lier à {0}" -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "Mot de passe" - -#: Template/Users/register.ctp:28 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Accepter les CGU ?" @@ -737,101 +730,94 @@ msgstr "Réenvoyer le courriel de validation" msgid "Email or username" msgstr "Courriel ou nom d'utilisateur" -#: Template/Users/view.ctp:18 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Supprimer l'utilisateur" -#: Template/Users/view.ctp:20 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Nouvel utilisateur" -#: Template/Users/view.ctp:28;67 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Identifiant" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Prénom" -#: Template/Users/view.ctp:36 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Nom" -#: Template/Users/view.ctp:40 +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nouveau rôle : {0}" + +#: Template/Users/view.ctp:45 +#, fuzzy msgid "Api Token" msgstr "Jeton d'API" -#: Template/Users/view.ctp:48;74 +#: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Expiration du jeton" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Date d'activiation" -#: Template/Users/view.ctp:52 +#: Template/Users/view.ctp:57 +#, fuzzy msgid "Tos Date" msgstr "Date CGU" -#: Template/Users/view.ctp:54;77 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Créé le" -#: Template/Users/view.ctp:56;78 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Modifié le" -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "Comptes liés" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "Identifiant utilisateur" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "Référence" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "Données" - #: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "S'identifier avec" -#: View/Helper/UserHelper.php:49 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:52 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:91 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Se déconnecter" -#: View/Helper/UserHelper.php:108 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Bienvenue, {0}" -#: View/Helper/UserHelper.php:131 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "reCaptcha n'est pas configuré ! Veuillez configurer Users.reCaptcha.key" -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "Ce champ est requis" - -#~ msgid "The old password does not match" -#~ msgstr "L'ancien mot de passe ne correspond pas" +#: View/Helper/UserHelper.php:215 +#, fuzzy +msgid "Connected with {0}" +msgstr "Identifiant : {0}" -#~ msgid "" -#~ "If the link is not correctly displayed, please copy the following address " -#~ "in your web browser {0}" -#~ msgstr "" -#~ "Si le lien n'est pas correctement affiché, veuillez copier l'adresse " -#~ "suivante dans votre navigateur web {0}" +#: View/Helper/UserHelper.php:220 +#, fuzzy +msgid "Connect with {0}" +msgstr "Identifiant : {0}" diff --git a/resources/locales/hu_HU/users.mo b/resources/locales/hu_HU/users.mo new file mode 100644 index 000000000..a212f90fc Binary files /dev/null and b/resources/locales/hu_HU/users.mo differ diff --git a/src/Locale/hu_HU/Users.po b/resources/locales/hu_HU/users.po similarity index 75% rename from src/Locale/hu_HU/Users.po rename to resources/locales/hu_HU/users.po index 4fb02e3f6..5bb53a404 100644 --- a/src/Locale/hu_HU/Users.po +++ b/resources/locales/hu_HU/users.po @@ -4,182 +4,143 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2017-10-23 16:36+0200\n" -"PO-Revision-Date: 2017-10-23 17:17+0200\n" -"Language-Team: LANGUAGE \n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:12-0200\n" +"Language-Team: CakeDC \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.1\n" -"Last-Translator: rrd \n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" "Language: hu_HU\n" -#: Auth/SocialAuthenticate.php:456 -msgid "Provider cannot be empty" -msgstr "A kiszolgáló nem lehet üres" - -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "A fiók sikeresen ellenőrizve" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "A fiókot nem tudtam ellenőrizni" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Érvénytelen token és/vagy közösségi fiók" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "A közösségi fiók már aktív" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "A közösségi fiók nem ellenőrizhető" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Az email sikeresen elküldve" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "Az emailt nem tudtuk elküldeni" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Érvénytelen fiók" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "Az emailt nem lehet újraküldeni" -#: Controller/Component/RememberMeComponent.php:68 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Érvénytelen alkalamzás só. Az alkalmazás sónak legalább 256 bit (32 byte) " -"hosszúnak kell lennie" - -#: Controller/Component/UsersAuthComponent.php:204 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "Nem tudod engedélyezni az email ellenőrzést ha a use_email false" - -#: Controller/Traits/LinkSocialTrait.php:54 +#: Controller/Traits/LinkSocialTrait.php:52 msgid "Could not associate account, please try again." msgstr "Nem sikerült társítani a fiókot, próbáld meg még egyszer." -#: Controller/Traits/LinkSocialTrait.php:77 +#: Controller/Traits/LinkSocialTrait.php:76 msgid "Social account was associated." msgstr "A közösségi fiók társítva." -#: Controller/Traits/LoginTrait.php:104 -msgid "Issues trying to log in with your social account" -msgstr "Gond van a közösségi fiókkal való belépéssel." - -#: Controller/Traits/LoginTrait.php:109 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Add meg az emailedet." - -#: Controller/Traits/LoginTrait.php:120 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"A felhasználód még nincs ellenőrizve. Názd meg az emailjeidet a teendőkért." - -#: Controller/Traits/LoginTrait.php:125 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"A közösségi fiókod még nincs ellenőrizve. Názd meg az emailjeidet a " -"teendőkért." - -#: Controller/Traits/LoginTrait.php:180 Controller/Traits/RegisterTrait.php:82 -msgid "Invalid reCaptcha" -msgstr "Érvénytelen reCaptcha" - -#: Controller/Traits/LoginTrait.php:191 -msgid "You are already logged in" -msgstr "Már be vagy jelentkezve" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Sikeresen kijelentkeztél" -#: Controller/Traits/LoginTrait.php:212 +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 msgid "Please enable Google Authenticator first." msgstr "Először engedélyezd a Google Authenticatort." -#: Controller/Traits/LoginTrait.php:287 -msgid "Verification code is invalid. Try again" -msgstr "Az ellenőrző kód érvénytelen. Próbáld meg újra" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "A felhasználót ne tudtuk hozzáadni:" -#: Controller/Traits/LoginTrait.php:340 -msgid "Username or password is incorrect" -msgstr "A felhasználói név vagy a jelszó helytelen" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Nem sikerült társítani a fiókot, próbáld meg még egyszer." -#: Controller/Traits/LoginTrait.php:363 -msgid "You've successfully logged out" -msgstr "Sikeresen kijelentkeztél" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Az ellenőrző kód érvénytelen. Próbáld meg újra" -#: Controller/Traits/PasswordManagementTrait.php:49;82 -#: Controller/Traits/ProfileTrait.php:50 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "A felhasználó nem található" -#: Controller/Traits/PasswordManagementTrait.php:70;78;86 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "A jelszót nem tudtuk módosítani" -#: Controller/Traits/PasswordManagementTrait.php:74 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "A jelszó siekresen módosítva" -#: Controller/Traits/PasswordManagementTrait.php:84 -msgid "{0}" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:127 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "Nézd meg az emailjeidet a jelszó visszaállítás folytatásához" -#: Controller/Traits/PasswordManagementTrait.php:130 Shell/UsersShell.php:247 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "A jelszó token létrehozása sikertelen. Próbáld meg újra" -#: Controller/Traits/PasswordManagementTrait.php:136 -#: Controller/Traits/UserValidationTrait.php:107 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "A {0} felhasználó nem található" -#: Controller/Traits/PasswordManagementTrait.php:138 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "A felhasználó nem aktív" -#: Controller/Traits/PasswordManagementTrait.php:140 -#: Controller/Traits/UserValidationTrait.php:102;111 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "A token nem állítható vissza" -#: Controller/Traits/PasswordManagementTrait.php:164 +#: Controller/Traits/PasswordManagementTrait.php:174 msgid "Google Authenticator token was successfully reset" msgstr "A Google Authenticator token sikeresen visszaállítva" -#: Controller/Traits/ProfileTrait.php:54 +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Még nem jelentkeztél be. Jelentkezz be" -#: Controller/Traits/RegisterTrait.php:43 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Ki kell lépned ahhoz, hogy új fiókot tudj létrehozni" -#: Controller/Traits/RegisterTrait.php:89 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "A felhasználót nem lehet menteni" -#: Controller/Traits/RegisterTrait.php:123 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "Érvénytelen reCaptcha" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Sikeresen regisztráltál, lépj be" -#: Controller/Traits/RegisterTrait.php:125 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Kérlek jitelesítsd a fiókodat mielőtt belépnél" @@ -199,63 +160,92 @@ msgstr "A {0} törölve" msgid "The {0} could not be deleted" msgstr "A {0} nem lett törölve" -#: Controller/Traits/SocialTrait.php:40 -msgid "The reCaptcha could not be validated" -msgstr "A reCaptcha nem ellenőrizhető" - -#: Controller/Traits/UserValidationTrait.php:43 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "A fiók sikeresen ellenőrizve" -#: Controller/Traits/UserValidationTrait.php:45 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "A fiókot nem sikerült ellnőrizni" -#: Controller/Traits/UserValidationTrait.php:48 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "A fiók már aktív" -#: Controller/Traits/UserValidationTrait.php:54 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "A jelszó helyreállító token sikeresen ellenőrizve" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "A jelszó helyreállító token ellenőrzése sikertelen" -#: Controller/Traits/UserValidationTrait.php:66 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Helytelen ellenőrzés típus" -#: Controller/Traits/UserValidationTrait.php:69 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Helytelen token vagy a fiók már ellenőrizve van" -#: Controller/Traits/UserValidationTrait.php:71 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "A token már lejárt" -#: Controller/Traits/UserValidationTrait.php:97 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "A token helyreállítva. Nézd meg az emailjeidet." -#: Controller/Traits/UserValidationTrait.php:109 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "{0} felhasználó már aktív" -#: Mailer/UsersMailer.php:34 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "A felhasználói név vagy a jelszó helytelen" + +#: Loader/LoginComponentLoader.php:51 +#, fuzzy +msgid "Could not proceed with social account. Please try again" +msgstr "Nem sikerült társítani a fiókot, próbáld meg még egyszer." + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"A felhasználód még nincs ellenőrizve. Názd meg az emailjeidet a teendőkért." + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"A közösségi fiókod még nincs ellenőrizve. Názd meg az emailjeidet a " +"teendőkért." + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "A fiókod hitelesítési linkje" -#: Mailer/UsersMailer.php:52 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0} A jelszó helyreállítás linkje" -#: Mailer/UsersMailer.php:75 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0} A közösségi fiókod ellenőrzési linkje" -#: Model/Behavior/AuthFinderBehavior.php:49 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Add meg az emailedet." + +#: Middleware/SocialAuthMiddleware.php:75 +#, fuzzy +msgid "Could not identify your account, please try again" +msgstr "Nem sikerült társítani a fiókot, próbáld meg még egyszer." + +#: Model/Behavior/AuthFinderBehavior.php:48 msgid "Missing 'username' in options data" msgstr "Hiányzó 'username' az opciókban" @@ -271,12 +261,12 @@ msgstr "A hivatkozás nem lehet null" msgid "Token expiration cannot be empty" msgstr "A token lejárat nme lehet üres" -#: Model/Behavior/PasswordBehavior.php:56;117 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "A felhasználó nem található" #: Model/Behavior/PasswordBehavior.php:60 -#: Model/Behavior/RegisterBehavior.php:112;205 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "A felhasználói fiók már ellenőrizve van" @@ -284,11 +274,11 @@ msgstr "A felhasználói fiók már ellenőrizve van" msgid "User not active" msgstr "A felhasználó nem aktív" -#: Model/Behavior/PasswordBehavior.php:122 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "A jelenlegi jelszó nem stimmel" -#: Model/Behavior/PasswordBehavior.php:125 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Nem lehet az új jelszó azonos a régivel" @@ -300,31 +290,36 @@ msgstr "Nem található felhasználó ehhez a tokenhez és emailhez." msgid "Token has already expired user with no token" msgstr "A token lejárt felhasználó token nélkül" -#: Model/Behavior/SocialAccountBehavior.php:103;130 +#: Model/Behavior/RegisterBehavior.php:151 +#, fuzzy +msgid "This field is required" +msgstr "Mező: {0} Hiba: {1}" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "A fiók már ellenőrizve van" -#: Model/Behavior/SocialAccountBehavior.php:106;133 +#: Model/Behavior/SocialAccountBehavior.php:105;132 msgid "Account not found for the given token and email." msgstr "Nem található fiók ehhez a tokenhez és emailhez." -#: Model/Behavior/SocialBehavior.php:82 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Nem tudom belépni a usert a {0} hivatkozással" -#: Model/Behavior/SocialBehavior.php:121 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Hiányzó email" -#: Model/Table/UsersTable.php:81 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "A jelszavad és a jelszó megerősítés nem azonos. Próbáld meg újra" -#: Model/Table/UsersTable.php:173 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "A felhasználónév már foglalt" -#: Model/Table/UsersTable.php:179 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Az email már foglalt" @@ -429,16 +424,18 @@ msgid "User added:" msgstr "A felhasználó hozzáadva:" #: Shell/UsersShell.php:312 +#, fuzzy msgid "Id: {0}" -msgstr "" +msgstr "A {0} felhasználó nem található" #: Shell/UsersShell.php:313 msgid "Username: {0}" msgstr "Felhasználónév: {0}" #: Shell/UsersShell.php:314 +#, fuzzy msgid "Email: {0}" -msgstr "" +msgstr "Az email sikeresen elküldve" #: Shell/UsersShell.php:315 msgid "Role: {0}" @@ -457,6 +454,7 @@ msgid "Field: {0} Error: {1}" msgstr "Mező: {0} Hiba: {1}" #: Shell/UsersShell.php:337 +#, fuzzy msgid "The user was not found." msgstr "A felhasználó nem található" @@ -519,53 +517,56 @@ msgid "" msgstr "" "Másold be a böngésződbe ezt a címet a közösségi fiókod aktiválásához {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:16 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 #: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Műveletek" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:27 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 #: Template/Users/view.ctp:23 msgid "List Users" msgstr "Felhasználók" -#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Új felhasználó" -#: Template/Users/add.ctp:23 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:30 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:33 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Felhasználói név" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:36 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 #: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 -#: Template/Users/register.ctp:20 Template/Users/view.ctp:35 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 +#, fuzzy msgid "Email" -msgstr "" +msgstr "Az email sikeresen elküldve" -#: Template/Users/add.ctp:25 Template/Users/register.ctp:21 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 msgid "Password" msgstr "Jelszó" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Keresztnév" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:27 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Családi név" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 #: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Aktív" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:36 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -584,48 +585,50 @@ msgstr "Jelenlegi jelszó" msgid "New password" msgstr "Új jelszó" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Jelszó megerősítése" -#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Törlés" -#: Template/Users/edit.ctp:23 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 #: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Biztos törlöd # {0}?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Felhasználó módosítása" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +#, fuzzy msgid "Token" -msgstr "" +msgstr "A token lejárt felhasználó token nélkül" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Token lejárat" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 +#, fuzzy msgid "API token" -msgstr "" +msgstr "Érvénytelen token és/vagy közösségi fiók" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Aktiválás dátuma" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "Felhasználó feltételek dátuma" -#: Template/Users/edit.ctp:63 +#: Template/Users/edit.ctp:64 msgid "Reset Google Authenticator Token" msgstr "Google Authenticator Token visszaállítása" -#: Template/Users/edit.ctp:69 +#: Template/Users/edit.ctp:70 msgid "Are you sure you want to reset token for user \"{0}\"?" msgstr "" "Biztos vagy benne, hogy vissza akarod állítani {0} felhasználó tokenjét?" @@ -674,9 +677,10 @@ msgstr "Jelszó visszaállítás" msgid "Login" msgstr "Belépés" -#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 +#: Template/Users/profile.ctp:21 +#, fuzzy msgid "{0} {1}" -msgstr "" +msgstr "Mező: {0} Hiba: {1}" #: Template/Users/profile.ctp:27 msgid "Change Password" @@ -695,14 +699,15 @@ msgid "Provider" msgstr "Kiszolgáló" #: Template/Users/profile.ctp:44 +#, fuzzy msgid "Link" -msgstr "" +msgstr "A fiókod hitelesítési linkje" #: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Kapcsolódva: {0}" -#: Template/Users/register.ctp:29 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Elfogadod a felhasználó feltételeket?" @@ -743,10 +748,12 @@ msgid "Id" msgstr "" #: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Keresztnév" #: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Családi név" @@ -755,14 +762,17 @@ msgid "Role" msgstr "Szerep" #: Template/Users/view.ctp:45 +#, fuzzy msgid "Api Token" -msgstr "" +msgstr "Érvénytelen token és/vagy közösségi fiók" #: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Token lejárat" #: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Aktiválás dátuma" @@ -778,39 +788,27 @@ msgstr "Létrehozva" msgid "Modified" msgstr "Módosítva" -#: View/Helper/UserHelper.php:45 +#: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Belépés ezzel" -#: View/Helper/UserHelper.php:48 -msgid "fa fa-{0}" -msgstr "" - -#: View/Helper/UserHelper.php:57 -msgid "btn btn-social btn-{0} " -msgstr "" - -#: View/Helper/UserHelper.php:106 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Kilép" -#: View/Helper/UserHelper.php:123 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Üdv {0}!" -#: View/Helper/UserHelper.php:146 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "A reCaptcha nincs beállítva! Ellenőrizd a Users.reCaptcha.key beállítást" -#: View/Helper/UserHelper.php:205 -msgid "btn btn-social btn-{0}" -msgstr "" - -#: View/Helper/UserHelper.php:211 +#: View/Helper/UserHelper.php:215 msgid "Connected with {0}" msgstr "Összekapcsolódva ezzel: {0}" -#: View/Helper/UserHelper.php:216 +#: View/Helper/UserHelper.php:220 msgid "Connect with {0}" msgstr "Kapcsolódás ehhez: {0} " diff --git a/resources/locales/it_IT/users.mo b/resources/locales/it_IT/users.mo new file mode 100644 index 000000000..b456aa036 Binary files /dev/null and b/resources/locales/it_IT/users.mo differ diff --git a/src/Locale/it_IT/Users.po b/resources/locales/it_IT/users.po similarity index 52% rename from src/Locale/it_IT/Users.po rename to resources/locales/it_IT/users.po index 80176186a..5b651cc07 100644 --- a/src/Locale/it_IT/Users.po +++ b/resources/locales/it_IT/users.po @@ -1,298 +1,309 @@ # LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2016, Cake Development Corporation (http://cakedc.com) +# Copyright YEAR NAME # msgid "" msgstr "" -"Project-Id-Version: CakeDC Users\n" -"POT-Creation-Date: 2017-12-21 12:01+0100\n" -"PO-Revision-Date: 2018-01-12 17:06+0100\n" -"Last-Translator: Silvia Ghignone \n" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:15-0200\n" "Language-Team: CakeDC \n" -"Language: it_IT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 2.0.5\n" - -#: Auth/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "Il tipo {0} non è valido" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "Il tipo {0} non ha nessuna funzione di chiamata associabile" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL è richiesto per ApiKey Authentication" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "File di configurazione mancante: \"config/{0}.php\". Utilizzo dei permessi di default" - -#: Auth/SocialAuthenticate.php:432 -msgid "Provider cannot be empty" -msgstr "Il campo Provider non può essere vuoto" - -#: Auth/Rules/AbstractRule.php:78 -msgid "" -"Table alias is empty, please define a table alias, we could not extract a default table from the " -"request" -msgstr "" -"L'alias della tabella è vuoto, per favore definire un alias tabella, non potremmo estrarre una tabella " -"di default dalla richiesta" - -#: Auth/Rules/Owner.php:67;70 -msgid "Missing column {0} in table {1} while checking ownership permissions for user {2}" -msgstr "" -"Colonna {0} mancante nella tabella {1} durante la verifica dei \"\"permessi di proprietà per l'utente " -"{2}" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: it_IT\n" -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Account convalidato con successo" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "Non è stato possibile convalidare l'account" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Token e/o account social non valido" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Account Social già attivo" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "Non è stato possibile convalidare l'Account Social" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Email inviata con successo" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "Impossibile inviare l'email" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Account non valido" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "Non è stato possibile reinviare l'email" -#: Controller/Component/RememberMeComponent.php:69 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "App salt non valido. App salt deve essere almeno lungo 256 bits (32 bytes)" - -#: Controller/Component/UsersAuthComponent.php:178 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "Non è possibile abilitare il workflow di validazione email se use_email è false" - -#: Controller/Traits/LoginTrait.php:96 -msgid "Issues trying to log in with your social account" -msgstr "Si sono verificati dei problemi nel tentativo di effettuare il log in col tuo account social" +#: Controller/Traits/LinkSocialTrait.php:52 +#, fuzzy +msgid "Could not associate account, please try again." +msgstr "" +"Non è stato possibile generare il token della password. Per favore riprovare" -#: Controller/Traits/LoginTrait.php:101 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Per favore inseirsci la tua email" +#: Controller/Traits/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Account Social già attivo" -#: Controller/Traits/LoginTrait.php:108 -msgid "Your user has not been validated yet. Please check your inbox for instructions" -msgstr "" -"Il vostro utente non è ancora stato convalidato. Per favore verifica le tue email in arrivoper " -"ottenere le istruzioni" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Log out effettuato con successo" -#: Controller/Traits/LoginTrait.php:110 -msgid "Your social account has not been validated yet. Please check your inbox for instructions" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." msgstr "" -"Il tuo account social non è stato ancora convalidato. Per favore verifica la tuacasella di posta " -"elettronica per maggiori informazioni" - -#: Controller/Traits/LoginTrait.php:161 Controller/Traits/RegisterTrait.php:81 -msgid "Invalid reCaptcha" -msgstr "reCaptcha non valida" -#: Controller/Traits/LoginTrait.php:171 -msgid "You are already logged in" -msgstr "E' già  stato effettuato il log in" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Registrazione utente non riuscita" -#: Controller/Traits/LoginTrait.php:217 -msgid "Username or password is incorrect" -msgstr "Username o password non corretti" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "" +"Non è stato possibile generare il token della password. Per favore riprovare" -#: Controller/Traits/LoginTrait.php:238 -msgid "You've successfully logged out" -msgstr "Log out effettuato con successo" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "" -#: Controller/Traits/PasswordManagementTrait.php:47;76 Controller/Traits/ProfileTrait.php:49 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "Utente non trovato" -#: Controller/Traits/PasswordManagementTrait.php:64;72;80 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Non è stato possibile cambiare la password" -#: Controller/Traits/PasswordManagementTrait.php:68 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Password modificata con successo" -#: Controller/Traits/PasswordManagementTrait.php:78 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:120 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" -msgstr "Controllare la propria email per continuare col processo di ripristino della password" +msgstr "" +"Controllare la propria email per continuare col processo di ripristino della " +"password" -#: Controller/Traits/PasswordManagementTrait.php:123 Shell/UsersShell.php:267 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" -msgstr "Non è stato possibile generare il token della password. Per favore riprovare" +msgstr "" +"Non è stato possibile generare il token della password. Per favore riprovare" -#: Controller/Traits/PasswordManagementTrait.php:129 Controller/Traits/UserValidationTrait.php:100 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "Utente {0} non trovato" -#: Controller/Traits/PasswordManagementTrait.php:131 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "Utente non attivo" -#: Controller/Traits/PasswordManagementTrait.php:133 Controller/Traits/UserValidationTrait.php:95;104 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "Non è stato possibile ripristinare il token" -#: Controller/Traits/ProfileTrait.php:53 +#: Controller/Traits/PasswordManagementTrait.php:174 +#, fuzzy +msgid "Google Authenticator token was successfully reset" +msgstr "Il token per il ripristino password è stato convalidato con successo" + +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Non autorizzato, per favore effettuare prima il login" -#: Controller/Traits/RegisterTrait.php:42 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Per registrare un nuovo account utente, effettuare prima il log out" -#: Controller/Traits/RegisterTrait.php:88 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "Registrazione utente non riuscita" -#: Controller/Traits/RegisterTrait.php:122 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "reCaptcha non valida" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Registrazione avvenuta con successo, per favore effettuare il log in" -#: Controller/Traits/RegisterTrait.php:124 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Per favore convalida il tuo account prima di effettuare il log in" -#: Controller/Traits/SimpleCrudTrait.php:76;106 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "{0} è stato salvato" -#: Controller/Traits/SimpleCrudTrait.php:80;110 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "Non è stato possibile salvare {0}" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "{0} è stato cancellato" -#: Controller/Traits/SimpleCrudTrait.php:132 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "Non è stato possibile cancellare {0}" -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "Non è stato possibile convalidare la reCaptcha" - -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Account utente convalidato con successo" -#: Controller/Traits/UserValidationTrait.php:44 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Non è stato possibile convalidare l'account utente" -#: Controller/Traits/UserValidationTrait.php:47 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Utente già attivo" -#: Controller/Traits/UserValidationTrait.php:53 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Il token per il ripristino password è stato convalidato con successo" -#: Controller/Traits/UserValidationTrait.php:58 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" -msgstr "Non è stato possibile convalidare il token per il ripristino della password" +msgstr "" +"Non è stato possibile convalidare il token per il ripristino della password" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Tipo di validazione non valido" -#: Controller/Traits/UserValidationTrait.php:65 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Toke non valido o account utente già convalidato" -#: Controller/Traits/UserValidationTrait.php:67 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token già scaduto" -#: Controller/Traits/UserValidationTrait.php:93 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." -msgstr "Il token è stato ripristinato con successo. Per favore verificare la propria email." +msgstr "" +"Il token è stato ripristinato con successo. Per favore verificare la propria " +"email." -#: Controller/Traits/UserValidationTrait.php:102 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "L'Utente {0} è già attivo" -#: Email/EmailSender.php:39 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Username o password non corretti" + +#: Loader/LoginComponentLoader.php:51 +msgid "Could not proceed with social account. Please try again" +msgstr "" + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Il vostro utente non è ancora stato convalidato. Per favore verifica le tue " +"email in arrivoper ottenere le istruzioni" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Il tuo account social non è stato ancora convalidato. Per favore verifica la " +"tuacasella di posta elettronica per maggiori informazioni" + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Il tuo link per convalidare l'account" -#: Mailer/UsersMailer.php:55 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0}Ecco il tuo link per ripristinare la password" -#: Mailer/UsersMailer.php:78 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0}Ecco il tuo link per convalidare il tuo account social" -#: Model/Behavior/PasswordBehavior.php:56 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Per favore inseirsci la tua email" + +#: Middleware/SocialAuthMiddleware.php:75 +msgid "Could not identify your account, please try again" +msgstr "" + +#: Model/Behavior/AuthFinderBehavior.php:48 +msgid "Missing 'username' in options data" +msgstr "'username' mancante nelle opzioni dati" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "" + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "La referenza non può essere nulla" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "La scadenza del Token non può essere vuota" -#: Model/Behavior/PasswordBehavior.php:67;116 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Utente non trovato" -#: Model/Behavior/PasswordBehavior.php:71 Model/Behavior/RegisterBehavior.php:111 +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Account utente già convalidato" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "Utente non attivo" -#: Model/Behavior/PasswordBehavior.php:121 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "L'attuale password non corrisponde" -#: Model/Behavior/PasswordBehavior.php:124 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Non puoi utilizzare la password attuale come nuova password" -#: Model/Behavior/RegisterBehavior.php:89 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Utente non trovato per l'email ed il token forniti." -#: Model/Behavior/RegisterBehavior.php:92 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "Il token è già scaduto utente senza token" +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Questo campo è obbligatorio" + #: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "Account già convalidato" @@ -301,167 +312,176 @@ msgstr "Account già convalidato" msgid "Account not found for the given token and email." msgstr "Account non trovato per l'email ed il token forniti." -#: Model/Behavior/SocialBehavior.php:56 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Impossibile effettuare il login per l'utente con referenza {0}" -#: Model/Behavior/SocialBehavior.php:98 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Email non presente" -#: Model/Table/UsersTable.php:82 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" -msgstr "La password non corrisponde con quella inserita nella conferma password.Per favore riprova" +msgstr "" +"La password non corrisponde con quella inserita nella conferma password.Per " +"favore riprova" -#: Model/Table/UsersTable.php:175 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Username già esistente" -#: Model/Table/UsersTable.php:181 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Email già esistente" -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "'username' mancante nelle opzioni dati" - -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Utilità per Plugin CakeDC Users" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Attiva un utente specifico" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "Aggiunge un nuovo superadmin per scopi di test" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Aggiungi un nuovo utente" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" msgstr "Cambia il ruolo per un utente specifico" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Disattiva un utente specifico" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Cancella un utente specifico" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Ripristina la password per email" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Ripristina la password per tutti gli utenti" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Ripristina la password per un utente specifico" -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "Utente aggiunto:" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "Identificatore : {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "Nome utente: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "Email : {0}" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "Password: {0}" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "Superuser aggiunto :" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "Lo Superuser non può essere aggiunto :" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "Campo : {0} Errore : {1}" - -#: Shell/UsersShell.php:153;179 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Per favore inserire una password." -#: Shell/UsersShell.php:157 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Password cambiata per tutti gli utenti" -#: Shell/UsersShell.php:158;186 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nuova password : {0}" -#: Shell/UsersShell.php:176;204;282;324 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Per favore inserire un nome utente." -#: Shell/UsersShell.php:185 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Password cambiata per l'utente : {0}" -#: Shell/UsersShell.php:207 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Per favore inserire un ruolo." -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Ruolo modificato per l'utente: {0}" -#: Shell/UsersShell.php:214 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Nuovo ruolo: {0}" -#: Shell/UsersShell.php:229 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "Utente attivato: {0}" -#: Shell/UsersShell.php:244 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "Utente disattivato: {0]" -#: Shell/UsersShell.php:256 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Per favore inserire un nome utente o email." -#: Shell/UsersShell.php:264 -msgid "Please ask the user to check the email to continue with password reset process" +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" msgstr "" -"Per favore richiedere all'utente di controllare l'email per continuare con la procedura di ripristino " -"password" +"Per favore richiedere all'utente di controllare l'email per continuare con " +"la procedura di ripristino password" + +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superuser aggiunto :" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Utente aggiunto:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Identificatore : {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Nome utente: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Email : {0}" -#: Shell/UsersShell.php:302 +#: Shell/UsersShell.php:315 +#, fuzzy +msgid "Role: {0}" +msgstr "Nuovo ruolo: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Password: {0}" + +#: Shell/UsersShell.php:318 +#, fuzzy +msgid "User could not be added:" +msgstr "Lo Superuser non può essere aggiunto :" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Campo : {0} Errore : {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "Utente non trovato." -#: Shell/UsersShell.php:332 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "L'Utente {0} non è stato cancellato. Per favore riprovare" -#: Shell/UsersShell.php:334 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "L'utente {0} è stato cancellato con successo" -#: Template/Email/html/reset_password.ctp:21 Template/Email/html/social_account_validation.ctp:14 -#: Template/Email/html/validation.ctp:21 Template/Email/text/reset_password.ctp:20 -#: Template/Email/text/social_account_validation.ctp:22 Template/Email/text/validation.ctp:20 +#: Template/Email/html/reset_password.ctp:21 +#: Template/Email/html/social_account_validation.ctp:14 +#: Template/Email/html/validation.ctp:21 +#: Template/Email/text/reset_password.ctp:20 +#: Template/Email/text/social_account_validation.ctp:22 +#: Template/Email/text/validation.ctp:20 msgid "Hi {0}" msgstr "Ciao {0}" @@ -469,16 +489,23 @@ msgstr "Ciao {0}" msgid "Reset your password here" msgstr "Ripristina la tua password qui" -#: Template/Email/html/reset_password.ctp:27 Template/Email/html/social_account_validation.ctp:32 +#: Template/Email/html/reset_password.ctp:27 +#: Template/Email/html/social_account_validation.ctp:32 #: Template/Email/html/validation.ctp:27 -msgid "If the link is not correcly displayed, please copy the following address in your web browser {0}" +#, fuzzy +msgid "" +"If the link is not correctly displayed, please copy the following address in " +"your web browser {0}" msgstr "" -"Se il link non viene mostrato correttamente, per favore copia il seguente indirizzo nel tuo browser " -"web {0}" - -#: Template/Email/html/reset_password.ctp:30 Template/Email/html/social_account_validation.ctp:35 -#: Template/Email/html/validation.ctp:30 Template/Email/text/reset_password.ctp:24 -#: Template/Email/text/social_account_validation.ctp:26 Template/Email/text/validation.ctp:24 +"Se il link non viene mostrato correttamente, per favore copia il seguente " +"indirizzo nel tuo browser web {0}" + +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 msgid "Thank you" msgstr "Grazie" @@ -490,58 +517,71 @@ msgstr "Attiva qui il tuo login social" msgid "Activate your account here" msgstr "Attiva qui il tuo account" -#: Template/Email/text/reset_password.ctp:22 Template/Email/text/validation.ctp:22 +#: Template/Email/text/reset_password.ctp:22 +#: Template/Email/text/validation.ctp:22 msgid "Please copy the following address in your web browser {0}" msgstr "Per favore copia il seguente indirizzo nel tuo browser web {0}" #: Template/Email/text/social_account_validation.ctp:24 -msgid "Please copy the following address in your web browser to activate your social login {0}" -msgstr "Per favore copia il seguente indirizzo nel tuo browser web per attivare il tuo login social {0}" +msgid "" +"Please copy the following address in your web browser to activate your " +"social login {0}" +msgstr "" +"Per favore copia il seguente indirizzo nel tuo browser web per attivare il " +"tuo login social {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:15 Template/Users/index.ctp:13;26 -#: Template/Users/view.ctp:15;79 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Azioni" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:26 Template/Users/view.ctp:19 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 +#: Template/Users/view.ctp:23 msgid "List Users" msgstr "Lista utenti" -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:27 Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "Lista Account" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Aggiunti Utente" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:35 Template/Users/index.ctp:22 -#: Template/Users/profile.ctp:27 Template/Users/register.ctp:18 Template/Users/view.ctp:30;70 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Username" -#: Template/Users/add.ctp:25 Template/Users/edit.ctp:36 Template/Users/index.ctp:23 -#: Template/Users/profile.ctp:29 Template/Users/register.ctp:19 Template/Users/view.ctp:32 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "Email" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 Template/Users/index.ctp:24 -#: Template/Users/register.ctp:25 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "Password" + +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Nome" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 Template/Users/index.ctp:25 -#: Template/Users/register.ctp:26 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Cognome" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 Template/Users/view.ctp:44;75 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 +#: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Attivo" -#: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 Template/Users/edit.ctp:57 -#: Template/Users/register.ctp:35 Template/Users/request_reset_password.ctp:8 -#: Template/Users/resend_token_validation.ctp:20 Template/Users/social_email.ctp:19 +#: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 +#: Template/Users/request_reset_password.ctp:8 +#: Template/Users/resend_token_validation.ctp:20 +#: Template/Users/social_email.ctp:19 msgid "Submit" msgstr "Inviare" @@ -557,47 +597,57 @@ msgstr "Password attuale" msgid "New password" msgstr "Nuova password" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:23 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Conferma password" -#: Template/Users/edit.ctp:20 Template/Users/index.ctp:40 Template/Users/view.ctp:101 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Cancellare" -#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 Template/Users/view.ctp:18;101 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Sei sicuro di voler cancellare # {0} ?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Modifica utente" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:38;73 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Token" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Scadenza Token" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "API Token" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Data di attivazione" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "Data TOS" +#: Template/Users/edit.ctp:64 +#, fuzzy +msgid "Reset Google Authenticator Token" +msgstr "Il token per il ripristino password è stato convalidato con successo" + +#: Template/Users/edit.ctp:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Nuovo {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Vedere" @@ -605,7 +655,7 @@ msgstr "Vedere" msgid "Change password" msgstr "Cambiare la password" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:99 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Modifier" @@ -637,39 +687,35 @@ msgstr "Ripristina Password" msgid "Login" msgstr "Login" -#: Template/Users/profile.ctp:18 View/Helper/UserHelper.php:51 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" -#: Template/Users/profile.ctp:24 +#: Template/Users/profile.ctp:27 msgid "Change Password" msgstr "Cambiare Password" -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Account Social" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:72 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Foto profilo" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:69 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Provider" -#: Template/Users/profile.ctp:40 +#: Template/Users/profile.ctp:44 msgid "Link" msgstr "Link" -#: Template/Users/profile.ctp:47 +#: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Link a  {0}" -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "Password" - -#: Template/Users/register.ctp:28 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Accetti le condizioni TOS?" @@ -685,99 +731,92 @@ msgstr "Rispedire Email di Convalidazione" msgid "Email or username" msgstr "Email o Username" -#: Template/Users/view.ctp:18 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Cancellare Utente" -#: Template/Users/view.ctp:20 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Nuovo Utente" -#: Template/Users/view.ctp:28;67 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Id" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Nome" -#: Template/Users/view.ctp:36 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Cognome" -#: Template/Users/view.ctp:40 +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nuovo ruolo: {0}" + +#: Template/Users/view.ctp:45 +#, fuzzy msgid "Api Token" msgstr "API Token" -#: Template/Users/view.ctp:48;74 +#: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Scadenza Token" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Data di attivazione" -#: Template/Users/view.ctp:52 +#: Template/Users/view.ctp:57 msgid "Tos Date" msgstr "Data Tos" -#: Template/Users/view.ctp:54;77 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Creato il" -#: Template/Users/view.ctp:56;78 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Modificato il" -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "Account correlati" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "Id Utente" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "Referenza" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "Dati" - #: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Registrati con" -#: View/Helper/UserHelper.php:49 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:52 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:91 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Logout" -#: View/Helper/UserHelper.php:108 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Benvenuto, {0}" -#: View/Helper/UserHelper.php:131 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "reCaptcha non configurata! Per favore configura Users.reCaptcha.key" -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "Questo campo è obbligatorio" - -#~ msgid "The old password does not match" -#~ msgstr "La vecchia password non corrisponde" +#: View/Helper/UserHelper.php:215 +#, fuzzy +msgid "Connected with {0}" +msgstr "Identificatore : {0}" -#~ msgid "" -#~ "If the link is not correctly displayed, please copy the following address in your web browser {0}" -#~ msgstr "" -#~ "Se il link non viene mostrato correttamente, per favore copiare il seguente indirizzonel tuo web " -#~ "browser {0}" +#: View/Helper/UserHelper.php:220 +#, fuzzy +msgid "Connect with {0}" +msgstr "Identificatore : {0}" diff --git a/resources/locales/pl/users.mo b/resources/locales/pl/users.mo new file mode 100644 index 000000000..0a2755293 Binary files /dev/null and b/resources/locales/pl/users.mo differ diff --git a/src/Locale/pl/Users.po b/resources/locales/pl/users.po similarity index 61% rename from src/Locale/pl/Users.po rename to resources/locales/pl/users.po index 7c7834fe0..5b3aba164 100644 --- a/src/Locale/pl/Users.po +++ b/resources/locales/pl/users.po @@ -4,310 +4,298 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2017-07-02 20:08+0200\n" -"PO-Revision-Date: 2017-07-02 20:08+0200\n" -"Last-Translator: \n" -"Language-Team: LANGUAGE \n" -"Language: pl\n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:17-0200\n" +"Language-Team: CakeDC \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 2.0.1\n" - -#: Auth/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "Typ {0} jest nieprawidłowy" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "Brak callable dla typu {0}" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "Uwierzytelnienie ApiKey wymaga SSL" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "" -"Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" -"Brak pliku konfiguracji: \"config/{0}.php\". Użyto domyślnych uprawnień" - -#: Auth/SocialAuthenticate.php:432 -msgid "Provider cannot be empty" -msgstr "Dostawca nie może być pusty" - -#: Auth/Rules/AbstractRule.php:78 -msgid "" -"Table alias is empty, please define a table alias, we could not extract a " -"default table from the request" -msgstr "Alias tabeli jest pusty, zdefiniuj alias tabeli" - -#: Auth/Rules/Owner.php:67;70 -msgid "" -"Missing column {0} in table {1} while checking ownership permissions for " -"user {2}" -msgstr "" -"Nieistniejąca kolumna {0} w tabeli {0} podczas sprawdzania uprawnień dla " -"użytkownika {2}" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 " +"|| n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: pl\n" -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Konto zostało aktywowane" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "Aktywacja konta nie powiodła się" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Nieprawidłowy token lub konto społecznościowe" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Konto społecznościowe jest już aktywne" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "Aktywacja nie powiodła się" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "E-mail został wysłany" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "E-mail nie mógł zostać wysłany" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Nieprawidłowe konto" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "Nie można wysłać wiadomości e-mail" -#: Controller/Component/RememberMeComponent.php:69 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Nieprawidłowa sól aplikacji, sól aplikacji powinna mieć długość co najmniej " -"256 bitów (32 bajty)" - -#: Controller/Component/UsersAuthComponent.php:178 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" -"Nie możesz włączyć potwierdzania kont przez e-mail jeśli opcja use_email = " -"false" - -#: Controller/Traits/LoginTrait.php:96 -msgid "Issues trying to log in with your social account" -msgstr "" -"Wystąpiły problemy z logowaniem za pośrednictwem konta społecznościowego" +#: Controller/Traits/LinkSocialTrait.php:52 +#, fuzzy +msgid "Could not associate account, please try again." +msgstr "Nie można wygenerować tokenu. Spróbuj ponownie" -#: Controller/Traits/LoginTrait.php:101 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Podaj swój adres e-mail" +#: Controller/Traits/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Konto społecznościowe jest już aktywne" -#: Controller/Traits/LoginTrait.php:108 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Twoje konto nie zostało jeszcze aktywowane. Sprawdź swoją skrzynkę odbiorczą" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Wylogowano" -#: Controller/Traits/LoginTrait.php:110 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." msgstr "" -"Konto społecznościowe nie zostało jeszcze aktywowane. Sprawdź swoją pocztę e-" -"mail." - -#: Controller/Traits/LoginTrait.php:161 Controller/Traits/RegisterTrait.php:81 -msgid "Invalid reCaptcha" -msgstr "Błąd reCaptcha" -#: Controller/Traits/LoginTrait.php:171 -msgid "You are already logged in" -msgstr "Jesteś już zalogowany" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Nie można utworzyć konta" -#: Controller/Traits/LoginTrait.php:217 -msgid "Username or password is incorrect" -msgstr "Nazwa użytkownika lub hasło jest nieprawidłowe" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Nie można wygenerować tokenu. Spróbuj ponownie" -#: Controller/Traits/LoginTrait.php:238 -msgid "You've successfully logged out" -msgstr "Wylogowano" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "" -#: Controller/Traits/PasswordManagementTrait.php:47;76 -#: Controller/Traits/ProfileTrait.php:49 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "Konto użytkownika nie istnieje" -#: Controller/Traits/PasswordManagementTrait.php:64;72;80 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Zmiana hasła nie powiodła się" -#: Controller/Traits/PasswordManagementTrait.php:68 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Hasło zostało zmienione" -#: Controller/Traits/PasswordManagementTrait.php:78 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:120 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "Aby dokończyć zmianę hasła, sprawdź swoją pocztę e-mail." -#: Controller/Traits/PasswordManagementTrait.php:123 Shell/UsersShell.php:267 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "Nie można wygenerować tokenu. Spróbuj ponownie" -#: Controller/Traits/PasswordManagementTrait.php:129 -#: Controller/Traits/UserValidationTrait.php:100 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "Nie znaleziono użytkownika {0}" -#: Controller/Traits/PasswordManagementTrait.php:131 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "Konto użytkownika nie jest aktywne" -#: Controller/Traits/PasswordManagementTrait.php:133 -#: Controller/Traits/UserValidationTrait.php:95;104 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "Nie można zresetować tokenu" -#: Controller/Traits/ProfileTrait.php:53 +#: Controller/Traits/PasswordManagementTrait.php:174 +#, fuzzy +msgid "Google Authenticator token was successfully reset" +msgstr "Token zmiany hasła został zatwierdzony" + +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Wymagane logowanie" -#: Controller/Traits/RegisterTrait.php:42 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Wyloguj się aby utworzyć nowe konto" -#: Controller/Traits/RegisterTrait.php:88 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "Nie można utworzyć konta" -#: Controller/Traits/RegisterTrait.php:122 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "Błąd reCaptcha" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Rejestracja zakończona sukcesem, możesz się zalogować" -#: Controller/Traits/RegisterTrait.php:124 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Potwierdź swoje konto przed logowaniem" -#: Controller/Traits/SimpleCrudTrait.php:76;106 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "{0} został zapisany" -#: Controller/Traits/SimpleCrudTrait.php:80;110 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "{0} nie mógł zostać zapisany" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "{0} został usunięty" -#: Controller/Traits/SimpleCrudTrait.php:132 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "{0} nie mógł zostać usunięty" -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "Błąd walidacji reCaptcha" - -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Konto użytkownika zostało aktywowane" -#: Controller/Traits/UserValidationTrait.php:44 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Aktywacja konta nie powiodła się" -#: Controller/Traits/UserValidationTrait.php:47 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Konto jest już aktywne" -#: Controller/Traits/UserValidationTrait.php:53 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Token zmiany hasła został zatwierdzony" -#: Controller/Traits/UserValidationTrait.php:58 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "Błąd podczas walidacji tokenu zmiany hasła" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Nieprawidłowy typ walidacji" -#: Controller/Traits/UserValidationTrait.php:65 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Nieprawidłowy token lub konto jest już aktywne" -#: Controller/Traits/UserValidationTrait.php:67 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token wygasł" -#: Controller/Traits/UserValidationTrait.php:93 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "Token został zresetowany. Sprawdź swoją pocztę e-mail." -#: Controller/Traits/UserValidationTrait.php:102 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "Użytkownik {0} jest już aktywny" -#: Email/EmailSender.php:39 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Nazwa użytkownika lub hasło jest nieprawidłowe" + +#: Loader/LoginComponentLoader.php:51 +msgid "Could not proceed with social account. Please try again" +msgstr "" + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Twoje konto nie zostało jeszcze aktywowane. Sprawdź swoją skrzynkę odbiorczą" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Konto społecznościowe nie zostało jeszcze aktywowane. Sprawdź swoją pocztę e-" +"mail." + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Twój link aktywacyjny" -#: Mailer/UsersMailer.php:55 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0}Twój link zmiany hasła" -#: Mailer/UsersMailer.php:78 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0} Twój link aktywacyjny" -#: Model/Behavior/PasswordBehavior.php:56 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Podaj swój adres e-mail" + +#: Middleware/SocialAuthMiddleware.php:75 +msgid "Could not identify your account, please try again" +msgstr "" + +#: Model/Behavior/AuthFinderBehavior.php:48 +msgid "Missing 'username' in options data" +msgstr "Brak klucza 'username'" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "" + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "Wartość nie może być pusta" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "Parametr 'expiration' nie może być pusty" -#: Model/Behavior/PasswordBehavior.php:67;116 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Nie odnaleziono użytkownika" -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:111 +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Konto użytkownika zostało już aktywowane" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "Użytkownik nie jest aktywny" -#: Model/Behavior/PasswordBehavior.php:121 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "Bieżące hasło nie jest prawidłowe" -#: Model/Behavior/PasswordBehavior.php:124 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Nowe hasło nie może być takie same jak obecne" -#: Model/Behavior/RegisterBehavior.php:89 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Nie odnaleziono użytkownika dla danego tokenu i adresu e-mail." -#: Model/Behavior/RegisterBehavior.php:92 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "Token wygasł" +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Pole wymagane" + #: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "Konto zostało już aktywowane" @@ -316,161 +304,163 @@ msgstr "Konto zostało już aktywowane" msgid "Account not found for the given token and email." msgstr "Nie odnaleziono konta dla danego tokenu i adresu e-mail." -#: Model/Behavior/SocialBehavior.php:56 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Nie można zalogować użytkownika o loginie {0}" -#: Model/Behavior/SocialBehavior.php:98 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Brak adresu e-mail" -#: Model/Table/UsersTable.php:82 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "Podane hasła różnią się. Spróbuj ponownie." -#: Model/Table/UsersTable.php:175 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Nazwa użytkownika już istnieje" -#: Model/Table/UsersTable.php:181 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Adres e-mail już istnieje" -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "Brak klucza 'username'" - -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Narzędzia dla CakeDC Users Plugin" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Aktywuj użytkownika" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "Dodaj konto administracyjne do celów testowych" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Dodaj użytkownika" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" msgstr "Zmień rolę konkretnego użytkownika" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Deaktywuj użytkownika" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Usuń użytkownika" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Zresetuj hasło przez e-mail" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Zresetuj hasło dla wszystkich użytkowników" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Zresetuj hasło dla konkretnego użytkownika" -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "Dodano użytkownika:" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "Id: {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "Nazwa użytkownika: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "E-mail: {0}" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "Hasło: {0}" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "Dodano super użytkownika:" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "Nie można dodać super użytkownika:" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "Pole: {0} Błąd: {1}" - -#: Shell/UsersShell.php:153;179 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Podaj hasło." -#: Shell/UsersShell.php:157 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Zmieniono hasło dla wszystkich użytkowników" -#: Shell/UsersShell.php:158;186 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nowe hasło: {0}" -#: Shell/UsersShell.php:176;204;282;324 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Podaj nazwę użytkownika." -#: Shell/UsersShell.php:185 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Zmieniono hasło dla użytkownika: {0}" -#: Shell/UsersShell.php:207 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Podaj rolę." -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Zmieniono rolę dla użytkownika: {0}" -#: Shell/UsersShell.php:214 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Nowa rola: {0}" -#: Shell/UsersShell.php:229 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "Użytkownik został aktywowany: {0}" -#: Shell/UsersShell.php:244 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "Użytkownik został zdeaktywowany: {0}" -#: Shell/UsersShell.php:256 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Podaj nazwę użytkownika lub hasło." -#: Shell/UsersShell.php:264 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" msgstr "Aby dokończyć zmianę hasła, sprawdź swoją pocztę e-mail" -#: Shell/UsersShell.php:302 +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Dodano super użytkownika:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Dodano użytkownika:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Nazwa użytkownika: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "E-mail: {0}" + +#: Shell/UsersShell.php:315 +#, fuzzy +msgid "Role: {0}" +msgstr "Nowa rola: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Hasło: {0}" + +#: Shell/UsersShell.php:318 +#, fuzzy +msgid "User could not be added:" +msgstr "Nie można dodać super użytkownika:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Pole: {0} Błąd: {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "Nie odnaleziono użytkownika." -#: Shell/UsersShell.php:332 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "Usuwanie użytkownika {0} nie powiodło się. Spróbuj ponownie" -#: Shell/UsersShell.php:334 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "Użytkownik {0} został usunięty" @@ -490,19 +480,20 @@ msgstr "Kontynuuj zmianę hasła" #: Template/Email/html/reset_password.ctp:27 #: Template/Email/html/social_account_validation.ctp:32 #: Template/Email/html/validation.ctp:27 +#, fuzzy msgid "" -"If the link is not correcly displayed, please copy the following address in " +"If the link is not correctly displayed, please copy the following address in " "your web browser {0}" msgstr "" "Jeśli link nie jest wyświetlony poprawnie, skopiuj łącze do paska adresu " "przeglądarki {0}" -#: Template/Email/html/reset_password.ctp:30 -#: Template/Email/html/social_account_validation.ctp:35 -#: Template/Email/html/validation.ctp:30 -#: Template/Email/text/reset_password.ctp:24 -#: Template/Email/text/social_account_validation.ctp:26 -#: Template/Email/text/validation.ctp:24 +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 msgid "Thank you" msgstr "Dziękujemy" @@ -525,54 +516,55 @@ msgid "" "social login {0}" msgstr "Skopiuj łącze do paska adresu przeglądarki, aby aktywować konto {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:15 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15;79 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Akcje" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:26 -#: Template/Users/view.ctp:19 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 +#: Template/Users/view.ctp:23 msgid "List Users" msgstr "Użytkownicy" -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "Konta" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Dodaj użytkownika" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:27 -#: Template/Users/register.ctp:18 Template/Users/view.ctp:30;70 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Nazwa użytkownika" -#: Template/Users/add.ctp:25 Template/Users/edit.ctp:36 -#: Template/Users/index.ctp:23 Template/Users/profile.ctp:29 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:32 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "E-mail" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:25 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "Hasło" + +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Imię" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Nazwisko" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 -#: Template/Users/view.ctp:44;75 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 +#: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Aktywny" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:35 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -591,49 +583,57 @@ msgstr "Obecne hasło" msgid "New password" msgstr "Nowe hasło" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:23 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Potwierdź hasło" -#: Template/Users/edit.ctp:20 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:101 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Usuń" -#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:18;101 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Czy na pewno usunąć # {0}?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Edytuj użytkownika" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:38;73 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Token" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Token wygasa" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "API token" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Data aktywacji" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "Data (TOS)" +#: Template/Users/edit.ctp:64 +#, fuzzy +msgid "Reset Google Authenticator Token" +msgstr "Token zmiany hasła został zatwierdzony" + +#: Template/Users/edit.ctp:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Nowy {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Szczegóły" @@ -641,7 +641,7 @@ msgstr "Szczegóły" msgid "Change password" msgstr "Zmień hasło" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:99 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Edytuj" @@ -673,39 +673,36 @@ msgstr "Zresetuj hasło" msgid "Login" msgstr "Zaloguj" -#: Template/Users/profile.ctp:18 View/Helper/UserHelper.php:51 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" -#: Template/Users/profile.ctp:24 +#: Template/Users/profile.ctp:27 +#, fuzzy msgid "Change Password" msgstr "Zmień hasło" -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Konta społecznościowe" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:72 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Avatar" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:69 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Dostawca" -#: Template/Users/profile.ctp:40 +#: Template/Users/profile.ctp:44 msgid "Link" msgstr "Link" -#: Template/Users/profile.ctp:47 +#: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Link do {0}" -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "Hasło" - -#: Template/Users/register.ctp:28 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Akceptuję warunki użytkowania" @@ -721,91 +718,93 @@ msgstr "Ponownie wyślij e-mail aktywacyjny" msgid "Email or username" msgstr "E-mail lub nazwa użytkownika" -#: Template/Users/view.ctp:18 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Usuń użytkownika" -#: Template/Users/view.ctp:20 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Nowy użytkownik" -#: Template/Users/view.ctp:28;67 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Id" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Imię" -#: Template/Users/view.ctp:36 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Nazwisko" -#: Template/Users/view.ctp:40 +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nowa rola: {0}" + +#: Template/Users/view.ctp:45 msgid "Api Token" msgstr "Api Token" -#: Template/Users/view.ctp:48;74 +#: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Token wygasa" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Data aktywacji" -#: Template/Users/view.ctp:52 +#: Template/Users/view.ctp:57 +#, fuzzy msgid "Tos Date" msgstr "Data (TOS)" -#: Template/Users/view.ctp:54;77 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Utworzono" -#: Template/Users/view.ctp:56;78 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Zmodyfikowano" -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "Powiązane konta" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "Id użytkownika" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "Reference" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "Dane" - #: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Zaloguj się za pomocą" -#: View/Helper/UserHelper.php:49 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:52 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:91 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Wyloguj" -#: View/Helper/UserHelper.php:108 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Witaj, {0}" -#: View/Helper/UserHelper.php:131 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "reCaptcha nie zostało skonfigurowane! Skonfiguruj opcję Users.reCaptcha.key" -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "Pole wymagane" +#: View/Helper/UserHelper.php:215 +#, fuzzy +msgid "Connected with {0}" +msgstr "Id: {0}" + +#: View/Helper/UserHelper.php:220 +#, fuzzy +msgid "Connect with {0}" +msgstr "Id: {0}" diff --git a/resources/locales/pt_BR/users.mo b/resources/locales/pt_BR/users.mo new file mode 100644 index 000000000..b9dc63f31 Binary files /dev/null and b/resources/locales/pt_BR/users.mo differ diff --git a/src/Locale/pt_BR/Users.po b/resources/locales/pt_BR/users.po similarity index 68% rename from src/Locale/pt_BR/Users.po rename to resources/locales/pt_BR/users.po index 760b5c903..6c4e524a0 100644 --- a/src/Locale/pt_BR/Users.po +++ b/resources/locales/pt_BR/users.po @@ -1,189 +1,146 @@ # LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2015, Cake Development Corporation (http://cakedc.com) +# Copyright YEAR NAME # msgid "" msgstr "" "Project-Id-Version: CakeDC Users\n" -"POT-Creation-Date: 2017-10-14 23:45+0000\n" -"PO-Revision-Date: 2017-10-14 21:49-0300\n" -"Last-Translator: Livio Ribeiro \n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 12:41-0200\n" "Language-Team: CakeDC \n" -"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 2.0.4\n" - -#: Auth/SocialAuthenticate.php:456 -msgid "Provider cannot be empty" -msgstr "O provedor não pode ser vazio" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: pt_BR\n" -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Conta validada com êxito" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "A conta não pode ser validada" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Token e/ou conta social inválido(s)" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Conta social já ativa" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "A conta social não pode ser validada" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Email enviado com sucesso" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "O email não pode ser enviado" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Conta inválida" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "O email não pode ser reenviado" -#: Controller/Component/RememberMeComponent.php:68 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Salt da aplicação inválido, o salt da aplicação deve ser de pelo menos 256 " -"bits (32 bytes)" - -#: Controller/Component/UsersAuthComponent.php:204 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" -"Você não pode habilitar o fluxo de validação por email se use_email for false" - -#: Controller/Traits/LinkSocialTrait.php:54 +#: Controller/Traits/LinkSocialTrait.php:52 msgid "Could not associate account, please try again." msgstr "Não foi possível associar a conta, tente novamente." -#: Controller/Traits/LinkSocialTrait.php:77 +#: Controller/Traits/LinkSocialTrait.php:76 msgid "Social account was associated." msgstr "A conta social foi associada." -#: Controller/Traits/LoginTrait.php:104 -msgid "Issues trying to log in with your social account" -msgstr "Problemas ao tentar autenticar a partir da sua conta social" - -#: Controller/Traits/LoginTrait.php:109 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Por favor indique seu email" - -#: Controller/Traits/LoginTrait.php:120 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Seu usuário ainda não foi validado. Por favor, verifique sua caixa de " -"entrada para instruções" - -#: Controller/Traits/LoginTrait.php:125 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Sua conta social ainda não foi validada. Por favor, verifique sua caixa de " -"entrada para instruções" - -#: Controller/Traits/LoginTrait.php:180 Controller/Traits/RegisterTrait.php:82 -msgid "Invalid reCaptcha" -msgstr "ReCaptcha inválido" - -#: Controller/Traits/LoginTrait.php:191 -msgid "You are already logged in" -msgstr "Você já está autenticado" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Você desconectou-se com êxito" -#: Controller/Traits/LoginTrait.php:212 +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 msgid "Please enable Google Authenticator first." msgstr "Por favor habilite o Google Authenticator primeiro." -#: Controller/Traits/LoginTrait.php:287 -msgid "Verification code is invalid. Try again" -msgstr "Código de verificação é inválido. Tente novamente" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +msgid "Could not find user data" +msgstr "Não foi possivel verificar os dados de usuário" -#: Controller/Traits/LoginTrait.php:340 -msgid "Username or password is incorrect" -msgstr "Nome de usuário e/ou senha incorreto(s)" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +msgid "Could not verify, please try again" +msgstr "Não foi possível verificar, por favor, tente novamente" -#: Controller/Traits/LoginTrait.php:363 -msgid "You've successfully logged out" -msgstr "Você desconectou-se com êxito" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Código de verificação é inválido. Tente novamente" -#: Controller/Traits/PasswordManagementTrait.php:49;82 -#: Controller/Traits/ProfileTrait.php:50 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "O usuário não foi encontrado" -#: Controller/Traits/PasswordManagementTrait.php:70;78;86 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "A senha não pode ser alterada" -#: Controller/Traits/PasswordManagementTrait.php:74 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Senha alterada com êxito" -#: Controller/Traits/PasswordManagementTrait.php:84 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:127 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "" "Por favor, verifique seu email para continuar o processo de redefinição de " "senha" -#: Controller/Traits/PasswordManagementTrait.php:130 Shell/UsersShell.php:247 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "O token de senha não pode ser gerado. Por favor, tente novamente" -#: Controller/Traits/PasswordManagementTrait.php:136 -#: Controller/Traits/UserValidationTrait.php:107 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "O usuário {0} não foi encontrado" -#: Controller/Traits/PasswordManagementTrait.php:138 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "O usuário não está ativo" -#: Controller/Traits/PasswordManagementTrait.php:140 -#: Controller/Traits/UserValidationTrait.php:102;111 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "O token não pode ser redefinido" -#: Controller/Traits/PasswordManagementTrait.php:164 +#: Controller/Traits/PasswordManagementTrait.php:174 msgid "Google Authenticator token was successfully reset" msgstr "O token do Google Authenticator foi reiniciado com sucesso" -#: Controller/Traits/ProfileTrait.php:54 +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Não autorizado, por favor, autentique-se primeiro" -#: Controller/Traits/RegisterTrait.php:43 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Você deve deslogar para registrar uma nova conta de usuário" -#: Controller/Traits/RegisterTrait.php:89 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "O usuário não pode ser salvo" -#: Controller/Traits/RegisterTrait.php:123 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "ReCaptcha inválido" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Você registrou-se com sucesso, por favor, autentique-se" -#: Controller/Traits/RegisterTrait.php:125 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Por favor, valide sua conta antes de autenticar" @@ -203,63 +160,92 @@ msgstr "O {0} foi deletado" msgid "The {0} could not be deleted" msgstr "O {0} não pode ser deletado" -#: Controller/Traits/SocialTrait.php:40 -msgid "The reCaptcha could not be validated" -msgstr "O reCaptcha não pode ser validado" - -#: Controller/Traits/UserValidationTrait.php:43 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Conta de usuário validada com êxito" -#: Controller/Traits/UserValidationTrait.php:45 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "A conta de usuário não pode ser validada" -#: Controller/Traits/UserValidationTrait.php:48 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Usuário já ativo" -#: Controller/Traits/UserValidationTrait.php:54 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Token de redefinição de senha validado com êxito" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "O token de redefinição de senha não pode ser validado" -#: Controller/Traits/UserValidationTrait.php:66 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Tipo de validação inválido" -#: Controller/Traits/UserValidationTrait.php:69 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Token inválido ou conta já validada" -#: Controller/Traits/UserValidationTrait.php:71 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token expirado" -#: Controller/Traits/UserValidationTrait.php:97 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "O token foi redefinido com êxito. Por favor, verifique seu email." -#: Controller/Traits/UserValidationTrait.php:109 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "O usuário {0} já está ativo" -#: Mailer/UsersMailer.php:34 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Nome de usuário e/ou senha incorreto(s)" + +#: Loader/LoginComponentLoader.php:51 +msgid "Could not proceed with social account. Please try again" +msgstr "" +"Não foi possível prosseguir com a conta social. Por favor, tente novamente." + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Seu usuário ainda não foi validado. Por favor, verifique sua caixa de " +"entrada para instruções" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Sua conta social ainda não foi validada. Por favor, verifique sua caixa de " +"entrada para instruções" + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Seu link de validação da conta" -#: Mailer/UsersMailer.php:52 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0}Seu link de redefinição de senha" -#: Mailer/UsersMailer.php:75 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0}Seu link de validação da conta social" -#: Model/Behavior/AuthFinderBehavior.php:49 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Por favor indique seu email" + +#: Middleware/SocialAuthMiddleware.php:75 +msgid "Could not identify your account, please try again" +msgstr "Não foi possível identificar a sua conta, por favor, tente novamente." + +#: Model/Behavior/AuthFinderBehavior.php:48 msgid "Missing 'username' in options data" msgstr "'username' ausente nas opções" @@ -275,12 +261,12 @@ msgstr "A referência não pode ser nula" msgid "Token expiration cannot be empty" msgstr "A expiração do token não pode ser vazia" -#: Model/Behavior/PasswordBehavior.php:56;117 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Usuário não encontrado" #: Model/Behavior/PasswordBehavior.php:60 -#: Model/Behavior/RegisterBehavior.php:112;205 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Conta de usuário já validada" @@ -288,11 +274,11 @@ msgstr "Conta de usuário já validada" msgid "User not active" msgstr "Usuário inativo" -#: Model/Behavior/PasswordBehavior.php:122 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "A senha atual não corresponde" -#: Model/Behavior/PasswordBehavior.php:125 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Você não pode usar a senha atual como nova senha" @@ -304,32 +290,36 @@ msgstr "Usuário não encontrado com a combinação de token e email" msgid "Token has already expired user with no token" msgstr "Token expirado, usuário sem token" -#: Model/Behavior/SocialAccountBehavior.php:103;130 +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Este campo é requerido" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "Conta já validada" -#: Model/Behavior/SocialAccountBehavior.php:106;133 +#: Model/Behavior/SocialAccountBehavior.php:105;132 msgid "Account not found for the given token and email." msgstr "Conta não encontrada com a combinação de token e email" -#: Model/Behavior/SocialBehavior.php:82 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Incapaz de autenticar usuário com a referência {0}" -#: Model/Behavior/SocialBehavior.php:121 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Email ausente" -#: Model/Table/UsersTable.php:81 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "" "Sua senha não corresponde com a confirmação. Por favor, tente novamente" -#: Model/Table/UsersTable.php:173 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Nome de usuário em uso" -#: Model/Table/UsersTable.php:179 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Email em uso" @@ -351,7 +341,7 @@ msgstr "Adicionar um novo usuário" #: Shell/UsersShell.php:69 msgid "Change the role for an specific user" -msgstr "Alterar o role de um usuário específico" +msgstr "Alterar o papel de um usuário específico" #: Shell/UsersShell.php:72 msgid "Deactivate an specific user" @@ -526,53 +516,57 @@ msgstr "" "Por favor, copie o endereço a seguir em seu navegador para ativar sua " "autenticação social {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:16 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 #: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Ações" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:27 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 #: Template/Users/view.ctp:23 msgid "List Users" msgstr "Listar usuários" -#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Adicionar usuário" -#: Template/Users/add.ctp:23 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:30 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:33 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Nome de usuário" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:36 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 #: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 -#: Template/Users/register.ctp:20 Template/Users/view.ctp:35 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "Email" -#: Template/Users/add.ctp:25 Template/Users/register.ctp:21 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 msgid "Password" msgstr "Senha" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 +#, fuzzy msgid "First name" msgstr "Nome" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:27 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 +#, fuzzy msgid "Last name" msgstr "Sobrenome" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 #: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Ativo" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:36 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -591,48 +585,49 @@ msgstr "Senha atual" msgid "New password" msgstr "Nova senha" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Confirmar senha" -#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Deletar" -#: Template/Users/edit.ctp:23 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 #: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Tem certeza que deseja deletar # {0}?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Editar usuário" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Token" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Token expira" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "Token da API" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 +#, fuzzy msgid "Activation date" msgstr "Data de ativação" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "Data TOS" -#: Template/Users/edit.ctp:63 +#: Template/Users/edit.ctp:64 msgid "Reset Google Authenticator Token" msgstr "Redefir Token do Google Authenticator" -#: Template/Users/edit.ctp:69 +#: Template/Users/edit.ctp:70 msgid "Are you sure you want to reset token for user \"{0}\"?" msgstr "Você tem certeza que deseja redefinir o token para o usuário “{0}”?" @@ -680,7 +675,7 @@ msgstr "Resetar Senha" msgid "Login" msgstr "Autenticar" -#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" @@ -708,7 +703,7 @@ msgstr "Link" msgid "Link to {0}" msgstr "Link para {0}" -#: Template/Users/register.ctp:29 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Concordar com Termos de Uso?" @@ -772,6 +767,7 @@ msgid "Activation Date" msgstr "Data de ativação" #: Template/Users/view.ctp:57 +#, fuzzy msgid "Tos Date" msgstr "Data TOS" @@ -783,126 +779,27 @@ msgstr "Criado" msgid "Modified" msgstr "Modificado" -#: View/Helper/UserHelper.php:45 +#: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Logar com" -#: View/Helper/UserHelper.php:48 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:57 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:106 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Desconectar" -#: View/Helper/UserHelper.php:123 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Bem-vindo(a), {0}" -#: View/Helper/UserHelper.php:146 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "reCaptcha não está configurado! Por favor, configure Users.reCaptcha.key" -#: View/Helper/UserHelper.php:205 -#, fuzzy -#| msgid "btn btn-social btn-{0} " -msgid "btn btn-social btn-{0}" -msgstr "btn btn-social btn-{0}" - -#: View/Helper/UserHelper.php:211 +#: View/Helper/UserHelper.php:215 msgid "Connected with {0}" msgstr "Conectador com {0}" -#: View/Helper/UserHelper.php:216 +#: View/Helper/UserHelper.php:220 msgid "Connect with {0}" msgstr "Conectar com {0}" - -#~ msgid "Type {0} is not valid" -#~ msgstr "Tipo {0} não é válido" - -#~ msgid "Type {0} has no associated callable" -#~ msgstr "Tipo {0} não tem chamada associada" - -#~ msgid "SSL is required for ApiKey Authentication" -#~ msgstr "SSL é requerido para autenticação por chave da API." - -#~ msgid "" -#~ "Missing configuration file: \"config/{0}.php\". Using default permissions" -#~ msgstr "" -#~ "Arquivo de configuração ausente: \"config/{0}.php\". Usando permissões " -#~ "padrão" - -#~ msgid "" -#~ "Table alias is empty, please define a table alias, we could not extract a " -#~ "default table from the request" -#~ msgstr "" -#~ "O alias da tabela está vazio, por favor, defina um alias de tabela, nós " -#~ "não pudemos extrair uma tabela padrão da requisição" - -#~ msgid "" -#~ "Missing column {0} in table {1} while checking ownership permissions for " -#~ "user {2}" -#~ msgstr "" -#~ "Coluna {0} ausente na tabela {1} ao verificar permissões de propriedade " -#~ "do usuário {2}" - -#~ msgid "" -#~ "If the link is not correcly displayed, please copy the following address " -#~ "in your web browser {0}" -#~ msgstr "" -#~ "Se o link não estiver sendo exibido corretamente, por favor, copie o " -#~ "seguinte endereço em seu navegador {0}" - -#~ msgid "List Accounts" -#~ msgstr "Listar contas" - -#~ msgid "Related Accounts" -#~ msgstr "Contas relacionadas" - -#~ msgid "User Id" -#~ msgstr "Id de usuário" - -#~ msgid "Reference" -#~ msgstr "Referência" - -#~ msgid "Data" -#~ msgstr "Dados" - -#~ msgid "This field is required" -#~ msgstr "Este campo é obrigatório" - -#~ msgid "The old password does not match" -#~ msgstr "A senha antiga não confere" - -#~ msgid "SocialAccount already active" -#~ msgstr "Conta social já ativa" - -#~ msgid "" -#~ "The social account is not active. Please check your email for " -#~ "instructions. {0}" -#~ msgstr "" -#~ "A conta social não está ativa. Por favor verifique seu email para " -#~ "instruções. {0}" - -#~ msgid "There was an error associating your social network account" -#~ msgstr "Houve um erro associando sua conta social" - -#~ msgid "Invalid token and/or email" -#~ msgstr "Token e/ou email inválido(s)" - -#~ msgid "The \"tos\" property is not present" -#~ msgstr "A propriedade “tos” não está presente" - -#~ msgid "+ {0} secs" -#~ msgstr "+ {0} segundos" - -#~ msgid "Sign in with Facebook" -#~ msgstr "Inscrever-se com Facebook" - -#~ msgid "Sign in with Twitter" -#~ msgstr "Inscrever-se com Twitter" diff --git a/resources/locales/sv/users.mo b/resources/locales/sv/users.mo new file mode 100644 index 000000000..d8b8a15df Binary files /dev/null and b/resources/locales/sv/users.mo differ diff --git a/src/Locale/sv/Users.po b/resources/locales/sv/users.po similarity index 71% rename from src/Locale/sv/Users.po rename to resources/locales/sv/users.po index 839edfdc3..4d952389f 100644 --- a/src/Locale/sv/Users.po +++ b/resources/locales/sv/users.po @@ -1,186 +1,146 @@ # LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2015, Cake Development Corporation (http://cakedc.com) +# Copyright YEAR NAME # msgid "" msgstr "" -"Project-Id-Version: Users\n" -"POT-Creation-Date: 2017-10-25 16:18+0200\n" -"PO-Revision-Date: 2017-10-25 16:24+0200\n" -"Last-Translator: Ulrik Södergren \n" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:21-0200\n" "Language-Team: CakeDC \n" -"Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.8.12\n" - -#: Auth/SocialAuthenticate.php:456 -msgid "Provider cannot be empty" -msgstr "Leverantör kan inte vara tomt" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: sv\n" -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Kontot har validerats" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "Konto kunde inte valideras" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Ogiltig token och/eller social konto" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Socialt konto redan aktivt" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "Socialt konto kunde inte valideras" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Epost har skickats." -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "Epost kunde inte skickas" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Ogiltigt konto" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "E-post kunde inte skickas om" -#: Controller/Component/RememberMeComponent.php:68 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Ogiltig app salt, måste app salt vara åtminstone 256 bitar (32 byte) långt" - -#: Controller/Component/UsersAuthComponent.php:204 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" -"Du kan inte aktivera epost-validering arbetsflöde om use_email är falskt" - -#: Controller/Traits/LinkSocialTrait.php:54 +#: Controller/Traits/LinkSocialTrait.php:52 msgid "Could not associate account, please try again." msgstr "Kunde inte ansluta konto, försök igen." -#: Controller/Traits/LinkSocialTrait.php:77 +#: Controller/Traits/LinkSocialTrait.php:76 msgid "Social account was associated." msgstr "Socialt konto anslöts." -#: Controller/Traits/LoginTrait.php:104 -msgid "Issues trying to log in with your social account" -msgstr "Problem vid inloggning med ditt sociala konto" - -#: Controller/Traits/LoginTrait.php:109 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Lägg till din epost" - -#: Controller/Traits/LoginTrait.php:120 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Ditt konto har inte verifierats ännu. Vänligen kontrollera din inbox för " -"instruktioner" - -#: Controller/Traits/LoginTrait.php:125 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Ditt sociala konto har inte validerats ännu. Kontrollera din inkorg för " -"instruktioner" - -#: Controller/Traits/LoginTrait.php:180 Controller/Traits/RegisterTrait.php:82 -msgid "Invalid reCaptcha" -msgstr "Ogiltig reCaptcha" - -#: Controller/Traits/LoginTrait.php:191 -msgid "You are already logged in" -msgstr "Du är redan inloggad." +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Du har loggats ut" -#: Controller/Traits/LoginTrait.php:212 +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 msgid "Please enable Google Authenticator first." msgstr "Vänligen aktivera Google Authenticator först." -#: Controller/Traits/LoginTrait.php:287 -msgid "Verification code is invalid. Try again" -msgstr "Valideringskoden är ogiltig. Försök igen" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Användare kunde inte läggas till:" -#: Controller/Traits/LoginTrait.php:340 -msgid "Username or password is incorrect" -msgstr "Användarnamn eller lösenord är felaktigt" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Kunde inte ansluta konto, försök igen." -#: Controller/Traits/LoginTrait.php:363 -msgid "You've successfully logged out" -msgstr "Du har loggats ut" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Valideringskoden är ogiltig. Försök igen" -#: Controller/Traits/PasswordManagementTrait.php:49;82 -#: Controller/Traits/ProfileTrait.php:50 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "Användaren hittades inte" -#: Controller/Traits/PasswordManagementTrait.php:70;78;86 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Lösenordet kunde inte ändras" -#: Controller/Traits/PasswordManagementTrait.php:74 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Lösenordsbytet lyckades!" -#: Controller/Traits/PasswordManagementTrait.php:84 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:127 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "Kontrollera din epost för att fortsätta lösenordsåterställningen" -#: Controller/Traits/PasswordManagementTrait.php:130 Shell/UsersShell.php:247 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "Lösenordstoken kunde inte skapas. Var god försök igen" -#: Controller/Traits/PasswordManagementTrait.php:136 -#: Controller/Traits/UserValidationTrait.php:107 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "Användaren {0} hittades inte" -#: Controller/Traits/PasswordManagementTrait.php:138 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "Användaren är inte aktiverad" -#: Controller/Traits/PasswordManagementTrait.php:140 -#: Controller/Traits/UserValidationTrait.php:102;111 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "Token kunde inte återställas" -#: Controller/Traits/PasswordManagementTrait.php:164 +#: Controller/Traits/PasswordManagementTrait.php:174 msgid "Google Authenticator token was successfully reset" msgstr "Google Authenticator token har återställts" -#: Controller/Traits/ProfileTrait.php:54 +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Inte auktoriserad, vänligen logga in först" -#: Controller/Traits/RegisterTrait.php:43 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Du måste logga ut för att registrera ett nytt användarkonto" -#: Controller/Traits/RegisterTrait.php:89 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "Användaren kunde inte sparas" -#: Controller/Traits/RegisterTrait.php:123 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "Ogiltig reCaptcha" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Din registrering är klar, vänligen logga in" -#: Controller/Traits/RegisterTrait.php:125 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Vänligen validera ditt konto innan inloggning" @@ -200,63 +160,93 @@ msgstr "{0} har tagits bort" msgid "The {0} could not be deleted" msgstr "{0} kunde inte tas bort" -#: Controller/Traits/SocialTrait.php:40 -msgid "The reCaptcha could not be validated" -msgstr "reCAPTCHA angavs inte korrekt" - -#: Controller/Traits/UserValidationTrait.php:43 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Användarkontot har validerats" -#: Controller/Traits/UserValidationTrait.php:45 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Användarkontot kunde inte valideras" -#: Controller/Traits/UserValidationTrait.php:48 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Användaren redan aktiv" -#: Controller/Traits/UserValidationTrait.php:54 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Återställning av lösenord lyckades" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "Token för att återställa lösenord kunde inte valideras" -#: Controller/Traits/UserValidationTrait.php:66 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Ogiltig valideringsmetod" -#: Controller/Traits/UserValidationTrait.php:69 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Ogiltig token eller så har användaren konto redan validerats" -#: Controller/Traits/UserValidationTrait.php:71 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token redan löpt ut" -#: Controller/Traits/UserValidationTrait.php:97 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "Token har återställts. Kontrollera din e-post." -#: Controller/Traits/UserValidationTrait.php:109 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "Användaren {0} är redan aktiv" -#: Mailer/UsersMailer.php:34 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Användarnamn eller lösenord är felaktigt" + +#: Loader/LoginComponentLoader.php:51 +#, fuzzy +msgid "Could not proceed with social account. Please try again" +msgstr "Kunde inte ansluta konto, försök igen." + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Ditt konto har inte verifierats ännu. Vänligen kontrollera din inbox för " +"instruktioner" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Ditt sociala konto har inte validerats ännu. Kontrollera din inkorg för " +"instruktioner" + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Din länk för att validera kontot" -#: Mailer/UsersMailer.php:52 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0} Din länk för att återställa lösenord" -#: Mailer/UsersMailer.php:75 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0} Din sociala konto svalideringslänk" -#: Model/Behavior/AuthFinderBehavior.php:49 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Lägg till din epost" + +#: Middleware/SocialAuthMiddleware.php:75 +#, fuzzy +msgid "Could not identify your account, please try again" +msgstr "Kunde inte ansluta konto, försök igen." + +#: Model/Behavior/AuthFinderBehavior.php:48 msgid "Missing 'username' in options data" msgstr "Saknas 'username' i alternativa uppgifter" @@ -272,12 +262,12 @@ msgstr "Referens får inte vara null" msgid "Token expiration cannot be empty" msgstr "Giltighet för token kan inte vara tom" -#: Model/Behavior/PasswordBehavior.php:56;117 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Användaren hittades inte" #: Model/Behavior/PasswordBehavior.php:60 -#: Model/Behavior/RegisterBehavior.php:112;205 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Kontot är redan aktiverat!" @@ -285,11 +275,11 @@ msgstr "Kontot är redan aktiverat!" msgid "User not active" msgstr "Användaren ej aktiv" -#: Model/Behavior/PasswordBehavior.php:122 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "Den nuvarande lösenord matchar inte" -#: Model/Behavior/PasswordBehavior.php:125 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Du kan inte använda det aktuella lösenordet som det nya" @@ -301,31 +291,37 @@ msgstr "Hittade ingen användare som matchar angivet token eller epost" msgid "Token has already expired user with no token" msgstr "Token har redan gått ut eller användare utan token" -#: Model/Behavior/SocialAccountBehavior.php:103;130 +#: Model/Behavior/RegisterBehavior.php:151 +#, fuzzy +msgid "This field is required" +msgstr "Fält: {0} fel: {1}" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 +#, fuzzy msgid "Account already validated" msgstr "Kontot är redan aktiverat!" -#: Model/Behavior/SocialAccountBehavior.php:106;133 +#: Model/Behavior/SocialAccountBehavior.php:105;132 msgid "Account not found for the given token and email." msgstr "Kontot hittades inte för givet token och e-post." -#: Model/Behavior/SocialBehavior.php:82 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Det går inte att logga in användaren med refrens {0}" -#: Model/Behavior/SocialBehavior.php:121 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Epost saknas" -#: Model/Table/UsersTable.php:81 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "Dina lösenord matchar inte. Vänligen försök igen." -#: Model/Table/UsersTable.php:173 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Användarnamnet är upptaget" -#: Model/Table/UsersTable.php:179 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "E-postadressen finns redan" @@ -522,53 +518,55 @@ msgstr "" "Kopiera följande adress till din webbläsare för att aktivera din sociala " "inloggning {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:16 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 #: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Resurser" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:27 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 #: Template/Users/view.ctp:23 msgid "List Users" msgstr "Lista användare" -#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Ny användare" -#: Template/Users/add.ctp:23 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:30 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:33 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Användarnamn" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:36 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 #: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 -#: Template/Users/register.ctp:20 Template/Users/view.ctp:35 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "Epost" -#: Template/Users/add.ctp:25 Template/Users/register.ctp:21 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 msgid "Password" msgstr "Lösenord" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Förnamn" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:27 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Efternamn" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 #: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Aktivt" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:36 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -587,48 +585,48 @@ msgstr "Nuvarande lösenord" msgid "New password" msgstr "Nytt lösenord" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Bekräfta lösenord" -#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Radera" -#: Template/Users/edit.ctp:23 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 #: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Är du säker på att du vill radera{0}" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Redigera användare" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Token" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Token förfaller" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "API-token" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Aktiveringsdatum" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "TOS datum" -#: Template/Users/edit.ctp:63 +#: Template/Users/edit.ctp:64 msgid "Reset Google Authenticator Token" msgstr "Återställ Google Authenticator Token" -#: Template/Users/edit.ctp:69 +#: Template/Users/edit.ctp:70 msgid "Are you sure you want to reset token for user \"{0}\"?" msgstr "Är du säker du vill återställa token för användare \"{0}\"?" @@ -676,7 +674,7 @@ msgstr "Återställ Lösenord" msgid "Login" msgstr "Logga in" -#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" @@ -704,7 +702,7 @@ msgstr "Länk" msgid "Link to {0}" msgstr "Länk till {0}" -#: Template/Users/register.ctp:29 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Jag accepterar villkoren" @@ -745,10 +743,12 @@ msgid "Id" msgstr "Id" #: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Förnamn" #: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Efternamn" @@ -761,10 +761,12 @@ msgid "Api Token" msgstr "Api-token" #: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Token förfaller" #: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Aktiveringsdatum" @@ -780,85 +782,26 @@ msgstr "Skapad" msgid "Modified" msgstr "Ändrad" -#: View/Helper/UserHelper.php:45 +#: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Logga in med" -#: View/Helper/UserHelper.php:48 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:57 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:106 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Logga ut" -#: View/Helper/UserHelper.php:123 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Välkommen, {0}" -#: View/Helper/UserHelper.php:146 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "reCaptcha är inte konfigurerad! Konfigurera Users.reCaptcha.key" -#: View/Helper/UserHelper.php:205 -msgid "btn btn-social btn-{0}" -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:211 +#: View/Helper/UserHelper.php:215 msgid "Connected with {0}" msgstr "Ansluten med {0}" -#: View/Helper/UserHelper.php:216 +#: View/Helper/UserHelper.php:220 msgid "Connect with {0}" msgstr "Anslut med {0}" - -#~ msgid "Type {0} is not valid" -#~ msgstr "Typ {0} är inte giltig" - -#~ msgid "Type {0} has no associated callable" -#~ msgstr "Typ {0} har inget tillhörande anrop" - -#~ msgid "SSL is required for ApiKey Authentication" -#~ msgstr "SSL krävs för ApiKey-autentisering" - -#~ msgid "" -#~ "Missing configuration file: \"config/{0}.php\". Using default permissions" -#~ msgstr "" -#~ "Saknad konfigurationsfil: \"config / {0} .php\". Använder " -#~ "standardbehörigheter" - -#~ msgid "" -#~ "Table alias is empty, please define a table alias, we could not extract a " -#~ "default table from the request" -#~ msgstr "" -#~ "Tabellalias är tomt, var vänlig ange ett tabellalias, vi kunde inte " -#~ "extrahera någon standardtabell" - -#~ msgid "" -#~ "Missing column {0} in table {1} while checking ownership permissions for " -#~ "user {2}" -#~ msgstr "" -#~ "Saknar kolumn {0} i tabell {1} vid kontroll av ägarbehörigheter för " -#~ "användare {2}" - -#~ msgid "List Accounts" -#~ msgstr "Lista konton" - -#~ msgid "Related Accounts" -#~ msgstr "Relaterade konton" - -#~ msgid "User Id" -#~ msgstr "Användar-ID" - -#~ msgid "Reference" -#~ msgstr "Referens" - -#~ msgid "Data" -#~ msgstr "Data" - -#~ msgid "This field is required" -#~ msgstr "Detta fält är obligatoriskt" diff --git a/resources/locales/tr_TR/users.mo b/resources/locales/tr_TR/users.mo new file mode 100644 index 000000000..66d510510 Binary files /dev/null and b/resources/locales/tr_TR/users.mo differ diff --git a/src/Locale/tr_TR/tr_TR.po b/resources/locales/tr_TR/users.po similarity index 75% rename from src/Locale/tr_TR/tr_TR.po rename to resources/locales/tr_TR/users.po index b7ce116d9..2e321dc67 100644 --- a/src/Locale/tr_TR/tr_TR.po +++ b/resources/locales/tr_TR/users.po @@ -4,185 +4,145 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2017-10-14 23:45+0000\n" -"PO-Revision-Date: 2018-01-22 22:21+0300\n" -"Language-Team: LANGUAGE \n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2019-01-10 13:25-0200\n" +"Language-Team: CakeDC \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.6\n" -"Last-Translator: \n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" "Language: tr_TR\n" -#: Auth/SocialAuthenticate.php:456 -msgid "Provider cannot be empty" -msgstr "Sağlayıcı boş olamaz" - -#: Controller/SocialAccountsController.php:52 +#: Controller/SocialAccountsController.php:50 msgid "Account validated successfully" msgstr "Hesap başarıyla doğrulandı" -#: Controller/SocialAccountsController.php:54 +#: Controller/SocialAccountsController.php:52 msgid "Account could not be validated" msgstr "Hesap doğrulanamadı" -#: Controller/SocialAccountsController.php:57 +#: Controller/SocialAccountsController.php:55 msgid "Invalid token and/or social account" msgstr "Geçersiz jeton ve/veya sosyal hesap" -#: Controller/SocialAccountsController.php:59;87 +#: Controller/SocialAccountsController.php:57;85 msgid "Social Account already active" msgstr "Sosyal Hesap zaten aktif" -#: Controller/SocialAccountsController.php:61 +#: Controller/SocialAccountsController.php:59 msgid "Social Account could not be validated" msgstr "Sosyal Hesap doğrulanamadı" -#: Controller/SocialAccountsController.php:80 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "E-posta başarıyla gönderildi" -#: Controller/SocialAccountsController.php:82 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "E-posta gönderilemedi" -#: Controller/SocialAccountsController.php:85 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Geçersiz hesap" -#: Controller/SocialAccountsController.php:89 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "E-posta tekrar gönderilemedi" -#: Controller/Component/RememberMeComponent.php:68 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" -"Geçersiz tuz kodu, tuz kodu en az 256 bit (32 bayt) “long” uzunluğunda olmalı" - -#: Controller/Component/UsersAuthComponent.php:204 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" -"Eğer use_email yanlışsa, e-posta doğrulama iş akışını etkinleştiremezsin" - -#: Controller/Traits/LinkSocialTrait.php:54 +#: Controller/Traits/LinkSocialTrait.php:52 msgid "Could not associate account, please try again." msgstr "Hesap eşleştirilemedi, lütfen tekrar deneyin." -#: Controller/Traits/LinkSocialTrait.php:77 +#: Controller/Traits/LinkSocialTrait.php:76 msgid "Social account was associated." msgstr "Sosyal hesap zaten eşleştirilmişti." -#: Controller/Traits/LoginTrait.php:104 -msgid "Issues trying to log in with your social account" -msgstr "Sosyal hesabınız ile giriş yaparken çıkan sorunlar" - -#: Controller/Traits/LoginTrait.php:109 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Lütfen e-posta hesabınızı girin" - -#: Controller/Traits/LoginTrait.php:120 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Kullanıcınız henüz doğrulanmadı. Lütfen talimatlar için gelen kutunuzu " -"kontrol edin" - -#: Controller/Traits/LoginTrait.php:125 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Sosyal hesabınız henüz doğrulanmadı. Lütfen talimatlar için gelen kutunuzu " -"kontrol edin" - -#: Controller/Traits/LoginTrait.php:180 Controller/Traits/RegisterTrait.php:82 -msgid "Invalid reCaptcha" -msgstr "Geçersiz reCaptcha" - -#: Controller/Traits/LoginTrait.php:191 -msgid "You are already logged in" -msgstr "Zaten giriş yapmış durumdasınız" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Başarıyla çıkış yaptınız" -#: Controller/Traits/LoginTrait.php:212 +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 msgid "Please enable Google Authenticator first." msgstr "Lütfen önce Google Authenticator ‘ı aktif hale getirin." -#: Controller/Traits/LoginTrait.php:287 -msgid "Verification code is invalid. Try again" -msgstr "Doğrulama kodu yanlış. Tekrar deneyin" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Kullanıcı eklenemedi:" -#: Controller/Traits/LoginTrait.php:340 -msgid "Username or password is incorrect" -msgstr "Kullanıcı adınız veya şifreniz yanlış" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Hesap eşleştirilemedi, lütfen tekrar deneyin." -#: Controller/Traits/LoginTrait.php:363 -msgid "You've successfully logged out" -msgstr "Başarıyla çıkış yaptınız" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Doğrulama kodu yanlış. Tekrar deneyin" -#: Controller/Traits/PasswordManagementTrait.php:49;82 -#: Controller/Traits/ProfileTrait.php:50 +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 msgid "User was not found" msgstr "Kullanıcı bulunamadı" -#: Controller/Traits/PasswordManagementTrait.php:70;78;86 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Şifre değiştirilemedi" -#: Controller/Traits/PasswordManagementTrait.php:74 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Şifre başarıyla değiştirildi" -#: Controller/Traits/PasswordManagementTrait.php:84 -msgid "{0}" -msgstr "{0}" - -#: Controller/Traits/PasswordManagementTrait.php:127 +#: Controller/Traits/PasswordManagementTrait.php:137 msgid "Please check your email to continue with password reset process" msgstr "" "Şifre sıfırlama işleminize devam edebilmek için lütfen e-postanızı kontrol " "edin" -#: Controller/Traits/PasswordManagementTrait.php:130 Shell/UsersShell.php:247 +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 msgid "The password token could not be generated. Please try again" msgstr "Şifre jetonu oluşturulamadı. Lütfen tekrar deneyin" -#: Controller/Traits/PasswordManagementTrait.php:136 -#: Controller/Traits/UserValidationTrait.php:107 +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 msgid "User {0} was not found" msgstr "Kullancı {0} bulunamadı" -#: Controller/Traits/PasswordManagementTrait.php:138 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "Kullanıcı aktif değil" -#: Controller/Traits/PasswordManagementTrait.php:140 -#: Controller/Traits/UserValidationTrait.php:102;111 +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 msgid "Token could not be reset" msgstr "Jeton sıfırlanamadı" -#: Controller/Traits/PasswordManagementTrait.php:164 +#: Controller/Traits/PasswordManagementTrait.php:174 msgid "Google Authenticator token was successfully reset" msgstr "Google Authenticator jetonu başarıyla sıfırlandı" -#: Controller/Traits/ProfileTrait.php:54 +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Yetkili değil, lütfen ilk önce giriş yapın" -#: Controller/Traits/RegisterTrait.php:43 +#: Controller/Traits/RegisterTrait.php:46 msgid "You must log out to register a new user account" msgstr "Yeni bir hesap oluşturmak oluşturmak için önce çıkış yapmanız gerekli" -#: Controller/Traits/RegisterTrait.php:89 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "Kullanıcı kaydedilemedi" -#: Controller/Traits/RegisterTrait.php:123 +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "Geçersiz reCaptcha" + +#: Controller/Traits/RegisterTrait.php:133 msgid "You have registered successfully, please log in" msgstr "Başarıyla kayıt oldunuz, lütfen giriş yapın" -#: Controller/Traits/RegisterTrait.php:125 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Lütfen giriş yapmadan önce hesabınızı doğrulayın" @@ -202,63 +162,93 @@ msgstr "{0} silindi" msgid "The {0} could not be deleted" msgstr "{0} silinemedi" -#: Controller/Traits/SocialTrait.php:40 -msgid "The reCaptcha could not be validated" -msgstr "reCaptcha doğrulanamadı" - -#: Controller/Traits/UserValidationTrait.php:43 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Kullanıcı hesabı başarıyla doğrulandı" -#: Controller/Traits/UserValidationTrait.php:45 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Kullanıcı hesabı doğrulanamadı" -#: Controller/Traits/UserValidationTrait.php:48 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Kullanıcı zaten aktif" -#: Controller/Traits/UserValidationTrait.php:54 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" msgstr "Şifre sıfırlama jetonu başarıyla doğrulandı" -#: Controller/Traits/UserValidationTrait.php:62 +#: Controller/Traits/UserValidationTrait.php:63 msgid "Reset password token could not be validated" msgstr "Şifre sıfırlama jetonu doğrulanamadı" -#: Controller/Traits/UserValidationTrait.php:66 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Geçersiz doğrulama cinsi" -#: Controller/Traits/UserValidationTrait.php:69 +#: Controller/Traits/UserValidationTrait.php:70 msgid "Invalid token or user account already validated" msgstr "Geçersiz jeton veya kullanıcı hesabı zaten doğrulandı" -#: Controller/Traits/UserValidationTrait.php:71 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Jeton süresi zaten doldu" -#: Controller/Traits/UserValidationTrait.php:97 +#: Controller/Traits/UserValidationTrait.php:106 msgid "Token has been reset successfully. Please check your email." msgstr "Jeton başarıyla sıfırlandı. Lütfen e-postanızı kontrol edin." -#: Controller/Traits/UserValidationTrait.php:109 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "Kullanıcı {0} zaten aktif" -#: Mailer/UsersMailer.php:34 +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Kullanıcı adınız veya şifreniz yanlış" + +#: Loader/LoginComponentLoader.php:51 +#, fuzzy +msgid "Could not proceed with social account. Please try again" +msgstr "Hesap eşleştirilemedi, lütfen tekrar deneyin." + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Kullanıcınız henüz doğrulanmadı. Lütfen talimatlar için gelen kutunuzu " +"kontrol edin" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Sosyal hesabınız henüz doğrulanmadı. Lütfen talimatlar için gelen kutunuzu " +"kontrol edin" + +#: Mailer/UsersMailer.php:33 msgid "Your account validation link" msgstr "Hesap doğrulama bağlantısı" -#: Mailer/UsersMailer.php:52 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0} Şifre sıfırlama bağlantısı" -#: Mailer/UsersMailer.php:75 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0} Sosyal hesap doğrulama bağlantısı" -#: Model/Behavior/AuthFinderBehavior.php:49 +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Lütfen e-posta hesabınızı girin" + +#: Middleware/SocialAuthMiddleware.php:75 +#, fuzzy +msgid "Could not identify your account, please try again" +msgstr "Hesap eşleştirilemedi, lütfen tekrar deneyin." + +#: Model/Behavior/AuthFinderBehavior.php:48 msgid "Missing 'username' in options data" msgstr "Seçenekler verilerinde ‘kullanıcı adı’ eksik" @@ -274,12 +264,12 @@ msgstr "Referans boş olamaz" msgid "Token expiration cannot be empty" msgstr "Jeton son kullanma tarihi boş olamaz" -#: Model/Behavior/PasswordBehavior.php:56;117 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Kullanıcı bulunamadı" #: Model/Behavior/PasswordBehavior.php:60 -#: Model/Behavior/RegisterBehavior.php:112;205 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Kullanıcı hesabı zaten doğrulandı" @@ -287,11 +277,11 @@ msgstr "Kullanıcı hesabı zaten doğrulandı" msgid "User not active" msgstr "Kullanıcı aktif durumda değil" -#: Model/Behavior/PasswordBehavior.php:122 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "Geçerli şifre uyuşmuyor" -#: Model/Behavior/PasswordBehavior.php:125 +#: Model/Behavior/PasswordBehavior.php:146 msgid "You cannot use the current password as the new one" msgstr "Geçerli şifrenizi yeni bir şifre olarak kullanamazsınız" @@ -303,31 +293,36 @@ msgstr "Verilen jeton ve e-posta için kullanıcı bulunamadı." msgid "Token has already expired user with no token" msgstr "Jeton son kullanma tarihi zaten doldu, kullanıcının jetonu yok" -#: Model/Behavior/SocialAccountBehavior.php:103;130 +#: Model/Behavior/RegisterBehavior.php:151 +#, fuzzy +msgid "This field is required" +msgstr "Alan: {0} Hata: {1}" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 msgid "Account already validated" msgstr "Hesap zaten doğrulandı" -#: Model/Behavior/SocialAccountBehavior.php:106;133 +#: Model/Behavior/SocialAccountBehavior.php:105;132 msgid "Account not found for the given token and email." msgstr "Verilen jeton ve e-posta için hesap bulunamadı." -#: Model/Behavior/SocialBehavior.php:82 +#: Model/Behavior/SocialBehavior.php:83 msgid "Unable to login user with reference {0}" msgstr "Referans {0} ile giriş başarısız" -#: Model/Behavior/SocialBehavior.php:121 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "E-posta yok" -#: Model/Table/UsersTable.php:81 +#: Model/Table/UsersTable.php:79 msgid "Your password does not match your confirm password. Please try again" msgstr "Şifreniz onay şifreniz ile uyuşmuyor. Lütfen tekrar deneyin" -#: Model/Table/UsersTable.php:173 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Kullanıcı adı zaten var" -#: Model/Table/UsersTable.php:179 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "E-posta zaten var" @@ -524,53 +519,55 @@ msgstr "" "Lütfen sosyal girişi aktif hale getirmek için adresi internet tarayıcınıza " "kopyalayın {0}" -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:16 +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 #: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 msgid "Actions" msgstr "Aksiyonlar" -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:27 +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 #: Template/Users/view.ctp:23 msgid "List Users" msgstr "Kullanıcıları Listele" -#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Kullanıcı Ekle" -#: Template/Users/add.ctp:23 Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 Template/Users/profile.ctp:30 -#: Template/Users/register.ctp:19 Template/Users/view.ctp:33 +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 msgid "Username" msgstr "Kullanıcı Adı" -#: Template/Users/add.ctp:24 Template/Users/edit.ctp:36 +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 #: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 -#: Template/Users/register.ctp:20 Template/Users/view.ctp:35 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 msgid "Email" msgstr "E-posta" -#: Template/Users/add.ctp:25 Template/Users/register.ctp:21 +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 msgid "Password" msgstr "Şifre" -#: Template/Users/add.ctp:26 Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 Template/Users/register.ctp:26 +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 msgid "First name" msgstr "Ad" -#: Template/Users/add.ctp:27 Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 Template/Users/register.ctp:27 +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 msgid "Last name" msgstr "Soyad" -#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 #: Template/Users/view.ctp:49;74 msgid "Active" msgstr "Aktif" #: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 Template/Users/register.ctp:36 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 #: Template/Users/request_reset_password.ctp:8 #: Template/Users/resend_token_validation.ctp:20 #: Template/Users/social_email.ctp:19 @@ -589,48 +586,48 @@ msgstr "Geçerli şifre" msgid "New password" msgstr "Yeni şifre" -#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 msgid "Confirm password" msgstr "Şifre onayla" -#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Sil" -#: Template/Users/edit.ctp:23 Template/Users/index.ctp:40 +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 #: Template/Users/view.ctp:21 msgid "Are you sure you want to delete # {0}?" msgstr "Silmek istediğine emin misin # {0}?" -#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Kullanıcıyı düzenle" -#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 msgid "Token" msgstr "Jeton" -#: Template/Users/edit.ctp:41 +#: Template/Users/edit.ctp:42 msgid "Token expires" msgstr "Jeton Son Kullanma Tarihi" -#: Template/Users/edit.ctp:44 +#: Template/Users/edit.ctp:45 msgid "API token" msgstr "API jetonu" -#: Template/Users/edit.ctp:47 +#: Template/Users/edit.ctp:48 msgid "Activation date" msgstr "Aktivasyon tarihi" -#: Template/Users/edit.ctp:50 +#: Template/Users/edit.ctp:51 msgid "TOS date" msgstr "TOS tarihi" -#: Template/Users/edit.ctp:63 +#: Template/Users/edit.ctp:64 msgid "Reset Google Authenticator Token" msgstr "Google Authenticator Jetonunu Sıfırla" -#: Template/Users/edit.ctp:69 +#: Template/Users/edit.ctp:70 msgid "Are you sure you want to reset token for user \"{0}\"?" msgstr "Kullanıcı {0} için jetonu sıfırlamak istediğine emin misin?" @@ -678,7 +675,7 @@ msgstr "Şifre Sıfırla" msgid "Login" msgstr "Giriş" -#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 +#: Template/Users/profile.ctp:21 msgid "{0} {1}" msgstr "{0} {1}" @@ -706,7 +703,7 @@ msgstr "Bağlantı" msgid "Link to {0}" msgstr "Şuna bağlı {0}" -#: Template/Users/register.ctp:29 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "TOS koşullarını kabul ediyor musun?" @@ -746,10 +743,12 @@ msgid "Id" msgstr "Id" #: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Ad" #: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Soyad" @@ -781,39 +780,27 @@ msgstr "Oluşturulmuş" msgid "Modified" msgstr "Değiştirilmiş" -#: View/Helper/UserHelper.php:45 +#: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Şununla giriş yapın" -#: View/Helper/UserHelper.php:48 -msgid "fa fa-{0}" -msgstr "fa fa-{0}" - -#: View/Helper/UserHelper.php:57 -msgid "btn btn-social btn-{0} " -msgstr "btn btn-social btn-{0} " - -#: View/Helper/UserHelper.php:106 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Çıkış" -#: View/Helper/UserHelper.php:123 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Hoşgeldiniz, {0}" -#: View/Helper/UserHelper.php:146 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" "reCaptcha konfigüre edilmedi! Lütfen konfigüre edin Users.reCaptcha.key" -#: View/Helper/UserHelper.php:205 -msgid "btn btn-social btn-{0}" -msgstr "btn btn-social btn-{0}" - -#: View/Helper/UserHelper.php:211 +#: View/Helper/UserHelper.php:215 msgid "Connected with {0}" msgstr "Şununla bağlanıldı {0}" -#: View/Helper/UserHelper.php:216 +#: View/Helper/UserHelper.php:220 msgid "Connect with {0}" msgstr "Şununla bağlan {0}" diff --git a/resources/locales/uk/users.mo b/resources/locales/uk/users.mo new file mode 100644 index 000000000..074b08d3f Binary files /dev/null and b/resources/locales/uk/users.mo differ diff --git a/resources/locales/uk/users.po b/resources/locales/uk/users.po new file mode 100644 index 000000000..0ea72d3d8 --- /dev/null +++ b/resources/locales/uk/users.po @@ -0,0 +1,800 @@ +# Ukrainian translation of CakePHP Application +# Copyright 2020 Yaroslav Kontsevyi +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2019-01-10 14:38+0000\n" +"PO-Revision-Date: 2020-04-03 14:57+0300\n" +"Language-Team: CakeDC \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 2.3\n" +"Last-Translator: Yaroslav Kontsevyi \n" +"Language: uk\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Обліковий запис успішно підтверджено" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Не вдалося підтвердити обліковий запис" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Недійсний маркер та / або соціальний акаунт" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Соціальний акаунт вже активний" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Не вдалося підтвердити соціальний обліковий запис" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "Електронна пошта відправлена успішно" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Не вдалося надіслати електронну пошту" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Недійсний обліковий запис" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Не вдалося відіслати електронний лист" + +#: Controller/Traits/LinkSocialTrait.php:52 +msgid "Could not associate account, please try again." +msgstr "Неможливо пов’язати обліковий запис. Повторіть спробу." + +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "Соціальний акаунт успішно активовано." + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Ви успішно вийшли" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Спершу ввімкніть Google Authenticator." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +msgid "Could not find user data" +msgstr "Не вдалося знайти дані користувача" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +msgid "Could not verify, please try again" +msgstr "Неможливо підтвердити. Повторіть спробу" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Код підтвердження недійсний. Спробуйте ще раз" + +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 +msgid "User was not found" +msgstr "Користувача не знайдено" + +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 +msgid "Password could not be changed" +msgstr "Не вдалося змінити пароль" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "Пароль успішно змінено" + +#: Controller/Traits/PasswordManagementTrait.php:137 +msgid "Please check your email to continue with password reset process" +msgstr "Будь ласка, перевірте вашу електронну пошту для відновлення пароля" + +#: Controller/Traits/PasswordManagementTrait.php:140 Shell/UsersShell.php:247 +msgid "The password token could not be generated. Please try again" +msgstr "Не вдалося створити маркер пароля. Будь ласка спробуйте ще раз" + +#: Controller/Traits/PasswordManagementTrait.php:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "Користувача {0} не знайдено" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "Користувач неактивний" + +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 +msgid "Token could not be reset" +msgstr "Не вдалося скинути маркер" + +#: Controller/Traits/PasswordManagementTrait.php:174 +msgid "Google Authenticator token was successfully reset" +msgstr "Маркер Google Authenticator успішно скинуто" + +#: Controller/Traits/ProfileTrait.php:57 +msgid "Not authorized, please login first" +msgstr "Не дозволено, спочатку увійдіть" + +#: Controller/Traits/RegisterTrait.php:46 +msgid "You must log out to register a new user account" +msgstr "Ви повинні вийти з системи, щоб зареєструвати новий обліковий запис" + +#: Controller/Traits/RegisterTrait.php:75;99 +msgid "The user could not be saved" +msgstr "Користувача не вдалося зберегти" + +#: Controller/Traits/RegisterTrait.php:92 Loader/LoginComponentLoader.php:32 +msgid "Invalid reCaptcha" +msgstr "Недійсна reCaptcha" + +#: Controller/Traits/RegisterTrait.php:133 +msgid "You have registered successfully, please log in" +msgstr "Ви успішно зареєструвались, будь ласка, увійдіть" + +#: Controller/Traits/RegisterTrait.php:135 +msgid "Please validate your account before log in" +msgstr "Будь ласка, підтвердьте свій обліковий запис, перш ніж увійти" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "Успішно збережено {0}" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "Не вдалося зберегти {0}" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "{0} видалено" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "{0} не може бути видалено" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Обліковий запис успішно підтверджено" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "Не вдалося підтвердити обліковий запис" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "Користувач вже активний" + +#: Controller/Traits/UserValidationTrait.php:55 +msgid "Reset password token was validated successfully" +msgstr "Маркер скидання пароля успішно перевірено" + +#: Controller/Traits/UserValidationTrait.php:63 +msgid "Reset password token could not be validated" +msgstr "Маркер скидання пароля не може бути перевірений" + +#: Controller/Traits/UserValidationTrait.php:67 +msgid "Invalid validation type" +msgstr "Недійсний тип перевірки" + +#: Controller/Traits/UserValidationTrait.php:70 +msgid "Invalid token or user account already validated" +msgstr "Недійсний маркер або обліковий запис користувача вже підтверджено" + +#: Controller/Traits/UserValidationTrait.php:76 +msgid "Token already expired" +msgstr "Маркер вже сплив" + +#: Controller/Traits/UserValidationTrait.php:106 +msgid "Token has been reset successfully. Please check your email." +msgstr "Маркер успішно скинуто. Будь ласка, перевірте свою електронну пошту." + +#: Controller/Traits/UserValidationTrait.php:118 +msgid "User {0} is already active" +msgstr "Користувач {0} вже активний" + +#: Loader/LoginComponentLoader.php:30 +msgid "Username or password is incorrect" +msgstr "Ім'я користувача або пароль невірні" + +#: Loader/LoginComponentLoader.php:51 +msgid "Could not proceed with social account. Please try again" +msgstr "" +"Не вдалось продовжити використання соціального облікового запису. Будь ласка " +"спробуйте ще раз" + +#: Loader/LoginComponentLoader.php:53 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Користувача ще не підтверджено. Будь ласка, перевірте вашу поштову скриньку " +"для інструкцій" + +#: Loader/LoginComponentLoader.php:57 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"Ваш соціальний обліковий запис ще не перевірено. Будь ласка, перевірте вашу " +"поштову скриньку для інструкцій" + +#: Mailer/UsersMailer.php:33 +msgid "Your account validation link" +msgstr "Посилання для перевірки облікового запису" + +#: Mailer/UsersMailer.php:51 +msgid "{0}Your reset password link" +msgstr "{0} Ваше посилання для скидання пароля" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} Посилання для перевірки облікового запису" + +#: Middleware/SocialAuthMiddleware.php:65 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Введіть адресу електронної пошти" + +#: Middleware/SocialAuthMiddleware.php:75 +msgid "Could not identify your account, please try again" +msgstr "Не вдалося визначити обліковий запис, будь ласка, спробуйте ще раз" + +#: Model/Behavior/AuthFinderBehavior.php:48 +msgid "Missing 'username' in options data" +msgstr "«Ім'я користувача» відсутнє в параметрах даних" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "Соціальний обліковий запис, вже пов'язаний з іншим користувачем" + +#: Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "Посилання не може бути пустим" + +#: Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "Термін дії маркера не може бути пустим" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Користувача не знайдено" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "Обліковий запис користувача вже підтверджено" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "Користувач не активний" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "Поточний пароль не збігається" + +#: Model/Behavior/PasswordBehavior.php:146 +msgid "You cannot use the current password as the new one" +msgstr "Не можна використовувати поточний пароль як новий" + +#: Model/Behavior/RegisterBehavior.php:90 +msgid "User not found for the given token and email." +msgstr "Не знайдено користувача з цим маркером та адресою електронної пошти." + +#: Model/Behavior/RegisterBehavior.php:93 +msgid "Token has already expired user with no token" +msgstr "Маркер вже сплив, користувач без маркера" + +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Це поле обов‘язкове" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 +msgid "Account already validated" +msgstr "Обліковий запис вже підтверджено" + +#: Model/Behavior/SocialAccountBehavior.php:105;132 +msgid "Account not found for the given token and email." +msgstr "Не знайдено користувача з цим маркером та адресою електронної пошти." + +#: Model/Behavior/SocialBehavior.php:83 +msgid "Unable to login user with reference {0}" +msgstr "Не вдається ввійти за посиланням {0}" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "Електронна пошта відсутня" + +#: Model/Table/UsersTable.php:79 +msgid "Your password does not match your confirm password. Please try again" +msgstr "Ваш пароль не відповідає підтвердженню. Будь ласка, спробуйте ще раз" + +#: Model/Table/UsersTable.php:171 +msgid "Username already exists" +msgstr "Такий користувач вже існує" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Електронна пошта вже існує" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Утиліти для плагіна CakeDC Users" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Активація конкретного користувача" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "Додавання нового користувача superadmin для цілей тестування" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "Додати нового користувача" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Змінення ролі для певного користувача" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Деактивувати конкретного користувача" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Видалення конкретного користувача" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Скинути пароль за допомогою електронної пошти" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Скидання пароля для всіх користувачів" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Скидання пароля для певного користувача" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Будь ласка, введіть пароль." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Пароль змінено для всіх користувачів" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Новий пароль: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Введіть ім'я користувача." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Пароль змінено для користувача: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Будь ласка, введіть роль." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Роль змінено для користувача: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Нова роль: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Користувач був активований: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Користувач був де-активований: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Введіть ім'я користувача або email." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Будь ласка, зверніться до користувача, щоб перевірити електронну пошту для " +"продовження процесу скидання пароля" + +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superuser-а додано:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Користувача додано:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Користувач: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Електронна пошта: {0}" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "Роль: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Пароль: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "Не вдалося додати користувача:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Поле: {0} Помилка: {1}" + +#: Shell/UsersShell.php:337 +msgid "The user was not found." +msgstr "Користувача не знайдено." + +#: Shell/UsersShell.php:367 +msgid "The user {0} was not deleted. Please try again" +msgstr "Користувача {0} не видалено. Будь ласка, спробуйте ще раз" + +#: Shell/UsersShell.php:369 +msgid "The user {0} was deleted successfully" +msgstr "Користувача {0} успішно видалено" + +#: Template/Email/html/reset_password.ctp:21 +#: Template/Email/html/social_account_validation.ctp:14 +#: Template/Email/html/validation.ctp:21 +#: Template/Email/text/reset_password.ctp:20 +#: Template/Email/text/social_account_validation.ctp:22 +#: Template/Email/text/validation.ctp:20 +msgid "Hi {0}" +msgstr "Вітаємо {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +msgstr "Скинути пароль" + +#: Template/Email/html/reset_password.ctp:27 +#: Template/Email/html/social_account_validation.ctp:32 +#: Template/Email/html/validation.ctp:27 +msgid "" +"If the link is not correctly displayed, please copy the following address in " +"your web browser {0}" +msgstr "" +"Якщо посилання не відображається належним чином, скопіюйте наступну адресу у " +"веб-переглядач {0}" + +#: Template/Email/html/reset_password.ctp:34 +#: Template/Email/html/social_account_validation.ctp:39 +#: Template/Email/html/validation.ctp:34 +#: Template/Email/text/reset_password.ctp:28 +#: Template/Email/text/social_account_validation.ctp:30 +#: Template/Email/text/validation.ctp:28 +msgid "Thank you" +msgstr "Дякуємо" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Активуйте свій соціальний логін тут" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Активуйте свій акаунт тут" + +#: Template/Email/text/reset_password.ctp:22 +#: Template/Email/text/validation.ctp:22 +msgid "Please copy the following address in your web browser {0}" +msgstr "Будь ласка, скопіюйте наступну адресу у веб-переглядач {0}" + +#: Template/Email/text/social_account_validation.ctp:24 +msgid "" +"Please copy the following address in your web browser to activate your " +"social login {0}" +msgstr "" +"Будь ласка, скопіюйте наступну адресу у веб-браузер, щоб активувати ваш " +"соціальний логін {0}" + +#: Template/Users/add.ctp:13 Template/Users/edit.ctp:17 +#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:15 +msgid "Actions" +msgstr "Дії" + +#: Template/Users/add.ctp:15 Template/Users/edit.ctp:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Перелік користувачів" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Створити користувача" + +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 +msgid "Username" +msgstr "Iм'я користувача" + +#: Template/Users/add.ctp:24 Template/Users/edit.ctp:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 +msgid "Email" +msgstr "Електронна пошта" + +#: Template/Users/add.ctp:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "Пароль" + +#: Template/Users/add.ctp:26 Template/Users/edit.ctp:38 +#: Template/Users/index.ctp:24 Template/Users/register.ctp:27 +msgid "First name" +msgstr "Ім‘я" + +#: Template/Users/add.ctp:27 Template/Users/edit.ctp:39 +#: Template/Users/index.ctp:25 Template/Users/register.ctp:28 +msgid "Last name" +msgstr "Прізвище" + +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:54 +#: Template/Users/view.ctp:49;74 +msgid "Active" +msgstr "Активний" + +#: Template/Users/add.ctp:34 Template/Users/change_password.ctp:25 +#: Template/Users/edit.ctp:58 Template/Users/register.ctp:37 +#: Template/Users/request_reset_password.ctp:8 +#: Template/Users/resend_token_validation.ctp:20 +#: Template/Users/social_email.ctp:19 +msgid "Submit" +msgstr "Відправити" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Будь ласка, введіть пароль" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Теперішний пароль" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Новий пароль" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Повторити пароль" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Видалити" + +#: Template/Users/edit.ctp:24 Template/Users/index.ctp:40 +#: Template/Users/view.ctp:21 +msgid "Are you sure you want to delete # {0}?" +msgstr "Впевнені, що хочете видалити # {0}?" + +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Редагувати користувача" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Маркер" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Маркер спилв" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "Маркер API" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Дата активації" + +#: Template/Users/edit.ctp:51 +msgid "TOS date" +msgstr "Дата приймання умов" + +#: Template/Users/edit.ctp:64 +msgid "Reset Google Authenticator Token" +msgstr "Скинути маркер Google Authenticator" + +#: Template/Users/edit.ctp:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "Ви дійсно бажаєте скинути маркер для користувача \"{0}\"?" + +#: Template/Users/index.ctp:15 +msgid "New {0}" +msgstr "Створити {0}" + +#: Template/Users/index.ctp:37 +msgid "View" +msgstr "Перегляд" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Змінити пароль" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Редагувати" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "попередня" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "наступна" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Введіть ім'я користувача і пароль" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Запам‘ятати мене" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Реєстрація" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Скинути пароль" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Ввійти" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Змінити пароль" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Соціальні акаунти" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Аватар" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Провайдер" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Посилання" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Посилання на {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Приймаєте умови сервісу?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Введіть адресу електронної пошти для скидання пароля" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Надислати код підтвердження ще раз" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Email або ім'я користувача" + +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "Код підтвердження" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" " +"Перевірити" + +#: Template/Users/view.ctp:19 +msgid "Delete User" +msgstr "Видалити користувача" + +#: Template/Users/view.ctp:24 +msgid "New User" +msgstr "Новий користувач" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +msgid "First Name" +msgstr "Ім‘я" + +#: Template/Users/view.ctp:39 +msgid "Last Name" +msgstr "Прізвище" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Роль" + +#: Template/Users/view.ctp:45 +msgid "Api Token" +msgstr "Маркер API" + +#: Template/Users/view.ctp:53 +msgid "Token Expires" +msgstr "Маркер спливає" + +#: Template/Users/view.ctp:55 +msgid "Activation Date" +msgstr "Дата активації" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Дата приймання умов" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Створено" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Змінено" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Увійти з використанням" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Вихiд" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Вітаємо, {0}" + +#: View/Helper/UserHelper.php:151 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "reCaptcha не налаштовано! Налаштуйте Users.reCaptcha.key" + +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "Під‘єднано до {0}" + +#: View/Helper/UserHelper.php:220 +msgid "Connect with {0}" +msgstr "Під‘єднати до {0}" diff --git a/resources/locales/users.pot b/resources/locales/users.pot new file mode 100644 index 000000000..c2992ab07 --- /dev/null +++ b/resources/locales/users.pot @@ -0,0 +1,920 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2022-03-11 13:46+0100\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: src/Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "" + +#: src/Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "" + +#: src/Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "" + +#: src/Controller/SocialAccountsController.php:57 +#: src/Controller/SocialAccountsController.php:85 +msgid "Social Account already active" +msgstr "" + +#: src/Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "" + +#: src/Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "" + +#: src/Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "" + +#: src/Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "" + +#: src/Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "" + +#: src/Controller/Traits/LinkSocialTrait.php:56 +msgid "Could not associate account, please try again." +msgstr "" + +#: src/Controller/Traits/LinkSocialTrait.php:80 +msgid "Social account was associated." +msgstr "" + +#: src/Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:75 +msgid "Please enable Google Authenticator first." +msgstr "" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:90 +msgid "Could not find user data" +msgstr "" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:129 +msgid "Could not verify, please try again" +msgstr "" + +#: src/Controller/Traits/OneTimePasswordVerifyTrait.php:161 +msgid "Verification code is invalid. Try again" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:63 +msgid "Changing another user's password is not allowed" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:77 +#: src/Controller/Traits/PasswordManagementTrait.php:121 +#: src/Controller/Traits/ProfileTrait.php:54 +msgid "User was not found" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:105 +#: src/Controller/Traits/PasswordManagementTrait.php:117 +#: src/Controller/Traits/PasswordManagementTrait.php:125 +msgid "Password could not be changed" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:113 +msgid "Password has been changed successfully" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:167 +msgid "Please check your email to continue with password reset process" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:170 +#: src/Shell/UsersShell.php:286 +msgid "The password token could not be generated. Please try again" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:176 +#: src/Controller/Traits/UserValidationTrait.php:124 +msgid "User {0} was not found" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:178 +msgid "The user is not active" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:180 +#: src/Controller/Traits/UserValidationTrait.php:119 +#: src/Controller/Traits/UserValidationTrait.php:128 +msgid "Token could not be reset" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:204 +msgid "Google Authenticator token was successfully reset" +msgstr "" + +#: src/Controller/Traits/PasswordManagementTrait.php:207 +msgid "Could not reset Google Authenticator" +msgstr "" + +#: src/Controller/Traits/ProfileTrait.php:58 +msgid "Not authorized, please login first" +msgstr "" + +#: src/Controller/Traits/RegisterTrait.php:47 +msgid "You must log out to register a new user account" +msgstr "" + +#: src/Controller/Traits/RegisterTrait.php:86 +#: src/Controller/Traits/RegisterTrait.php:117 +msgid "The user could not be saved" +msgstr "" + +#: src/Controller/Traits/RegisterTrait.php:103 +#: src/Loader/LoginComponentLoader.php:33 +msgid "Invalid reCaptcha" +msgstr "" + +#: src/Controller/Traits/RegisterTrait.php:151 +msgid "You have registered successfully, please log in" +msgstr "" + +#: src/Controller/Traits/RegisterTrait.php:153 +msgid "Please validate your account before log in" +msgstr "" + +#: src/Controller/Traits/SimpleCrudTrait.php:77 +#: src/Controller/Traits/SimpleCrudTrait.php:107 +msgid "The {0} has been saved" +msgstr "" + +#: src/Controller/Traits/SimpleCrudTrait.php:81 +#: src/Controller/Traits/SimpleCrudTrait.php:111 +msgid "The {0} could not be saved" +msgstr "" + +#: src/Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "" + +#: src/Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "" + +#: src/Controller/Traits/U2fTrait.php:216 +msgid "U2F requires SSL." +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:49 +msgid "User account validated successfully" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:51 +msgid "User account could not be validated" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:54 +msgid "User already active" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:60 +msgid "Reset password token was validated successfully" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:68 +msgid "Reset password token could not be validated" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:72 +msgid "Invalid validation type" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:75 +msgid "Invalid token or user account already validated" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:81 +msgid "Token already expired" +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:114 +msgid "Token has been reset successfully. Please check your email." +msgstr "" + +#: src/Controller/Traits/UserValidationTrait.php:126 +msgid "User {0} is already active" +msgstr "" + +#: src/Controller/Traits/Webauthn2faTrait.php:53 +#: src/Controller/Traits/Webauthn2faTrait.php:73 +msgid "User already has configured webauthn2fa" +msgstr "" + +#: src/Controller/Traits/Webauthn2faTrait.php:77 +#: src/Controller/Traits/Webauthn2faTrait.php:122 +msgid "Register error with webauthn for user id: {0}" +msgstr "" + +#: src/Loader/AuthenticationServiceLoader.php:109 +msgid "Property {0}.className should be defined" +msgstr "" + +#: src/Loader/LoginComponentLoader.php:31 +msgid "Username or password is incorrect" +msgstr "" + +#: src/Loader/LoginComponentLoader.php:52 +msgid "Could not proceed with social account. Please try again" +msgstr "" + +#: src/Loader/LoginComponentLoader.php:54 +msgid "Your user has not been validated yet. Please check your inbox for instructions" +msgstr "" + +#: src/Loader/LoginComponentLoader.php:58 +msgid "Your social account has not been validated yet. Please check your inbox for instructions" +msgstr "" + +#: src/Mailer/UsersMailer.php:36 +msgid "Your account validation link" +msgstr "" + +#: src/Mailer/UsersMailer.php:63 +msgid "{0}Your reset password link" +msgstr "" + +#: src/Mailer/UsersMailer.php:95 +msgid "{0}Your social account validation link" +msgstr "" + +#: src/Middleware/SocialAuthMiddleware.php:47 +#: templates/Users/social_email.php:16 +msgid "Please enter your email" +msgstr "" + +#: src/Middleware/SocialAuthMiddleware.php:57 +msgid "Could not identify your account, please try again" +msgstr "" + +#: src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php:112 +msgid "You are not authorized to access that location." +msgstr "" + +#: src/Model/Behavior/AuthFinderBehavior.php:49 +msgid "Missing 'username' in options data" +msgstr "" + +#: src/Model/Behavior/LinkSocialBehavior.php:52 +msgid "Social account already associated to another user" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:56 +#: src/Model/Behavior/PasswordBehavior.php:136 +msgid "User not found" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:60 +#: src/Model/Behavior/RegisterBehavior.php:129 +msgid "User account already validated" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:66 +msgid "User not active" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:141 +msgid "The current password does not match" +msgstr "" + +#: src/Model/Behavior/PasswordBehavior.php:144 +msgid "You cannot use the current password as the new one" +msgstr "" + +#: src/Model/Behavior/RegisterBehavior.php:107 +msgid "User not found for the given token and email." +msgstr "" + +#: src/Model/Behavior/RegisterBehavior.php:110 +msgid "Token has already expired user with no token" +msgstr "" + +#: src/Model/Behavior/RegisterBehavior.php:167 +msgid "This field is required" +msgstr "" + +#: src/Model/Behavior/SocialAccountBehavior.php:100 +#: src/Model/Behavior/SocialAccountBehavior.php:130 +msgid "Account already validated" +msgstr "" + +#: src/Model/Behavior/SocialAccountBehavior.php:104 +#: src/Model/Behavior/SocialAccountBehavior.php:135 +msgid "Account not found for the given token and email." +msgstr "" + +#: src/Model/Behavior/SocialBehavior.php:85 +msgid "Unable to login user with reference {0}" +msgstr "" + +#: src/Model/Behavior/SocialBehavior.php:136 +msgid "Email not present" +msgstr "" + +#: src/Model/Table/UsersTable.php:107 +msgid "Your password does not match your confirm password. Please try again" +msgstr "" + +#: src/Model/Table/UsersTable.php:201 +msgid "Username already exists" +msgstr "" + +#: src/Model/Table/UsersTable.php:207 +msgid "Email already exists" +msgstr "" + +#: src/Shell/UsersShell.php:46 +msgid "Utilities for CakeDC Users Plugin" +msgstr "" + +#: src/Shell/UsersShell.php:48 +msgid "Activate an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:51 +msgid "Add a new superadmin user for testing purposes" +msgstr "" + +#: src/Shell/UsersShell.php:54 +msgid "Add a new user" +msgstr "" + +#: src/Shell/UsersShell.php:57 +msgid "Change the role for an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:60 +msgid "Change the api token for an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:63 +msgid "Deactivate an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:66 +msgid "Delete an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:69 +msgid "Reset the password via email" +msgstr "" + +#: src/Shell/UsersShell.php:72 +msgid "Reset the password for all users" +msgstr "" + +#: src/Shell/UsersShell.php:75 +msgid "Reset the password for an specific user" +msgstr "" + +#: src/Shell/UsersShell.php:135 +#: src/Shell/UsersShell.php:161 +msgid "Please enter a password." +msgstr "" + +#: src/Shell/UsersShell.php:139 +msgid "Password changed for all users" +msgstr "" + +#: src/Shell/UsersShell.php:140 +#: src/Shell/UsersShell.php:168 +msgid "New password: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:158 +#: src/Shell/UsersShell.php:186 +#: src/Shell/UsersShell.php:214 +#: src/Shell/UsersShell.php:304 +#: src/Shell/UsersShell.php:403 +msgid "Please enter a username." +msgstr "" + +#: src/Shell/UsersShell.php:167 +msgid "Password changed for user: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:189 +msgid "Please enter a role." +msgstr "" + +#: src/Shell/UsersShell.php:195 +msgid "Role changed for user: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:196 +msgid "New role: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:217 +msgid "Please enter a token." +msgstr "" + +#: src/Shell/UsersShell.php:224 +msgid "User was not saved, check validation errors" +msgstr "" + +#: src/Shell/UsersShell.php:229 +msgid "Api token changed for user: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:230 +msgid "New token: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:245 +msgid "User was activated: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:260 +msgid "User was de-activated: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:272 +msgid "Please enter a username or email." +msgstr "" + +#: src/Shell/UsersShell.php:280 +msgid "Please ask the user to check the email to continue with password reset process" +msgstr "" + +#: src/Shell/UsersShell.php:350 +msgid "Superuser added:" +msgstr "" + +#: src/Shell/UsersShell.php:352 +msgid "User added:" +msgstr "" + +#: src/Shell/UsersShell.php:354 +msgid "Id: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:355 +msgid "Username: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:356 +msgid "Email: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:357 +msgid "Role: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:358 +msgid "Password: {0}" +msgstr "" + +#: src/Shell/UsersShell.php:360 +msgid "User could not be added:" +msgstr "" + +#: src/Shell/UsersShell.php:363 +msgid "Field: {0} Error: {1}" +msgstr "" + +#: src/Shell/UsersShell.php:379 +msgid "The user was not found." +msgstr "" + +#: src/Shell/UsersShell.php:414 +msgid "The user {0} was not deleted. Please try again" +msgstr "" + +#: src/Shell/UsersShell.php:416 +msgid "The user {0} was deleted successfully" +msgstr "" + +#: src/View/Helper/UserHelper.php:50 +msgid "Sign in with" +msgstr "" + +#: src/View/Helper/UserHelper.php:113 +msgid "Logout" +msgstr "" + +#: src/View/Helper/UserHelper.php:134 +msgid "Welcome, {0}" +msgstr "" + +#: src/View/Helper/UserHelper.php:165 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "" + +#: src/View/Helper/UserHelper.php:218 +msgid "Connected with {0}" +msgstr "" + +#: src/View/Helper/UserHelper.php:223 +msgid "Connect with {0}" +msgstr "" + +#: templates/Users/add.php:13 +#: templates/Users/edit.php:17 +#: templates/Users/index.php:13 +#: templates/Users/index.php:26 +#: templates/Users/view.php:15 +msgid "Actions" +msgstr "" + +#: templates/Users/add.php:15 +#: templates/Users/edit.php:28 +#: templates/Users/view.php:23 +msgid "List Users" +msgstr "" + +#: templates/Users/add.php:21 +#: templates/Users/register.php:18 +msgid "Add User" +msgstr "" + +#: templates/Users/add.php:23 +#: templates/Users/edit.php:36 +#: templates/Users/index.php:22 +#: templates/Users/login.php:20 +#: templates/Users/profile.php:30 +#: templates/Users/register.php:20 +#: templates/Users/view.php:33 +msgid "Username" +msgstr "" + +#: templates/Users/add.php:24 +#: templates/Users/edit.php:37 +#: templates/Users/index.php:23 +#: templates/Users/profile.php:32 +#: templates/Users/register.php:21 +#: templates/Users/view.php:35 +msgid "Email" +msgstr "" + +#: templates/Users/add.php:25 +#: templates/Users/login.php:21 +#: templates/Users/register.php:22 +msgid "Password" +msgstr "" + +#: templates/Users/add.php:26 +#: templates/Users/edit.php:38 +#: templates/Users/index.php:24 +#: templates/Users/register.php:28 +msgid "First name" +msgstr "" + +#: templates/Users/add.php:27 +#: templates/Users/edit.php:39 +#: templates/Users/index.php:25 +#: templates/Users/register.php:29 +msgid "Last name" +msgstr "" + +#: templates/Users/add.php:30 +#: templates/Users/edit.php:54 +#: templates/Users/view.php:49 +#: templates/Users/view.php:74 +msgid "Active" +msgstr "" + +#: templates/Users/add.php:34 +#: templates/Users/change_password.php:25 +#: templates/Users/edit.php:58 +#: templates/Users/register.php:38 +#: templates/Users/request_reset_password.php:23 +#: templates/Users/resend_token_validation.php:20 +#: templates/Users/social_email.php:19 +msgid "Submit" +msgstr "" + +#: templates/Users/change_password.php:5 +msgid "Please enter the new password" +msgstr "" + +#: templates/Users/change_password.php:10 +msgid "Current password" +msgstr "" + +#: templates/Users/change_password.php:16 +msgid "New password" +msgstr "" + +#: templates/Users/change_password.php:21 +#: templates/Users/register.php:26 +msgid "Confirm password" +msgstr "" + +#: templates/Users/edit.php:22 +#: templates/Users/index.php:40 +msgid "Delete" +msgstr "" + +#: templates/Users/edit.php:24 +#: templates/Users/index.php:40 +#: templates/Users/view.php:21 +msgid "Are you sure you want to delete # {0}?" +msgstr "" + +#: templates/Users/edit.php:34 +#: templates/Users/view.php:17 +msgid "Edit User" +msgstr "" + +#: templates/Users/edit.php:40 +#: templates/Users/view.php:43 +msgid "Token" +msgstr "" + +#: templates/Users/edit.php:42 +msgid "Token expires" +msgstr "" + +#: templates/Users/edit.php:45 +msgid "API token" +msgstr "" + +#: templates/Users/edit.php:48 +msgid "Activation date" +msgstr "" + +#: templates/Users/edit.php:51 +msgid "TOS date" +msgstr "" + +#: templates/Users/edit.php:64 +msgid "Reset Google Authenticator Token" +msgstr "" + +#: templates/Users/edit.php:70 +msgid "Are you sure you want to reset token for user \"{0}\"?" +msgstr "" + +#: templates/Users/index.php:15 +msgid "New {0}" +msgstr "" + +#: templates/Users/index.php:37 +msgid "View" +msgstr "" + +#: templates/Users/index.php:38 +msgid "Change password" +msgstr "" + +#: templates/Users/index.php:39 +msgid "Edit" +msgstr "" + +#: templates/Users/index.php:49 +msgid "previous" +msgstr "" + +#: templates/Users/index.php:51 +msgid "next" +msgstr "" + +#: templates/Users/login.php:19 +msgid "Please enter your username and password" +msgstr "" + +#: templates/Users/login.php:29 +msgid "Remember me" +msgstr "" + +#: templates/Users/login.php:37 +msgid "Register" +msgstr "" + +#: templates/Users/login.php:43 +msgid "Reset Password" +msgstr "" + +#: templates/Users/login.php:48 +msgid "Login" +msgstr "" + +#: templates/Users/profile.php:21 +msgid "{0} {1}" +msgstr "" + +#: templates/Users/profile.php:27 +msgid "Change Password" +msgstr "" + +#: templates/Users/profile.php:38 +#: templates/Users/view.php:68 +msgid "Social Accounts" +msgstr "" + +#: templates/Users/profile.php:42 +#: templates/Users/view.php:73 +msgid "Avatar" +msgstr "" + +#: templates/Users/profile.php:43 +#: templates/Users/view.php:72 +msgid "Provider" +msgstr "" + +#: templates/Users/profile.php:44 +msgid "Link" +msgstr "" + +#: templates/Users/profile.php:51 +msgid "Link to {0}" +msgstr "" + +#: templates/Users/register.php:31 +msgid "Accept TOS conditions?" +msgstr "" + +#: templates/Users/request_reset_password.php:20 +msgid "Please enter your email or username to reset your password" +msgstr "" + +#: templates/Users/resend_token_validation.php:15 +msgid "Resend Validation email" +msgstr "" + +#: templates/Users/resend_token_validation.php:17 +msgid "Email or username" +msgstr "" + +#: templates/Users/u2f_authenticate.php:22 +#: templates/Users/webauthn2fa.php:25 +msgid "Verify your registered yubico key" +msgstr "" + +#: templates/Users/u2f_authenticate.php:23 +#: templates/Users/u2f_register.php:23 +#: templates/Users/webauthn2fa.php:20 +#: templates/Users/webauthn2fa.php:26 +msgid "Please insert and tap your yubico key" +msgstr "" + +#: templates/Users/u2f_authenticate.php:24 +#: templates/Users/webauthn2fa.php:27 +msgid "You can now finish the authentication process using the registered device." +msgstr "" + +#: templates/Users/u2f_authenticate.php:25 +#: templates/Users/webauthn2fa.php:22 +#: templates/Users/webauthn2fa.php:28 +msgid "When the YubiKey starts blinking, press the golden disc to activate it. Depending on the web browser you might need to confirm the use of extended information from the YubiKey." +msgstr "" + +#: templates/Users/u2f_authenticate.php:27 +#: templates/Users/u2f_register.php:27 +#: templates/Users/webauthn2fa.php:31 +msgid "Reload" +msgstr "" + +#: templates/Users/u2f_authenticate.php:51 +#: templates/Users/u2f_register.php:56 +msgid "Yubico key check has failed, please try again" +msgstr "" + +#: templates/Users/u2f_register.php:22 +#: templates/Users/webauthn2fa.php:19 +msgid "Registering your yubico key" +msgstr "" + +#: templates/Users/verify.php:13 +msgid "Verification Code" +msgstr "" + +#: templates/Users/verify.php:15 +msgid " Verify" +msgstr "" + +#: templates/Users/view.php:19 +msgid "Delete User" +msgstr "" + +#: templates/Users/view.php:24 +msgid "New User" +msgstr "" + +#: templates/Users/view.php:31 +msgid "Id" +msgstr "" + +#: templates/Users/view.php:37 +msgid "First Name" +msgstr "" + +#: templates/Users/view.php:39 +msgid "Last Name" +msgstr "" + +#: templates/Users/view.php:41 +msgid "Role" +msgstr "" + +#: templates/Users/view.php:45 +msgid "Api Token" +msgstr "" + +#: templates/Users/view.php:53 +msgid "Token Expires" +msgstr "" + +#: templates/Users/view.php:55 +msgid "Activation Date" +msgstr "" + +#: templates/Users/view.php:57 +msgid "Tos Date" +msgstr "" + +#: templates/Users/view.php:59 +#: templates/Users/view.php:75 +msgid "Created" +msgstr "" + +#: templates/Users/view.php:61 +#: templates/Users/view.php:76 +msgid "Modified" +msgstr "" + +#: templates/Users/webauthn2fa.php:8 +msgid "Two-factor authentication" +msgstr "" + +#: templates/Users/webauthn2fa.php:21 +msgid "In order to enable your YubiKey the first step is to perform a registration." +msgstr "" + +#: templates/email/html/reset_password.php:14 +#: templates/email/html/social_account_validation.php:14 +#: templates/email/html/validation.php:13 +#: templates/email/text/reset_password.php:12 +#: templates/email/text/social_account_validation.php:12 +#: templates/email/text/validation.php:12 +msgid "Hi {0}" +msgstr "" + +#: templates/email/html/reset_password.php:17 +msgid "Reset your password here" +msgstr "" + +#: templates/email/html/reset_password.php:20 +#: templates/email/html/social_account_validation.php:23 +#: templates/email/html/validation.php:19 +msgid "If the link is not correctly displayed, please copy the following address in your web browser {0}" +msgstr "" + +#: templates/email/html/reset_password.php:27 +#: templates/email/html/social_account_validation.php:30 +#: templates/email/html/validation.php:26 +#: templates/email/text/reset_password.php:20 +#: templates/email/text/social_account_validation.php:20 +#: templates/email/text/validation.php:20 +msgid "Thank you" +msgstr "" + +#: templates/email/html/social_account_validation.php:18 +msgid "Activate your social login here" +msgstr "" + +#: templates/email/html/validation.php:16 +msgid "Activate your account here" +msgstr "" + +#: templates/email/text/reset_password.php:14 +#: templates/email/text/validation.php:14 +msgid "Please copy the following address in your web browser {0}" +msgstr "" + +#: templates/email/text/social_account_validation.php:14 +msgid "Please copy the following address in your web browser to activate your social login {0}" +msgstr "" + diff --git a/src/Auth/Exception/InvalidProviderException.php b/src/Auth/Exception/InvalidProviderException.php deleted file mode 100644 index d19eee654..000000000 --- a/src/Auth/Exception/InvalidProviderException.php +++ /dev/null @@ -1,31 +0,0 @@ - 'id', - 'username' => 'username', - 'full_name' => 'name', - 'first_name' => 'first_name', - 'last_name' => 'last_name', - 'email' => 'email', - 'avatar' => 'avatar', - 'gender' => 'gender', - 'link' => 'link', - 'bio' => 'bio', - 'locale' => 'locale', - 'validated' => 'validated' - ]; - - /** - * Constructor - * - * @param mixed $rawData raw data - * @param mixed $mapFields map fields - */ - public function __construct($rawData, $mapFields = null) - { - $this->_rawData = $rawData; - if (!is_null($mapFields)) { - $this->_mapFields = $mapFields; - } - $this->_mapFields = array_merge($this->_defaultMapFields, $this->_mapFields); - } - /** - * Invoke method - * - * @return mixed - */ - public function __invoke() - { - return $this->_map(); - } - - /** - * If email is present the user is validated - * - * @return bool - */ - protected function _validated() - { - $email = Hash::get($this->_rawData, $this->_mapFields['email']); - - return !empty($email); - } - - /** - * Maps raw data using mapFields - * - * @return mixed - */ - protected function _map() - { - $result = []; - collection($this->_mapFields)->each(function ($mappedField, $field) use (&$result) { - $value = Hash::get($this->_rawData, $mappedField); - $function = '_' . $field; - if (method_exists($this, $function)) { - $value = $this->{$function}(); - } - $result[$field] = $value; - }); - $token = Hash::get($this->_rawData, 'token'); - $result['credentials'] = [ - 'token' => is_array($token) ? Hash::get($token, 'accessToken') : $token->getToken(), - 'secret' => is_array($token) ? Hash::get($token, 'tokenSecret') : null, - 'expires' => is_array($token) ? Hash::get($token, 'expires') : $token->getExpires(), - ]; - $result['raw'] = $this->_rawData; - - return $result; - } -} diff --git a/src/Auth/Social/Mapper/Amazon.php b/src/Auth/Social/Mapper/Amazon.php deleted file mode 100644 index 20c532052..000000000 --- a/src/Auth/Social/Mapper/Amazon.php +++ /dev/null @@ -1,43 +0,0 @@ - 'user_id' - ]; - - /** - * @return string - */ - protected function _link() - { - return self::AMAZON_BASE_URL . Hash::get($this->_rawData, $this->_mapFields['id']); - } -} diff --git a/src/Auth/Social/Mapper/Facebook.php b/src/Auth/Social/Mapper/Facebook.php deleted file mode 100644 index b578fe82d..000000000 --- a/src/Auth/Social/Mapper/Facebook.php +++ /dev/null @@ -1,44 +0,0 @@ - 'name', - ]; - - /** - * Get avatar url - * @return string - */ - protected function _avatar() - { - return self::FB_GRAPH_BASE_URL . Hash::get($this->_rawData, 'id') . '/picture?type=large'; - } -} diff --git a/src/Auth/Social/Mapper/Google.php b/src/Auth/Social/Mapper/Google.php deleted file mode 100644 index b895d5361..000000000 --- a/src/Auth/Social/Mapper/Google.php +++ /dev/null @@ -1,43 +0,0 @@ - 'image.url', - 'full_name' => 'displayName', - 'email' => 'emails.0.value', - 'first_name' => 'name.givenName', - 'last_name' => 'name.familyName', - 'bio' => 'aboutMe', - 'link' => 'url' - ]; - - /** - * @return string - */ - protected function _link() - { - return Hash::get($this->_rawData, $this->_mapFields['link']) ?: '#'; - } -} diff --git a/src/Auth/Social/Mapper/Instagram.php b/src/Auth/Social/Mapper/Instagram.php deleted file mode 100644 index b6adecf1d..000000000 --- a/src/Auth/Social/Mapper/Instagram.php +++ /dev/null @@ -1,43 +0,0 @@ - 'full_name', - 'avatar' => 'profile_picture', - ]; - - /** - * @return string - */ - protected function _link() - { - return self::INSTAGRAM_BASE_URL . Hash::get($this->_rawData, $this->_mapFields['username']); - } -} diff --git a/src/Auth/Social/Mapper/LinkedIn.php b/src/Auth/Social/Mapper/LinkedIn.php deleted file mode 100644 index cadda4b3c..000000000 --- a/src/Auth/Social/Mapper/LinkedIn.php +++ /dev/null @@ -1,28 +0,0 @@ - 'pictureUrl', - 'first_name' => 'firstName', - 'last_name' => 'lastName', - 'email' => 'emailAddress', - 'bio' => 'headline', - 'link' => 'publicProfileUrl' - ]; -} diff --git a/src/Auth/Social/Mapper/Twitter.php b/src/Auth/Social/Mapper/Twitter.php deleted file mode 100644 index 3dccfa45f..000000000 --- a/src/Auth/Social/Mapper/Twitter.php +++ /dev/null @@ -1,59 +0,0 @@ - 'uid', - 'username' => 'nickname', - 'full_name' => 'name', - 'first_name' => 'firstName', - 'last_name' => 'lastName', - 'email' => 'email', - 'avatar' => 'imageUrl', - 'bio' => 'description', - 'validated' => 'validated' - ]; - - /** - * @return string - */ - protected function _link() - { - return self::TWITTER_BASE_URL . Hash::get($this->_rawData, $this->_mapFields['username']); - } - - /** - * @return string - */ - protected function _avatar() - { - return str_replace('normal', 'bigger', Hash::get($this->_rawData, $this->_mapFields['avatar'])); - } -} diff --git a/src/Auth/Social/Util/SocialUtils.php b/src/Auth/Social/Util/SocialUtils.php deleted file mode 100644 index 0ae12fbf1..000000000 --- a/src/Auth/Social/Util/SocialUtils.php +++ /dev/null @@ -1,35 +0,0 @@ -getShortName(); - } -} diff --git a/src/Auth/SocialAuthenticate.php b/src/Auth/SocialAuthenticate.php deleted file mode 100755 index a5602c150..000000000 --- a/src/Auth/SocialAuthenticate.php +++ /dev/null @@ -1,488 +0,0 @@ -_isProviderEnabled($oauthConfig['providers']['twitter']); - //We unset twitter from providers to exclude from OAuth2 config - unset($oauthConfig['providers']['twitter']); - - $providers = []; - foreach ($oauthConfig['providers'] as $provider => $options) { - if ($this->_isProviderEnabled($options)) { - $providers[$provider] = $options; - } - } - $oauthConfig['providers'] = $providers; - Configure::write('OAuth2', $oauthConfig); - $config = $this->normalizeConfig(Hash::merge($config, $oauthConfig), $enabledNoOAuth2Provider); - parent::__construct($registry, $config); - } - - /** - * Normalizes providers' configuration. - * - * @param array $config Array of config to normalize. - * @param bool $enabledNoOAuth2Provider True when any noOAuth2 provider is enabled - * @return array - * @throws \Exception - */ - public function normalizeConfig(array $config, $enabledNoOAuth2Provider = false) - { - $config = Hash::merge((array)Configure::read('OAuth2'), $config); - - if (empty($config['providers']) && !$enabledNoOAuth2Provider) { - throw new MissingProviderConfigurationException(); - } - - if (!empty($config['providers'])) { - array_walk($config['providers'], [$this, '_normalizeConfig'], $config); - } - - return $config; - } - - /** - * Callback to loop through config values. - * - * @param array $config Configuration. - * @param string $alias Provider's alias (key) in configuration. - * @param array $parent Parent configuration. - * @return void - */ - protected function _normalizeConfig(&$config, $alias, $parent) - { - unset($parent['providers']); - - $defaults = [ - 'className' => null, - 'options' => [], - 'collaborators' => [], - 'mapFields' => [], - ] + $parent + $this->_defaultConfig; - - $config = array_intersect_key($config, $defaults); - $config += $defaults; - - array_walk($config, [$this, '_validateConfig']); - - foreach (['options', 'collaborators'] as $key) { - if (empty($parent[$key]) || empty($config[$key])) { - continue; - } - - $config[$key] = array_merge($parent[$key], $config[$key]); - } - } - - /** - * Validates the configuration. - * - * @param mixed $value Value. - * @param string $key Key. - * @return void - * @throws \CakeDC\Users\Auth\Exception\InvalidProviderException - * @throws \CakeDC\Users\Auth\Exception\InvalidSettingsException - */ - protected function _validateConfig(&$value, $key) - { - if ($key === 'className' && !class_exists($value)) { - throw new InvalidProviderException([$value]); - } elseif (!is_array($value) && in_array($key, ['options', 'collaborators'])) { - throw new InvalidSettingsException([$key]); - } - } - - /** - * Get the controller associated with the collection. - * - * @return \Cake\Controller\Controller Controller instance - */ - protected function _getController() - { - return $this->_registry->getController(); - } - - /** - * Returns when a provider has been enabled. - * - * @param array $options array of options by provider - * @return bool - */ - protected function _isProviderEnabled($options) - { - return !empty($options['options']['redirectUri']) && !empty($options['options']['clientId']) && - !empty($options['options']['clientSecret']); - } - - /** - * Get a user based on information in the request. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @param \Cake\Http\Response $response Response object. - * @return bool - * @throws \RuntimeException If the `CakeDC/Users/OAuth2.newUser` event is missing or returns empty. - */ - public function authenticate(ServerRequest $request, Response $response) - { - return $this->getUser($request); - } - - /** - * Authenticates with OAuth2 provider by getting an access token and - * retrieving the authorized user's profile data. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array|bool - */ - protected function _authenticate(ServerRequest $request) - { - if (!$this->_validate($request)) { - return false; - } - - $provider = $this->provider($request); - $code = $request->getQuery('code'); - - try { - $token = $provider->getAccessToken('authorization_code', compact('code')); - - return compact('token') + $provider->getResourceOwner($token)->toArray(); - } catch (\Exception $e) { - $message = sprintf( - "Error getting an access token / retrieving the authorized user's profile data. Error message: %s %s", - $e->getMessage(), - $e - ); - $this->log($message); - - return false; - } - } - - /** - * Validates OAuth2 request. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return bool - */ - protected function _validate(ServerRequest $request) - { - if (!array_key_exists('code', $request->getQueryParams()) || !$this->provider($request)) { - return false; - } - - $session = $request->getSession(); - $sessionKey = 'oauth2state'; - $state = $request->getQuery('state'); - - if ($this->getConfig('options.state') && - (!$state || $state !== $session->read($sessionKey))) { - $session->delete($sessionKey); - - return false; - } - - return true; - } - - /** - * Maps raw provider's user profile data to local user's data schema. - * - * @param array $data Raw user data. - * @return array - */ - protected function _map($data) - { - if (!$map = $this->getConfig('mapFields')) { - return $data; - } - - foreach ($map as $dst => $src) { - $data[$dst] = $data[$src]; - unset($data[$src]); - } - - return $data; - } - - /** - * Handles unauthenticated access attempts. Will automatically forward to the - * requested provider's authorization URL to let the user grant access to the - * application. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @param \Cake\Http\Response $response Response object. - * @return \Cake\Http\Response|null - */ - public function unauthenticated(ServerRequest $request, Response $response) - { - $provider = $this->provider($request); - if (empty($provider) || !empty($request->getQuery('code'))) { - return null; - } - - if ($this->getConfig('options.state')) { - $request->getSession()->write('oauth2state', $provider->getState()); - } - - $response = $response->withLocation($provider->getAuthorizationUrl()); - - return $response; - } - - /** - * Returns the `$request`-ed provider. - * - * @param \Cake\Http\ServerRequest $request Current HTTP request. - * @return \League\Oauth2\Client\Provider\GenericProvider|false - */ - public function provider(ServerRequest $request) - { - if (!$alias = $request->getParam('provider')) { - return false; - } - - if (empty($this->_provider)) { - $this->_provider = $this->_getProvider($alias); - } - - return $this->_provider; - } - - /** - * Instantiates provider object. - * - * @param string $alias of the provider. - * @return \League\Oauth2\Client\Provider\GenericProvider - */ - protected function _getProvider($alias) - { - if (!$config = $this->getConfig('providers.' . $alias)) { - return false; - } - - $this->setConfig($config); - - if (is_object($config) && $config instanceof AbstractProvider) { - return $config; - } - - $class = $config['className']; - - return new $class($config['options'], $config['collaborators']); - } - - /** - * Find or create local user - * - * @param array $data data - * @return array|bool|mixed - * @throws MissingEmailException - */ - protected function _touch(array $data) - { - try { - if (empty($data['provider']) && !empty($this->_provider)) { - $data['provider'] = SocialUtils::getProvider($this->_provider); - } - $user = $this->_socialLogin($data); - } catch (UserNotActiveException $ex) { - $exception = $ex; - } catch (AccountNotActiveException $ex) { - $exception = $ex; - } catch (MissingEmailException $ex) { - $exception = $ex; - } - if (!empty($exception)) { - $args = ['exception' => $exception, 'rawData' => $data]; - $this->_getController()->dispatchEvent(UsersAuthComponent::EVENT_FAILED_SOCIAL_LOGIN, $args); - if (method_exists($this->_getController(), 'failedSocialLogin')) { - $this->_getController()->failedSocialLogin($exception, $data, true); - } - - return false; - } - - // If new SocialAccount was created $user is returned containing it - if ($user->get('social_accounts')) { - $this->_getController()->dispatchEvent(UsersAuthComponent::EVENT_AFTER_REGISTER, compact('user')); - } - - if (!empty($user->username)) { - $user = $this->_findUser($user->username); - } - - return $user; - } - - /** - * Get a user based on information in the request. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return mixed Either false or an array of user information - * @throws \RuntimeException If the `CakeDC/Users/OAuth2.newUser` event is missing or returns empty. - */ - public function getUser(ServerRequest $request) - { - $data = $request->getSession()->read(Configure::read('Users.Key.Session.social')); - $requestDataEmail = $request->getData('email'); - if (!empty($data) && empty($data['uid']) && (!empty($data['email']) || !empty($requestDataEmail))) { - if (!empty($requestDataEmail)) { - $data['email'] = $requestDataEmail; - } - $user = $data; - $request->getSession()->delete(Configure::read('Users.Key.Session.social')); - } else { - if (empty($data) && !$rawData = $this->_authenticate($request)) { - return false; - } - if (empty($rawData)) { - $rawData = $data; - } - - $provider = $this->_getProviderName($request); - try { - $user = $this->_mapUser($provider, $rawData); - if ($this->_getController()->components()->has('Auth')) { - $this->_getController()->Auth->setConfig('authError', false); - } - } catch (MissingProviderException $ex) { - $request->getSession()->delete(Configure::read('Users.Key.Session.social')); - throw $ex; - } - if ($user['provider'] === SocialAccountsTable::PROVIDER_TWITTER) { - $request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); - } - } - - if (!$user || !$this->getConfig('userModel')) { - return false; - } - - if (!$result = $this->_touch($user)) { - return false; - } - - if ($request->getSession()->check(Configure::read('Users.Key.Session.social'))) { - $request->getSession()->delete(Configure::read('Users.Key.Session.social')); - } - $request->getSession()->write('Users.successSocialLogin', true); - - return $result; - } - - /** - * Get the provider name based on the request or on the provider set. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return mixed Either false or an array of user information - */ - protected function _getProviderName($request = null) - { - $provider = false; - if (!empty($request->getParam('provider'))) { - $provider = ucfirst($request->getParam('provider')); - } elseif (!is_null($this->_provider)) { - $provider = SocialUtils::getProvider($this->_provider); - } - - return $provider; - } - - /** - * Get the provider name based on the request or on the provider set. - * - * @param string $provider Provider name. - * @param array $data User data - * @throws MissingProviderException - * @return mixed Either false or an array of user information - */ - protected function _mapUser($provider, $data) - { - if (empty($provider)) { - throw new MissingProviderException(__d('CakeDC/Users', "Provider cannot be empty")); - } - $providerMapperClass = $this->getConfig('providers.' . strtolower($provider) . '.options.mapper') ?: "\\CakeDC\\Users\\Auth\\Social\\Mapper\\$provider"; - $providerMapper = new $providerMapperClass($data); - $user = $providerMapper(); - $user['provider'] = $provider; - - return $user; - } - - /** - * @param mixed $data data - * @return mixed - */ - protected function _socialLogin($data) - { - $options = [ - 'use_email' => Configure::read('Users.Email.required'), - 'validate_email' => Configure::read('Users.Email.validate'), - 'token_expiration' => Configure::read('Users.Token.expiration') - ]; - - $userModel = Configure::read('Users.table'); - $User = TableRegistry::getTableLocator()->get($userModel); - $user = $User->socialLogin($data, $options); - - return $user; - } -} diff --git a/src/Authenticator/SocialAuthTrait.php b/src/Authenticator/SocialAuthTrait.php new file mode 100644 index 000000000..9f3cef77c --- /dev/null +++ b/src/Authenticator/SocialAuthTrait.php @@ -0,0 +1,46 @@ +getIdentifier()->identify([SocialIdentifier::CREDENTIAL_KEY => $rawData]); + if (!empty($user)) { + return new Result($user, Result::SUCCESS); + } + + return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND); + } catch (AccountNotActiveException $e) { + return new Result(null, self::FAILURE_ACCOUNT_NOT_ACTIVE); + } catch (UserNotActiveException $e) { + return new Result(null, self::FAILURE_USER_NOT_ACTIVE); + } catch (MissingEmailException $exception) { + throw new SocialAuthenticationException(['rawData' => $rawData], null, $exception); + } + } +} diff --git a/src/Authenticator/SocialAuthenticator.php b/src/Authenticator/SocialAuthenticator.php new file mode 100644 index 000000000..3edd6394d --- /dev/null +++ b/src/Authenticator/SocialAuthenticator.php @@ -0,0 +1,32 @@ + [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ], + 'urlChecker' => 'Authentication.CakeRouter', + ]; + + /** + * Prepares the error object for a login URL error + * + * @param \Psr\Http\Message\ServerRequestInterface $request The request that contains login information. + * @return \Authentication\Authenticator\ResultInterface + */ + protected function _buildLoginUrlErrorResult($request) + { + $errors = [ + sprintf( + 'Login URL `%s` did not match `%s`.', + (string)$request->getUri(), + implode('` or `', (array)$this->getConfig('loginUrl')) + ), + ]; + + return new Result(null, Result::FAILURE_OTHER, $errors); + } + + /** + * Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields` + * to find POST data that is used to find a matching record in the `config.userModel`. Will return false if + * there is no post data, either username or password is missing, or if the scope conditions have not been met. + * + * @param \Psr\Http\Message\ServerRequestInterface $request The request that contains login information. + * @return \Authentication\Authenticator\ResultInterface + */ + public function authenticate(ServerRequestInterface $request): ResultInterface + { + if (!$this->_checkUrl($request)) { + return $this->_buildLoginUrlErrorResult($request); + } + $rawData = $request->getAttribute('session')->read(Configure::read('Users.Key.Session.social')); + $body = $request->getParsedBody(); + $email = $body['email'] ?? null; + + if (empty($rawData) || empty($email)) { + return new Result(null, Result::FAILURE_CREDENTIALS_MISSING); + } + $rawData['email'] = $email; + + return $this->identify($rawData); + } +} diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 40b1bf4bd..68c4951e2 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -1,11 +1,13 @@ loadComponent('Security'); if ($this->request->getParam('_csrfToken') === false) { $this->loadComponent('Csrf'); } - $this->loadComponent('CakeDC/Users.UsersAuth'); + $this->loadComponent('CakeDC/Users.Setup'); } } diff --git a/src/Controller/Component/GoogleAuthenticatorComponent.php b/src/Controller/Component/GoogleAuthenticatorComponent.php deleted file mode 100644 index 71fab3cb6..000000000 --- a/src/Controller/Component/GoogleAuthenticatorComponent.php +++ /dev/null @@ -1,83 +0,0 @@ -tfa = new TwoFactorAuth( - Configure::read('Users.GoogleAuthenticator.issuer'), - Configure::read('Users.GoogleAuthenticator.digits'), - Configure::read('Users.GoogleAuthenticator.period'), - Configure::read('Users.GoogleAuthenticator.algorithm'), - Configure::read('Users.GoogleAuthenticator.qrcodeprovider'), - Configure::read('Users.GoogleAuthenticator.rngprovider') - ); - } - } - - /** - * createSecret - * @return string base32 shared secret stored in users table - */ - public function createSecret() - { - return $this->tfa->createSecret(); - } - - /** - * verifyCode - * Verifying tfa code with shared secret - * @param string $secret of the user - * @param string $code from verification form - * @return bool - */ - public function verifyCode($secret, $code) - { - return $this->tfa->verifyCode($secret, $code); - } - - /** - * getQRCodeImageAsDataUri - * @param string $issuer issuer - * @param string $secret secret - * @return string base64 string containing QR code for shared secret - */ - public function getQRCodeImageAsDataUri($issuer, $secret) - { - return $this->tfa->getQRCodeImageAsDataUri($issuer, $secret); - } -} diff --git a/src/Controller/Component/LoginComponent.php b/src/Controller/Component/LoginComponent.php new file mode 100644 index 000000000..7ddbbf420 --- /dev/null +++ b/src/Controller/Component/LoginComponent.php @@ -0,0 +1,238 @@ + null, + 'messages' => [], + 'targetAuthenticator' => null, + ]; + + /** + * Gets the request instance. + * + * @return \Cake\Http\ServerRequest + */ + public function getRequest(): ServerRequest + { + return $this->getController()->getRequest(); + } + + /** + * Handle login, if success redirect to 'AuthenticationComponent.loginRedirect' or show error + * + * @param bool $errorOnlyPost should handle failure only on post request + * @param bool $redirectFailure should redirect on failure? + * @return \Cake\Http\Response|null + */ + public function handleLogin($errorOnlyPost, $redirectFailure) + { + $request = $this->getController()->getRequest(); + $service = $request->getAttribute('authentication'); + if (!$service) { + throw new \UnexpectedValueException('Authentication service not found in this request'); + } + $result = $service->getResult(); + if ($result->isValid()) { + $user = $request->getAttribute('identity')->getOriginalData(); + $this->handlePasswordRehash($service, $user, $request); + $this->updateLastLogin($user); + + return $this->afterIdentifyUser($user); + } + if ($request->is('post') || $errorOnlyPost === false) { + return $this->handleFailure($redirectFailure); + } + + return null; + } + + /** + * Handle login failure + * + * @param bool $redirect should redirect? + * @return \Cake\Http\Response|null + */ + public function handleFailure($redirect = true) + { + $controller = $this->getController(); + $request = $controller->getRequest(); + + $service = $request->getAttribute('authentication'); + $result = $this->getTargetAuthenticatorResult($service); + $controller->Flash->error($this->getErrorMessage($result), ['element' => 'default', 'key' => 'auth']); + + if (!$redirect) { + return null; + } + + return $controller->redirect(UsersUrl::actionUrl('login')); + } + + /** + * Get the target authenticator result for current login action + * + * @param \CakeDC\Auth\Authentication\AuthenticationService $service authentication service. + * @return \Authentication\Authenticator\ResultInterface|null + */ + public function getTargetAuthenticatorResult(AuthenticationService $service) + { + $target = $this->getConfig('targetAuthenticator'); + $failures = $service->getFailures(); + foreach ($failures as $failure) { + if ($failure->getAuthenticator() instanceof $target) { + return $failure->getResult(); + } + } + + return null; + } + + /** + * Get the error message for result status + * + * @param \Authentication\Authenticator\ResultInterface|null $result Result object; + * @return string + */ + public function getErrorMessage(?ResultInterface $result = null) + { + $messagesMap = $this->getConfig('messages'); + + if ($result === null || !isset($messagesMap[$result->getStatus()])) { + return $this->getConfig('defaultMessage'); + } + + return $messagesMap[$result->getStatus()]; + } + + /** + * Determine redirect url after user identified + * + * @param array $user user data after identified + * @return \Cake\Http\Response|null + */ + protected function afterIdentifyUser($user) + { + $event = $this->getController()->dispatchEvent(Plugin::EVENT_AFTER_LOGIN, ['user' => $user]); + if (is_array($event->getResult())) { + // in this case we don't checkSafeHost the url as the url params are generated by an event + return $this->getController()->redirect($event->getResult()); + } + + $queryRedirect = $this->getController()->getRequest()->getQuery('redirect'); + $redirectUrl = $this->getController()->Authentication->getConfig('loginRedirect'); + if (!$this->checkSafeHost($queryRedirect)) { + $userId = $user['id'] ?? null; + Log::info( + "Unsafe redirect `$queryRedirect` ignored, user id `{$userId}` " . + "redirected to `$redirectUrl` after successful login" + ); + $queryRedirect = $redirectUrl; + } + // even if the host is safe, we need to check if the url is authorized for the given user + // this check ignores the host + if ($this->isAuthorized($queryRedirect ?? null)) { + $redirectUrl = $queryRedirect; + } + + return $this->getController()->redirect($redirectUrl); + } + + /** + * Handle password rehash logic + * + * @param \CakeDC\Auth\Authentication\AuthenticationService $service Authentication service + * @param \CakeDC\Users\Model\Entity\User $user User entity. + * @param \Cake\Http\ServerRequest $request The http request. + * @return void + */ + protected function handlePasswordRehash($service, $user, \Cake\Http\ServerRequest $request) + { + $indentifiersNames = (array)Configure::read('Auth.PasswordRehash.identifiers'); + foreach ($indentifiersNames as $indentifierName) { + /** + * @var \Authentication\Identifier\AbstractIdentifier|null $checker + */ + $checker = $service->identifiers()->get($indentifierName); + if (!$checker || method_exists($checker, 'needsPasswordRehash') && !$checker->needsPasswordRehash()) { + continue; + } + $password = $request->getData('password'); + $user->set('password', $password); + $user->setDirty('modified'); + $this->getController()->getUsersTable()->save($user); + break; + } + } + + /** + * Check if there is a host defined in the $queryRedirect and it's in the allowed list of hosts + * + * @param string|null $queryRedirect redirect url + * @return bool + */ + protected function checkSafeHost(?string $queryRedirect = null): bool + { + if ($queryRedirect === null) { + return true; + } + + $uri = new Uri($queryRedirect); + $host = $uri->getHost(); + if (!$host) { + return true; + } + + return in_array($host, Configure::read('Users.AllowedRedirectHosts')); + } + + /** + * Update last loging date + * + * @param \CakeDC\Users\Model\Entity\User $user User entity. + * @return void + */ + protected function updateLastLogin($user) + { + $now = \Cake\I18n\FrozenTime::now(); + $user->set('last_login', $now); + $this->getController()->getUsersTable()->updateAll( + ['last_login' => $now], + ['id' => $user->id] + ); + } +} diff --git a/src/Controller/Component/RememberMeComponent.php b/src/Controller/Component/RememberMeComponent.php deleted file mode 100644 index 742d91b63..000000000 --- a/src/Controller/Component/RememberMeComponent.php +++ /dev/null @@ -1,159 +0,0 @@ -_cookieName = Configure::read('Users.RememberMe.Cookie.name'); - $this->_validateConfig(); - $this->setCookieOptions(); - $this->_attachEvents(); - } - - /** - * Validate component config - * - * @throws InvalidArgumentException - * @return void - */ - protected function _validateConfig() - { - if (mb_strlen(Security::getSalt(), '8bit') < 32) { - throw new InvalidArgumentException( - __d('CakeDC/Users', 'Invalid app salt, app salt must be at least 256 bits (32 bytes) long') - ); - } - } - - /** - * Attach the afterLogin and beforeLogount events - * - * @return void - */ - protected function _attachEvents() - { - $eventManager = $this->getController()->getEventManager(); - $eventManager->on(UsersAuthComponent::EVENT_AFTER_LOGIN, [], [$this, 'setLoginCookie']); - $eventManager->on(UsersAuthComponent::EVENT_BEFORE_LOGOUT, [], [$this, 'destroy']); - } - - /** - * Sets cookie configuration options - * - * @return void - */ - public function setCookieOptions() - { - $cookieConfig = Configure::read('Users.RememberMe.Cookie.Config'); - $this->Cookie->configKey($this->_cookieName, $cookieConfig); - } - - /** - * Sets the login cookie that handles the remember me feature - * - * @param Event $event event - * @return void - */ - public function setLoginCookie(Event $event) - { - $user['id'] = $this->Auth->user('id'); - if (empty($user)) { - return; - } - $user['user_agent'] = $this->getController()->getRequest()->getHeaderLine('User-Agent'); - if (!(bool)$this->getController()->getRequest()->getData(Configure::read('Users.Key.Data.rememberMe'))) { - return; - } - $this->Cookie->write($this->_cookieName, $user); - } - - /** - * Destroys the remember me cookie - * - * @param Event $event event - * @return void - */ - public function destroy(Event $event) - { - if ($this->Cookie->check($this->_cookieName)) { - $this->Cookie->delete($this->_cookieName); - } - } - - /** - * Reads the stored cookie and auto login the user if present - * - * @param Event $event event - * @return mixed - */ - public function beforeFilter(Event $event) - { - $user = $this->Auth->user(); - if (!empty($user) || - $this->getController()->getRequest()->is(['post', 'put']) || - $this->getController()->getRequest()->getParam('action') === 'logout' || - $this->getController()->getRequest()->getSession()->check(Configure::read('Users.Key.Session.social')) || - $this->getController()->getRequest()->getParam('provider')) { - return; - } - - $user = $this->Auth->identify(); - // No user no cookies - if (empty($user)) { - return; - } - $this->Auth->setUser($user); - $event = $this->getController()->dispatchEvent(UsersAuthComponent::EVENT_AFTER_COOKIE_LOGIN); - if (is_array($event->result)) { - return $this->getController()->redirect($event->result); - } - $url = $this->getController()->getRequest()->getRequestTarget(); - - return $this->getController()->redirect($url); - } -} diff --git a/src/Controller/Component/SetupComponent.php b/src/Controller/Component/SetupComponent.php new file mode 100644 index 000000000..62d9cdfc6 --- /dev/null +++ b/src/Controller/Component/SetupComponent.php @@ -0,0 +1,57 @@ +loadAuthComponents($this->getController()); + } + + /** + * Load all auth components needed: Authentication.Authentication, Authorization.Authorization and CakeDC/OneTimePasswordAuthenticator + * + * @param \Cake\Controller\Controller $controller Target controller + * @return void + * @throws \Exception + */ + protected function loadAuthComponents($controller) + { + $authenticationConfig = Configure::read('Auth.AuthenticationComponent'); + if ($authenticationConfig['load'] ?? false) { + unset($authenticationConfig['config']); + $controller->loadComponent('Authentication.Authentication', $authenticationConfig); + } + + if (Configure::read('Auth.AuthorizationComponent.enable') !== false) { + $config = (array)Configure::read('Auth.AuthorizationComponent'); + $controller->loadComponent('Authorization.Authorization', $config); + } + + if (Configure::read('OneTimePasswordAuthenticator.login') !== false) { + $controller->loadComponent('CakeDC/Auth.OneTimePasswordAuthenticator'); + } + } +} diff --git a/src/Controller/Component/UsersAuthComponent.php b/src/Controller/Component/UsersAuthComponent.php deleted file mode 100644 index f45ef42a4..000000000 --- a/src/Controller/Component/UsersAuthComponent.php +++ /dev/null @@ -1,234 +0,0 @@ -_validateConfig(); - $this->_initAuth(); - - if (Configure::read('Users.Social.login')) { - $this->_loadSocialLogin(); - } - if (Configure::read('Users.RememberMe.active')) { - $this->_loadRememberMe(); - } - - if (Configure::read('Users.GoogleAuthenticator.login')) { - $this->_loadGoogleAuthenticator(); - } - - $this->_attachPermissionChecker(); - } - - /** - * Load GoogleAuthenticator object - * - * @return void - */ - protected function _loadGoogleAuthenticator() - { - $this->getController()->loadComponent('CakeDC/Users.GoogleAuthenticator'); - } - - /** - * Load Social Auth object - * - * @return void - */ - protected function _loadSocialLogin() - { - $this->getController()->Auth->setConfig('authenticate', [ - Configure::read('Users.Social.authenticator') - ], true); - } - - /** - * Load RememberMe component and Auth objects - * - * @return void - */ - protected function _loadRememberMe() - { - $this->getController()->loadComponent('CakeDC/Users.RememberMe'); - } - - /** - * Attach the isUrlAuthorized event to allow using the Auth authorize from the UserHelper - * - * @return void - */ - protected function _attachPermissionChecker() - { - EventManager::instance()->on(self::EVENT_IS_AUTHORIZED, [], [$this, 'isUrlAuthorized']); - } - - /** - * Initialize the AuthComponent and configure allowed actions - * - * @return void - */ - protected function _initAuth() - { - if (Configure::read('Users.auth')) { - //initialize Auth - $this->getController()->loadComponent('Auth', Configure::read('Auth')); - } - - list($plugin, $controller) = pluginSplit(Configure::read('Users.controller')); - if ($this->getController()->getRequest()->getParam('plugin', null) === $plugin && - $this->getController()->getRequest()->getParam('controller') === $controller - ) { - $this->getController()->Auth->allow([ - // LoginTrait - 'twitterLogin', - 'login', - 'socialEmail', - 'verify', - // RegisterTrait - 'register', - 'validateEmail', - // PasswordManagementTrait used in RegisterTrait - 'changePassword', - 'resetPassword', - 'requestResetPassword', - // UserValidationTrait used in PasswordManagementTrait - 'resendTokenValidation', - // Social - 'endpoint', - 'authenticated', - ]); - } - } - - /** - * Check if a given url is authorized - * - * @param Event $event event - * - * @return bool - */ - public function isUrlAuthorized(Event $event) - { - $url = Hash::get((array)$event->getData(), 'url'); - if (empty($url)) { - return false; - } - - if (is_array($url)) { - $requestUrl = Router::normalize(Router::reverse($url)); - $requestParams = Router::parseRequest(new ServerRequest($requestUrl)); - } else { - try { - //remove base from $url if exists - $normalizedUrl = Router::normalize($url); - $requestParams = Router::parseRequest(new ServerRequest($normalizedUrl)); - } catch (MissingRouteException $ex) { - //if it's a url pointing to our own app - if (substr($normalizedUrl, 0, 1) === '/') { - throw $ex; - } - - return true; - } - $requestUrl = $url; - } - // check if controller action is allowed - if ($this->_isActionAllowed($requestParams)) { - return true; - } - - // check we are logged in - $user = $this->getController()->Auth->user(); - if (empty($user)) { - return false; - } - - $request = new ServerRequest($requestUrl); - $request = $request->withAttribute('params', $requestParams); - - $isAuthorized = $this->getController()->Auth->isAuthorized(null, $request); - - return $isAuthorized; - } - - /** - * Validate if the passed configuration makes sense - * - * @throws BadConfigurationException - * @return void - */ - protected function _validateConfig() - { - if (!Configure::read('Users.Email.required') && Configure::read('Users.Email.validate')) { - $message = __d('CakeDC/Users', 'You can\'t enable email validation workflow if use_email is false'); - throw new BadConfigurationException($message); - } - } - - /** - * Check if the action is in allowedActions array for the controller - * Important, this function will check only for allowed actions in the current - * controller, creating a new instance and providing initialization for the Auth - * instance in another controller could lead to undesired side effects. - * - * @param array $requestParams request parameters - * @return bool - */ - protected function _isActionAllowed($requestParams = []) - { - if (empty($requestParams['action'])) { - return false; - } - if (!empty($requestParams['controller']) && $requestParams['controller'] !== $this->getController()->getName()) { - return false; - } - $action = strtolower($requestParams['action']); - if (in_array($action, array_map('strtolower', $this->getController()->Auth->allowedActions))) { - return true; - } - - return false; - } -} diff --git a/src/Controller/SocialAccountsController.php b/src/Controller/SocialAccountsController.php index b22507464..e69a14869 100644 --- a/src/Controller/SocialAccountsController.php +++ b/src/Controller/SocialAccountsController.php @@ -1,39 +1,37 @@ Auth->allow(['validateAccount', 'resendValidation']); } /** @@ -42,26 +40,26 @@ public function initialize() * @param string $provider provider * @param string $reference reference * @param string $token token - * @return Response + * @return \Cake\Http\Response */ public function validateAccount($provider, $reference, $token) { try { $result = $this->SocialAccounts->validateAccount($provider, $reference, $token); if ($result) { - $this->Flash->success(__d('CakeDC/Users', 'Account validated successfully')); + $this->Flash->success(__d('cake_d_c/users', 'Account validated successfully')); } else { - $this->Flash->error(__d('CakeDC/Users', 'Account could not be validated')); + $this->Flash->error(__d('cake_d_c/users', 'Account could not be validated')); } } catch (RecordNotFoundException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Invalid token and/or social account')); + $this->Flash->error(__d('cake_d_c/users', 'Invalid token and/or social account')); } catch (AccountAlreadyActiveException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Social Account already active')); + $this->Flash->error(__d('cake_d_c/users', 'Social Account already active')); } catch (\Exception $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Social Account could not be validated')); + $this->Flash->error(__d('cake_d_c/users', 'Social Account could not be validated')); } - return $this->redirect(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + return $this->redirect(UsersUrl::actionUrl('login')); } /** @@ -70,25 +68,25 @@ public function validateAccount($provider, $reference, $token) * @param string $provider provider * @param string $reference reference * @return mixed - * @throws AccountAlreadyActiveException + * @throws \CakeDC\Users\Exception\AccountAlreadyActiveException */ public function resendValidation($provider, $reference) { try { $result = $this->SocialAccounts->resendValidation($provider, $reference); if ($result) { - $this->Flash->success(__d('CakeDC/Users', 'Email sent successfully')); + $this->Flash->success(__d('cake_d_c/users', 'Email sent successfully')); } else { - $this->Flash->error(__d('CakeDC/Users', 'Email could not be sent')); + $this->Flash->error(__d('cake_d_c/users', 'Email could not be sent')); } } catch (RecordNotFoundException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Invalid account')); + $this->Flash->error(__d('cake_d_c/users', 'Invalid account')); } catch (AccountAlreadyActiveException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Social Account already active')); + $this->Flash->error(__d('cake_d_c/users', 'Social Account already active')); } catch (\Exception $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Email could not be resent')); + $this->Flash->error(__d('cake_d_c/users', 'Email could not be resent')); } - return $this->redirect(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + return $this->redirect(UsersUrl::actionUrl('login')); } } diff --git a/src/Controller/Traits/CustomUsersTableTrait.php b/src/Controller/Traits/CustomUsersTableTrait.php index f016f66de..c003996ca 100644 --- a/src/Controller/Traits/CustomUsersTableTrait.php +++ b/src/Controller/Traits/CustomUsersTableTrait.php @@ -1,11 +1,13 @@ _getSocialProvider($alias); + $authUrl = (new ServiceFactory()) + ->setRedirectUriField('callbackLinkSocialUri') + ->createFromProvider($alias) + ->getAuthorizationUrl($this->getRequest()); - $temporaryCredentials = []; - if (ucfirst($alias) === SocialAccountsTable::PROVIDER_TWITTER) { - $temporaryCredentials = $provider->getTemporaryCredentials(); - $this->request->getSession()->write('temporary_credentials', $temporaryCredentials); - } - $authUrl = $provider->getAuthorizationUrl($temporaryCredentials); - if (empty($temporaryCredentials)) { - $this->request->session()->write('SocialLink.oauth2state', $provider->getState()); - } + $this->dispatchEvent(Plugin::EVENT_BEFORE_SOCIAL_LOGIN_REDIRECT, [ + 'location' => $authUrl, + 'request' => $this->request, + ]); return $this->redirect($authUrl); } @@ -51,80 +48,40 @@ public function linkSocial($alias = null) * Callback to get user information from provider * * @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 */ public function callbackLinkSocial($alias = null) { - $message = __d('CakeDC/Users', 'Could not associate account, please try again.'); - $provider = $this->_getSocialProvider($alias); - $error = false; - if (ucfirst($alias) === SocialAccountsTable::PROVIDER_TWITTER) { - $server = new Twitter([ - 'identifier' => Configure::read('OAuth.providers.twitter.options.clientId'), - 'secret' => Configure::read('OAuth.providers.twitter.options.clientSecret'), - 'callbackUri' => Configure::read('OAuth.providers.twitter.options.callbackLinkSocialUri'), - ]); - $oauthToken = $this->request->getQuery('oauth_token'); - $oauthVerifier = $this->request->getQuery('oauth_verifier'); - if (!empty($oauthToken) && !empty($oauthVerifier)) { - $temporaryCredentials = $this->request->getSession()->read('temporary_credentials'); - try { - $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $oauthToken, $oauthVerifier); - $data = (array)$server->getUserDetails($tokenCredentials); - $data['token'] = [ - 'accessToken' => $tokenCredentials->getIdentifier(), - 'tokenSecret' => $tokenCredentials->getSecret(), - ]; - } catch (\Exception $e) { - $error = $e; - } - } - } else { - if (!$this->_validateCallbackSocialLink()) { + $message = __d('cake_d_c/users', 'Could not associate account, please try again.'); + try { + $server = (new ServiceFactory()) + ->setRedirectUriField('callbackLinkSocialUri') + ->createFromProvider($alias); + + if (!$server->isGetUserStep($this->getRequest())) { $this->Flash->error($message); return $this->redirect(['action' => 'profile']); } - $code = $this->request->getQuery('code'); - try { - $token = $provider->getAccessToken('authorization_code', compact('code')); - - $data = compact('token') + $provider->getResourceOwner($token)->toArray(); - } catch (\Exception $e) { - $error = $e; - } - } - - if (!empty($error) || empty($data)) { - $log = sprintf( - "Error getting an access token. Error message: %s %s", - $error->getMessage(), - $error - ); - $this->log($log); - - $this->Flash->error($message); - - return $this->redirect(['action' => 'profile']); - } - - try { - $data = $this->_mapSocialUser($alias, $data); - - $user = $this->getUsersTable()->get($this->Auth->user('id')); + $data = $server->getUser($this->getRequest()); + $mapper = new MapUser(); + $data = $mapper($server, $data); + $identity = $this->getRequest()->getAttribute('identity'); + $identity = $identity ?? []; + $userId = $identity['id'] ?? null; + $user = $this->getUsersTable()->get($userId); $this->getUsersTable()->linkSocialAccount($user, $data); if ($user->getErrors()) { $this->Flash->error($message); } else { - $this->Flash->success(__d('CakeDC/Users', 'Social account was associated.')); + $this->Flash->success(__d('cake_d_c/users', 'Social account was associated.')); } } catch (\Exception $e) { $log = sprintf( - "Error retrieving the authorized user's profile data. Error message: %s %s", + 'Error linking social account: %s %s', $e->getMessage(), $e ); @@ -135,103 +92,4 @@ public function callbackLinkSocial($alias = null) return $this->redirect(['action' => 'profile']); } - - /** - * Get the provider name based on the request or on the provider set. - * - * @param string $alias of the provider. - * @param array $data User data. - * - * @throws MissingProviderException - * @return array - */ - protected function _mapSocialUser($alias, $data) - { - $alias = ucfirst($alias); - $providerMapperClass = "\\CakeDC\\Users\\Auth\\Social\\Mapper\\$alias"; - $providerMapper = new $providerMapperClass($data); - $user = $providerMapper(); - $user['provider'] = $alias; - - return $user; - } - - /** - * Instantiates provider object. - * - * @param string $alias of the provider. - * - * @throws \Cake\Http\Exception\NotFoundException - * @return \League\OAuth2\Client\Provider\AbstractProvider|\League\OAuth1\Client\Server\Twitter - */ - protected function _getSocialProvider($alias) - { - $config = Configure::read('OAuth.providers.' . $alias); - if (!$config || !isset($config['options'], $config['options']['callbackLinkSocialUri'])) { - throw new NotFoundException; - } - - if (!isset($config['options']['clientId'], $config['options']['clientSecret'])) { - throw new NotFoundException; - } - - return $this->_createSocialProvider($config, ucfirst($alias)); - } - - /** - * Instantiates provider object. - * - * @param array $config for social provider. - * @param string $alias provider alias - * - * @throws \Cake\Http\Exception\NotFoundException - * @return \League\OAuth2\Client\Provider\AbstractProvider|\League\OAuth1\Client\Server\Twitter - */ - protected function _createSocialProvider($config, $alias = null) - { - if ($alias === SocialAccountsTable::PROVIDER_TWITTER) { - $server = new Twitter([ - 'identifier' => Configure::read('OAuth.providers.twitter.options.clientId'), - 'secret' => Configure::read('OAuth.providers.twitter.options.clientSecret'), - 'callback_uri' => Configure::read('OAuth.providers.twitter.options.callbackLinkSocialUri'), - ]); - - return $server; - } - $class = $config['className']; - $redirectUri = $config['options']['callbackLinkSocialUri']; - - unset($config['options']['callbackLinkSocialUri'], $config['options']['linkSocialUri']); - - $config['options']['redirectUri'] = $redirectUri; - - return new $class($config['options'], []); - } - - /** - * Validates OAuth2 request. - * - * @return bool - */ - protected function _validateCallbackSocialLink() - { - $queryParams = $this->request->getQueryParams(); - - if (isset($queryParams['error']) && !empty($queryParams['error'])) { - $this->log('Got error in _validateCallbackSocialLink: ' . htmlspecialchars($queryParams['error'], ENT_QUOTES, 'UTF-8')); - - return false; - } - - if (!array_key_exists('code', $queryParams)) { - return false; - } - - $sessionKey = 'SocialLink.oauth2state'; - $oauth2state = $this->request->session()->read($sessionKey); - $this->request->session()->delete($sessionKey); - $state = $queryParams['state']; - - return $oauth2state === $state; - } } diff --git a/src/Controller/Traits/LoginTrait.php b/src/Controller/Traits/LoginTrait.php index 28c30d588..2b1c637ac 100644 --- a/src/Controller/Traits/LoginTrait.php +++ b/src/Controller/Traits/LoginTrait.php @@ -1,27 +1,21 @@ autoRender = false; - $server = new Twitter([ - 'identifier' => Configure::read('OAuth.providers.twitter.options.clientId'), - 'secret' => Configure::read('OAuth.providers.twitter.options.clientSecret'), - 'callback_uri' => Configure::read('OAuth.providers.twitter.options.redirectUri'), - ]); - $oauthToken = $this->request->getQuery('oauth_token'); - $oauthVerifier = $this->request->getQuery('oauth_verifier'); - if (!empty($oauthToken) && !empty($oauthVerifier)) { - $temporaryCredentials = $this->request->getSession()->read('temporary_credentials'); - $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $oauthToken, $oauthVerifier); - $user = (array)$server->getUserDetails($tokenCredentials); - $user['token'] = [ - 'accessToken' => $tokenCredentials->getIdentifier(), - 'tokenSecret' => $tokenCredentials->getSecret(), - ]; - $this->request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); - try { - $user = $this->Auth->identify(); - $this->_afterIdentifyUser($user, true); - } catch (UserNotActiveException $ex) { - $exception = $ex; - } catch (AccountNotActiveException $ex) { - $exception = $ex; - } catch (MissingEmailException $ex) { - $exception = $ex; - } - - if (!empty($exception)) { - return $this->failedSocialLogin( - $exception, - $this->request->getSession()->read(Configure::read('Users.Key.Session.social')), - true - ); - } - } else { - $temporaryCredentials = $server->getTemporaryCredentials(); - $this->request->getSession()->write('temporary_credentials', $temporaryCredentials); - $url = $server->getAuthorizationUrl($temporaryCredentials); - - return $this->redirect($url); - } - } - - /** - * @param Event $event event - * @return mixed - */ - public function failedSocialLoginListener(Event $event) - { - return $this->failedSocialLogin($event->data['exception'], $event->data['rawData'], true); - } - - /** - * @param mixed $exception exception - * @param mixed $data data - * @param bool|false $flash flash - * @return mixed - */ - public function failedSocialLogin($exception, $data, $flash = false) - { - $msg = __d('CakeDC/Users', 'Issues trying to log in with your social account'); - - if (isset($exception)) { - if ($exception instanceof MissingEmailException) { - if ($flash) { - $this->Flash->success(__d('CakeDC/Users', 'Please enter your email'), ['clear' => true]); - } - $this->request->getSession()->write(Configure::read('Users.Key.Session.social'), $data); - - return $this->redirect([ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'socialEmail' - ]); - } - if ($exception instanceof UserNotActiveException) { - $msg = __d( - 'CakeDC/Users', - 'Your user has not been validated yet. Please check your inbox for instructions' - ); - } elseif ($exception instanceof AccountNotActiveException) { - $msg = __d( - 'CakeDC/Users', - 'Your social account has not been validated yet. Please check your inbox for instructions' - ); - } - } - if ($flash) { - $this->request->getSession()->delete(Configure::read('Users.Key.Session.social')); - $this->Flash->success($msg, ['clear' => true]); - } - - return $this->redirect(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); - } - /** * Social login * - * @throws NotFoundException - * @return array + * @throws \Cake\Http\Exception\NotFoundException + * @return mixed */ public function socialLogin() { - $socialProvider = $this->request->getParam('provider'); - $socialUser = $this->request->getSession()->read(Configure::read('Users.Key.Session.social')); - - if (empty($socialProvider) && empty($socialUser)) { - throw new NotFoundException(); - } - $user = $this->Auth->user(); + $Login = LoginComponentLoader::forSocial($this); - return $this->_afterIdentifyUser($user, true); + return $Login->handleLogin(false, true); } /** * Login user * * @return mixed + * @throws \Exception */ public function login() { - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_BEFORE_LOGIN); - if (is_array($event->result)) { - return $this->_afterIdentifyUser($event->result); - } - if ($event->isStopped()) { - return $this->redirect($event->result); - } - - $socialLogin = $this->_isSocialLogin(); - $googleAuthenticatorLogin = $this->_isGoogleAuthenticator(); - - if ($this->request->is('post')) { - if (!$this->_checkReCaptcha()) { - $this->Flash->error(__d('CakeDC/Users', 'Invalid reCaptcha')); - - return; - } - $user = $this->Auth->identify(); - - return $this->_afterIdentifyUser($user, $socialLogin, $googleAuthenticatorLogin); - } - - if (!$this->request->is('post') && !$socialLogin) { - if ($this->Auth->user()) { - if (!$this->request->getSession()->read('Users.successSocialLogin')) { - $msg = __d('CakeDC/Users', 'You are already logged in'); - $this->Flash->error($msg); - } else { - $this->request->getSession()->delete('Users.successSocialLogin'); - $this->request->getSession()->delete('Flash'); - } - $url = $this->Auth->redirectUrl(); - - return $this->redirect($url); - } - } - } - - /** - * Verify for Google Authenticator - * If Google Authenticator's enabled we need to verify - * authenticated user. To avoid accidental access to - * other URL's we store auth'ed used into temporary session - * to perform code verification. - * - * @return mixed - */ - public function verify() - { - if (!Configure::read('Users.GoogleAuthenticator.login')) { - $message = __d('CakeDC/Users', 'Please enable Google Authenticator first.'); - $this->Flash->error($message, 'default', [], 'auth'); - - return $this->redirect(Configure::read('Auth.loginAction')); - } - - // storing user's session in the temporary one - // until the GA verification is checked - $temporarySession = $this->Auth->user(); - $this->request->getSession()->delete('Auth.User'); - - if (!empty($temporarySession)) { - $this->request->getSession()->write('temporarySession', $temporarySession); - } - - if (array_key_exists('secret', $temporarySession)) { - $secret = $temporarySession['secret']; - } - - $secretVerified = Hash::get((array)$temporarySession, 'secret_verified'); - - // showing QR-code until shared secret is verified - if (!$secretVerified) { - if (empty($secret)) { - $secret = $this->GoogleAuthenticator->createSecret(); - - // catching sql exception in case of any sql inconsistencies - try { - $query = $this->getUsersTable()->query(); - $query->update() - ->set(['secret' => $secret]) - ->where(['id' => $temporarySession['id']]); - $query->execute(); - } catch (\Exception $e) { - $this->request->getSession()->destroy(); - $message = $e->getMessage(); - $this->Flash->error($message, 'default', [], 'auth'); - - return $this->redirect(Configure::read('Auth.loginAction')); - } - } - $secretDataUri = $this->GoogleAuthenticator->getQRCodeImageAsDataUri( - Hash::get((array)$temporarySession, 'email'), - $secret - ); - $this->set(compact('secretDataUri')); - } - - if ($this->request->is('post')) { - $codeVerified = false; - $verificationCode = $this->request->getData('code'); - $user = $this->request->getSession()->read('temporarySession'); - $entity = $this->getUsersTable()->get($user['id']); - - if (!empty($entity['secret'])) { - $codeVerified = $this->GoogleAuthenticator->verifyCode($entity['secret'], $verificationCode); - } - - if ($codeVerified) { - unset($user['secret']); - - if (!$user['secret_verified']) { - $this->getUsersTable()->query()->update() - ->set(['secret_verified' => true]) - ->where(['id' => $user['id']]) - ->execute(); - } - - $this->request->getSession()->delete('temporarySession'); - $this->request->getSession()->write('Auth.User', $user); - $url = $this->Auth->redirectUrl(); - - return $this->redirect($url); - } else { - $this->request->getSession()->destroy(); - $message = __d('CakeDC/Users', 'Verification code is invalid. Try again'); - $this->Flash->error($message, 'default', [], 'auth'); - - return $this->redirect(Configure::read('Auth.loginAction')); - } - } - } - - /** - * Check reCaptcha if enabled for login - * - * @return bool - */ - protected function _checkReCaptcha() - { - if (!Configure::read('Users.reCaptcha.login')) { - return true; - } - - return $this->validateReCaptcha( - $this->request->getData('g-recaptcha-response'), - $this->request->clientIp() - ); - } - - /** - * Update remember me and determine redirect url after user identified - * @param array $user user data after identified - * @param bool $socialLogin is social login - * @param bool $googleAuthenticatorLogin googleAuthenticatorLogin - * @return array - */ - protected function _afterIdentifyUser($user, $socialLogin = false, $googleAuthenticatorLogin = false) - { - if (!empty($user)) { - $this->Auth->setUser($user); - - if ($googleAuthenticatorLogin) { - $url = Configure::read('GoogleAuthenticator.verifyAction'); - - return $this->redirect($url); - } - - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_AFTER_LOGIN, ['user' => $user]); - if (is_array($event->result)) { - return $this->redirect($event->result); - } - - $url = $this->Auth->redirectUrl(); - - return $this->redirect($url); - } else { - if (!$socialLogin) { - $message = __d('CakeDC/Users', 'Username or password is incorrect'); - $this->Flash->error($message, 'default', [], 'auth'); - } + $this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY); + $Login = LoginComponentLoader::forForm($this); - return $this->redirect(Configure::read('Auth.loginAction')); - } + return $Login->handleLogin(true, false); } /** @@ -355,42 +61,22 @@ protected function _afterIdentifyUser($user, $socialLogin = false, $googleAuthen */ public function logout() { - $user = (array)$this->Auth->user(); + $user = $this->getRequest()->getAttribute('identity'); + $user = $user ?? []; - $eventBefore = $this->dispatchEvent(UsersAuthComponent::EVENT_BEFORE_LOGOUT, ['user' => $user]); - if (is_array($eventBefore->result)) { - return $this->redirect($eventBefore->result); + $eventBefore = $this->dispatchEvent(Plugin::EVENT_BEFORE_LOGOUT, ['user' => $user]); + if (is_array($eventBefore->getResult())) { + return $this->redirect($eventBefore->getResult()); } - $this->request->getSession()->destroy(); - $this->Flash->success(__d('CakeDC/Users', 'You\'ve successfully logged out')); + $this->getRequest()->getSession()->destroy(); + $this->Flash->success(__d('cake_d_c/users', 'You\'ve successfully logged out')); - $eventAfter = $this->dispatchEvent(UsersAuthComponent::EVENT_AFTER_LOGOUT, ['user' => $user]); - if (is_array($eventAfter->result)) { - return $this->redirect($eventAfter->result); + $eventAfter = $this->dispatchEvent(Plugin::EVENT_AFTER_LOGOUT, ['user' => $user]); + if (is_array($eventAfter->getResult())) { + return $this->redirect($eventAfter->getResult()); } - return $this->redirect($this->Auth->logout()); - } - - /** - * Check if we are doing a social login - * - * @return bool true if social login is enabled and we are processing the social login - * data in the request - */ - protected function _isSocialLogin() - { - return Configure::read('Users.Social.login') && - $this->request->getSession()->check(Configure::read('Users.Key.Session.social')); - } - - /** - * Check if we doing Google Authenticator Two Factor auth - * @return bool true if Google Authenticator is enabled - */ - protected function _isGoogleAuthenticator() - { - return Configure::read('Users.GoogleAuthenticator.login'); + return $this->redirect($this->Authentication->logout()); } } diff --git a/src/Controller/Traits/OneTimePasswordVerifyTrait.php b/src/Controller/Traits/OneTimePasswordVerifyTrait.php new file mode 100644 index 000000000..9538569a6 --- /dev/null +++ b/src/Controller/Traits/OneTimePasswordVerifyTrait.php @@ -0,0 +1,197 @@ + $this->getRequest()->getQueryParams(), + ] + ); + if (!$this->isVerifyAllowed()) { + return $this->redirect($loginAction); + } + + $temporarySession = $this->getRequest()->getSession()->read( + AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY + ); + $secretVerified = $temporarySession['secret_verified'] ?? null; + // showing QR-code until shared secret is verified + if (!$secretVerified) { + $secret = $this->onVerifyGetSecret($temporarySession); + if (empty($secret)) { + return $this->redirect($loginAction); + } + + $secretDataUri = $this->OneTimePasswordAuthenticator->getQRCodeImageAsDataUri( + $temporarySession['email'], + $secret + ); + $this->set(['secretDataUri' => $secretDataUri]); + } + + if ($this->getRequest()->is('post')) { + return $this->onPostVerifyCode($loginAction); + } + } + + /** + * Check If Google Authenticator's enabled we need to verify + * authenticated user and if temporySession is present + * + * @return bool + */ + protected function isVerifyAllowed() + { + if (!Configure::read('OneTimePasswordAuthenticator.login')) { + $message = __d('cake_d_c/users', 'Please enable Google Authenticator first.'); + $this->Flash->error($message, [ + 'key' => 'auth', + 'element' => 'default', + 'params' => [], + ]); + + return false; + } + + $temporarySession = $this->getRequest()->getSession()->read( + AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY + ); + + if (empty($temporarySession) || !isset($temporarySession['id'])) { + $message = __d('cake_d_c/users', 'Could not find user data'); + $this->Flash->error($message, [ + 'key' => 'auth', + 'element' => 'default', + 'params' => [], + ]); + + return false; + } + + return true; + } + + /** + * Get the Google Authenticator secret of user, if not exists try to create one and save + * + * @param \CakeDC\Users\Model\Entity\User $user user data present on session + * @return string if empty the creation has failed + */ + protected function onVerifyGetSecret($user) + { + if (isset($user['secret']) && $user['secret']) { + return $user['secret']; + } + + $secret = $this->OneTimePasswordAuthenticator->createSecret(); + + // catching sql exception in case of any sql inconsistencies + try { + $query = $this->getUsersTable()->query(); + $query->update() + ->set(['secret' => $secret]) + ->where(['id' => $user['id']]); + $query->execute(); + $user['secret'] = $secret; + $this->getRequest()->getSession()->write(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY, $user); + } catch (\Exception $e) { + $this->getRequest()->getSession()->destroy(); + $this->log($e); + $message = __d('cake_d_c/users', 'Could not verify, please try again'); + $this->Flash->error($message, [ + 'key' => 'auth', + 'element' => 'default', + 'params' => [], + ]); + + return ''; + } + + return $secret; + } + + /** + * Handle the action when user post the form with code + * + * @param array $loginAction url to login page used in redirect + * @return \Cake\Http\Response + */ + protected function onPostVerifyCode($loginAction) + { + $codeVerified = false; + $verificationCode = $this->getRequest()->getData('code'); + $user = $this->getRequest()->getSession()->read(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY); + $entity = $this->getUsersTable()->get($user['id']); + + if (!empty($entity['secret'])) { + $codeVerified = $this->OneTimePasswordAuthenticator->verifyCode($entity['secret'], $verificationCode); + } + + if (!$codeVerified) { + $this->getRequest()->getSession()->destroy(); + $message = __d('cake_d_c/users', 'Verification code is invalid. Try again'); + $this->Flash->error($message, [ + 'key' => 'auth', + 'element' => 'default', + 'params' => [], + ]); + + return $this->redirect($loginAction); + } + + return $this->onPostVerifyCodeOkay($loginAction, $user); + } + + /** + * Handle the part of action when user post the form with valid code + * + * @param array $loginAction url to login page used in redirect + * @param \CakeDC\Users\Model\Entity\User $user user data present on session + * @return \Cake\Http\Response + */ + protected function onPostVerifyCodeOkay($loginAction, $user) + { + unset($user['secret']); + + if (!$user['secret_verified']) { + $this->getUsersTable()->query()->update() + ->set(['secret_verified' => true]) + ->where(['id' => $user['id']]) + ->execute(); + } + + $this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY); + $this->getRequest()->getSession()->write(TwoFactorAuthenticator::USER_SESSION_KEY, $user); + + return $this->redirect($loginAction); + } +} diff --git a/src/Controller/Traits/PasswordManagementTrait.php b/src/Controller/Traits/PasswordManagementTrait.php index 1efb3a45b..09878ae2a 100644 --- a/src/Controller/Traits/PasswordManagementTrait.php +++ b/src/Controller/Traits/PasswordManagementTrait.php @@ -1,23 +1,24 @@ getUsersTable()->newEntity(); - $id = $this->Auth->user('id'); - if (!empty($id)) { - $user->id = $this->Auth->user('id'); - $validatePassword = true; - //@todo add to the documentation: list of routes used - $redirect = Configure::read('Users.Profile.route'); + $user = $this->getUsersTable()->newEntity([], ['validate' => false]); + $user->setNew(false); + + $identity = $this->getRequest()->getAttribute('identity'); + $identity = $identity ?? []; + $userId = $identity['id'] ?? null; + + if ($userId) { + if ($id && $identity['is_superuser'] && Configure::read('Users.Superuser.allowedToChangePasswords')) { + // superuser editing any account's password + $user->id = $id; + $validatePassword = false; + $redirect = ['action' => 'index']; + } elseif (!$id || $id === $userId) { + // normal user editing own password + $user->id = $userId; + $validatePassword = true; + $redirect = Configure::read('Users.Profile.route'); + } else { + $this->Flash->error( + __d('cake_d_c/users', 'Changing another user\'s password is not allowed') + ); + $this->redirect(Configure::read('Users.Profile.route')); + + return; + } } else { - $user->id = $this->request->getSession()->read(Configure::read('Users.Key.Session.resetPasswordUserId')); + // password reset + $user->id = $this->getRequest()->getSession()->read( + Configure::read('Users.Key.Session.resetPasswordUserId') + ); $validatePassword = false; + $redirect = $this->Authentication->getConfig('loginAction'); if (!$user->id) { - $this->Flash->error(__d('CakeDC/Users', 'User was not found')); - $this->redirect($this->Auth->getConfig('loginAction')); + $this->Flash->error(__d('cake_d_c/users', 'User was not found')); + $this->redirect($redirect); return; } - //@todo add to the documentation: list of routes used - $redirect = $this->Auth->getConfig('loginAction'); } $this->set('validatePassword', $validatePassword); - if ($this->request->is('post')) { + if ($this->getRequest()->is(['post', 'put'])) { try { $validator = $this->getUsersTable()->validationPasswordConfirm(new Validator()); - if (!empty($id)) { + if ($validatePassword) { $validator = $this->getUsersTable()->validationCurrentPassword($validator); } + $this->getUsersTable()->setValidator('current', $validator); $user = $this->getUsersTable()->patchEntity( $user, - $this->request->getData(), - ['validate' => $validator] + $this->getRequest()->getData(), + [ + 'validate' => 'current', + 'accessibleFields' => [ + 'current_password' => true, + 'password' => true, + 'password_confirm' => true, + ], + ] ); + if ($user->getErrors()) { - $this->Flash->error(__d('CakeDC/Users', 'Password could not be changed')); + $this->Flash->error(__d('cake_d_c/users', 'Password could not be changed')); } else { - $user = $this->getUsersTable()->changePassword($user); - if ($user) { - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_AFTER_CHANGE_PASSWORD, ['user' => $user]); - if (!empty($event) && is_array($event->result)) { - return $this->redirect($event->result); + $result = $this->getUsersTable()->changePassword($user); + if ($result) { + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_CHANGE_PASSWORD, ['user' => $result]); + if (!empty($event) && is_array($event->getResult())) { + return $this->redirect($event->getResult()); } - $this->Flash->success(__d('CakeDC/Users', 'Password has been changed successfully')); + $this->Flash->success(__d('cake_d_c/users', 'Password has been changed successfully')); return $this->redirect($redirect); } else { - $this->Flash->error(__d('CakeDC/Users', 'Password could not be changed')); + $this->Flash->error(__d('cake_d_c/users', 'Password could not be changed')); } } } catch (UserNotFoundException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'User was not found')); + $this->Flash->error(__d('cake_d_c/users', 'User was not found')); } catch (WrongPasswordException $wpe) { $this->Flash->error($wpe->getMessage()); } catch (Exception $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Password could not be changed')); + $this->Flash->error(__d('cake_d_c/users', 'Password could not be changed')); $this->log($exception->getMessage()); } } - $this->set(compact('user')); + $this->set(['user' => $user]); $this->set('_serialize', ['user']); } @@ -114,41 +148,42 @@ public function resetPassword($token = null) */ public function requestResetPassword() { - $this->set('user', $this->getUsersTable()->newEntity()); + $this->set('user', $this->getUsersTable()->newEntity([], ['validate' => false])); $this->set('_serialize', ['user']); - if (!$this->request->is('post')) { + if (!$this->getRequest()->is('post')) { return; } - $reference = $this->request->getData('reference'); + $reference = $this->getRequest()->getData('reference'); try { $resetUser = $this->getUsersTable()->resetToken($reference, [ 'expiration' => Configure::read('Users.Token.expiration'), 'checkActive' => false, 'sendEmail' => true, - 'ensureActive' => Configure::read('Users.Registration.ensureActive') + 'ensureActive' => Configure::read('Users.Registration.ensureActive'), + 'type' => 'password', ]); if ($resetUser) { - $msg = __d('CakeDC/Users', 'Please check your email to continue with password reset process'); + $msg = __d('cake_d_c/users', 'Please check your email to continue with password reset process'); $this->Flash->success($msg); } else { - $msg = __d('CakeDC/Users', 'The password token could not be generated. Please try again'); + $msg = __d('cake_d_c/users', 'The password token could not be generated. Please try again'); $this->Flash->error($msg); } return $this->redirect(['action' => 'login']); } catch (UserNotFoundException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'User {0} was not found', $reference)); + $this->Flash->error(__d('cake_d_c/users', 'User {0} was not found', $reference)); } catch (UserNotActiveException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'The user is not active')); + $this->Flash->error(__d('cake_d_c/users', 'The user is not active')); } catch (Exception $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Token could not be reset')); + $this->Flash->error(__d('cake_d_c/users', 'Token could not be reset')); $this->log($exception->getMessage()); } } /** - * resetGoogleAuthenticator + * resetOneTimePasswordAuthenticator * * Resets Google Authenticator token by setting secret_verified * to false. @@ -156,9 +191,9 @@ public function requestResetPassword() * @param mixed $id of the user record. * @return mixed. */ - public function resetGoogleAuthenticator($id = null) + public function resetOneTimePasswordAuthenticator($id = null) { - if ($this->request->is('post')) { + if ($this->getRequest()->is('post')) { try { $query = $this->getUsersTable()->query(); $query->update() @@ -166,14 +201,13 @@ public function resetGoogleAuthenticator($id = null) ->where(['id' => $id]); $query->execute(); - $message = __d('CakeDC/Users', 'Google Authenticator token was successfully reset'); + $message = __d('cake_d_c/users', 'Google Authenticator token was successfully reset'); $this->Flash->success($message, 'default'); } catch (\Exception $e) { - $message = $e->getMessage(); - $this->Flash->error($message, 'default'); + $this->Flash->error(__d('cake_d_c/users', 'Could not reset Google Authenticator'), 'default'); } } - return $this->redirect($this->request->referer()); + return $this->redirect($this->getRequest()->referer()); } } diff --git a/src/Controller/Traits/ProfileTrait.php b/src/Controller/Traits/ProfileTrait.php index db61724d3..a67ac991c 100644 --- a/src/Controller/Traits/ProfileTrait.php +++ b/src/Controller/Traits/ProfileTrait.php @@ -1,11 +1,13 @@ Auth->user('id'); + $identity = $this->getRequest()->getAttribute('identity'); + $identity = $identity ?? []; + $loggedUserId = $identity['id'] ?? null; $isCurrentUser = false; if (!Configure::read('Users.Profile.viewOthers') || empty($id)) { $id = $loggedUserId; } try { $appContain = (array)Configure::read('Auth.authenticate.' . AuthComponent::ALL . '.contain'); - $socialContain = Configure::read('Users.Social.login') ? ['SocialAccounts']: []; + $socialContain = Configure::read('Users.Social.login') ? ['SocialAccounts'] : []; $user = $this->getUsersTable()->get($id, [ - 'contain' => array_merge((array)$appContain, (array)$socialContain) + 'contain' => array_merge((array)$appContain, (array)$socialContain), ]); $this->set('avatarPlaceholder', Configure::read('Users.Avatar.placeholder')); if ($user->id === $loggedUserId) { $isCurrentUser = true; } } catch (RecordNotFoundException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'User was not found')); + $this->Flash->error(__d('cake_d_c/users', 'User was not found')); - return $this->redirect($this->request->referer()); + return $this->redirect($this->getRequest()->referer()); } catch (InvalidPrimaryKeyException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'Not authorized, please login first')); + $this->Flash->error(__d('cake_d_c/users', 'Not authorized, please login first')); - return $this->redirect($this->request->referer()); + return $this->redirect($this->getRequest()->referer()); } - $this->set(compact('user', 'isCurrentUser')); + $this->set(['user' => $user, 'isCurrentUser' => $isCurrentUser]); $this->set('_serialize', ['user', 'isCurrentUser']); } } diff --git a/src/Controller/Traits/ReCaptchaTrait.php b/src/Controller/Traits/ReCaptchaTrait.php index d35773e77..410511b6e 100644 --- a/src/Controller/Traits/ReCaptchaTrait.php +++ b/src/Controller/Traits/ReCaptchaTrait.php @@ -1,11 +1,13 @@ Auth->user('id'); + $identity = $this->getRequest()->getAttribute('identity'); + $identity = $identity ?? []; + $userId = $identity['id'] ?? null; if (!empty($userId) && !Configure::read('Users.Registration.allowLoggedIn')) { - $this->Flash->error(__d('CakeDC/Users', 'You must log out to register a new user account')); + $this->Flash->error(__d('cake_d_c/users', 'You must log out to register a new user account')); return $this->redirect(Configure::read('Users.Profile.route')); } $usersTable = $this->getUsersTable(); - $user = $usersTable->newEntity(); + $user = $usersTable->newEmptyEntity(); $validateEmail = (bool)Configure::read('Users.Email.validate'); $useTos = (bool)Configure::read('Users.Tos.required'); $tokenExpiration = Configure::read('Users.Token.expiration'); $options = [ 'token_expiration' => $tokenExpiration, 'validate_email' => $validateEmail, - 'use_tos' => $useTos + 'use_tos' => $useTos, ]; - $requestData = $this->request->getData(); - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_BEFORE_REGISTER, [ + $requestData = $this->getRequest()->getData(); + $event = $this->dispatchEvent(Plugin::EVENT_BEFORE_REGISTER, [ 'usersTable' => $usersTable, 'options' => $options, 'userEntity' => $user, ]); - if ($event->result instanceof EntityInterface) { - $data = $event->result->toArray(); - $data['password'] = $requestData['password']; //since password is a hidden property - if ($userSaved = $usersTable->register($user, $data, $options)) { + $result = $event->getResult(); + if ($result instanceof EntityInterface) { + $data = $result->toArray(); + $data['password'] = $requestData['password'] ?? null; //since password is a hidden property + $userSaved = $usersTable->register($user, $data, $options); + $errors = \collection($user->getErrors())->unfold()->toArray(); + if ($userSaved) { return $this->_afterRegister($userSaved); - } else { + } elseif (Configure::read('Users.Registration.showVerboseError') && count($errors) > 0) { $this->set(compact('user')); - $this->Flash->error(__d('CakeDC/Users', 'The user could not be saved')); + foreach ($errors as $error) { + $this->Flash->error(__($error)); + } + + return; + } else { + $this->set(['user' => $user]); + $this->Flash->error(__d('cake_d_c/users', 'The user could not be saved')); return; } } if ($event->isStopped()) { - return $this->redirect($event->result); + return $this->redirect($event->getResult()); } - $this->set(compact('user')); + $this->set(['user' => $user]); $this->set('_serialize', ['user']); - if (!$this->request->is('post')) { + if (!$this->getRequest()->is('post')) { return; } if (!$this->_validateRegisterPost()) { - $this->Flash->error(__d('CakeDC/Users', 'Invalid reCaptcha')); + $this->Flash->error(__d('cake_d_c/users', 'Invalid reCaptcha')); return; } $userSaved = $usersTable->register($user, $requestData, $options); - if (!$userSaved) { - $this->Flash->error(__d('CakeDC/Users', 'The user could not be saved')); + $errors = \collection($user->getErrors())->unfold()->toArray(); + if (!$userSaved && Configure::read('Users.Registration.showVerboseError') && count($errors) > 0) { + foreach ($errors as $error) { + $this->Flash->error(__($error)); + } + + return; + } elseif (!$userSaved) { + $this->Flash->error(__d('cake_d_c/users', 'The user could not be saved')); return; } @@ -113,29 +134,30 @@ protected function _validateRegisterPost() } return $this->validateReCaptcha( - $this->request->getData('g-recaptcha-response'), - $this->request->clientIp() + $this->getRequest()->getData('g-recaptcha-response'), + $this->getRequest()->clientIp() ); } /** * Prepare flash messages after registration, and dispatch afterRegister event * - * @param EntityInterface $userSaved User entity saved - * @return Response + * @param \Cake\Datasource\EntityInterface $userSaved User entity saved + * @return \Cake\Http\Response */ protected function _afterRegister(EntityInterface $userSaved) { $validateEmail = (bool)Configure::read('Users.Email.validate'); - $message = __d('CakeDC/Users', 'You have registered successfully, please log in'); + $message = __d('cake_d_c/users', 'You have registered successfully, please log in'); if ($validateEmail) { - $message = __d('CakeDC/Users', 'Please validate your account before log in'); + $message = __d('cake_d_c/users', 'Please validate your account before log in'); } - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_AFTER_REGISTER, [ - 'user' => $userSaved + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_REGISTER, [ + 'user' => $userSaved, ]); - if ($event->result instanceof Response) { - return $event->result; + $result = $event->getResult(); + if ($result instanceof Response) { + return $result; } $this->Flash->success($message); diff --git a/src/Controller/Traits/SimpleCrudTrait.php b/src/Controller/Traits/SimpleCrudTrait.php index 4f2480a0a..33346d298 100644 --- a/src/Controller/Traits/SimpleCrudTrait.php +++ b/src/Controller/Traits/SimpleCrudTrait.php @@ -1,18 +1,18 @@ loadModel(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ - 'contain' => [] + 'contain' => [], ]); $this->set($tableAlias, $entity); $this->set('tableAlias', $tableAlias); @@ -64,21 +64,21 @@ public function add() { $table = $this->loadModel(); $tableAlias = $table->getAlias(); - $entity = $table->newEntity(); + $entity = $table->newEmptyEntity(); $this->set($tableAlias, $entity); $this->set('tableAlias', $tableAlias); $this->set('_serialize', [$tableAlias, 'tableAlias']); - if (!$this->request->is('post')) { + if (!$this->getRequest()->is('post')) { return; } - $entity = $table->patchEntity($entity, $this->request->getData()); + $entity = $table->patchEntity($entity, $this->getRequest()->getData()); $singular = Inflector::singularize(Inflector::humanize($tableAlias)); if ($table->save($entity)) { - $this->Flash->success(__d('CakeDC/Users', 'The {0} has been saved', $singular)); + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been saved', $singular)); return $this->redirect(['action' => 'index']); } - $this->Flash->error(__d('CakeDC/Users', 'The {0} could not be saved', $singular)); + $this->Flash->error(__d('cake_d_c/users', 'The {0} could not be saved', $singular)); } /** @@ -86,51 +86,51 @@ public function add() * * @param string|null $id User id. * @return mixed Redirects on successful edit, renders view otherwise. - * @throws NotFoundException When record not found. + * @throws \Cake\Http\Exception\NotFoundException When record not found. */ public function edit($id = null) { $table = $this->loadModel(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ - 'contain' => [] + 'contain' => [], ]); $this->set($tableAlias, $entity); $this->set('tableAlias', $tableAlias); $this->set('_serialize', [$tableAlias, 'tableAlias']); - if (!$this->request->is(['patch', 'post', 'put'])) { + if (!$this->getRequest()->is(['patch', 'post', 'put'])) { return; } - $entity = $table->patchEntity($entity, $this->request->getData()); + $entity = $table->patchEntity($entity, $this->getRequest()->getData()); $singular = Inflector::singularize(Inflector::humanize($tableAlias)); if ($table->save($entity)) { - $this->Flash->success(__d('CakeDC/Users', 'The {0} has been saved', $singular)); + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been saved', $singular)); return $this->redirect(['action' => 'index']); } - $this->Flash->error(__d('CakeDC/Users', 'The {0} could not be saved', $singular)); + $this->Flash->error(__d('cake_d_c/users', 'The {0} could not be saved', $singular)); } /** * Delete method * * @param string|null $id User id. - * @return Response Redirects to index. - * @throws NotFoundException When record not found. + * @return \Cake\Http\Response Redirects to index. + * @throws \Cake\Http\Exception\NotFoundException When record not found. */ public function delete($id = null) { - $this->request->allowMethod(['post', 'delete']); + $this->getRequest()->allowMethod(['post', 'delete']); $table = $this->loadModel(); $tableAlias = $table->getAlias(); $entity = $table->get($id, [ - 'contain' => [] + 'contain' => [], ]); $singular = Inflector::singularize(Inflector::humanize($tableAlias)); if ($table->delete($entity)) { - $this->Flash->success(__d('CakeDC/Users', 'The {0} has been deleted', $singular)); + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been deleted', $singular)); } else { - $this->Flash->error(__d('CakeDC/Users', 'The {0} could not be deleted', $singular)); + $this->Flash->error(__d('cake_d_c/users', 'The {0} could not be deleted', $singular)); } return $this->redirect(['action' => 'index']); diff --git a/src/Controller/Traits/SocialTrait.php b/src/Controller/Traits/SocialTrait.php index f019f9e1d..eddbb919f 100644 --- a/src/Controller/Traits/SocialTrait.php +++ b/src/Controller/Traits/SocialTrait.php @@ -1,18 +1,19 @@ request->getSession()->check(Configure::read('Users.Key.Session.social'))) { - throw new NotFoundException(); - } - $this->request->getSession()->delete('Flash.auth'); - - if ($this->request->is('post')) { - $validPost = $this->_validateRegisterPost(); - if (!$validPost) { - $this->Flash->error(__d('CakeDC/Users', 'The reCaptcha could not be validated')); - - return; - } - $user = $this->Auth->identify(); + $Login = LoginComponentLoader::forSocial($this); - return $this->_afterIdentifyUser($user, true); - } + return $Login->handleLogin(true, false); } } diff --git a/src/Controller/Traits/U2fTrait.php b/src/Controller/Traits/U2fTrait.php new file mode 100644 index 000000000..a469a9ce8 --- /dev/null +++ b/src/Controller/Traits/U2fTrait.php @@ -0,0 +1,235 @@ +getRequest()->getQueryParams(); + if (empty($url['?'])) { + unset($url['?']); + } + + return $this->redirect($url); + } + + /** + * U2f entry point + * + * @return \Cake\Http\Response|null + */ + public function u2f() + { + $data = $this->getU2fData(); + if (!$data['valid']) { + return $this->redirectWithQuery([ + 'action' => 'login', + ]); + } + if (!$data['registration']) { + return $this->redirectWithQuery([ + 'action' => 'u2fRegister', + ]); + } + + return $this->redirectWithQuery([ + 'action' => 'u2fAuthenticate', + ]); + } + + /** + * Show u2f register start step + * + * @return \Cake\Http\Response|null + * @throws \u2flib_server\Error + */ + public function u2fRegister() + { + $data = $this->getU2fData(); + if (!$data['valid']) { + return $this->redirectWithQuery([ + 'action' => 'login', + ]); + } + + if (!$data['registration']) { + [$registerRequest, $signs] = $this->createU2fLib()->getRegisterData(); + $this->getRequest()->getSession()->write('U2f.registerRequest', json_encode($registerRequest)); + $this->set(['registerRequest' => $registerRequest, 'signs' => $signs]); + + return null; + } + + return $this->redirectWithQuery([ + 'action' => 'u2fAuthenticate', + ]); + } + + /** + * Show u2f register finish step + * + * @return \Cake\Http\Response|null + */ + public function u2fRegisterFinish() + { + $data = $this->getU2fData(); + $request = json_decode($this->getRequest()->getSession()->read('U2f.registerRequest')); + $response = json_decode($this->getRequest()->getData('registerResponse')); + try { + $result = $this->createU2fLib()->doRegister($request, $response); + $additionalData = $data['user']->additional_data; + $additionalData['u2f_registration'] = $result; + $data['user']->additional_data = $additionalData; + $this->getUsersTable()->saveOrFail($data['user'], ['checkRules' => false]); + $this->getRequest()->getSession()->delete('U2f.registerRequest'); + + return $this->redirectWithQuery([ + 'action' => 'u2fAuthenticate', + ]); + } catch (\Exception $e) { + $this->getRequest()->getSession()->delete('U2f.registerRequest'); + + return $this->redirectWithQuery([ + 'action' => 'u2fRegister', + ]); + } + } + + /** + * Show u2f authenticate start step + * + * @return \Cake\Http\Response|null + */ + public function u2fAuthenticate() + { + $data = $this->getU2fData(); + if (!$data['valid']) { + return $this->redirectWithQuery([ + 'action' => 'login', + ]); + } + + if (!$data['registration']) { + return $this->redirectWithQuery([ + 'action' => 'u2fRegister', + ]); + } + $authenticateRequest = $this->createU2fLib()->getAuthenticateData([$data['registration']]); + $this->getRequest()->getSession()->write('U2f.authenticateRequest', json_encode($authenticateRequest)); + $this->set(['authenticateRequest' => $authenticateRequest]); + + return null; + } + + /** + * Show u2f Authenticate finish step + * + * @return \Cake\Http\Response|null + */ + public function u2fAuthenticateFinish() + { + $data = $this->getU2fData(); + $request = json_decode($this->getRequest()->getSession()->read('U2f.authenticateRequest')); + $response = json_decode($this->getRequest()->getData('authenticateResponse')); + + try { + $registration = $data['registration']; + $result = $this->createU2fLib()->doAuthenticate($request, [$registration], $response); + $registration->counter = $result->counter; + $additionalData = $data['user']->additional_data; + $additionalData['u2f_registration'] = $result; + $data['user']->additional_data = $additionalData; + $this->getUsersTable()->saveOrFail($data['user'], ['checkRules' => false]); + $this->getRequest()->getSession()->delete('U2f'); + $this->request->getSession()->delete(AuthenticationService::U2F_SESSION_KEY); + $this->request->getSession()->write(TwoFactorAuthenticator::USER_SESSION_KEY, $data['user']); + + return $this->redirectWithQuery(Configure::read('Auth.AuthenticationComponent.loginAction')); + } catch (\Exception $e) { + $this->getRequest()->getSession()->delete('U2f.authenticateRequest'); + + return $this->redirectWithQuery([ + 'action' => 'u2fAuthenticate', + ]); + } + } + + /** + * Create a u2f lib + * + * @return \u2flib_server\U2F + * @throws \u2flib_server\Error + */ + protected function createU2fLib() + { + $appId = $this->getRequest()->scheme() . '://' . $this->getRequest()->host(); + + return new U2F($appId); + } + + /** + * Get essential U2f data + * + * @return array + */ + protected function getU2fData() + { + $data = [ + 'valid' => false, + 'user' => null, + 'registration' => null, + ]; + $user = $this->getRequest()->getSession()->read(AuthenticationService::U2F_SESSION_KEY); + if (!isset($user['id'])) { + return $data; + } + if (!$this->request->is('ssl')) { + throw new \UnexpectedValueException(__d('cake_d_c/users', 'U2F requires SSL.')); + } + $entity = $this->getUsersTable()->get($user['id']); + $data['user'] = $user; + $data['valid'] = $this->getU2fAuthenticationChecker()->isEnabled(); + $data['registration'] = $entity->u2f_registration; + + return $data; + } + + /** + * Get the configured u2f authentication checker + * + * @return \CakeDC\Auth\Authentication\U2fAuthenticationCheckerInterface + */ + protected function getU2fAuthenticationChecker() + { + return (new U2fAuthenticationCheckerFactory())->build(); + } +} diff --git a/src/Controller/Traits/UserValidationTrait.php b/src/Controller/Traits/UserValidationTrait.php index 2229c7abb..16bcf90a0 100644 --- a/src/Controller/Traits/UserValidationTrait.php +++ b/src/Controller/Traits/UserValidationTrait.php @@ -1,21 +1,23 @@ getUsersTable()->validate($token, 'activateUser'); if ($result) { - $this->Flash->success(__d('CakeDC/Users', 'User account validated successfully')); + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION, ['user' => $result]); + if (!empty($event) && is_array($event->getResult())) { + return $this->redirect($event->getResult()); + } + $this->Flash->success(__d('cake_d_c/users', 'User account validated successfully')); } else { - $this->Flash->error(__d('CakeDC/Users', 'User account could not be validated')); + $this->Flash->error(__d('cake_d_c/users', 'User account could not be validated')); } } catch (UserAlreadyActiveException $exception) { - $this->Flash->error(__d('CakeDC/Users', 'User already active')); + $this->Flash->error(__d('cake_d_c/users', 'User already active')); } break; case 'password': $result = $this->getUsersTable()->validate($token); if (!empty($result)) { - $this->Flash->success(__d('CakeDC/Users', 'Reset password token was validated successfully')); - $this->request->getSession()->write( + $this->Flash->success(__d('cake_d_c/users', 'Reset password token was validated successfully')); + $this->getRequest()->getSession()->write( Configure::read('Users.Key.Session.resetPasswordUserId'), $result->id ); return $this->redirect(['action' => 'changePassword']); } else { - $this->Flash->error(__d('CakeDC/Users', 'Reset password token could not be validated')); + $this->Flash->error(__d('cake_d_c/users', 'Reset password token could not be validated')); } break; default: - $this->Flash->error(__d('CakeDC/Users', 'Invalid validation type')); + $this->Flash->error(__d('cake_d_c/users', 'Invalid validation type')); } } catch (UserNotFoundException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'Invalid token or user account already validated')); + $this->Flash->error(__d('cake_d_c/users', 'Invalid token or user account already validated')); } catch (TokenExpiredException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'Token already expired')); + $event = $this->dispatchEvent(Plugin::EVENT_ON_EXPIRED_TOKEN, ['type' => $type]); + if (!empty($event) && is_array($event->getResult())) { + return $this->redirect($event->getResult()); + } + $this->Flash->error(__d('cake_d_c/users', 'Token already expired')); } return $this->redirect(['action' => 'login']); @@ -81,34 +91,41 @@ public function validate($type = null, $token = null) */ public function resendTokenValidation() { - $this->set('user', $this->getUsersTable()->newEntity()); + $this->set('user', $this->getUsersTable()->newEntity([], ['validate' => false])); $this->set('_serialize', ['user']); - if (!$this->request->is('post')) { + if (!$this->getRequest()->is('post')) { return; } - $reference = $this->request->getData('reference'); + $reference = $this->getRequest()->getData('reference'); try { - if ($this->getUsersTable()->resetToken($reference, [ + if ( + $this->getUsersTable()->resetToken($reference, [ 'expiration' => Configure::read('Users.Token.expiration'), 'checkActive' => true, 'sendEmail' => true, - 'emailTemplate' => 'CakeDC/Users.validation' - ])) { + 'type' => 'email', + ]) + ) { + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_RESEND_TOKEN_VALIDATION); + $result = $event->getResult(); + if (!empty($event) && is_array($result)) { + return $this->redirect($result); + } $this->Flash->success(__d( - 'CakeDC/Users', + 'cake_d_c/users', 'Token has been reset successfully. Please check your email.' )); } else { - $this->Flash->error(__d('CakeDC/Users', 'Token could not be reset')); + $this->Flash->error(__d('cake_d_c/users', 'Token could not be reset')); } return $this->redirect(['action' => 'login']); } catch (UserNotFoundException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'User {0} was not found', $reference)); + $this->Flash->error(__d('cake_d_c/users', 'User {0} was not found', $reference)); } catch (UserAlreadyActiveException $ex) { - $this->Flash->error(__d('CakeDC/Users', 'User {0} is already active', $reference)); + $this->Flash->error(__d('cake_d_c/users', 'User {0} is already active', $reference)); } catch (Exception $ex) { - $this->Flash->error(__d('CakeDC/Users', 'Token could not be reset')); + $this->Flash->error(__d('cake_d_c/users', 'Token could not be reset')); } } } diff --git a/src/Controller/Traits/Webauthn2faTrait.php b/src/Controller/Traits/Webauthn2faTrait.php new file mode 100644 index 000000000..034a17442 --- /dev/null +++ b/src/Controller/Traits/Webauthn2faTrait.php @@ -0,0 +1,142 @@ +getWebauthn2faRegisterAdapter(); + $user = $adapter->getUser(); + $this->set('isRegister', !$adapter->hasCredential()); + $this->set('username', $user->webauthn_username ?? $user->username); + } + + /** + * Action to provide register options to frontend (from js requests) + * + * @return \Cake\Http\Response + */ + public function webauthn2faRegisterOptions() + { + $adapter = $this->getWebauthn2faRegisterAdapter(); + if (!$adapter->hasCredential()) { + return $this->getResponse() + ->withStringBody(json_encode($adapter->getOptions())); + } + + throw new BadRequestException( + __d('cake_d_c/users', 'User already has configured webauthn2fa') + ); + } + + /** + * Action to verify and save the new credential based on the webauthn register response. + * + * @return \Cake\Http\Response + * @throws \Throwable + */ + public function webauthn2faRegister(): \Cake\Http\Response + { + try { + $adapter = $this->getWebauthn2faRegisterAdapter(); + if (!$adapter->hasCredential()) { + $adapter->verifyResponse(); + + return $this->getResponse()->withStringBody(json_encode(['success' => true])); + } + throw new BadRequestException( + __d('cake_d_c/users', 'User already has configured webauthn2fa') + ); + } catch (\Throwable $e) { + $user = $this->request->getSession()->read('Webauthn2fa.User'); + Log::debug(__d('cake_d_c/users', 'Register error with webauthn for user id: {0}', $user['id'] ?? 'empty')); + throw $e; + } + } + + /** + * Action to provide authenticate options to frontend (from js requests) + * + * @return \Cake\Http\Response + */ + public function webauthn2faAuthenticateOptions(): \Cake\Http\Response + { + $adapter = $this->getWebauthn2faAuthenticateAdapter(); + + return $this->getResponse()->withStringBody( + json_encode($adapter->getOptions()) + ); + } + + /** + * Action to authenticate user based on the webauthn authenticate response. + * + * @return \Cake\Http\Response + * @throws \Throwable + */ + public function webauthn2faAuthenticate(): \Cake\Http\Response + { + try { + $adapter = $this->getWebauthn2faAuthenticateAdapter(); + $adapter->verifyResponse(); + $redirectUrl = Configure::read('Auth.AuthenticationComponent.loginAction') + [ + '?' => $this->getRequest()->getQueryParams(), + ]; + $this->getRequest()->getSession()->delete('Webauthn2fa'); + $this->getRequest()->getSession()->write( + TwoFactorAuthenticator::USER_SESSION_KEY, + $adapter->getUser() + ); + + return $this->getResponse()->withStringBody(json_encode([ + 'success' => true, + 'redirectUrl' => Router::url($redirectUrl), + ])); + } catch (\Throwable $e) { + $user = $this->request->getSession()->read('Webauthn2fa.User'); + Log::debug(__d('cake_d_c/users', 'Register error with webauthn for user id: {0}', $user['id'] ?? 'empty')); + throw $e; + } + } + + /** + * @return \CakeDC\Users\Webauthn\RegisterAdapter + */ + protected function getWebauthn2faRegisterAdapter(): RegisterAdapter + { + return new RegisterAdapter($this->getRequest(), $this->getUsersTable()); + } + + /** + * @return \CakeDC\Users\Webauthn\AuthenticateAdapter + */ + protected function getWebauthn2faAuthenticateAdapter(): AuthenticateAdapter + { + return new AuthenticateAdapter($this->getRequest()); + } +} diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index b86bd0940..096bbc9c1 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -1,41 +1,71 @@ components()->has('Security')) { + $this->Security->setConfig( + 'unlockedActions', + [ + 'login', + 'u2fRegister', + 'u2fRegisterFinish', + 'u2fAuthenticate', + 'u2fAuthenticateFinish', + 'webauthn2faRegister', + 'webauthn2faRegisterOptions', + 'webauthn2faAuthenticate', + 'webauthn2faAuthenticateOptions', + ] + ); + } + } } diff --git a/src/Exception/AccountAlreadyActiveException.php b/src/Exception/AccountAlreadyActiveException.php index f3eddabff..4cef96afd 100644 --- a/src/Exception/AccountAlreadyActiveException.php +++ b/src/Exception/AccountAlreadyActiveException.php @@ -1,22 +1,23 @@ 'active', + ]; + + /** + * Identifies an user or service by the passed credentials + * + * @param array $credentials Authentication credentials + * @return \ArrayAccess|array|null + */ + public function identify(array $credentials) + { + if (!isset($credentials[self::CREDENTIAL_KEY])) { + return null; + } + + $user = $this->createOrGetUser($credentials[self::CREDENTIAL_KEY]); + + if (!$user) { + return null; + } + + if ($user->get('social_accounts')) { + $this->dispatchEvent(Plugin::EVENT_AFTER_REGISTER, ['user' => $user]); + } + + return $this->findUser($user)->firstOrFail(); + } + + /** + * Get query object for fetching user from database. + * + * @param \Cake\Datasource\EntityInterface $user The user. + * @return \Cake\ORM\Query + */ + protected function findUser($user) + { + $table = $this->getUsersTable(); + $finder = $this->getConfig('authFinder'); + + $primaryKey = (array)$table->getPrimaryKey(); + + $conditions = []; + foreach ($primaryKey as $key) { + $conditions[$table->aliasField($key)] = $user->get($key); + } + + return $table->find($finder)->where($conditions); + } + + /** + * Create a new user or get if exists one for the social data + * + * @param mixed $data social data + * @return mixed + */ + protected function createOrGetUser($data) + { + $options = [ + 'use_email' => Configure::read('Users.Email.required'), + 'validate_email' => Configure::read('Users.Email.validate'), + 'token_expiration' => Configure::read('Users.Token.expiration'), + ]; + + return $this->getUsersTable()->socialLogin($data, $options); + } + + /** + * Get users table based on internal config (usersTable) or users config (Users.table) + * + * @return \Cake\ORM\Table + */ + protected function getUsersTable() + { + $userModel = $this->getConfig('usersTable', Configure::read('Users.table')); + + return $this->getTableLocator()->get($userModel); + } +} diff --git a/src/Loader/AuthenticationServiceLoader.php b/src/Loader/AuthenticationServiceLoader.php new file mode 100644 index 000000000..c4d2b6958 --- /dev/null +++ b/src/Loader/AuthenticationServiceLoader.php @@ -0,0 +1,117 @@ +loadIdentifiers($service); + $this->loadAuthenticators($service); + $this->loadTwoFactorAuthenticator($service); + + return $service; + } + + /** + * Load the indentifiers defined at config Auth.Identifiers + * + * @param \CakeDC\Auth\Authentication\AuthenticationService $service Authentication service to load identifiers + * @return void + */ + protected function loadIdentifiers($service) + { + $identifiers = Configure::read('Auth.Identifiers'); + foreach ($identifiers as $key => $item) { + [$identifier, $options] = $this->_getItemLoadData($item, $key); + + $service->loadIdentifier($identifier, $options); + } + } + + /** + * Load the authenticators defined at config Auth.Authenticators + * + * @param \CakeDC\Auth\Authentication\AuthenticationService $service Authentication service to load identifiers + * @return void + */ + protected function loadAuthenticators($service) + { + $authenticators = Configure::read('Auth.Authenticators'); + + foreach ($authenticators as $key => $item) { + [$authenticator, $options] = $this->_getItemLoadData($item, $key); + + $service->loadAuthenticator($authenticator, $options); + } + } + + /** + * Load the CakeDC/Auth.TwoFactor based on config OneTimePasswordAuthenticator.login + * + * @param \CakeDC\Auth\Authentication\AuthenticationService $service Authentication service to load identifiers + * @return void + */ + protected function loadTwoFactorAuthenticator($service) + { + if ( + Configure::read('OneTimePasswordAuthenticator.login') !== false + || Configure::read('U2f.enabled') !== false + ) { + $service->loadAuthenticator('CakeDC/Auth.TwoFactor', [ + 'skipTwoFactorVerify' => true, + ]); + } + } + + /** + * @param mixed $item Item configuration or className + * @param string $key Item array key. + * @return array + */ + protected function _getItemLoadData($item, $key) + { + $options = []; + if (!is_array($item)) { + return [$item, $options]; + } + $options = $item; + if (!isset($options['className'])) { + throw new \InvalidArgumentException( + __d('cake_d_c/users', 'Property {0}.className should be defined', $key) + ); + } + $className = $options['className']; + unset($options['className']); + + return [$className, $options]; + } +} diff --git a/src/Loader/AuthorizationServiceLoader.php b/src/Loader/AuthorizationServiceLoader.php new file mode 100644 index 000000000..20badb32c --- /dev/null +++ b/src/Loader/AuthorizationServiceLoader.php @@ -0,0 +1,55 @@ +map( + ServerRequest::class, + new CollectionPolicy([ + SuperuserPolicy::class, + new RbacPolicy(Configure::read('Auth.RbacPolicy')), + ]) + ); + + $orm = new OrmResolver(); + + $resolver = new ResolverCollection([ + $map, + $orm, + ]); + + return new AuthorizationService($resolver); + } +} diff --git a/src/Loader/LoginComponentLoader.php b/src/Loader/LoginComponentLoader.php new file mode 100644 index 000000000..29e6a235c --- /dev/null +++ b/src/Loader/LoginComponentLoader.php @@ -0,0 +1,85 @@ + 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + 'FAILURE_INVALID_RECAPTCHA' => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => \CakeDC\Auth\Authenticator\FormAuthenticator::class, + ]; + + return self::createComponent($controller, 'Auth.FormLoginFailure', $config); + } + + /** + * Load the login component for social login + * + * @param \Cake\Controller\Controller $controller Target controller + * @return \CakeDC\Users\Controller\Component\LoginComponent|\Cake\Controller\Component + * @throws \Exception + */ + public static function forSocial($controller) + { + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + 'FAILURE_USER_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + 'FAILURE_ACCOUNT_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ), + ], + 'targetAuthenticator' => \CakeDC\Users\Authenticator\SocialAuthenticator::class, + ]; + + return self::createComponent($controller, 'Auth.SocialLoginFailure', $config); + } + + /** + * Create the component using base $config and the one from $key + * + * @param \Cake\Controller\Controller $controller Target controller + * @param string $key configuration key + * @param array $config base configuration + * @return \CakeDC\Users\Controller\Component\LoginComponent|\Cake\Controller\Component + * @throws \Exception + */ + protected static function createComponent($controller, $key, array $config) + { + $custom = (array)Configure::read($key); + $config = Hash::merge($config, $custom); + + return $controller->loadComponent($config['component'], $config); + } +} diff --git a/src/Loader/MiddlewareQueueLoader.php b/src/Loader/MiddlewareQueueLoader.php new file mode 100644 index 000000000..cb140d87c --- /dev/null +++ b/src/Loader/MiddlewareQueueLoader.php @@ -0,0 +1,132 @@ +loadSocialMiddleware($middlewareQueue); + $this->loadAuthenticationMiddleware($middlewareQueue, $authenticationServiceProvider); + $this->load2faMiddleware($middlewareQueue); + + return $this->loadAuthorizationMiddleware($middlewareQueue, $authorizationServiceProvider); + } + + /** + * Load social middlewares if enabled. Based on config 'Users.Social.login' + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update. + * @return void + */ + protected function loadSocialMiddleware(MiddlewareQueue $middlewareQueue) + { + if (Configure::read('Users.Social.login')) { + $middlewareQueue + ->add(SocialAuthMiddleware::class) + ->add(SocialEmailMiddleware::class); + } + } + + /** + * Load authentication middleware + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue queue of middleware + * @param \Authentication\AuthenticationServiceProviderInterface $authenticationServiceProvider Authentication service provider + * @return void + */ + protected function loadAuthenticationMiddleware( + MiddlewareQueue $middlewareQueue, + AuthenticationServiceProviderInterface $authenticationServiceProvider + ) { + $authentication = new AuthenticationMiddleware($authenticationServiceProvider); + $middlewareQueue->add($authentication); + } + + /** + * Load OneTimePasswordAuthenticatorMiddleware if enabled. Based on config 'OneTimePasswordAuthenticator.login' + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue queue of middleware + * @return void + */ + protected function load2faMiddleware(MiddlewareQueue $middlewareQueue) + { + if ( + Configure::read('OneTimePasswordAuthenticator.login') !== false + || Configure::read('U2f.enabled') !== false + ) { + $middlewareQueue->add(TwoFactorMiddleware::class); + } + } + + /** + * Load authorization middleware based on Auth.Authorization. + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue queue of middleware + * @param \Authorization\AuthorizationServiceProviderInterface $authorizationServiceProvider Authorization service provider + * @return \Cake\Http\MiddlewareQueue + */ + protected function loadAuthorizationMiddleware( + MiddlewareQueue $middlewareQueue, + AuthorizationServiceProviderInterface $authorizationServiceProvider + ) { + if (Configure::read('Auth.Authorization.enable') === false) { + return $middlewareQueue; + } + $middlewareQueue->add( + new AuthorizationMiddleware( + $authorizationServiceProvider, + Configure::read('Auth.AuthorizationMiddleware') + ) + ); + if (Configure::read('Auth.AuthorizationMiddleware.requireAuthorizationCheck') !== false) { + $middlewareQueue->add(new RequestAuthorizationMiddleware()); + } + + return $middlewareQueue; + } +} diff --git a/src/Locale/Users.pot b/src/Locale/Users.pot deleted file mode 100644 index 6d82471e0..000000000 --- a/src/Locale/Users.pot +++ /dev/null @@ -1,821 +0,0 @@ -# LANGUAGE translation of CakePHP Application -# Copyright YEAR NAME -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2017-10-14 23:45+0000\n" -"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" -"Last-Translator: NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" - -#: Auth/SocialAuthenticate.php:456 -msgid "Provider cannot be empty" -msgstr "" - -#: Controller/SocialAccountsController.php:52 -msgid "Account validated successfully" -msgstr "" - -#: Controller/SocialAccountsController.php:54 -msgid "Account could not be validated" -msgstr "" - -#: Controller/SocialAccountsController.php:57 -msgid "Invalid token and/or social account" -msgstr "" - -#: Controller/SocialAccountsController.php:59;87 -msgid "Social Account already active" -msgstr "" - -#: Controller/SocialAccountsController.php:61 -msgid "Social Account could not be validated" -msgstr "" - -#: Controller/SocialAccountsController.php:80 -msgid "Email sent successfully" -msgstr "" - -#: Controller/SocialAccountsController.php:82 -msgid "Email could not be sent" -msgstr "" - -#: Controller/SocialAccountsController.php:85 -msgid "Invalid account" -msgstr "" - -#: Controller/SocialAccountsController.php:89 -msgid "Email could not be resent" -msgstr "" - -#: Controller/Component/RememberMeComponent.php:68 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" - -#: Controller/Component/UsersAuthComponent.php:204 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" - -#: Controller/Traits/LinkSocialTrait.php:54 -msgid "Could not associate account, please try again." -msgstr "" - -#: Controller/Traits/LinkSocialTrait.php:77 -msgid "Social account was associated." -msgstr "" - -#: Controller/Traits/LoginTrait.php:104 -msgid "Issues trying to log in with your social account" -msgstr "" - -#: Controller/Traits/LoginTrait.php:109 -#: Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "" - -#: Controller/Traits/LoginTrait.php:120 -msgid "Your user has not been validated yet. Please check your inbox for instructions" -msgstr "" - -#: Controller/Traits/LoginTrait.php:125 -msgid "Your social account has not been validated yet. Please check your inbox for instructions" -msgstr "" - -#: Controller/Traits/LoginTrait.php:180 -#: Controller/Traits/RegisterTrait.php:82 -msgid "Invalid reCaptcha" -msgstr "" - -#: Controller/Traits/LoginTrait.php:191 -msgid "You are already logged in" -msgstr "" - -#: Controller/Traits/LoginTrait.php:212 -msgid "Please enable Google Authenticator first." -msgstr "" - -#: Controller/Traits/LoginTrait.php:287 -msgid "Verification code is invalid. Try again" -msgstr "" - -#: Controller/Traits/LoginTrait.php:340 -msgid "Username or password is incorrect" -msgstr "" - -#: Controller/Traits/LoginTrait.php:363 -msgid "You've successfully logged out" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:49;82 -#: Controller/Traits/ProfileTrait.php:50 -msgid "User was not found" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:70;78;86 -msgid "Password could not be changed" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:74 -msgid "Password has been changed successfully" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:84 -msgid "{0}" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:127 -msgid "Please check your email to continue with password reset process" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:130 -#: Shell/UsersShell.php:247 -msgid "The password token could not be generated. Please try again" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:136 -#: Controller/Traits/UserValidationTrait.php:107 -msgid "User {0} was not found" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:138 -msgid "The user is not active" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:140 -#: Controller/Traits/UserValidationTrait.php:102;111 -msgid "Token could not be reset" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:164 -msgid "Google Authenticator token was successfully reset" -msgstr "" - -#: Controller/Traits/ProfileTrait.php:54 -msgid "Not authorized, please login first" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:43 -msgid "You must log out to register a new user account" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:89 -msgid "The user could not be saved" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:123 -msgid "You have registered successfully, please log in" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:125 -msgid "Please validate your account before log in" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:77;107 -msgid "The {0} has been saved" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:81;111 -msgid "The {0} could not be saved" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:131 -msgid "The {0} has been deleted" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:133 -msgid "The {0} could not be deleted" -msgstr "" - -#: Controller/Traits/SocialTrait.php:40 -msgid "The reCaptcha could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:43 -msgid "User account validated successfully" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:45 -msgid "User account could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:48 -msgid "User already active" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:54 -msgid "Reset password token was validated successfully" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:62 -msgid "Reset password token could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:66 -msgid "Invalid validation type" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:69 -msgid "Invalid token or user account already validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:71 -msgid "Token already expired" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:97 -msgid "Token has been reset successfully. Please check your email." -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:109 -msgid "User {0} is already active" -msgstr "" - -#: Mailer/UsersMailer.php:34 -msgid "Your account validation link" -msgstr "" - -#: Mailer/UsersMailer.php:52 -msgid "{0}Your reset password link" -msgstr "" - -#: Mailer/UsersMailer.php:75 -msgid "{0}Your social account validation link" -msgstr "" - -#: Model/Behavior/AuthFinderBehavior.php:49 -msgid "Missing 'username' in options data" -msgstr "" - -#: Model/Behavior/LinkSocialBehavior.php:53 -msgid "Social account already associated to another user" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:45 -msgid "Reference cannot be null" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:50 -msgid "Token expiration cannot be empty" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:56;117 -msgid "User not found" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:60 -#: Model/Behavior/RegisterBehavior.php:112;205 -msgid "User account already validated" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:67 -msgid "User not active" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:122 -msgid "The current password does not match" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:125 -msgid "You cannot use the current password as the new one" -msgstr "" - -#: Model/Behavior/RegisterBehavior.php:90 -msgid "User not found for the given token and email." -msgstr "" - -#: Model/Behavior/RegisterBehavior.php:93 -msgid "Token has already expired user with no token" -msgstr "" - -#: Model/Behavior/SocialAccountBehavior.php:103;130 -msgid "Account already validated" -msgstr "" - -#: Model/Behavior/SocialAccountBehavior.php:106;133 -msgid "Account not found for the given token and email." -msgstr "" - -#: Model/Behavior/SocialBehavior.php:82 -msgid "Unable to login user with reference {0}" -msgstr "" - -#: Model/Behavior/SocialBehavior.php:121 -msgid "Email not present" -msgstr "" - -#: Model/Table/UsersTable.php:81 -msgid "Your password does not match your confirm password. Please try again" -msgstr "" - -#: Model/Table/UsersTable.php:173 -msgid "Username already exists" -msgstr "" - -#: Model/Table/UsersTable.php:179 -msgid "Email already exists" -msgstr "" - -#: Shell/UsersShell.php:58 -msgid "Utilities for CakeDC Users Plugin" -msgstr "" - -#: Shell/UsersShell.php:60 -msgid "Activate an specific user" -msgstr "" - -#: Shell/UsersShell.php:63 -msgid "Add a new superadmin user for testing purposes" -msgstr "" - -#: Shell/UsersShell.php:66 -msgid "Add a new user" -msgstr "" - -#: Shell/UsersShell.php:69 -msgid "Change the role for an specific user" -msgstr "" - -#: Shell/UsersShell.php:72 -msgid "Deactivate an specific user" -msgstr "" - -#: Shell/UsersShell.php:75 -msgid "Delete an specific user" -msgstr "" - -#: Shell/UsersShell.php:78 -msgid "Reset the password via email" -msgstr "" - -#: Shell/UsersShell.php:81 -msgid "Reset the password for all users" -msgstr "" - -#: Shell/UsersShell.php:84 -msgid "Reset the password for an specific user" -msgstr "" - -#: Shell/UsersShell.php:133;159 -msgid "Please enter a password." -msgstr "" - -#: Shell/UsersShell.php:137 -msgid "Password changed for all users" -msgstr "" - -#: Shell/UsersShell.php:138;166 -msgid "New password: {0}" -msgstr "" - -#: Shell/UsersShell.php:156;184;262;359 -msgid "Please enter a username." -msgstr "" - -#: Shell/UsersShell.php:165 -msgid "Password changed for user: {0}" -msgstr "" - -#: Shell/UsersShell.php:187 -msgid "Please enter a role." -msgstr "" - -#: Shell/UsersShell.php:193 -msgid "Role changed for user: {0}" -msgstr "" - -#: Shell/UsersShell.php:194 -msgid "New role: {0}" -msgstr "" - -#: Shell/UsersShell.php:209 -msgid "User was activated: {0}" -msgstr "" - -#: Shell/UsersShell.php:224 -msgid "User was de-activated: {0}" -msgstr "" - -#: Shell/UsersShell.php:236 -msgid "Please enter a username or email." -msgstr "" - -#: Shell/UsersShell.php:244 -msgid "Please ask the user to check the email to continue with password reset process" -msgstr "" - -#: Shell/UsersShell.php:308 -msgid "Superuser added:" -msgstr "" - -#: Shell/UsersShell.php:310 -msgid "User added:" -msgstr "" - -#: Shell/UsersShell.php:312 -msgid "Id: {0}" -msgstr "" - -#: Shell/UsersShell.php:313 -msgid "Username: {0}" -msgstr "" - -#: Shell/UsersShell.php:314 -msgid "Email: {0}" -msgstr "" - -#: Shell/UsersShell.php:315 -msgid "Role: {0}" -msgstr "" - -#: Shell/UsersShell.php:316 -msgid "Password: {0}" -msgstr "" - -#: Shell/UsersShell.php:318 -msgid "User could not be added:" -msgstr "" - -#: Shell/UsersShell.php:321 -msgid "Field: {0} Error: {1}" -msgstr "" - -#: Shell/UsersShell.php:337 -msgid "The user was not found." -msgstr "" - -#: Shell/UsersShell.php:367 -msgid "The user {0} was not deleted. Please try again" -msgstr "" - -#: Shell/UsersShell.php:369 -msgid "The user {0} was deleted successfully" -msgstr "" - -#: Template/Email/html/reset_password.ctp:21 -#: Template/Email/html/social_account_validation.ctp:14 -#: Template/Email/html/validation.ctp:21 -#: Template/Email/text/reset_password.ctp:20 -#: Template/Email/text/social_account_validation.ctp:22 -#: Template/Email/text/validation.ctp:20 -msgid "Hi {0}" -msgstr "" - -#: Template/Email/html/reset_password.ctp:24 -msgid "Reset your password here" -msgstr "" - -#: Template/Email/html/reset_password.ctp:27 -#: Template/Email/html/social_account_validation.ctp:32 -#: Template/Email/html/validation.ctp:27 -msgid "If the link is not correctly displayed, please copy the following address in your web browser {0}" -msgstr "" - -#: Template/Email/html/reset_password.ctp:34 -#: Template/Email/html/social_account_validation.ctp:39 -#: Template/Email/html/validation.ctp:34 -#: Template/Email/text/reset_password.ctp:28 -#: Template/Email/text/social_account_validation.ctp:30 -#: Template/Email/text/validation.ctp:28 -msgid "Thank you" -msgstr "" - -#: Template/Email/html/social_account_validation.ctp:18 -msgid "Activate your social login here" -msgstr "" - -#: Template/Email/html/validation.ctp:24 -msgid "Activate your account here" -msgstr "" - -#: Template/Email/text/reset_password.ctp:22 -#: Template/Email/text/validation.ctp:22 -msgid "Please copy the following address in your web browser {0}" -msgstr "" - -#: Template/Email/text/social_account_validation.ctp:24 -msgid "Please copy the following address in your web browser to activate your social login {0}" -msgstr "" - -#: Template/Users/add.ctp:13 -#: Template/Users/edit.ctp:16 -#: Template/Users/index.ctp:13;26 -#: Template/Users/view.ctp:15 -msgid "Actions" -msgstr "" - -#: Template/Users/add.ctp:15 -#: Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:23 -msgid "List Users" -msgstr "" - -#: Template/Users/add.ctp:21 -#: Template/Users/register.ctp:17 -msgid "Add User" -msgstr "" - -#: Template/Users/add.ctp:23 -#: Template/Users/edit.ctp:35 -#: Template/Users/index.ctp:22 -#: Template/Users/profile.ctp:30 -#: Template/Users/register.ctp:19 -#: Template/Users/view.ctp:33 -msgid "Username" -msgstr "" - -#: Template/Users/add.ctp:24 -#: Template/Users/edit.ctp:36 -#: Template/Users/index.ctp:23 -#: Template/Users/profile.ctp:32 -#: Template/Users/register.ctp:20 -#: Template/Users/view.ctp:35 -msgid "Email" -msgstr "" - -#: Template/Users/add.ctp:25 -#: Template/Users/register.ctp:21 -msgid "Password" -msgstr "" - -#: Template/Users/add.ctp:26 -#: Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 -#: Template/Users/register.ctp:26 -msgid "First name" -msgstr "" - -#: Template/Users/add.ctp:27 -#: Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 -#: Template/Users/register.ctp:27 -msgid "Last name" -msgstr "" - -#: Template/Users/add.ctp:30 -#: Template/Users/edit.ctp:53 -#: Template/Users/view.ctp:49;74 -msgid "Active" -msgstr "" - -#: Template/Users/add.ctp:34 -#: Template/Users/change_password.ctp:25 -#: Template/Users/edit.ctp:57 -#: Template/Users/register.ctp:36 -#: Template/Users/request_reset_password.ctp:8 -#: Template/Users/resend_token_validation.ctp:20 -#: Template/Users/social_email.ctp:19 -msgid "Submit" -msgstr "" - -#: Template/Users/change_password.ctp:5 -msgid "Please enter the new password" -msgstr "" - -#: Template/Users/change_password.ctp:10 -msgid "Current password" -msgstr "" - -#: Template/Users/change_password.ctp:16 -msgid "New password" -msgstr "" - -#: Template/Users/change_password.ctp:21 -#: Template/Users/register.ctp:24 -msgid "Confirm password" -msgstr "" - -#: Template/Users/edit.ctp:21 -#: Template/Users/index.ctp:40 -msgid "Delete" -msgstr "" - -#: Template/Users/edit.ctp:23 -#: Template/Users/index.ctp:40 -#: Template/Users/view.ctp:21 -msgid "Are you sure you want to delete # {0}?" -msgstr "" - -#: Template/Users/edit.ctp:33 -#: Template/Users/view.ctp:17 -msgid "Edit User" -msgstr "" - -#: Template/Users/edit.ctp:39 -#: Template/Users/view.ctp:43 -msgid "Token" -msgstr "" - -#: Template/Users/edit.ctp:41 -msgid "Token expires" -msgstr "" - -#: Template/Users/edit.ctp:44 -msgid "API token" -msgstr "" - -#: Template/Users/edit.ctp:47 -msgid "Activation date" -msgstr "" - -#: Template/Users/edit.ctp:50 -msgid "TOS date" -msgstr "" - -#: Template/Users/edit.ctp:63 -msgid "Reset Google Authenticator Token" -msgstr "" - -#: Template/Users/edit.ctp:69 -msgid "Are you sure you want to reset token for user \"{0}\"?" -msgstr "" - -#: Template/Users/index.ctp:15 -msgid "New {0}" -msgstr "" - -#: Template/Users/index.ctp:37 -msgid "View" -msgstr "" - -#: Template/Users/index.ctp:38 -msgid "Change password" -msgstr "" - -#: Template/Users/index.ctp:39 -msgid "Edit" -msgstr "" - -#: Template/Users/index.ctp:49 -msgid "previous" -msgstr "" - -#: Template/Users/index.ctp:51 -msgid "next" -msgstr "" - -#: Template/Users/login.ctp:19 -msgid "Please enter your username and password" -msgstr "" - -#: Template/Users/login.ctp:29 -msgid "Remember me" -msgstr "" - -#: Template/Users/login.ctp:37 -msgid "Register" -msgstr "" - -#: Template/Users/login.ctp:43 -msgid "Reset Password" -msgstr "" - -#: Template/Users/login.ctp:48 -msgid "Login" -msgstr "" - -#: Template/Users/profile.ctp:21 -#: View/Helper/UserHelper.php:54 -msgid "{0} {1}" -msgstr "" - -#: Template/Users/profile.ctp:27 -msgid "Change Password" -msgstr "" - -#: Template/Users/profile.ctp:38 -#: Template/Users/view.ctp:68 -msgid "Social Accounts" -msgstr "" - -#: Template/Users/profile.ctp:42 -#: Template/Users/view.ctp:73 -msgid "Avatar" -msgstr "" - -#: Template/Users/profile.ctp:43 -#: Template/Users/view.ctp:72 -msgid "Provider" -msgstr "" - -#: Template/Users/profile.ctp:44 -msgid "Link" -msgstr "" - -#: Template/Users/profile.ctp:51 -msgid "Link to {0}" -msgstr "" - -#: Template/Users/register.ctp:29 -msgid "Accept TOS conditions?" -msgstr "" - -#: Template/Users/request_reset_password.ctp:5 -msgid "Please enter your email to reset your password" -msgstr "" - -#: Template/Users/resend_token_validation.ctp:15 -msgid "Resend Validation email" -msgstr "" - -#: Template/Users/resend_token_validation.ctp:17 -msgid "Email or username" -msgstr "" - -#: Template/Users/verify.ctp:13 -msgid "Verification Code" -msgstr "" - -#: Template/Users/verify.ctp:15 -msgid " Verify" -msgstr "" - -#: Template/Users/view.ctp:19 -msgid "Delete User" -msgstr "" - -#: Template/Users/view.ctp:24 -msgid "New User" -msgstr "" - -#: Template/Users/view.ctp:31 -msgid "Id" -msgstr "" - -#: Template/Users/view.ctp:37 -msgid "First Name" -msgstr "" - -#: Template/Users/view.ctp:39 -msgid "Last Name" -msgstr "" - -#: Template/Users/view.ctp:41 -msgid "Role" -msgstr "" - -#: Template/Users/view.ctp:45 -msgid "Api Token" -msgstr "" - -#: Template/Users/view.ctp:53 -msgid "Token Expires" -msgstr "" - -#: Template/Users/view.ctp:55 -msgid "Activation Date" -msgstr "" - -#: Template/Users/view.ctp:57 -msgid "Tos Date" -msgstr "" - -#: Template/Users/view.ctp:59;75 -msgid "Created" -msgstr "" - -#: Template/Users/view.ctp:61;76 -msgid "Modified" -msgstr "" - -#: View/Helper/UserHelper.php:45 -msgid "Sign in with" -msgstr "" - -#: View/Helper/UserHelper.php:48 -msgid "fa fa-{0}" -msgstr "" - -#: View/Helper/UserHelper.php:57 -msgid "btn btn-social btn-{0} " -msgstr "" - -#: View/Helper/UserHelper.php:106 -msgid "Logout" -msgstr "" - -#: View/Helper/UserHelper.php:123 -msgid "Welcome, {0}" -msgstr "" - -#: View/Helper/UserHelper.php:146 -msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" -msgstr "" - -#: View/Helper/UserHelper.php:205 -msgid "btn btn-social btn-{0}" -msgstr "" - -#: View/Helper/UserHelper.php:211 -msgid "Connected with {0}" -msgstr "" - -#: View/Helper/UserHelper.php:216 -msgid "Connect with {0}" -msgstr "" - diff --git a/src/Locale/es/Users.mo b/src/Locale/es/Users.mo deleted file mode 100644 index 50edf1e2b..000000000 Binary files a/src/Locale/es/Users.mo and /dev/null differ diff --git a/src/Locale/fr_FR/Users.mo b/src/Locale/fr_FR/Users.mo deleted file mode 100644 index fc623371d..000000000 Binary files a/src/Locale/fr_FR/Users.mo and /dev/null differ diff --git a/src/Locale/hu_HU/Users.mo b/src/Locale/hu_HU/Users.mo deleted file mode 100644 index ce8e248ce..000000000 Binary files a/src/Locale/hu_HU/Users.mo and /dev/null differ diff --git a/src/Locale/it_IT/Users.mo b/src/Locale/it_IT/Users.mo deleted file mode 100644 index f242989f4..000000000 Binary files a/src/Locale/it_IT/Users.mo and /dev/null differ diff --git a/src/Locale/pl/Users.mo b/src/Locale/pl/Users.mo deleted file mode 100644 index c5a1c88a3..000000000 Binary files a/src/Locale/pl/Users.mo and /dev/null differ diff --git a/src/Locale/pt_BR/Users.mo b/src/Locale/pt_BR/Users.mo deleted file mode 100644 index 5cbc087b8..000000000 Binary files a/src/Locale/pt_BR/Users.mo and /dev/null differ diff --git a/src/Locale/sv/Users.mo b/src/Locale/sv/Users.mo deleted file mode 100644 index 746e0c282..000000000 Binary files a/src/Locale/sv/Users.mo and /dev/null differ diff --git a/src/Locale/tr_TR/tr_TR.mo b/src/Locale/tr_TR/tr_TR.mo deleted file mode 100644 index c65ebec0d..000000000 Binary files a/src/Locale/tr_TR/tr_TR.mo and /dev/null differ diff --git a/src/Mailer/UsersMailer.php b/src/Mailer/UsersMailer.php index 2e021884d..2e4070e96 100644 --- a/src/Mailer/UsersMailer.php +++ b/src/Mailer/UsersMailer.php @@ -1,81 +1,115 @@ setHidden(['password', 'token_expires', 'api_token']); - $subject = __d('CakeDC/Users', 'Your account validation link'); + $subject = __d('cake_d_c/users', 'Your account validation link'); + $viewVars = [ + 'activationUrl' => UsersUrl::actionUrl('validateEmail', [ + '_full' => true, + $user['token'], + ]), + ] + $user->toArray(); + $this ->setTo($user['email']) - ->setSubject($firstName . $subject) - ->setViewVars($user->toArray()) - ->setTemplate('CakeDC/Users.validation'); + ->setSubject($firstName . $subject) + ->setEmailFormat(Message::MESSAGE_BOTH) + ->setViewVars($viewVars); + + $this->viewBuilder() + ->setTemplate('CakeDC/Users.validation'); } /** * Send the reset password email to the user * - * @param EntityInterface $user User entity - * + * @param \Cake\Datasource\EntityInterface $user User entity * @return void */ protected function resetPassword(EntityInterface $user) { - $firstName = isset($user['first_name'])? $user['first_name'] . ', ' : ''; - $subject = __d('CakeDC/Users', '{0}Your reset password link', $firstName); + $firstName = isset($user['first_name']) ? $user['first_name'] . ', ' : ''; + $subject = __d('cake_d_c/users', '{0}Your reset password link', $firstName); // un-hide the token to be able to send it in the email content $user->setHidden(['password', 'token_expires', 'api_token']); + $viewVars = [ + 'activationUrl' => UsersUrl::actionUrl('resetPassword', [ + '_full' => true, + $user['token'], + ]), + ] + $user->toArray(); + $this ->setTo($user['email']) ->setSubject($subject) - ->setViewVars($user->toArray()) + ->setEmailFormat(Message::MESSAGE_BOTH) + ->setViewVars($viewVars); + $this + ->viewBuilder() ->setTemplate('CakeDC/Users.resetPassword'); } /** * Send account validation email to the user * - * @param EntityInterface $user User entity - * @param EntityInterface $socialAccount SocialAccount entity - * + * @param \Cake\Datasource\EntityInterface $user User entity + * @param \Cake\Datasource\EntityInterface $socialAccount SocialAccount entity * @return void */ protected function socialAccountValidation(EntityInterface $user, EntityInterface $socialAccount) { - $firstName = isset($user['first_name'])? $user['first_name'] . ', ' : ''; + $firstName = isset($user['first_name']) ? $user['first_name'] . ', ' : ''; // note: we control the space after the username in the previous line - $subject = __d('CakeDC/Users', '{0}Your social account validation link', $firstName); + $subject = __d('cake_d_c/users', '{0}Your social account validation link', $firstName); + $activationUrl = [ + '_full' => true, + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => 'validateAccount', + $socialAccount['provider'] ?? null, + $socialAccount['reference'] ?? null, + $socialAccount['token'] ?? null, + ]; $this ->setTo($user['email']) ->setSubject($subject) - ->setViewVars(compact('user', 'socialAccount')) + ->setEmailFormat(Message::MESSAGE_BOTH) + ->setViewVars(['user' => $user, 'socialAccount' => $socialAccount, 'activationUrl' => $activationUrl]); + $this + ->viewBuilder() ->setTemplate('CakeDC/Users.socialAccountValidation'); } } diff --git a/src/Middleware/SocialAuthMiddleware.php b/src/Middleware/SocialAuthMiddleware.php new file mode 100644 index 000000000..00ca65130 --- /dev/null +++ b/src/Middleware/SocialAuthMiddleware.php @@ -0,0 +1,133 @@ +getPrevious() !== null ? get_class($exception->getPrevious()) : self::class; + $response = new Response(); + if ($baseClassName === MissingEmailException::class) { + $this->setErrorMessage($request, __d('cake_d_c/users', 'Please enter your email')); + + $request->getSession()->write( + Configure::read('Users.Key.Session.social'), + $exception->getAttributes()['rawData'] + ); + + return $this->responseWithActionLocation($response, 'socialEmail'); + } + + $this->setErrorMessage($request, __d('cake_d_c/users', 'Could not identify your account, please try again')); + + return $this->responseWithActionLocation($response, 'login'); + } + + /** + * Set request error message + * + * @param \Cake\Http\ServerRequest $request the request with session attribute + * @param string $message the message + * @return void + */ + private function setErrorMessage(ServerRequest $request, $message) + { + $messages = (array)$request->getSession()->read('Flash.flash'); + $messages[] = [ + 'key' => 'flash', + 'element' => 'Flash/error', + 'params' => [], + 'message' => $message, + ]; + $request->getSession()->write('Flash.flash', $messages); + } + + /** + * Set location header to response using the string action + * + * @param \Cake\Http\Response $response to set location header + * @param string $action action at users controller + * @return \Psr\Http\Message\ResponseInterface + */ + protected function responseWithActionLocation(Response $response, $action) + { + $url = Router::url(UsersUrl::actionUrl($action)); + + return $response->withLocation($url); + } + + /** + * Go to next handling SocialAuthenticationException + * + * @param \Cake\Http\ServerRequest $request The request + * @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler. + * @return \Psr\Http\Message\ResponseInterface + */ + protected function goNext(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + return $handler->handle($request); + } catch (SocialAuthenticationException $exception) { + return $this->onAuthenticationException($request, $exception); + } + } + + /** + * Callable implementation for the middleware stack. + * + * @param \Psr\Http\Message\ServerRequestInterface $request The request. + * @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler. + * @return \Psr\Http\Message\ResponseInterface A response. + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (!(new UsersUrl())->checkActionOnRequest('socialLogin', $request)) { + return $handler->handle($request); + } + + $service = (new ServiceFactory())->createFromRequest($request); + if (!$service->isGetUserStep($request)) { + return (new Response()) + ->withLocation($service->getAuthorizationUrl($request)); + } + $request = $request->withAttribute(SocialAuthenticator::SOCIAL_SERVICE_ATTRIBUTE, $service); + + return $this->goNext($request, $handler); + } +} diff --git a/src/Middleware/SocialEmailMiddleware.php b/src/Middleware/SocialEmailMiddleware.php new file mode 100644 index 000000000..8f2d2d60b --- /dev/null +++ b/src/Middleware/SocialEmailMiddleware.php @@ -0,0 +1,43 @@ +getAttribute('session'); + if (!(new UsersUrl())->checkActionOnRequest('socialEmail', $request)) { + $session->delete(Configure::read('Users.Key.Session.social')); + + return $handler->handle($request); + } + + if (!$session->check(Configure::read('Users.Key.Session.social'))) { + throw new NotFoundException(); + } + + return $this->goNext($request, $handler); + } +} diff --git a/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php b/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php new file mode 100644 index 000000000..709a3fe43 --- /dev/null +++ b/src/Middleware/UnauthorizedHandler/DefaultRedirectHandler.php @@ -0,0 +1,118 @@ + [ + 'MissingIdentityException' => MissingIdentityException::class, + 'ForbiddenException' => ForbiddenException::class, + ], + 'url' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'login', + ], + 'queryParam' => 'redirect', + 'statusCode' => 302, + 'flash' => [], + ]; + + /** + * @inheritDoc + */ + public function handle(Exception $exception, ServerRequestInterface $request, array $options = []): ResponseInterface + { + $options += $this->defaultOptions; + $response = parent::handle($exception, $request, $options); + $session = $request->getAttribute('session'); + if ($session instanceof Session) { + $this->addFlashMessage($session, $options); + } + + return $response; + } + + /** + * @inheritDoc + */ + protected function getUrl(ServerRequestInterface $request, array $options): string + { + $url = $options['url']; + if (is_callable($url)) { + return $url($request, $options); + } + + if ($request->getAttribute('identity') && $request instanceof ServerRequest) { + return $request->referer() ?? '/'; + } + + if ($options['queryParam'] !== null) { + $url['?'][$options['queryParam']] = (string)$request->getUri(); + } + + return Router::url($url); + } + + /** + * Add a flash message informing location is not authorized. + * + * @param \Cake\Http\Session $session The CakePHP session. + * @param array $options Defined options. + * @return void + */ + protected function addFlashMessage(Session $session, $options): void + { + $messages = (array)$session->read('Flash.flash'); + $messages[] = $this->createFlashMessage($options); + $session->write('Flash.flash', $messages); + } + + /** + * Create a flash message data. + * + * @param array $options Handler options + * @return array + */ + protected function createFlashMessage($options): array + { + $message = (array)($options['flash'] ?? []); + + return $message + [ + 'message' => __d('cake_d_c/users', 'You are not authorized to access that location.'), + 'key' => 'flash', + 'element' => 'flash/error', + 'params' => [], + ]; + } +} diff --git a/src/Model/Behavior/AuthFinderBehavior.php b/src/Model/Behavior/AuthFinderBehavior.php index ef51d1fc9..3dc19fe2b 100644 --- a/src/Model/Behavior/AuthFinderBehavior.php +++ b/src/Model/Behavior/AuthFinderBehavior.php @@ -1,11 +1,13 @@ where([$this->_table->aliasField('active') => 1]); @@ -37,21 +37,21 @@ public function findActive(Query $query, array $options = []) /** * Custom finder to log in users * - * @param Query $query Query object to modify + * @param \Cake\ORM\Query $query Query object to modify * @param array $options Query options - * @return Query + * @return \Cake\ORM\Query * @throws \BadMethodCallException */ public function findAuth(Query $query, array $options = []) { - $identifier = Hash::get($options, 'username'); + $identifier = $options['username'] ?? null; if (empty($identifier)) { - throw new \BadMethodCallException(__d('CakeDC/Users', 'Missing \'username\' in options data')); + throw new \BadMethodCallException(__d('cake_d_c/users', 'Missing \'username\' in options data')); } $where = $query->clause('where') ?: []; $query ->where(function ($exp) use ($identifier, $where) { - $or = $exp->or_([$this->_table->aliasField('email') => $identifier]); + $or = $exp->or([$this->_table->aliasField('email') => $identifier]); return $or->add($where); }, [], true) diff --git a/src/Model/Behavior/BaseTokenBehavior.php b/src/Model/Behavior/BaseTokenBehavior.php index 1e6207159..fbbe3cc89 100644 --- a/src/Model/Behavior/BaseTokenBehavior.php +++ b/src/Model/Behavior/BaseTokenBehavior.php @@ -1,18 +1,20 @@ updateToken($tokenExpiration); } else { $user['active'] = true; - $user['activation_date'] = new Time(); + $user['activation_date'] = new FrozenTime(); } return $user; @@ -45,15 +47,14 @@ protected function _updateActive(EntityInterface $user, $validateEmail, $tokenEx /** * Remove user token for validation * - * @param EntityInterface $user user object. - * @return EntityInterface + * @param \Cake\Datasource\EntityInterface $user user object. + * @return \Cake\Datasource\EntityInterface */ protected function _removeValidationToken(EntityInterface $user) { - $user->token = null; - $user->token_expires = null; - $result = $this->_table->save($user); + $user['token'] = null; + $user['token_expires'] = null; - return $result; + return $this->_table->save($user); } } diff --git a/src/Model/Behavior/LinkSocialBehavior.php b/src/Model/Behavior/LinkSocialBehavior.php index 68f48608c..14096acea 100644 --- a/src/Model/Behavior/LinkSocialBehavior.php +++ b/src/Model/Behavior/LinkSocialBehavior.php @@ -1,27 +1,27 @@ _table->SocialAccounts->getAlias(); $socialAccount = $this->_table->SocialAccounts->find() ->where([ $alias . '.reference' => $reference, - $alias . '.provider' => Hash::get($data, 'provider') + $alias . '.provider' => $data['provider'] ?? null, ])->first(); if ($socialAccount && $user->id !== $socialAccount->user_id) { $user->setErrors([ 'social_accounts' => [ - '_existsIn' => __d('CakeDC/Users', 'Social account already associated to another user') - ] + '_existsIn' => __d('cake_d_c/users', 'Social account already associated to another user'), + ], ]); return $user; @@ -63,16 +62,15 @@ public function linkSocialAccount(EntityInterface $user, $data) /** * Create or update a new social account linking to the user. * - * @param EntityInterface $user User to link. + * @param \Cake\Datasource\EntityInterface $user User to link. * @param array $data Social account information. - * @param EntityInterface $socialAccount to update or create. - * - * @return EntityInterface + * @param \Cake\Datasource\EntityInterface $socialAccount to update or create. + * @return \Cake\Datasource\EntityInterface */ protected function createOrUpdateSocialAccount(EntityInterface $user, $data, $socialAccount) { if (!$socialAccount) { - $socialAccount = $this->_table->SocialAccounts->newEntity(); + $socialAccount = $this->_table->SocialAccounts->newEntity([]); } $data['user_id'] = $user->id; @@ -105,36 +103,37 @@ protected function createOrUpdateSocialAccount(EntityInterface $user, $data, $so /** * Populate the social account * - * @param EntityInterface $socialAccount to populate. + * @param \Cake\Datasource\EntityInterface $socialAccount to populate. * @param array $data Social account information. - * - * @return EntityInterface + * @return \Cake\Datasource\EntityInterface */ protected function populateSocialAccount($socialAccount, $data) { $accountData = $socialAccount->toArray(); - $accountData['username'] = Hash::get($data, 'username'); - $accountData['reference'] = Hash::get($data, 'id'); - $accountData['avatar'] = Hash::get($data, 'avatar'); - $accountData['link'] = Hash::get($data, 'link'); - $accountData['avatar'] = str_replace('normal', 'square', $accountData['avatar']); - $accountData['description'] = Hash::get($data, 'bio'); - $accountData['token'] = Hash::get($data, 'credentials.token'); - $accountData['token_secret'] = Hash::get($data, 'credentials.secret'); - $accountData['user_id'] = Hash::get($data, 'user_id'); + $accountData['username'] = $data['username'] ?? null; + $accountData['reference'] = $data['id'] ?? null; + $accountData['avatar'] = $data['avatar'] ?? null; + $accountData['link'] = $data['link'] ?? null; + if ($accountData['avatar'] ?? null) { + $accountData['avatar'] = str_replace('normal', 'square', $accountData['avatar']); + } + $accountData['description'] = $data['bio'] ?? null; + $accountData['token'] = $data['credentials']['token'] ?? null; + $accountData['token_secret'] = $data['credentials']['secret'] ?? null; + $accountData['user_id'] = $data['user_id'] ?? null; $accountData['token_expires'] = null; - $expires = Hash::get($data, 'credentials.expires'); + $expires = $data['credentials']['expires'] ?? null; if (!empty($expires)) { - $expiresTime = new Time(); + $expiresTime = new FrozenTime(); $accountData['token_expires'] = $expiresTime->setTimestamp($expires)->format('Y-m-d H:i:s'); } - $accountData['data'] = serialize(Hash::get($data, 'raw')); + $accountData['data'] = serialize($data['raw'] ?? null); $accountData['active'] = true; $socialAccount = $this->_table->SocialAccounts->patchEntity($socialAccount, $accountData); //ensure provider is present in Entity - $socialAccount['provider'] = Hash::get($data, 'provider'); + $socialAccount['provider'] = $data['provider'] ?? null; return $socialAccount; } diff --git a/src/Model/Behavior/PasswordBehavior.php b/src/Model/Behavior/PasswordBehavior.php index 852ca9a7f..e307d15a0 100644 --- a/src/Model/Behavior/PasswordBehavior.php +++ b/src/Model/Behavior/PasswordBehavior.php @@ -1,25 +1,26 @@ _getUser($reference); if (empty($user)) { - throw new UserNotFoundException(__d('CakeDC/Users', "User not found")); + throw new UserNotFoundException(__d('cake_d_c/users', 'User not found')); } - if (Hash::get($options, 'checkActive')) { + if ($options['checkActive'] ?? false) { if ($user->active) { - throw new UserAlreadyActiveException(__d('CakeDC/Users', "User account already validated")); + throw new UserAlreadyActiveException(__d('cake_d_c/users', 'User account already validated')); } $user->active = false; $user->activation_date = null; } - if (Hash::get($options, 'ensureActive')) { - if (!$user['active']) { - throw new UserNotActiveException(__d('CakeDC/Users', "User not active")); - } + if (($options['ensureActive'] ?? false) && !$user['active']) { + throw new UserNotActiveException(__d('cake_d_c/users', 'User not active')); } $user->updateToken($expiration); $saveResult = $this->_table->save($user); - if (Hash::get($options, 'sendEmail')) { - $this->sendResetPasswordEmail($user); + if ($options['sendEmail'] ?? false) { + switch ($options['type'] ?? null) { + case 'email': + $this->_sendValidationEmail($user); + break; + case 'password': + $this->_sendResetPasswordEmail($user); + break; + } } return $saveResult; @@ -79,16 +84,30 @@ public function resetToken($reference, array $options = []) /** * Send the reset password related email link * - * @param EntityInterface $user user + * @param \Cake\Datasource\EntityInterface $user user * @return void */ - protected function sendResetPasswordEmail($user) + protected function _sendResetPasswordEmail($user) { $this ->getMailer(Configure::read('Users.Email.mailerClass') ?: 'CakeDC/Users.Users') ->send('resetPassword', [$user]); } + /** + * Wrapper for mailer + * + * @param \Cake\Datasource\EntityInterface $user user + * @return void + */ + protected function _sendValidationEmail($user) + { + $mailer = Configure::read('Users.Email.mailerClass') ?: 'CakeDC/Users.Users'; + $this + ->getMailer($mailer) + ->send('validation', [$user]); + } + /** * Get the user by email or username * @@ -103,27 +122,27 @@ protected function _getUser($reference) /** * Change password method * - * @param EntityInterface $user user data. - * @throws WrongPasswordException + * @param \Cake\Datasource\EntityInterface $user user data. + * @throws \CakeDC\Users\Exception\WrongPasswordException * @return mixed */ public function changePassword(EntityInterface $user) { try { $currentUser = $this->_table->get($user->id, [ - 'contain' => [] + 'contain' => [], ]); } catch (RecordNotFoundException $e) { - throw new UserNotFoundException(__d('CakeDC/Users', "User not found")); + throw new UserNotFoundException(__d('cake_d_c/users', 'User not found')); } if (!empty($user->current_password)) { if (!$user->checkPassword($user->current_password, $currentUser->password)) { - throw new WrongPasswordException(__d('CakeDC/Users', 'The current password does not match')); + throw new WrongPasswordException(__d('cake_d_c/users', 'The current password does not match')); } if ($user->current_password === $user->password_confirm) { throw new WrongPasswordException(__d( - 'CakeDC/Users', + 'cake_d_c/users', 'You cannot use the current password as the new one' )); } diff --git a/src/Model/Behavior/RegisterBehavior.php b/src/Model/Behavior/RegisterBehavior.php index fc3243a2f..b2fb4fb10 100644 --- a/src/Model/Behavior/RegisterBehavior.php +++ b/src/Model/Behavior/RegisterBehavior.php @@ -1,25 +1,25 @@ validateEmail = (bool)Configure::read('Users.Email.validate'); @@ -44,19 +53,27 @@ public function initialize(array $config) /** * Registers an user. * - * @param EntityInterface $user User information + * @param \Cake\Datasource\EntityInterface $user User information * @param array $data User information * @param array $options ['tokenExpiration] - * @return bool|EntityInterface + * @return bool|\Cake\Datasource\EntityInterface */ public function register($user, $data, $options) { - $validateEmail = Hash::get($options, 'validate_email'); - $tokenExpiration = Hash::get($options, 'token_expiration'); + $validateEmail = $options['validate_email'] ?? null; + $tokenExpiration = $options['token_expiration'] ?? null; + $validator = $options['validator'] ?? null; + if (is_string($validator)) { + $validate = $validator; + } else { + $this->_table->setValidator('current', $validator ?: $this->getRegisterValidators($options)); + $validate = 'current'; + } + $user = $this->_table->patchEntity( $user, $data, - ['validate' => Hash::get($options, 'validator') ?: $this->getRegisterValidators($options)] + ['validate' => $validate] ); $user['role'] = Configure::read('Users.Registration.defaultRole') ?: 'user'; $user->validated = false; @@ -76,23 +93,23 @@ public function register($user, $data, $options) * * @param string $token toke to be validated. * @param null $callback function that will be returned. - * @throws TokenExpiredException when token has expired. - * @throws UserNotFoundException when user isn't found. - * @return EntityInterface $user + * @throws \CakeDC\Users\Exception\TokenExpiredException when token has expired. + * @throws \CakeDC\Users\Exception\UserNotFoundException when user isn't found. + * @return \Cake\Datasource\EntityInterface $user */ public function validate($token, $callback = null) { - $user = $this->_table->find() + $user = $token ? $this->_table->find() ->select(['token_expires', 'id', 'active', 'token']) ->where(['token' => $token]) - ->first(); + ->first() : null; if (empty($user)) { - throw new UserNotFoundException(__d('CakeDC/Users', "User not found for the given token and email.")); + throw new UserNotFoundException(__d('cake_d_c/users', 'User not found for the given token and email.')); } if ($user->tokenExpired()) { - throw new TokenExpiredException(__d('CakeDC/Users', "Token has already expired user with no token")); + throw new TokenExpiredException(__d('cake_d_c/users', 'Token has already expired user with no token')); } - if (!method_exists($this, $callback)) { + if (!method_exists($this, (string)$callback)) { return $user; } @@ -102,32 +119,31 @@ public function validate($token, $callback = null) /** * Activates an user * - * @param EntityInterface $user user object. + * @param \Cake\Datasource\EntityInterface $user user object. * @return mixed User entity or bool false if the user could not be activated - * @throws UserAlreadyActiveException + * @throws \CakeDC\Users\Exception\UserAlreadyActiveException */ public function activateUser(EntityInterface $user) { if ($user->active) { - throw new UserAlreadyActiveException(__d('CakeDC/Users', "User account already validated")); + throw new UserAlreadyActiveException(__d('cake_d_c/users', 'User account already validated')); } $user->activation_date = new \DateTime(); $user->token_expires = null; $user->active = true; - $result = $this->_table->save($user); - return $result; + return $this->_table->save($user); } /** * buildValidator * - * @param Event $event event - * @param Validator $validator validator + * @param \Cake\Event\Event $event event + * @param \Cake\Validation\Validator $validator validator * @param string $name name - * @return Validator + * @return \Cake\Validation\Validator */ - public function buildValidator(Event $event, Validator $validator, $name) + public function buildValidator(\Cake\Event\EventInterface $event, Validator $validator, $name) { if ($name === 'default') { return $this->_emailValidator($validator, $this->validateEmail); @@ -139,16 +155,16 @@ public function buildValidator(Event $event, Validator $validator, $name) /** * Email validator * - * @param Validator $validator Validator instance. + * @param \Cake\Validation\Validator $validator Validator instance. * @param bool $validateEmail true when email needs to be required - * @return Validator + * @return \Cake\Validation\Validator */ protected function _emailValidator(Validator $validator, $validateEmail) { $this->validateEmail = $validateEmail; $validator ->add('email', 'valid', ['rule' => 'email']) - ->notEmpty('email', __d('Users', 'This field is required'), function ($context) { + ->notBlank('email', __d('cake_d_c/users', 'This field is required'), function ($context) { return $this->validateEmail; }); @@ -158,14 +174,14 @@ protected function _emailValidator(Validator $validator, $validateEmail) /** * Tos validator * - * @param Validator $validator Validator instance. - * @return Validator + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator */ protected function _tosValidator(Validator $validator) { $validator ->requirePresence('tos', 'create') - ->notEmpty('tos'); + ->notBlank('tos'); return $validator; } @@ -174,12 +190,12 @@ protected function _tosValidator(Validator $validator) * Returns the list of validators * * @param array $options Array of options ['validate_email' => true/false, 'use_tos' => true/false] - * @return Validator + * @return \Cake\Validation\Validator */ public function getRegisterValidators($options) { - $validateEmail = Hash::get($options, 'validate_email'); - $useTos = Hash::get($options, 'use_tos'); + $validateEmail = $options['validate_email'] ?? null; + $useTos = $options['use_tos'] ?? null; $validator = $this->_table->validationDefault(new Validator()); $validator = $this->_table->validationRegister($validator); @@ -194,31 +210,10 @@ public function getRegisterValidators($options) return $validator; } - /** - * @param EntityInterface $user User information - * @param array $options ['tokenExpiration] - * @return bool|EntityInterface - */ - public function resendValidationEmail($user, $options) - { - if ($user->active) { - throw new UserAlreadyActiveException(__d('CakeDC/Users', "User account already validated")); - } - - $tokenExpiration = Hash::get($options, 'token_expiration'); - $user = $this->_updateActive($user, true, $tokenExpiration); - $userSaved = $this->_table->saveOrFail($user); - if ($userSaved) { - $this->_sendValidationEmail($userSaved); - } - - return $userSaved; - } - /** * Wrapper for mailer * - * @param EntityInterface $user user + * @param \Cake\Datasource\EntityInterface $user user * @return void */ protected function _sendValidationEmail($user) diff --git a/src/Model/Behavior/SocialAccountBehavior.php b/src/Model/Behavior/SocialAccountBehavior.php index 1ab405ab3..10408a060 100644 --- a/src/Model/Behavior/SocialAccountBehavior.php +++ b/src/Model/Behavior/SocialAccountBehavior.php @@ -1,30 +1,29 @@ _table->belongsTo('Users', [ - 'foreignKey' => 'user_id', - 'joinType' => 'INNER', - 'className' => Configure::read('Users.table') - ]); + $this->_table->belongsTo('Users')->setForeignKey('user_id')->setJoinType('INNER')->setClassName(Configure::read('Users.table')); } /** * After save callback * - * @param Event $event event - * @param Entity $entity entity + * @param \Cake\Event\EventInterface $event event + * @param \Cake\Datasource\EntityInterface $entity entity * @param \ArrayObject $options options * @return mixed */ - public function afterSave(Event $event, Entity $entity, $options) + public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { - if ($entity->active) { + if ($entity->get('active')) { return true; } - $user = $this->_table->Users->find()->where(['Users.id' => $entity->user_id, 'Users.active' => true])->first(); + $user = $this->_table->getAssociation('Users')->find() + ->where(['Users.id' => $entity->get('user_id'), 'Users.active' => true]) + ->first(); if (empty($user)) { return true; } @@ -70,9 +67,9 @@ public function afterSave(Event $event, Entity $entity, $options) /** * Send social validation email to the user * - * @param EntityInterface $socialAccount social account - * @param EntityInterface $user user - * @return void + * @param \Cake\Datasource\EntityInterface $socialAccount social account + * @param \Cake\Datasource\EntityInterface $user user + * @return array */ protected function sendSocialValidationEmail(EntityInterface $socialAccount, EntityInterface $user) { @@ -87,9 +84,9 @@ protected function sendSocialValidationEmail(EntityInterface $socialAccount, Ent * @param string $provider provider * @param string $reference reference * @param string $token token - * @throws RecordNotFoundException - * @throws AccountAlreadyActiveException - * @return User + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * @throws \CakeDC\Users\Exception\AccountAlreadyActiveException + * @return \CakeDC\Users\Model\Entity\User */ public function validateAccount($provider, $reference, $token) { @@ -100,10 +97,12 @@ public function validateAccount($provider, $reference, $token) if (!empty($socialAccount) && $socialAccount->token === $token) { if ($socialAccount->active) { - throw new AccountAlreadyActiveException(__d('CakeDC/Users', "Account already validated")); + throw new AccountAlreadyActiveException(__d('cake_d_c/users', 'Account already validated')); } } else { - throw new RecordNotFoundException(__d('CakeDC/Users', "Account not found for the given token and email.")); + throw new RecordNotFoundException( + __d('cake_d_c/users', 'Account not found for the given token and email.') + ); } return $this->_activateAccount($socialAccount); @@ -114,9 +113,9 @@ public function validateAccount($provider, $reference, $token) * * @param string $provider provider * @param string $reference reference - * @throws RecordNotFoundException - * @throws AccountAlreadyActiveException - * @return User + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * @throws \CakeDC\Users\Exception\AccountAlreadyActiveException + * @return \CakeDC\Users\Model\Entity\User */ public function resendValidation($provider, $reference) { @@ -127,10 +126,14 @@ public function resendValidation($provider, $reference) if (!empty($socialAccount)) { if ($socialAccount->active) { - throw new AccountAlreadyActiveException(__d('CakeDC/Users', "Account already validated")); + throw new AccountAlreadyActiveException( + __d('cake_d_c/users', 'Account already validated') + ); } } else { - throw new RecordNotFoundException(__d('CakeDC/Users', "Account not found for the given token and email.")); + throw new RecordNotFoundException( + __d('cake_d_c/users', 'Account not found for the given token and email.') + ); } return $this->sendSocialValidationEmail($socialAccount, $socialAccount->user); @@ -139,14 +142,13 @@ public function resendValidation($provider, $reference) /** * Activates an account * - * @param SocialAccount $socialAccount social account - * @return EntityInterface + * @param \CakeDC\Users\Model\Entity\SocialAccount $socialAccount social account + * @return \Cake\Datasource\EntityInterface */ protected function _activateAccount($socialAccount) { $socialAccount->active = true; - $result = $this->_table->save($socialAccount); - return $result; + return $this->_table->save($socialAccount); } } diff --git a/src/Model/Behavior/SocialBehavior.php b/src/Model/Behavior/SocialBehavior.php index 7fdd2c0f0..7911ed802 100644 --- a/src/Model/Behavior/SocialBehavior.php +++ b/src/Model/Behavior/SocialBehavior.php @@ -1,30 +1,32 @@ _username = $config['username']; @@ -58,18 +60,18 @@ public function initialize(array $config) * * @param array $data Array social login. * @param array $options Array option data. - * @throws InvalidArgumentException - * @throws UserNotActiveException - * @throws AccountNotActiveException - * @return bool|EntityInterface|mixed + * @throws \InvalidArgumentException + * @throws \CakeDC\Users\Exception\UserNotActiveException + * @throws \CakeDC\Users\Exception\AccountNotActiveException + * @return bool|\Cake\Datasource\EntityInterface|mixed */ public function socialLogin(array $data, array $options) { - $reference = Hash::get($data, 'id'); + $reference = $data['id'] ?? null; $existingAccount = $this->_table->SocialAccounts->find() ->where([ 'SocialAccounts.reference' => $reference, - 'SocialAccounts.provider' => Hash::get($data, 'provider') + 'SocialAccounts.provider' => $data['provider'] ?? null, ]) ->contain(['Users']) ->first(); @@ -79,22 +81,35 @@ public function socialLogin(array $data, array $options) $existingAccount = $user->social_accounts[0]; } else { //@todo: what if we don't have a social account after createSocialUser? - throw new InvalidArgumentException(__d('CakeDC/Users', 'Unable to login user with reference {0}', $reference)); + throw new InvalidArgumentException( + __d('cake_d_c/users', 'Unable to login user with reference {0}', $reference) + ); } } else { $user = $existingAccount->user; + $accountData = $this->extractAccountData($data); + $this->_table->SocialAccounts->patchEntity($existingAccount, $accountData); + $this->_table->SocialAccounts->save($existingAccount); + $event = $this->dispatchEvent(Plugin::EVENT_SOCIAL_LOGIN_EXISTING_ACCOUNT, [ + 'userEntity' => $user, + 'data' => $data, + ]); + + if ($event->getResult() instanceof EntityInterface) { + $user = $this->_table->save($event->getResult()); + } } if (!empty($existingAccount)) { if (!$existingAccount->active) { throw new AccountNotActiveException([ $existingAccount->provider, - $existingAccount->reference + $existingAccount->reference, ]); } if (!$user->active) { throw new UserNotActiveException([ $existingAccount->provider, - $existingAccount->$user + $existingAccount->$user, ]); } } @@ -107,37 +122,36 @@ public function socialLogin(array $data, array $options) * * @param array $data Array social user. * @param array $options Array option data. - * @throws MissingEmailException - * @return bool|EntityInterface|mixed result of the save operation + * @throws \CakeDC\Users\Exception\MissingEmailException + * @return bool|\Cake\Datasource\EntityInterface|mixed result of the save operation */ protected function _createSocialUser($data, $options = []) { - $useEmail = Hash::get($options, 'use_email'); - $validateEmail = Hash::get($options, 'validate_email'); - $tokenExpiration = Hash::get($options, 'token_expiration'); + $useEmail = $options['use_email'] ?? null; + $validateEmail = $options['validate_email'] ?? null; + $tokenExpiration = $options['token_expiration'] ?? null; $existingUser = null; - $email = Hash::get($data, 'email'); + $email = $data['email'] ?? null; if ($useEmail && empty($email)) { - throw new MissingEmailException(__d('CakeDC/Users', 'Email not present')); + throw new MissingEmailException(__d('cake_d_c/users', 'Email not present')); } else { - $existingUser = $this->_table->find() - ->where([$this->_table->aliasField('email') => $email]) - ->first(); + $existingUser = $this->_table->find('existingForSocialLogin', ['email' => $email])->first(); } $user = $this->_populateUser($data, $existingUser, $useEmail, $validateEmail, $tokenExpiration); - $event = $this->dispatchEvent(UsersAuthComponent::EVENT_BEFORE_SOCIAL_LOGIN_USER_CREATE, [ + $event = $this->dispatchEvent(Plugin::EVENT_BEFORE_SOCIAL_LOGIN_USER_CREATE, [ 'userEntity' => $user, + 'data' => $data, ]); - if ($event->result instanceof EntityInterface) { - $user = $event->result; + $result = $event->getResult(); + if ($result instanceof EntityInterface) { + $user = $result; } $this->_table->isValidateEmail = $validateEmail; - $result = $this->_table->save($user); - return $result; + return $this->_table->save($user); } /** @@ -145,76 +159,61 @@ protected function _createSocialUser($data, $options = []) * data to create a new one * * @param array $data Array social login. - * @param EntityInterface $existingUser user data. + * @param \Cake\Datasource\EntityInterface $existingUser user data. * @param string $useEmail email to use. * @param string $validateEmail email to validate. * @param string $tokenExpiration token_expires data. - * @return EntityInterface + * @return \Cake\Datasource\EntityInterface * @todo refactor */ protected function _populateUser($data, $existingUser, $useEmail, $validateEmail, $tokenExpiration) { - $accountData['username'] = Hash::get($data, 'username'); - $accountData['reference'] = Hash::get($data, 'id'); - $accountData['avatar'] = Hash::get($data, 'avatar'); - $accountData['link'] = Hash::get($data, 'link'); - - $accountData['avatar'] = str_replace('normal', 'square', $accountData['avatar']); - $accountData['description'] = Hash::get($data, 'bio'); - $accountData['token'] = Hash::get($data, 'credentials.token'); - $accountData['token_secret'] = Hash::get($data, 'credentials.secret'); - $expires = Hash::get($data, 'credentials.expires'); - if (!empty($expires)) { - $expiresTime = new DateTime(); - $accountData['token_expires'] = $expiresTime->setTimestamp($expires)->format('Y-m-d H:i:s'); - } else { - $accountData['token_expires'] = null; - } - $accountData['data'] = serialize(Hash::get($data, 'raw')); + $userData = []; + $accountData = $this->extractAccountData($data); $accountData['active'] = true; - $dataValidated = Hash::get($data, 'validated'); + $dataValidated = $data['validated'] ?? null; if (empty($existingUser)) { - $firstName = Hash::get($data, 'first_name'); - $lastName = Hash::get($data, 'last_name'); + $firstName = $data['first_name'] ?? null; + $lastName = $data['last_name'] ?? null; if (!empty($firstName) && !empty($lastName)) { $userData['first_name'] = $firstName; $userData['last_name'] = $lastName; } else { - $name = explode(' ', Hash::get($data, 'full_name')); + $name = explode(' ', $data['full_name'] ?? ''); $userData['first_name'] = Hash::get($name, 0); array_shift($name); $userData['last_name'] = implode(' ', $name); } - $userData['username'] = Hash::get($data, 'username'); - $username = Hash::get($userData, 'username'); + $userData['username'] = $data['username'] ?? null; + $username = $userData['username'] ?? null; if (empty($username)) { - $dataEmail = Hash::get($data, 'email'); + $dataEmail = $data['email'] ?? null; if (!empty($dataEmail)) { $email = explode('@', $dataEmail); $userData['username'] = Hash::get($email, 0); } else { - $firstName = Hash::get($userData, 'first_name'); - $lastName = Hash::get($userData, 'last_name'); + $firstName = $userData['first_name'] ?? null; + $lastName = $userData['last_name'] ?? null; $userData['username'] = strtolower($firstName . $lastName); - $userData['username'] = preg_replace('/[^A-Za-z0-9]/i', '', Hash::get($userData, 'username')); + $userData['username'] = preg_replace('/[^A-Za-z0-9]/i', '', $userData['username'] ?? null); } } - $userData['username'] = $this->generateUniqueUsername(Hash::get($userData, 'username')); + $userData['username'] = $this->generateUniqueUsername($userData['username'] ?? null); if ($useEmail) { - $userData['email'] = Hash::get($data, 'email'); + $userData['email'] = $data['email'] ?? null; if (empty($dataValidated)) { $accountData['active'] = false; } } $userData['password'] = $this->randomString(); - $userData['avatar'] = Hash::get($data, 'avatar'); + $userData['avatar'] = $data['avatar'] ?? null; $userData['validated'] = !empty($dataValidated); - $userData['tos_date'] = date("Y-m-d H:i:s"); - $userData['gender'] = Hash::get($data, 'gender'); + $userData['tos_date'] = date('Y-m-d H:i:s'); + $userData['gender'] = $data['gender'] ?? null; $userData['social_accounts'][] = $accountData; $user = $this->_table->newEntity($userData); @@ -227,8 +226,9 @@ protected function _populateUser($data, $existingUser, $useEmail, $validateEmail } $socialAccount = $this->_table->SocialAccounts->newEntity($accountData); //ensure provider is present in Entity - $socialAccount['provider'] = Hash::get($data, 'provider'); + $socialAccount['provider'] = $data['provider'] ?? null; $user['social_accounts'] = [$socialAccount]; + $user['role'] = Configure::read('Users.Registration.defaultRole') ?: 'user'; return $user; } @@ -247,7 +247,7 @@ public function generateUniqueUsername($username) ->where([$this->_table->aliasField($this->_username) => $username]) ->count(); if ($existingUsername > 0) { - $username = $username . $i; + $username .= $i; $i++; continue; } @@ -256,4 +256,51 @@ public function generateUniqueUsername($username) return $username; } + + /** + * Prepare a query to retrieve existing entity for social login + * + * @param \Cake\ORM\Query $query The base query. + * @param array $options Find options with email key. + * @return \Cake\ORM\Query + */ + public function findExistingForSocialLogin(\Cake\ORM\Query $query, array $options) + { + return $query->where([ + $this->_table->aliasField('email') => $options['email'], + ]); + } + + /** + * Extract the account data to insert/update + * + * @param array $data Social data. + * @throws \Exception + * @return array + */ + protected function extractAccountData(array $data) + { + $accountData = []; + $accountData['username'] = $data['username'] ?? null; + $accountData['reference'] = $data['id'] ?? null; + $accountData['avatar'] = $data['avatar'] ?? null; + $accountData['link'] = $data['link'] ?? null; + + if ($accountData['avatar'] ?? null) { + $accountData['avatar'] = str_replace('normal', 'square', $accountData['avatar']); + } + $accountData['description'] = $data['bio'] ?? null; + $accountData['token'] = $data['credentials']['token'] ?? null; + $accountData['token_secret'] = $data['credentials']['secret'] ?? null; + $expires = $data['credentials']['expires'] ?? null; + if (!empty($expires)) { + $expiresTime = new DateTime(); + $accountData['token_expires'] = $expiresTime->setTimestamp($expires)->format('Y-m-d H:i:s'); + } else { + $accountData['token_expires'] = null; + } + $accountData['data'] = serialize($data['raw'] ?? null); + + return $accountData; + } } diff --git a/src/Model/Entity/SocialAccount.php b/src/Model/Entity/SocialAccount.php index 9c6f421ec..c8429655f 100644 --- a/src/Model/Entity/SocialAccount.php +++ b/src/Model/Entity/SocialAccount.php @@ -1,11 +1,13 @@ set('tos_date', Time::now()); + if ((bool)$tos) { + $this->set('tos_date', FrozenTime::now()); } return $tos; @@ -88,7 +99,7 @@ public function hashPassword($password) { $PasswordHasher = $this->getPasswordHasher(); - return $PasswordHasher->hash($password); + return $PasswordHasher->hash((string)$password); } /** @@ -100,10 +111,10 @@ public function getPasswordHasher() { $passwordHasher = Configure::read('Users.passwordHasher'); if (!class_exists($passwordHasher)) { - $passwordHasher = '\Cake\Auth\DefaultPasswordHasher'; + $passwordHasher = \Cake\Auth\DefaultPasswordHasher::class; } - return new $passwordHasher; + return new $passwordHasher(); } /** @@ -131,7 +142,7 @@ public function tokenExpired() return true; } - return new Time($this->token_expires) < Time::now(); + return new FrozenTime($this->token_expires) < FrozenTime::now(); } /** @@ -142,21 +153,40 @@ public function tokenExpired() protected function _getAvatar() { $avatar = null; - if (!empty($this->_properties['social_accounts'][0])) { - $avatar = $this->_properties['social_accounts'][0]['avatar']; + if (isset($this->social_accounts[0])) { + $avatar = $this->social_accounts[0]['avatar']; } return $avatar; } + /** + * Return the u2f_registration inside additional_data + * + * @return object|null + */ + protected function _getU2fRegistration() + { + if (is_string($this->additional_data)) { + $this->additional_data = json_decode($this->additional_data, true); + } + if (!isset($this->additional_data['u2f_registration'])) { + return null; + } + $object = (object)$this->additional_data['u2f_registration']; + + return $object->keyHandle !== null ? $object : null; + } + /** * Generate token_expires and token in a user + * * @param int $tokenExpiration seconds to expire the token from Now * @return void */ public function updateToken($tokenExpiration = 0) { - $expiration = new Time('now'); + $expiration = new FrozenTime('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 1363dd399..1d1578c19 100644 --- a/src/Model/Table/SocialAccountsTable.php +++ b/src/Model/Table/SocialAccountsTable.php @@ -1,17 +1,18 @@ add('id', 'valid', ['rule' => 'uuid']) - ->allowEmpty('id', 'create'); + ->allowEmptyString('id', null, 'create'); $validator ->requirePresence('provider', 'create') - ->notEmpty('provider'); + ->notEmptyString('provider'); $validator - ->allowEmpty('username'); + ->allowEmptyString('username'); $validator ->requirePresence('reference', 'create') - ->notEmpty('reference'); + ->notEmptyString('reference'); $validator ->requirePresence('link', 'create') - ->notEmpty('reference'); + ->notEmptyString('reference'); $validator - ->allowEmpty('avatar'); + ->allowEmptyString('avatar'); $validator - ->allowEmpty('description'); + ->allowEmptyString('description'); $validator ->requirePresence('token', 'create') - ->notEmpty('token'); + ->notEmptyString('token'); $validator - ->allowEmpty('token_secret'); + ->allowEmptyString('token_secret'); $validator ->add('token_expires', 'valid', ['rule' => 'datetime']) - ->allowEmpty('token_expires'); + ->allowEmptyString('token_expires'); $validator ->add('active', 'valid', ['rule' => 'boolean']) ->requirePresence('active', 'create') - ->notEmpty('active'); + ->notBlank('active'); $validator ->requirePresence('data', 'create') - ->notEmpty('data'); + ->notBlank('data'); return $validator; } @@ -108,10 +110,10 @@ public function validationDefault(Validator $validator) * Returns a rules checker object that will be used for validating * application integrity. * - * @param RulesChecker $rules The rules object to be modified. - * @return RulesChecker + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker */ - public function buildRules(RulesChecker $rules) + public function buildRules(RulesChecker $rules): RulesChecker { $rules->add($rules->existsIn(['user_id'], 'Users')); @@ -121,13 +123,13 @@ public function buildRules(RulesChecker $rules) /** * Finder for active social accounts * - * @param Query $query query + * @param \Cake\ORM\Query $query query * @return \Cake\ORM\Query */ public function findActive(Query $query) { return $query->where([ - $this->aliasField('active') => true + $this->aliasField('active') => true, ]); } } diff --git a/src/Model/Table/UsersTable.php b/src/Model/Table/UsersTable.php index d8e1919f7..4e6954348 100644 --- a/src/Model/Table/UsersTable.php +++ b/src/Model/Table/UsersTable.php @@ -1,33 +1,48 @@ setColumnType('additional_data', 'json'); + + return parent::_initializeSchema($schema); + } + /** * Initialize method * * @param array $config The configuration for the Table. * @return void */ - public function initialize(array $config) + public function initialize(array $config): void { parent::initialize($config); @@ -55,31 +83,32 @@ public function initialize(array $config) $this->addBehavior('CakeDC/Users.Social'); $this->addBehavior('CakeDC/Users.LinkSocial'); $this->addBehavior('CakeDC/Users.AuthFinder'); - $this->hasMany('SocialAccounts', [ - 'foreignKey' => 'user_id', - 'className' => 'CakeDC/Users.SocialAccounts' - ]); + $this->hasMany('SocialAccounts')->setForeignKey('user_id')->setClassName('CakeDC/Users.SocialAccounts'); } /** * Adds some rules for password confirm - * @param Validator $validator Cake validator object. - * @return Validator + * + * @param \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator */ public function validationPasswordConfirm(Validator $validator) { $validator ->requirePresence('password_confirm', 'create') - ->notEmpty('password_confirm'); + ->notBlank('password_confirm'); $validator ->requirePresence('password', 'create') - ->notEmpty('password') + ->notBlank('password') ->add('password', [ 'password_confirm_check' => [ 'rule' => ['compareWith', 'password_confirm'], - 'message' => __d('CakeDC/Users', 'Your password does not match your confirm password. Please try again'), - 'allowEmpty' => false + 'message' => __d( + 'cake_d_c/users', + 'Your password does not match your confirm password. Please try again' + ), + 'allowEmpty' => false, ]]); return $validator; @@ -88,13 +117,13 @@ public function validationPasswordConfirm(Validator $validator) /** * Adds rules for current password * - * @param Validator $validator Cake validator object. - * @return Validator + * @param \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator */ public function validationCurrentPassword(Validator $validator) { $validator - ->notEmpty('current_password'); + ->notBlank('current_password'); return $validator; } @@ -102,81 +131,80 @@ public function validationCurrentPassword(Validator $validator) /** * Default validation rules. * - * @param Validator $validator Validator instance. - * @return Validator + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator */ - public function validationDefault(Validator $validator) + public function validationDefault(Validator $validator): Validator { $validator - ->allowEmpty('id', 'create'); + ->allowEmptyString('id', null, 'create'); $validator ->requirePresence('username', 'create') - ->notEmpty('username'); + ->notEmptyString('username'); $validator ->requirePresence('password', 'create') - ->notEmpty('password'); + ->notEmptyString('password'); $validator - ->allowEmpty('first_name'); + ->allowEmptyString('first_name'); $validator - ->allowEmpty('last_name'); + ->allowEmptyString('last_name'); $validator - ->allowEmpty('token'); + ->allowEmptyString('token'); $validator ->add('token_expires', 'valid', ['rule' => 'datetime']) - ->allowEmpty('token_expires'); + ->allowEmptyDateTime('token_expires'); $validator - ->allowEmpty('api_token'); + ->allowEmptyString('api_token'); $validator ->add('activation_date', 'valid', ['rule' => 'datetime']) - ->allowEmpty('activation_date'); + ->allowEmptyDateTime('activation_date'); $validator ->add('tos_date', 'valid', ['rule' => 'datetime']) - ->allowEmpty('tos_date'); + ->allowEmptyDateTime('tos_date'); return $validator; } /** * Wrapper for all validation rules for register - * @param Validator $validator Cake validator object. * - * @return Validator + * @param \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator */ public function validationRegister(Validator $validator) { $validator = $this->validationDefault($validator); - $validator = $this->validationPasswordConfirm($validator); - return $validator; + return $this->validationPasswordConfirm($validator); } /** * Returns a rules checker object that will be used for validating * application integrity. * - * @param RulesChecker $rules The rules object to be modified. - * @return RulesChecker + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker */ - public function buildRules(RulesChecker $rules) + public function buildRules(RulesChecker $rules): RulesChecker { $rules->add($rules->isUnique(['username']), '_isUnique', [ 'errorField' => 'username', - 'message' => __d('CakeDC/Users', 'Username already exists') + 'message' => __d('cake_d_c/users', 'Username already exists'), ]); if ($this->isValidateEmail) { $rules->add($rules->isUnique(['email']), '_isUnique', [ 'errorField' => 'email', - 'message' => __d('CakeDC/Users', 'Email already exists') + 'message' => __d('cake_d_c/users', 'Email already exists'), ]); } diff --git a/src/Plugin.php b/src/Plugin.php new file mode 100644 index 000000000..b39c99565 --- /dev/null +++ b/src/Plugin.php @@ -0,0 +1,59 @@ +getLoader('Users.middlewareQueueLoader'); + + return $loader( + $middlewareQueue, + new AuthenticationServiceProvider(), + new AuthorizationServiceProvider() + ); + } +} diff --git a/src/Provider/AuthenticationServiceProvider.php b/src/Provider/AuthenticationServiceProvider.php new file mode 100644 index 000000000..1c4dcc3e4 --- /dev/null +++ b/src/Provider/AuthenticationServiceProvider.php @@ -0,0 +1,39 @@ +loadService($request, $key); + } +} diff --git a/src/Provider/AuthorizationServiceProvider.php b/src/Provider/AuthorizationServiceProvider.php new file mode 100644 index 000000000..8c727f41a --- /dev/null +++ b/src/Provider/AuthorizationServiceProvider.php @@ -0,0 +1,39 @@ +loadService($request, $key); + } +} diff --git a/src/Provider/ServiceProviderLoaderTrait.php b/src/Provider/ServiceProviderLoaderTrait.php new file mode 100644 index 000000000..ccfc0183c --- /dev/null +++ b/src/Provider/ServiceProviderLoaderTrait.php @@ -0,0 +1,55 @@ +getLoader($loaderKey); + + return $serviceLoader($request); + } + + /** + * Get the loader callable + * + * @param string $loaderKey loader configuration key + * @return callable + */ + protected function getLoader($loaderKey) + { + $serviceLoader = Configure::read($loaderKey); + if (is_string($serviceLoader)) { + $serviceLoader = new $serviceLoader(); + } + + return $serviceLoader; + } +} diff --git a/src/Shell/UsersShell.php b/src/Shell/UsersShell.php index 292dc71b3..bd38e2e44 100644 --- a/src/Shell/UsersShell.php +++ b/src/Shell/UsersShell.php @@ -1,32 +1,32 @@ Users = $this->loadModel(Configure::read('Users.table')); - } - - /** - * - * @return ConsoleOptionParser - */ - public function getOptionParser() + public function getOptionParser(): ConsoleOptionParser { $parser = parent::getOptionParser(); - $parser->setDescription(__d('CakeDC/Users', 'Utilities for CakeDC Users Plugin')) + $parser->setDescription(__d('cake_d_c/users', 'Utilities for CakeDC Users Plugin')) ->addSubcommand('activateUser', [ - 'help' => __d('CakeDC/Users', 'Activate an specific user') + 'help' => __d('cake_d_c/users', 'Activate an specific user'), ]) ->addSubcommand('addSuperuser', [ - 'help' => __d('CakeDC/Users', 'Add a new superadmin user for testing purposes') + 'help' => __d('cake_d_c/users', 'Add a new superadmin user for testing purposes'), ]) ->addSubcommand('addUser', [ - 'help' => __d('CakeDC/Users', 'Add a new user') + 'help' => __d('cake_d_c/users', 'Add a new user'), ]) ->addSubcommand('changeRole', [ - 'help' => __d('CakeDC/Users', 'Change the role for an specific user') + '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('CakeDC/Users', 'Deactivate an specific user') + 'help' => __d('cake_d_c/users', 'Deactivate an specific user'), ]) ->addSubcommand('deleteUser', [ - 'help' => __d('CakeDC/Users', 'Delete an specific user') + 'help' => __d('cake_d_c/users', 'Delete an specific user'), ]) ->addSubcommand('passwordEmail', [ - 'help' => __d('CakeDC/Users', 'Reset the password via email') + 'help' => __d('cake_d_c/users', 'Reset the password via email'), ]) ->addSubcommand('resetAllPasswords', [ - 'help' => __d('CakeDC/Users', 'Reset the password for all users') + 'help' => __d('cake_d_c/users', 'Reset the password for all users'), ]) ->addSubcommand('resetPassword', [ - 'help' => __d('CakeDC/Users', 'Reset the password for an specific user') + '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'] + '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 * @@ -113,7 +115,7 @@ public function addSuperuser() $this->_createUser([ 'username' => 'superadmin', 'role' => 'superuser', - 'is_superuser' => true + 'is_superuser' => true, ]); } @@ -130,12 +132,12 @@ public function resetAllPasswords() { $password = Hash::get($this->args, 0); if (empty($password)) { - $this->abort(__d('CakeDC/Users', 'Please enter a 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('CakeDC/Users', 'Password changed for all users')); - $this->out(__d('CakeDC/Users', 'New password: {0}', $password)); + $this->out(__d('cake_d_c/users', 'Password changed for all users')); + $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); } /** @@ -153,17 +155,17 @@ public function resetPassword() $username = Hash::get($this->args, 0); $password = Hash::get($this->args, 1); if (empty($username)) { - $this->abort(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } if (empty($password)) { - $this->abort(__d('CakeDC/Users', 'Please enter a password.')); + $this->abort(__d('cake_d_c/users', 'Please enter a password.')); } $data = [ - 'password' => $password + 'password' => $password, ]; $this->_updateUser($username, $data); - $this->out(__d('CakeDC/Users', 'Password changed for user: {0}', $username)); - $this->out(__d('CakeDC/Users', 'New password: {0}', $password)); + $this->out(__d('cake_d_c/users', 'Password changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); } /** @@ -181,17 +183,51 @@ public function changeRole() $username = Hash::get($this->args, 0); $role = Hash::get($this->args, 1); if (empty($username)) { - $this->abort(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } if (empty($role)) { - $this->abort(__d('CakeDC/Users', 'Please enter a 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 = [ - 'role' => $role + 'api_token' => $token, ]; $savedUser = $this->_updateUser($username, $data); - $this->out(__d('CakeDC/Users', 'Role changed for user: {0}', $username)); - $this->out(__d('CakeDC/Users', 'New role: {0}', $savedUser->role)); + 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)); } /** @@ -206,7 +242,7 @@ public function changeRole() public function activateUser() { $user = $this->_changeUserActive(true); - $this->out(__d('CakeDC/Users', 'User was activated: {0}', $user->username)); + $this->out(__d('cake_d_c/users', 'User was activated: {0}', $user->username)); } /** @@ -221,7 +257,7 @@ public function activateUser() public function deactivateUser() { $user = $this->_changeUserActive(false); - $this->out(__d('CakeDC/Users', 'User was de-activated: {0}', $user->username)); + $this->out(__d('cake_d_c/users', 'User was de-activated: {0}', $user->username)); } /** @@ -233,7 +269,7 @@ public function passwordEmail() { $reference = Hash::get($this->args, 0); if (empty($reference)) { - $this->abort(__d('CakeDC/Users', 'Please enter a username or email.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username or email.')); } $resetUser = $this->Users->resetToken($reference, [ 'expiration' => Configure::read('Users.Token.expiration'), @@ -241,10 +277,16 @@ public function passwordEmail() 'sendEmail' => true, ]); if ($resetUser) { - $msg = __d('CakeDC/Users', 'Please ask the user to check the email to continue with password reset process'); + $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('CakeDC/Users', 'The password token could not be generated. Please try again'); + $msg = __d( + 'cake_d_c/users', + 'The password token could not be generated. Please try again' + ); $this->abort($msg); } } @@ -259,10 +301,10 @@ protected function _changeUserActive($active) { $username = Hash::get($this->args, 0); if (empty($username)) { - $this->abort(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } $data = [ - 'active' => $active + 'active' => $active, ]; return $this->_updateUser($username, $data); @@ -279,8 +321,8 @@ protected function _createUser($template) if (!empty($this->params['username'])) { $username = $this->params['username']; } else { - $username = !empty($template['username']) ? - $template['username'] : $this->_generateRandomUsername(); + $username = empty($template['username']) ? + $this->_generateRandomUsername() : $template['username']; } $password = (empty($this->params['password']) ? @@ -303,22 +345,22 @@ protected function _createUser($template) $userEntity->role = $role; $savedUser = $this->Users->save($userEntity); - if (!empty($savedUser)) { + if (is_object($savedUser)) { if ($savedUser->is_superuser) { - $this->out(__d('CakeDC/Users', 'Superuser added:')); + $this->out(__d('cake_d_c/users', 'Superuser added:')); } else { - $this->out(__d('CakeDC/Users', 'User added:')); + $this->out(__d('cake_d_c/users', 'User added:')); } - $this->out(__d('CakeDC/Users', 'Id: {0}', $savedUser->id)); - $this->out(__d('CakeDC/Users', 'Username: {0}', $savedUser->username)); - $this->out(__d('CakeDC/Users', 'Email: {0}', $savedUser->email)); - $this->out(__d('CakeDC/Users', 'Role: {0}', $savedUser->role)); - $this->out(__d('CakeDC/Users', 'Password: {0}', $password)); + $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('CakeDC/Users', 'User could not be added:')); + $this->out(__d('cake_d_c/users', 'User could not be added:')); collection($userEntity->getErrors())->each(function ($error, $field) { - $this->out(__d('CakeDC/Users', 'Field: {0} Error: {1}', $field, implode(',', $error))); + $this->out(__d('cake_d_c/users', 'Field: {0} Error: {1}', $field, implode(',', $error))); }); } } @@ -328,23 +370,25 @@ protected function _createUser($template) * * @param string $username username * @param array $data data - * @return bool + * @return \CakeDC\Users\Model\Entity\User|bool */ protected function _updateUser($username, $data) { $user = $this->Users->find()->where(['username' => $username])->first(); - if (empty($user)) { - $this->abort(__d('CakeDC/Users', 'The user was not found.')); + 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; }); - $savedUser = $this->Users->save($user); - return $savedUser; + return $this->Users->save($user); } /** @@ -356,17 +400,20 @@ public function deleteUser() { $username = Hash::get($this->args, 0); if (empty($username)) { - $this->abort(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } - $user = $this->Users->find()->where(['username' => $username])->first(); + /** + * @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('CakeDC/Users', 'The user {0} was not deleted. Please try again', $username)); + $this->abort(__d('cake_d_c/users', 'The user {0} was not deleted. Please try again', $username)); } - $this->out(__d('CakeDC/Users', 'The user {0} was deleted successfully', $username)); + $this->out(__d('cake_d_c/users', 'The user {0} was deleted successfully', $username)); } /** @@ -397,8 +444,9 @@ protected function _generateRandomUsername() */ protected function _generatedHashedPassword($password) { - return (new User)->hashPassword($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/Template/Email/html/reset_password.ctp b/src/Template/Email/html/reset_password.ctp deleted file mode 100644 index f6055a1d4..000000000 --- a/src/Template/Email/html/reset_password.ctp +++ /dev/null @@ -1,35 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'resetPassword', - isset($token) ? $token : '' -]; -?> -

- , -

-

- Html->link(__d('CakeDC/Users', 'Reset your password here'), $activationUrl) ?> -

-

-Url->build($activationUrl) -) ?> -

-

- , -

diff --git a/src/Template/Email/html/social_account_validation.ctp b/src/Template/Email/html/social_account_validation.ctp deleted file mode 100644 index acef3fbc5..000000000 --- a/src/Template/Email/html/social_account_validation.ctp +++ /dev/null @@ -1,40 +0,0 @@ - - -

- , -

-

- true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'SocialAccounts', - 'action' => 'validateAccount', - $socialAccount['provider'], - $socialAccount['reference'], - $socialAccount['token'], - ]; - echo $this->Html->link($text, $activationUrl); - ?> -

-

-Url->build($activationUrl) -) ?> -

-

- , -

diff --git a/src/Template/Email/html/validation.ctp b/src/Template/Email/html/validation.ctp deleted file mode 100644 index 8a1840468..000000000 --- a/src/Template/Email/html/validation.ctp +++ /dev/null @@ -1,35 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'validateEmail', - isset($token) ? $token : '' -]; -?> -

- , -

-

- Html->link(__d('CakeDC/Users', 'Activate your account here'), $activationUrl) ?> -

-

-Url->build($activationUrl) -) ?> -

-

- , -

diff --git a/src/Template/Email/text/reset_password.ctp b/src/Template/Email/text/reset_password.ctp deleted file mode 100644 index 96f001565..000000000 --- a/src/Template/Email/text/reset_password.ctp +++ /dev/null @@ -1,29 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'resetPassword', - isset($token) ? $token : '' -]; -?> -, - -Url->build($activationUrl) -) ?> - -, - diff --git a/src/Template/Email/text/social_account_validation.ctp b/src/Template/Email/text/social_account_validation.ctp deleted file mode 100644 index 87e2d6813..000000000 --- a/src/Template/Email/text/social_account_validation.ctp +++ /dev/null @@ -1,31 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'SocialAccounts', - 'action' => 'validateAccount', - $socialAccount['provider'], - $socialAccount['reference'], - $socialAccount['token'], -]; -?> -, - -Url->build($activationUrl) -) ?> - -, - diff --git a/src/Template/Email/text/validation.ctp b/src/Template/Email/text/validation.ctp deleted file mode 100644 index ecf9d96a8..000000000 --- a/src/Template/Email/text/validation.ctp +++ /dev/null @@ -1,29 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'validateEmail', - isset($token) ? $token : '' -]; -?> -, - -Url->build($activationUrl) -) ?> - -, - diff --git a/src/Template/Users/index.ctp b/src/Template/Users/index.ctp deleted file mode 100644 index 6c9e49871..000000000 --- a/src/Template/Users/index.ctp +++ /dev/null @@ -1,55 +0,0 @@ - -
-

-
    -
  • Html->link(__d('CakeDC/Users', 'New {0}', $tableAlias), ['action' => 'add']) ?>
  • -
-
-
- - - - - - - - - - - - - - - - - - - - - - -
Paginator->sort('username', __d('CakeDC/Users', 'Username')) ?>Paginator->sort('email', __d('CakeDC/Users', 'Email')) ?>Paginator->sort('first_name', __d('CakeDC/Users', 'First name')) ?>Paginator->sort('last_name', __d('CakeDC/Users', 'Last name')) ?>
username) ?>email) ?>first_name) ?>last_name) ?> - Html->link(__d('CakeDC/Users', 'View'), ['action' => 'view', $user->id]) ?> - Html->link(__d('CakeDC/Users', 'Change password'), ['action' => 'changePassword', $user->id]) ?> - Html->link(__d('CakeDC/Users', 'Edit'), ['action' => 'edit', $user->id]) ?> - Form->postLink(__d('CakeDC/Users', 'Delete'), ['action' => 'delete', $user->id], ['confirm' => __d('CakeDC/Users', 'Are you sure you want to delete # {0}?', $user->id)]) ?> -
-
-
    - Paginator->prev('< ' . __d('CakeDC/Users', 'previous')) ?> - Paginator->numbers() ?> - Paginator->next(__d('CakeDC/Users', 'next') . ' >') ?> -
-

Paginator->counter() ?>

-
-
diff --git a/src/Template/Users/request_reset_password.ctp b/src/Template/Users/request_reset_password.ctp deleted file mode 100644 index e406ff573..000000000 --- a/src/Template/Users/request_reset_password.ctp +++ /dev/null @@ -1,10 +0,0 @@ -
- Flash->render('auth') ?> - Form->create('User') ?> -
- - Form->control('reference') ?> -
- Form->button(__d('CakeDC/Users', 'Submit')); ?> - Form->end() ?> -
diff --git a/src/Template/Users/social_email.ctp b/src/Template/Users/social_email.ctp deleted file mode 100644 index b0a272b6d..000000000 --- a/src/Template/Users/social_email.ctp +++ /dev/null @@ -1,21 +0,0 @@ - -
- Flash->render() ?> - Form->create('User') ?> -
- - Form->control('email') ?> -
- Form->button(__d('CakeDC/Users', 'Submit')); ?> - Form->end() ?> -
diff --git a/src/Traits/RandomStringTrait.php b/src/Traits/RandomStringTrait.php index 668a0c828..6caffad7b 100644 --- a/src/Traits/RandomStringTrait.php +++ b/src/Traits/RandomStringTrait.php @@ -1,11 +1,13 @@ $prefix, 'plugin' => $plugin, 'controller' => $controller, 'action' => $action]; + } + + /** + * Check if the action is the one from a request + * + * @param string $action users action + * @param \Cake\Http\ServerRequest $request the request + * @return bool + */ + public static function checkActionOnRequest($action, ServerRequest $request) + { + $route = static::actionParams($action); + foreach ($route as $param => $value) { + if ($request->getParam($param, null) !== $value) { + return false; + } + } + + return true; + } + + /** + * Setup needed config urls but without overwriting + * + * @return void + */ + public static function setupConfigUrls() + { + $urls = self::getDefaultConfigUrls(); + foreach ($urls as $configKey => $url) { + if (!Configure::check($configKey)) { + Configure::write($configKey, $url); + } + } + } + + /** + * Get a list of default config urls using static::actionUrl method for users url. + * + * @return array + */ + private static function getDefaultConfigUrls() + { + $loginAction = static::actionUrl('login'); + + return [ + 'Users.Profile.route' => static::actionUrl('profile'), + 'OneTimePasswordAuthenticator.verifyAction' => static::actionUrl('verify'), + 'U2f.startAction' => static::actionUrl('u2f'), + 'Webauthn2fa.startAction' => static::actionUrl('webauthn2fa'), + 'Auth.AuthenticationComponent.loginAction' => $loginAction, + 'Auth.AuthenticationComponent.logoutRedirect' => $loginAction, + 'Auth.Authenticators.Form.loginUrl' => $loginAction, + 'Auth.Authenticators.Cookie.loginUrl' => $loginAction, + 'Auth.Authenticators.SocialPendingEmail.loginUrl' => $loginAction, + 'Auth.AuthorizationMiddleware.unauthorizedHandler.url' => $loginAction, + 'OAuth.path' => static::actionParams('socialLogin'), + ]; + } +} diff --git a/src/View/Helper/AuthLinkHelper.php b/src/View/Helper/AuthLinkHelper.php index b9b8e0c97..06274ae37 100644 --- a/src/View/Helper/AuthLinkHelper.php +++ b/src/View/Helper/AuthLinkHelper.php @@ -1,28 +1,31 @@ isAuthorized($url)) { - return Hash::get($options, 'before') . + return ($options['before'] ?? '') . parent::link($title, $url, $linkOptions) . - Hash::get($options, 'after'); + ($options['after'] ?? ''); } - return false; + return ''; } /** - * Returns true if the target url is authorized for the logged in user + * Wrapper for FormHelper.postLink. + * Write the link only if user is authorized. * - * @param string|array|null $url url that the user is making request. - * @return bool + * @param string $title Link's title + * @param string|array $url Link's url + * @param array $options Link's options + * @return string Link as a string. */ - public function isAuthorized($url = null) + public function postLink($title, $url = null, array $options = []): string { - $event = new Event(UsersAuthComponent::EVENT_IS_AUTHORIZED, $this, ['url' => $url]); - $result = EventManager::instance()->dispatch($event); + return $this->isAuthorized($url) + ? $this->Form->postLink($title, $url, $options) + : ''; + } - return $result->result; + /** + * Get the current request + * + * @return \Cake\Http\ServerRequest + */ + public function getRequest() + { + return $this->getView()->getRequest(); } } diff --git a/src/View/Helper/UserHelper.php b/src/View/Helper/UserHelper.php index c1f4cb7b7..00d32bd93 100644 --- a/src/View/Helper/UserHelper.php +++ b/src/View/Helper/UserHelper.php @@ -1,25 +1,30 @@ Html->tag('i', '', [ 'class' => 'fa fa-' . strtolower($name), @@ -51,13 +56,17 @@ public function socialLogin($name, $options = []) if (isset($options['title'])) { $providerTitle = $options['title']; } else { - $providerTitle = Hash::get($options, 'label') . ' ' . Inflector::camelize($name); + $providerTitle = ($options['label'] ?? '') . ' ' . Inflector::camelize($name); } - $providerClass = 'btn btn-social btn-' . strtolower($name) . ((Hash::get($options, 'class')) ? ' ' . Hash::get($options, 'class') : ''); + $providerClass = 'btn btn-social btn-' . strtolower($name); + $optionClass = $options['class'] ?? null; + if ($optionClass) { + $providerClass .= " $optionClass"; + } return $this->Html->link($icon . $providerTitle, "/auth/$name", [ - 'escape' => false, 'class' => $providerClass + 'escape' => false, 'class' => $providerClass, ]); } @@ -75,7 +84,8 @@ public function socialLoginList(array $providerOptions = []) $outProviders = []; $providers = Configure::read('OAuth.providers'); foreach ($providers as $provider => $options) { - if (!empty($options['options']['redirectUri']) && + if ( + !empty($options['options']['redirectUri']) && !empty($options['options']['clientId']) && !empty($options['options']['clientSecret']) ) { @@ -99,30 +109,40 @@ public function socialLoginList(array $providerOptions = []) */ public function logout($message = null, $options = []) { - return $this->AuthLink->link(empty($message) ? __d('CakeDC/Users', 'Logout') : $message, [ - 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout' - ], $options); + $url = UsersUrl::actionUrl('logout'); + $title = empty($message) ? __d('cake_d_c/users', 'Logout') : $message; + + return $this->AuthLink->link($title, $url, $options); } /** * Welcome display - * @return mixed + * + * @return string|null */ public function welcome() { - $userId = $this->request->getSession()->read('Auth.User.id'); - if (empty($userId)) { - return; + $identity = $this->getView()->getRequest()->getAttribute('identity'); + if (!$identity) { + return null; } $profileUrl = Configure::read('Users.Profile.route'); - $label = __d('CakeDC/Users', 'Welcome, {0}', $this->AuthLink->link($this->request->getSession()->read('Auth.User.first_name') ?: $this->request->getSession()->read('Auth.User.username'), $profileUrl)); + $title = $identity['first_name'] ?? null; + $title = $title ?: ($identity['username'] ?? null); + $title = is_array($title) ? '-' : (string)$title; + $label = __d( + 'cake_d_c/users', + 'Welcome, {0}', + $this->AuthLink->link($title, $profileUrl) + ); return $this->Html->tag('span', $label, ['class' => 'welcome']); } /** * Add reCaptcha script + * * @return void */ public function addReCaptchaScript() @@ -134,19 +154,32 @@ public function addReCaptchaScript() /** * Add reCaptcha to the form + * * @return mixed */ public function addReCaptcha() { if (!Configure::read('Users.reCaptcha.key')) { - return $this->Html->tag('p', __d('CakeDC/Users', 'reCaptcha is not configured! Please configure Users.reCaptcha.key')); + return $this->Html->tag( + 'p', + __d( + 'cake_d_c/users', + 'reCaptcha is not configured! Please configure Users.reCaptcha.key' + ) + ); } $this->addReCaptchaScript(); - $this->Form->unlockField('g-recaptcha-response'); + try { + $this->Form->unlockField('g-recaptcha-response'); + } catch (\Exception $e) { + } return $this->Html->tag('div', '', [ 'class' => 'g-recaptcha', - 'data-sitekey' => Configure::read('Users.reCaptcha.key') + 'data-sitekey' => Configure::read('Users.reCaptcha.key'), + 'data-theme' => Configure::read('Users.reCaptcha.theme') ?: 'light', + 'data-size' => Configure::read('Users.reCaptcha.size') ?: 'normal', + 'data-tabindex' => Configure::read('Users.reCaptcha.tabindex') ?: '3', ]); } @@ -154,7 +187,6 @@ public function addReCaptcha() * Generate a link if the target url is authorized for the logged in user * * @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 $options Array with option data. @@ -170,49 +202,32 @@ public function link($title, $url = null, array $options = []) return $this->AuthLink->link($title, $url, $options); } - /** - * Returns true if the target url is authorized for the logged in user - * - * @deprecated Since 3.2.1. Use AuthLinkHelper::link() instead - * - * @param string|array|null $url url that the user is making request. - * @return bool - */ - public function isAuthorized($url = null) - { - trigger_error( - 'UserHelper::isAuthorized() deprecated since 3.2.1. Use AuthLinkHelper::isAuthorized() instead', - E_USER_DEPRECATED - ); - - return $this->AuthLink->isAuthorized($url); - } /** * Create links for all social providers enabled social link (connect) * * @param string $name Provider name in lowercase * @param array $provider Provider configuration * @param bool $isConnected User is connected with this provider - * * @return string */ public function socialConnectLink($name, $provider, $isConnected = false) { - $linkClass = 'btn btn-social btn-' . strtolower($name) . ((Hash::get($provider['options'], 'class')) ? ' ' . Hash::get($provider['options'], 'class') : ''); + $optionClass = $provider['options']['class'] ?? null; + $linkClass = 'btn btn-social btn-' . strtolower($name) . ($optionClass ? ' ' . $optionClass : ''); if ($isConnected) { - $title = __d('CakeDC/Users', 'Connected with {0}', Inflector::camelize($name)); + $title = __d('cake_d_c/users', 'Connected with {0}', Inflector::camelize($name)); return " $title"; } - $title = __d('CakeDC/Users', 'Connect with {0}', Inflector::camelize($name)); + $title = __d('cake_d_c/users', 'Connect with {0}', Inflector::camelize($name)); return $this->Html->link( " $title", "/link-social/$name", [ 'escape' => false, - 'class' => $linkClass + 'class' => $linkClass, ] ); } @@ -221,22 +236,25 @@ public function socialConnectLink($name, $provider, $isConnected = false) * Create links for all social providers enabled social link (connect) * * @param array $socialAccounts All social accounts connected by a user. - * * @return string */ public function socialConnectLinkList($socialAccounts = []) { if (!Configure::read('Users.Social.login')) { - return ""; + return ''; } - $html = ""; - $connectedProviders = array_map(function ($item) { - return strtolower($item->provider); - }, (array)$socialAccounts); + $html = ''; + $connectedProviders = array_map( + function ($item) { + return strtolower($item->provider); + }, + (array)$socialAccounts + ); $providers = Configure::read('OAuth.providers'); foreach ($providers as $name => $provider) { - if (!empty($provider['options']['callbackLinkSocialUri']) && + if ( + !empty($provider['options']['callbackLinkSocialUri']) && !empty($provider['options']['linkSocialUri']) && !empty($provider['options']['clientId']) && !empty($provider['options']['clientSecret']) diff --git a/src/Webauthn/AuthenticateAdapter.php b/src/Webauthn/AuthenticateAdapter.php new file mode 100644 index 000000000..592c9590a --- /dev/null +++ b/src/Webauthn/AuthenticateAdapter.php @@ -0,0 +1,68 @@ +getUserEntity(); + $allowed = array_map(function (PublicKeyCredentialSource $credential) { + return $credential->getPublicKeyCredentialDescriptor(); + }, $this->repository->findAllForUserEntity($userEntity)); + + $options = $this->server->generatePublicKeyCredentialRequestOptions( + PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value + $allowed + ); + $this->request->getSession()->write( + 'Webauthn2fa.authenticateOptions', + $options + ); + + return $options; + } + + /** + * Verify the registration response + * + * @return \Webauthn\PublicKeyCredentialSource + */ + public function verifyResponse(): \Webauthn\PublicKeyCredentialSource + { + $options = $this->request->getSession()->read('Webauthn2fa.authenticateOptions'); + + return $this->loadAndCheckAssertionResponse($options); + } + + /** + * @param \Webauthn\PublicKeyCredentialRequestOptions $options request options + * @return \Webauthn\PublicKeyCredentialSource + */ + protected function loadAndCheckAssertionResponse($options): PublicKeyCredentialSource + { + return $this->server->loadAndCheckAssertionResponse( + json_encode($this->request->getData()), + $options, + $this->getUserEntity(), + $this->request + ); + } +} diff --git a/src/Webauthn/BaseAdapter.php b/src/Webauthn/BaseAdapter.php new file mode 100644 index 000000000..4ee30b09c --- /dev/null +++ b/src/Webauthn/BaseAdapter.php @@ -0,0 +1,94 @@ +request = $request; + $rpEntity = new PublicKeyCredentialRpEntity( + Configure::read('Webauthn2fa.appName'), // The application name + Configure::read('Webauthn2fa.id') + ); + /** + * @var \Cake\ORM\Entity $userSession + */ + $userSession = $request->getSession()->read('Webauthn2fa.User'); + $usersTable = $usersTable ?? TableRegistry::getTableLocator() + ->get($userSession->getSource()); + $this->user = $usersTable->get($userSession->id); + $this->repository = new UserCredentialSourceRepository( + $this->user, + $usersTable + ); + + $this->server = new Server( + $rpEntity, + $this->repository + ); + } + + /** + * @return \Webauthn\PublicKeyCredentialUserEntity + */ + protected function getUserEntity(): PublicKeyCredentialUserEntity + { + $user = $this->getUser(); + + return new PublicKeyCredentialUserEntity( + $user->webauthn_username ?? $user->username, + (string)$user->id, + (string)$user->first_name + ); + } + + /** + * @return array|mixed|null + */ + public function getUser() + { + return $this->user; + } + + /** + * @return bool + */ + public function hasCredential(): bool + { + return (bool)$this->repository->findAllForUserEntity( + $this->getUserEntity() + ); + } +} diff --git a/src/Webauthn/RegisterAdapter.php b/src/Webauthn/RegisterAdapter.php new file mode 100644 index 000000000..c28b0504a --- /dev/null +++ b/src/Webauthn/RegisterAdapter.php @@ -0,0 +1,65 @@ +getUserEntity(); + $options = $this->server->generatePublicKeyCredentialCreationOptions( + $userEntity, + PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, + [] + ); + $this->request->getSession()->write('Webauthn2fa.registerOptions', $options); + $this->request->getSession()->write('Webauthn2fa.userEntity', $userEntity); + + return $options; + } + + /** + * Verify the registration response + * + * @return \Webauthn\PublicKeyCredentialSource + */ + public function verifyResponse(): \Webauthn\PublicKeyCredentialSource + { + $options = $this->request->getSession()->read('Webauthn2fa.registerOptions'); + $credential = $this->loadAndCheckAttestationResponse($options); + $this->repository->saveCredentialSource($credential); + + return $credential; + } + + /** + * @param \Webauthn\PublicKeyCredentialCreationOptions $options creation options + * @return \Webauthn\PublicKeyCredentialSource + */ + protected function loadAndCheckAttestationResponse($options): \Webauthn\PublicKeyCredentialSource + { + $credential = $this->server->loadAndCheckAttestationResponse( + json_encode($this->request->getData()), + $options, + $this->request + ); + + return $credential; + } +} diff --git a/src/Webauthn/Repository/UserCredentialSourceRepository.php b/src/Webauthn/Repository/UserCredentialSourceRepository.php new file mode 100644 index 000000000..ecda3cbb0 --- /dev/null +++ b/src/Webauthn/Repository/UserCredentialSourceRepository.php @@ -0,0 +1,77 @@ +user = $user; + $this->usersTable = $usersTable; + } + + /** + * @param string $publicKeyCredentialId Public key credential id + * @return \Webauthn\PublicKeyCredentialSource|null + */ + public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource + { + $encodedId = Base64Url::encode($publicKeyCredentialId); + $credential = $this->user['additional_data']['webauthn_credentials'][$encodedId] ?? null; + + return $credential + ? PublicKeyCredentialSource::createFromArray($credential) + : null; + } + + /** + * @inheritDoc + */ + public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array + { + if ($publicKeyCredentialUserEntity->getId() != $this->user->id) { + return []; + } + $credentials = $this->user['additional_data']['webauthn_credentials'] ?? []; + $list = []; + foreach ($credentials as $credential) { + $list[] = PublicKeyCredentialSource::createFromArray($credential); + } + + return $list; + } + + /** + * @param \Webauthn\PublicKeyCredentialSource $publicKeyCredentialSource Public key credential source + */ + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void + { + $credentials = $this->user['additional_data']['webauthn_credentials'] ?? []; + $id = Base64Url::encode($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; + $this->usersTable->saveOrFail($this->user); + } +} diff --git a/src/Template/Users/add.ctp b/templates/Users/add.php similarity index 59% rename from src/Template/Users/add.ctp rename to templates/Users/add.php index 751b2f3c6..2b795d20b 100644 --- a/src/Template/Users/add.ctp +++ b/templates/Users/add.php @@ -1,36 +1,36 @@
-

+

    -
  • Html->link(__d('CakeDC/Users', 'List Users'), ['action' => 'index']) ?>
  • +
  • Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ?>
Form->create(${$tableAlias}); ?>
- + Form->control('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->control('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->control('password', ['label' => __d('CakeDC/Users', 'Password')]); - echo $this->Form->control('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->control('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); + echo $this->Form->control('username', ['label' => __d('cake_d_c/users', 'Username')]); + echo $this->Form->control('email', ['label' => __d('cake_d_c/users', 'Email')]); + echo $this->Form->control('password', ['label' => __d('cake_d_c/users', 'Password')]); + echo $this->Form->control('first_name', ['label' => __d('cake_d_c/users', 'First name')]); + echo $this->Form->control('last_name', ['label' => __d('cake_d_c/users', 'Last name')]); echo $this->Form->control('active', [ 'type' => 'checkbox', - 'label' => __d('CakeDC/Users', 'Active') + 'label' => __d('cake_d_c/users', 'Active') ]); ?>
- Form->button(__d('CakeDC/Users', 'Submit')) ?> + Form->button(__d('cake_d_c/users', 'Submit')) ?> Form->end() ?>
diff --git a/src/Template/Users/change_password.ctp b/templates/Users/change_password.php similarity index 64% rename from src/Template/Users/change_password.ctp rename to templates/Users/change_password.php index 339f2fc65..7efba8f10 100644 --- a/src/Template/Users/change_password.ctp +++ b/templates/Users/change_password.php @@ -2,26 +2,26 @@ Flash->render('auth') ?> Form->create($user) ?>
- + Form->control('current_password', [ 'type' => 'password', 'required' => true, - 'label' => __d('CakeDC/Users', 'Current password')]); + 'label' => __d('cake_d_c/users', 'Current password')]); ?> Form->control('password', [ 'type' => 'password', 'required' => true, - 'label' => __d('CakeDC/Users', 'New password')]); + 'label' => __d('cake_d_c/users', 'New password')]); ?> Form->control('password_confirm', [ 'type' => 'password', 'required' => true, - 'label' => __d('CakeDC/Users', 'Confirm password')]); + 'label' => __d('cake_d_c/users', 'Confirm password')]); ?>
- Form->button(__d('CakeDC/Users', 'Submit')); ?> + Form->button(__d('cake_d_c/users', 'Submit')); ?> Form->end() ?> \ No newline at end of file diff --git a/src/Template/Users/edit.ctp b/templates/Users/edit.php similarity index 52% rename from src/Template/Users/edit.ctp rename to templates/Users/edit.php index 91794b822..551076252 100644 --- a/src/Template/Users/edit.ctp +++ b/templates/Users/edit.php @@ -1,73 +1,74 @@
-

+

  • Form->postLink( - __d('CakeDC/Users', 'Delete'), + __d('cake_d_c/users', 'Delete'), ['action' => 'delete', $Users->id], - ['confirm' => __d('CakeDC/Users', 'Are you sure you want to delete # {0}?', $Users->id)] + ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $Users->id)] ); ?>
  • -
  • Html->link(__d('CakeDC/Users', 'List Users'), ['action' => 'index']) ?>
  • +
  • Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ?>
Form->create($Users); ?>
- + Form->control('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->control('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->control('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->control('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); - echo $this->Form->control('token', ['label' => __d('CakeDC/Users', 'Token')]); + echo $this->Form->control('username', ['label' => __d('cake_d_c/users', 'Username')]); + echo $this->Form->control('email', ['label' => __d('cake_d_c/users', 'Email')]); + echo $this->Form->control('first_name', ['label' => __d('cake_d_c/users', 'First name')]); + echo $this->Form->control('last_name', ['label' => __d('cake_d_c/users', 'Last name')]); + echo $this->Form->control('token', ['label' => __d('cake_d_c/users', 'Token')]); echo $this->Form->control('token_expires', [ - 'label' => __d('CakeDC/Users', 'Token expires') + 'label' => __d('cake_d_c/users', 'Token expires') ]); echo $this->Form->control('api_token', [ - 'label' => __d('CakeDC/Users', 'API token') + 'label' => __d('cake_d_c/users', 'API token') ]); echo $this->Form->control('activation_date', [ - 'label' => __d('CakeDC/Users', 'Activation date') + 'label' => __d('cake_d_c/users', 'Activation date') ]); echo $this->Form->control('tos_date', [ - 'label' => __d('CakeDC/Users', 'TOS date') + 'label' => __d('cake_d_c/users', 'TOS date') ]); echo $this->Form->control('active', [ - 'label' => __d('CakeDC/Users', 'Active') + 'label' => __d('cake_d_c/users', 'Active') ]); ?>
- Form->button(__d('CakeDC/Users', 'Submit')) ?> + Form->button(__d('cake_d_c/users', 'Submit')) ?> Form->end() ?> - +
Reset Google Authenticator Form->postLink( - __d('CakeDC/Users', 'Reset Google Authenticator Token'), [ + __d('cake_d_c/users', 'Reset Google Authenticator Token'), [ 'plugin' => 'CakeDC/Users', 'controller' => 'Users', - 'action' => 'resetGoogleAuthenticator', $Users->id + 'action' => 'resetOneTimePasswordAuthenticator', $Users->id ], [ 'class' => 'btn btn-danger', 'confirm' => __d( - 'CakeDC/Users', + 'cake_d_c/users', 'Are you sure you want to reset token for user "{0}"?', $Users->username ) ]); diff --git a/templates/Users/index.php b/templates/Users/index.php new file mode 100644 index 000000000..273c5211c --- /dev/null +++ b/templates/Users/index.php @@ -0,0 +1,55 @@ + +
+

+
    +
  • Html->link(__d('cake_d_c/users', 'New {0}', $tableAlias), ['action' => 'add']) ?>
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
Paginator->sort('username', __d('cake_d_c/users', 'Username')) ?>Paginator->sort('email', __d('cake_d_c/users', 'Email')) ?>Paginator->sort('first_name', __d('cake_d_c/users', 'First name')) ?>Paginator->sort('last_name', __d('cake_d_c/users', 'Last name')) ?>
username) ?>email) ?>first_name) ?>last_name) ?> + Html->link(__d('cake_d_c/users', 'View'), ['action' => 'view', $user->id]) ?> + Html->link(__d('cake_d_c/users', 'Change password'), ['action' => 'changePassword', $user->id]) ?> + Html->link(__d('cake_d_c/users', 'Edit'), ['action' => 'edit', $user->id]) ?> + Form->postLink(__d('cake_d_c/users', 'Delete'), ['action' => 'delete', $user->id], ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $user->id)]) ?> +
+
+
    + Paginator->prev('< ' . __d('cake_d_c/users', 'previous')) ?> + Paginator->numbers() ?> + Paginator->next(__d('cake_d_c/users', 'next') . ' >') ?> +
+

Paginator->counter() ?>

+
+
diff --git a/src/Template/Users/login.ctp b/templates/Users/login.php similarity index 60% rename from src/Template/Users/login.ctp rename to templates/Users/login.php index 5e08f7cd6..99aaf4edd 100644 --- a/src/Template/Users/login.ctp +++ b/templates/Users/login.php @@ -1,11 +1,11 @@ Flash->render('auth') ?> Form->create() ?>
- - Form->control('username', ['label' => __d('CakeDC/Users', 'Username'), 'required' => true]) ?> - Form->control('password', ['label' => __d('CakeDC/Users', 'Password'), 'required' => true]) ?> + + Form->control('username', ['label' => __d('cake_d_c/users', 'Username'), 'required' => true]) ?> + Form->control('password', ['label' => __d('cake_d_c/users', 'Password'), 'required' => true]) ?> User->addReCaptcha(); @@ -26,7 +26,7 @@ if (Configure::read('Users.RememberMe.active')) { echo $this->Form->control(Configure::read('Users.Key.Data.rememberMe'), [ 'type' => 'checkbox', - 'label' => __d('CakeDC/Users', 'Remember me'), + 'label' => __d('cake_d_c/users', 'Remember me'), 'checked' => Configure::read('Users.RememberMe.checked') ]); } @@ -34,17 +34,17 @@ Html->link(__d('CakeDC/Users', 'Register'), ['action' => 'register']); + echo $this->Html->link(__d('cake_d_c/users', 'Register'), ['action' => 'register']); } if (Configure::read('Users.Email.required')) { if ($registrationActive) { echo ' | '; } - echo $this->Html->link(__d('CakeDC/Users', 'Reset Password'), ['action' => 'requestResetPassword']); + echo $this->Html->link(__d('cake_d_c/users', 'Reset Password'), ['action' => 'requestResetPassword']); } ?>
User->socialLoginList()); ?> - Form->button(__d('CakeDC/Users', 'Login')); ?> + Form->button(__d('cake_d_c/users', 'Login')); ?> Form->end() ?>
diff --git a/src/Template/Users/profile.ctp b/templates/Users/profile.php similarity index 67% rename from src/Template/Users/profile.ctp rename to templates/Users/profile.php index 8b59d9ee2..bd786ffc1 100644 --- a/src/Template/Users/profile.ctp +++ b/templates/Users/profile.php @@ -1,11 +1,11 @@ @@ -18,37 +18,37 @@ Html->tag( 'span', - __d('CakeDC/Users', '{0} {1}', $user->first_name, $user->last_name), + __d('cake_d_c/users', '{0} {1}', $user->first_name, $user->last_name), ['class' => 'full_name'] ) ?> - Html->link(__d('CakeDC/Users', 'Change Password'), ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'changePassword']); ?> + Html->link(__d('cake_d_c/users', 'Change Password'), ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'changePassword']); ?>
-
+

username) ?>

-
+

email) ?>

User->socialConnectLinkList($user->social_accounts) ?> social_accounts)): ?> -
+
- - - + + + social_accounts as $socialAccount): $escapedUsername = h($socialAccount->username); - $linkText = empty($escapedUsername) ? __d('CakeDC/Users', 'Link to {0}', h($socialAccount->provider)) : h($socialAccount->username) + $linkText = empty($escapedUsername) ? __d('cake_d_c/users', 'Link to {0}', h($socialAccount->provider)) : h($socialAccount->username) ?> + ) : '-' ?>
Form->create($user); ?>
- + Form->control('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->control('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->control('password', ['label' => __d('CakeDC/Users', 'Password')]); + echo $this->Form->control('username', ['label' => __d('cake_d_c/users', 'Username')]); + echo $this->Form->control('email', ['label' => __d('cake_d_c/users', 'Email')]); + echo $this->Form->control('password', ['label' => __d('cake_d_c/users', 'Password')]); echo $this->Form->control('password_confirm', [ + 'required' => true, 'type' => 'password', - 'label' => __d('CakeDC/Users', 'Confirm password') + 'label' => __d('cake_d_c/users', 'Confirm password') ]); - echo $this->Form->control('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->control('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); + echo $this->Form->control('first_name', ['label' => __d('cake_d_c/users', 'First name')]); + echo $this->Form->control('last_name', ['label' => __d('cake_d_c/users', 'Last name')]); if (Configure::read('Users.Tos.required')) { - echo $this->Form->control('tos', ['type' => 'checkbox', 'label' => __d('CakeDC/Users', 'Accept TOS conditions?'), 'required' => true]); + echo $this->Form->control('tos', ['type' => 'checkbox', 'label' => __d('cake_d_c/users', 'Accept TOS conditions?'), 'required' => true]); } if (Configure::read('Users.reCaptcha.registration')) { echo $this->User->addReCaptcha(); } ?>
- Form->button(__d('CakeDC/Users', 'Submit')) ?> + Form->button(__d('cake_d_c/users', 'Submit')) ?> Form->end() ?>
diff --git a/templates/Users/request_reset_password.php b/templates/Users/request_reset_password.php new file mode 100644 index 000000000..cde49a009 --- /dev/null +++ b/templates/Users/request_reset_password.php @@ -0,0 +1,25 @@ + +
+ Flash->render('auth') ?> + Form->create($user) ?> +
+ + Form->control('reference') ?> +
+ Form->button(__d('cake_d_c/users', 'Submit')); ?> + Form->end() ?> +
diff --git a/src/Template/Users/resend_token_validation.ctp b/templates/Users/resend_token_validation.php similarity index 61% rename from src/Template/Users/resend_token_validation.ctp rename to templates/Users/resend_token_validation.php index 2d2680fbe..2023e3c18 100644 --- a/src/Template/Users/resend_token_validation.ctp +++ b/templates/Users/resend_token_validation.php @@ -1,22 +1,22 @@
Form->create($user); ?>
- + Form->control('reference', ['label' => __d('CakeDC/Users', 'Email or username')]); + echo $this->Form->control('reference', ['label' => __d('cake_d_c/users', 'Email or username')]); ?>
- Form->button(__d('CakeDC/Users', 'Submit')) ?> + Form->button(__d('cake_d_c/users', 'Submit')) ?> Form->end() ?>
diff --git a/templates/Users/social_email.php b/templates/Users/social_email.php new file mode 100644 index 000000000..55c1208e9 --- /dev/null +++ b/templates/Users/social_email.php @@ -0,0 +1,21 @@ + +
+ Flash->render() ?> + Form->create() ?> +
+ + Form->control('email', ['type' => 'email', 'required' => true]) ?> +
+ Form->button(__d('cake_d_c/users', 'Submit')); ?> + Form->end() ?> +
diff --git a/templates/Users/u2f_authenticate.php b/templates/Users/u2f_authenticate.php new file mode 100644 index 000000000..16ce1bf01 --- /dev/null +++ b/templates/Users/u2f_authenticate.php @@ -0,0 +1,59 @@ +Html->script('CakeDC/Users.u2f-api.js', ['block' => true]); +?> +
+
+
+
+ Form->create(null, [ + 'url' => [ + 'action' => 'u2fAuthenticateFinish', + '?' => $this->request->getQueryParams() + ], + 'id' => 'u2fAuthenticateFrm' + ]) ?> + + Flash->render('auth') ?> + Flash->render() ?> +
+

+

+

+

+

Html->link( + __d('cake_d_c/users', 'Reload'), + ['action' => 'u2fAuthenticate'], + ['class' => 'btn btn-primary'] + )?>

+
+ Form->hidden('authenticateResponse', ['secure' => false, 'id' => 'authenticateResponse'])?> + Form->end() ?> +
+
+
+
+Html->scriptStart(['block' => true]); +?> + setTimeout(function() { + var req = ; + var appId = req[0].appId; + var challenge = req[0].challenge; + + u2f.sign(appId, challenge, req, function(data) { + var targetForm = document.getElementById('u2fAuthenticateFrm'); + var targetInput = document.getElementById('authenticateResponse'); + if(data.errorCode && data.errorCode != 0) { + alert(""); + + return; + } + targetInput.value = JSON.stringify(data); + targetForm.submit(); + }); + }, 1000); +Html->scriptEnd();?> diff --git a/templates/Users/u2f_register.php b/templates/Users/u2f_register.php new file mode 100644 index 000000000..0ea8da5af --- /dev/null +++ b/templates/Users/u2f_register.php @@ -0,0 +1,64 @@ +Html->script('CakeDC/Users.u2f-api.js', ['block' => true]); +?> +
+
+
+
+ Form->create(null, [ + 'url' => [ + 'action' => 'u2fRegisterFinish', + '?' => $this->request->getQueryParams() + ], + 'id' => 'u2fRegisterFrm' + ]) ?> + + Flash->render('auth') ?> + Flash->render() ?> +
+

+

+

In order to enable your YubiKey the first step is to perform a registration.

+

When the YubiKey starts blinking, press the golden disc to activate it. Depending on the web browser you might need to confirm the use of extended information from the YubiKey.

+

Html->link( + __d('cake_d_c/users', 'Reload'), + ['action' => 'u2fRegister'], + ['class' => 'btn btn-primary'] + )?>

+
+ Form->hidden('registerResponse', ['secure' => false, 'id' => 'registerResponse'])?> + Form->end() ?> +
+
+
+
+ $registerRequest->appId, + 'version' => $registerRequest->version, + 'challenge' => $registerRequest->challenge, + 'attestation' => 'direct' +]); +$this->Html->scriptStart(['block' => true]); +?> +setTimeout(function() { + var req = ; + var appId = req.appId; + var registerRequests = [req]; + u2f.register(appId, registerRequests, [], function(data) { + var targetForm = document.getElementById('u2fRegisterFrm'); + var targetInput = document.getElementById('registerResponse'); + + if(data.errorCode && data.errorCode != 0) { + alert(""); + + return; + } + targetInput.value = JSON.stringify(data); + targetForm.submit(); + }); +}, 1000); +Html->scriptEnd();?> diff --git a/src/Template/Users/verify.ctp b/templates/Users/verify.php similarity index 73% rename from src/Template/Users/verify.ctp rename to templates/Users/verify.php index f1f7d2524..92648a029 100644 --- a/src/Template/Users/verify.ctp +++ b/templates/Users/verify.php @@ -10,9 +10,9 @@

- Form->control('code', ['required' => true, 'label' => __d('CakeDC/Users', 'Verification Code')]) ?> + Form->control('code', ['required' => true, 'label' => __d('cake_d_c/users', 'Verification Code')]) ?> - Form->button(__d('CakeDC/Users', ' Verify'), ['class' => 'btn btn-primary']); ?> + Form->button(__d('cake_d_c/users', ' Verify'), ['class' => 'btn btn-primary', 'escapeTitle' => false]); ?> Form->end() ?> diff --git a/src/Template/Users/view.ctp b/templates/Users/view.php similarity index 50% rename from src/Template/Users/view.ctp rename to templates/Users/view.php index d2e21bf7a..9e0d28106 100644 --- a/src/Template/Users/view.ctp +++ b/templates/Users/view.php @@ -1,79 +1,79 @@
-

+

    -
  • Html->link(__d('CakeDC/Users', 'Edit User'), ['action' => 'edit', $Users->id]) ?>
  • +
  • Html->link(__d('cake_d_c/users', 'Edit User'), ['action' => 'edit', $Users->id]) ?>
  • Form->postLink( - __d('CakeDC/Users', 'Delete User'), + __d('cake_d_c/users', 'Delete User'), ['action' => 'delete', $Users->id], - ['confirm' => __d('CakeDC/Users', 'Are you sure you want to delete # {0}?', $Users->id)] + ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $Users->id)] ) ?>
  • -
  • Html->link(__d('CakeDC/Users', 'List Users'), ['action' => 'index']) ?>
  • -
  • Html->link(__d('CakeDC/Users', 'New User'), ['action' => 'add']) ?>
  • +
  • Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ?>
  • +
  • Html->link(__d('cake_d_c/users', 'New User'), ['action' => 'add']) ?>

id) ?>

-
+

id) ?>

-
+

username) ?>

-
+

email) ?>

-
+

first_name) ?>

-
+

last_name) ?>

-
+

role) ?>

-
+

token) ?>

-
+

api_token) ?>

-
+

Number->format($Users->active) ?>

-
+

token_expires) ?>

-
+

activation_date) ?>

-
+

tos_date) ?>

-
+

created) ?>

-
+

modified) ?>

provider) ?> Html->link( + $socialAccount->link && $socialAccount->link != '#' ? $this->Html->link( $linkText, $socialAccount->link, ['target' => '_blank'] - ) ?>
- - - - - + + + + + social_accounts as $socialAccount) : ?> diff --git a/templates/Users/webauthn2fa.php b/templates/Users/webauthn2fa.php new file mode 100644 index 000000000..3c2b7c898 --- /dev/null +++ b/templates/Users/webauthn2fa.php @@ -0,0 +1,56 @@ +Html->script('CakeDC/Users.webauthn.js', ['block' => true]); +$this->assign('title', __d('cake_d_c/users','Two-factor authentication')); +?> +
+
+
+
+ + Flash->render('auth') ?> + Flash->render() ?> +
+ + +

Html->link( + __d('cake_d_c/users','Reload'), + ['action' => 'webauthn2fa'], + ['class' => 'btn btn-primary'] + )?>

+
+
+
+
+
+ $this->Url->build(['action' => 'webauthn2faAuthenticate']), + 'authenticateOptionsUrl' => $this->Url->build(['action' => 'webauthn2faAuthenticateOptions']), + 'registerActionUrl' => $this->Url->build(['action' => 'webauthn2faRegister']), + 'registerOptionsUrl' => $this->Url->build(['action' => 'webauthn2faRegisterOptions']), + 'isRegister' => $isRegister, + 'username' => h($username), + 'registerElemId' => 'webauthn2faRegisterInfo', + 'authenticateElemId' => 'webauthn2faAuthenticateInfo', +]; +$this->Html->scriptStart(['block' => true]); +?> +setTimeout(function() { + Webauthn2faHelper.run(); +}, 1000); +Html->scriptEnd();?> diff --git a/templates/email/html/reset_password.php b/templates/email/html/reset_password.php new file mode 100644 index 000000000..4009e9134 --- /dev/null +++ b/templates/email/html/reset_password.php @@ -0,0 +1,28 @@ + +

+ , +

+

+ Html->link(__d('cake_d_c/users', 'Reset your password here'), $activationUrl) ?> +

+

+Url->build($activationUrl) +) ?> +

+

+ , +

diff --git a/templates/email/html/social_account_validation.php b/templates/email/html/social_account_validation.php new file mode 100644 index 000000000..01d2fab85 --- /dev/null +++ b/templates/email/html/social_account_validation.php @@ -0,0 +1,31 @@ + + +

+ , +

+

+ Html->link($text, $activationUrl); + ?> +

+

+Url->build($activationUrl) +) ?> +

+

+ , +

diff --git a/templates/email/html/validation.php b/templates/email/html/validation.php new file mode 100644 index 000000000..ad92d2f3e --- /dev/null +++ b/templates/email/html/validation.php @@ -0,0 +1,27 @@ + +

+ , +

+

+ Html->link(__d('cake_d_c/users', 'Activate your account here'), $activationUrl) ?> +

+

+Url->build($activationUrl) +) ?> +

+

+ , +

diff --git a/templates/email/text/reset_password.php b/templates/email/text/reset_password.php new file mode 100644 index 000000000..312ac428d --- /dev/null +++ b/templates/email/text/reset_password.php @@ -0,0 +1,21 @@ + +, + +Url->build($activationUrl) +) ?> + +, + diff --git a/templates/email/text/social_account_validation.php b/templates/email/text/social_account_validation.php new file mode 100644 index 000000000..79dc496de --- /dev/null +++ b/templates/email/text/social_account_validation.php @@ -0,0 +1,21 @@ + +, + +Url->build($activationUrl) +) ?> + +, + diff --git a/templates/email/text/validation.php b/templates/email/text/validation.php new file mode 100644 index 000000000..312ac428d --- /dev/null +++ b/templates/email/text/validation.php @@ -0,0 +1,21 @@ + +, + +Url->build($activationUrl) +) ?> + +, + diff --git a/src/Template/Layout/Email/html/default.ctp b/templates/layout/email/html/default.php similarity index 76% rename from src/Template/Layout/Email/html/default.ctp rename to templates/layout/email/html/default.php index b0c828681..98d045608 100644 --- a/src/Template/Layout/Email/html/default.ctp +++ b/templates/layout/email/html/default.php @@ -1,11 +1,11 @@ diff --git a/src/Template/Layout/Email/text/default.ctp b/templates/layout/email/text/default.php similarity index 68% rename from src/Template/Layout/Email/text/default.ctp rename to templates/layout/email/text/default.php index 686aa84ab..4d493b97c 100644 --- a/src/Template/Layout/Email/text/default.ctp +++ b/templates/layout/email/text/default.php @@ -1,11 +1,11 @@ diff --git a/tests/App/Template/Email/text/custom_template_in_app_namespace.ctp b/tests/App/Template/Email/text/custom_template_in_app_namespace.ctp deleted file mode 100644 index 1619a4eb2..000000000 --- a/tests/App/Template/Email/text/custom_template_in_app_namespace.ctp +++ /dev/null @@ -1 +0,0 @@ -some custom template here \ No newline at end of file diff --git a/tests/App/Template/Layout/Email/html/default.ctp b/tests/App/Template/Layout/Email/html/default.ctp deleted file mode 100644 index 21c7e0044..000000000 --- a/tests/App/Template/Layout/Email/html/default.ctp +++ /dev/null @@ -1,2 +0,0 @@ -fetch('content'); diff --git a/tests/App/Template/Layout/Email/text/default.ctp b/tests/App/Template/Layout/Email/text/default.ctp deleted file mode 100644 index 21c7e0044..000000000 --- a/tests/App/Template/Layout/Email/text/default.ctp +++ /dev/null @@ -1,2 +0,0 @@ -fetch('content'); diff --git a/tests/Fixture/PostsFixture.php b/tests/Fixture/PostsFixture.php index 257e98be2..4057cbbb8 100644 --- a/tests/Fixture/PostsFixture.php +++ b/tests/Fixture/PostsFixture.php @@ -1,11 +1,11 @@ ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'title' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // @codingStandardsIgnoreEnd - /** * Records * diff --git a/tests/Fixture/PostsUsersFixture.php b/tests/Fixture/PostsUsersFixture.php index 8b5948f5d..5f5350e9e 100644 --- a/tests/Fixture/PostsUsersFixture.php +++ b/tests/Fixture/PostsUsersFixture.php @@ -1,11 +1,11 @@ ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'post_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // @codingStandardsIgnoreEnd - /** * Records * diff --git a/tests/Fixture/SocialAccountsFixture.php b/tests/Fixture/SocialAccountsFixture.php index 48a966eda..9789d2c45 100644 --- a/tests/Fixture/SocialAccountsFixture.php +++ b/tests/Fixture/SocialAccountsFixture.php @@ -1,11 +1,11 @@ ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'provider' => ['type' => 'string', 'length' => 255, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], - 'username' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'reference' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'avatar' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'description' => ['type' => 'text', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'token' => ['type' => 'string', 'length' => 500, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'token_secret' => ['type' => 'string', 'length' => 500, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'token_expires' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'active' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => true, 'comment' => '', 'precision' => null], - 'data' => ['type' => 'text', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // @codingStandardsIgnoreEnd - /** * Records * @@ -71,7 +38,7 @@ class SocialAccountsFixture extends TestFixture 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' + 'modified' => '2015-05-22 21:52:44', ], [ 'id' => '00000000-0000-0000-0000-000000000002', @@ -87,7 +54,7 @@ class SocialAccountsFixture extends TestFixture 'active' => true, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' + 'modified' => '2015-05-22 21:52:44', ], [ 'id' => '00000000-0000-0000-0000-000000000003', @@ -103,7 +70,7 @@ class SocialAccountsFixture extends TestFixture 'active' => true, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' + 'modified' => '2015-05-22 21:52:44', ], [ 'id' => '00000000-0000-0000-0000-000000000004', @@ -119,7 +86,7 @@ class SocialAccountsFixture extends TestFixture 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' + 'modified' => '2015-05-22 21:52:44', ], [ 'id' => '00000000-0000-0000-0000-000000000005', @@ -135,7 +102,7 @@ class SocialAccountsFixture extends TestFixture 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' + 'modified' => '2015-05-22 21:52:44', ], ]; } diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php index d0256aed8..d48f6dc55 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -1,267 +1,262 @@ ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'username' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'email' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'password' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'first_name' => ['type' => 'string', 'length' => 50, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'last_name' => ['type' => 'string', 'length' => 50, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'token' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'token_expires' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'api_token' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'activation_date' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'secret' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], - 'secret_verified' => ['type' => 'boolean', 'length' => null, 'null' => true, 'default' => false, 'comment' => '', 'precision' => null], - 'tos_date' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'active' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => true, 'comment' => '', 'precision' => null], - 'is_superuser' => ['type' => 'boolean', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => false, 'comment' => '', 'precision' => null, 'autoIncrement' => null], - 'role' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => 'user', 'comment' => '', 'precision' => null, 'fixed' => null], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // @codingStandardsIgnoreEnd + public function init(): void + { + $this->records = [ + [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + 'email' => 'user-1@test.com', + 'password' => '12345', + 'first_name' => 'first1', + 'last_name' => 'last1', + 'token' => 'ae93ddbe32664ce7927cf0c5c5a5e59d', + 'token_expires' => '2035-06-24 17:33:54', + 'api_token' => 'yyy', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'yyy', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => false, + 'is_superuser' => true, + 'role' => 'admin', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + 'additional_data' => [ + 'u2f_registration' => [ + 'keyHandle' => 'fake key handle', + 'publicKey' => 'afdoaj0-23u423-ad ujsf-as8-0-afsd', + 'certificate' => '23jdsfoasdj0f9sa082304823423', + 'counter' => 1, + ], + 'webauthn_credentials' => [ + 'MTJiMzc0ODYtOTI5OS00MzMxLWFjMzMtODViMmQ5ODViNmZl' => [ + 'publicKeyCredentialId' => '12b37486-9299-4331-ac33-85b2d985b6fe', + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + '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'), + 'counter' => 190, + 'otherUI' => null, + ], + ], + ], + 'last_login' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + 'email' => 'user-2@test.com', + //The password real value is 12345 + 'password' => '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde', + 'first_name' => 'user', + 'last_name' => 'second', + 'token' => '6614f65816754310a5f0553436dd89e9', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => 'xxx', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'xxx', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => true, + 'role' => 'admin', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + 'last_login' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000003', + 'username' => 'user-3', + 'email' => 'user-3@test.com', + 'password' => '12345', + 'first_name' => 'user', + 'last_name' => 'third', + 'token' => 'token-3', + 'token_expires' => '2030-06-20 17:33:54', + 'api_token' => 'xxx', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'xxx', + 'secret_verified' => true, + 'is_superuser' => true, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => false, + 'role' => 'admin', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000004', + 'username' => 'user-4', + 'email' => '4@example.com', + 'password' => '$2y$10$Nvu7ipP.z8tiIl75OdUvt.86vuG6iKMoHIOc7O7mboFI85hSyTEde', + 'first_name' => 'FirstName4', + 'last_name' => 'Lorem ipsum dolor sit amet', + 'token' => 'token-4', + 'token_expires' => '2030-06-24 17:33:54', + 'api_token' => 'Lorem ipsum dolor sit amet', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'Lorem ipsum dolor sit amet', + 'secret_verified' => true, + 'is_superuser' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'role' => 'Lorem ipsum dolor sit amet', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000005', + 'username' => 'user-5', + 'email' => 'test@example.com', + 'password' => '12345', + 'first_name' => 'first-user-5', + 'last_name' => 'firts name 5', + 'token' => 'token-5', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => '', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => '', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'user', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000006', + 'username' => 'user-6', + 'email' => '6@example.com', + 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', + 'first_name' => 'first-user-6', + 'last_name' => 'firts name 6', + 'token' => 'token-6', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => '', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => '', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'user', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000007', + 'username' => 'Lorem ipsum dolor sit amet', + 'email' => 'Lorem ipsum dolor sit amet', + 'password' => 'Lorem ipsum dolor sit amet', + 'first_name' => 'Lorem ipsum dolor sit amet', + 'last_name' => 'Lorem ipsum dolor sit amet', + 'token' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => 'Lorem ipsum dolor sit amet', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'Lorem ipsum dolor sit amet', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'Lorem ipsum dolor sit amet', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000008', + 'username' => 'Lorem ipsum dolor sit amet', + 'email' => 'Lorem ipsum dolor sit amet', + 'password' => 'Lorem ipsum dolor sit amet', + 'first_name' => 'Lorem ipsum dolor sit amet', + 'last_name' => 'Lorem ipsum dolor sit amet', + 'token' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => 'Lorem ipsum dolor sit amet', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'Lorem ipsum dolor sit amet', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'Lorem ipsum dolor sit amet', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000009', + 'username' => 'Lorem ipsum dolor sit amet', + 'email' => 'Lorem ipsum dolor sit amet', + 'password' => 'Lorem ipsum dolor sit amet', + 'first_name' => 'Lorem ipsum dolor sit amet', + 'last_name' => 'Lorem ipsum dolor sit amet', + 'token' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => 'Lorem ipsum dolor sit amet', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'Lorem ipsum dolor sit amet', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'Lorem ipsum dolor sit amet', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000010', + 'username' => 'Lorem ipsum dolor sit amet', + 'email' => 'Lorem ipsum dolor sit amet', + 'password' => 'Lorem ipsum dolor sit amet', + 'first_name' => 'Lorem ipsum dolor sit amet', + 'last_name' => 'Lorem ipsum dolor sit amet', + 'token' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-06-24 17:33:54', + 'api_token' => 'Lorem ipsum dolor sit amet', + 'activation_date' => '2015-06-24 17:33:54', + 'secret' => 'Lorem ipsum dolor sit amet', + 'secret_verified' => false, + 'tos_date' => '2015-06-24 17:33:54', + 'active' => true, + 'is_superuser' => false, + 'role' => 'Lorem ipsum dolor sit amet', + 'created' => '2015-06-24 17:33:54', + 'modified' => '2015-06-24 17:33:54', + ], + ]; - /** - * Records - * - * @var array - */ - public $records = [ - [ - 'id' => '00000000-0000-0000-0000-000000000001', - 'username' => 'user-1', - 'email' => 'user-1@test.com', - 'password' => '12345', - 'first_name' => 'first1', - 'last_name' => 'last1', - 'token' => 'ae93ddbe32664ce7927cf0c5c5a5e59d', - 'token_expires' => '2035-06-24 17:33:54', - 'api_token' => 'yyy', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'yyy', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => false, - 'is_superuser' => true, - 'role' => 'admin', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000002', - 'username' => 'user-2', - 'email' => 'user-2@test.com', - 'password' => '12345', - 'first_name' => 'user', - 'last_name' => 'second', - 'token' => '6614f65816754310a5f0553436dd89e9', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => 'xxx', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'xxx', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => true, - 'role' => 'admin', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000003', - 'username' => 'user-3', - 'email' => 'user-3@test.com', - 'password' => '12345', - 'first_name' => 'user', - 'last_name' => 'third', - 'token' => 'token-3', - 'token_expires' => '2030-06-20 17:33:54', - 'api_token' => 'xxx', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'xxx', - 'secret_verified' => true, - 'is_superuser' => true, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => false, - 'is_superuser' => true, - 'role' => 'admin', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000004', - 'username' => 'user-4', - 'email' => '4@example.com', - 'password' => 'Lorem ipsum dolor sit amet', - 'first_name' => 'FirstName4', - 'last_name' => 'Lorem ipsum dolor sit amet', - 'token' => 'token-4', - 'token_expires' => '2030-06-24 17:33:54', - 'api_token' => 'Lorem ipsum dolor sit amet', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'Lorem ipsum dolor sit amet', - 'secret_verified' => true, - 'is_superuser' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'Lorem ipsum dolor sit amet', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000005', - 'username' => 'user-5', - 'email' => 'test@example.com', - 'password' => '12345', - 'first_name' => 'first-user-5', - 'last_name' => 'firts name 5', - 'token' => 'token-5', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => '', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => '', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'user', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000006', - 'username' => 'user-6', - 'email' => '6@example.com', - 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', - 'first_name' => 'first-user-6', - 'last_name' => 'firts name 6', - 'token' => 'token-6', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => '', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => '', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'user', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000007', - 'username' => 'Lorem ipsum dolor sit amet', - 'email' => 'Lorem ipsum dolor sit amet', - 'password' => 'Lorem ipsum dolor sit amet', - 'first_name' => 'Lorem ipsum dolor sit amet', - 'last_name' => 'Lorem ipsum dolor sit amet', - 'token' => 'Lorem ipsum dolor sit amet', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => 'Lorem ipsum dolor sit amet', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'Lorem ipsum dolor sit amet', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'Lorem ipsum dolor sit amet', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000008', - 'username' => 'Lorem ipsum dolor sit amet', - 'email' => 'Lorem ipsum dolor sit amet', - 'password' => 'Lorem ipsum dolor sit amet', - 'first_name' => 'Lorem ipsum dolor sit amet', - 'last_name' => 'Lorem ipsum dolor sit amet', - 'token' => 'Lorem ipsum dolor sit amet', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => 'Lorem ipsum dolor sit amet', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'Lorem ipsum dolor sit amet', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'Lorem ipsum dolor sit amet', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000009', - 'username' => 'Lorem ipsum dolor sit amet', - 'email' => 'Lorem ipsum dolor sit amet', - 'password' => 'Lorem ipsum dolor sit amet', - 'first_name' => 'Lorem ipsum dolor sit amet', - 'last_name' => 'Lorem ipsum dolor sit amet', - 'token' => 'Lorem ipsum dolor sit amet', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => 'Lorem ipsum dolor sit amet', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'Lorem ipsum dolor sit amet', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'Lorem ipsum dolor sit amet', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - [ - 'id' => '00000000-0000-0000-0000-000000000010', - 'username' => 'Lorem ipsum dolor sit amet', - 'email' => 'Lorem ipsum dolor sit amet', - 'password' => 'Lorem ipsum dolor sit amet', - 'first_name' => 'Lorem ipsum dolor sit amet', - 'last_name' => 'Lorem ipsum dolor sit amet', - 'token' => 'Lorem ipsum dolor sit amet', - 'token_expires' => '2015-06-24 17:33:54', - 'api_token' => 'Lorem ipsum dolor sit amet', - 'activation_date' => '2015-06-24 17:33:54', - 'secret' => 'Lorem ipsum dolor sit amet', - 'secret_verified' => false, - 'tos_date' => '2015-06-24 17:33:54', - 'active' => true, - 'is_superuser' => false, - 'role' => 'Lorem ipsum dolor sit amet', - 'created' => '2015-06-24 17:33:54', - 'modified' => '2015-06-24 17:33:54' - ], - ]; + parent::init(); + } } diff --git a/tests/TestCase/Auth/Exception/InvalidProviderExceptionTest.php b/tests/TestCase/Auth/Exception/InvalidProviderExceptionTest.php deleted file mode 100644 index 5a44e7451..000000000 --- a/tests/TestCase/Auth/Exception/InvalidProviderExceptionTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Auth/Exception/InvalidSettingsExceptionTest.php b/tests/TestCase/Auth/Exception/InvalidSettingsExceptionTest.php deleted file mode 100644 index 68d18bc24..000000000 --- a/tests/TestCase/Auth/Exception/InvalidSettingsExceptionTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Auth/Exception/MissingEventListenerExceptionTest.php b/tests/TestCase/Auth/Exception/MissingEventListenerExceptionTest.php deleted file mode 100644 index f182d9083..000000000 --- a/tests/TestCase/Auth/Exception/MissingEventListenerExceptionTest.php +++ /dev/null @@ -1,45 +0,0 @@ -assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Auth/Exception/MissingProviderConfigurationExceptionTest.php b/tests/TestCase/Auth/Exception/MissingProviderConfigurationExceptionTest.php deleted file mode 100644 index e1eb1bb3d..000000000 --- a/tests/TestCase/Auth/Exception/MissingProviderConfigurationExceptionTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Auth/Social/Mapper/FacebookTest.php b/tests/TestCase/Auth/Social/Mapper/FacebookTest.php deleted file mode 100644 index 8c8ba9e85..000000000 --- a/tests/TestCase/Auth/Social/Mapper/FacebookTest.php +++ /dev/null @@ -1,91 +0,0 @@ - 'test-token', - 'expires' => 1490988496 - ]); - $rawData = [ - 'token' => $token, - 'id' => '1', - 'name' => 'Test User', - 'first_name' => 'Test', - 'last_name' => 'User', - 'email' => 'test@gmail.com', - 'hometown' => [ - 'id' => '108226049197930', - 'name' => 'Madrid' - ], - 'picture' => [ - 'data' => [ - 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', - 'is_silhouette' => false - ] - ], - 'cover' => [ - 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', - 'id' => '1' - ], - 'gender' => 'male', - 'locale' => 'en_US', - 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', - 'timezone' => -5, - 'age_range' => [ - 'min' => 21 - ], - 'bio' => 'I am the best test user in the world.', - 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', - 'is_silhouette' => false, - 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg' - ]; - $providerMapper = new Facebook($rawData); - $user = $providerMapper(); - $this->assertEquals([ - 'id' => '1', - 'username' => null, - 'full_name' => 'Test User', - 'first_name' => 'Test', - 'last_name' => 'User', - 'email' => 'test@gmail.com', - 'avatar' => 'https://graph.facebook.com/1/picture?type=large', - 'gender' => 'male', - 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', - 'bio' => 'I am the best test user in the world.', - 'locale' => 'en_US', - 'validated' => true, - 'credentials' => [ - 'token' => 'test-token', - 'secret' => null, - 'expires' => 1490988496 - ], - 'raw' => $rawData - ], $user); - } -} diff --git a/tests/TestCase/Auth/Social/Mapper/GoogleTest.php b/tests/TestCase/Auth/Social/Mapper/GoogleTest.php deleted file mode 100644 index 376e8273a..000000000 --- a/tests/TestCase/Auth/Social/Mapper/GoogleTest.php +++ /dev/null @@ -1,73 +0,0 @@ - 'test-token', - 'expires' => 1490988496 - ]); - $rawData = [ - 'token' => $token, - 'emails' => [['value' => 'test@gmail.com']], - 'id' => '1', - 'displayName' => 'Test User', - 'name' => [ - 'familyName' => 'User', - 'givenName' => 'Test' - ], - 'aboutMe' => 'I am the best test user in the world.', - 'url' => 'https://plus.google.com/+TestUser', - 'image' => [ - 'url' => 'https://lh3.googleusercontent.com/photo.jpg' - ] - ]; - $providerMapper = new Google($rawData); - $user = $providerMapper(); - $this->assertEquals([ - 'id' => '1', - 'username' => null, - 'full_name' => 'Test User', - 'first_name' => 'Test', - 'last_name' => 'User', - 'email' => 'test@gmail.com', - 'avatar' => 'https://lh3.googleusercontent.com/photo.jpg', - 'gender' => null, - 'link' => 'https://plus.google.com/+TestUser', - 'bio' => 'I am the best test user in the world.', - 'locale' => null, - 'validated' => true, - 'credentials' => [ - 'token' => 'test-token', - 'secret' => null, - 'expires' => 1490988496 - ], - 'raw' => $rawData - ], $user); - } -} diff --git a/tests/TestCase/Auth/Social/Mapper/InstagramTest.php b/tests/TestCase/Auth/Social/Mapper/InstagramTest.php deleted file mode 100644 index 7d3b05625..000000000 --- a/tests/TestCase/Auth/Social/Mapper/InstagramTest.php +++ /dev/null @@ -1,72 +0,0 @@ - 'test-token', - 'expires' => 1490988496 - ]); - $rawData = [ - 'token' => $token, - 'profile_picture' => 'https://scontent-lax3-2.cdninstagram.com/test.jpg', - 'username' => 'test', - 'id' => '1', - 'full_name' => '', - 'website' => '', - 'counts' => [ - 'followed_by' => 35, - 'media' => 1, - 'follows' => 44 - ], - 'bio' => '' - ]; - $providerMapper = new Instagram($rawData); - $user = $providerMapper(); - $this->assertEquals([ - 'id' => '1', - 'username' => 'test', - 'full_name' => '', - 'first_name' => null, - 'last_name' => null, - 'email' => null, - 'avatar' => 'https://scontent-lax3-2.cdninstagram.com/test.jpg', - 'gender' => null, - 'link' => 'https://instagram.com/test', - 'bio' => '', - 'locale' => null, - 'validated' => false, - 'credentials' => [ - 'token' => 'test-token', - 'secret' => null, - 'expires' => 1490988496 - ], - 'raw' => $rawData - ], $user); - } -} diff --git a/tests/TestCase/Auth/Social/Mapper/LinkedInTest.php b/tests/TestCase/Auth/Social/Mapper/LinkedInTest.php deleted file mode 100644 index 3f17e77a7..000000000 --- a/tests/TestCase/Auth/Social/Mapper/LinkedInTest.php +++ /dev/null @@ -1,75 +0,0 @@ - 'test-token', - 'expires' => 1490988496 - ]); - $rawData = [ - 'token' => $token, - 'emailAddress' => 'test@gmail.com', - 'firstName' => 'Test', - 'headline' => 'The best test user in the world.', - 'id' => '1', - 'industry' => 'Computer Software', - 'lastName' => 'User', - 'location' => [ - 'country' => [ - 'code' => 'es' - ], - 'name' => 'Spain' - ], - 'pictureUrl' => 'https://media.licdn.com/mpr/mprx/test.jpg', - 'publicProfileUrl' => 'https://www.linkedin.com/in/test' - ]; - $providerMapper = new LinkedIn($rawData); - $user = $providerMapper(); - $this->assertEquals([ - 'id' => '1', - 'username' => null, - 'full_name' => null, - 'first_name' => 'Test', - 'last_name' => 'User', - 'email' => 'test@gmail.com', - 'avatar' => 'https://media.licdn.com/mpr/mprx/test.jpg', - 'gender' => null, - 'link' => 'https://www.linkedin.com/in/test', - 'bio' => 'The best test user in the world.', - 'locale' => null, - 'validated' => true, - 'credentials' => [ - 'token' => 'test-token', - 'secret' => null, - 'expires' => 1490988496 - ], - 'raw' => $rawData - ], $user); - } -} diff --git a/tests/TestCase/Auth/Social/Mapper/TwitterTest.php b/tests/TestCase/Auth/Social/Mapper/TwitterTest.php deleted file mode 100644 index d33aa343f..000000000 --- a/tests/TestCase/Auth/Social/Mapper/TwitterTest.php +++ /dev/null @@ -1,71 +0,0 @@ - '1', - 'nickname' => 'test', - 'name' => 'Test User', - 'firstName' => null, - 'lastName' => null, - 'email' => null, - 'location' => '', - 'description' => '', - 'imageUrl' => 'http://pbs.twimg.com/profile_images/test.jpeg', - 'urls' => [], - 'extra' => [], - 'token' => [ - 'accessToken' => 'test-token', - 'tokenSecret' => 'test-secret' - ] - ]; - $providerMapper = new Twitter($rawData); - $user = $providerMapper(); - $this->assertEquals([ - 'id' => '1', - 'username' => 'test', - 'full_name' => 'Test User', - 'first_name' => null, - 'last_name' => null, - 'email' => null, - 'avatar' => 'http://pbs.twimg.com/profile_images/test.jpeg', - 'gender' => null, - 'link' => 'https://twitter.com/test', - 'bio' => '', - 'locale' => null, - 'validated' => false, - 'credentials' => [ - 'token' => 'test-token', - 'secret' => 'test-secret', - 'expires' => null - ], - 'raw' => $rawData - ], $user); - } -} diff --git a/tests/TestCase/Auth/SocialAuthenticateTest.php b/tests/TestCase/Auth/SocialAuthenticateTest.php deleted file mode 100644 index 14a5f72bb..000000000 --- a/tests/TestCase/Auth/SocialAuthenticateTest.php +++ /dev/null @@ -1,644 +0,0 @@ -Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - - $this->Token = $this->getMockBuilder('League\OAuth2\Client\Token\AccessToken') - ->setMethods(['getToken', 'getExpires']) - ->disableOriginalConstructor() - ->getMock(); - - $this->controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(['failedSocialLogin', 'dispatchEvent']) - ->setConstructorArgs([$request, $response]) - ->getMock(); - - $this->controller->expects($this->any()) - ->method('dispatchEvent') - ->will($this->returnValue(new Event('test'))); - - $this->Request = $request; - $this->SocialAuthenticate = $this->_getSocialAuthenticateMockMethods(['_authenticate', '_getProviderName', - '_mapUser', '_socialLogin', 'dispatchEvent', '_validateConfig', '_getController']); - - $this->SocialAuthenticate->expects($this->any()) - ->method('_getController') - ->will($this->returnValue($this->controller)); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - public function tearDown() - { - unset($this->SocialAuthenticate, $this->controller); - } - - protected function _getSocialAuthenticateMock() - { - return $this->getMockBuilder('CakeDC\Users\Auth\SocialAuthenticate') - ->disableOriginalConstructor() - ->getMock(); - } - - protected function _getSocialAuthenticateMockMethods($methods) - { - return $this->getMockBuilder('CakeDC\Users\Auth\SocialAuthenticate') - ->disableOriginalConstructor() - ->setMethods($methods) - ->getMock(); - } - - /** - * test - * - * @expectedException \CakeDC\Users\Auth\Exception\MissingProviderConfigurationException - */ - public function testConstructorMissingConfig() - { - $socialAuthenticate = new SocialAuthenticate(new ComponentRegistry($this->controller)); - } - - /** - * test - * - */ - public function testConstructor() - { - $socialAuthenticate = new SocialAuthenticate(new ComponentRegistry($this->controller), [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => 'http://example.com/auth/facebook', - ] - ] - ] - ]); - - $this->assertInstanceOf('\CakeDC\Users\Auth\SocialAuthenticate', $socialAuthenticate); - } - - /** - * test - * - * @expectedException \CakeDC\Users\Auth\Exception\InvalidProviderException - */ - public function testConstructorMissingProvider() - { - $socialAuthenticate = new SocialAuthenticate(new ComponentRegistry($this->controller), [ - 'providers' => [ - 'facebook' => [ - 'className' => 'missing', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => 'http://example.com/auth/facebook', - ] - ] - ] - ]); - } - - /** - * Test getUser - * - * @dataProvider providerGetUser - */ - public function testGetUserAuth($rawData, $mapper) - { - $user = $this->Table->get('00000000-0000-0000-0000-000000000002', ['contain' => ['SocialAccounts']]); - - $this->controller->expects($this->once()) - ->method('dispatchEvent') - ->with(UsersAuthComponent::EVENT_AFTER_REGISTER, compact('user')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_authenticate') - ->with($this->Request) - ->will($this->returnValue($rawData)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_getProviderName') - ->will($this->returnValue('facebook')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_mapUser') - ->will($this->returnValue($mapper)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_socialLogin') - ->will($this->returnValue($user)); - - $result = $this->SocialAuthenticate->getUser($this->Request); - $this->assertTrue($result['active']); - $this->assertEquals('00000000-0000-0000-0000-000000000002', $result['id']); - } - - /** - * Provider for getUser test method - * - */ - public function providerGetUser() - { - return [ - [ - 'rawData' => [ - 'token' => 'token', - 'id' => 'reference-2-1', - 'name' => 'User S', - 'first_name' => 'user', - 'last_name' => 'second', - 'email' => 'userSecond@example.com', - 'cover' => [ - 'id' => 'reference-2-1' - ], - 'gender' => 'female', - 'locale' => 'en_US', - 'link' => 'link', - ], - 'mappedData' => [ - 'id' => 'reference-2-1', - 'username' => null, - 'full_name' => 'User S', - 'first_name' => 'user', - 'last_name' => 'second', - 'email' => 'userSecond@example.com', - 'link' => 'link', - 'bio' => null, - 'locale' => 'en_US', - 'validated' => true, - 'credentials' => [ - 'token' => 'token', - 'secret' => null, - 'expires' => 1458423682 - ], - 'raw' => [ - - ], - 'provider' => 'Facebook' - ], - ] - - ]; - } - - /** - * Test getUser - * - */ - public function testGetUserSessionData() - { - $user = ['username' => 'username', 'email' => 'myemail@test.com']; - $this->SocialAuthenticate = $this->_getSocialAuthenticateMockMethods(['_authenticate', - '_getProviderName', '_mapUser', '_touch', '_validateConfig']); - - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['read', 'delete']) - ->getMock(); - $session->expects($this->once()) - ->method('read') - ->with('Users.social') - ->will($this->returnValue($user)); - - $session->expects($this->once()) - ->method('delete') - ->with('Users.social'); - - $this->Request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession']) - ->getMock(); - $this->Request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_touch') - ->will($this->returnValue($user)); - - $this->SocialAuthenticate->getUser($this->Request); - } - - /** - * Test getUser - * - * @dataProvider providerGetUser - */ - public function testGetUserNotEmailProvided($rawData, $mapper) - { - $this->SocialAuthenticate->expects($this->once()) - ->method('_authenticate') - ->with($this->Request) - ->will($this->returnValue($rawData)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_getProviderName') - ->will($this->returnValue('facebook')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_mapUser') - ->will($this->returnValue($mapper)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_socialLogin') - ->will($this->throwException(new MissingEmailException('missing email'))); - - $this->controller->expects($this->once()) - ->method('dispatchEvent') - ->with(UsersAuthComponent::EVENT_FAILED_SOCIAL_LOGIN); - - $this->controller->expects($this->once()) - ->method('failedSocialLogin'); - - $this->SocialAuthenticate->getUser($this->Request); - } - - /** - * Test getUser - * - * @dataProvider providerGetUser - */ - public function testGetUserNotActive($rawData, $mapper) - { - $this->SocialAuthenticate->expects($this->once()) - ->method('_authenticate') - ->with($this->Request) - ->will($this->returnValue($rawData)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_getProviderName') - ->will($this->returnValue('facebook')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_mapUser') - ->will($this->returnValue($mapper)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_socialLogin') - ->will($this->throwException(new UserNotActiveException('user not active'))); - - $this->SocialAuthenticate->getUser($this->Request); - } - - /** - * Test getUser - * - * @dataProvider providerGetUser - */ - public function testGetUserNotActiveAccount($rawData, $mapper) - { - $this->SocialAuthenticate->expects($this->once()) - ->method('_authenticate') - ->with($this->Request) - ->will($this->returnValue($rawData)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_getProviderName') - ->will($this->returnValue('facebook')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_mapUser') - ->will($this->returnValue($mapper)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_socialLogin') - ->will($this->throwException(new AccountNotActiveException('user not active'))); - - $this->SocialAuthenticate->getUser($this->Request); - } - - /** - * Test getUser - * - * @dataProvider providerTwitter - */ - public function testGetUserNotEmailProvidedTwitter($rawData, $mapper) - { - $this->SocialAuthenticate->expects($this->once()) - ->method('_authenticate') - ->with($this->Request) - ->will($this->returnValue($rawData)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_getProviderName') - ->will($this->returnValue('twitter')); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_mapUser') - ->will($this->returnValue($mapper)); - - $this->SocialAuthenticate->expects($this->once()) - ->method('_socialLogin') - ->will($this->throwException(new MissingEmailException('missing email'))); - - $this->SocialAuthenticate->getUser($this->Request); - } - - /** - * Provider for getUser test method - * - */ - public function providerTwitter() - { - return [ - [ - 'rawData' => [ - 'token' => 'token', - 'id' => 'reference-2-1', - 'name' => 'User S', - 'first_name' => 'user', - 'last_name' => 'second', - 'email' => 'userSecond@example.com', - 'cover' => [ - 'id' => 'reference-2-1' - ], - 'gender' => 'female', - 'locale' => 'en_US', - 'link' => 'link', - ], - 'mappedData' => [ - 'id' => 'reference-2-1', - 'username' => null, - 'full_name' => 'User S', - 'first_name' => 'user', - 'last_name' => 'second', - 'email' => 'userSecond@example.com', - 'link' => 'link', - 'bio' => null, - 'locale' => 'en_US', - 'validated' => true, - 'credentials' => [ - 'token' => 'token', - 'secret' => null, - 'expires' => 1458423682 - ], - 'raw' => [ - - ], - 'provider' => 'Twitter' - ], - ] - - ]; - } - - /** - * Test _socialLogin - * - * @dataProvider providerMapper - */ - public function testSocialLogin() - { - $this->SocialAuthenticate = $this->_getSocialAuthenticateMock(); - - $reflectedClass = new ReflectionClass($this->SocialAuthenticate); - $socialLogin = $reflectedClass->getMethod('_socialLogin'); - $socialLogin->setAccessible(true); - $data = [ - 'id' => 'reference-2-1', - 'provider' => 'Facebook' - ]; - $result = $socialLogin->invoke($this->SocialAuthenticate, $data); - $this->assertEquals($result->id, '00000000-0000-0000-0000-000000000002'); - $this->assertTrue($result->active); - } - - /** - * Test _mapUser - * - * @dataProvider providerMapper - */ - public function testMapUser($data, $mappedData) - { - $data['token'] = $this->Token; - $this->SocialAuthenticate = $this->_getSocialAuthenticateMock(); - - $reflectedClass = new ReflectionClass($this->SocialAuthenticate); - $mapUser = $reflectedClass->getMethod('_mapUser'); - $mapUser->setAccessible(true); - - $this->Token->expects($this->once()) - ->method('getToken') - ->will($this->returnValue('token')); - - $this->Token->expects($this->once()) - ->method('getExpires') - ->will($this->returnValue(1458510952)); - - $result = $mapUser->invoke($this->SocialAuthenticate, 'Facebook', $data); - unset($result['raw']); - $this->assertEquals($mappedData, $result); - } - - /** - * Provider for _mapUser test method - * - */ - public function providerMapper() - { - return [ - [ - 'rawData' => [ - 'id' => 'my-facebook-id', - 'name' => 'My name.', - 'first_name' => 'My first name', - 'last_name' => 'My lastname.', - 'email' => 'myemail@example.com', - 'gender' => 'female', - 'locale' => 'en_US', - 'link' => 'https://www.facebook.com/app_scoped_user_id/my-facebook-id/', - ], - 'mappedData' => [ - 'id' => 'my-facebook-id', - 'username' => null, - 'full_name' => 'My name.', - 'first_name' => 'My first name', - 'last_name' => 'My lastname.', - 'email' => 'myemail@example.com', - 'avatar' => 'https://graph.facebook.com/my-facebook-id/picture?type=large', - 'gender' => 'female', - 'link' => 'https://www.facebook.com/app_scoped_user_id/my-facebook-id/', - 'bio' => null, - 'locale' => 'en_US', - 'validated' => true, - 'credentials' => [ - 'token' => 'token', - 'secret' => null, - 'expires' => (int)1458510952 - ], - 'provider' => 'Facebook' - ], - ] - - ]; - } - - /** - * Test _mapUser - * - * @expectedException CakeDC\Users\Exception\MissingProviderException - */ - public function testMapUserException() - { - $data = []; - $this->SocialAuthenticate = $this->_getSocialAuthenticateMock(); - - $reflectedClass = new ReflectionClass($this->SocialAuthenticate); - $mapUser = $reflectedClass->getMethod('_mapUser'); - $mapUser->setAccessible(true); - $mapUser->invoke($this->SocialAuthenticate, null, $data); - } - - /** - * Provider for normalizeConfig test method - * - * @dataProvider providers - */ - public function testNormalizeConfig($data, $oauth2, $callTimes, $enabledNoOAuth2Provider) - { - Configure::write('OAuth2', $oauth2); - $this->SocialAuthenticate = $this->_getSocialAuthenticateMockMethods(['_authenticate', - '_getProviderName', '_mapUser', '_touch', '_validateConfig', '_normalizeConfig']); - - $this->SocialAuthenticate->expects($this->exactly($callTimes)) - ->method('_normalizeConfig'); - - $this->SocialAuthenticate->normalizeConfig($data, $enabledNoOAuth2Provider); - } - - /** - * Test normalizeConfig - * - * @expectedException CakeDC\Users\Auth\Exception\MissingProviderConfigurationException - */ - public function testNormalizeConfigException() - { - $this->SocialAuthenticate->normalizeConfig([]); - } - - /** - * Provider for normalizeConfig test method - * - */ - public function providers() - { - return [ - [ - [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - ], - 'instagram' => [ - 'className' => 'League\OAuth2\Client\Provider\Instagram', - ] - ], - - ], - [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - ], - 'instagram' => [ - 'className' => 'League\OAuth2\Client\Provider\Instagram', - ] - ] - ], - 2, - false - ], - [ - [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - ], - ], - - ], - [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - ], - ] - ], - 1, - false - ], - [ - [ - 'providers' => [ - 'facebook' => [ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - ], - ], - - ], - [ - 'providers' => [ - 'instagram' => [ - 'className' => 'League\OAuth2\Client\Provider\Instagram', - ] - ] - ], - 2, - false - ], - [ - [], - [], - 0, - true - ] - ]; - } -} diff --git a/tests/TestCase/Authenticator/SocialAuthenticatorTest.php b/tests/TestCase/Authenticator/SocialAuthenticatorTest.php new file mode 100644 index 000000000..981f63ea2 --- /dev/null +++ b/tests/TestCase/Authenticator/SocialAuthenticatorTest.php @@ -0,0 +1,610 @@ +Provider = $this->getMockBuilder('\League\OAuth2\Client\Provider\Facebook')->setConstructorArgs([ + [ + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + [], + ])->setMethods([ + 'getAccessToken', 'getState', 'getAuthorizationUrl', 'getResourceOwner', + ])->getMock(); + + $config = [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => $this->Provider, + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'options' => [ + 'state' => '__TEST_STATE__', + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => null, + ], + ]; + Configure::write('OAuth.providers.facebook', $config); + + $this->Request = ServerRequestFactory::fromGlobals(); + } + + /** + * Test authenticate method without social service + * + * @return void + */ + public function testAuthenticateNoSocialService() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $result = $Authenticator->authenticate($this->Request, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::FAILURE_CREDENTIALS_MISSING, $result->getStatus()); + $actual = $result->getData(); + $this->assertEmpty($actual); + } + + /** + * Test authenticate method with successfull authentication + * + * @return void + */ + public function testAuthenticateSuccessfullyAuthenticated() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); + + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $this->Request = $this->Request->withAttribute('socialService', $service); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $result = $Authenticator->authenticate($this->Request, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::SUCCESS, $result->getStatus()); + $actual = $result->getData(); + $this->assertInstanceOf(User::class, $actual); + $this->assertEquals('test@gmail.com', $actual->email); + } + + /** + * Test authenticate method with error, getRawData is null + * + * @return void + */ + public function testAuthenticateGetRawDataNull() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); + + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->throwException(new \UnexpectedValueException('User not found'))); + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $this->Request = $this->Request->withAttribute('socialService', $service); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $result = $Authenticator->authenticate($this->Request, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::FAILURE_IDENTITY_NOT_FOUND, $result->getStatus()); + $actual = $result->getData(); + $this->assertEmpty($actual); + } + + /** + * Test authenticate method when social users does not have email + * + * @return void + */ + public function testAuthenticateErrorNoEmail() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); + + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $this->Request = $this->Request->withAttribute('socialService', $service); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $result = false; + try { + $Authenticator->authenticate($this->Request, $Response); + } catch (SocialAuthenticationException $e) { + $rawData = ['token' => $Token] + $user->toArray(); + $mapper = new MapUser(); + $expected = [ + 'rawData' => $mapper($service, $rawData), + ]; + $actual = $e->getAttributes(); + $this->assertEquals($expected, $actual); + $this->assertEquals(400, $e->getCode()); + + $this->assertInstanceOf(MissingEmailException::class, $e->getPrevious()); + $result = true; + } + $this->assertTrue($result); + } + + /** + * Test authenticate method when social identifier return null + * + * @return void + */ + public function testAuthenticateIdentifierReturnedNull() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); + + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $this->Request = $this->Request->withAttribute('socialService', $service); + $identifiers = new IdentifierCollection([]); + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $Authenticator->authenticate($this->Request, $Response); + $result = $Authenticator->authenticate($this->Request, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::FAILURE_IDENTITY_NOT_FOUND, $result->getStatus()); + $actual = $result->getData(); + $this->assertEmpty($actual); + } + + /** + * Data provider for testAuthenticateErrorException + * + * @return array + */ + public function dataProviderAuthenticateErrorException() + { + return [ + [ + new AccountNotActiveException('Not Active'), + SocialAuthenticator::FAILURE_ACCOUNT_NOT_ACTIVE, + ], + [ + new UserNotActiveException('Not Active'), + SocialAuthenticator::FAILURE_USER_NOT_ACTIVE, + ], + ]; + } + + /** + * Test authenticate method with successfull authentication + * + * @param \Exception $exception thrown exception + * @param string $status expected status from Result object + * @dataProvider dataProviderAuthenticateErrorException + * @return void + */ + public function testAuthenticateErrorException($exception, $status) + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); + + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $this->Request = $this->Request->withAttribute('socialService', $service); + $identifiers = $this->getMockBuilder(IdentifierCollection::class)->getMock(); + $identifiers->expects($this->once()) + ->method('identify') + ->will($this->throwException($exception)); + + $Authenticator = new SocialAuthenticator($identifiers); + $Response = new Response(); + $result = $Authenticator->authenticate($this->Request, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals($status, $result->getStatus()); + $actual = $result->getData(); + $this->assertEmpty($actual); + } +} diff --git a/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php b/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php new file mode 100644 index 000000000..949153707 --- /dev/null +++ b/tests/TestCase/Authenticator/SocialPendingEmailAuthenticatorTest.php @@ -0,0 +1,187 @@ +Request = ServerRequestFactory::fromGlobals(); + } + + /** + * testAuthenticate + * + * @return void + */ + public function testAuthenticateInvalidUrl() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $user = $this->getUserData(); + $requestNoEmail = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/users/social-email-invalid'], + [], + [] + ); + $requestNoEmail->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + $Response = new Response(); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialPendingEmailAuthenticator($identifiers); + $result = $Authenticator->authenticate($requestNoEmail, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::FAILURE_OTHER, $result->getStatus()); + } + + /** + * testAuthenticate + * + * @return void + */ + public function testAuthenticateBaseFailed() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/social-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $user = $this->getUserData(); + $requestNoEmail = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/social-email', 'PHP_SELF' => ''], + [], + [] + ); + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/users/social-email', 'PHP_SELF' => ''], + [], + ['email' => 'testAuthenticateBaseFailed@example.com'] + ); + Configure::write('Users.Email.validate', false); + $request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + $requestNoEmail->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + $Response = new Response(); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $Authenticator = new SocialPendingEmailAuthenticator($identifiers); + $result = $Authenticator->authenticate($requestNoEmail, $Response); + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::FAILURE_CREDENTIALS_MISSING, $result->getStatus()); + + $Authenticator = new SocialPendingEmailAuthenticator($identifiers); + $result = $Authenticator->authenticate($request, $Response); + + $this->assertInstanceOf(Result::class, $result); + $this->assertEquals(Result::SUCCESS, $result->getStatus()); + $data = $result->getData(); + $this->assertInstanceOf(User::class, $data); + $this->assertEquals('testAuthenticateBaseFailed@example.com', $data['email']); + } + + /** + * Get social user data for test + * + * @return mixed + */ + protected function getUserData() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $data = [ + 'token' => $Token, + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]; + + $mapper = new Facebook(); + $user = $mapper($data); + $user['provider'] = 'facebook'; + $user['validated'] = true; + + return $user; + } +} diff --git a/tests/TestCase/Controller/Component/GoogleAuthenticatorComponentTest.php b/tests/TestCase/Controller/Component/GoogleAuthenticatorComponentTest.php deleted file mode 100644 index f708e7f6f..000000000 --- a/tests/TestCase/Controller/Component/GoogleAuthenticatorComponentTest.php +++ /dev/null @@ -1,136 +0,0 @@ -backupUsersConfig = Configure::read('Users'); - - Router::reload(); - Plugin::routes('CakeDC/Users'); - Router::connect('/route/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword' - ]); - Router::connect('/notAllowed/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'edit' - ]); - - Security::setSalt('YJfIxfs2guVoUubWDYhG93b0qyJfIxfs2guwvniR2G0FgaC9mi'); - Configure::write('App.namespace', 'Users'); - Configure::write('Users.GoogleAuthenticator.login', true); - - $this->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is', 'method']) - ->getMock(); - $this->request->expects($this->any())->method('is')->will($this->returnValue(true)); - $this->response = $this->getMockBuilder('Cake\Http\Response') - ->setMethods(['stop']) - ->getMock(); - $this->Controller = new Controller($this->request, $this->response); - $this->Registry = $this->Controller->components(); - $this->Controller->GoogleAuthenticator = new GoogleAuthenticatorComponent($this->Registry); - } - - /** - * tearDown method - * - * @return void - */ - public function tearDown() - { - parent::tearDown(); - - $_SESSION = []; - unset($this->Controller, $this->GoogleAuthenticator); - Configure::write('Users', $this->backupUsersConfig); - Configure::write('Users.GoogleAuthenticator.login', false); - } - - /** - * Test initialize - * - */ - public function testInitialize() - { - $this->Controller->GoogleAuthenticator = new GoogleAuthenticatorComponent($this->Registry); - $this->assertInstanceOf('CakeDC\Users\Controller\Component\GoogleAuthenticatorComponent', $this->Controller->GoogleAuthenticator); - } - - /** - * test base64 qr-code returned from component - * @return void - */ - public function testgetQRCodeImageAsDataUri() - { - $this->Controller->GoogleAuthenticator->initialize([]); - $result = $this->Controller->GoogleAuthenticator->getQRCodeImageAsDataUri('test@localhost.com', '123123'); - - $this->assertContains('data:image/png;base64', $result); - } - - /** - * Making sure we return secret - * @return void - */ - public function testCreateSecret() - { - $this->Controller->GoogleAuthenticator->initialize([]); - $result = $this->Controller->GoogleAuthenticator->createSecret(); - $this->assertNotEmpty($result); - } - - /** - * Testing code verification in the component - * @return void - */ - public function testVerifyCode() - { - $this->Controller->GoogleAuthenticator->initialize([]); - $secret = $this->Controller->GoogleAuthenticator->createSecret(); - $verificationCode = $this->Controller->GoogleAuthenticator->tfa->getCode($secret); - - $verified = $this->Controller->GoogleAuthenticator->verifyCode($secret, $verificationCode); - $this->assertTrue($verified); - } -} diff --git a/tests/TestCase/Controller/Component/RememberMeComponentTest.php b/tests/TestCase/Controller/Component/RememberMeComponentTest.php deleted file mode 100644 index e6b117686..000000000 --- a/tests/TestCase/Controller/Component/RememberMeComponentTest.php +++ /dev/null @@ -1,194 +0,0 @@ -request = new ServerRequest('controller_posts/index'); - $this->request = $this->request->withParam('pass', []); - $this->controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(['redirect']) - ->setConstructorArgs([$this->request]) - ->getMock(); - $this->registry = new ComponentRegistry($this->controller); - $this->rememberMeComponent = new RememberMeComponent($this->registry, []); - } - - /** - * tearDown method - * - * @return void - */ - public function tearDown() - { - unset($this->rememberMeComponent); - - parent::tearDown(); - } - - /** - * Test initialize method - * - * @return void - */ - public function testInitialize() - { - $cookieOptions = [ - 'expires' => '1 month', - 'httpOnly' => true, - 'path' => '', - 'domain' => '', - 'secure' => false, - 'key' => '2a20bac195a9eb2e28f05b7ac7090afe599365a8fe480b7d8a5ce0f79687346e', - 'encryption' => 'aes', - 'enabled' => false - ]; - $this->assertEquals($cookieOptions, $this->rememberMeComponent->Cookie->configKey('remember_me')); - } - - /** - * Test initialize method - * - * @return void - */ - public function testInitializeException() - { - $salt = Security::getSalt(); - Security::setSalt('too small'); - try { - $this->rememberMeComponent = new RememberMeComponent($this->registry, []); - } catch (InvalidArgumentException $ex) { - $this->assertEquals('Invalid app salt, app salt must be at least 256 bits (32 bytes) long', $ex->getMessage()); - } - - Security::setSalt($salt); - } - - /** - * Test - * - * @return void - */ - public function testSetLoginCookie() - { - $event = new Event('event'); - $this->rememberMeComponent->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user']) - ->disableOriginalConstructor() - ->getMock(); - $this->rememberMeComponent->Auth->expects($this->once()) - ->method('user') - ->with('id') - ->will($this->returnValue(1)); - $this->rememberMeComponent->Cookie = $this->getMockBuilder('Cake\Controller\Component\CookieComponent') - ->setMethods(['write']) - ->disableOriginalConstructor() - ->getMock(); - $this->rememberMeComponent->request = (new ServerRequest('/'))->withEnv('HTTP_USER_AGENT', 'user-agent') - ->withData(Configure::read('Users.Key.Data.rememberMe'), '1'); - $this->rememberMeComponent->Cookie->expects($this->once()) - ->method('write') - ->with('remember_me', ['id' => 1, 'user_agent' => 'user-agent']); - $this->rememberMeComponent->setLoginCookie($event); - } - - /** - * Test - * - * @return void - */ - public function testBeforeFilter() - { - $event = new Event('event'); - $this->rememberMeComponent->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); - $this->rememberMeComponent->Auth->expects($this->once()) - ->method('user'); - $user = ['id' => 1]; - $this->rememberMeComponent->Auth->expects($this->once()) - ->method('identify') - ->will($this->returnValue($user)); - $this->rememberMeComponent->Auth->expects($this->once()) - ->method('setUser') - ->with($user); - $this->controller->expects($this->once()) - ->method('redirect') - ->with('/controller_posts/index'); - $this->rememberMeComponent->beforeFilter($event); - } - - /** - * Test - * - * @return void - */ - public function testBeforeFilterNotIdentified() - { - $event = new Event('event'); - $this->rememberMeComponent->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); - $this->rememberMeComponent->Auth->expects($this->at(0)) - ->method('user'); - $this->rememberMeComponent->Auth->expects($this->at(1)) - ->method('identify'); - - $this->assertNull($this->rememberMeComponent->beforeFilter($event)); - } - - /** - * Test - * - * @return void - */ - public function testBeforeFilterUserLoggedIn() - { - $event = new Event('event'); - $this->rememberMeComponent->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); - $this->rememberMeComponent->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue([ - 'id' => 1, - ])); - $this->assertNull($this->rememberMeComponent->beforeFilter($event)); - } -} diff --git a/tests/TestCase/Controller/Component/SetupComponentTest.php b/tests/TestCase/Controller/Component/SetupComponentTest.php new file mode 100644 index 000000000..d685949a2 --- /dev/null +++ b/tests/TestCase/Controller/Component/SetupComponentTest.php @@ -0,0 +1,102 @@ +Controller = new Controller(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + unset($this->Controller, $this->Component); + + parent::tearDown(); + } + + /** + * Data provider for testInitialization + * + * @return array + */ + public function dataProviderInitialization() + { + return [ + [true, true, true], + [false, true, true], + [true, false, true], + [true, true, false], + [false, false, false], + ]; + } + + /** + * Test initial setup + * + * @param bool $authentication Should use authentication component + * @param booll $authorization Should use authorization component + * @param booll $oneTimePass Should use OneTimePassword component + * @throws \Exception + * @dataProvider dataProviderInitialization + * @return void + */ + public function testInitialization($authentication, $authorization, $oneTimePass) + { + Configure::write('Auth.AuthenticationComponent.load', $authentication); + Configure::write('Auth.AuthorizationComponent.enable', $authorization); + Configure::write('OneTimePasswordAuthenticator.login', $oneTimePass); + $registry = new ComponentRegistry($this->Controller); + $this->Component = new SetupComponent($registry); + $this->Component->initialize([]); + $this->assertSame($authentication, $this->Controller->components()->has('Authentication')); + $this->assertSame($authorization, $this->Controller->components()->has('Authorization')); + $this->assertSame($oneTimePass, $this->Controller->components()->has('OneTimePasswordAuthenticator')); + } +} diff --git a/tests/TestCase/Controller/Component/UsersAuthComponentTest.php b/tests/TestCase/Controller/Component/UsersAuthComponentTest.php deleted file mode 100644 index 922646bda..000000000 --- a/tests/TestCase/Controller/Component/UsersAuthComponentTest.php +++ /dev/null @@ -1,516 +0,0 @@ -backupUsersConfig = Configure::read('Users'); - - Router::reload(); - Plugin::routes('CakeDC/Users'); - Router::connect('/route/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword' - ]); - Router::connect('/notAllowed/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'edit' - ]); - Security::setSalt('YJfIxfs2guVoUubWDYhG93b0qyJfIxfs2guwvniR2G0FgaC9mi'); - Configure::write('App.namespace', 'Users'); - $this->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is', 'method']) - ->getMock(); - $this->request->expects($this->any())->method('is')->will($this->returnValue(true)); - $this->response = $this->getMockBuilder('Cake\Http\Response') - ->setMethods(['stop']) - ->getMock(); - $this->Controller = new Controller($this->request, $this->response); - $this->Controller->setName('Users'); - $this->Registry = $this->Controller->components(); - $this->Controller->UsersAuth = new UsersAuthComponent($this->Registry); - } - - /** - * tearDown method - * - * @return void - */ - public function tearDown() - { - parent::tearDown(); - - $_SESSION = []; - unset($this->Controller, $this->UsersAuth); - Configure::write('Users', $this->backupUsersConfig); - } - - /** - * Test initialize - * - */ - public function testInitialize() - { - $this->Registry->unload('Auth'); - $this->Controller->UsersAuth = new UsersAuthComponent($this->Registry); - $this->assertInstanceOf('CakeDC\Users\Controller\Component\UsersAuthComponent', $this->Controller->UsersAuth); - } - - /** - * Test initialize with not rememberMe component needed - * - */ - public function testInitializeNoRequiredRememberMe() - { - Configure::write('Users.RememberMe.active', false); - $class = 'CakeDC\Users\Controller\Component\UsersAuthComponent'; - $this->Controller->UsersAuth = $this->getMockBuilder($class) - ->setMethods(['_loadRememberMe', '_initAuth', '_loadSocialLogin', '_attachPermissionChecker']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->UsersAuth->expects($this->once()) - ->method('_initAuth'); - $this->Controller->UsersAuth->expects($this->never()) - ->method('_loadRememberMe'); - $this->Controller->UsersAuth->initialize([]); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUserNotLoggedIn() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(false)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertFalse($result); - } - - /** - * test The user is not logged in, but the controller action is public $this->Auth->allow() - * - * @return void - */ - public function testIsUrlAuthorizedUserNotLoggedInActionAllowed() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->allowedActions = ['requestResetPassword']; - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test The user is logged in and not allowed by rules to access this action, - * but the controller action is public $this->Auth->allow() - * - * @return void - */ - public function testIsUrlAuthorizedUserLoggedInNotAllowedActionAllowed() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/notAllowed', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->allowedActions = ['edit']; - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test The user is logged in and allowed by rules to access this action, - * and the controller action is public $this->Auth->allow() - * - * @return void - */ - public function testIsUrlAuthorizedUserLoggedInAllowedActionAllowed() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->allowedActions = ['requestResetPassword']; - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedNoUrl() - { - $event = new Event('event'); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertFalse($result); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUrlRelativeString() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(['id' => 1])); - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $this->callback(function ($subject) { - return $subject->getAttribute('params') === [ - 'pass' => [], - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - '_matchedRoute' => '/route/*', - ]; - })) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test - * - * @return void - * @expectedException \Cake\Routing\Exception\MissingRouteException - */ - public function testIsUrlAuthorizedMissingRouteString() - { - $event = new Event('event'); - $event->setData([ - 'url' => '/missingRoute', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - } - - /** - * test - * - * @return void - * @expectedException \Cake\Routing\Exception\MissingRouteException - */ - public function testIsUrlAuthorizedMissingRouteArray() - { - $event = new Event('event'); - $event->setData([ - 'url' => [ - 'controller' => 'missing', - 'action' => 'missing', - ], - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUrlAbsoluteForCurrentAppString() - { - $event = new Event('event'); - $event->setData([ - 'url' => Router::fullBaseUrl() . '/route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(['id' => 1])); - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $this->callback(function ($subject) { - return $subject->getAttribute('params') === [ - 'pass' => [], - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - '_matchedRoute' => '/route/*', - ]; - })) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUrlRelativeForCurrentAppString() - { - $event = new Event('event'); - $event->setData([ - 'url' => 'route', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(['id' => 1])); - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $this->callback(function ($subject) { - return $subject->getAttribute('params') === [ - 'pass' => [], - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - '_matchedRoute' => '/route/*', - ]; - })) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * - * - * @return void - */ - public function testIsUrlAuthorizedUrlAbsoluteForOtherAppString() - { - $event = new Event('event'); - $event->setData([ - 'url' => 'http://example.com', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->never()) - ->method('user'); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUrlArray() - { - $event = new Event('event'); - $event->setData([ - 'url' => [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - 'pass-one' - ], - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(['id' => 1])); - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $this->callback(function ($subject) { - return $subject->getAttribute('params') === [ - 'pass' => ['pass-one'], - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - '_matchedRoute' => '/route/*', - ]; - })) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * When application is installed using a base folder, we need to ensure array routes are - * normalized too to remove the base from the url used for matching the rules - * - * @see https://github.com/CakeDC/users/issues/538 - * - * @return void - */ - public function testIsUrlAuthorizedBaseUrl() - { - Configure::write('App.base', 'app'); - Router::pushRequest(new ServerRequest([ - 'base' => '/app', - 'url' => '/', - ])); - $event = new Event('event'); - $event->setData([ - 'url' => [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - ], - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(['id' => 1])); - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $this->callback(function ($subject) { - return $subject->getAttribute('params') === [ - 'pass' => [], - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - '_matchedRoute' => '/route/*', - ]; - })) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - /** - * test The user is logged in and allowed by rules to access this action, - * and we are checking another controller action not allowed - * - * this case would prevent permissions checked for allowed actions in another controller - * @see https://github.com/CakeDC/users/issues/527 for a workaround if you need to - * check allowed on another controller - * - * @return void - */ - public function testIsUrlAuthorizedUserLoggedInAllowedActionAllowedAnotherController() - { - Router::connect('/route-another-controller/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'AnotherController', - 'action' => 'requestResetPassword' - ]); - $event = new Event('event'); - $event->setData([ - 'url' => '/route-another-controller', - ]); - $this->Controller->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'isAuthorized']) - ->disableOriginalConstructor() - ->getMock(); - $this->Controller->Auth->allowedActions = ['requestResetPassword']; - $this->Controller->Auth->expects($this->once()) - ->method('user') - ->will($this->returnValue(false)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertFalse($result); - } -} diff --git a/tests/TestCase/Controller/SocialAccountsControllerTest.php b/tests/TestCase/Controller/SocialAccountsControllerTest.php index 296f15f2f..47a544d7e 100644 --- a/tests/TestCase/Controller/SocialAccountsControllerTest.php +++ b/tests/TestCase/Controller/SocialAccountsControllerTest.php @@ -1,24 +1,22 @@ 'Debug' - ]); + TransportFactory::setConfig('test', ['className' => 'Debug']); $this->configEmail = Email::getConfig('default'); Email::drop('default'); Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); - $request = new ServerRequest('/users/users/index'); + $request = new ServerRequest(['url' => '/users/users/index']); $request = $request->withParam('plugin', 'CakeDC/Users'); $this->Controller = $this->getMockBuilder('CakeDC\Users\Controller\SocialAccountsController') - ->setMethods(['redirect', 'render']) + ->onlyMethods(['redirect', 'render']) ->setConstructorArgs([$request, null, 'SocialAccounts']) ->getMock(); - $this->Controller->SocialAccounts = $this->getMockForModel('CakeDC\Users.SocialAccounts', ['sendSocialValidationEmail'], [ - 'className' => 'CakeDC\Users\Model\Table\SocialAccountsTable' - ]); } /** @@ -74,10 +67,10 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { Email::drop('default'); - Email::dropTransport('test'); + TransportFactory::drop('test'); //Email::setConfig('default', $this->configEmail); Configure::write('Opauth', $this->configOpauth); @@ -95,9 +88,9 @@ public function testValidateAccountHappy() { $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->validateAccount('Facebook', 'reference-1-1234', 'token-1234'); - $this->assertEquals('Account validated successfully', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Account validated successfully', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -109,9 +102,9 @@ public function testValidateAccountInvalidToken() { $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->validateAccount('Facebook', 'reference-1-1234', 'token-not-found'); - $this->assertEquals('Invalid token and/or social account', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Invalid token and/or social account', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -123,9 +116,9 @@ public function testValidateAccountAlreadyActive() { $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->validateAccount('Twitter', 'reference-1-1234', 'token-1234'); - $this->assertEquals('Social Account already active', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Social Account already active', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -136,7 +129,7 @@ public function testValidateAccountAlreadyActive() public function testResendValidationHappy() { $behaviorMock = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialAccountBehavior') - ->setMethods(['sendSocialValidationEmail']) + ->onlyMethods(['sendSocialValidationEmail']) ->setConstructorArgs([$this->Controller->SocialAccounts]) ->getMock(); $this->Controller->SocialAccounts->behaviors()->set('SocialAccount', $behaviorMock); @@ -145,10 +138,10 @@ public function testResendValidationHappy() ->will($this->returnValue(true)); $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->resendValidation('Facebook', 'reference-1-1234'); - $this->assertEquals('Email sent successfully', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Email sent successfully', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -159,7 +152,7 @@ public function testResendValidationHappy() public function testResendValidationEmailError() { $behaviorMock = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialAccountBehavior') - ->setMethods(['sendSocialValidationEmail']) + ->onlyMethods(['sendSocialValidationEmail']) ->setConstructorArgs([$this->Controller->SocialAccounts]) ->getMock(); $this->Controller->SocialAccounts->behaviors()->set('SocialAccount', $behaviorMock); @@ -168,10 +161,10 @@ public function testResendValidationEmailError() ->will($this->returnValue(false)); $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->resendValidation('Facebook', 'reference-1-1234'); - $this->assertEquals('Email could not be sent', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Email could not be sent', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -183,9 +176,9 @@ public function testResendValidationInvalid() { $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->resendValidation('Facebook', 'reference-invalid'); - $this->assertEquals('Invalid account', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Invalid account', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -197,8 +190,8 @@ public function testResendValidationAlreadyActive() { $this->Controller->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); $this->Controller->validateAccount('Twitter', 'reference-1-1234', 'token-1234'); - $this->assertEquals('Social Account already active', $this->Controller->request->getSession()->read('Flash.flash.0.message')); + $this->assertEquals('Social Account already active', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } } diff --git a/tests/TestCase/Controller/Traits/BaseTraitTest.php b/tests/TestCase/Controller/Traits/BaseTraitTest.php index a7ff42461..693940e8a 100644 --- a/tests/TestCase/Controller/Traits/BaseTraitTest.php +++ b/tests/TestCase/Controller/Traits/BaseTraitTest.php @@ -1,23 +1,40 @@ loadPlugins(['CakeDC/Users' => ['routes' => true]]); $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) - ->getMockForTrait(); + ->getMock(); $this->Trait->expects($this->any()) ->method('getUsersTable') ->will($this->returnValue($this->table)); } catch (PHPUnit_Framework_MockObject_RuntimeException $ex) { debug($ex); - $this->fail("Unit tests extending BaseTraitTest should declare the trait class name in the \$traitClassName variable before calling setUp()"); + $this->fail('Unit tests extending BaseTraitTest should declare the trait class name in the $traitClassName variable before calling setUp()'); } if ($this->mockDefaultEmail) { - Email::setConfigTransport('test', [ - 'className' => 'Debug' + TransportFactory::setConfig('test', [ + 'className' => 'Debug', ]); $this->configEmail = Email::getConfig('default'); Email::drop('default'); Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); } } @@ -78,12 +112,12 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->table, $this->Trait); if ($this->mockDefaultEmail) { Email::drop('default'); - Email::dropTransport('test'); + TransportFactory::drop('test'); //Email::setConfig('default', $this->setConfigEmail); } parent::tearDown(); @@ -92,7 +126,7 @@ public function tearDown() /** * Mock session and mock session attributes * - * @return void + * @return \Cake\Http\Session */ protected function _mockSession($attributes) { @@ -102,10 +136,13 @@ protected function _mockSession($attributes) $session->write($field, $value); } - $this->Trait->request + $this->Trait + ->getRequest() ->expects($this->any()) - ->method('session') + ->method('getSession') ->willReturn($session); + + return $session; } /** @@ -118,16 +155,17 @@ protected function _mockRequestGet($withSession = false) $methods = ['is', 'referer', 'getData']; if ($withSession) { - $methods[] = 'session'; + $methods[] = 'getSession'; } - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') + $request = $this->getMockBuilder('Cake\Http\ServerRequest') ->setMethods($methods) ->getMock(); - $this->Trait->request->expects($this->any()) + $request->expects($this->any()) ->method('is') ->with('post') ->will($this->returnValue(false)); + $this->Trait->setRequest($request); } /** @@ -151,13 +189,14 @@ protected function _mockFlash() */ protected function _mockRequestPost($with = 'post') { - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') + $request = $this->getMockBuilder('Cake\Http\ServerRequest') ->setMethods(['is', 'getData', 'allow']) ->getMock(); - $this->Trait->request->expects($this->any()) + $request->expects($this->any()) ->method('is') ->with($with) ->will($this->returnValue(true)); + $this->Trait->setRequest($request); } /** @@ -167,34 +206,134 @@ protected function _mockRequestPost($with = 'post') */ protected function _mockAuthLoggedIn($user = []) { - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); $user += [ 'id' => '00000000-0000-0000-0000-000000000001', 'password' => '12345', ]; - $this->Trait->Auth->expects($this->any()) - ->method('identify') - ->will($this->returnValue($user)); - $this->Trait->Auth->expects($this->any()) - ->method('user') - ->with('id') - ->will($this->returnValue($user['id'])); + + $this->_mockAuthentication($user); + } + + /** + * Mock the Authentication service + * + * @param array $user + * @param array $failures + * @param \Authentication\Identifier\IdentifierCollection $identifiers custom identifiers collection + * @return void + */ + protected function _mockAuthentication($user = null, $failures = [], $identifiers = null) + { + if ($identifiers === null) { + $passwordIdentifier = $this->getMockBuilder(PasswordIdentifier::class) + ->setMethods(['needsPasswordRehash']) + ->getMock(); + $passwordIdentifier->expects($this->any()) + ->method('needsPasswordRehash') + ->willReturn(false); + $identifiers = new IdentifierCollection([]); + $identifiers->set('Password', $passwordIdentifier); + } + + $config = [ + 'identifiers' => [ + 'Authentication.Password', + ], + 'authenticators' => [ + 'Authentication.Session', + 'Authentication.Form', + ], + ]; + $authentication = $this->getMockBuilder(AuthenticationService::class)->setConstructorArgs([$config])->setMethods([ + 'getResult', + 'getFailures', + 'identifiers', + ])->getMock(); + + if ($user) { + $user = new User($user); + $identity = new Identity($user); + $result = new Result($user, Result::SUCCESS); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('identity', $identity)); + } else { + $result = new Result($user, Result::FAILURE_CREDENTIALS_MISSING); + } + + $authentication->expects($this->any()) + ->method('getResult') + ->will($this->returnValue($result)); + + $authentication->expects($this->any()) + ->method('getFailures') + ->will($this->returnValue($failures)); + + $authentication->expects($this->any()) + ->method('identifiers') + ->will($this->returnValue($identifiers)); + + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('authentication', $authentication)); + + $controller = new Controller($this->Trait->getRequest()); + $registry = new ComponentRegistry($controller); + $this->Trait->Authentication = new AuthenticationComponent($registry, [ + 'loginRedirect' => $this->successLoginRedirect, + 'logoutRedirect' => $this->logoutRedirect, + 'loginAction' => $this->loginAction, + ]); } /** - * Mock the Auth component + * Mock the Authentication service with a Password Rehash being required. * + * @param array $user + * @param array $failures * @return void */ - protected function _mockAuth() + protected function _mockAuthenticationWithPasswordRehash($user = null, $failures = []) { - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); + $config = [ + 'identifiers' => [ + 'Authentication.Password', + ], + 'authenticators' => [ + 'Authentication.Session', + 'Authentication.Form', + ], + ]; + $authentication = $this->getMockBuilder(AuthenticationService::class)->setConstructorArgs([$config])->setMethods([ + 'getResult', + 'getFailures', + 'identifiers', + ])->getMock(); + $authentication->expects($this->any()) + ->method('identifiers') + ->willReturn($identifiers); + if ($user) { + $user = is_object($user) ? $user : new User($user); + $identity = new Identity($user); + $result = new Result($user, Result::SUCCESS); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('identity', $identity)); + } else { + $result = new Result($user, Result::FAILURE_CREDENTIALS_MISSING); + } + + $authentication->expects($this->any()) + ->method('getResult') + ->will($this->returnValue($result)); + + $authentication->expects($this->any()) + ->method('getFailures') + ->will($this->returnValue($failures)); + + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('authentication', $authentication)); + + //$controller = new Controller($this->Trait->getRequest()); + $registry = new ComponentRegistry($this->Trait); + $this->Trait->Authentication = new AuthenticationComponent($registry, [ + 'loginRedirect' => $this->successLoginRedirect, + 'logoutRedirect' => $this->logoutRedirect, + 'loginAction' => $this->loginAction, + ]); } /** @@ -204,14 +343,14 @@ protected function _mockAuth() * @param array $result array of data * @return void */ - protected function _mockDispatchEvent(Event $event = null, $result = []) + protected function _mockDispatchEvent(?Event $event = null, $result = []) { if (is_null($event)) { $event = new Event('cool-name-here'); } if (!empty($result)) { - $event->result = new Entity($result); + $event->setResult(new Entity($result)); } $this->Trait->expects($this->any()) ->method('dispatchEvent') diff --git a/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php index 3511fd9a5..8bad2e04b 100644 --- a/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php +++ b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php @@ -1,11 +1,13 @@ controller = $this->getMockBuilder('Cake\Controller\Controller') @@ -25,7 +27,7 @@ public function setUp() $this->controller->Trait = $this->getMockForTrait('CakeDC\Users\Controller\Traits\CustomUsersTableTrait'); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); } diff --git a/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php new file mode 100644 index 000000000..9bf8d74d4 --- /dev/null +++ b/tests/TestCase/Controller/Traits/Integration/LoginTraitIntegrationTest.php @@ -0,0 +1,297 @@ +get('CakeDC/Users.Users') + ->get($id); + + $this->session(['Auth' => $user]); + } + + /** + * Test login action with get request + * + * @return void + */ + public function testRedirectToLogin() + { + $this->enableRetainFlashMessages(); + $this->get('/pages/home'); + $this->assertRedirectContains('/login?redirect=http%3A%2F%2Flocalhost%2Fpages%2Fhome'); + $this->assertFlashMessage('You are not authorized to access that location.'); + } + + /** + * Test login action with get request + * + * @return void + */ + public function testLoginGetRequestNoSocialLogin() + { + EventManager::instance()->on('TestApp.afterPluginBootstrap', function () { + Configure::write(['Users.Social.login' => false]); + }); + + $this->get('/login'); + $this->assertResponseOk(); + $this->assertResponseNotContains('Username or password is incorrect'); + $this->assertResponseContains(''); + $this->assertResponseContains('Please enter your username and password'); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains('Register'); + $this->assertResponseContains('Reset Password'); + + $this->assertResponseNotContains('auth/facebook'); + $this->assertResponseNotContains('auth/twitter'); + $this->assertResponseNotContains('auth/google'); + $this->assertResponseNotContains('auth/cognito'); + $this->assertResponseNotContains('auth/amazon'); + } + + /** + * Test login action with get request + * + * @return void + */ + public function testLoginGetRequest() + { + $this->get('/login'); + $this->assertResponseOk(); + $this->assertResponseNotContains('Username or password is incorrect'); + $this->assertResponseContains(''); + $this->assertResponseContains('Please enter your username and password'); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains('Register'); + $this->assertResponseContains('Reset Password'); + + $this->assertResponseContains('Sign in with Facebook'); + $this->assertResponseContains('Sign in with Twitter'); + $this->assertResponseContains('Sign in with Google'); + $this->assertResponseNotContains('/auth/cognito'); + $this->assertResponseNotContains('/auth/amazon'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestInvalidPassword() + { + $this->post('/login', [ + 'username' => 'user-2', + 'password' => '123456789', + ]); + $this->assertResponseOk(); + $this->assertResponseContains('Username or password is incorrect'); + $this->assertResponseContains(''); + $this->assertResponseContains('Please enter your username and password'); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestRightPasswordWithBaseRedirectUrl() + { + $this->enableRetainFlashMessages(); + $this->post('/login?redirect=http://localhost/articles', [ + 'username' => 'user-2', + 'password' => '12345', + ]); + $this->assertRedirect('http://localhost/articles'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestRightPasswordNoBaseRedirectUrl() + { + $this->enableRetainFlashMessages(); + $this->post('/login', [ + 'username' => 'user-2', + 'password' => '12345', + ]); + $this->assertRedirect('/pages/home'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestRightPasswordWithBaseRedirectUrlButCantAccess() + { + $this->enableRetainFlashMessages(); + $this->post('/login?redirect=http://localhost/articles', [ + 'username' => 'user-4', + 'password' => '12345', + ]); + $this->assertRedirect('/pages/home'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestRightPasswordIsEnabledOTP() + { + EventManager::instance()->on('TestApp.afterPluginBootstrap', function () { + Configure::write(['OneTimePasswordAuthenticator.login' => true]); + }); + $this->enableRetainFlashMessages(); + $this->post('/login', [ + 'username' => 'user-2', + 'password' => '12345', + ]); + $this->assertRedirectContains('/verify'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testLoginPostRequestRightPasswordIsEnabledU2f() + { + EventManager::instance()->on('TestApp.afterPluginBootstrap', function () { + Configure::write(['U2f.enabled' => true]); + }); + $this->enableRetainFlashMessages(); + $this->post('/login', [ + 'username' => 'user-2', + 'password' => '12345', + ]); + $this->assertRedirectContains('/users/u2f'); + } + + /** + * Test logout action + * + * @return void + */ + public function testLogout() + { + $this->loginAsUserId('00000000-0000-0000-0000-000000000002'); + $this->get('/logout'); + $this->assertRedirect('/login'); + } + + /** + * Test logout action + * + * @return void + */ + public function testLogoutNoUser() + { + $this->get('/logout'); + $this->assertRedirect('/login'); + } + + /** + * Test redirect should not happen if the host is not defined as a known host + * + * @return void + */ + public function testRedirectAfterLoginToHostUnknown() + { + $this->post('/login?redirect=http://unknown.com/', [ + 'username' => 'user-4', + 'password' => '12345', + ]); + $this->assertRedirect('/pages/home'); + } + + /** + * Test redirect should happen for defaul localhost + * + * @return void + */ + public function testRedirectAfterLoginToAllowedHost() + { + Configure::write('Users.AllowedRedirectHosts', ['example.com']); + $this->post('/login?redirect=http://example.com/login', [ + 'username' => 'user-4', + 'password' => '12345', + ]); + // /login is authorized for this user, and example.com is in the allowed hosts + $this->assertRedirect('http://example.com/login'); + } + + public function testRedirectAfterLoginToFullBase(): void + { + $this->post('/login?redirect=http://example.com/login', [ + 'username' => 'user-4', + 'password' => '12345', + ]); + // /login is authorized for this user, and example.com is in the allowed hosts + $this->assertRedirect('http://example.com/login'); + } + + /** + * Test redirect fails if url is not allowed + * + * @return void + */ + public function testRedirectFailsIfUrlNotAllowed() + { + $this->post('/login?redirect=http://localhost/not-allowed', [ + 'username' => 'user-4', + 'password' => '12345', + ]); + $this->assertRedirect('/pages/home'); + } +} diff --git a/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php new file mode 100644 index 000000000..47a9e337d --- /dev/null +++ b/tests/TestCase/Controller/Traits/Integration/PasswordManagementTraitIntegrationTest.php @@ -0,0 +1,112 @@ +get('/users/request-reset-password'); + $this->assertResponseOk(); + $this->assertResponseContains('Please enter your email or username to reset your password'); + $this->assertResponseContains(''); + } + + /** + * Test reset password workflow + * + * @return void + */ + public function testRequestResetPasswordPostValidEmail() + { + $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userBefore = $Table->find()->where(['email' => '4@example.com'])->firstOrFail(); + $this->assertEquals('token-4', $userBefore->token); + $this->enableRetainFlashMessages(); + $this->enableSecurityToken(); + $data = [ + 'reference' => '4@example.com', + ]; + $this->post('/users/request-reset-password', $data); + $this->assertRedirect('/login'); + $this->assertFlashMessage('Please check your email to continue with password reset process'); + $userAfter = $Table->find()->where(['email' => '4@example.com'])->firstOrFail(); + $this->assertNotEquals('token-4', $userAfter->token); + $this->assertNotEmpty($userAfter->token); + + $this->get("/users/reset-password/{$userAfter->token}"); + $this->assertRedirect('/users/change-password'); + + $fieldName = Configure::read('Users.Key.Session.resetPasswordUserId'); + $this->session([ + $fieldName => $userAfter->id, + ]); + $this->get('/users/change-password'); + $this->assertResponseOk(); + + $this->assertResponseContains(''); + $this->assertResponseContains('Please enter the new password'); + $this->assertResponseContains('assertResponseContains('assertResponseContains(''); + + $this->post('/users/change-password', [ + 'password' => '9080706050', + 'password_confirm' => '9080706050', + ]); + $this->assertRedirect('/login'); + $this->assertFlashMessage('Password has been changed successfully'); + } + + /** + * Test login action with post request + * + * @return void + */ + public function testRequestResetPasswordPostInvalidEmail() + { + $email = 'someother.un@example.com'; + $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->assertFalse($Table->exists(['email' => $email])); + $this->enableRetainFlashMessages(); + $this->enableSecurityToken(); + $data = [ + 'reference' => $email, + ]; + $this->post('/users/request-reset-password', $data); + $this->assertResponseOk(); + $this->assertFlashMessage('User someother.un@example.com was not found'); + } +} diff --git a/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php new file mode 100644 index 000000000..ea555a89d --- /dev/null +++ b/tests/TestCase/Controller/Traits/Integration/ProfileTraitIntegrationTest.php @@ -0,0 +1,79 @@ +get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000001'); + + $this->session(['Auth' => $user]); + $this->get('/profile'); + $this->assertResponseOk(); + $this->assertResponseContains('Change Password'); + $this->assertResponseContains('first1 last1'); + $this->assertResponseContains('user-1'); + $this->assertResponseContains('user-1@test.com'); + $this->assertResponseContains('Connected with Facebook'); + $this->assertResponseNotContains('Connect with Facebook'); + $this->assertResponseNotContains('/link-social/facebook'); + $this->assertResponseContains('Connected with Twitter'); + $this->assertResponseNotContains('Connect with Twitter'); + $this->assertResponseNotContains('/link-social/twitter'); + $this->assertResponseContains(' Connect with Google'); + $this->assertResponseNotContains('Connected with Google'); + $this->assertResponseContains('Social Accounts'); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseNotContains(''); + $this->assertResponseNotContains('/link-social/amazon'); + $this->assertResponseNotContains(''); + $this->assertResponseContains('Welcome, first1'); + + $this->get('/users/change-password'); + $this->assertResponseOk(); + + $this->enableSecurityToken(); + $this->post('/users/change-password', [ + 'password' => '98765432102', + 'password_confirm' => '98765432102', + ]); + $this->assertRedirect('/profile'); + $this->assertFlashMessage('Password has been changed successfully'); + } +} diff --git a/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php new file mode 100644 index 000000000..8c7d4e852 --- /dev/null +++ b/tests/TestCase/Controller/Traits/Integration/RegisterTraitIntegrationTest.php @@ -0,0 +1,140 @@ +get('/register'); + $this->assertResponseOk(); + $this->assertResponseContains(''); + $this->assertResponseContains('Add User'); + $this->assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + } + + /** + * Test register action + * + * @return void + */ + public function testRegisterPostWithErrors() + { + $this->enableRetainFlashMessages(); + $this->enableSecurityToken(); + $data = [ + 'username' => 'user1', + 'email' => 'use1sample@example.com', + 'password' => '23423423', + 'password_confirm' => '11', + 'first_name' => '', + 'last_name' => '', + 'tos' => '0', + ]; + $this->post('/register', $data); + $this->assertResponseOk(); + $this->assertFlashMessage('The user could not be saved'); + $this->assertResponseNotContains('The user could not be saved'); + $this->assertResponseContains(''); + $this->assertResponseContains('Add User'); + $this->assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains('assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + } + + /** + * Test register action + * + * @return void + */ + public function testRegisterPostOkay() + { + $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->assertFalse($Table->exists(['username' => 'user1'])); + $this->enableRetainFlashMessages(); + $this->enableSecurityToken(); + $data = [ + 'username' => 'user1', + 'email' => 'use1sample@example.com', + 'password' => '123456', + 'password_confirm' => '123456', + 'first_name' => '', + 'last_name' => '', + 'tos' => '0', + ]; + $this->post('/register', $data); + $this->assertRedirect('/login'); + $this->assertFlashMessage('Please validate your account before log in'); + $user = $Table->find()->where(['username' => 'user1'])->firstOrFail(); + $this->assertFalse($user->active); + + //Validate email + $this->assertNotEmpty($user['token']); + $url = '/users/validate-email/' . $user['token']; + $this->get($url); + $this->assertRedirect('/login'); + $this->assertFlashMessage('User account validated successfully'); + + //If access again get error + $this->get($url); + $this->assertRedirect('/login'); + $this->assertFlashMessage('Token already expired'); + } + + /** + * Test /users/validate-email without token + * + * @throws \Throwable + */ + public function testValidateEmailNoToken() + { + $this->get('/users/validate-email'); + $this->assertRedirect('/login'); + $this->assertFlashMessage('Invalid token or user account already validated'); + } +} diff --git a/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php b/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php new file mode 100644 index 000000000..9b606146e --- /dev/null +++ b/tests/TestCase/Controller/Traits/Integration/SimpleCrudTraitIntegrationTest.php @@ -0,0 +1,138 @@ +get('CakeDC/Users.Users') + ->get($userId); + + $this->session(['Auth' => $user]); + $this->enableRetainFlashMessages(); + $this->get('/users/index'); + $this->assertResponseOk(); + $this->assertResponseContains('Welcome, user'); + $this->assertResponseContains('New Users'); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains('View'); + $this->assertResponseContains('Change password'); + $this->assertResponseContains('Edit'); + $this->assertResponseContains('style="display:none;" method="post" action="/users/delete/00000000-0000-0000-0000-000000000001"'); + $this->assertResponseContains('>Delete<'); + + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains(''); + $this->assertResponseContains('View'); + $this->assertResponseContains('Change password'); + $this->assertResponseContains('Edit'); + $this->assertResponseContains('style="display:none;" method="post" action="/users/delete/00000000-0000-0000-0000-000000000006"'); + + $this->get('/users/change-password/00000000-0000-0000-0000-000000000006'); + $this->assertFlashMessage('Changing another user\'s password is not allowed'); + $this->assertRedirect('/profile'); + + EventManager::instance()->on(Application::EVENT_AFTER_PLUGIN_BOOTSTRAP, function () { + Configure::write('Users.Superuser.allowedToChangePasswords', true); + }); + $this->get('/users/change-password/00000000-0000-0000-0000-000000000005'); + $this->assertResponseOk(); + $this->assertResponseContains(''); + $this->assertResponseContains('assertResponseContains('assertResponseContains(''); + + $this->enableSecurityToken(); + $this->post('/users/change-password/00000000-0000-0000-0000-000000000005', [ + 'password' => '123456', + 'password_confirm' => '123456', + ]); + $this->assertRedirect('/users/index'); + $this->assertFlashMessage('Password has been changed successfully'); + + $this->get('/users/edit/00000000-0000-0000-0000-000000000006'); + $this->assertResponseContains('assertResponseContains('assertResponseContains('Active'); + $this->assertResponseContains('style="display:none;" method="post" action="/users/delete/00000000-0000-0000-0000-000000000006"'); + $this->assertResponseContains('List Users'); + + $this->enableSecurityToken(); + $this->post('/users/edit/00000000-0000-0000-0000-000000000006', [ + 'username' => 'my-new-username', + 'email' => 'crud.email992@example.com', + 'first_name' => 'Joe', + 'last_name' => 'Doe K', + ]); + $this->assertRedirect('/users/index'); + $this->assertFlashMessage('The User has been saved'); + $this->get('/users/view/00000000-0000-0000-0000-000000000006'); + $this->assertResponseOk(); + $this->assertResponseContains('>00000000-0000-0000-0000-000000000006<'); + $this->assertResponseContains('>my-new-username<'); + $this->assertResponseContains('>crud.email992@example.com<'); + $this->assertResponseContains('>Joe<'); + $this->assertResponseContains('>Doe K<'); + $this->assertResponseContains('>token-6<'); + $this->assertResponseContains('Edit User'); + $this->assertResponseContains('New User'); + $this->assertResponseContains('List Users'); + $this->assertResponseContains('style="display:none;" method="post" action="/users/delete/00000000-0000-0000-0000-000000000006"'); + + $this->post('/users/delete/00000000-0000-0000-0000-000000000006'); + $this->assertRedirect('/users/index'); + $this->assertFlashMessage('The User has been deleted'); + + $this->get('/users/index'); + $this->assertResponseOk(); + $this->assertResponseNotContains('00000000-0000-0000-0000-000000000006'); + $this->assertResponseContains('00000000-0000-0000-0000-000000000001'); + } +} diff --git a/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php b/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php index 00ce7b11b..30ed9fe7b 100644 --- a/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php +++ b/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php @@ -1,105 +1,110 @@ oauthConfig === null) { - $this->oauthConfig = Configure::read('OAuth'); - } - $this->traitClassName = 'CakeDC\Users\Controller\Traits\LinkSocialTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; parent::setUp(); $request = new ServerRequest(); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set']) - ->getMockForTrait(); + ->getMock(); $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') ->setMethods(['setConfig']) ->disableOriginalConstructor() ->getMock(); - $this->Trait->request = $request; - } - - /** - * tearDown - * - * @return void - */ - public function tearDown() - { - Configure::write('OAuth', $this->oauthConfig); - parent::tearDown(); - } - - /** - * mock request for GET - * - * @return void - */ - protected function _mockRequestGet($withSession = false) - { - $methods = ['is', 'referer', 'getData', 'getQuery', 'getQueryParams']; - - if ($withSession) { - $methods[] = 'session'; - } - - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods($methods) - ->getMock(); - $this->Trait->request->expects($this->any()) - ->method('is') - ->with('post') - ->will($this->returnValue(false)); + $this->Trait->setRequest($request); + + $this->Provider = $this->getMockBuilder('\League\OAuth2\Client\Provider\Facebook')->setConstructorArgs([ + [ + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + [], + ])->setMethods([ + 'getAccessToken', 'getState', 'getAuthorizationUrl', 'getResourceOwner', + ])->getMock(); + + $config = [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => $this->Provider, + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'options' => [ + 'state' => '__TEST_STATE__', + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => null, + ], + ]; + Configure::write('OAuth.providers.facebook', $config); } /** @@ -112,56 +117,48 @@ public function testLinkSocialHappy() Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); + ->getMock(); + + $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); + $this->Trait->getRequest()->getSession()->write('oauth2state', '__TEST_STATE__'); + $uri = new Uri('/callback-link-social/facebook'); + + $this->Trait->setRequest($this->Trait->getRequest()->withUri($uri)); + $this->Trait->setRequest($this->Trait->getRequest()->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ])); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'facebook', + ])); - $this->_mockRequestGet(true); $this->_mockAuthLoggedIn(); $this->_mockDispatchEvent(new Event('event')); $this->_mockFlash(); - $this->_mockSession([]); - $this->Trait->Flash->expects($this->never()) - ->method('error'); - - $this->Trait->Flash->expects($this->never()) - ->method('success'); - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAuthorizationUrl', 'getState']) - ->disableOriginalConstructor() - ->getMock(); + $this->Provider->expects($this->any()) + ->method('getState') + ->will($this->returnValue('_NEW_STATE_')); - $ProviderMock->expects($this->once()) + $this->Provider->expects($this->any()) ->method('getAuthorizationUrl') - ->will($this->returnValue('http://localhost/fake/facebook/login')); + ->will($this->returnValue('http://facebook.com/redirect/url')); - $ProviderMock->expects($this->once()) - ->method('getState') - ->will($this->returnValue('a3423ja9ads90u3242309')); + $this->Trait->Flash->expects($this->never()) + ->method('error'); - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); + $this->Trait->Flash->expects($this->never()) + ->method('success'); $this->Trait->expects($this->once()) ->method('redirect') - ->with( - $this->equalTo('http://localhost/fake/facebook/login') - ); + ->with($this->equalTo('http://facebook.com/redirect/url')) + ->will($this->returnValue(new Response())); $this->Trait->linkSocial('facebook'); } @@ -171,118 +168,98 @@ public function testLinkSocialHappy() * * @return void */ - public function testLinkSocialNotDefineLinkSocialRedirectUri() + public function testCallbackLinkSocialHappy() { Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); - Configure::delete('OAuth.providers.facebook.options.callbackLinkSocialUri'); - - $result = false; - try { - $this->_mockRequestGet(); - $this->_mockAuthLoggedIn(); - $this->_mockFlash(); - $this->_mockDispatchEvent(new Event('event')); + $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - $this->Trait->linkSocial('facebook'); - } catch (NotFoundException $e) { - $result = true; - } - $this->assertTrue($result); - } + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); - /** - * test - * - * @return void - */ - public function testLinkSocialNotDefinedClientId() - { - Configure::delete('OAuth.providers.facebook.options.clientId'); - Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); - $result = false; - try { - $this->_mockRequestGet(); - $this->_mockAuthLoggedIn(); - $this->_mockFlash(); + $user = new FacebookUser([ + 'id' => '9999911112255', + 'name' => 'Ful Name.', + 'username' => 'mock_username', + 'first_name' => 'First Name', + 'last_name' => 'Last name', + 'email' => 'user-1@test.com', + 'Location' => 'mock_home', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'facebook-link-15579', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); - $this->_mockDispatchEvent(new Event('event')); + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); - $this->Trait->linkSocial('facebook'); - } catch (NotFoundException $e) { - $result = true; - } - $this->assertTrue($result); - } + $this->Provider->expects($this->never()) + ->method('getState'); - /** - * test - * - * @return void - */ - public function testLinkSocialNotDefinedClientSecret() - { - Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); - Configure::delete('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); - $result = false; - try { - $this->_mockRequestGet(); - $this->_mockAuthLoggedIn(); - $this->_mockFlash(); - - $this->_mockDispatchEvent(new Event('event')); - - $this->Trait->linkSocial('facebook'); - } catch (NotFoundException $e) { - $result = true; - } - $this->assertTrue($result); - } + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); - /** - * test - * - * @return void - */ - public function testCallbackLinkSocialHappy() - { - Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); - Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); - $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->getMock(); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); + $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); + $this->Trait->getRequest()->getSession()->write('oauth2state', '__TEST_STATE__'); + $uri = new Uri('/callback-link-social/facebook'); + + $this->Trait->setRequest($this->Trait->getRequest()->withUri($uri)); + $this->Trait->setRequest($this->Trait->getRequest()->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ])); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'facebook', + ])); $this->Trait->expects($this->any()) ->method('getUsersTable') ->will($this->returnValue($Table)); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); - - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->once()) - ->method('getQuery') - ->with('code') - ->will($this->returnValue('99999000222220')); - - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'code' => '99999000222220', - 'state' => 'a393j2942789' - ])); - - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); $this->_mockAuthLoggedIn(); $this->_mockDispatchEvent(new Event('event')); $this->_mockFlash(); @@ -291,81 +268,27 @@ public function testCallbackLinkSocialHappy() $this->Trait->Flash->expects($this->once()) ->method('success') - ->with(__d('CakeDC/Users', 'Social account was associated.')); - - $fbToken = new AccessToken([ - 'access_token' => 'token', - 'tokenSecret' => null, - 'expires' => 1458423682 - ]); - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAccessToken', 'getResourceOwner']) - ->disableOriginalConstructor() - ->getMock(); - - $ProviderMock->expects($this->once()) - ->method('getAccessToken') - ->with( - $this->equalTo('authorization_code'), - $this->equalTo([ - 'code' => '99999000222220' - ]) - )->will($this->returnValue($fbToken)); - - $fbUser = new FacebookUser([ - 'id' => '9999911112255', - 'name' => 'Ful Name.', - 'username' => 'mock_username', - 'first_name' => 'First Name', - 'last_name' => 'Last name', - 'email' => 'user-1@test.com', - 'Location' => 'mock_home', - 'bio' => 'mock_description', - 'link' => 'facebook-link-15579', - ]); - $ProviderMock->expects($this->once()) - ->method('getResourceOwner') - ->with( - $this->equalTo($fbToken) - )->will($this->returnValue($fbUser)); - - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); + ->with(__d('cake_d_c/users', 'Social account was associated.')); $this->Trait->callbackLinkSocial('facebook'); $actual = $Table->SocialAccounts->find('all')->where(['reference' => '9999911112255'])->firstOrFail(); - $expiresTime = new Time(); - $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); + $expiresTime = new FrozenTime(); + $tokenExpires = $expiresTime->setTimestamp($Token->getExpires())->format('Y-m-d H:i:s'); $expected = [ - 'provider' => 'Facebook', + 'provider' => 'facebook', 'username' => 'mock_username', 'reference' => '9999911112255', 'avatar' => 'https://graph.facebook.com/9999911112255/picture?type=large', - 'description' => 'mock_description', - 'token' => 'token', + 'description' => 'I am the best test user in the world.', + 'token' => 'test-token', 'token_secret' => null, 'user_id' => '00000000-0000-0000-0000-000000000001', - 'active' => true + 'active' => true, ]; foreach ($expected as $property => $value) { - $check = $actual->$property; $this->assertEquals($value, $actual->$property); } $this->assertEquals($tokenExpires, $actual->token_expires->format('Y-m-d H:i:s')); @@ -383,8 +306,8 @@ public function testCallbackLinkSocialWithValidationErrors() $user = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->get('00000000-0000-0000-0000-000000000001'); $user->setErrors([ 'social_accounts' => [ - '_existsIn' => __d('CakeDC/Users', 'Social account already associated to another user') - ] + '_existsIn' => __d('cake_d_c/users', 'Social account already associated to another user'), + ], ]); $Table = $this->getMockForModel('CakeDC/Users.Users', ['linkSocialAccount', 'get']); $Table->setAlias('Users'); @@ -397,67 +320,12 @@ public function testCallbackLinkSocialWithValidationErrors() ->method('linkSocialAccount') ->will($this->returnValue($user)); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); - - $this->Trait->expects($this->any()) - ->method('getUsersTable') - ->will($this->returnValue($Table)); - - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); - - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->once()) - ->method('getQuery') - ->with('code') - ->will($this->returnValue('99999000222220')); - - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'code' => '99999000222220', - 'state' => 'a393j2942789' - ])); - - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); - $this->_mockAuthLoggedIn(); - $this->_mockDispatchEvent(new Event('event')); - $this->_mockFlash(); - $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with(__d('CakeDC/Users', 'Could not associate account, please try again.')); - - $this->Trait->Flash->expects($this->never()) - ->method('success'); - - $fbToken = new AccessToken([ - 'access_token' => 'token', - 'tokenSecret' => null, - 'expires' => 1458423682 + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, ]); - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAccessToken', 'getResourceOwner']) - ->disableOriginalConstructor() - ->getMock(); - $ProviderMock->expects($this->once()) - ->method('getAccessToken') - ->with( - $this->equalTo('authorization_code'), - $this->equalTo([ - 'code' => '99999000222220' - ]) - )->will($this->returnValue($fbToken)); - - $fbUser = new FacebookUser([ + $user = new FacebookUser([ 'id' => '9999911112255', 'name' => 'Ful Name.', 'username' => 'mock_username', @@ -465,125 +333,87 @@ public function testCallbackLinkSocialWithValidationErrors() 'last_name' => 'Last name', 'email' => 'user-1@test.com', 'Location' => 'mock_home', - 'bio' => 'mock_description', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', 'link' => 'facebook-link-15579', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', ]); - $ProviderMock->expects($this->once()) - ->method('getResourceOwner') - ->with( - $this->equalTo($fbToken) - )->will($this->returnValue($fbUser)); - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); - $this->Trait->callbackLinkSocial('facebook'); + $this->Provider->expects($this->never()) + ->method('getState'); - $actual = $Table->SocialAccounts->exists(['reference' => '9999911112255']); - $this->assertFalse($actual); - } - - /** - * test - * - * @return void - */ - public function testCallbackLinkSocialFailGettingAccessToken() - { - Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); - Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + $this->Provider->expects($this->any()) + ->method('getAccessToken') + ->with( + $this->equalTo('authorization_code'), + $this->equalTo(['code' => 'ZPO9972j3092304230']) + ) + ->will($this->returnValue($Token)); - $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Provider->expects($this->any()) + ->method('getResourceOwner') + ->with( + $this->equalTo($Token) + ) + ->will($this->returnValue($user)); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->getMock(); $this->Trait->expects($this->any()) ->method('getUsersTable') ->will($this->returnValue($Table)); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); + $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); + $this->Trait->getRequest()->getSession()->write('oauth2state', '__TEST_STATE__'); + $uri = new Uri('/callback-link-social/facebook'); + + $this->Trait->setRequest($this->Trait->getRequest()->withUri($uri)); + $this->Trait->setRequest($this->Trait->getRequest()->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ])); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'facebook', + ])); - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->once()) - ->method('getQuery') - ->with('code') - ->will($this->returnValue('99999000222220')); - - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'code' => '99999000222220', - 'state' => 'a393j2942789' - ])); - - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); $this->_mockAuthLoggedIn(); $this->_mockDispatchEvent(new Event('event')); $this->_mockFlash(); $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with(__d('CakeDC/Users', 'Could not associate account, please try again.')); + ->method('error'); $this->Trait->Flash->expects($this->never()) ->method('success'); - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAccessToken', 'getResourceOwner']) - ->disableOriginalConstructor() - ->getMock(); - - $ProviderMock->expects($this->once()) - ->method('getAccessToken') - ->with( - $this->equalTo('authorization_code'), - $this->equalTo([ - 'code' => '99999000222220' - ]) - )->will($this->throwException(new \Exception)); - - $ProviderMock->expects($this->never()) - ->method('getResourceOwner'); - - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); - $this->Trait->callbackLinkSocial('facebook'); $actual = $Table->SocialAccounts->exists(['reference' => '9999911112255']); @@ -602,128 +432,43 @@ public function testCallbackLinkSocialQueryHasErrors() $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); - - $this->Trait->expects($this->any()) - ->method('getUsersTable') - ->will($this->returnValue($Table)); - - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); - - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->never()) - ->method('getQuery'); - - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'error' => 'We got some error', - 'code' => '99999000222220', - 'state' => 'a393j2942789' - ])); - $this->Trait->expects($this->once()) - ->method('redirect') - ->with( - $this->equalTo(['action' => 'profile']) - ); + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); - $this->_mockAuthLoggedIn(); - $this->_mockDispatchEvent(new Event('event')); - $this->_mockFlash(); - $this->Trait->Flash->expects($this->never()) - ->method('success'); + $this->Provider->expects($this->never()) + ->method('getState'); - $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with(__d('CakeDC/Users', 'Could not associate account, please try again.')); - - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAccessToken', 'getResourceOwner']) - ->disableOriginalConstructor() - ->getMock(); - - $ProviderMock->expects($this->never()) + $this->Provider->expects($this->never()) ->method('getAccessToken'); - $ProviderMock->expects($this->never()) + $this->Provider->expects($this->never()) ->method('getResourceOwner'); - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); - - $this->Trait->callbackLinkSocial('facebook'); - } - - /** - * test - * - * @return void - */ - public function testCallbackLinkSocialWrongState() - { - Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); - Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->getMock(); - $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); + $this->Trait->getRequest()->getSession()->write('oauth2state', '__TEST_STATE__'); + $uri = new Uri('/callback-link-social/facebook'); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) - ->getMockForTrait(); + $this->Trait->setRequest($this->Trait->getRequest()->withUri($uri)); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'facebook', + ])); $this->Trait->expects($this->any()) ->method('getUsersTable') ->will($this->returnValue($Table)); - - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); - - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->never()) - ->method('getQuery'); - - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'code' => '99999000222220', - 'state' => 'bd393j2942789' - ])); $this->Trait->expects($this->once()) ->method('redirect') - ->with( - $this->equalTo(['action' => 'profile']) - ); - - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); + ->with($this->equalTo([ + 'action' => 'profile', + ])) + ->will($this->returnValue(new Response())); $this->_mockAuthLoggedIn(); $this->_mockDispatchEvent(new Event('event')); $this->_mockFlash(); @@ -732,37 +477,10 @@ public function testCallbackLinkSocialWrongState() $this->Trait->Flash->expects($this->once()) ->method('error') - ->with(__d('CakeDC/Users', 'Could not associate account, please try again.')); - - $ProviderMock = $this->getMockBuilder('League\OAuth2\Client\Provider\Facebook') - ->setMethods(['getAccessToken', 'getResourceOwner']) - ->disableOriginalConstructor() - ->getMock(); + ->with(__d('cake_d_c/users', 'Could not associate account, please try again.')); - $ProviderMock->expects($this->never()) - ->method('getAccessToken'); - - $ProviderMock->expects($this->never()) - ->method('getResourceOwner'); - - $this->Trait->expects($this->once()) - ->method('_createSocialProvider') - ->with( - $this->equalTo([ - 'className' => 'League\OAuth2\Client\Provider\Facebook', - 'options' => [ - 'graphApiVersion' => 'v2.8', - 'redirectUri' => '/auth/facebook', - 'linkSocialUri' => '/link-social/facebook', - 'callbackLinkSocialUri' => '/callback-link-social/facebook', - 'clientId' => 'testclientidtestclientid', - 'clientSecret' => 'testclientsecrettestclientsecret' - ] - ]) - ) - ->will($this->returnValue($ProviderMock)); - - $this->Trait->callbackLinkSocial('facebook'); + $result = $this->Trait->callbackLinkSocial('facebook'); + $this->assertInstanceOf(Response::class, $result); } /** @@ -770,46 +488,48 @@ public function testCallbackLinkSocialWrongState() * * @return void */ - public function testCallbackLinkSocialMissingCode() + public function testCallbackLinkSocialUnknownProvider() { Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); - $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LinkSocialTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) - ->getMockForTrait(); + $this->Provider->expects($this->never()) + ->method('getState'); - $this->Trait->expects($this->any()) - ->method('getUsersTable') - ->will($this->returnValue($Table)); + $this->Provider->expects($this->never()) + ->method('getAccessToken'); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setConfig']) - ->disableOriginalConstructor() + $this->Provider->expects($this->never()) + ->method('getResourceOwner'); + + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) ->getMock(); - $this->_mockRequestGet(true); - $this->Trait->request->expects($this->never()) - ->method('getQuery'); + $this->Trait->setRequest(ServerRequestFactory::fromGlobals()); + $this->Trait->getRequest()->getSession()->write('oauth2state', '__TEST_STATE__'); + $uri = new Uri('/callback-link-social/facebook'); + + $this->Trait->setRequest($this->Trait->getRequest()->withUri($uri)); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'unknown', + ])); + + $this->Trait->expects($this->never()) + ->method('getUsersTable'); - $this->Trait->request->expects($this->once()) - ->method('getQueryParams') - ->will($this->returnValue([ - 'state' => 'bd393j2942789' - ])); $this->Trait->expects($this->once()) ->method('redirect') - ->with( - $this->equalTo(['action' => 'profile']) - ); - - $this->_mockSession([ - 'SocialLink' => [ - 'oauth2state' => 'a393j2942789' - ] - ]); + ->with($this->equalTo([ + 'action' => 'profile', + ])) + ->will($this->returnValue(new Response())); $this->_mockAuthLoggedIn(); $this->_mockDispatchEvent(new Event('event')); $this->_mockFlash(); @@ -818,8 +538,9 @@ public function testCallbackLinkSocialMissingCode() $this->Trait->Flash->expects($this->once()) ->method('error') - ->with(__d('CakeDC/Users', 'Could not associate account, please try again.')); + ->with(__d('cake_d_c/users', 'Could not associate account, please try again.')); - $this->Trait->callbackLinkSocial('facebook'); + $result = $this->Trait->callbackLinkSocial('unknown'); + $this->assertInstanceOf(Response::class, $result); } } diff --git a/tests/TestCase/Controller/Traits/LoginTraitTest.php b/tests/TestCase/Controller/Traits/LoginTraitTest.php index 47a3d29c5..79d0a860b 100644 --- a/tests/TestCase/Controller/Traits/LoginTraitTest.php +++ b/tests/TestCase/Controller/Traits/LoginTraitTest.php @@ -1,29 +1,31 @@ traitClassName = 'CakeDC\Users\Controller\Traits\LoginTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; parent::setUp(); - $request = new ServerRequest(); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LoginTrait') - ->setMethods(['dispatchEvent', 'redirect', 'set']) - ->getMockForTrait(); + $this->Trait->setRequest(new ServerRequest()); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'loadComponent']) + ->getMock(); $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') ->setMethods(['setConfig']) ->disableOriginalConstructor() ->getMock(); - - $this->Trait->request = $request; } /** @@ -56,7 +56,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -68,93 +68,74 @@ public function tearDown() */ public function testLoginHappy() { + $identifiers = new IdentifierCollection(); + $SessionAuth = new SessionAuthenticator($identifiers); + + $sessionFailure = new Failure( + $SessionAuth, + new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND) + ); + $failures = [$sessionFailure]; + $this->_mockDispatchEvent(new Event('event')); - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') ->setMethods(['is']) ->getMock(); - $this->Trait->request->expects($this->any()) - ->method('is') - ->with('post') - ->will($this->returnValue(true)); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); - $user = [ - 'id' => 1, - ]; - $redirectLoginOK = '/'; - $this->Trait->Auth->expects($this->at(0)) - ->method('identify') - ->will($this->returnValue($user)); - $this->Trait->Auth->expects($this->at(1)) - ->method('setUser') - ->with($user); - $this->Trait->Auth->expects($this->at(2)) - ->method('redirectUrl') - ->will($this->returnValue($redirectLoginOK)); + + $this->_mockRequestPost(); + $this->Trait->getRequest()->expects($this->never()) + ->method('getData'); + $this->Trait->setRequest($request); + + $this->_mockFlash(); + $user = $this->Trait->getUsersTable()->get('00000000-0000-0000-0000-000000000002'); + $passwordBefore = $user['password']; + $this->assertNotEmpty($passwordBefore); + $lastLoginBefore = $user['last_login']; + $this->assertNotEmpty($lastLoginBefore); + $this->_mockAuthentication($user->toArray(), $failures); + $this->Trait->Flash->expects($this->never()) + ->method('error'); $this->Trait->expects($this->once()) ->method('redirect') - ->with($redirectLoginOK); - $this->Trait->login(); - } + ->with($this->successLoginRedirect) + ->will($this->returnValue(new Response())); - /** - * test - * - * @return void - */ - public function testAfterIdentifyEmptyUser() - { - $this->_mockDispatchEvent(new Event('event')); - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is']) - ->getMock(); - $this->Trait->request->expects($this->any()) - ->method('is') - ->with('post') - ->will($this->returnValue(true)); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); - $user = []; - $this->Trait->Auth->expects($this->once()) - ->method('identify') - ->will($this->returnValue($user)); - $this->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') - ->setMethods(['error']) - ->disableOriginalConstructor() + $registry = new ComponentRegistry(); + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + FormAuthenticator::FAILURE_INVALID_RECAPTCHA => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => FormAuthenticator::class, + ]; + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $config]) ->getMock(); - $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with('Username or password is incorrect', 'default', [], 'auth'); - $this->Trait->login(); - } - /** - * test - * - * @return void - */ - public function testAfterIdentifyEmptyUserSocialLogin() - { - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LoginTrait') - ->setMethods(['dispatchEvent', 'redirect', '_isSocialLogin']) - ->getMockForTrait(); + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); $this->Trait->expects($this->any()) - ->method('_isSocialLogin') - ->will($this->returnValue(true)); - $this->_mockDispatchEvent(new Event('event')); - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is']) - ->getMock(); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); - $this->Trait->login(); + $result = $this->Trait->login(); + $this->assertInstanceOf(Response::class, $result); + $userAfter = $this->Trait->getUsersTable()->get('00000000-0000-0000-0000-000000000002'); + $passwordAfter = $userAfter['password']; + $this->assertSame($passwordBefore, $passwordAfter); + $lastLoginAfter = $userAfter['last_login']; + $this->assertNotEmpty($lastLoginAfter); + $now = \Cake\I18n\FrozenTime::now(); + $this->assertEqualsWithDelta($lastLoginAfter->timestamp, $now->timestamp, 2); } /** @@ -162,56 +143,78 @@ public function testAfterIdentifyEmptyUserSocialLogin() * * @return void */ - public function testLoginBeforeLoginReturningArray() + public function testLoginRehash() { - $user = [ - 'id' => 1 - ]; - $event = new Event('event'); - $event->result = $user; - $this->Trait->expects($this->at(0)) - ->method('dispatchEvent') - ->with(UsersAuthComponent::EVENT_BEFORE_LOGIN) - ->will($this->returnValue($event)); - $this->Trait->expects($this->at(1)) - ->method('dispatchEvent') - ->with(UsersAuthComponent::EVENT_AFTER_LOGIN) - ->will($this->returnValue(new Event('name'))); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['setUser', 'redirectUrl']) - ->disableOriginalConstructor() + $passwordIdentifier = $this->getMockBuilder(PasswordIdentifier::class) + ->setMethods(['needsPasswordRehash']) ->getMock(); - $redirectLoginOK = '/'; - $this->Trait->Auth->expects($this->once()) - ->method('setUser') - ->with($user); - $this->Trait->Auth->expects($this->once()) - ->method('redirectUrl') - ->will($this->returnValue($redirectLoginOK)); - $this->Trait->expects($this->once()) - ->method('redirect') - ->with($redirectLoginOK); - $this->Trait->login(); - } + $passwordIdentifier->expects($this->once()) + ->method('needsPasswordRehash') + ->willReturn(true); + $identifiers = new IdentifierCollection([]); + $identifiers->set('Password', $passwordIdentifier); - /** - * test - * - * @return void - */ - public function testLoginBeforeLoginReturningStoppedEvent() - { - $event = new Event('event'); - $event->result = '/'; - $event->stopPropagation(); - $this->Trait->expects($this->at(0)) - ->method('dispatchEvent') - ->with(UsersAuthComponent::EVENT_BEFORE_LOGIN) - ->will($this->returnValue($event)); + $SessionAuth = new SessionAuthenticator($identifiers); + + $sessionFailure = new Failure( + $SessionAuth, + new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND) + ); + $failures = [$sessionFailure]; + + $userPassword = 'testLoginRehash' . time(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockRequestPost(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with($this->equalTo('password')) + ->willReturn($userPassword); + + $this->_mockFlash(); + $user = $this->Trait->getUsersTable()->get('00000000-0000-0000-0000-000000000002'); + $passwordBefore = $user['password']; + $this->assertNotEmpty($passwordBefore); + $this->_mockAuthentication($user->toArray(), $failures, $identifiers); + $this->Trait->Flash->expects($this->never()) + ->method('error'); $this->Trait->expects($this->once()) ->method('redirect') - ->with('/'); - $this->Trait->login(); + ->with($this->successLoginRedirect) + ->will($this->returnValue(new Response())); + + $registry = new ComponentRegistry(); + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + FormAuthenticator::FAILURE_INVALID_RECAPTCHA => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => FormAuthenticator::class, + ]; + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $config]) + ->getMock(); + + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); + + $result = $this->Trait->login(); + $this->assertInstanceOf(Response::class, $result); + $userAfter = $this->Trait->getUsersTable()->get('00000000-0000-0000-0000-000000000002'); + $passwordAfter = $userAfter['password']; + $this->assertNotEquals($passwordBefore, $passwordAfter); + $passwordHasher = new DefaultPasswordHasher(); + $check = $passwordHasher->check($userPassword, $passwordAfter); + $this->assertTrue($check); } /** @@ -222,26 +225,56 @@ public function testLoginBeforeLoginReturningStoppedEvent() public function testLoginGet() { $this->_mockDispatchEvent(new Event('event')); - $socialLogin = Configure::read('Users.Social.login'); - Configure::write('Users.Social.login', false); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user']) - ->disableOriginalConstructor() - ->getMock(); - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') + $request = $this->getMockBuilder('Cake\Http\ServerRequest') ->setMethods(['is']) - ->disableOriginalConstructor() ->getMock(); - $this->Trait->request->expects($this->at(0)) - ->method('is') - ->with('post') - ->will($this->returnValue(false)); - $this->Trait->request->expects($this->at(1)) + $this->Trait->setRequest($request); + $request->expects($this->once()) ->method('is') ->with('post') ->will($this->returnValue(false)); + $this->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') + ->setMethods(['error']) + ->disableOriginalConstructor() + ->getMock(); + + $this->Trait->Flash->expects($this->never()) + ->method('error'); + + $this->Trait->expects($this->never()) + ->method('redirect'); + + $this->_mockAuthentication(); + + $registry = new ComponentRegistry(); + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + FormAuthenticator::FAILURE_INVALID_RECAPTCHA => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => FormAuthenticator::class, + ]; + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $config]) + ->getMock(); + + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + // $this->Trait->expects($this->any()) + // ->method('getRequest') + // ->will($this->returnValue($request)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); + $this->Trait->login(); - Configure::write('Users.Social.login', $socialLogin); } /** @@ -256,13 +289,12 @@ public function testLogout() ->setMethods(['logout', 'user']) ->disableOriginalConstructor() ->getMock(); - $redirectLogoutOK = '/'; - $this->Trait->Auth->expects($this->once()) - ->method('logout') - ->will($this->returnValue($redirectLogoutOK)); + $this->_mockAuthentication([ + 'id' => 1, + ]); $this->Trait->expects($this->once()) ->method('redirect') - ->with($redirectLogoutOK); + ->with($this->logoutRedirect); $this->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') ->setMethods(['success']) ->disableOriginalConstructor() @@ -274,199 +306,231 @@ public function testLogout() } /** - * test - * - * @return void + * Data provider for testLogin */ - public function testFailedSocialLoginMissingEmail() + public function dataProviderLogin() { - $event = new Entity(); - $event->data = [ - 'exception' => new MissingEmailException('Email not present'), - 'rawData' => [ - 'id' => 11111, - 'username' => 'user-1' - ] + $socialLoginConfig = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + SocialAuthenticator::FAILURE_USER_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + SocialAuthenticator::FAILURE_ACCOUNT_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ), + ], + 'targetAuthenticator' => SocialAuthenticator::class, + ]; + $loginConfig = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Username or password is incorrect'), + 'messages' => [ + FormAuthenticator::FAILURE_INVALID_RECAPTCHA => __d('cake_d_c/users', 'Invalid reCaptcha'), + ], + 'targetAuthenticator' => FormAuthenticator::class, ]; - $this->_mockFlash(); - $this->_mockRequestGet(); - $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Please enter your email'); - - $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialEmail']); - $this->Trait->failedSocialLogin($event->data['exception'], $event->data['rawData'], true); + return [ + [ + SocialAuthenticator::class, + SocialAuthenticator::FAILURE_USER_NOT_ACTIVE, + 'Your user has not been validated yet. Please check your inbox for instructions', + 'socialLogin', + $socialLoginConfig, + ], + [ + SocialAuthenticator::class, + SocialAuthenticator::FAILURE_ACCOUNT_NOT_ACTIVE, + 'Your social account has not been validated yet. Please check your inbox for instructions', + 'socialLogin', + $socialLoginConfig, + ], + [ + SocialAuthenticator::class, + Result::FAILURE_IDENTITY_NOT_FOUND, + 'Could not proceed with social account. Please try again', + 'socialLogin', + $socialLoginConfig, + ], + [ + FormAuthenticator::class, + Result::FAILURE_IDENTITY_NOT_FOUND, + 'Username or password is incorrect', + 'login', + $loginConfig, + ], + [ + FormAuthenticator::class, + FormAuthenticator::FAILURE_INVALID_RECAPTCHA, + 'Invalid reCaptcha', + 'login', + $loginConfig, + ], + ]; } /** - * test + * test socialLogin/login failure * + * @dataProvider dataProviderLogin * @return void */ - public function testFailedSocialUserNotActive() + public function testLogin($AuthClass, $resultStatus, $message, $method, $failureConfig) { - $event = new Entity(); - $event->data = [ - 'exception' => new UserNotActiveException('Facebook user-1'), - 'rawData' => [ - 'id' => 111111, - 'username' => 'user-1' - ] - ]; - $this->_mockFlash(); - $this->_mockRequestGet(); - $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Your user has not been validated yet. Please check your inbox for instructions'); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $FormAuth = new FormAuthenticator($identifiers); + $SessionAuth = new SessionAuthenticator($identifiers); + $SocialAuth = new $AuthClass($identifiers); - $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + $sessionFailure = new Failure( + $SessionAuth, + new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND) + ); + $formFailure = new Failure( + $FormAuth, + new Result(null, $resultStatus, [ + 'Password' => [], + ]) + ); + $socialFailure = new Failure( + $SocialAuth, + new Result(null, $resultStatus) + ); + $failures = [$sessionFailure, $formFailure, $socialFailure]; - $this->Trait->failedSocialLogin($event->data['exception'], $event->data['rawData'], true); - } + $this->_mockDispatchEvent(new Event('event')); + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is']) + ->getMock(); + $request->expects($this->any()) + ->method('is') + ->with('post') + ->will($this->returnValue(true)); + $this->Trait->setRequest($request); - /** - * test - * - * @return void - */ - public function testFailedSocialUserAccountNotActive() - { - $event = new Entity(); - $event->data = [ - 'exception' => new AccountNotActiveException('Facebook user-1'), - 'rawData' => [ - 'id' => 111111, - 'username' => 'user-1' - ] - ]; $this->_mockFlash(); - $this->_mockRequestGet(); + $this->_mockAuthentication(null, $failures); $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Your social account has not been validated yet. Please check your inbox for instructions'); + ->method('error') + ->with($message); - $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + $registry = new ComponentRegistry(); + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $failureConfig]) + ->getMock(); - $this->Trait->failedSocialLogin($event->data['exception'], $event->data['rawData'], true); + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($failureConfig) + ) + ->will($this->returnValue($Login)); + + if ($method === 'login') { + $this->Trait->expects($this->never()) + ->method('redirect'); + $result = $this->Trait->$method(); + $this->assertNull($result); + } else { + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]) + ->will($this->returnValue(new Response())); + $result = $this->Trait->$method(); + $this->assertInstanceOf(Response::class, $result); + } } /** - * test + * test socialLogin success * * @return void */ - public function testFailedSocialUserAccount() + public function testSocialLoginSuccess() { - $event = new Entity(); - $event->data = [ - 'rawData' => [ - 'id' => 111111, - 'username' => 'user-1' - ] - ]; - $this->_mockFlash(); - $this->_mockRequestGet(); - $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Issues trying to log in with your social account'); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $FormAuth = new FormAuthenticator($identifiers); + $SessionAuth = new SessionAuthenticator($identifiers); - $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + $sessionFailure = new Failure( + $SessionAuth, + new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND) + ); + $formFailure = new Failure( + $FormAuth, + new Result(null, Result::FAILURE_CREDENTIALS_MISSING, [ + 'Password' => [], + ]) + ); + $failures = [$sessionFailure, $formFailure]; - $this->Trait->failedSocialLogin(null, $event->data['rawData'], true); - } + $this->_mockDispatchEvent(new Event('event')); - /** - * testVerifyHappy - * - */ - public function testVerifyHappy() - { - Configure::write('Users.GoogleAuthenticator.login', true); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', ]) - ->disableOriginalConstructor() - ->getMock(); - $user = [ - 'id' => 1, - 'secret_verified' => 1, - ]; - $this->Trait->Auth->expects($this->at(0)) - ->method('user') - ->will($this->returnValue($user)); - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is', 'getData', 'allow']) + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is']) ->getMock(); - $this->Trait->request->expects($this->at(0)) + $request->expects($this->any()) ->method('is') ->with('post') - ->will($this->returnValue(false)); - $this->Trait->verify(); - } + ->will($this->returnValue(true)); + $this->Trait->setRequest($request); - /** - * testVerifyHappy - * - */ - public function testVerifyNotEnabled() - { $this->_mockFlash(); - Configure::write('Users.GoogleAuthenticator.login', false); - $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with('Please enable Google Authenticator first.'); - $this->Trait->verify(); - } + $this->_mockAuthentication(['id' => 1], $failures); + $this->Trait->Flash->expects($this->never()) + ->method('error'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->successLoginRedirect) + ->will($this->returnValue(new Response())); - /** - * testVerifyHappy - * - */ - public function testVerifyGetShowQR() - { - Configure::write('Users.GoogleAuthenticator.login', true); - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', ]) - ->disableOriginalConstructor() - ->getMock(); - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - 'email' => 'email@example.com', - 'secret_verified' => 0, + $registry = new ComponentRegistry(); + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + SocialAuthenticator::FAILURE_USER_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + SocialAuthenticator::FAILURE_ACCOUNT_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ), + ], + 'targetAuthenticator' => SocialAuthenticator::class, ]; - $this->Trait->Auth->expects($this->at(0)) - ->method('user') - ->will($this->returnValue($user)); - $this->Trait->GoogleAuthenticator = $this->getMockBuilder(GoogleAuthenticatorComponent::class) - ->disableOriginalConstructor() - ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $config]) ->getMock(); - $this->Trait->request = $this->getMockBuilder(ServerRequest::class) - ->setMethods(['is', 'getData', 'allow']) - ->getMock(); - $this->Trait->request->expects($this->at(0)) - ->method('is') - ->with('post') - ->will($this->returnValue(false)); - $this->Trait->GoogleAuthenticator->expects($this->at(0)) - ->method('createSecret') - ->will($this->returnValue('newSecret')); - $this->Trait->GoogleAuthenticator->expects($this->at(1)) - ->method('getQRCodeImageAsDataUri') - ->with('email@example.com', 'newSecret') - ->will($this->returnValue('newDataUriGenerated')); - $this->Trait->expects($this->at(0)) - ->method('set') - ->with(['secretDataUri' => 'newDataUriGenerated']); - $this->Trait->verify(); + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); + + $result = $this->Trait->socialLogin(); + $this->assertInstanceOf(Response::class, $result); } } diff --git a/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php new file mode 100644 index 000000000..683bfc0c7 --- /dev/null +++ b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php @@ -0,0 +1,271 @@ + 'CakeDC/Users', + 'prefix' => false, + 'controller' => 'users', + 'action' => 'login', + ]; + + /** + * setup + * + * @return void + */ + public function setUp(): void + { + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + + parent::setUp(); + $request = new ServerRequest(); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable']) + ->getMock(); + + $this->Trait->setRequest($request); + Configure::write('Auth.AuthenticationComponent.loginAction', $this->loginPage); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * testVerifyHappy + */ + public function testVerifyHappy() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + $this->Trait->expects($this->never()) + ->method('redirect'); + + $this->_mockSession([ + 'temporarySession' => [ + 'id' => 1, + 'secret_verified' => 1, + ], + ]); + + $this->Trait->verify(); + } + + /** + * testVerifyHappy + */ + public function testVerifyNotEnabled() + { + $this->_mockFlash(); + Configure::write('OneTimePasswordAuthenticator.login', false); + $this->Trait->setRequest($this->Trait->getRequest()->withQueryParams(['redirect' => 'dashboard/list'])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Please enable Google Authenticator first.'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->loginPage + ['?' => ['redirect' => 'dashboard/list']]); + + $this->Trait->verify(); + } + + /** + * testVerifyHappy + */ + public function testVerifyGetShowQR() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + $this->Trait->OneTimePasswordAuthenticator = $this->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + + $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => 0, + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + $this->Trait->OneTimePasswordAuthenticator->expects($this->once()) + ->method('createSecret') + ->will($this->returnValue('newSecret')); + $this->Trait->OneTimePasswordAuthenticator->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'newSecret') + ->will($this->returnValue('newDataUriGenerated')); + $this->Trait->expects($this->once()) + ->method('set') + ->with(['secretDataUri' => 'newDataUriGenerated']); + + $this->Trait->verify(); + $user = $this->Trait->getUsersTable()->findById('00000000-0000-0000-0000-000000000001')->firstOrFail(); + $this->assertEquals('newSecret', $user->secret); + } + + /** + * Tests that a GET request causes a a new secret to be generated in case it's + * not already present in the session. + */ + public function testVerifyGetGeneratesNewSecret() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + + $this->Trait->OneTimePasswordAuthenticator = $this + ->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait + ->getRequest() + ->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('createSecret') + ->will($this->returnValue('newSecret')); + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'newSecret') + ->will($this->returnValue('newDataUriGenerated')); + + $session = $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + $this->Trait->verify(); + + $this->assertEquals( + [ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'newSecret', + ], + ], + $session->read() + ); + } + + /** + * Tests that a GET request does not cause a new secret to be generated in case + * it's already present in the session. + */ + public function testVerifyGetDoesNotGenerateNewSecret() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + + $this->Trait->OneTimePasswordAuthenticator = $this + ->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait + ->getRequest() + ->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->never()) + ->method('createSecret'); + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'alreadyPresentSecret') + ->will($this->returnValue('newDataUriGenerated')); + + $session = $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'alreadyPresentSecret', + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + $this->Trait->verify(); + + $this->assertEquals( + [ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'alreadyPresentSecret', + ], + ], + $session->read() + ); + } +} diff --git a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php index f4d292765..9a3d8a6ad 100644 --- a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php +++ b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php @@ -1,11 +1,13 @@ traitClassName = 'CakeDC\Users\Controller\Traits\PasswordManagementTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['set', 'redirect', 'validate', 'log', 'dispatchEvent']; $this->mockDefaultEmail = true; parent::setUp(); @@ -37,7 +43,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -50,10 +56,10 @@ public function tearDown() public function testChangePasswordHappy() { $this->assertEquals('12345', $this->table->get('00000000-0000-0000-0000-000000000001')->password); - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'password' => 'new', @@ -61,7 +67,7 @@ public function testChangePasswordHappy() ])); $this->Trait->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']); + ->with(['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']); $this->Trait->Flash->expects($this->any()) ->method('success') ->with('Password has been changed successfully'); @@ -78,10 +84,10 @@ public function testChangePasswordHappy() public function testChangePasswordWithError() { $this->assertEquals('12345', $this->table->get('00000000-0000-0000-0000-000000000001')->password); - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'password' => 'new', @@ -101,19 +107,19 @@ public function testChangePasswordWithError() public function testChangePasswordWithAfterChangeEvent() { $this->assertEquals('12345', $this->table->get('00000000-0000-0000-0000-000000000001')->password); - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'password' => 'new', 'password_confirm' => 'new', ])); $event = new Event('event'); - $event->result = [ + $event->setResult([ 'action' => 'newAction', - ]; + ]); $this->Trait->expects($this->once()) ->method('dispatchEvent') ->will($this->returnValue($event)); @@ -139,10 +145,10 @@ public function testChangePasswordWithSamePassword() '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', $this->table->get('00000000-0000-0000-0000-000000000006')->password ); - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'current_password' => '12345', @@ -162,10 +168,10 @@ public function testChangePasswordWithSamePassword() */ public function testChangePasswordWithEmptyCurrentPassword() { - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'current_password' => '', @@ -189,10 +195,10 @@ public function testChangePasswordWithWrongCurrentPassword() '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', $this->table->get('00000000-0000-0000-0000-000000000006')->password ); - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'current_password' => 'wrong-password', @@ -212,10 +218,10 @@ public function testChangePasswordWithWrongCurrentPassword() */ public function testChangePasswordWithInvalidUser() { - $this->_mockRequestPost(); + $this->_mockRequestPost(['post', 'put']); $this->_mockAuthLoggedIn(['id' => '12312312-0000-0000-0000-000000000002', 'password' => 'invalid-pass']); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'password' => 'new', @@ -234,7 +240,14 @@ public function testChangePasswordWithInvalidUser() */ public function testChangePasswordGetLoggedIn() { - $this->_mockRequestGet(); + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'referer', 'getData']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait->getRequest()->expects($this->any()) + ->method('is') + ->with(['post', 'put']) + ->will($this->returnValue(false)); $this->_mockAuthLoggedIn(); $this->Trait->expects($this->any()) ->method('set') @@ -253,11 +266,20 @@ public function testChangePasswordGetLoggedIn() */ public function testChangePasswordGetNotLoggedInInsideResetPasswordFlow() { - $this->_mockRequestGet(true); - $this->_mockAuth(); + $methods = ['is', 'referer', 'getData', 'getSession']; + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods($methods) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait->getRequest()->expects($this->any()) + ->method('is') + ->with(['post', 'put']) + ->will($this->returnValue(false)); + + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockSession([ - Configure::read('Users.Key.Session.resetPasswordUserId') => '00000000-0000-0000-0000-000000000001' + Configure::read('Users.Key.Session.resetPasswordUserId') => '00000000-0000-0000-0000-000000000001', ]); $this->Trait->expects($this->any()) ->method('set') @@ -277,7 +299,7 @@ public function testChangePasswordGetNotLoggedInInsideResetPasswordFlow() public function testChangePasswordGetNotLoggedInOutsideResetPasswordFlow() { $this->_mockRequestGet(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->Trait->Flash->expects($this->once()) ->method('error') @@ -309,7 +331,7 @@ public function testRequestResetPasswordGet() $this->assertEquals('ae93ddbe32664ce7927cf0c5c5a5e59d', $this->table->get('00000000-0000-0000-0000-000000000001')->token); $this->_mockRequestGet(); $this->_mockFlash(); - $this->Trait->request->expects($this->never()) + $this->Trait->getRequest()->expects($this->never()) ->method('getData'); $this->Trait->requestResetPassword(); } @@ -326,7 +348,7 @@ public function testRequestPasswordHappy() $this->_mockAuthLoggedIn(); $this->_mockFlash(); $reference = 'user-2'; - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue($reference)); @@ -348,14 +370,18 @@ public function testRequestPasswordInvalidUser() $this->_mockAuthLoggedIn(['id' => 'invalid-id', 'password' => 'invalid-pass']); $this->_mockFlash(); $reference = '12312312-0000-0000-0000-000000000002'; - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue($reference)); $this->Trait->Flash->expects($this->any()) ->method('error') ->with('User 12312312-0000-0000-0000-000000000002 was not found'); - $this->Trait->requestResetPassword(); + + $this->Trait->expects($this->never()) + ->method('redirect'); + $actual = $this->Trait->requestResetPassword(); + $this->assertNull($actual); } /** @@ -369,19 +395,22 @@ public function testRequestPasswordEmptyReference() $this->_mockAuthLoggedIn(['id' => 'invalid-id', 'password' => 'invalid-pass']); $this->_mockFlash(); $reference = ''; - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue($reference)); $this->Trait->Flash->expects($this->any()) ->method('error') ->with('Token could not be reset'); - $this->Trait->requestResetPassword(); + + $this->Trait->expects($this->never()) + ->method('redirect'); + $actual = $this->Trait->requestResetPassword(); + $this->assertNull($actual); } /** * @dataProvider ensureUserActiveForResetPasswordFeature - * * @return void */ public function testEnsureUserActiveForResetPasswordFeature($ensureActive) @@ -397,7 +426,7 @@ public function testEnsureUserActiveForResetPasswordFeature($ensureActive) $this->_mockRequestPost(); $this->_mockFlash(); $reference = 'user-1'; - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue($reference)); @@ -415,13 +444,12 @@ public function ensureUserActiveForResetPasswordFeature() return [ [$ensureActive], - [$defaultBehavior] + [$defaultBehavior], ]; } /** - * @dataProvider ensureGoogleAuthenticatorResets - * + * @dataProvider ensureOneTimePasswordAuthenticatorResets * @return void */ public function testRequestGoogleAuthTokenResetWithValidUser($userId, $entityId, $method, $msg) @@ -443,15 +471,21 @@ public function testRequestGoogleAuthTokenResetWithValidUser($userId, $entityId, $this->Trait->Flash->expects($this->any()) ->method($method) ->with($msg); + $response = new \Cake\Http\Response(); + $response = $response->withLocation('/'); + $this->Trait->expects($this->any()) + ->method('redirect') + ->willReturn($response); - $this->Trait->resetGoogleAuthenticator($entityId); + $actual = $this->Trait->resetOneTimePasswordAuthenticator($entityId); + $this->assertSame($response, $actual); } - public function ensureGoogleAuthenticatorResets() + public function ensureOneTimePasswordAuthenticatorResets() { $error = 'error'; $success = 'success'; - $errorMsg = 'You are not allowed to reset users Google Authenticator token'; + $errorMsg = 'Could not reset Google Authenticator'; $successMsg = 'Google Authenticator token was successfully reset'; return [ diff --git a/tests/TestCase/Controller/Traits/ProfileTraitTest.php b/tests/TestCase/Controller/Traits/ProfileTraitTest.php index f0ab42c68..24dd1912c 100644 --- a/tests/TestCase/Controller/Traits/ProfileTraitTest.php +++ b/tests/TestCase/Controller/Traits/ProfileTraitTest.php @@ -1,20 +1,18 @@ traitClassName = 'CakeDC\Users\Controller\Traits\ProfileTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['set', 'getUsersTable', 'redirect', 'validate']; parent::setUp(); } @@ -48,7 +46,7 @@ public function testProfileGetNotLoggedInUserNotFound() { $userId = '00000000-0000-0000-0000-000000000000'; //not found $this->_mockRequestGet(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->Trait->Flash->expects($this->once()) ->method('error') @@ -81,7 +79,7 @@ public function testProfileGetLoggedInUserNotFound() public function testProfileGetNotLoggedInEmptyId() { $this->_mockRequestGet(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->Trait->Flash->expects($this->once()) ->method('error') diff --git a/tests/TestCase/Controller/Traits/RecaptchaTraitTest.php b/tests/TestCase/Controller/Traits/ReCaptchaTraitTest.php similarity index 69% rename from tests/TestCase/Controller/Traits/RecaptchaTraitTest.php rename to tests/TestCase/Controller/Traits/ReCaptchaTraitTest.php index a72250221..d05b17e49 100644 --- a/tests/TestCase/Controller/Traits/RecaptchaTraitTest.php +++ b/tests/TestCase/Controller/Traits/ReCaptchaTraitTest.php @@ -1,11 +1,13 @@ Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait') @@ -35,7 +37,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -55,17 +57,18 @@ public function testValidateValidReCaptcha() ->setMethods(['isSuccess']) ->disableOriginalConstructor() ->getMock(); - $Response->expects($this->any()) + $Response->expects($this->once()) ->method('isSuccess') ->will($this->returnValue(true)); - $ReCaptcha->expects($this->any()) + $ReCaptcha->expects($this->once()) ->method('verify') ->with('value') ->will($this->returnValue($Response)); - $this->Trait->expects($this->any()) + $this->Trait->expects($this->once()) ->method('_getReCaptchaInstance') ->will($this->returnValue($ReCaptcha)); - $this->Trait->validateReCaptcha('value', '255.255.255.255'); + $actual = $this->Trait->validateReCaptcha('value', '255.255.255.255'); + $this->assertTrue($actual); } /** @@ -83,17 +86,18 @@ public function testValidateInvalidReCaptcha() ->setMethods(['isSuccess']) ->disableOriginalConstructor() ->getMock(); - $Response->expects($this->any()) + $Response->expects($this->once()) ->method('isSuccess') ->will($this->returnValue(false)); - $ReCaptcha->expects($this->any()) + $ReCaptcha->expects($this->once()) ->method('verify') ->with('invalid') ->will($this->returnValue($Response)); - $this->Trait->expects($this->any()) + $this->Trait->expects($this->once()) ->method('_getReCaptchaInstance') ->will($this->returnValue($ReCaptcha)); - $this->Trait->validateReCaptcha('invalid', '255.255.255.255'); + $actual = $this->Trait->validateReCaptcha('invalid', '255.255.255.255'); + $this->assertFalse($actual); } public function testGetRecaptchaInstance() @@ -117,7 +121,25 @@ public function testGetRecaptchaInstanceNull() public function testValidateReCaptchaFalse() { - $trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait')->getMockForTrait(); + $ReCaptcha = $this->getMockBuilder('ReCaptcha\ReCaptcha') + ->setMethods(['verify']) + ->disableOriginalConstructor() + ->getMock(); + $Response = $this->getMockBuilder('ReCaptcha\Response') + ->setMethods(['isSuccess']) + ->disableOriginalConstructor() + ->getMock(); + $Response->expects($this->once()) + ->method('isSuccess') + ->will($this->returnValue(false)); + $ReCaptcha->expects($this->once()) + ->method('verify') + ->with('value') + ->will($this->returnValue($Response)); + $this->Trait->expects($this->once()) + ->method('_getReCaptchaInstance') + ->will($this->returnValue($ReCaptcha)); + $this->assertFalse($this->Trait->validateReCaptcha('value', '255.255.255.255')); } } diff --git a/tests/TestCase/Controller/Traits/RegisterTraitTest.php b/tests/TestCase/Controller/Traits/RegisterTraitTest.php index ec2a005d9..1ee7df055 100644 --- a/tests/TestCase/Controller/Traits/RegisterTraitTest.php +++ b/tests/TestCase/Controller/Traits/RegisterTraitTest.php @@ -1,22 +1,22 @@ traitClassName = 'CakeDC\Users\Controller\Traits\RegisterTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['validate', 'dispatchEvent', 'set', 'validateReCaptcha', 'redirect']; $this->mockDefaultEmail = true; parent::setUp(); - - Plugin::routes('CakeDC/Users'); } /** @@ -40,7 +38,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -66,9 +64,16 @@ public function testValidateEmail() */ public function testRegister() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) @@ -77,14 +82,14 @@ public function testRegister() $this->Trait->expects($this->once()) ->method('redirect') ->with(['action' => 'login']); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue([ 'username' => 'testRegistration', 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -101,7 +106,7 @@ public function testRegisterWithEventFalseResult() { $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(new Event('Users.Component.UsersAuth.beforeRegister'), ['username' => 'hello']); $this->Trait->Flash->expects($this->once()) @@ -109,7 +114,7 @@ public function testRegisterWithEventFalseResult() ->with('The user could not be saved'); $this->Trait->expects($this->never()) ->method('redirect'); - $this->Trait->request->expects($this->never()) + $this->Trait->getRequest()->expects($this->never()) ->method('is'); $this->Trait->register(); @@ -123,20 +128,27 @@ public function testRegisterWithEventFalseResult() */ public function testRegisterWithEventSuccessResult() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $data = [ 'username' => 'testRegistration', 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ]; $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(new Event('Users.Component.UsersAuth.beforeRegister'), $data); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->will($this->returnValue($data)); $this->Trait->Flash->expects($this->once()) @@ -145,7 +157,7 @@ public function testRegisterWithEventSuccessResult() $this->Trait->expects($this->once()) ->method('redirect') ->with(['action' => 'login']); - $this->Trait->request->expects($this->never()) + $this->Trait->getRequest()->expects($this->never()) ->method('is'); $this->Trait->register(); @@ -159,10 +171,17 @@ public function testRegisterWithEventSuccessResult() */ public function testRegisterReCaptcha() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + Configure::write('Users.reCaptcha.registration', true); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) @@ -174,7 +193,7 @@ public function testRegisterReCaptcha() $this->Trait->expects($this->once()) ->method('redirect') ->with(['action' => 'login']); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->any()) ->method('getData') ->with() ->will($this->returnValue([ @@ -182,7 +201,7 @@ public function testRegisterReCaptcha() 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -200,7 +219,7 @@ public function testRegisterValidationErrors() Configure::write('Users.reCaptcha.registration', true); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) @@ -211,7 +230,7 @@ public function testRegisterValidationErrors() ->will($this->returnValue(true)); $this->Trait->expects($this->never()) ->method('redirect'); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->any()) ->method('getData') ->with() ->will($this->returnValue([ @@ -219,7 +238,7 @@ public function testRegisterValidationErrors() 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'not-matching', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -237,16 +256,16 @@ public function testRegisterRecaptchaNotValid() Configure::write('Users.reCaptcha.registration', true); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) ->method('error') ->with('Invalid reCaptcha'); - $this->Trait->expects($this->once()) + $this->Trait->expects($this->any()) ->method('validateRecaptcha') ->will($this->returnValue(false)); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->any()) ->method('getData') ->with() ->will($this->returnValue([ @@ -254,7 +273,7 @@ public function testRegisterRecaptchaNotValid() 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -271,7 +290,7 @@ public function testRegisterGet() { $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestGet(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->never()) @@ -292,10 +311,17 @@ public function testRegisterGet() */ public function testRegisterRecaptchaDisabled() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + Configure::write('Users.Registration.reCaptcha', false); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) @@ -306,7 +332,7 @@ public function testRegisterRecaptchaDisabled() $this->Trait->expects($this->once()) ->method('redirect') ->with(['action' => 'login']); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -314,7 +340,7 @@ public function testRegisterRecaptchaDisabled() 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -326,13 +352,13 @@ public function testRegisterRecaptchaDisabled() * test * * @return void - * @expectedException Cake\Http\Exception\NotFoundException */ public function testRegisterNotEnabled() { + $this->expectException(NotFoundException::class); Configure::write('Users.Registration.active', false); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->register(); @@ -345,6 +371,13 @@ public function testRegisterNotEnabled() */ public function testRegisterLoggedInUserAllowed() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + Configure::write('Users.Registration.allowLoggedIn', true); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); @@ -357,7 +390,7 @@ public function testRegisterLoggedInUserAllowed() $this->Trait->expects($this->once()) ->method('redirect') ->with(['action' => 'login']); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -365,7 +398,7 @@ public function testRegisterLoggedInUserAllowed() 'password' => 'password', 'email' => 'test-registration@example.com', 'password_confirm' => 'password', - 'tos' => 1 + 'tos' => 1, ])); $this->Trait->register(); @@ -392,10 +425,73 @@ public function testRegisterLoggedInUserNotAllowed() $this->Trait->expects($this->once()) ->method('redirect') ->with(Configure::read('Users.Profile.route')); - $this->Trait->request->expects($this->never()) + $this->Trait->getRequest()->expects($this->never()) ->method('getData') ->with(); $this->Trait->register(); } + + /** + * test + * + * @return void + */ + public function testNotShowingVerboseErrorOnRegisterWithDefaultConfig() + { + //register user and not validate the email + $this->testRegister(); + + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('The user could not be saved'); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); + + $this->Trait->register(); + } + + /** + * test + * + * @return void + */ + public function testShowingVerboseErrorOnRegisterWithUpdatedConfig() + { + //register user and not validate the email + $this->testRegister(); + + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Email already exists'); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'username' => 'testRegistration1', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); + Configure::write('Users.Registration.showVerboseError', true); + $this->Trait->register(); + } } diff --git a/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php index be5358db3..dc792ee49 100644 --- a/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php +++ b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php @@ -1,19 +1,26 @@ traitClassName = 'CakeDC\Users\Controller\Traits\SimpleCrudTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'loadModel', 'paginate']; parent::setUp(); $viewVarsContainer = $this; @@ -44,7 +51,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { $this->viewVars = null; parent::tearDown(); @@ -67,8 +74,8 @@ public function testIndex() 'tableAlias' => 'Users', '_serialize' => [ 'Users', - 'tableAlias' - ] + 'tableAlias', + ], ]; $this->assertSame($expected, $this->viewVars); } @@ -87,8 +94,8 @@ public function testView() 'tableAlias' => 'Users', '_serialize' => [ 'Users', - 'tableAlias' - ] + 'tableAlias', + ], ]; $this->assertEquals($expected, $this->viewVars); } @@ -97,10 +104,10 @@ public function testView() * test * * @return void - * @expectedException Cake\Datasource\Exception\RecordNotFoundException */ public function testViewNotFound() { + $this->expectException(RecordNotFoundException::class); $this->Trait->view('00000000-0000-0000-0000-000000000000'); } @@ -108,10 +115,10 @@ public function testViewNotFound() * test * * @return void - * @expectedException Cake\Datasource\Exception\InvalidPrimaryKeyException */ public function testViewInvalidPK() { + $this->expectException(InvalidPrimaryKeyException::class); $this->Trait->view(); } @@ -125,12 +132,12 @@ public function testAddGet() $this->_mockRequestGet(); $this->Trait->add(); $expected = [ - 'Users' => $this->table->newEntity(), + 'Users' => $this->table->newEmptyEntity(), 'tableAlias' => 'Users', '_serialize' => [ 'Users', - 'tableAlias' - ] + 'tableAlias', + ], ]; $this->assertEquals($expected, $this->viewVars); } @@ -145,11 +152,11 @@ public function testAddPostHappy() $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('is') ->with('post') ->will($this->returnValue(true)); - $this->Trait->request->expects($this->at(1)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -178,11 +185,11 @@ public function testAddPostErrors() $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('is') ->with('post') ->will($this->returnValue(true)); - $this->Trait->request->expects($this->at(1)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -211,11 +218,11 @@ public function testEditPostHappy() $this->assertEquals('user-1@test.com', $this->table->get('00000000-0000-0000-0000-000000000001')->email); $this->_mockRequestPost(['patch', 'post', 'put']); $this->_mockFlash(); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('is') ->with(['patch', 'post', 'put']) ->will($this->returnValue(true)); - $this->Trait->request->expects($this->at(1)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -241,11 +248,11 @@ public function testEditPostErrors() $this->assertEquals('user-1@test.com', $this->table->get('00000000-0000-0000-0000-000000000001')->email); $this->_mockRequestPost(['patch', 'post', 'put']); $this->_mockFlash(); - $this->Trait->request->expects($this->at(0)) + $this->Trait->getRequest()->expects($this->once()) ->method('is') ->with(['patch', 'post', 'put']) ->will($this->returnValue(true)); - $this->Trait->request->expects($this->at(1)) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with() ->will($this->returnValue([ @@ -265,16 +272,20 @@ public function testEditPostErrors() * test * * @return void - * @expectedException Cake\Datasource\Exception\RecordNotFoundException */ public function testDeleteHappy() { + $this->expectException(RecordNotFoundException::class); $this->assertNotEmpty($this->table->get('00000000-0000-0000-0000-000000000001')); $this->_mockRequestPost(); - $this->Trait->request->expects($this->any()) - ->method('allow') + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'allowMethod']) + ->getMock(); + $request->expects($this->any()) + ->method('allowMethod') ->with(['post', 'delete']) ->will($this->returnValue(true)); + $this->Trait->setRequest($request); $this->_mockFlash(); $this->Trait->Flash->expects($this->once()) @@ -290,16 +301,21 @@ public function testDeleteHappy() * test * * @return void - * @expectedException Cake\Datasource\Exception\RecordNotFoundException */ public function testDeleteNotFound() { + $this->expectException(RecordNotFoundException::class); $this->assertNotEmpty($this->table->get('00000000-0000-0000-0000-000000000001')); $this->_mockRequestPost(); - $this->Trait->request->expects($this->any()) - ->method('allow') + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'allowMethod']) + ->getMock(); + $request->expects($this->any()) + ->method('allowMethod') ->with(['post', 'delete']) ->will($this->returnValue(true)); + $this->Trait->setRequest($request); $this->Trait->delete('00000000-0000-0000-0000-000000000000'); } diff --git a/tests/TestCase/Controller/Traits/SocialTraitTest.php b/tests/TestCase/Controller/Traits/SocialTraitTest.php index 054bc7cb1..b86d5a1a6 100644 --- a/tests/TestCase/Controller/Traits/SocialTraitTest.php +++ b/tests/TestCase/Controller/Traits/SocialTraitTest.php @@ -1,180 +1,137 @@ traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + parent::setUp(); - $this->controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(['header', 'redirect', 'render', '_stop']) + $request = new ServerRequest(); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods(['dispatchEvent', 'redirect', 'set', 'loadComponent']) ->getMock(); - $this->controller->Trait = $this->getMockForTrait( - 'CakeDC\Users\Controller\Traits\SocialTrait', - [], - '', - true, - true, - true, - ['_getOpauthInstance', 'redirect', '_generateOpauthCompleteUrl', '_afterIdentifyUser', '_validateRegisterPost'] - ); - } - - public function tearDown() - { - parent::tearDown(); + $this->Trait->request = $request; } /** - * Test socialEmail + * tearDown * + * @return void */ - public function testSocialEmail() + public function tearDown(): void { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['check', 'delete']) - ->getMock(); - $session->expects($this->at(0)) - ->method('check') - ->with('Users.social') - ->will($this->returnValue('social_key')); - - $session->expects($this->at(1)) - ->method('delete') - ->with('Flash.auth'); - - $this->controller->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); - - $this->controller->Trait->socialEmail(); + parent::tearDown(); } /** - * Test socialEmail + * test socialLogin success * - * @expectedException \Cake\Http\Exception\NotFoundException + * @return void */ - public function testSocialEmailInvalid() - { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['check']) - ->getMock(); - $session->expects($this->once()) - ->method('check') - ->with('Users.social') - ->will($this->returnValue(null)); - - $this->controller->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession']) - ->getMock(); - $this->controller->Trait->request->expects($this->once()) - ->method('getSession') - ->will($this->returnValue($session)); - - $this->controller->Trait->socialEmail(); - } - - public function testSocialEmailPostValidateFalse() - { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['check', 'delete']) - ->getMock(); - $session->expects($this->any()) - ->method('check') - ->with('Users.social') - ->will($this->returnValue(true)); - - $session->expects($this->once()) - ->method('delete') - ->with('Flash.auth'); - - $this->controller->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession', 'is']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); - - $this->controller->Trait->request->expects($this->once()) - ->method('is') - ->with('post') - ->will($this->returnValue(true)); - - $this->controller->Trait->expects($this->once()) - ->method('_validateRegisterPost') - ->will($this->returnValue(false)); - - $this->controller->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') - ->setMethods(['error']) - ->disableOriginalConstructor() - ->getMock(); - - $this->controller->Trait->Flash->expects($this->once()) - ->method('error') - ->with('The reCaptcha could not be validated'); - - $this->controller->Trait->socialEmail(); - } - - public function testSocialEmailPostValidateTrue() + public function testSocialEmailSuccess() { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['check', 'delete']) - ->getMock(); - $session->expects($this->any()) - ->method('check') - ->with('Users.social') - ->will($this->returnValue(true)); - - $session->expects($this->once()) - ->method('delete') - ->with('Flash.auth'); - - $this->controller->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession', 'is']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $FormAuth = new FormAuthenticator($identifiers); + $SessionAuth = new SessionAuthenticator($identifiers); + + $sessionFailure = new Failure( + $SessionAuth, + new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND) + ); + $formFailure = new Failure( + $FormAuth, + new Result(null, Result::FAILURE_CREDENTIALS_MISSING, [ + 'Password' => [], + ]) + ); + $failures = [$sessionFailure, $formFailure]; - $this->controller->Trait->request->expects($this->once()) + $this->_mockDispatchEvent(new Event('event')); + $this->Trait->setRequest($this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is']) + ->getMock()); + $this->Trait->getRequest()->expects($this->any()) ->method('is') ->with('post') ->will($this->returnValue(true)); - $this->controller->Trait->expects($this->once()) - ->method('_validateRegisterPost') - ->will($this->returnValue(true)); - - $this->controller->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['identify']) - ->disableOriginalConstructor() + $this->_mockFlash(); + $this->_mockAuthentication(['id' => 1], $failures); + $this->Trait->Flash->expects($this->never()) + ->method('error'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->successLoginRedirect) + ->will($this->returnValue(new Response())); + + $registry = new ComponentRegistry(); + $config = [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + SocialAuthenticator::FAILURE_USER_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + SocialAuthenticator::FAILURE_ACCOUNT_NOT_ACTIVE => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ), + ], + 'targetAuthenticator' => SocialAuthenticator::class, + ]; + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $config]) ->getMock(); - $this->controller->Trait->Auth->expects($this->once()) - ->method('identify'); - - $this->controller->Trait->expects($this->once()) - ->method('_afterIdentifyUser'); - - $this->controller->Trait->socialEmail(); + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); + + $result = $this->Trait->socialEmail(); + $this->assertInstanceOf(Response::class, $result); } } diff --git a/tests/TestCase/Controller/Traits/U2fTraitTest.php b/tests/TestCase/Controller/Traits/U2fTraitTest.php new file mode 100644 index 000000000..6d7db567e --- /dev/null +++ b/tests/TestCase/Controller/Traits/U2fTraitTest.php @@ -0,0 +1,771 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'createU2fLib', 'getData', 'getU2fAuthenticationChecker']; + + parent::setUp(); + + $this->Trait->expects($this->any()) + ->method('getU2fAuthenticationChecker') + ->willReturn(new DefaultU2fAuthenticationChecker()); + + $request = new ServerRequest(); + $this->Trait->setRequest($request); + Configure::write('U2f.enabled', true); + } + + /** + * Mock session and mock session attributes + * + * @return \Cake\Http\Session + */ + protected function _mockSession($attributes) + { + $session = new \Cake\Http\Session(); + + foreach ($attributes as $field => $value) { + $session->write($field, $value); + } + + $this->Trait + ->getRequest() + ->expects($this->any()) + ->method('getSession') + ->willReturn($session); + + return $session; + } + + /** + * Data provider for testU2User + * + * @return array + */ + public function dataProviderU2User() + { + $empty = []; + $withRegistration = new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]); + $withoutRegistration = new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]); + + return [ + // [$empty, ['action' => 'login']], + // [$withoutRegistration, ['action' => 'u2fRegister']], + [$withRegistration, ['action' => 'u2fAuthenticate']], + ]; + } + + /** + * Test u2f method + * + * @param array $userData session user data + * @param mixed $redirect expetected redirect + * @dataProvider dataProviderU2User + * @return void + */ + public function testU2fCustomUser($userData, $redirect) + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + $response = new Response([ + 'body' => (string)time(), + ]); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with( + $this->equalTo($redirect) + )->will($this->returnValue($response)); + $this->_mockSession([ + 'U2f.User' => $userData, + ]); + $actual = $this->Trait->u2f(); + $this->assertSame($response, $actual); + } + + /** + * Test u2fRegister method + * + * @return void + */ + public function testU2fRegisterOkay() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['getRegisterData']) + ->getMock(); + + $registerRequest = new RegisterRequest('sample chalange', 'https://localhost'); + $signs = [ + ['fake' => new \stdClass()], + ['fake2' => new \stdClass()], + ]; + $u2fLib->expects($this->once()) + ->method('getRegisterData') + ->will($this->returnValue([$registerRequest, $signs])); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + $this->Trait->expects($this->once()) + ->method('set') + ->with( + $this->equalTo([ + 'registerRequest' => $registerRequest, + 'signs' => $signs, + ]) + ); + $this->Trait->expects($this->never()) + ->method('redirect'); + + $this->_mockSession([ + 'U2f.User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]), + ]); + $actual = $this->Trait->u2fRegister(); + $this->assertNull($actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f.registerRequest'); + $expected = json_encode($registerRequest); + $this->assertEquals($expected, $actual); + } + + /** + * Data provider for testU2fRegisterRedirect + * + * @return array + */ + public function dataProviderU2fRegisterRedirect() + { + $empty = []; + $withRegistration = new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]); + + return [ + [$empty, ['action' => 'login']], + [$withRegistration, ['action' => 'u2fAuthenticate']], + ]; + } + + /** + * Test u2fRegister method + * + * @param array $userData session user data + * @param mixed $redirect expetected redirect + * @dataProvider dataProviderU2fRegisterRedirect + * @return void + */ + public function testU2fRegisterRedirect($userData, $redirect) + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + $this->Trait->expects($this->never()) + ->method('createU2fLib'); + + $this->Trait->expects($this->never()) + ->method('set'); + + $this->_mockSession([ + 'U2f.User' => $userData, + ]); + $response = new Response([ + 'body' => (string)time(), + ]); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with( + $this->equalTo($redirect) + )->will($this->returnValue($response)); + + $actual = $this->Trait->u2fRegister(); + $this->assertSame($response, $actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f.registerRequest'); + $expected = null; + $this->assertEquals($expected, $actual); + } + + /** + * Test u2fRegister method + * + * @return void + */ + public function testU2fRegisterFinishOkay() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is', 'getData']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['doRegister']) + ->getMock(); + + $registerRequest = new RegisterRequest('sample chalange', 'https://localhost'); + $registerRequest = json_decode(json_encode($registerRequest)); + $signs = [ + ['fake' => new \stdClass()], + ['fake2' => new \stdClass()], + ]; + $registerResponse = json_decode(json_encode([ + 'fakeA' => 'fakevaluea', + 'fakeB' => 'fakevalueb', + ])); + $registration = new Registration(); + $registration->certificate = 'user registration cert ' . time(); + $registration->counter = 1; + $registration->publicKey = 'pub skska08u90234230990'; + $registration->keyHandle = 'hahdofa02390423udu9ma0dumfá0dsufm2um9432uu903u923'; + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with($this->equalTo('registerResponse')) + ->will($this->returnValue(json_encode($registerResponse))); + $this->_mockSession([ + 'U2f' => [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]), + 'registerRequest' => json_encode($registerRequest), + ], + ]); + $u2fLib->expects($this->once()) + ->method('doRegister') + ->with( + $this->equalTo($registerRequest), + $this->equalTo($registerResponse) + ) + ->will($this->returnValue($registration)); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertNotNull($actual); + + $response = new Response(); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with( + $this->equalTo([ + 'action' => 'u2fAuthenticate', + ]) + )->will($this->returnValue($response)); + + $actual = $this->Trait->u2fRegisterFinish(); + $this->assertSame($response, $actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertEquals('00000000-0000-0000-0000-000000000002', $actual['User']['id']); + $this->assertEquals('user-2', $actual['User']['username']); + $this->assertNotEmpty($actual['User']['additional_data']); + $this->assertNotEmpty($actual['User']['additional_data']['u2f_registration']); + + $saveUser = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000002'); + + $savedRegistration = $saveUser->u2f_registration; + $this->assertNotNull($savedRegistration); + $this->assertEquals(json_encode($registration), json_encode($savedRegistration)); + + $registration = new Registration(); + $registration->certificate = 'user registration cert ' . time(); + $registration->counter = 1; + $registration->publicKey = 'pub skska08u90234230990'; + $registration->keyHandle = 'hahdofa02390423udu9ma0dumfá0dsufm2um9432uu903u923'; + } + + /** + * Test u2fRegister method + * + * @return void + */ + public function testU2fRegisterFinishException() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is', 'getData']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['doRegister']) + ->getMock(); + + $registerRequest = new RegisterRequest('sample chalange', 'https://localhost'); + $registerRequest = json_decode(json_encode($registerRequest)); + $registerResponse = json_decode(json_encode([ + 'fakeA' => 'fakevaluea', + 'fakeB' => 'fakevalueb', + ])); + $registration = new Registration(); + $registration->certificate = 'user registration cert ' . time(); + $registration->counter = 1; + $registration->publicKey = 'pub skska08u90234230990'; + $registration->keyHandle = 'hahdofa02390423udu9ma0dumfá0dsufm2um9432uu903u923'; + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with($this->equalTo('registerResponse')) + ->will($this->returnValue(json_encode($registerResponse))); + $this->_mockSession([ + 'U2f' => [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]), + 'registerRequest' => json_encode($registerRequest), + ], + ]); + $u2fLib->expects($this->once()) + ->method('doRegister') + ->with( + $this->equalTo($registerRequest), + $this->equalTo($registerResponse) + ) + ->will($this->throwException(new \Exception('Invalid request'))); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertNotNull($actual); + + $response = new Response(); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with( + $this->equalTo([ + 'action' => 'u2fRegister', + ]) + )->will($this->returnValue($response)); + + $actual = $this->Trait->u2fRegisterFinish(); + $this->assertSame($response, $actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertEquals( + [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]), + ], + $actual + ); + + $saveUser = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000002'); + + $savedRegistration = $saveUser->u2f_registration; + $this->assertNull($savedRegistration); + + $registration = new Registration(); + $registration->certificate = 'user registration cert ' . time(); + $registration->counter = 1; + $registration->publicKey = 'pub skska08u90234230990'; + $registration->keyHandle = 'hahdofa02390423udu9ma0dumfá0dsufm2um9432uu903u923'; + } + + /** + * Data provider for testU2fAuthenticateRedirectCustomUser + * + * @return array + */ + public function dataProviderU2fAuthenticateRedirectCustomUser() + { + $empty = []; + $withWhoutRegistration = new User([ + 'id' => '00000000-0000-0000-0000-000000000002', + 'username' => 'user-2', + ]); + + return [ + [$empty, ['action' => 'login']], + [$withWhoutRegistration, ['action' => 'u2fRegister']], + ]; + } + + /** + * Test u2fAuthenticate method redirect cases + * + * @param array $userData session user data + * @param mixed $redirect expetected redirect + * @dataProvider dataProviderU2fAuthenticateRedirectCustomUser + * @return void + */ + public function testU2fAuthenticateRedirectCustomUser($userData, $redirect) + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + $response = new Response([ + 'body' => (string)time(), + ]); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with( + $this->equalTo($redirect) + )->will($this->returnValue($response)); + $this->_mockSession([ + 'U2f.User' => $userData, + ]); + $actual = $this->Trait->u2fAuthenticate(); + $this->assertSame($response, $actual); + } + + /** + * Test u2fAuthenticate method + * + * @return void + */ + public function testU2fAuthenticate() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['getAuthenticateData']) + ->getMock(); + + $signs = [ + ['fake' => new \stdClass()], + ['fake2' => new \stdClass()], + ]; + $reg1 = [ + 'keyHandle' => 'fake key handle', + 'publicKey' => 'afdoaj0-23u423-ad ujsf-as8-0-afsd', + 'certificate' => '23jdsfoasdj0f9sa082304823423', + 'counter' => 1, + ]; + $registrations = [ + (object)$reg1, + ]; + $u2fLib->expects($this->once()) + ->method('getAuthenticateData') + ->with( + $this->equalTo($registrations) + ) + ->will($this->returnValue($signs)); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + $this->Trait->expects($this->once()) + ->method('set') + ->with( + $this->equalTo([ + 'authenticateRequest' => $signs, + ]) + ); + $this->Trait->expects($this->never()) + ->method('redirect'); + + $this->_mockSession([ + 'U2f.User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]), + ]); + $actual = $this->Trait->u2fAuthenticate(); + $this->assertNull($actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f.authenticateRequest'); + $expected = json_encode($signs); + $this->assertEquals($expected, $actual); + } + + /** + * Test u2fAuthenticateFinish method + * + * @return void + */ + public function testU2fAutheticateFinishOkay() + { + $user = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000001'); + $this->assertNotNull($user->u2f_registration); + + $registration = $user->u2f_registration; + $registrationEntityResult = new Registration(); + $registrationEntityResult->keyHandle = $registration->keyHandle; + $registrationEntityResult->publicKey = $registration->publicKey; + $registrationEntityResult->counter = $registration->counter + 1; + $registrationEntityResult->certificate = $registration->certificate; + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is', 'getData']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['doAuthenticate']) + ->getMock(); + + $signs = json_decode(json_encode([ + ['fake' => new \stdClass()], + ['fake2' => new \stdClass()], + ])); + $authenticateResponse = json_decode(json_encode([ + 'fakeA' => 'fakevaluea', + 'fakeB' => 'fakevalueb', + ])); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with($this->equalTo('authenticateResponse')) + ->will($this->returnValue(json_encode($authenticateResponse))); + $this->_mockSession([ + 'U2f' => [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]), + 'authenticateRequest' => json_encode($signs), + ], + ]); + + $u2fLib->expects($this->once()) + ->method('doAuthenticate') + ->with( + $this->equalTo($signs), + $this->equalTo([$registration]), + $this->equalTo($authenticateResponse) + ) + ->will($this->returnValue($registrationEntityResult)); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertNotNull($actual); + + $response = new Response(); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with([ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'login', + 'prefix' => false, + ])->will($this->returnValue($response)); + + $actual = $this->Trait->u2fAuthenticateFinish(); + $this->assertSame($response, $actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertNull($actual); + + $updatedEntity = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get($user['id']) + ->u2f_registration; + + $this->assertEquals($registrationEntityResult->counter, $updatedEntity->counter); + } + + /** + * Test u2fAuthenticateFinish method with exception + * + * @return void + */ + public function testU2fAutheticateFinishWithException() + { + $saveUser = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000001'); + + $savedRegistration = $saveUser->u2f_registration; + $this->assertNotNull($savedRegistration); + $registration = $saveUser->u2f_registration; + $counter = $registration->counter; + $this->assertNotNull($registration); + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is', 'getData']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->once()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $u2fLib = $this->getMockBuilder(U2F::class) + ->setConstructorArgs(['https://localhost']) + ->setMethods(['doAuthenticate']) + ->getMock(); + + $signs = json_decode(json_encode([ + ['fake' => new \stdClass()], + ['fake2' => new \stdClass()], + ])); + $authenticateResponse = json_decode(json_encode([ + 'fakeA' => 'fakevaluea', + 'fakeB' => 'fakevalueb', + ])); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with($this->equalTo('authenticateResponse')) + ->will($this->returnValue(json_encode($authenticateResponse))); + + $this->_mockSession([ + 'U2f' => [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]), + 'authenticateRequest' => json_encode($signs), + ], + ]); + + $u2fLib->expects($this->once()) + ->method('doAuthenticate') + ->with( + $this->equalTo($signs), + $this->equalTo([$registration]), + $this->equalTo($authenticateResponse) + ) + ->will($this->throwException(new \Exception('Invalid'))); + + $this->Trait->expects($this->once()) + ->method('createU2fLib') + ->will($this->returnValue($u2fLib)); + + $response = new Response(); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'u2fAuthenticate']) + ->will($this->returnValue($response)); + + $actual = $this->Trait->u2fAuthenticateFinish(); + $this->assertSame($response, $actual); + $actual = $this->Trait->getRequest()->getSession()->read('U2f'); + $this->assertEquals( + [ + 'User' => new User([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'username' => 'user-1', + ]), + ], + $actual + ); + + $updatedEntityUser = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get('00000000-0000-0000-0000-000000000001'); + + $updatedEntity = $updatedEntityUser->u2f_registration; + $this->assertEquals($counter, $updatedEntity->counter); + } +} diff --git a/tests/TestCase/Controller/Traits/UserValidationTraitTest.php b/tests/TestCase/Controller/Traits/UserValidationTraitTest.php index db475020b..584d8596c 100644 --- a/tests/TestCase/Controller/Traits/UserValidationTraitTest.php +++ b/tests/TestCase/Controller/Traits/UserValidationTraitTest.php @@ -1,29 +1,35 @@ traitClassName = 'CakeDC\Users\Controller\Traits\UserValidationTrait'; + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; $this->mockDefaultEmail = true; parent::setUp(); @@ -50,6 +56,26 @@ public function testValidateHappyEmail() $this->assertTrue($user->active); } + /** + * test + * + * @return void + */ + public function testValidateHappyEmailWithAfterEmailTokenValidationEvent() + { + $event = new Event('event'); + $event->setResult([ + 'action' => 'newAction', + ]); + $this->Trait->expects($this->once()) + ->method('dispatchEvent') + ->will($this->returnValue($event)); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'newAction']); + $this->Trait->validate('email', 'token-3'); + } + /** * test * @@ -84,6 +110,26 @@ public function testValidateTokenExpired() $this->Trait->validate('email', '6614f65816754310a5f0553436dd89e9'); } + /** + * test + * + * @return void + */ + public function testValidateTokenExpiredWithOnExpiredEvent() + { + $event = new Event('event'); + $event->setResult([ + 'action' => 'newAction', + ]); + $this->Trait->expects($this->once()) + ->method('dispatchEvent') + ->will($this->returnValue($event)); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'newAction']); + $this->Trait->validate('email', '6614f65816754310a5f0553436dd89e9'); + } + /** * test * @@ -132,7 +178,7 @@ public function testResendTokenValidationHappy() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue('user-3')); @@ -146,6 +192,34 @@ public function testResendTokenValidationHappy() $this->Trait->resendTokenValidation(); } + /** + * test + * + * @return void + */ + public function testResendTokenValidationWithAfterResendTokenValidationEvent() + { + $this->_mockRequestPost(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue('user-3')); + + $event = new Event('event'); + $event->setResult([ + 'action' => 'newAction', + ]); + $this->Trait->expects($this->once()) + ->method('dispatchEvent') + ->will($this->returnValue($event)); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'newAction']); + + $this->Trait->resendTokenValidation(); + } + /** * test * @@ -155,7 +229,7 @@ public function testResendTokenValidationAlreadyActive() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue('user-4')); @@ -178,7 +252,7 @@ public function testResendTokenValidationNotFound() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) + $this->Trait->getRequest()->expects($this->once()) ->method('getData') ->with('reference') ->will($this->returnValue('not-found')); diff --git a/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php new file mode 100644 index 000000000..e47f2c661 --- /dev/null +++ b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php @@ -0,0 +1,493 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'createU2fLib', 'getData', 'getU2fAuthenticationChecker']; + + parent::setUp(); + + $request = new ServerRequest(); + $this->Trait->setRequest($request); + Configure::write('Webauthn2fa.enabled', true); + Configure::write('Webauthn2fa.appName', 'ACME Webauthn Server'); + Configure::write('Webauthn2fa.id', 'localhost'); + } + + /** + * Mock session and mock session attributes + * + * @return \Cake\Http\Session + */ + protected function _mockSession($attributes) + { + $session = new \Cake\Http\Session(); + + foreach ($attributes as $field => $value) { + $session->write($field, $value); + } + + $this->Trait + ->getRequest() + ->expects($this->any()) + ->method('getSession') + ->willReturn($session); + + return $session; + } + + /** + * Test webauthn2fa method when requires register + * + * @return void + */ + public function testWebauthn2faIsRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->Trait + ->expects($this->exactly(2)) + ->method('set') + ->withConsecutive(['isRegister', true], ['username', 'user-2']); + $this->Trait->webauthn2fa(); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2fa method when DON'T require register + * + * @return void + */ + public function testWebauthn2faDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->Trait + ->expects($this->exactly(2)) + ->method('set') + ->withConsecutive(['isRegister', false], ['username', 'user-1']); + $this->Trait->webauthn2fa(); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faRegisterOptions method when DON'T require register + * + * @return void + */ + public function testWebauthn2faRegisterOptionsDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('User already has configured webauthn2fa'); + $this->Trait->webauthn2faRegisterOptions(); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegisterOptions() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + + $response = $this->Trait->webauthn2faRegisterOptions(); + $data = json_decode((string)$response->getBody(), true); + $this->assertArrayHasKey('rp', $data); + $this->assertArrayHasKey('user', $data); + $this->assertSame('user-2', $data['user']['name']); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faRegister method when DON'T require register + * + * @return void + */ + public function testWebauthn2faRegisterDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('User already has configured webauthn2fa'); + $this->Trait->webauthn2faRegister(); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegister() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(RegisterAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000002'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willReturn($credential); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faRegisterAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + $request = $request->withParsedBody( + json_decode($data, true) + ); + $this->Trait->setRequest($request); + $response = $this->Trait->webauthn2faRegister(); + $this->assertEquals('{"success":true}', (string)$response->getBody()); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegisterError() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(RegisterAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willThrowException(new \Exception('Testing error exception for webauthn2faRegister')); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faRegisterAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + $request = $request->withParsedBody( + json_decode($data, true) + ); + $this->Trait->setRequest($request); + $this->expectException(\Exception::class); + $this->expectErrorMessage('Testing error exception for webauthn2faRegister'); + $this->Trait->webauthn2faRegister(); + } + + /** + * Test webauthn2faAuthenticateOptions + * + * @return void + */ + public function testWebauthn2faAuthenticateOptions() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + + $response = $this->Trait->webauthn2faAuthenticateOptions(); + $data = json_decode((string)$response->getBody(), true); + $this->assertArrayHasKey('challenge', $data); + $this->assertArrayHasKey('userVerification', $data); + $this->assertArrayHasKey('allowCredentials', $data); + $expectedCredentials = [ + [ + 'type' => 'public-key', + 'id' => '12b37486-9299-4331-ac33-85b2d985b6fe', + ], + ]; + $this->assertEquals($expectedCredentials, $data['allowCredentials']); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faAuthenticateOptions method + * + * @return void + */ + public function testWebauthn2faAuthenticate() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(AuthenticateAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000002'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willReturn($credential); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faAuthenticateAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $this->Trait->setRequest($request); + $response = $this->Trait->webauthn2faAuthenticate(); + $expected = [ + 'success' => true, + 'redirectUrl' => '/login', + ]; + $actual = json_decode((string)$response->getBody(), true); + $this->assertEquals($expected, $actual); + + $this->assertNull( + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + $userSession = $this->Trait->getRequest()->getSession()->read('TwoFactorAuthenticator.User'); + $this->assertInstanceOf( + User::class, + $userSession + ); + $this->assertEquals($userSession->toArray(), $user->toArray()); + } + + /** + * Test webauthn2faAuthenticateOptions method + * + * @return void + */ + public function testWebauthn2faAuthenticateError() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(AuthenticateAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willThrowException(new \Exception('Test exception error for webauthn2faAuthenticate')); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faAuthenticateAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $this->Trait->setRequest($request); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Test exception error for webauthn2faAuthenticate'); + $this->Trait->webauthn2faAuthenticate(); + } +} diff --git a/tests/TestCase/Controller/UsersControllerTest.php b/tests/TestCase/Controller/UsersControllerTest.php new file mode 100644 index 000000000..9cebd1f89 --- /dev/null +++ b/tests/TestCase/Controller/UsersControllerTest.php @@ -0,0 +1,94 @@ +assertInstanceOf(ServerRequest::class, $request); + $this->assertIsArray($options); + + return '/my/custom/url/'; + }); + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://localhost/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/my/custom/url/'); + } + + /** + * Test unathorize redirect when user is NOT logged + * + * @return void + */ + public function testUnauthorizedRedirectNotLogged() + { + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://localhost/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/login?redirect=http%3A%2F%2Flocalhost%2Fusers%2Findex'); + } + + /** + * Test unathorize redirect when user is logged + * + * @return void + */ + public function testUnauthorizedRedirectLogged() + { + $userId = '00000000-0000-0000-0000-000000000004'; + $user = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get($userId); + + $this->session(['Auth' => $user]); + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://example.com/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/profile'); + } +} diff --git a/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php b/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php index a42d842f9..e7951a877 100644 --- a/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php +++ b/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php @@ -1,25 +1,27 @@ assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Exception/MissingEmailExceptionTest.php b/tests/TestCase/Exception/MissingEmailExceptionTest.php index 299b51b3f..08a49fb99 100644 --- a/tests/TestCase/Exception/MissingEmailExceptionTest.php +++ b/tests/TestCase/Exception/MissingEmailExceptionTest.php @@ -1,25 +1,27 @@ assertEquals('message', $exception->getMessage()); - } -} diff --git a/tests/TestCase/Exception/TokenExpiredExceptionTest.php b/tests/TestCase/Exception/TokenExpiredExceptionTest.php index 195ae5432..1b3fbf552 100644 --- a/tests/TestCase/Exception/TokenExpiredExceptionTest.php +++ b/tests/TestCase/Exception/TokenExpiredExceptionTest.php @@ -1,25 +1,27 @@ '']; + $result = $identifier->identify($user); + $this->assertNull($result); + + $identifier = new SocialIdentifier([]); + + $user = []; + $result = $identifier->identify($user); + $this->assertNull($result); + } + + /** + * Test identify method + * + * @return void + */ + public function testIdentify() + { + $identifier = new SocialIdentifier([]); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $data = [ + 'token' => $Token, + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]; + + $mapper = new Facebook(); + $user = $mapper($data); + $user['provider'] = 'facebook'; + + $result = $identifier->identify(['socialAuthUser' => $user]); + $this->assertInstanceOf('CakeDC\Users\Model\Entity\User', $result); + $this->assertNotEmpty($result->id); + $this->assertEquals('test@gmail.com', $result->email); + $this->assertEquals('test', $result->username); + } + + /** + * Test identify method error in social login + * + * @return void + */ + public function testIdentifyErrorSocialLogin() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $identifier = $this->getMockBuilder(SocialIdentifier::class)->setMethods([ + 'createOrGetUser', + ])->getMock(); + $identifier->expects($this->once()) + ->method('createOrGetUser') + ->will($this->returnValue(false)); + + $data = [ + 'token' => $Token, + 'id' => '1', + 'name' => '', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]; + + $mapper = new Facebook(); + $user = $mapper($data); + $user['provider'] = 'facebook'; + + $result = $identifier->identify(['socialAuthUser' => $user]); + $this->assertNull($result); + } + + /** + * Test identify method no email + * + * @return void + */ + public function testIdentifyNoEmail() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $data = [ + 'token' => $Token, + 'id' => '1', + 'name' => '', + 'first_name' => 'Test', + 'last_name' => 'User', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]; + + $identifier = new SocialIdentifier([]); + $mapper = new Facebook(); + $user = $mapper($data); + $user['provider'] = 'facebook'; + + $this->expectException(MissingEmailException::class); + $identifier->identify(['socialAuthUser' => $user]); + } +} diff --git a/tests/TestCase/Mailer/UsersMailerTest.php b/tests/TestCase/Mailer/UsersMailerTest.php index ae79ca496..294c245e9 100644 --- a/tests/TestCase/Mailer/UsersMailerTest.php +++ b/tests/TestCase/Mailer/UsersMailerTest.php @@ -1,19 +1,22 @@ UsersMailer = new UsersMailer(); parent::setUp(); - $this->Email = $this->getMockBuilder('Cake\Mailer\Email') - ->setMethods(['setTo', 'setSubject', 'setViewVars', 'setTemplate']) - ->getMock(); - - $this->UsersMailer = $this->getMockBuilder('CakeDC\Users\Mailer\UsersMailer') - ->setConstructorArgs([$this->Email]) - ->setMethods(['setTo', 'setSubject', 'setViewVars', 'setTemplate']) - ->getMock(); } /** @@ -53,10 +53,9 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->UsersMailer); - unset($this->Email); parent::tearDown(); } @@ -68,28 +67,33 @@ public function tearDown() public function testValidation() { $table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - $data = [ + $expectedViewVars = [ + 'activationUrl' => [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + '_full' => true, + '12345678', + ], 'first_name' => 'FirstName', + 'last_name' => 'Bond', 'email' => 'test@example.com', - 'token' => '12345' + 'token' => '12345678', ]; - $user = $table->newEntity($data); - $this->UsersMailer->expects($this->once()) - ->method('setTo') - ->with($user['email']) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setSubject') - ->with('FirstName, Your account validation link') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setViewVars') - ->with($data) - ->will($this->returnValue($this->Email)); + $user = $table->newEntity([ + 'first_name' => 'FirstName', + 'last_name' => 'Bond', + 'email' => 'test@example.com', + 'token' => '12345678', + ]); $this->invokeMethod($this->UsersMailer, 'validation', [$user]); + $this->assertSame(['test@example.com' => 'test@example.com'], $this->UsersMailer->getTo()); + $this->assertSame('FirstName, Your account validation link', $this->UsersMailer->getSubject()); + $this->assertSame(Message::MESSAGE_BOTH, $this->UsersMailer->getEmailFormat()); + $this->assertSame($expectedViewVars, $this->UsersMailer->viewBuilder()->getVars()); + $this->assertSame('CakeDC/Users.validation', $this->UsersMailer->viewBuilder()->getTemplate()); } /** @@ -101,23 +105,28 @@ public function testSocialAccountValidation() { $social = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts') ->get('00000000-0000-0000-0000-000000000001', ['contain' => 'Users']); - - $this->UsersMailer->expects($this->once()) - ->method('setTo') - ->with('user-1@test.com') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setSubject') - ->with('first1, Your social account validation link') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setViewVars') - ->with(['user' => $social->user, 'socialAccount' => $social]) - ->will($this->returnValue($this->Email)); + $this->assertInstanceOf(User::class, $social->user); + $expectedViewVars = [ + 'user' => $social->user, + 'socialAccount' => $social, + 'activationUrl' => [ + '_full' => true, + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => 'validateAccount', + 'Facebook', + 'reference-1-1234', + 'token-1234', + ], + ]; $this->invokeMethod($this->UsersMailer, 'socialAccountValidation', [$social->user, $social]); + $this->assertSame(['user-1@test.com' => 'user-1@test.com'], $this->UsersMailer->getTo()); + $this->assertSame('first1, Your social account validation link', $this->UsersMailer->getSubject()); + $this->assertSame(Message::MESSAGE_BOTH, $this->UsersMailer->getEmailFormat()); + $this->assertSame($expectedViewVars, $this->UsersMailer->viewBuilder()->getVars()); + $this->assertSame('CakeDC/Users.socialAccountValidation', $this->UsersMailer->viewBuilder()->getTemplate()); } /** @@ -128,28 +137,31 @@ public function testSocialAccountValidation() public function testResetPassword() { $table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); - $data = [ + $user = $table->newEntity([ + 'first_name' => 'FirstName', + 'email' => 'test@example.com', + 'token' => '12345', + ]); + $expectedViewVars = [ + 'activationUrl' => [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'resetPassword', + '_full' => true, + '12345', + ], 'first_name' => 'FirstName', 'email' => 'test@example.com', - 'token' => '12345' + 'token' => '12345', ]; - $user = $table->newEntity($data); - $this->UsersMailer->expects($this->once()) - ->method('setTo') - ->with($user['email']) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setSubject') - ->with('FirstName, Your reset password link') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('setViewVars') - ->with($data) - ->will($this->returnValue($this->Email)); $this->invokeMethod($this->UsersMailer, 'resetPassword', [$user]); + $this->assertSame(['test@example.com' => 'test@example.com'], $this->UsersMailer->getTo()); + $this->assertSame('FirstName, Your reset password link', $this->UsersMailer->getSubject()); + $this->assertSame(Message::MESSAGE_BOTH, $this->UsersMailer->getEmailFormat()); + $this->assertSame($expectedViewVars, $this->UsersMailer->viewBuilder()->getVars()); + $this->assertSame('CakeDC/Users.resetPassword', $this->UsersMailer->viewBuilder()->getTemplate()); } /** @@ -158,7 +170,6 @@ public function testResetPassword() * @param object &$object Instantiated object that we will run method on. * @param string $methodName Method name to call * @param array $parameters Array of parameters to pass into method. - * * @return mixed Method return. */ public function invokeMethod(&$object, $methodName, $parameters = []) diff --git a/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php b/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php new file mode 100644 index 000000000..6bbb8e28d --- /dev/null +++ b/tests/TestCase/Middleware/SocialAuthMiddlewareTest.php @@ -0,0 +1,393 @@ +Provider = $this->getMockBuilder('\League\OAuth2\Client\Provider\Facebook')->setConstructorArgs([ + [ + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + [], + ])->setMethods([ + 'getAccessToken', 'getState', 'getAuthorizationUrl', 'getResourceOwner', + ])->getMock(); + + $config = [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => $this->Provider, + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'options' => [ + 'state' => '__TEST_STATE__', + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => false, + ], + ]; + Configure::write('OAuth.providers.facebook', $config); + + $this->Request = ServerRequestFactory::fromGlobals(); + } + + /** + * teardown any static object changes and restore them. + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + + unset($this->Provider, $this->Request); + } + + /** + * Test when user is on step one + * + * @return void + */ + public function testProceedStepOne() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + + $this->Provider->expects($this->any()) + ->method('getState') + ->will($this->returnValue('_NEW_STATE_')); + + $this->Provider->expects($this->any()) + ->method('getAuthorizationUrl') + ->will($this->returnValue('http://facebook.com/redirect/url')); + + $Middleware = new SocialAuthMiddleware(); + $response = new Response(); + $handlerCb = function () use ($response) { + $this->fail('Should not call $next'); + }; + + $handler = new TestRequestHandler($handlerCb); + /** + * @var Response $result + */ + $result = $Middleware->process($this->Request, $handler); + $this->assertInstanceOf(Response::class, $result); + if (!$result) { + $this->fail('No response set, cannot assert location header. '); + } + + $actual = $this->Request->getSession()->read('oauth2state'); + $expected = '_NEW_STATE_'; + $this->assertEquals($expected, $actual); + + $actual = $result->getHeaderLine('Location'); + $expected = 'http://facebook.com/redirect/url'; + $this->assertEquals($expected, $actual); + } + + /** + * Test when user is on get user step + * + * @return void + */ + public function testSuccessfullyAuthenticated() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + $Middleware = new SocialAuthMiddleware(); + + $ResponseOriginal = new Response(); + $checked = false; + $handlerCb = function (ServerRequest $request) use ($ResponseOriginal, &$checked) { + /** + * @var OAuth2Service $service + */ + $service = $request->getAttribute('socialService'); + $this->assertInstanceOf(OAuth2Service::class, $service); + $this->assertEquals('facebook', $service->getProviderName()); + $this->assertTrue($service->isGetUserStep($request)); + $checked = true; + + return $ResponseOriginal; + }; + $handler = new TestRequestHandler($handlerCb); + $response = $Middleware->process($this->Request, $handler); + + $this->assertSame($response, $ResponseOriginal); + $this->assertTrue($checked); + } + + /** + * Data provider for testSocialAuthenticationException + * + * @return array + */ + public function dataProviderSocialAuthenticationException() + { + $missingEmail = [ + new MissingEmailException('Missing email'), + [ + 'key' => 'flash', + 'element' => 'Flash/error', + 'params' => [], + 'message' => __d('cake_d_c/users', 'Please enter your email'), + ], + '/users/users/social-email', + true, + ]; + $unknown = [ + new UnexpectedValueException('User not active'), + [ + 'key' => 'flash', + 'element' => 'Flash/error', + 'params' => [], + 'message' => __d('cake_d_c/users', 'Could not identify your account, please try again'), + ], + '/login', + false, + ]; + + return [ + $missingEmail, + $unknown, + ]; + } + + /** + * Test when has error getting user + * + * @param \Exception $previousException previous exception used on SocialAuthenticationException + * @param array $flash flash that should be on session + * @param array $location value of location header that should be on request + * @param bool $keepSocialUser should keed a raw data of social user + * @dataProvider dataProviderSocialAuthenticationException + * @return void + */ + public function testSocialAuthenticationException($previousException, $flash, $location, $keepSocialUser) + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/login', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'login', + '_ext' => null, + 'prefix' => null, + ]); + $builder->connect('/users/users/social-email', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + '_ext' => null, + 'prefix' => null, + ]); + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withQueryParams([ + 'code' => 'ZPO9972j3092304230', + 'state' => '__TEST_STATE__', + ]); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'provider' => 'facebook', + ]); + $this->Request->getSession()->write('oauth2state', '__TEST_STATE__'); + + $Middleware = new SocialAuthMiddleware(); + + $ResponseOriginal = new Response(); + $checked = false; + + $service = (new ServiceFactory())->createFromProvider('facebook'); + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => 'test@gmail.com', + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + $user = ['token' => $Token] + $user->toArray(); + $mapper = new MapUser(); + $rawData = $mapper($service, $user); + $handlerCb = function (ServerRequest $request) use ($previousException, $ResponseOriginal, &$checked, $rawData) { + /** + * @var OAuth2Service $service + */ + $service = $request->getAttribute('socialService'); + $this->assertInstanceOf(OAuth2Service::class, $service); + $this->assertEquals('facebook', $service->getProviderName()); + $this->assertTrue($service->isGetUserStep($request)); + $checked = true; + + throw new SocialAuthenticationException( + [ + 'rawData' => $rawData, + ], + null, + $previousException + ); + }; + $handler = new TestRequestHandler($handlerCb); + /** + * @var Response $result + */ + $result = $Middleware->process($this->Request, $handler); + + $this->assertInstanceOf(Response::class, $result); + $actual = $result->getHeader('Location'); + $expected = [$location]; + $this->assertEquals($expected, $actual); + $expected = [ + $flash, + ]; + $actual = $this->Request->getSession()->read('Flash.flash'); + $this->assertEquals($expected, $actual); + + if ($keepSocialUser) { + $actual = $this->Request->getSession()->read(Configure::read('Users.Key.Session.social')); + $mapper = new MapUser(); + $expected = $mapper($service, $user); + $this->assertEquals($expected, $actual); + } + } + + /** + * Test when action is not valid for social login + * + * @return void + */ + public function testNotValidAction() + { + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $Middleware = new SocialAuthMiddleware(); + $handlerCb = function ($request) use ($response) { + return $response; + }; + + $handler = new TestRequestHandler($handlerCb); + /** + * @var Response $result + */ + $result = $Middleware->process($this->Request, $handler); + $this->assertSame($result, $result); + } +} diff --git a/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php b/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php new file mode 100644 index 000000000..3bfc954df --- /dev/null +++ b/tests/TestCase/Middleware/SocialEmailMiddlewareTest.php @@ -0,0 +1,387 @@ + 'CakeDC\Auth\Social\Service\OAuth2Service', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'options' => [ + 'state' => '__TEST_STATE__', + 'graphApiVersion' => 'v2.8', + 'redirectUri' => '/auth/facebook', + 'linkSocialUri' => '/link-social/facebook', + 'callbackLinkSocialUri' => '/callback-link-social/facebook', + 'clientId' => '10003030300303', + 'clientSecret' => 'secretpassword', + ], + 'collaborators' => [], + 'signature' => null, + 'mapFields' => [], + 'path' => [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + 'prefix' => null, + ], + ]; + Configure::write('OAuth.providers.facebook', $config); + + $this->Request = ServerRequestFactory::fromGlobals(); + } + + /** + * teardown any static object changes and restore them. + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + + unset($this->Request); + } + + /** + * Test when action with get request + * + * @return void + */ + public function testWithGetRquest() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => null, + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + $user = [ + 'token' => $Token, + ] + $user->toArray(); + + $mapper = new Facebook(); + $user = $mapper($user); + $user['provider'] = 'facebook'; + $user['validated'] = true; + Configure::write('Users.Email.validate', false); + $this->Request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $Middleware = new SocialEmailMiddleware(); + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $handler = $this->getMockBuilder(Runner::class) + ->setMethods(['handle']) + ->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with($this->equalTo($this->Request)) + ->willReturn($response); + + $result = $Middleware->process($this->Request, $handler); + $this->assertSame($response, $result); + $this->assertEmpty($this->Request->getSession()->read('Auth')); + $this->assertEmpty($this->Request->getSession()->read('Users.successSocialLogin')); + } + + /** + * Test when action without user + * + * @return void + */ + public function testWithoutUser() + { + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withParsedBody([ + 'email' => 'example@example.com', + ]); + $this->Request = $this->Request->withMethod('POST'); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $Middleware = new SocialEmailMiddleware(); + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $handler = $this->getMockBuilder(Runner::class) + ->setMethods(['handle']) + ->getMock(); + $handler->expects($this->never()) + ->method('handle'); + + $this->expectException(NotFoundException::class); + $Middleware->process($this->Request, $handler); + } + + /** + * Test when action with successfull authentication + * + * @return void + */ + public function testWithUser() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => null, + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + $user = [ + 'token' => $Token, + ] + $user->toArray(); + + $mapper = new Facebook(); + $user = $mapper($user); + $user['provider'] = 'facebook'; + $user['validated'] = true; + Configure::write('Users.Email.validate', false); + $this->Request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withParsedBody([ + 'email' => 'example@example.com', + ]); + $this->Request = $this->Request->withMethod('POST'); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $Middleware = new SocialEmailMiddleware(); + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $handler = $this->getMockBuilder(Runner::class) + ->setMethods(['handle']) + ->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with($this->equalTo($this->Request)) + ->willReturn($response); + + $result = $Middleware->process($this->Request, $handler); + $this->assertSame($response, $result); + + $actual = $this->Request->getSession()->read(Configure::read('Users.Key.Session.social')); + $this->assertSame($user, $actual); + } + + /** + * Test when action without email + * + * @return void + */ + public function testWithoutEmail() + { + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $user = new FacebookUser([ + 'id' => '1', + 'name' => 'Test User', + 'first_name' => 'Test', + 'last_name' => 'User', + 'email' => null, + 'hometown' => [ + 'id' => '108226049197930', + 'name' => 'Madrid', + ], + 'picture' => [ + 'data' => [ + 'url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + ], + ], + 'cover' => [ + 'source' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'id' => '1', + ], + 'gender' => 'male', + 'locale' => 'en_US', + 'link' => 'https://www.facebook.com/app_scoped_user_id/1/', + 'timezone' => -5, + 'age_range' => [ + 'min' => 21, + ], + 'bio' => 'I am the best test user in the world.', + 'picture_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + 'is_silhouette' => false, + 'cover_photo_url' => 'https://scontent.xx.fbcdn.net/v/test.jpg', + ]); + $user = [ + 'token' => $Token, + ] + $user->toArray(); + + $mapper = new Facebook(); + $user = $mapper($user); + $user['provider'] = 'facebook'; + $user['validated'] = true; + Configure::write('Users.Email.validate', false); + $this->Request->getSession()->write(Configure::read('Users.Key.Session.social'), $user); + + $uri = new Uri('/auth/facebook'); + $this->Request = $this->Request->withUri($uri); + $this->Request = $this->Request->withMethod('POST'); + $this->Request = $this->Request->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialEmail', + ]); + + $Middleware = new SocialEmailMiddleware(); + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $handler = $this->getMockBuilder(Runner::class) + ->setMethods(['handle']) + ->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with($this->equalTo($this->Request)) + ->willReturn($response); + + $result = $Middleware->process($this->Request, $handler); + $this->assertSame($response, $result); + $this->assertEmpty($this->Request->getSession()->read('Auth')); + } + + /** + * Test when action is not valid for social login + * + * @return void + */ + public function testNotValidAction() + { + $Middleware = new SocialEmailMiddleware(); + $response = new Response(); + $response = $response->withStringBody(__METHOD__ . time()); + $handler = $this->getMockBuilder(Runner::class) + ->setMethods(['handle']) + ->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with($this->equalTo($this->Request)) + ->willReturn($response); + + $result = $Middleware->process($this->Request, $handler); + $this->assertSame($response, $result); + } +} diff --git a/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php b/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php index 2134da7eb..b98fa6dac 100644 --- a/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php @@ -1,25 +1,22 @@ table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); @@ -52,7 +49,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->table, $this->Behavior); parent::tearDown(); @@ -60,7 +57,6 @@ public function tearDown() /** * Test findActive method. - * */ public function testFindActive() { @@ -72,13 +68,12 @@ public function testFindActive() /** * Test findAuth method. - * - * @expectedException \BadMethodCallException - * @expectedExceptionMessage Missing 'username' in options data */ public function testFindAuthBadMethodCallException() { - $user = $this->table->find('auth'); + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage("Missing 'username' in options data"); + $this->table->find('auth'); } /** diff --git a/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php index 0d2710ae0..df6e5620e 100644 --- a/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php @@ -1,28 +1,28 @@ Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); @@ -57,7 +57,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Table, $this->Behavior); parent::tearDown(); @@ -69,7 +69,6 @@ public function tearDown() * @param array $data Test input data * @param string $userId User id to add social account * @param array $result Expected result - * * @author Marcelo Rocha * @return void * @dataProvider providerFacebookLinkSocialAccount @@ -77,7 +76,7 @@ public function tearDown() public function testlinkSocialAccountFacebookProvider($data, $userId, $result) { $user = $this->Table->get($userId, [ - 'contain' => 'SocialAccounts' + 'contain' => 'SocialAccounts', ]); $resultUser = $this->Behavior->linkSocialAccount($user, $data); $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); @@ -102,7 +101,7 @@ public function testlinkSocialAccountFacebookProvider($data, $userId, $result) */ public function providerFacebookLinkSocialAccount() { - $expiresTime = new Time(); + $expiresTime = new DateTime(); $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); return [ @@ -122,18 +121,18 @@ public function providerFacebookLinkSocialAccount() 'email' => 'user-1@test.com', 'picture' => [ 'data' => [ - 'url' => 'data-url' - ] - ] + 'url' => 'data-url', + ], + ], ], 'credentials' => [ 'token' => 'token', 'secret' => null, - 'expires' => 1458423682 + 'expires' => 1458423682, ], 'validated' => true, 'link' => 'facebook-link-15579', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'user' => '00000000-0000-0000-0000-000000000001', 'result' => [ @@ -147,10 +146,10 @@ public function providerFacebookLinkSocialAccount() 'token_secret' => null, 'token_expires' => $tokenExpires, 'user_id' => '00000000-0000-0000-0000-000000000001', - 'active' => true + 'active' => true, - ] - ] + ], + ], ]; } @@ -160,13 +159,11 @@ public function providerFacebookLinkSocialAccount() * * @param array $data Test input data * @param string $userId User id to add social account - * @param array $result Expected result - * * @author Marcelo Rocha * @return void * @dataProvider providerFacebookLinkSocialAccountErrorSaving */ - public function testlinkSocialAccountErrorSavingFacebookProvider($data, $userId, $result) + public function testlinkSocialAccountErrorSavingFacebookProvider($data, $userId) { $user = $this->Table->get($userId); $resultUser = $this->Behavior->linkSocialAccount($user, $data); @@ -180,15 +177,14 @@ public function testlinkSocialAccountErrorSavingFacebookProvider($data, $userId, 'social_accounts' => [ [ 'token' => [ - '_empty' => 'This field cannot be left empty' - ] - ] - ] + '_empty' => 'This field cannot be left empty', + '_required' => 'This field is required', + + ], + ], + ], ]; $this->assertEquals($expected, $actual); - - $error = $user->getErrors('social_accounts'); - $error = $error ? reset($error) : $message; } /** @@ -199,7 +195,7 @@ public function testlinkSocialAccountErrorSavingFacebookProvider($data, $userId, */ public function providerFacebookLinkSocialAccountErrorSaving() { - $expiresTime = new Time(); + $expiresTime = new DateTime(); $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); return [ @@ -219,18 +215,18 @@ public function providerFacebookLinkSocialAccountErrorSaving() 'email' => 'user-1@test.com', 'picture' => [ 'data' => [ - 'url' => 'data-url' - ] - ] + 'url' => 'data-url', + ], + ], ], 'credentials' => [ 'token' => '', 'secret' => null, - 'expires' => 1458423682 + 'expires' => 1458423682, ], 'validated' => true, 'link' => 'facebook-link-15579', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'user' => '00000000-0000-0000-0000-000000000001', 'result' => [ @@ -244,10 +240,10 @@ public function providerFacebookLinkSocialAccountErrorSaving() 'token_secret' => null, 'token_expires' => $tokenExpires, 'user_id' => '00000000-0000-0000-0000-000000000001', - 'active' => true + 'active' => true, - ] - ] + ], + ], ]; } @@ -258,7 +254,6 @@ public function providerFacebookLinkSocialAccountErrorSaving() * @param array $data Test input data * @param string $userId User id to add social account * @param array $result Expected result - * * @author Marcelo Rocha * @return void * @dataProvider providerFacebookLinkSocialAccountAccountExists @@ -271,20 +266,20 @@ public function testlinkSocialAccountFacebookProviderAccountExists($data, $userI $this->assertFalse($resultUser->has('social_accounts')); $expected = [ 'social_accounts' => [ - '_existsIn' => __d('CakeDC/Users', 'Social account already associated to another user') - ] + '_existsIn' => __d('cake_d_c/users', 'Social account already associated to another user'), + ], ]; $actual = $user->getErrors(); $this->assertEquals($expected, $actual); //Se for o usuário que já esta associado então okay - $socialAccount = $this->Table->SocialAccounts->find()->where([ + $this->Table->SocialAccounts->find()->where([ 'reference' => $data['id'], - 'provider' => $data['provider'] + 'provider' => $data['provider'], ])->firstOrFail(); $userBase = $this->Table->get('00000000-0000-0000-0000-000000000002', [ - 'contain' => ['SocialAccounts'] + 'contain' => ['SocialAccounts'], ]); $resultUser = $this->Behavior->linkSocialAccount($userBase, $data); $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); @@ -308,7 +303,7 @@ public function testlinkSocialAccountFacebookProviderAccountExists($data, $userI */ public function providerFacebookLinkSocialAccountAccountExists() { - $expiresTime = new Time(); + $expiresTime = new DateTime(); $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); return [ @@ -328,18 +323,18 @@ public function providerFacebookLinkSocialAccountAccountExists() 'email' => 'email@example.com', 'picture' => [ 'data' => [ - 'url' => 'data-url' - ] - ] + 'url' => 'data-url', + ], + ], ], 'credentials' => [ 'token' => 'token', 'secret' => null, - 'expires' => 1458423682 + 'expires' => 1458423682, ], 'validated' => true, 'link' => 'facebook-link-15579', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'user' => '00000000-0000-0000-0000-000000000001', 'result' => [ @@ -354,9 +349,9 @@ public function providerFacebookLinkSocialAccountAccountExists() 'token_secret' => null, 'token_expires' => $tokenExpires, 'user_id' => '00000000-0000-0000-0000-000000000002', - 'active' => true - ] - ] + 'active' => true, + ], + ], ]; } diff --git a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php index 5ba5a7950..23b12f02f 100644 --- a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php @@ -1,26 +1,34 @@ table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\PasswordBehavior') - ->setMethods(['sendResetPasswordEmail']) + ->setMethods(['_sendResetPasswordEmail']) ->setConstructorArgs([$this->table]) ->getMock(); - Email::setConfigTransport('test', [ - 'className' => 'Debug' - ]); + TransportFactory::drop('test'); + TransportFactory::setConfig('test', ['className' => 'Debug']); //$this->configEmail = Email::getConfig('default'); - Email::setConfig('default', [ + Mailer::drop('default'); + Mailer::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); } @@ -62,24 +70,23 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->table, $this->Behavior); Email::drop('default'); - Email::dropTransport('test'); + TransportFactory::drop('test'); parent::tearDown(); } /** * Test resetToken - * */ public function testResetToken() { $user = $this->table->findByUsername('user-1')->first(); $token = $user->token; $this->Behavior->expects($this->never()) - ->method('sendResetPasswordEmail') + ->method('_sendResetPasswordEmail') ->with($user); $result = $this->Behavior->resetToken('user-1', [ 'expiration' => 3600, @@ -92,7 +99,6 @@ public function testResetToken() /** * Test resetToken - * */ public function testResetTokenSendEmail() { @@ -100,11 +106,12 @@ public function testResetTokenSendEmail() $token = $user->token; $tokenExpires = $user->token_expires; $this->Behavior->expects($this->once()) - ->method('sendResetPasswordEmail'); + ->method('_sendResetPasswordEmail'); $result = $this->Behavior->resetToken('user-1', [ 'expiration' => 3600, 'checkActive' => true, - 'sendEmail' => true + 'sendEmail' => true, + 'type' => 'password', ]); $this->assertNotEquals($token, $result->token); $this->assertNotEquals($tokenExpires, $result->token_expires); @@ -114,51 +121,47 @@ public function testResetTokenSendEmail() /** * Test resetToken - * - * @expectedException InvalidArgumentException */ public function testResetTokenWithNullParams() { + $this->expectException(\InvalidArgumentException::class); $this->Behavior->resetToken(null); } /** * Test resetTokenNoExpiration - * - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Token expiration cannot be empty */ public function testResetTokenNoExpiration() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Token expiration cannot be empty'); $this->Behavior->resetToken('ref'); } /** * Test resetToken - * - * @expectedException \CakeDC\Users\Exception\UserNotFoundException */ public function testResetTokenNotExistingUser() { + $this->expectException(UserNotFoundException::class); $this->Behavior->resetToken('user-not-found', [ - 'expiration' => 3600 + 'expiration' => 3600, ]); } /** * Test resetToken - * - * @expectedException \CakeDC\Users\Exception\UserAlreadyActiveException */ public function testResetTokenUserAlreadyActive() { + $this->expectException(UserAlreadyActiveException::class); $activeUser = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->findByUsername('user-4')->first(); $this->assertTrue($activeUser->active); $this->table = $this->getMockForModel('CakeDC/Users.Users', ['save']); $this->table->expects($this->never()) ->method('save'); $this->Behavior->expects($this->never()) - ->method('sendResetPasswordEmail'); + ->method('_sendResetPasswordEmail'); $this->Behavior->resetToken('user-4', [ 'expiration' => 3600, 'checkActive' => true, @@ -167,15 +170,14 @@ public function testResetTokenUserAlreadyActive() /** * Test resetToken - * - * @expectedException \CakeDC\Users\Exception\UserNotActiveException */ public function testResetTokenUserNotActive() { - $user = $this->table->findByUsername('user-1')->first(); + $this->expectException(UserNotActiveException::class); + $this->table->findByUsername('user-1')->firstOrFail(); $this->Behavior->resetToken('user-1', [ 'ensureActive' => true, - 'expiration' => 3600 + 'expiration' => 3600, ]); } @@ -187,7 +189,7 @@ public function testResetTokenUserActive() $user = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->findByUsername('user-2')->first(); $result = $this->Behavior->resetToken('user-2', [ 'ensureActive' => true, - 'expiration' => 3600 + 'expiration' => 3600, ]); $this->assertEquals($user->id, $result->id); } @@ -203,6 +205,8 @@ public function testChangePassword() $user->password_confirmation = 'new'; $result = $this->Behavior->changePassword($user); + $this->assertInstanceOf(User::class, $result); + $this->assertEmpty($result->getErrors()); } /** @@ -218,18 +222,21 @@ public function testEmailOverride() ->setConstructorArgs([$this->table]) ->setMethods(['getMailer']) ->getMock(); + $responseEmail = ['headers' => ['A' => 111, 'B' => 33], 'message' => 'My message' . time()]; $overrideMailer->expects($this->once()) ->method('send') ->with('resetPassword') - ->willReturn(true); + ->willReturn($responseEmail); $this->Behavior->expects($this->once()) ->method('getMailer') ->with(OverrideMailer::class) ->willReturn($overrideMailer); - $this->Behavior->resetToken('user-1', [ + $result = $this->Behavior->resetToken('user-1', [ 'expiration' => 3600, 'checkActive' => true, - 'sendEmail' => true + 'sendEmail' => true, + 'type' => 'password', ]); + $this->assertInstanceOf(User::class, $result); } } diff --git a/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php index d1dbcee6e..317cc6a80 100644 --- a/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php @@ -1,56 +1,65 @@ get('CakeDC/Users.Users'); $table->addBehavior('CakeDC/Users/Register.Register'); $this->Table = $table; $this->Behavior = $table->behaviors()->Register; - Email::setConfigTransport('test', [ - 'className' => 'Debug' - ]); + TransportFactory::setConfig('test', ['className' => 'Debug']); Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); } @@ -59,11 +68,11 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Table, $this->Behavior); Email::drop('default'); - Email::dropTransport('test'); + TransportFactory::drop('test'); parent::tearDown(); } @@ -81,9 +90,9 @@ public function testValidateRegisterNoValidateEmail() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); $this->assertTrue($result->active); } @@ -95,7 +104,7 @@ public function testValidateRegisterNoValidateEmail() public function testValidateRegisterEmptyUser() { $user = []; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); $this->assertFalse($result); } @@ -106,6 +115,13 @@ public function testValidateRegisterEmptyUser() */ public function testValidateRegisterValidateEmailAndTos() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $user = [ 'username' => 'testuser', 'email' => 'testuser@test.com', @@ -113,9 +129,9 @@ public function testValidateRegisterValidateEmailAndTos() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); $this->assertNotEmpty($result); $this->assertFalse($result->active); $this->assertNotEmpty($result->tos_date); @@ -128,6 +144,13 @@ public function testValidateRegisterValidateEmailAndTos() */ public function testValidateRegisterValidatorOption() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $this->Table = $this->getMockForModel('CakeDC/Users.Users', ['validationCustom', 'patchEntity', 'errors', 'save']); $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\RegisterBehavior') @@ -142,7 +165,7 @@ public function testValidateRegisterValidatorOption() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; $this->Behavior->expects($this->never()) @@ -156,7 +179,7 @@ public function testValidateRegisterValidatorOption() $this->Table->expects($this->once()) ->method('patchEntity') - ->with($this->Table->newEntity(), $user, ['validate' => 'custom']) + ->with($this->Table->newEmptyEntity(), $user, ['validate' => 'custom']) ->will($this->returnValue($entityUser)); $this->Table->expects($this->once()) @@ -164,13 +187,12 @@ public function testValidateRegisterValidatorOption() ->with($entityUser) ->will($this->returnValue($entityUser)); - $result = $this->Behavior->register($this->Table->newEntity(), $user, ['validator' => 'custom', 'validate_email' => 1]); + $result = $this->Behavior->register($this->Table->newEmptyEntity(), $user, ['validator' => 'custom', 'validate_email' => 1]); $this->assertNotEmpty($result->tos_date); } /** * Test register method - * */ public function testValidateRegisterTosRequired() { @@ -182,7 +204,7 @@ public function testValidateRegisterTosRequired() 'first_name' => 'test', 'last_name' => 'user', ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); $this->assertFalse($result); } @@ -193,6 +215,13 @@ public function testValidateRegisterTosRequired() */ public function testValidateRegisterNoTosRequired() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $user = [ 'username' => 'testuser', 'email' => 'testuser@test.com', @@ -201,7 +230,7 @@ public function testValidateRegisterNoTosRequired() 'first_name' => 'test', 'last_name' => 'user', ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); $this->assertNotEmpty($result); } @@ -233,10 +262,10 @@ public function testValidate() * Test Validate method * * @return void - * @expectedException \CakeDC\Users\Exception\TokenExpiredException */ public function testValidateUserWithExpiredToken() { + $this->expectException(TokenExpiredException::class); $this->Table->validate('token-5', 'activateUser'); } @@ -244,10 +273,10 @@ public function testValidateUserWithExpiredToken() * Test Validate method * * @return void - * @expectedException \CakeDC\Users\Exception\UserNotFoundException */ public function testValidateNotExistingUser() { + $this->expectException(UserNotFoundException::class); $this->Table->validate('not-existing-token', 'activateUser'); } @@ -259,15 +288,14 @@ public function testValidateNotExistingUser() public function testActiveUserRemoveValidationToken() { $user = $this->Table->find()->where(['id' => '00000000-0000-0000-0000-000000000001'])->first(); - $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\RegisterBehavior') - ->setConstructorArgs([$this->Table]) - ->getMock(); - - $resultValidationToken = $user; - $resultValidationToken->token_expires = null; - $resultValidationToken->token = null; - - $this->Behavior->activateUser($user); + $this->Behavior = new RegisterBehavior($this->Table); + + $result = $this->Behavior->activateUser($user); + $this->assertSame($result, $user); + $this->assertNull($user->token_expires); + $this->assertTrue($user->active); + $this->assertInstanceOf(\DateTime::class, $user->activation_date); + $this->assertEquals(date('Y-m-d'), $user->activation_date->format('Y-m-d')); } /** @@ -284,12 +312,12 @@ public function testRegisterUsingDefaultRole() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; Configure::write('Users.Registration.defaultRole', false); - $result = $this->Table->register($this->Table->newEntity(), $user, [ + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, [ 'token_expiration' => 3600, - 'validate_email' => 0 + 'validate_email' => 0, ]); $this->assertSame('user', $result['role']); } @@ -308,61 +336,13 @@ public function testRegisterUsingCustomRole() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; Configure::write('Users.Registration.defaultRole', 'emperor'); - $result = $this->Table->register($this->Table->newEntity(), $user, [ + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, [ 'token_expiration' => 3600, 'validate_email' => 0, ]); $this->assertSame('emperor', $result['role']); } - - /** - * Test resendValidationEmail method - * - * @return void - */ - public function testResendValidationEmail() - { - $user = [ - 'username' => 'testuser', - 'email' => 'testuser@test.com', - 'password' => 'password', - 'password_confirm' => 'password', - 'first_name' => 'test', - 'last_name' => 'user', - 'tos' => 1 - ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); - $this->assertFalse($result->active); - $originalExpiration = $result->token_expires; - $updatedResult = $this->Table->resendValidationEmail($result, ['token_expiration' => 4000]); - $this->assertNotEmpty($updatedResult); - $this->assertFalse($updatedResult->active); - $newExpiration = $updatedResult->token_expires; - $this->assertNotEquals($originalExpiration, $newExpiration); - } - - /** - * Test resendValidationEmail method throw exception on active user - * - * @return void - */ - public function testResendValidationEmailThrows() - { - $user = [ - 'username' => 'testuser', - 'email' => 'testuser@test.com', - 'password' => 'password', - 'password_confirm' => 'password', - 'first_name' => 'test', - 'last_name' => 'user', - 'tos' => 1 - ]; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); - $activeUser = $this->Table->activateUser($result); - $this->expectException(UserAlreadyActiveException::class); - $updatedResult = $this->Table->resendValidationEmail($activeUser, ['token_expiration' => 4000]); - } } diff --git a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php index b9149c516..5e35cf818 100644 --- a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php @@ -1,23 +1,24 @@ Table = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts'); @@ -51,7 +52,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Table, $this->Behavior, $this->Email); parent::tearDown(); @@ -72,31 +73,28 @@ public function testValidateEmail() /** * Test validateEmail method - * - * @expectedException \Cake\Datasource\Exception\RecordNotFoundException */ public function testValidateEmailInvalidToken() { + $this->expectException(RecordNotFoundException::class); $this->Behavior->validateAccount(1, 'reference-1234', 'invalid-token'); } /** * Test validateEmail method - * - * @expectedException \Cake\Datasource\Exception\RecordNotFoundException */ public function testValidateEmailInvalidUser() { + $this->expectException(RecordNotFoundException::class); $this->Behavior->validateAccount(1, 'invalid-user', 'token-1234'); } /** * Test validateEmail method - * - * @expectedException CakeDC\Users\Exception\AccountAlreadyActiveException */ public function testValidateEmailActiveAccount() { + $this->expectException(AccountAlreadyActiveException::class); $this->Behavior->validateAccount(SocialAccountsTable::PROVIDER_TWITTER, 'reference-1-1234', 'token-1234'); } @@ -110,7 +108,7 @@ public function testAfterSaveSocialNotActiveUserNotActive() { $event = new Event('eventName'); $entity = $this->Table->find()->first(); - $this->assertTrue($this->Behavior->afterSave($event, $entity, [])); + $this->assertTrue($this->Behavior->afterSave($event, $entity, new \ArrayObject([]))); } /** @@ -123,7 +121,7 @@ public function testAfterSaveSocialActiveUserActive() { $event = new Event('eventName'); $entity = $this->Table->findById('00000000-0000-0000-0000-000000000003')->first(); - $this->assertTrue($this->Behavior->afterSave($event, $entity, [])); + $this->assertTrue($this->Behavior->afterSave($event, $entity, new \ArrayObject([]))); } /** @@ -136,6 +134,6 @@ public function testAfterSaveSocialActiveUserNotActive() { $event = new Event('eventName'); $entity = $this->Table->findById('00000000-0000-0000-0000-000000000002')->first(); - $this->assertTrue($this->Behavior->afterSave($event, $entity, [])); + $this->assertTrue($this->Behavior->afterSave($event, $entity, new \ArrayObject([]))); } } diff --git a/tests/TestCase/Model/Behavior/SocialBehaviorTest.php b/tests/TestCase/Model/Behavior/SocialBehaviorTest.php index 0582eac9b..684f6c4db 100644 --- a/tests/TestCase/Model/Behavior/SocialBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SocialBehaviorTest.php @@ -1,17 +1,22 @@ Table = $this->getMockForModel('CakeDC/Users.Users', ['save']); @@ -48,7 +53,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Table, $this->Behavior, $this->Email); parent::tearDown(); @@ -121,7 +126,6 @@ public function testSocialLoginFacebookProviderUsingEmail($data, $options, $data /** * Provider for socialLogin with facebook and not existing user - * */ public function providerFacebookSocialLogin() { @@ -142,23 +146,23 @@ public function providerFacebookSocialLogin() 'email' => 'email@example.com', 'picture' => [ 'data' => [ - 'url' => 'data-url' - ] - ] + 'url' => 'data-url', + ], + ], ], 'credentials' => [ 'token' => 'token', 'secret' => null, - 'expires' => 1458423682 + 'expires' => 1458423682, ], 'validated' => true, 'link' => 'facebook-link', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'options' => [ 'use_email' => true, 'validate_email' => true, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ], 'result' => [ 'first_name' => 'First name', @@ -181,13 +185,13 @@ public function providerFacebookSocialLogin() 'token_secret' => null, 'token_expires' => '2016-03-19 21:41:22', 'data' => '-', - 'active' => true - ] + 'active' => true, + ], ], 'activation_date' => '2016-01-20 15:45:09', 'active' => true, - ] - ] + ], + ], ]; } @@ -197,7 +201,7 @@ public function providerFacebookSocialLogin() * * @dataProvider providerFacebookSocialLoginExistingReference */ - public function testSocialLoginExistingReference($data, $options) + public function testSocialLoginExistingReferenceOkay($data, $options) { $this->Behavior->expects($this->never()) ->method('generateUniqueUsername'); @@ -208,14 +212,48 @@ public function testSocialLoginExistingReference($data, $options) $this->Behavior->expects($this->never()) ->method('_updateActive'); - $result = $this->Behavior->socialLogin($data, $options); + $fullData = $data + [ + 'credentials' => [ + 'token' => 'aT0ken' . time(), + 'secret' => 'AS3crEt' . time(), + 'expires' => 1458423682, + ], + 'avatar' => 'http://localhost/avatar.jpg' . time(), + 'link' => 'facebook-link' . time(), + 'bio' => 'This is a sample bio' . time(), + 'raw' => [ + 'bio' => 'This is a raw bio', + 'extra' => 'value', + 'foo' => 'bar', + ], + ]; + $accountBefore = $this->Table->SocialAccounts->find()->where([ + 'SocialAccounts.reference' => $data['id'], + 'SocialAccounts.provider' => $data['provider'], + ])->firstOrFail(); + $result = $this->Behavior->socialLogin($fullData + [], $options); $this->assertEquals($result->id, '00000000-0000-0000-0000-000000000002'); $this->assertTrue($result->active); + + $account = $this->Table->SocialAccounts->find()->where([ + 'SocialAccounts.reference' => $data['id'], + 'SocialAccounts.provider' => $data['provider'], + ])->firstOrFail(); + + $this->assertEquals($fullData['avatar'], $account->avatar); + $this->assertEquals($fullData['link'], $account->link); + $this->assertEquals($fullData['bio'], $account->description); + $this->assertEquals($fullData['raw'], unserialize($account->data)); + $this->assertEquals($fullData['credentials']['token'], $account->token); + $this->assertEquals($fullData['credentials']['secret'], $account->token_secret); + $this->assertNotEmpty($account->token_expires); + $this->assertNotEquals($accountBefore->token_expires, $account->token_expires); + $this->assertSame($accountBefore->id, $account->id); + $this->assertSame($accountBefore->active, $account->active); } /** * Provider for socialLogin with facebook with existing and active user - * */ public function providerFacebookSocialLoginExistingReference() { @@ -223,14 +261,14 @@ public function providerFacebookSocialLoginExistingReference() 'provider' => [ 'data' => [ 'id' => 'reference-2-1', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'options' => [ 'use_email' => true, 'validate_email' => true, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ], - ] + ], ]; } @@ -238,11 +276,11 @@ public function providerFacebookSocialLoginExistingReference() /** * Test socialLogin with existing and active user and not active social account * - * @expectedException CakeDC\Users\Exception\AccountNotActiveException * @dataProvider providerSocialLoginExistingAndNotActiveAccount */ public function testSocialLoginExistingNotActiveReference($data, $options) { + $this->expectException(AccountNotActiveException::class); $this->Behavior->expects($this->never()) ->method('generateUniqueUsername'); @@ -256,7 +294,6 @@ public function testSocialLoginExistingNotActiveReference($data, $options) /** * Provider for socialLogin with existing and active user and not active social account - * */ public function providerSocialLoginExistingAndNotActiveAccount() { @@ -264,14 +301,14 @@ public function providerSocialLoginExistingAndNotActiveAccount() 'provider' => [ 'data' => [ 'id' => 'reference-1-1234', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'options' => [ 'use_email' => true, 'validate_email' => true, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ], - ] + ], ]; } @@ -279,11 +316,11 @@ public function providerSocialLoginExistingAndNotActiveAccount() /** * Test socialLogin with existing and active account but not active user * - * @expectedException CakeDC\Users\Exception\UserNotActiveException * @dataProvider providerSocialLoginExistingAccountNotActiveUser */ public function testSocialLoginExistingReferenceNotActiveUser($data, $options) { + $this->expectException(UserNotActiveException::class); $this->Behavior->expects($this->never()) ->method('generateUniqueUsername'); @@ -297,7 +334,6 @@ public function testSocialLoginExistingReferenceNotActiveUser($data, $options) /** * Provider for socialLogin with existing and active account but not active user - * */ public function providerSocialLoginExistingAccountNotActiveUser() { @@ -305,14 +341,14 @@ public function providerSocialLoginExistingAccountNotActiveUser() 'provider' => [ 'data' => [ 'id' => 'reference-1-1234', - 'provider' => 'Twitter' + 'provider' => 'Twitter', ], 'options' => [ 'use_email' => true, 'validate_email' => true, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ], - ] + ], ]; } @@ -321,16 +357,15 @@ public function providerSocialLoginExistingAccountNotActiveUser() * Test socialLogin with facebook and not existing user * * @dataProvider providerFacebookSocialLoginNoEmail - * @expectedException CakeDC\Users\Exception\MissingEmailException */ public function testSocialLoginNoEmail($data, $options) { + $this->expectException(MissingEmailException::class); $this->Behavior->socialLogin($data, $options); } /** * Provider for socialLogin with facebook and not existing user - * */ public function providerFacebookSocialLoginNoEmail() { @@ -344,14 +379,14 @@ public function providerFacebookSocialLoginNoEmail() 'last_name' => 'Last name', 'validated' => true, 'link' => 'facebook-link', - 'provider' => 'Facebook' + 'provider' => 'Facebook', ], 'options' => [ 'use_email' => true, 'validate_email' => true, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ], - ] + ], ]; } @@ -374,14 +409,13 @@ public function testGenerateUniqueUsername($param, $expected) /** * Provider for socialLogin with facebook and not existing user - * */ public function providerGenerateUsername() { return [ ['username', 'username'], ['user-1', 'user-10'], - ['user-5', 'user-50'] + ['user-5', 'user-50'], ]; } diff --git a/tests/TestCase/Model/Entity/UserTest.php b/tests/TestCase/Model/Entity/UserTest.php index 03165a769..4f0981f71 100644 --- a/tests/TestCase/Model/Entity/UserTest.php +++ b/tests/TestCase/Model/Entity/UserTest.php @@ -1,21 +1,23 @@ now = Time::now(); - Time::setTestNow($this->now); + $this->now = FrozenTime::now(); + FrozenTime::setTestNow($this->now); $this->User = new User(); } @@ -40,10 +42,10 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->User); - Time::setTestNow(); + FrozenTime::setTestNow(); parent::tearDown(); } @@ -110,14 +112,14 @@ public function testPasswordsAreEncrypted() { $pw = 'password'; $this->User->password = $pw; - $this->assertTrue((new DefaultPasswordHasher)->check($pw, $this->User->password)); + $this->assertTrue((new DefaultPasswordHasher())->check($pw, $this->User->password)); } public function testConfirmPasswordsAreEncrypted() { $pw = 'password'; $this->User->confirm_password = $pw; - $this->assertTrue((new DefaultPasswordHasher)->check($pw, $this->User->confirm_password)); + $this->assertTrue((new DefaultPasswordHasher())->check($pw, $this->User->confirm_password)); } /** @@ -128,7 +130,7 @@ public function testConfirmPasswordsAreEncrypted() public function testCheckPassword() { $pw = 'password'; - $this->assertTrue($this->User->checkPassword($pw, (new DefaultPasswordHasher)->hash($pw))); + $this->assertTrue($this->User->checkPassword($pw, (new DefaultPasswordHasher())->hash($pw))); $this->assertFalse($this->User->checkPassword($pw, 'fail')); } @@ -143,7 +145,7 @@ public function testGetAvatar() $avatar = 'first-avatar'; $this->User->social_accounts = [ ['avatar' => 'first-avatar'], - ['avatar' => 'second-avatar'] + ['avatar' => 'second-avatar'], ]; $this->assertSame($avatar, $this->User->avatar); } diff --git a/tests/TestCase/Model/Table/SocialAccountsTableTest.php b/tests/TestCase/Model/Table/SocialAccountsTableTest.php index f662d7bf2..04999b7f5 100644 --- a/tests/TestCase/Model/Table/SocialAccountsTableTest.php +++ b/tests/TestCase/Model/Table/SocialAccountsTableTest.php @@ -1,22 +1,19 @@ SocialAccounts = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts'); @@ -51,7 +47,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->SocialAccounts); diff --git a/tests/TestCase/Model/Table/UsersTableTest.php b/tests/TestCase/Model/Table/UsersTableTest.php index dcad61b07..58b1e7fd6 100644 --- a/tests/TestCase/Model/Table/UsersTableTest.php +++ b/tests/TestCase/Model/Table/UsersTableTest.php @@ -1,27 +1,25 @@ Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $this->fullBaseBackup = Router::fullBaseUrl(); Router::fullBaseUrl('http://users.test'); - Email::setConfigTransport('test', [ - 'className' => 'Debug' - ]); + TransportFactory::drop('test'); + TransportFactory::setConfig('test', ['className' => 'Debug']); Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); - Plugin::routes('CakeDC/Users'); } /** @@ -64,12 +60,11 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Users); Router::fullBaseUrl($this->fullBaseBackup); Email::drop('default'); - Email::dropTransport('test'); parent::tearDown(); } @@ -88,9 +83,9 @@ public function testValidateRegisterNoValidateEmail() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; - $result = $this->Users->register($this->Users->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); $this->assertTrue($result->active); } @@ -102,7 +97,7 @@ public function testValidateRegisterNoValidateEmail() public function testValidateRegisterEmptyUser() { $user = []; - $result = $this->Users->register($this->Users->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); $this->assertFalse($result); } @@ -113,6 +108,13 @@ public function testValidateRegisterEmptyUser() */ public function testValidateRegisterValidateEmail() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $user = [ 'username' => 'testuser', 'email' => 'testuser@test.com', @@ -120,9 +122,9 @@ public function testValidateRegisterValidateEmail() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; - $result = $this->Users->register($this->Users->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); $this->assertNotEmpty($result); $this->assertFalse($result->active); } @@ -140,7 +142,7 @@ public function testValidateRegisterTosRequired() 'first_name' => 'test', 'last_name' => 'user', ]; - $userEntity = $this->Users->newEntity(); + $userEntity = $this->Users->newEmptyEntity(); $this->Users->register($userEntity, $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); $this->assertEquals(['tos' => ['_required' => 'This field is required']], $userEntity->getErrors()); } @@ -151,6 +153,13 @@ public function testValidateRegisterTosRequired() */ public function testValidateRegisterNoTosRequired() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + $user = [ 'username' => 'testuser', 'email' => 'testuser@test.com', @@ -159,7 +168,7 @@ public function testValidateRegisterNoTosRequired() 'first_name' => 'test', 'last_name' => 'user', ]; - $result = $this->Users->register($this->Users->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); $this->assertNotEmpty($result); } @@ -189,13 +198,13 @@ public function testSocialLogin() 'gender' => 'female', 'verified' => 1, 'user_email' => 'user-2@test.com', - 'link' => 'link' - ] + 'link' => 'link', + ], ]; $options = [ 'use_email' => 1, 'validate_email' => 1, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ]; $result = $this->Users->socialLogin($data, $options); $this->assertEquals('user-2@test.com', $result->email); @@ -204,11 +213,10 @@ public function testSocialLogin() /** * Test socialLogin - * - * @expectedException CakeDC\Users\Exception\AccountNotActiveException */ public function testSocialLoginInactiveAccount() { + $this->expectException(AccountNotActiveException::class); $data = [ 'provider' => SocialAccountsTable::PROVIDER_TWITTER, 'email' => 'hello@test.com', @@ -220,12 +228,12 @@ public function testSocialLoginInactiveAccount() 'gender' => 'female', 'verified' => 1, 'user_email' => 'hello@test.com', - ] + ], ]; $options = [ 'use_email' => 1, 'validate_email' => 1, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ]; $result = $this->Users->socialLogin($data, $options); $this->assertEquals('user-2@test.com', $result->email); @@ -234,11 +242,10 @@ public function testSocialLoginInactiveAccount() /** * Test socialLogin - * - * @expectedException InvalidArgumentException */ public function testSocialLoginCreateNewAccountWithNoCredentials() { + $this->expectException(\InvalidArgumentException::class); $data = [ 'provider' => SocialAccountsTable::PROVIDER_TWITTER, 'email' => 'user@test.com', @@ -257,7 +264,7 @@ public function testSocialLoginCreateNewAccountWithNoCredentials() $options = [ 'use_email' => 0, 'validate_email' => 1, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ]; $result = $this->Users->socialLogin($data, $options); $this->assertFalse($result); @@ -265,7 +272,6 @@ public function testSocialLoginCreateNewAccountWithNoCredentials() /** * Test socialLogin - * */ public function testSocialLoginCreateNewAccount() { @@ -282,25 +288,25 @@ public function testSocialLoginCreateNewAccount() 'last_name' => 'Last Name', 'gender' => 'male', 'user_email' => 'user@test.com', - 'twitter' => 'link' + 'twitter' => 'link', ], 'info' => [ 'first_name' => 'First Name', 'last_name' => 'Last Name', - 'urls' => ['twitter' => 'twitter'] + 'urls' => ['twitter' => 'twitter'], ], 'validated' => true, 'credentials' => [ 'token' => 'token', 'token_secret' => 'secret', - 'token_expires' => '' + 'token_expires' => '', ], ]; $options = [ 'use_email' => 0, 'validate_email' => 0, - 'token_expiration' => 3600 + 'token_expiration' => 3600, ]; $result = $this->Users->socialLogin($data, $options); $this->assertNotEmpty($result); diff --git a/tests/TestCase/PluginTest.php b/tests/TestCase/PluginTest.php new file mode 100644 index 000000000..d4fedf44f --- /dev/null +++ b/tests/TestCase/PluginTest.php @@ -0,0 +1,267 @@ +middleware($middleware); + + // next two is DoublePassDecoratorMiddleware as they not implements MiddlewareInterface + $middleware->seek(0); + $this->assertInstanceOf(SocialAuthMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(SocialEmailMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(3); + $this->assertInstanceOf(TwoFactorMiddleware::class, $middleware->current()); + $middleware->seek(4); + $this->assertInstanceOf(AuthorizationMiddleware::class, $middleware->current()); + $middleware->seek(5); + $this->assertInstanceOf(RequestAuthorizationMiddleware::class, $middleware->current()); + $this->assertEquals(6, $middleware->count()); + } + + /** + * testMiddleware + * + * @return void + */ + public function testMiddlewareAuthorizationMiddlewareAndRbacMiddleware() + { + Configure::write('Users.Social.login', true); + Configure::write('OneTimePasswordAuthenticator.login', true); + Configure::write('Auth.Authorization.enable', true); + + $plugin = new Plugin(); + + $middleware = new MiddlewareQueue(); + + $middleware = $plugin->middleware($middleware); + $middleware->seek(0); + $this->assertInstanceOf(SocialAuthMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(SocialEmailMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(3); + $this->assertInstanceOf(TwoFactorMiddleware::class, $middleware->current()); + $middleware->seek(4); + $this->assertInstanceOf(AuthorizationMiddleware::class, $middleware->current()); + $middleware->seek(5); + $this->assertInstanceOf(RequestAuthorizationMiddleware::class, $middleware->current()); + $this->assertEquals(6, $middleware->count()); + } + + /** + * testMiddleware without authorization + * + * @return void + */ + public function testMiddlewareWithoutAuhorization() + { + Configure::write('Users.Social.login', true); + Configure::write('OneTimePasswordAuthenticator.login', true); + Configure::write('Auth.Authorization.enable', false); + + $plugin = new Plugin(); + + $middleware = new MiddlewareQueue(); + + $middleware = $plugin->middleware($middleware); + $middleware->seek(0); + $this->assertInstanceOf(SocialAuthMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(SocialEmailMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(3); + $this->assertInstanceOf(TwoFactorMiddleware::class, $middleware->current()); + $this->assertEquals(4, $middleware->count()); + } + + /** + * testMiddleware + * + * @return void + */ + public function testMiddlewareNotSocial() + { + Configure::write('Users.Social.login', false); + Configure::write('OneTimePasswordAuthenticator.login', true); + Configure::write('Auth.Authorization.enable', true); + $plugin = new Plugin(); + + $middleware = new MiddlewareQueue(); + + $middleware = $plugin->middleware($middleware); + $middleware->seek(0); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(TwoFactorMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(AuthorizationMiddleware::class, $middleware->current()); + $middleware->seek(3); + $this->assertInstanceOf(RequestAuthorizationMiddleware::class, $middleware->current()); + } + + /** + * testMiddleware + * + * @return void + */ + public function testMiddlewareNotOneTimePasswordAuthenticator() + { + Configure::write('Users.Social.login', true); + Configure::write('OneTimePasswordAuthenticator.login', false); + Configure::write('Auth.Authorization.enable', true); + $plugin = new Plugin(); + + $middleware = new MiddlewareQueue(); + + $middleware = $plugin->middleware($middleware); + $middleware->seek(0); + $this->assertInstanceOf(SocialAuthMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(SocialEmailMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(3); + $this->assertInstanceOf(AuthorizationMiddleware::class, $middleware->current()); + $middleware->seek(4); + $this->assertInstanceOf(RequestAuthorizationMiddleware::class, $middleware->current()); + } + + /** + * testMiddleware + * + * @return void + */ + public function testMiddlewareNotGoogleAuthenticationAndNotSocial() + { + Configure::write('Users.Social.login', false); + Configure::write('OneTimePasswordAuthenticator.login', false); + Configure::write('Auth.Authorization.enable', true); + $plugin = new Plugin(); + + $middleware = new MiddlewareQueue(); + + $middleware = $plugin->middleware($middleware); + $middleware->seek(0); + $this->assertInstanceOf(AuthenticationMiddleware::class, $middleware->current()); + $middleware->seek(1); + $this->assertInstanceOf(AuthorizationMiddleware::class, $middleware->current()); + $middleware->seek(2); + $this->assertInstanceOf(RequestAuthorizationMiddleware::class, $middleware->current()); + } + + /** + * test bootstrap method + * + * @param string $urlConfigKey The url config key. + * @param array $expectedUrl The expected url value for $urlConfigKey. + * @dataProvider dataProviderConfigUsersUrls + * @return void + */ + public function testBootstrap($urlConfigKey, $expectedUrl) + { + $actual = Configure::read($urlConfigKey); + $this->assertSame($expectedUrl, $actual); + } + + /** + * Data provider for users urls + * + * @return array + */ + public function dataProviderConfigUsersUrls() + { + $defaultVerifyAction = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'verify', + ]; + $defaultProfileAction = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]; + $defaultU2fStartAction = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'u2f', + ]; + $defaultLoginAction = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'login', + ]; + $defaultOauthPath = [ + 'prefix' => null, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'socialLogin', + ]; + + return [ + ['Users.Profile.route', $defaultProfileAction], + ['OneTimePasswordAuthenticator.verifyAction', $defaultVerifyAction], + ['U2f.startAction', $defaultU2fStartAction], + ['Auth.AuthenticationComponent.loginAction', $defaultLoginAction], + ['Auth.AuthenticationComponent.logoutRedirect', $defaultLoginAction], + ['Auth.AuthenticationComponent.loginRedirect', '/'], + ['Auth.Authenticators.Form.loginUrl', $defaultLoginAction], + ['Auth.Authenticators.Cookie.loginUrl', $defaultLoginAction], + ['Auth.Authenticators.SocialPendingEmail.loginUrl', $defaultLoginAction], + ['Auth.AuthorizationMiddleware.unauthorizedHandler.url', $defaultLoginAction], + ['OAuth.path', $defaultOauthPath], + ]; + } +} diff --git a/tests/TestCase/Provider/AuthenticationServiceProviderTest.php b/tests/TestCase/Provider/AuthenticationServiceProviderTest.php new file mode 100644 index 000000000..710402173 --- /dev/null +++ b/tests/TestCase/Provider/AuthenticationServiceProviderTest.php @@ -0,0 +1,249 @@ + [ + 'className' => 'Authentication.Session', + 'skipTwoFactorVerify' => true, + 'sessionKey' => 'CustomAuth', + 'fields' => ['username' => 'email'], + 'identify' => true, + ], + 'Form' => [ + 'className' => 'CakeDC/Auth.Form', + 'loginUrl' => '/login', + 'fields' => ['username' => 'email', 'password' => 'alt_password'], + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'skipTwoFactorVerify' => true, + 'header' => null, + 'queryParam' => 'api_key', + 'tokenPrefix' => null, + ], + ]); + Configure::write('Auth.Identifiers', [ + 'Password' => [ + 'className' => 'Authentication.Password', + 'fields' => [ + 'username' => 'email_2', + 'password' => 'password_2', + ], + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'tokenField' => 'api_token', + ], + 'Authentication.JwtSubject', + ]); + Configure::write('OneTimePasswordAuthenticator.login', true); + + $authenticationServiceProvider = new AuthenticationServiceProvider(); + $service = $authenticationServiceProvider->getAuthenticationService(new ServerRequest(), new Response()); + $this->assertInstanceOf(CakeDCAuthenticationService::class, $service); + + /** + * @var \Authentication\Authenticator\AuthenticatorCollection $authenticators + */ + $authenticators = $service->authenticators(); + $expected = [ + SessionAuthenticator::class => [ + 'fields' => ['username' => 'email'], + 'sessionKey' => 'CustomAuth', + 'identify' => true, + 'identityAttribute' => 'identity', + 'skipTwoFactorVerify' => true, + ], + FormAuthenticator::class => [ + 'loginUrl' => '/login', + 'keyCheckEnabledRecaptcha' => 'Users.reCaptcha.login', + 'fields' => ['username' => 'email', 'password' => 'alt_password'], + ], + TokenAuthenticator::class => [ + 'header' => null, + 'queryParam' => 'api_key', + 'tokenPrefix' => null, + 'skipTwoFactorVerify' => true, + ], + TwoFactorAuthenticator::class => [ + 'loginUrl' => null, + 'urlChecker' => 'Authentication.Default', + 'skipTwoFactorVerify' => true, + ], + ]; + $actual = []; + foreach ($authenticators as $key => $value) { + $actual[get_class($value)] = $value->getConfig(); + } + $this->assertEquals($expected, $actual); + + /** + * @var \Authentication\Identifier\IdentifierCollection $identifiers + */ + $identifiers = $service->identifiers(); + $expected = [ + PasswordIdentifier::class => [ + 'fields' => [ + 'username' => 'email_2', + 'password' => 'password_2', + ], + 'resolver' => 'Authentication.Orm', + 'passwordHasher' => null, + ], + TokenIdentifier::class => [ + 'tokenField' => 'api_token', + 'dataField' => 'token', + 'resolver' => 'Authentication.Orm', + ], + JwtSubjectIdentifier::class => [ + 'tokenField' => 'id', + 'dataField' => 'sub', + 'resolver' => 'Authentication.Orm', + ], + ]; + $actual = []; + foreach ($identifiers as $key => $value) { + $actual[get_class($value)] = $value->getConfig(); + } + $this->assertEquals($expected, $actual); + } + + /** + * testGetAuthenticationService + * + * @return void + */ + public function testGetAuthenticationServiceCallableDefined() + { + $request = ServerRequestFactory::fromGlobals(); + $request->withQueryParams(['method' => __METHOD__]); + $service = new CakeDCAuthenticationService([ + 'identifiers' => [ + 'Authentication.Password', + ], + ]); + Configure::write('Auth.Authentication.serviceLoader', function ($aRequest) use ($request, $service) { + $this->assertSame($request, $aRequest); + + return $service; + }); + + $authenticationServiceProvider = new AuthenticationServiceProvider(); + $actualService = $authenticationServiceProvider->getAuthenticationService($request); + $this->assertSame($service, $actualService); + } + + /** + * testGetAuthenticationService + * + * @return void + */ + public function testGetAuthenticationServiceWithoutOneTimePasswordAuthenticator() + { + Configure::write('Auth.Authenticators', [ + 'Session' => [ + 'className' => 'Authentication.Session', + 'skipTwoFactorVerify' => true, + 'sessionKey' => 'CustomAuth', + 'fields' => ['username' => 'email'], + 'identify' => true, + ], + 'Form' => [ + 'className' => 'CakeDC/Auth.Form', + 'loginUrl' => '/login', + 'fields' => ['username' => 'email', 'password' => 'alt_password'], + ], + 'Token' => [ + 'className' => 'Authentication.Token', + 'skipTwoFactorVerify' => true, + 'header' => null, + 'queryParam' => 'api_key', + 'tokenPrefix' => null, + ], + ]); + Configure::write('Auth.Identifiers', [ + 'Authentication.Password', + 'Token' => [ + 'className' => 'Authentication.Token', + 'tokenField' => 'api_token', + ], + 'Authentication.JwtSubject', + ]); + Configure::write('OneTimePasswordAuthenticator.login', false); + + $authenticationServiceProvider = new AuthenticationServiceProvider(); + $service = $authenticationServiceProvider->getAuthenticationService(new ServerRequest(), new Response()); + $this->assertInstanceOf(CakeDCAuthenticationService::class, $service); + + /** + * @var \Authentication\Authenticator\AuthenticatorCollection $authenticators + */ + $authenticators = $service->authenticators(); + $expected = [ + SessionAuthenticator::class => [ + 'fields' => ['username' => 'email'], + 'sessionKey' => 'CustomAuth', + 'identify' => true, + 'identityAttribute' => 'identity', + 'skipTwoFactorVerify' => true, + ], + FormAuthenticator::class => [ + 'loginUrl' => '/login', + 'fields' => ['username' => 'email', 'password' => 'alt_password'], + 'keyCheckEnabledRecaptcha' => 'Users.reCaptcha.login', + ], + TokenAuthenticator::class => [ + 'header' => null, + 'queryParam' => 'api_key', + 'tokenPrefix' => null, + 'skipTwoFactorVerify' => true, + ], + ]; + $actual = []; + foreach ($authenticators as $key => $value) { + $actual[get_class($value)] = $value->getConfig(); + } + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/TestCase/Provider/AuthorizationServiceProviderTest.php b/tests/TestCase/Provider/AuthorizationServiceProviderTest.php new file mode 100644 index 000000000..d8ffb30e5 --- /dev/null +++ b/tests/TestCase/Provider/AuthorizationServiceProviderTest.php @@ -0,0 +1,63 @@ +getAuthorizationService(new ServerRequest()); + $this->assertInstanceOf(AuthorizationService::class, $service); + } + + /** + * testGetAuthorizationService + * + * @return void + */ + public function testGetAuthorizationServiceCallableDefined() + { + $request = ServerRequestFactory::fromGlobals(); + $request->withQueryParams(['method' => __METHOD__]); + $service = new AuthorizationService(new ResolverCollection()); + Configure::write('Auth.Authorization.serviceLoader', function ($aRequest) use ($request, $service) { + $this->assertSame($request, $aRequest); + + return $service; + }); + + $authorizationServiceProvider = new AuthorizationServiceProvider(); + $actualService = $authorizationServiceProvider->getAuthorizationService($request); + $this->assertSame($service, $actualService); + } +} diff --git a/tests/TestCase/Shell/UsersShellTest.php b/tests/TestCase/Shell/UsersShellTest.php index 05bd3e58f..2ce3ea7c0 100644 --- a/tests/TestCase/Shell/UsersShellTest.php +++ b/tests/TestCase/Shell/UsersShellTest.php @@ -1,21 +1,24 @@ out = new ConsoleOutput(); @@ -62,7 +65,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); unset($this->Shell); @@ -272,12 +275,11 @@ public function testResetAllPasswords() /** * Reset all passwords - * - * @expectedException \Cake\Console\Exception\StopException - * @expectedExceptionMessage Please enter a password. */ public function testResetAllPasswordsNoPassingParams() { + $this->expectException(StopException::class); + $this->expectExceptionMessage('Please enter a password.'); $this->Shell->runCommand(['resetAllPasswords']); } @@ -288,7 +290,7 @@ public function testResetAllPasswordsNoPassingParams() */ public function testResetPassword() { - $user = $this->Users->newEntity(); + $user = $this->Users->newEmptyEntity(); $user->username = 'user-1'; $user->password = 'password'; @@ -368,7 +370,7 @@ public function testAddUserCustomRole() '--username=custom', '--password=12345678', '--email=custom@example.com', - '--role=custom' + '--role=custom', ]); $user = $this->Users->findByUsername('custom')->first(); $this->assertSame('custom', $user['role']); diff --git a/tests/TestCase/Traits/RandomStringTraitTest.php b/tests/TestCase/Traits/RandomStringTraitTest.php index f1411a44a..e3d9397ab 100644 --- a/tests/TestCase/Traits/RandomStringTraitTest.php +++ b/tests/TestCase/Traits/RandomStringTraitTest.php @@ -1,11 +1,13 @@ Trait = $this->getMockForTrait('CakeDC\Users\Traits\RandomStringTrait'); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); } diff --git a/tests/TestCase/Utility/UsersUrlTest.php b/tests/TestCase/Utility/UsersUrlTest.php new file mode 100644 index 000000000..42db22eee --- /dev/null +++ b/tests/TestCase/Utility/UsersUrlTest.php @@ -0,0 +1,389 @@ + 'CakeDC/Users', 'controller' => 'Users', 'action' => 'verify'], null], + ['linkSocial', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'linkSocial'], null], + ['callbackLinkSocial', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'callbackLinkSocial'], null], + ['socialLogin', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin'], null], + ['login', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login'], null], + ['logout', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout'], null], + ['getUsersTable', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'getUsersTable'], null], + ['setUsersTable', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'setUsersTable'], null], + ['profile', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], null], + ['validateReCaptcha', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateReCaptcha'], null], + ['register', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'register'], null], + ['validateEmail', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateEmail'], null], + ['changePassword', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'changePassword'], null], + ['resetPassword', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetPassword'], null], + ['requestResetPassword', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'requestResetPassword'], null], + ['resetOneTimePasswordAuthenticator', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], null], + ['validate', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validate'], null], + ['resendTokenValidation', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resendTokenValidation'], null], + ['index', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'index'], null], + ['view', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'view'], null], + ['add', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'add'], null], + ['edit', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'edit'], null], + ['delete', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'delete'], null], + ['socialEmail', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialEmail'], null], + + ['verify', ['controller' => 'Users', 'action' => 'verify'], 'Users'], + ['linkSocial', ['controller' => 'Users', 'action' => 'linkSocial'], 'Users'], + ['callbackLinkSocial', ['controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Users'], + ['socialLogin', ['controller' => 'Users', 'action' => 'socialLogin'], 'Users'], + ['login', ['controller' => 'Users', 'action' => 'login'], 'Users'], + ['logout', ['controller' => 'Users', 'action' => 'logout'], 'Users'], + ['getUsersTable', ['controller' => 'Users', 'action' => 'getUsersTable'], 'Users'], + ['setUsersTable', ['controller' => 'Users', 'action' => 'setUsersTable'], 'Users'], + ['profile', ['controller' => 'Users', 'action' => 'profile'], 'Users'], + ['validateReCaptcha', ['controller' => 'Users', 'action' => 'validateReCaptcha'], 'Users'], + ['register', ['controller' => 'Users', 'action' => 'register'], 'Users'], + ['validateEmail', ['controller' => 'Users', 'action' => 'validateEmail'], 'Users'], + ['changePassword', ['controller' => 'Users', 'action' => 'changePassword'], 'Users'], + ['resetPassword', ['controller' => 'Users', 'action' => 'resetPassword'], 'Users'], + ['requestResetPassword', ['controller' => 'Users', 'action' => 'requestResetPassword'], 'Users'], + ['resetOneTimePasswordAuthenticator', ['controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Users'], + ['validate', ['controller' => 'Users', 'action' => 'validate'], 'Users'], + ['resendTokenValidation', ['controller' => 'Users', 'action' => 'resendTokenValidation'], 'Users'], + ['index', ['controller' => 'Users', 'action' => 'index'], 'Users'], + ['view', ['controller' => 'Users', 'action' => 'view'], 'Users'], + ['add', ['controller' => 'Users', 'action' => 'add'], 'Users'], + ['edit', ['controller' => 'Users', 'action' => 'edit'], 'Users'], + ['delete', ['controller' => 'Users', 'action' => 'delete'], 'Users'], + ['socialEmail', ['controller' => 'Users', 'action' => 'socialEmail'], 'Users'], + + ['verify', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'verify'], 'Admin/Users'], + ['linkSocial', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'linkSocial'], 'Admin/Users'], + ['callbackLinkSocial', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Admin/Users'], + ['socialLogin', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'socialLogin'], 'Admin/Users'], + ['login', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'login'], 'Admin/Users'], + ['logout', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'logout'], 'Admin/Users'], + ['getUsersTable', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'getUsersTable'], 'Admin/Users'], + ['setUsersTable', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'setUsersTable'], 'Admin/Users'], + ['profile', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'profile'], 'Admin/Users'], + ['validateReCaptcha', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'validateReCaptcha'], 'Admin/Users'], + ['register', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'register'], 'Admin/Users'], + ['validateEmail', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'validateEmail'], 'Admin/Users'], + ['changePassword', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'changePassword'], 'Admin/Users'], + ['resetPassword', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'resetPassword'], 'Admin/Users'], + ['requestResetPassword', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'requestResetPassword'], 'Admin/Users'], + ['resetOneTimePasswordAuthenticator', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Admin/Users'], + ['validate', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'validate'], 'Admin/Users'], + ['resendTokenValidation', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'resendTokenValidation'], 'Admin/Users'], + ['index', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'], 'Admin/Users'], + ['view', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'view'], 'Admin/Users'], + ['add', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'add'], 'Admin/Users'], + ['edit', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'edit'], 'Admin/Users'], + ['delete', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'delete'], 'Admin/Users'], + ['socialEmail', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'socialEmail'], 'Admin/Users'], + ]; + } + + /** + * Test actionRouteParams method + * + * @dataProvider dataProviderActionRouteParams + * @param string $action user action. + * @param array $expected expected url + * @param string $controller controller name for users, optional + * @return void + */ + public function testActionRouteParams($action, $expected, $controller = null) + { + Configure::write('Users.controller', $controller); + $actual = UsersUrl::actionRouteParams($action); + $this->assertSame($expected, $actual); + } + + /** + * Data provider for test testActionParams + * + * @return array + */ + public function dataProviderActionParams() + { + return [ + ['verify', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'verify'], null], + ['linkSocial', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'linkSocial'], null], + ['callbackLinkSocial', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'callbackLinkSocial'], null], + ['socialLogin', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin'], null], + ['login', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login'], null], + ['logout', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout'], null], + ['getUsersTable', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'getUsersTable'], null], + ['setUsersTable', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'setUsersTable'], null], + ['profile', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], null], + ['validateReCaptcha', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateReCaptcha'], null], + ['register', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'register'], null], + ['validateEmail', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateEmail'], null], + ['changePassword', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'changePassword'], null], + ['resetPassword', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetPassword'], null], + ['requestResetPassword', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'requestResetPassword'], null], + ['resetOneTimePasswordAuthenticator', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], null], + ['validate', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validate'], null], + ['resendTokenValidation', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resendTokenValidation'], null], + ['index', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'index'], null], + ['view', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'view'], null], + ['add', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'add'], null], + ['edit', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'edit'], null], + ['delete', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'delete'], null], + ['socialEmail', ['prefix' => null, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialEmail'], null], + + ['verify', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'verify'], 'Users'], + ['linkSocial', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'linkSocial'], 'Users'], + ['callbackLinkSocial', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Users'], + ['socialLogin', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'socialLogin'], 'Users'], + ['login', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'login'], 'Users'], + ['logout', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'logout'], 'Users'], + ['getUsersTable', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'getUsersTable'], 'Users'], + ['setUsersTable', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'setUsersTable'], 'Users'], + ['profile', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'profile'], 'Users'], + ['validateReCaptcha', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'validateReCaptcha'], 'Users'], + ['register', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'register'], 'Users'], + ['validateEmail', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'validateEmail'], 'Users'], + ['changePassword', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'changePassword'], 'Users'], + ['resetPassword', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'resetPassword'], 'Users'], + ['requestResetPassword', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'requestResetPassword'], 'Users'], + ['resetOneTimePasswordAuthenticator', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Users'], + ['validate', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'validate'], 'Users'], + ['resendTokenValidation', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'resendTokenValidation'], 'Users'], + ['index', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'index'], 'Users'], + ['view', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'view'], 'Users'], + ['add', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'add'], 'Users'], + ['edit', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'edit'], 'Users'], + ['delete', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'delete'], 'Users'], + ['socialEmail', ['prefix' => null, 'plugin' => null, 'controller' => 'Users', 'action' => 'socialEmail'], 'Users'], + + ['verify', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'verify'], 'Admin/Users'], + ['linkSocial', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'linkSocial'], 'Admin/Users'], + ['callbackLinkSocial', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Admin/Users'], + ['socialLogin', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'socialLogin'], 'Admin/Users'], + ['login', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'login'], 'Admin/Users'], + ['logout', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'logout'], 'Admin/Users'], + ['getUsersTable', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'getUsersTable'], 'Admin/Users'], + ['setUsersTable', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'setUsersTable'], 'Admin/Users'], + ['profile', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'profile'], 'Admin/Users'], + ['validateReCaptcha', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'validateReCaptcha'], 'Admin/Users'], + ['register', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'register'], 'Admin/Users'], + ['validateEmail', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'validateEmail'], 'Admin/Users'], + ['changePassword', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'changePassword'], 'Admin/Users'], + ['resetPassword', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'resetPassword'], 'Admin/Users'], + ['requestResetPassword', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'requestResetPassword'], 'Admin/Users'], + ['resetOneTimePasswordAuthenticator', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Admin/Users'], + ['validate', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'validate'], 'Admin/Users'], + ['resendTokenValidation', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'resendTokenValidation'], 'Admin/Users'], + ['index', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'index'], 'Admin/Users'], + ['view', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'view'], 'Admin/Users'], + ['add', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'add'], 'Admin/Users'], + ['edit', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'edit'], 'Admin/Users'], + ['delete', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'delete'], 'Admin/Users'], + ['socialEmail', ['prefix' => 'Admin', 'plugin' => null, 'controller' => 'Users', 'action' => 'socialEmail'], 'Admin/Users'], + ]; + } + + /** + * Test actionParams method + * + * @dataProvider dataProviderActionParams + * @param string $action user action. + * @param array $expected expected url + * @param string $controller controller name for users, optional + * @return void + */ + public function testActionParams($action, $expected, $controller = null) + { + Configure::write('Users.controller', $controller); + $actual = UsersUrl::actionParams($action); + $this->assertSame($expected, $actual); + } + + /** + * Data provider for test testActionUrl + * + * @return array + */ + public function dataProviderActionUrl() + { + return [ + ['verify', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'verify'], null], + ['linkSocial', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'linkSocial'], null], + ['callbackLinkSocial', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'callbackLinkSocial'], null], + ['socialLogin', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin'], null], + ['login', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login'], null], + ['logout', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout'], null], + ['getUsersTable', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'getUsersTable'], null], + ['setUsersTable', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'setUsersTable'], null], + ['profile', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], null], + ['validateReCaptcha', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateReCaptcha'], null], + ['register', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'register'], null], + ['validateEmail', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validateEmail'], null], + ['changePassword', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'changePassword'], null], + ['resetPassword', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetPassword'], null], + ['requestResetPassword', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'requestResetPassword'], null], + ['resetOneTimePasswordAuthenticator', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], null], + ['validate', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'validate'], null], + ['resendTokenValidation', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resendTokenValidation'], null], + ['index', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'index'], null], + ['view', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'view'], null], + ['add', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'add'], null], + ['edit', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'edit'], null], + ['delete', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'delete'], null], + ['socialEmail', ['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialEmail'], null], + + ['verify', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'verify'], 'Users'], + ['linkSocial', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'linkSocial'], 'Users'], + ['callbackLinkSocial', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Users'], + ['socialLogin', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'socialLogin'], 'Users'], + ['login', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'login'], 'Users'], + ['logout', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'logout'], 'Users'], + ['getUsersTable', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'getUsersTable'], 'Users'], + ['setUsersTable', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'setUsersTable'], 'Users'], + ['profile', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'profile'], 'Users'], + ['validateReCaptcha', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'validateReCaptcha'], 'Users'], + ['register', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'register'], 'Users'], + ['validateEmail', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'validateEmail'], 'Users'], + ['changePassword', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'changePassword'], 'Users'], + ['resetPassword', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'resetPassword'], 'Users'], + ['requestResetPassword', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'requestResetPassword'], 'Users'], + ['resetOneTimePasswordAuthenticator', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Users'], + ['validate', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'validate'], 'Users'], + ['resendTokenValidation', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'resendTokenValidation'], 'Users'], + ['index', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'index'], 'Users'], + ['view', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'view'], 'Users'], + ['add', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'add'], 'Users'], + ['edit', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'edit'], 'Users'], + ['delete', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'delete'], 'Users'], + ['socialEmail', ['prefix' => false, 'plugin' => false, 'controller' => 'Users', 'action' => 'socialEmail'], 'Users'], + + ['verify', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'verify'], 'Admin/Users'], + ['linkSocial', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'linkSocial'], 'Admin/Users'], + ['callbackLinkSocial', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'callbackLinkSocial'], 'Admin/Users'], + ['socialLogin', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'socialLogin'], 'Admin/Users'], + ['login', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'login'], 'Admin/Users'], + ['logout', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'logout'], 'Admin/Users'], + ['getUsersTable', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'getUsersTable'], 'Admin/Users'], + ['setUsersTable', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'setUsersTable'], 'Admin/Users'], + ['profile', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'profile'], 'Admin/Users'], + ['validateReCaptcha', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'validateReCaptcha'], 'Admin/Users'], + ['register', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'register'], 'Admin/Users'], + ['validateEmail', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'validateEmail'], 'Admin/Users'], + ['changePassword', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'changePassword'], 'Admin/Users'], + ['resetPassword', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'resetPassword'], 'Admin/Users'], + ['requestResetPassword', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'requestResetPassword'], 'Admin/Users'], + ['resetOneTimePasswordAuthenticator', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator'], 'Admin/Users'], + ['validate', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'validate'], 'Admin/Users'], + ['resendTokenValidation', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'resendTokenValidation'], 'Admin/Users'], + ['index', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'index'], 'Admin/Users'], + ['view', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'view'], 'Admin/Users'], + ['add', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'add'], 'Admin/Users'], + ['edit', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'edit'], 'Admin/Users'], + ['delete', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'delete'], 'Admin/Users'], + ['socialEmail', ['prefix' => 'Admin', 'plugin' => false, 'controller' => 'Users', 'action' => 'socialEmail'], 'Admin/Users'], + ]; + } + + /** + * Test actionUrl method + * + * @dataProvider dataProviderActionUrl + * @param string $action user action. + * @param array $expected expected url + * @param string $controller controller name for users, optional + * @return void + */ + public function testActionUrl($action, $expected, $controller = null) + { + Configure::write('Users.controller', $controller); + $actual = UsersUrl::actionUrl($action); + $this->assertSame($expected, $actual); + } + + /** + * Data provider for testCheckActionOnRequest + * + * @return array + */ + public function dataProviderCheckActionOnRequest() + { + return [ + [ + 'socialLogin', + ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'provider' => 'facebook'], + null, + true, + ], + [ + 'socialLogin', + ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'provider' => 'facebook'], + 'CakeDC/Users.Users', + true, + ], + [ + 'socialLogin', + ['plugin' => null, 'controller' => 'Users', 'action' => 'socialLogin', 'provider' => 'facebook'], + 'CakeDC/Users.Users', + false, + ], + [ + 'login', + ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'provider' => 'facebook'], + 'CakeDC/Users.Users', + false, + ], + [ + 'socialLogin', + ['plugin' => null, 'controller' => 'Users', 'action' => 'socialLogin', 'provider' => 'facebook'], + 'Users', + true, + ], + ]; + } + + /** + * Test checkActionOnRequest method + * + * @param string $action user action + * @param array $params request params + * @param string $controller users controller + * @param bool $expected result expected + * @dataProvider dataProviderCheckActionOnRequest + * @return void + */ + public function testCheckActionOnRequest($action, $params, $controller, $expected) + { + Configure::write('Users.controller', $controller); + + $uri = new Uri('/auth/facebook'); + $request = ServerRequestFactory::fromGlobals(); + $request = $request->withUri($uri); + $request = $request->withAttribute('params', $params); + $actual = UsersUrl::checkActionOnRequest($action, $request); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/TestCase/View/Helper/AuthLinkHelperTest.php b/tests/TestCase/View/Helper/AuthLinkHelperTest.php index 3817c10ef..c69f5393c 100644 --- a/tests/TestCase/View/Helper/AuthLinkHelperTest.php +++ b/tests/TestCase/View/Helper/AuthLinkHelperTest.php @@ -1,29 +1,29 @@ AuthLink = new AuthLinkHelper($view); + $view = new View(new ServerRequest()); + $this->AuthLink = $this->getMockBuilder(AuthLinkHelper::class) + ->setMethods(['isAuthorized']) + ->setConstructorArgs([$view]) + ->getMock(); } /** @@ -48,7 +51,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->AuthLink); @@ -60,10 +63,20 @@ public function tearDown() * * @return void */ - public function testLinkFalse() + public function testLinkFalseWithMock(): void { - $link = $this->AuthLink->link('title', ['controller' => 'noaccess']); - $this->assertSame(false, $link); + $this->AuthLink->expects($this->once()) + ->method('isAuthorized') + ->with( + $this->equalTo(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']) + ) + ->will($this->returnValue(false)); + $result = $this->AuthLink->link( + 'title', + ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], + ['before' => 'before_', 'after' => '_after', 'class' => 'link-class'] + ); + $this->assertEmpty($result); } /** @@ -71,22 +84,26 @@ public function testLinkFalse() * * @return void */ - public function testLinkAuthorized() + public function testLinkAuthorizedHappy(): void { - $view = new View(); - $eventManagerMock = $this->getMockBuilder('Cake\Event\EventManager') - ->setMethods(['dispatch']) - ->getMock(); - EventManager::instance($eventManagerMock); - $this->AuthLink = new AuthLinkHelper($view); - $result = new Event('dispatch-result'); - $result->result = true; - $eventManagerMock->expects($this->once()) - ->method('dispatch') - ->will($this->returnValue($result)); - - $link = $this->AuthLink->link('title', '/', ['before' => 'before_', 'after' => '_after', 'class' => 'link-class']); - $this->assertSame('before_title_after', $link); + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); + $this->AuthLink->expects($this->once()) + ->method('isAuthorized') + ->with( + $this->equalTo(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']) + ) + ->will($this->returnValue(true)); + $link = $this->AuthLink->link( + 'title', + ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], + ['before' => 'before_', 'after' => '_after', 'class' => 'link-class'] + ); + $this->assertSame('before_title_after', $link); } /** @@ -94,19 +111,8 @@ public function testLinkAuthorized() * * @return void */ - public function testLinkAuthorizedAllowedTrue() + public function testLinkAuthorizedAllowedTrue(): void { - $view = new View(); - $eventManagerMock = $this->getMockBuilder('Cake\Event\EventManager') - ->setMethods(['dispatch']) - ->getMock(); - EventManager::instance($eventManagerMock); - $this->AuthLink = new AuthLinkHelper($view); - $result = new Event('dispatch-result'); - $result->result = true; - $eventManagerMock->expects($this->never()) - ->method('dispatch'); - $link = $this->AuthLink->link('title', '/', ['allowed' => true, 'before' => 'before_', 'after' => '_after', 'class' => 'link-class']); $this->assertSame('before_title_after', $link); } @@ -116,41 +122,113 @@ public function testLinkAuthorizedAllowedTrue() * * @return void */ - public function testLinkAuthorizedAllowedFalse() + public function testLinkAuthorizedAllowedFalse(): void { - $view = new View(); - $eventManagerMock = $this->getMockBuilder('Cake\Event\EventManager') - ->setMethods(['dispatch']) - ->getMock(); - $view->getEventManager($eventManagerMock); - $this->AuthLink = new AuthLinkHelper($view); - $result = new Event('dispatch-result'); - $eventManagerMock->expects($this->never()) - ->method('dispatch'); $link = $this->AuthLink->link('title', '/', ['allowed' => false, 'before' => 'before_', 'after' => '_after', 'class' => 'link-class']); - $this->assertFalse($link); + $this->assertEmpty($link); } /** - * Test isAuthorized + * Test getRequest method + * + * @retunr void + */ + public function testGetRequest(): void + { + $actual = $this->AuthLink->getRequest(); + $this->assertInstanceOf(ServerRequest::class, $actual); + } + + /** + * Test post link with delete user method + * Logged as Super user * * @return void */ - public function testIsAuthorized() + public function testPostLinkAuthorizedAllowedTrueLoggedAsAdmin(): void { - $view = new View(); - $eventManagerMock = $this->getMockBuilder('Cake\Event\EventManager') - ->setMethods(['dispatch']) - ->getMock(); - EventManager::instance($eventManagerMock); - $this->AuthLink = new AuthLinkHelper($view); - $result = new Event('dispatch-result'); - $result->result = true; - $eventManagerMock->expects($this->once()) - ->method('dispatch') - ->will($this->returnValue($result)); - - $result = $this->AuthLink->isAuthorized(['controller' => 'MyController', 'action' => 'myAction']); - $this->assertTrue($result); + $url = [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'delete', + '00000000-0000-0000-0000-000000000010', + ]; + $builder = Router::createRouteBuilder('/'); + $builder->connect('/Users/delete/00000000-0000-0000-0000-000000000010', $url); + $this->AuthLink->expects($this->once()) + ->method('isAuthorized') + ->with( + $this->equalTo($url) + ) + ->will($this->returnValue(true)); + $link = $this->AuthLink->postLink('Post Link Title', $url, [ + 'allowed' => true, + 'class' => 'link-class', + 'confirm' => 'confirmation message', + ]); + $this->assertStringContainsString('data-confirm-message="confirmation message"', $link); + $this->assertStringContainsString('Post Link Title', $link); + } + + /** + * Test post link with delete user method + * Logged as normal user + * + * @return void + */ + public function testPostLinkAuthorizedAllowedFalseLoggedWithoutRole(): void + { + $url = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'delete', + '00000000-0000-0000-0000-000000000010', + ]; + + $this->AuthLink->expects($this->once()) + ->method('isAuthorized') + ->with( + $this->equalTo($url) + ) + ->will($this->returnValue(false)); + + $link = $this->AuthLink->postLink('Post Link Title', $url, [ + 'allowed' => true, + 'class' => 'link-class', + 'confirm' => 'confirmation message', + ]); + + $this->assertEmpty($link); + } + + /** + * Test post link with delete user method + * + * @return void + */ + public function testPostLinkAuthorizedAllowedFalse(): void + { + $url = [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'delete', + '00000000-0000-0000-0000-000000000010', + ]; + + $this->AuthLink->expects($this->once()) + ->method('isAuthorized') + ->with( + $this->equalTo($url) + ) + ->will($this->returnValue(false)); + + $link = $this->AuthLink->postLink('Post Link Title', $url, [ + 'allowed' => true, + 'class' => 'link-class', + 'confirm' => 'confirmation message', + ]); + $this->assertEmpty($link); } } diff --git a/tests/TestCase/View/Helper/UserHelperTest.php b/tests/TestCase/View/Helper/UserHelperTest.php index e20b30e77..1ce9a8f58 100644 --- a/tests/TestCase/View/Helper/UserHelperTest.php +++ b/tests/TestCase/View/Helper/UserHelperTest.php @@ -1,29 +1,26 @@ oauthConfig === null) { $this->oauthConfig = (array)Configure::read('OAuth'); @@ -57,7 +64,6 @@ public function setUp() } parent::setUp(); - Plugin::routes('CakeDC/Users'); $this->View = $this->getMockBuilder('Cake\View\View') ->setMethods(['append']) ->getMock(); @@ -79,7 +85,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { Configure::write('OAuth', $this->oauthConfig); Configure::write('Users.Social.login', $this->socialLogin); @@ -95,6 +101,13 @@ public function tearDown() */ public function testLogout() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/logout', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'logout', + ]); + $result = $this->User->logout(); $expected = 'Logout'; $this->assertEquals($expected, $result); @@ -107,6 +120,13 @@ public function testLogout() */ public function testLogoutDifferentMessage() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/logout', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'logout', + ]); + $result = $this->User->logout('Sign Out'); $expected = 'Sign Out'; $this->assertEquals($expected, $result); @@ -119,6 +139,13 @@ public function testLogoutDifferentMessage() */ public function testLogoutWithOptions() { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/logout', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'logout', + ]); + $result = $this->User->logout('Sign Out', ['class' => 'logout']); $expected = 'Sign Out'; $this->assertEquals($expected, $result); @@ -131,27 +158,52 @@ public function testLogoutWithOptions() */ public function testWelcome() { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['read']) - ->getMock(); - $session->expects($this->at(0)) - ->method('read') - ->with('Auth.User.id') - ->will($this->returnValue(2)); - - $session->expects($this->at(1)) - ->method('read') - ->with('Auth.User.first_name') - ->will($this->returnValue('david')); - - $this->User->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession']) - ->getMock(); - $this->User->request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); + + $user = [ + 'id' => 2, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'username' => 'j04n.d09', + ]; + $identity = new Identity($user); + $request = new ServerRequest(); + $request = $request->withAttribute('identity', $identity); + $this->User->getView()->setRequest($request); + $expected = 'Welcome, John'; + $result = $this->User->welcome(); + $this->assertEquals($expected, $result); + } - $expected = 'Welcome, david'; + /** + * Test link + * + * @return void + */ + public function testWelcomeWillDisplayUsernameInstead() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); + + $user = [ + 'id' => 2, + 'last_name' => 'Doe', + 'username' => 'j04n.d09', + ]; + $identity = new Identity($user); + $request = new ServerRequest(); + $request = $request->withAttribute('identity', $identity); + $this->User->getView()->setRequest($request); + $expected = 'Welcome, j04n.d09'; $result = $this->User->welcome(); $this->assertEquals($expected, $result); } @@ -163,21 +215,8 @@ public function testWelcome() */ public function testWelcomeNotLoggedInUser() { - $session = $this->getMockBuilder('Cake\Http\Session') - ->setMethods(['read']) - ->getMock(); - $session->expects($this->at(0)) - ->method('read') - ->with('Auth.User.id') - ->will($this->returnValue(null)); - - $this->User->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['getSession']) - ->getMock(); - $this->User->request->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($session)); - + $request = new ServerRequest(); + $this->User->getView()->setRequest($request); $result = $this->User->welcome(); $this->assertEmpty($result); } @@ -190,8 +229,12 @@ public function testWelcomeNotLoggedInUser() public function testAddReCaptcha() { Configure::write('Users.reCaptcha.key', 'testKey'); + Configure::write('Users.reCaptcha.theme', 'light'); + Configure::write('Users.reCaptcha.size', 'normal'); + Configure::write('Users.reCaptcha.tabindex', '3'); + $this->User->Form->create(); $result = $this->User->addReCaptcha(); - $this->assertEquals('
', $result); + $this->assertEquals('
', $result); } /** @@ -213,7 +256,7 @@ public function testAddReCaptchaEmpty() */ public function testAddReCaptchaScript() { - $this->View->expects($this->at(0)) + $this->View->expects($this->once()) ->method('append') ->with('script', $this->stringContains('https://www.google.com/recaptcha/api.js')); $this->User->addReCaptchaScript(); @@ -297,8 +340,8 @@ public function testSocialConnectLinkListIsConnectedWithFacebook() 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' - ]) + 'modified' => '2015-05-22 21:52:44', + ]), ]; $actual = $this->User->socialConnectLinkList($socialAccounts); $expected = ' Connected with Facebook'; @@ -336,8 +379,8 @@ public function testSocialConnectLinkListSocialIsNotEnabled() 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' - ]) + 'modified' => '2015-05-22 21:52:44', + ]), ]; $actual = $this->User->socialConnectLinkList($socialAccounts); $expected = ''; @@ -368,8 +411,8 @@ public function testSocialConnectLinkListSocialEnabledButNotConfiguredProvider() 'active' => false, 'data' => '', 'created' => '2015-05-22 21:52:44', - 'modified' => '2015-05-22 21:52:44' - ]) + 'modified' => '2015-05-22 21:52:44', + ]), ]; $actual = $this->User->socialConnectLinkList($socialAccounts); $expected = ''; diff --git a/tests/TestCase/Webauthn/AuthenticateAdapterTest.php b/tests/TestCase/Webauthn/AuthenticateAdapterTest.php new file mode 100644 index 000000000..70c101c90 --- /dev/null +++ b/tests/TestCase/Webauthn/AuthenticateAdapterTest.php @@ -0,0 +1,79 @@ +get('CakeDC/Users.Users'); + $user = $UsersTable->get($userId); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + $adapter = new AuthenticateAdapter($request); + $options = $adapter->getOptions(); + $this->assertInstanceOf(PublicKeyCredentialRequestOptions::class, $options); + $this->assertSame($options, $request->getSession()->read('Webauthn2fa.authenticateOptions')); + $data = json_decode('{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAA","signature":"MEYCIQCv7EqsBRtf2E4o_BjzZfBwNpP8fLjd5y6TUOLWt5l9DQIhANiYig9newAJZYTzG1i5lwP-YQk9uXFnnDaHnr2yCKXL","userHandle":"","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4ZGowQ0JmWDY5MnFzQVRweTBrTmM4NTMzSmR2ZExVcHFZUDh3RFRYX1pFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9"},"type":"public-key"}', true); + $request = $request->withParsedBody($data); + + $adapter = $this->getMockBuilder(AuthenticateAdapter::class) + ->onlyMethods(['loadAndCheckAssertionResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000001'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('loadAndCheckAssertionResponse') + ->with( + $this->equalTo($options) + ) + ->willReturn($credential); + $actual = $adapter->verifyResponse(); + $this->assertEquals($credential, $actual); + + $adapter = new AuthenticateAdapter($request); + + $this->expectException(\Assert\InvalidArgumentException::class); + $this->getExpectedExceptionMessage('The credential ID is not allowed.'); + $adapter->verifyResponse(); + } +} diff --git a/tests/TestCase/Webauthn/RegisterAdapterTest.php b/tests/TestCase/Webauthn/RegisterAdapterTest.php new file mode 100644 index 000000000..3ce86a8e8 --- /dev/null +++ b/tests/TestCase/Webauthn/RegisterAdapterTest.php @@ -0,0 +1,90 @@ +get('CakeDC/Users.Users'); + $user = $UsersTable->get($userId); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + $adapter = new RegisterAdapter($request, $UsersTable); + $this->assertFalse($adapter->hasCredential()); + $options = $adapter->getOptions(); + $this->assertInstanceOf(PublicKeyCredentialCreationOptions::class, $options); + $this->assertSame($options, $request->getSession()->read('Webauthn2fa.registerOptions')); + + $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + $request = $request->withParsedBody( + json_decode($data, true) + ); + //Mock success response + $adapter = $this->getMockBuilder(RegisterAdapter::class) + ->onlyMethods(['loadAndCheckAttestationResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000002'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('loadAndCheckAttestationResponse') + ->with( + $this->equalTo($options) + ) + ->willReturn($credential); + $credentialsList = $adapter->getUser()->additional_data['webauthn_credentials'] ?? []; + $this->assertCount(0, $credentialsList); + $actual = $adapter->verifyResponse(); + $this->assertEquals($credential, $actual); + $credentialsList = $adapter->getUser()->additional_data['webauthn_credentials']; + $this->assertCount(1, $credentialsList); + $key = key($credentialsList); + $this->assertIsString($key); + $this->assertTrue(isset($credentialsList[$key]['publicKeyCredentialId'])); + $this->assertTrue($adapter->hasCredential()); + //Invalid challenge without mock + $adapter = new RegisterAdapter($request); + $this->expectException(\Assert\InvalidArgumentException::class); + $this->getExpectedExceptionMessage('Invalid challenge.'); + $adapter->verifyResponse(); + } +} diff --git a/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php b/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php new file mode 100644 index 000000000..021d08c8b --- /dev/null +++ b/tests/TestCase/Webauthn/Repository/UserCredentialSourceRepositoryTest.php @@ -0,0 +1,122 @@ +get('CakeDC/Users.Users'); + $user = $UsersTable->get('00000000-0000-0000-0000-000000000001'); + $repository = new UserCredentialSourceRepository($user, $UsersTable); + $credential = $repository->findOneByCredentialId('12b37486-9299-4331-ac33-85b2d985b6fe'); + $this->assertInstanceOf(PublicKeyCredentialSource::class, $credential); + + //Not found id + $repository = new UserCredentialSourceRepository($user, $UsersTable); + $credential = $repository->findOneByCredentialId('some-testing-value'); + $this->assertNull($credential); + } + + /** + * Test findAllForUserEntity method + * + * @return void + */ + public function testFindAllForUserEntity() + { + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $userId = '00000000-0000-0000-0000-000000000001'; + $user = $UsersTable->get($userId); + $userEntity = new PublicKeyCredentialUserEntity( + 'john.doe', + $userId, + 'John Doe' + ); + $repository = new UserCredentialSourceRepository($user, $UsersTable); + $credentials = $repository->findAllForUserEntity($userEntity); + $this->assertCount(1, $credentials); + $this->assertInstanceOf(PublicKeyCredentialSource::class, $credentials[0]); + + //Not found id + $userEntityInvalid = new PublicKeyCredentialUserEntity( + 'john.doe', + '00000000-0000-0000-0000-000000000004', + 'John Doe' + ); + $repository = new UserCredentialSourceRepository($user, $UsersTable); + $credentials = $repository->findAllForUserEntity($userEntityInvalid); + $this->assertEmpty($credentials); + } + + /** + * Test saveCredentialSource method + * + * @return void + */ + public function testSaveCredentialSource() + { + \Cake\Database\TypeFactory::map('json', \Cake\Database\Type\JsonType::class); + + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000001'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $UsersTable = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $user = $UsersTable->get($userId); + $firstKey = key($user->additional_data['webauthn_credentials']); + $userEntity = new PublicKeyCredentialUserEntity( + 'john.doe', + $userId, + 'John Doe' + ); + $publicKey = PublicKeyCredentialSource::createFromArray($credentialData); + $repository = new UserCredentialSourceRepository($user, $UsersTable); + $repository->saveCredentialSource($publicKey); + $credentials = $repository->findAllForUserEntity($userEntity); + $this->assertCount(2, $credentials); + $userAfter = $UsersTable->get($user->id); + $this->assertArrayHasKey( + '12b37486-9299-4331-ac33-85b2d985b6fe', + $userAfter->additional_data['webauthn_credentials'] + ); + $this->assertArrayHasKey( + $firstKey, + $userAfter->additional_data['webauthn_credentials'] + ); + $this->assertCount( + 2, + $userAfter->additional_data['webauthn_credentials'] + ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e94978c62..f5424bd41 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,11 +1,13 @@ 'Users\Test\App']); - Cake\Core\Configure::write('debug', true); -Cake\Core\Configure::write('App.encoding', 'UTF-8'); ini_set('intl.default_locale', 'en_US'); @@ -84,47 +88,56 @@ Cake\Cache\Cache::setConfig($cache); Cake\Core\Configure::write('Session', [ - 'defaults' => 'php' + 'defaults' => 'php', ]); -//init router -\Cake\Routing\Router::reload(); - -\Cake\Core\Plugin::load('CakeDC/Users', [ +Plugin::getCollection()->add(new \CakeDC\Users\Plugin([ 'path' => dirname(dirname(__FILE__)) . DS, - 'routes' => true -]); + 'routes' => true, +])); if (file_exists($root . '/config/bootstrap.php')) { require $root . '/config/bootstrap.php'; } -Cake\Routing\DispatcherFactory::add('Routing'); -Cake\Routing\DispatcherFactory::add('ControllerFactory'); - -class_alias('CakeDC\Users\Test\App\Controller\AppController', 'App\Controller\AppController'); - -// Ensure default test connection is defined if (!getenv('db_dsn')) { putenv('db_dsn=sqlite:///:memory:'); } Cake\Datasource\ConnectionManager::setConfig('test', [ 'url' => getenv('db_dsn'), -// 'className' => 'Cake\Database\Connection', -// 'driver' => 'Cake\Database\Driver\Postgres', -// 'persistent' => true, -// 'host' => 'localhost', -// 'username' => 'my_app', -// 'password' => null, -// 'database' => 'test', -// 'schema' => 'public', -// 'port' => 5432, -// 'encoding' => 'utf8', -// 'flags' => [], -// 'init' => [], - 'timezone' => 'UTC' + 'timezone' => 'UTC', ]); -\Cake\Core\Configure::write('App.paths.templates', [ - APP . 'Template/', +class_alias('TestApp\Controller\AppController', 'App\Controller\AppController'); + +\Cake\Core\Configure::write('App', [ + 'namespace' => 'TestApp', + 'encoding' => 'UTF-8', + 'base' => false, + 'baseUrl' => false, + 'dir' => 'src', + 'webroot' => WEBROOT_DIR, + 'wwwRoot' => WWW_ROOT, + 'fullBaseUrl' => 'http://example.com', + 'imageBaseUrl' => 'img/', + 'jsBaseUrl' => 'js/', + 'cssBaseUrl' => 'css/', + 'paths' => [ + 'plugins' => [dirname(APP) . DS . 'plugins' . DS], + 'templates' => [dirname(APP) . DS . 'templates' . DS], + ], ]); +\Cake\Utility\Security::setSalt('yoyz186elmi66ab9pz4imbb3tgy9vnsgsfgwe2r8tyxbbfdygu9e09tlxyg8p7dq'); + +Plugin::getCollection()->add(new \CakeDC\Users\Plugin()); +session_id('cli'); + +\Cake\Core\Configure::write('Users.AllowedRedirectHosts', [ + 'localhost', + 'example.com', +]); + +if (env('FIXTURE_SCHEMA_METADATA')) { + $loader = new \Cake\TestSuite\Fixture\SchemaLoader(); + $loader->loadInternalFile(env('FIXTURE_SCHEMA_METADATA')); +} diff --git a/tests/config/routes.php b/tests/config/routes.php deleted file mode 100644 index 911b3c38b..000000000 --- a/tests/config/routes.php +++ /dev/null @@ -1,35 +0,0 @@ - '/users'], function ($routes) { - $routes->fallbacks('DashedRoute'); -}); - -$oauthPath = Configure::read('Opauth.path'); -if (is_array($oauthPath)) { - Router::scope('/auth', function ($routes) use ($oauthPath) { - $routes->connect( - '/*', - $oauthPath - ); - }); -} -Router::connect('/accounts/validate/*', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'SocialAccounts', - 'action' => 'validate' -]); -Router::connect('/profile/*', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']); -Router::connect('/login', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); -Router::connect('/logout', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout']); diff --git a/tests/schema.php b/tests/schema.php new file mode 100644 index 000000000..18db1b7c5 --- /dev/null +++ b/tests/schema.php @@ -0,0 +1,95 @@ + 'posts', + 'columns' => [ + 'id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'title' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + ], + 'constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], + ], + ], + [ + 'table' => 'social_accounts', + 'columns' => [ + 'id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'provider' => ['type' => 'string', 'length' => 255, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], + 'username' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'reference' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'avatar' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'description' => ['type' => 'text', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'link' => ['type' => 'text', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'token' => ['type' => 'string', 'length' => 500, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'token_secret' => ['type' => 'string', 'length' => 500, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'token_expires' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'active' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => true, 'comment' => '', 'precision' => null], + 'data' => ['type' => 'text', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], + [ + 'table' => 'users', + 'columns' => [ + 'id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'username' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'email' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'password' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'first_name' => ['type' => 'string', 'length' => 50, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'last_name' => ['type' => 'string', 'length' => 50, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'token' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'token_expires' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'api_token' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'activation_date' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'secret' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'secret_verified' => ['type' => 'boolean', 'length' => null, 'null' => true, 'default' => false, 'comment' => '', 'precision' => null], + 'tos_date' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'active' => ['type' => 'boolean', 'length' => null, 'null' => false, 'default' => true, 'comment' => '', 'precision' => null], + 'is_superuser' => ['type' => 'boolean', 'length' => null, 'unsigned' => false, 'null' => false, 'default' => false, 'comment' => '', 'precision' => null, 'autoIncrement' => null], + 'role' => ['type' => 'string', 'length' => 255, 'null' => true, 'default' => 'user', 'comment' => '', 'precision' => null, 'fixed' => null], + 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'additional_data' => ['type' => 'text', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + 'last_login' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], + [ + 'table' => 'posts_users', + 'columns' => [ + 'id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], + 'user_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + 'post_id' => ['type' => 'uuid', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null], + ], + 'constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], + ], + ], +]; diff --git a/tests/test_app/TestApp/Application.php b/tests/test_app/TestApp/Application.php new file mode 100644 index 000000000..f34718450 --- /dev/null +++ b/tests/test_app/TestApp/Application.php @@ -0,0 +1,77 @@ + [ + 'className' => \Cake\Mailer\Transport\DebugTransport::class, + ], + ]); + } + if (!\Cake\Mailer\Email::getConfig('default')) { + \Cake\Mailer\Email::setConfig([ + 'default' => [ + 'transport' => 'default', + 'from' => 'you@localhost', + ], + ]); + } + $this->addPlugin(Plugin::class); + } + + /** + * @inheritDoc + */ + public function pluginBootstrap(): void + { + Configure::write('Users.config', ['users']); + parent::pluginBootstrap(); + $this->dispatchEvent(static::EVENT_AFTER_PLUGIN_BOOTSTRAP); + } + + /** + * Setup the middleware queue your application will use. + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup. + * @return \Cake\Http\MiddlewareQueue The updated middleware queue. + */ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + $middlewareQueue + ->add(new AssetMiddleware([ + 'cacheTime' => Configure::read('Asset.cacheTime'), + ])) + ->add(new RoutingMiddleware($this)); + + return $middlewareQueue; + } +} diff --git a/tests/App/Controller/AppController.php b/tests/test_app/TestApp/Controller/AppController.php similarity index 70% rename from tests/App/Controller/AppController.php rename to tests/test_app/TestApp/Controller/AppController.php index 4afebdf12..db11ec36d 100644 --- a/tests/App/Controller/AppController.php +++ b/tests/test_app/TestApp/Controller/AppController.php @@ -1,15 +1,17 @@ loadComponent('Flash'); - // $this->loadComponent('CakeDC/Users.UsersAuth'); $this->loadComponent('RequestHandler'); } } diff --git a/tests/test_app/TestApp/Http/TestRequestHandler.php b/tests/test_app/TestApp/Http/TestRequestHandler.php new file mode 100644 index 000000000..fe913db9b --- /dev/null +++ b/tests/test_app/TestApp/Http/TestRequestHandler.php @@ -0,0 +1,30 @@ +callable = $callable ?: function ($request) { + return new Response(); + }; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $this->request = $request; + + return ($this->callable)($request); + } +} diff --git a/tests/App/Mailer/OverrideMailer.php b/tests/test_app/TestApp/Mailer/OverrideMailer.php similarity index 81% rename from tests/App/Mailer/OverrideMailer.php rename to tests/test_app/TestApp/Mailer/OverrideMailer.php index 3affd4dc0..3dbe44fe8 100644 --- a/tests/App/Mailer/OverrideMailer.php +++ b/tests/test_app/TestApp/Mailer/OverrideMailer.php @@ -1,27 +1,29 @@ setRouteClass(DashedRoute::class); + +$routes->scope('/', function (RouteBuilder $builder) { + /** + * Connect catchall routes for all controllers. + * + * The `fallbacks` method is a shortcut for + * + * ``` + * $builder->connect('/:controller', ['action' => 'index']); + * $builder->connect('/:controller/:action/*', []); + * ``` + * + * You can remove these routes once you've connected the + * routes you want in your application. + */ + $builder->fallbacks(); +}); diff --git a/tests/test_app/config/users.php b/tests/test_app/config/users.php new file mode 100644 index 000000000..35e35adcd --- /dev/null +++ b/tests/test_app/config/users.php @@ -0,0 +1,12 @@ + true, + 'Auth.AuthenticationComponent.loginRedirect' => '/pages/home', + 'OAuth.providers.facebook.options.clientId' => '1010101010101010', + 'OAuth.providers.facebook.options.clientSecret' => 'ABABABABABABABABA', + 'OAuth.providers.twitter.options.clientId' => 'QWERTY0009', + 'OAuth.providers.twitter.options.clientSecret' => '999988899', + 'OAuth.providers.google.options.clientId' => '0000000990909090', + 'OAuth.providers.google.options.clientSecret' => '1565464559789798', +]; diff --git a/tests/test_app/templates/Error/empty b/tests/test_app/templates/Error/empty new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_app/templates/Error/error400.php b/tests/test_app/templates/Error/error400.php new file mode 100644 index 000000000..840e81bf6 --- /dev/null +++ b/tests/test_app/templates/Error/error400.php @@ -0,0 +1,29 @@ + +

+

+ : + '{$url}'" + ) ?> +

+element('exception_stack_trace'); +endif; +?> diff --git a/tests/test_app/templates/Error/error500.php b/tests/test_app/templates/Error/error500.php new file mode 100644 index 000000000..f522672f1 --- /dev/null +++ b/tests/test_app/templates/Error/error500.php @@ -0,0 +1,27 @@ + +

+

+ : + +

+element('auto_table_warning'); + echo $this->element('exception_stack_trace'); +endif; +?> diff --git a/tests/test_app/templates/Pages/home.php b/tests/test_app/templates/Pages/home.php new file mode 100644 index 000000000..0d8fca5d7 --- /dev/null +++ b/tests/test_app/templates/Pages/home.php @@ -0,0 +1,173 @@ + +

+

+ Read the changelog +

+ + +

+ URL rewriting is not properly configured on your server. + 1) Help me configure it + 2) I don't / can't use URL rewriting +

+ + +

+=')): ?> + Your version of PHP is 5.4.3 or higher + + Your version of PHP is too low. You need PHP 5.4.3 or higher to use CakePHP. + +

+ +

+ + Your version of PHP has mbstring extension loaded. + + Your version of PHP does NOT have the mbstring extension loaded. + +

+ +

+ + Your tmp directory is writable. + + Your tmp directory is NOT writable. + +

+ +

+config() : false; +if (!empty($settings)): ?> + The Engine is being used for core caching. To change the config edit APP/Config/cache.php + + Your cache is NOT working. Please check the settings in APP/Config/cache.php + +

+ +

+ + Your datasources configuration file is present. + + + Your datasources configuration file is NOT present. +
+ Rename APP/Config/datasources.default.php to APP/Config/datasources.php +
+ +

+ + +

' + PCRE has not been compiled with Unicode support.'; +
+ Recompile PCRE with Unicode support by adding --enable-unicode-properties when configuring +

+ + +

+ + DebugKit plugin is present + + '; + DebugKit is not installed. It will help you inspect and debug different aspects of your application. +
+ You can install it from Html->link('GitHub', 'https://github.com/cakephp/debug_kit'); ?> +
+ +

+ +

Editing this Page

+

+To change the content of this page, edit: APP/View/Pages/home.ctp.
+To change its layout, edit: APP/View/Layout/default.ctp.
+You can also add some CSS styles for your pages at: APP/webroot/css.; +

+ +

Getting Started

+

+ Html->link( + 'New CakePHP 3.0 Docs', + 'https://book.cakephp.org/4/en/', + ['target' => '_blank', 'escape' => false] + ); + ?> +

+

+ Html->link( + 'The 15 min Blog Tutorial', + 'https://book.cakephp.org/4/en/getting-started.html#blog-tutorial', + ['target' => '_blank', 'escape' => false] + ); + ?> +

+ +

Official Plugins

+

+

    +
  • + Html->link('DebugKit', 'https://github.com/cakephp/debug_kit') ?>: + provides a debugging toolbar and enhanced debugging tools for CakePHP application. +
  • +
  • + Html->link('Localized', 'https://github.com/cakephp/localized') ?>: + contains various localized validation classes and translations for specific countries +
  • +
+

+ +

More about CakePHP

+

+CakePHP is a rapid development framework for PHP which uses commonly known design patterns like Active Record, Association Data Mapping, Front Controller and MVC. +

+

+Our primary goal is to provide a structured framework that enables PHP users at all levels to rapidly develop robust web applications, without any loss to flexibility. +

+ + diff --git a/tests/test_app/templates/element/flash/default.php b/tests/test_app/templates/element/flash/default.php new file mode 100644 index 000000000..6e581ce22 --- /dev/null +++ b/tests/test_app/templates/element/flash/default.php @@ -0,0 +1 @@ +
diff --git a/tests/test_app/templates/element/flash/error.php b/tests/test_app/templates/element/flash/error.php new file mode 100644 index 000000000..6e581ce22 --- /dev/null +++ b/tests/test_app/templates/element/flash/error.php @@ -0,0 +1 @@ +
diff --git a/tests/test_app/templates/email/html/default.php b/tests/test_app/templates/email/html/default.php new file mode 100644 index 000000000..57985e141 --- /dev/null +++ b/tests/test_app/templates/email/html/default.php @@ -0,0 +1,7 @@ + ' . $line . '

'; +endforeach; +?> diff --git a/tests/test_app/templates/email/text/default.php b/tests/test_app/templates/email/text/default.php new file mode 100644 index 000000000..90e6d7909 --- /dev/null +++ b/tests/test_app/templates/email/text/default.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/test_app/templates/layout/ajax.php b/tests/test_app/templates/layout/ajax.php new file mode 100644 index 000000000..2f16ebc45 --- /dev/null +++ b/tests/test_app/templates/layout/ajax.php @@ -0,0 +1 @@ + diff --git a/tests/test_app/templates/layout/default.php b/tests/test_app/templates/layout/default.php new file mode 100644 index 000000000..3bf15b9f8 --- /dev/null +++ b/tests/test_app/templates/layout/default.php @@ -0,0 +1,43 @@ +loadHelper('Html'); + +$cakeDescription = 'My Test App'; +?> + + + + Html->charset(); ?> + + <?= $cakeDescription ?>: + <?= $this->fetch('title'); ?> + + Html->meta('icon'); + + echo $this->Html->css('cake.generic'); + + echo $this->fetch('script'); + ?> + + +
+ +
+ + fetch('content'); ?> + +
+ +
+ + diff --git a/tests/test_app/templates/layout/email/html/default.php b/tests/test_app/templates/layout/email/html/default.php new file mode 100644 index 000000000..809f44c2b --- /dev/null +++ b/tests/test_app/templates/layout/email/html/default.php @@ -0,0 +1,13 @@ + + + + + <?= $this->fetch('title'); ?> + + + + fetch('content'); ?> + +

This email was sent using the CakePHP Framework

+ + diff --git a/tests/test_app/templates/layout/email/text/default.php b/tests/test_app/templates/layout/email/text/default.php new file mode 100644 index 000000000..1f8162711 --- /dev/null +++ b/tests/test_app/templates/layout/email/text/default.php @@ -0,0 +1,4 @@ + +fetch('content'); ?> + +This email was sent using the CakePHP Framework, https://cakephp.org. \ No newline at end of file diff --git a/tests/test_app/templates/layout/error.php b/tests/test_app/templates/layout/error.php new file mode 100644 index 000000000..3c56766ec --- /dev/null +++ b/tests/test_app/templates/layout/error.php @@ -0,0 +1 @@ +fetch('content'); ?> diff --git a/tests/test_app/templates/layout/js/default.php b/tests/test_app/templates/layout/js/default.php new file mode 100644 index 000000000..77f86d94f --- /dev/null +++ b/tests/test_app/templates/layout/js/default.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/test_app/templates/layout/rss/default.php b/tests/test_app/templates/layout/rss/default.php new file mode 100644 index 000000000..b92cd3069 --- /dev/null +++ b/tests/test_app/templates/layout/rss/default.php @@ -0,0 +1,17 @@ +Rss->header(); + +if (!isset($channel)) { + $channel = []; +} +if (!isset($channel['title'])) { + $channel['title'] = $this->fetch('title'); +} + +echo $this->Rss->document( + $this->Rss->channel( + [], $channel, $this->fetch('content') + ) +); + +?> diff --git a/webroot/js/u2f-api.js b/webroot/js/u2f-api.js new file mode 100644 index 000000000..ac478feff --- /dev/null +++ b/webroot/js/u2f-api.js @@ -0,0 +1,748 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC, USB_INTERNAL}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/webroot/js/webauthn.js b/webroot/js/webauthn.js new file mode 100644 index 000000000..45f9e9c22 --- /dev/null +++ b/webroot/js/webauthn.js @@ -0,0 +1,214 @@ +//Source code based on https://github.com/web-auth/webauthn-helper +// Predefined fetch function +const fetchEndpoint = (data, url, header) => { + return fetch( + url, + { + method: 'POST', + credentials: 'same-origin', + redirect: 'error', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...header + }, + body: JSON.stringify(data), + } + ); +} + +// Decodes a Base64Url string +const base64UrlDecode = (input) => { + input = input + .replace(/-/g, '+') + .replace(/_/g, '/'); + + const pad = input.length % 4; + if (pad) { + if (pad === 1) { + throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); + } + input += new Array(5-pad).join('='); + } + + return window.atob(input); +}; + +// Converts an array of bytes into a Base64Url string +const arrayToBase64String = (a) => btoa(String.fromCharCode(...a)); + +// Prepares the public key options object returned by the Webauthn Framework +const preparePublicKeyOptions = publicKey => { + //Convert challenge from Base64Url string to Uint8Array + publicKey.challenge = Uint8Array.from( + base64UrlDecode(publicKey.challenge), + c => c.charCodeAt(0) + ); + + //Convert the user ID from Base64 string to Uint8Array + if (publicKey.user !== undefined) { + publicKey.user = { + ...publicKey.user, + id: Uint8Array.from( + window.atob(publicKey.user.id), + c => c.charCodeAt(0) + ), + }; + } + + //If excludeCredentials is defined, we convert all IDs to Uint8Array + if (publicKey.excludeCredentials !== undefined) { + publicKey.excludeCredentials = publicKey.excludeCredentials.map( + data => { + return { + ...data, + id: Uint8Array.from( + base64UrlDecode(data.id), + c => c.charCodeAt(0) + ), + }; + } + ); + } + + if (publicKey.allowCredentials !== undefined) { + publicKey.allowCredentials = publicKey.allowCredentials.map( + data => { + return { + ...data, + id: Uint8Array.from( + base64UrlDecode(data.id), + c => c.charCodeAt(0) + ), + }; + } + ); + } + + return publicKey; +}; + +// Prepares the public key credentials object returned by the authenticator +const preparePublicKeyCredentials = data => { + const publicKeyCredential = { + id: data.id, + type: data.type, + rawId: arrayToBase64String(new Uint8Array(data.rawId)), + response: { + clientDataJSON: arrayToBase64String( + new Uint8Array(data.response.clientDataJSON) + ), + }, + }; + + if (data.response.attestationObject !== undefined) { + publicKeyCredential.response.attestationObject = arrayToBase64String( + new Uint8Array(data.response.attestationObject) + ); + } + + if (data.response.authenticatorData !== undefined) { + publicKeyCredential.response.authenticatorData = arrayToBase64String( + new Uint8Array(data.response.authenticatorData) + ); + } + + if (data.response.signature !== undefined) { + publicKeyCredential.response.signature = arrayToBase64String( + new Uint8Array(data.response.signature) + ); + } + + if (data.response.userHandle !== undefined) { + publicKeyCredential.response.userHandle = arrayToBase64String( + new Uint8Array(data.response.userHandle) + ); + } + + return publicKeyCredential; +}; + +const useLogin = ({actionUrl = '/login', actionHeader = {}, optionsUrl = '/login/options'}, optionsHeader = {}) => { + return async (data) => { + const optionsResponse = await fetchEndpoint(data, optionsUrl, optionsHeader); + const json = await optionsResponse.json(); + const publicKey = preparePublicKeyOptions(json); + const credentials = await navigator.credentials.get({publicKey}); + const publicKeyCredential = preparePublicKeyCredentials(credentials); + const actionResponse = await fetchEndpoint(publicKeyCredential, actionUrl, actionHeader); + if (! actionResponse.ok) { + throw actionResponse; + } + const responseBody = await actionResponse.text(); + + return responseBody !== '' ? JSON.parse(responseBody) : responseBody; + }; +}; + + +const useRegistration = ({actionUrl = '/register', actionHeader = {}, optionsUrl = '/register/options'}, optionsHeader = {}) => { + return async (data) => { + const optionsResponse = await fetchEndpoint(data, optionsUrl, optionsHeader); + const json = await optionsResponse.json(); + const publicKey = preparePublicKeyOptions(json); + const credentials = await navigator.credentials.create({publicKey}); + const publicKeyCredential = preparePublicKeyCredentials(credentials); + const actionResponse = await fetchEndpoint(publicKeyCredential, actionUrl, actionHeader); + if (! actionResponse.ok) { + throw actionResponse; + } + const responseBody = await actionResponse.text(); + + return responseBody !== '' ? JSON.parse(responseBody) : responseBody; + }; +}; + +var Webauthn2faHelper = { + toggleElem: function(options) { + console.log({options}); + document.getElementById(options.authenticateElemId).style.display = options.isRegister ? 'none' : 'block'; + document.getElementById(options.registerElemId).style.display = options.isRegister ? 'block' : 'none'; + }, + authenticate: function(options) { + var authenticate = useLogin({ + actionUrl: options.authenticateActionUrl, + optionsUrl: options.authenticateOptionsUrl + }); + authenticate({ + username: options.username + }) + .then(function (response) { + window.location.href = response.redirectUrl; + }) + .catch((error) => { + window.alert('Authentication failure'); + }) + }, + register: function(options) { + var register = useRegistration({ + actionUrl: options.registerActionUrl, + optionsUrl: options.registerOptionsUrl + }); + + return register({ + username: options.username, + }) + .catch((error) => { + window.alert('Registration failed'); + }); + }, + run: function(options) { + Webauthn2faHelper.toggleElem(options); + if (options.isRegister) { + return Webauthn2faHelper.register(options) + .then(function (response) { + if (response && response.success) { + options.isRegister = false; + Webauthn2faHelper.toggleElem(options); + Webauthn2faHelper.authenticate(options); + } + }); + } + return Webauthn2faHelper.authenticate(options); + } +}
FacebookTwitterGoogleAmazonuser-1user-1@test.comfirst1last1user-66@example.comfirst-user-6firts name 6