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 4e2c08050..f696b2eed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ /vendor /.idea composer.lock +.php_cs* +/coverage +.phpunit.result.cache diff --git a/.semver b/.semver index 7048659d7..726c4d95e 100644 --- a/.semver +++ b/.semver @@ -1,5 +1,5 @@ --- -:major: 3 -:minor: 2 -:patch: 5 +:major: 11 +:minor: 0 +:patch: 0 :special: '' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 97ee5fed5..000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -language: php - -php: - - 5.5 - - 5.6 - - 7.0 - -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: 5.5 - env: PHPCS=1 DEFAULT=0 - - - php: 5.5 - env: COVERALLS=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;'; 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:dev-master; fi" - - - sh -c "if [ '$COVERALLS' = '1' ]; then composer require --dev satooshi/php-coveralls:dev-master; fi" - - sh -c "if [ '$COVERALLS' = '1' ]; then mkdir -p build/logs; fi" - -script: - - sh -c "if [ '$DEFAULT' = '1' ]; then 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 [ '$COVERALLS' = '1' ]; then phpunit --stderr --coverage-clover build/logs/clover.xml; fi" - - sh -c "if [ '$COVERALLS' = '1' ]; then php vendor/bin/coveralls -c .coveralls.yml -v; fi" - -notifications: - email: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aaae87b8..78c464692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,189 @@ 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 + * Added a new `UsersAuthComponent::EVENT_AFTER_CHANGE_PASSWORD` + * Updated docs + +* 6.0.0 + * Removed deprecations and orWhere usage + * Amazon login implemented + * Fixed issues with login via twitter + * Updated Facebook Graph version to 2.8 + * Fixed flash error messages on logic + * Added link social account feature for twitter + * Switched to codecov + +* 5.2.0 + * Compatible with 3.5, deprecations will be removed in next major version of the plugin + * Username is now custom in SocialBehavior + * Better handling of the RememberMe checkbox + * Updated CakeDC/Auth to use ^2.0 + * Use of UsersMailer class, and allow override of the emails sent by the plugin + * Better token generation via randomBytes + * Improved documentation + * Fixed bugs reported + +* 5.1.0 + * New resend validation method in RegisterBehavior + * Allow upgrade to CakePHP 3.5.x + * New feature connect social account + * New polish translations + * Fixed bugs reported + +* 5.0.3 + * Implemented event dispatching on social login + * Fixed bugs reported + * Don't check for allowed actions in other controllers + +* 5.0.2 + * Fixed bug parsing rule urls when application installed in a subdirectory + +* 5.0.1 + * Bugfix release + * Minor BR language improvements + +* 5.0.0 + * Some Auth objects refactored into https://github.com/CakeDC/auth + * Upgrade to CakePHP 3.4 + +* 4.2.1 + * Improvements in unit tests + +* 4.2.0 + * New configuration param `Users.Registration.defaultRole` to set the default role on user registration or addUser Shell action + +* 4.1.3 + * Configurable rememberMe checkbox status + * Update brazilian portuguese translations + * Add active finder to SocialAccountsTable + * Improvements in UsersShell for superuser add options + * Update to robthree/twofactorauth 1.6 + * UserHelper improvements + +* 4.1.2 + * Fix RememberMe redirect + * Fix AuthLink rendering inside Cells + +* 4.1.1 + * Add missing password field in add user + +* 4.1.0 + * Add reset action for Google Authenticator + +* 4.0.0 + * Add Google Authenticator + * Add improvements to SimpleRbac, like star to invert rules and `user.` prefix to match values from the user array + * Add `allowed` to manage the AuthLinkHelper when action is allowed + * Add option to configure the api table and finder in ApiKeyAuthenticate + * 3.2.5 * 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 @@ -20,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/CONTRIBUTING.md b/CONTRIBUTING.md index 26f53081b..653b64f7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing ============ -This repository follows the [CakeDC Plugin Standard](http://cakedc.com/plugin-standard). If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our [Contribution Guidelines](http://cakedc.com/contribution-guidelines) for detailed instructions. +This repository follows the [CakeDC Plugin Standard](https://www.cakedc.com/plugin-standard). If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our [Contribution Guidelines](https://www.cakedc.com/contribution-guidelines) for detailed instructions. diff --git a/Docs/Documentation/ApiKeyAuthenticate.md b/Docs/Documentation/ApiKeyAuthenticate.md deleted file mode 100644 index 80f331c4c..000000000 --- a/Docs/Documentation/ApiKeyAuthenticate.md +++ /dev/null @@ -1,37 +0,0 @@ -ApiKeyAuthenticate -============= - -Setup ---------------- - -ApiKeyAuthenticate default configuration is -```php - protected $_defaultConfig = [ - //type, can be either querystring or header - 'type' => self::TYPE_QUERYSTRING, - //name to retrieve the api key value from - 'name' => 'api_key', - //db field where the key is stored - 'field' => 'api_token', - //require SSL to pass the token. You should always require SSL to use tokens for Auth - 'require_ssl' => true, - ]; -``` - -We are using query strings for passing the api_key token. And we require SSL by default. -Note you can override these options using - -```php -$config['Auth']['authenticate']['CakeDC/Users.ApiKey'] = [ - 'type' => 'header', - ]; -``` - -In order to allow stateless authentication, enable these configuration: - -```php - $this->Auth->config('storage', 'Memory'); - $this->Auth->config('unauthorizedRedirect', 'false'); - $this->Auth->config('checkAuthIn', 'Controller.initialize'); - $this->Auth->config('loginAction', false); -``` \ No newline at end of file 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 1f05b7262..a7118a99b 100644 --- a/Docs/Documentation/Configuration.md +++ b/Docs/Documentation/Configuration.md @@ -6,11 +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 @@ -27,24 +27,35 @@ $ 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) +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** + 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 ``` @@ -54,100 +65,127 @@ 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 ``` 'Users' => [ - //Table used to manage users + // Table used to manage users 'table' => 'CakeDC/Users.Users', - //configure Auth component - 'auth' => true, + // Controller used to manage users plugin features & actions + 'controller' => 'CakeDC/Users.Users', 'Email' => [ - //determines if the user should include email + // determines if the user should include email 'required' => true, - //determines if registration workflow includes email validation + // determines if registration workflow includes email validation 'validate' => true, ], 'Registration' => [ - //determines if the register is enabled + // determines if the register is enabled 'active' => true, - //determines if the reCaptcha is enabled for registration + // determines if the reCaptcha is enabled for registration 'reCaptcha' => true, //ensure user is active (confirmed email) to reset his password - 'ensureActive' => false + '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 + // determines if the user should include tos accepted 'required' => true, ], 'Social' => [ - //enable social login + // enable social login 'login' => false, ], - //Avatar placeholder + // Avatar placeholder 'Avatar' => ['placeholder' => 'CakeDC/Users.avatar_placeholder.png'], 'RememberMe' => [ - //configure Remember Me component + // 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/Users.RememberMe', - 'Form', + 'Authentication' => [ + 'serviceLoader' => \CakeDC\Users\Loader\AuthenticationServiceLoader::class ], - 'authorize' => [ - 'CakeDC/Users.Superuser', - 'CakeDC/Users.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 +-------------------------------- + +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. -Using the UsersAuthComponent default initialization, the component will load the following objects into AuthComponent: -* Authenticate - * 'Form' - * 'Social' check [SocialAuthenticate](SocialAuthenticate.md) for configuration options - * 'RememberMe' check [SocialAuthenticate](RememberMeAuthenticate.md) for configuration options -* Authorize - * 'Users.Superuser' check [SuperuserAuthorize](SuperuserAuthorize.md) for configuration options - * 'Users.SimpleRbac' check [SimpleRbacAuthorize](SimpleRbacAuthorize.md) for configuration options +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->input 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->input('email', ['required' => true]) ?> - Form->input('password', ['required' => true]) ?> - // ... rest of your login.ctp code + Form->control('email', ['required' => true]) ?> + Form->control('password', ['required' => true]) ?> + // ... rest of your login.php code ``` @@ -159,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 27056682e..4f8cfe77e 100644 --- a/Docs/Documentation/Extending-the-Plugin.md +++ b/Docs/Documentation/Extending-the-Plugin.md @@ -7,9 +7,9 @@ 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 in our application ```my_users``` +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 ```php @@ -18,7 +18,7 @@ namespace App\Model\Table; use CakeDC\Users\Model\Table\UsersTable; /** - * Users Model + * Application specific Users Table with non plugin conform field(s) */ class MyUsersTable extends UsersTable { @@ -32,8 +32,32 @@ namespace App\Model\Entity; use CakeDC\Users\Model\Entity\User; +/** + * Application specific User Entity with non plugin conform field(s) + */ class MyUser extends User { + /** + * Map CakeDC's User.active field to User.is_active when getting + * + * @return mixed The value of the mapped property. + */ + protected function _getActive() + { + return $this->_properties['is_active']; + } + + /** + * Map CakeDC's User.active field to User.is_active when setting + * + * @param mixed $value The value to set. + * @return static + */ + protected function _setActive($value) + { + $this->set('is_active', $value); + return $value; + } } ``` @@ -42,7 +66,7 @@ class MyUser extends User config/bootstrap.php ``` Configure::write('Users.config', ['users']); -Plugin::load('Users', ['routes' => true, 'bootstrap' => true]); +Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); ``` Then in your config/users.php @@ -53,7 +77,7 @@ return [ ``` Now the Users Plugin will use MyUsers Table and Entity to register and login user in. Use the -Entity to match your own columns in case they don't match the default column names: +Entity as shown above to match your own columns in case they don't match the default column names: ```sql CREATE TABLE IF NOT EXISTS `users` ( @@ -83,6 +107,8 @@ Extending the Controller You want to use one of your controllers to handle all the users features in your app, and keep the login/register/etc actions from Users Plugin, +First create a new controller class: + ```php 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 } ``` -Note you'll need to **copy the Plugin templates** you need into your project src/Template/MyUsers/[action].ctp +Don't forget to update the `Users.controller` configuration in `users.php` this is +needed to setup correct url/route for authentication. + +```php + 'Users' => [ + // ... + // Controller used to manage users plugin features & actions + 'controller' => 'MyUsers', + // ... +``` + +**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 ----------------------------- @@ -115,7 +186,7 @@ You could use a new Trait. For example, you want to add an 'impersonate' feature namespace App\Controller\Traits; use Cake\Datasource\Exception\RecordNotFoundException; -use Cake\Network\Exception\NotFoundException; +use Cake\Http\Exception\NotFoundException; /** * Impersonate Trait @@ -123,18 +194,13 @@ use Cake\Network\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 } } ``` @@ -142,7 +208,45 @@ 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}/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 +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 +* socialAccountValidation, sent with a link to validate the social account used for login + +Example, to override the validation email you would need to: +* Create a new class in your application +```php +namespace App\Mailer; + +use Cake\Datasource\EntityInterface; +use CakeDC\Users\Mailer\UsersMailer; + +class MyUsersMailer extends UsersMailer +{ + public function resetPassword(EntityInterface $user) + { + parent::resetPassword($user); + $this->setSubject('This is the new subject'); + $this->setTemplate('custom-template-in-app-namespace'); + } +} +``` +* 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 `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/Installation.md b/Docs/Documentation/Installation.md index 35a6b8967..f9d8d8e8d 100644 --- a/Docs/Documentation/Installation.md +++ b/Docs/Documentation/Installation.md @@ -2,7 +2,7 @@ Installation ============ Composer ------- +-------- ``` composer require cakedc/users @@ -18,12 +18,8 @@ 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 -login is disabled by default. Check the [Configuration](Configuration.md) page for more details. - -``` -Configure::write('Users.Social.login', true); //to enable social login -``` +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. If you want to use reCaptcha features... @@ -34,6 +30,67 @@ composer require google/recaptcha:@stable NOTE: you'll need to configure the reCaptcha key and secret, check the [Configuration](Configuration.md) page for more details. +If you want to use Google Authenticator features... + +``` +composer require robthree/twofactorauth:"^1.5.2" +``` + +NOTE: you'll need to enable `OneTimePasswordAuthenticator.login` in your config/users.php file: + +```php +'OneTimePasswordAuthenticator.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: @@ -46,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', @@ -75,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` @@ -84,17 +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(); - $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/4.x-5.0.md b/Docs/Documentation/Migration/4.x-5.0.md new file mode 100644 index 000000000..9aba06bd3 --- /dev/null +++ b/Docs/Documentation/Migration/4.x-5.0.md @@ -0,0 +1,56 @@ +Migration 4.x to 5.0 +====================== + +5.0 is compatible with CakePHP ^3.4 and refactored some Auth objects into a new plugin +* Fix your Users configuration file, usually under `config/users.php` for Auth object references + +in `config/users.php` + +```php +$config = [ + // ... + 'Auth' => [ + // ... + 'authenticate' => [ + 'all' => [ + 'finder' => 'auth', + ], + 'CakeDC/Auth.ApiKey', + 'CakeDC/Auth.RememberMe', + 'Form', + ], + 'authorize' => [ + 'CakeDC/Auth.Superuser', + 'CakeDC/Auth.SimpleRbac', + ], + // ... +``` + +* Add a new configuration key to specify the controller name you are using to handle auth in your project + +in `config/users.php` + +```php +$config = [ + 'Users' => [ + // ... + // Controller used to manage users plugin features & actions + 'controller' => 'CakeDC/Users.Users', + // ... +``` + +* If you are using simple RBAC, set `CakeDC/Auth.SimpleRbac.permissions` configuration key to `null` if you want to load permissions from the file `config/permissions.php`. + +in `config/users.php` + +```php +$config = [ + 'Auth.authorize' => [ + 'CakeDC/Auth.SimpleRbac' => [ + 'autoload_config' => 'permissions', + 'permissions' => null, + ], + ], +``` + +If the value is false or empty array, now it is assumed there is no permissions for the user. \ No newline at end of file diff --git a/Docs/Documentation/Migration/6.x-7.0.md b/Docs/Documentation/Migration/6.x-7.0.md new file mode 100644 index 000000000..3258e398f --- /dev/null +++ b/Docs/Documentation/Migration/6.x-7.0.md @@ -0,0 +1,18 @@ +Migration 6.x to 7.0 +====================== + +7.0 is compatible with CakePHP ^3.6 and the plugin code was updated to remove all deprecations. + +* Add 'enableBeforeRedirect' configuration option to RequestHandler component load in your `src/Controller/AppController.php` file + +``` + 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/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/OneTimePasswordAuthenticator/App.png b/Docs/Documentation/OneTimePasswordAuthenticator/App.png new file mode 100644 index 000000000..dd47e9845 Binary files /dev/null and b/Docs/Documentation/OneTimePasswordAuthenticator/App.png differ diff --git a/Docs/Documentation/OneTimePasswordAuthenticator/FirstLogin.png b/Docs/Documentation/OneTimePasswordAuthenticator/FirstLogin.png new file mode 100644 index 000000000..18d4ce76e Binary files /dev/null and b/Docs/Documentation/OneTimePasswordAuthenticator/FirstLogin.png differ diff --git a/Docs/Documentation/Overview.md b/Docs/Documentation/Overview.md index 4e2e392ba..cc47d0159 100644 --- a/Docs/Documentation/Overview.md +++ b/Docs/Documentation/Overview.md @@ -8,11 +8,12 @@ The plugin itself is already capable of: * User registration * Account verification by a token sent via email * User login (email / password) -* Social login (Twitter, Facebook) +* Social login (Twitter, Facebook, Google, Instagram, LinkedIn) * Password reset based on requesting a token by email and entering a new password * User management (add / edit / delete) * Simple roles management * Simple Rbac and SuperUser Authorize * RememberMe using cookie feature * reCaptcha for user registration and login +* Two Factor authentication diff --git a/Docs/Documentation/OwnerRule.md b/Docs/Documentation/OwnerRule.md deleted file mode 100644 index db98eb455..000000000 --- a/Docs/Documentation/OwnerRule.md +++ /dev/null @@ -1,86 +0,0 @@ -Owner Rule -============= - -Setup ---------------- - -SimpleRbacAuthorize will pick and use specific Rule classes (implementing the \CakeDC\Users\Auth\Rules\Rule interface). -You only need to create your own permission rules, implement the required interface and pass the instance in the -'allowed' param. - -We provide an AbstractRule you could extend too, in case you want to use rules accessing the database and using the -default table associated to the current controller. - -Owner rule configuration ------------------ - -The Owner rule can be configured to use the following options -```php - //field in the owned table matching the user_id - 'ownerForeignKey' => 'user_id', - /* - * request key type to retrieve the table id, could be "params", "query", "data" to locate the table id - * example: - * yoursite.com/controller/action/XXX would be - * tableKeyType => 'params', 'tableIdParamsKey' => 'pass.0' - * yoursite.com/controlerr/action?post_id=XXX would be - * tableKeyType => 'query', 'tableIdParamsKey' => 'post_id' - * yoursite.com/controller/action [posted form with a field named post_id] would be - * tableKeyType => 'data', 'tableIdParamsKey' => 'post_id' - */ - 'tableKeyType' => 'params', - // request->params key path to retrieve the owned table id - 'tableIdParamsKey' => 'pass.0', - /* define table to use or pick it from controller name defaults if null - * if null, table used will be based on current controller's default table - * if string, TableRegistry::get will be used - * if Table, the table object will be used - */ - 'table' => null, - 'conditions' => [], -``` - -Example: - -(in your permissions.php file) -```php -[ - 'role' => ['user'], - 'controller' => ['Posts'], - 'action' => ['edit'], - 'allowed' => new Owner([ - 'ownerForeignKey' => 'owner_id', - ]), -], -``` - -In this example, action `/posts/edit/55` will be allowed if: - * The user is logged in - * The user role is 'user' - * There is a post in posts table with id 55 - * The 'owner_id' field in the post matches the user id - -Checking ownership in belongsToMany associations ------------------ - -Let's say you have users, posts, posts_users tables, and Posts belongsToMany Users, -you could check the ownership configuring the Owner rule: -```php -[ - 'role' => ['user'], - 'controller' => ['Posts'], - 'action' => ['edit'], - 'allowed' => new Owner([ - 'table' => 'PostsUsers', - 'id' => 'post_id', - 'ownerForeignKey' => 'owner_id' - ]), -], -``` - -In this example, action `/posts/edit/55` will be allowed if: - * The user is logged in - * The user role is 'user' - * There is a row in posts_users table matching - * 'owner_id' = user id - * 'post_id' = 55 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/SimpleRbacAuthorize.md b/Docs/Documentation/SimpleRbacAuthorize.md deleted file mode 100644 index fbcda06e1..000000000 --- a/Docs/Documentation/SimpleRbacAuthorize.md +++ /dev/null @@ -1,100 +0,0 @@ -SimpleRbacAuthorize -============= - -Setup ---------------- - -SimpleRbacAuthorize is configured by default, but you can customize the way it works easily - -Example, in your AppController, you can configure the way the SimpleRbac works by setting the options: - -```php -$config['Auth']['authorize']['CakeDC/Users.SimpleRbac'] = [ - //autoload permissions.php - 'autoload_config' => 'permissions', - //role field in the Users table - 'role_field' => 'role', - //default role, used in new users registered and also as role matcher when no role is available - 'default_role' => 'user', - /* - * This is a quick roles-permissions implementation - * Rules are evaluated top-down, first matching rule will apply - * Each line define - * [ - * 'role' => 'admin', - * 'plugin', (optional, default = null) - * 'prefix', (optional, default = null) - * 'extension', (optional, default = null) - * 'controller', - * 'action', - * 'allowed' (optional, default = true) - * ] - * You could use '*' to match anything - * Suggestion: put your rules into a specific config file - */ - 'permissions' => [], // you could set an array of permissions or load them using a file 'autoload_config' - ]; -``` - -This is the default configuration, based on it the Authorize object will first check your ```config/permissions.php``` -file and load the permissions using the configuration key ```Users.SimpleRbac.permissions```, there is an -example file you can copy into your ```config/permissions.php``` under the Plugin's config directory. - -If you don't want to use a file for configuring the permissions, you just need to tweak the configuration and set -```'autoload_config' => false,``` then define all your rules in AppController (not a good practice as the rules -tend to grow over time). - -The Users Plugin will use the field ```role_field``` in the Users Table to match the role of the user and -check if there is a rule allowing him to access the url. - -The ```default_role``` will be used to set the role of the registered users by default. - -Permission rules syntax ------------------ - -* Rules are evaluated top-down, first matching rule will apply -* Each rule is defined: -```php -[ - 'role' => 'REQUIRED_NAME_OF_THE_ROLE_OR_[]_OR_*', - 'prefix' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL', - 'extension' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL', - 'plugin' => 'OPTIONAL_NAME_OF_THE_PLUGIN_OR_[]_OR_*_DEFAULT_NULL', - 'controller' => 'REQUIRED_NAME_OF_THE_CONTROLLER_OR_[]_OR_*' - 'action' => 'REQUIRED_NAME_OF_ACTION_OR_[]_OR_*', - 'allowed' => 'OPTIONAL_BOOLEAN_OR_CALLABLE_OR_INSTANCE_OF_RULE_DEFAULT_TRUE' -] -``` -* If no rule allowed = true is matched for a given user role and url, default return value will be false -* Note for Superadmin access (permission to access ALL THE THINGS in your app) there is a specific Authorize Object provided - -Permission Callbacks ------------------ -You could use a callback in your 'allowed' to process complex authentication, like - - ownership - - permissions stored in your database - - permission based on an external service API call - -Example *ownership* callback, to allow users to edit their own Posts: - -```php - 'allowed' => function (array $user, $role, Request $request) { - $postId = Hash::get($request->params, 'pass.0'); - $post = TableRegistry::get('Posts')->get($postId); - $userId = Hash::get($user, 'id'); - if (!empty($post->user_id) && !empty($userId)) { - return $post->user_id === $userId; - } - return false; - } -``` - -Permission Rules ----------------- -You could use an instance of the \CakeDC\Users\Auth\Rules\Rule interface to reuse your custom rules -Examples: -```php -'allowed' => new Owner() //will pick by default the post id from the first pass param -``` -Check the [Owner Rule](OwnerRule.md) documentation for more details - diff --git a/Docs/Documentation/SocialAuthenticate.md b/Docs/Documentation/SocialAuthenticate.md deleted file mode 100644 index 602611076..000000000 --- a/Docs/Documentation/SocialAuthenticate.md +++ /dev/null @@ -1,68 +0,0 @@ -SocialAuthenticate -============= - -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. - -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. - 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/SuperuserAuthorize.md b/Docs/Documentation/SuperuserAuthorize.md deleted file mode 100644 index 7db2cb042..000000000 --- a/Docs/Documentation/SuperuserAuthorize.md +++ /dev/null @@ -1,18 +0,0 @@ -SuperuserAuthorize -============= - -Setup ---------------- - -SuperuserAuthorize is here to provide full permissions to specific "SUPER" users in your app. - -```php -$config['Auth']['authorize']['Users.Superuser'] = [ - //superuser field in the Users table - 'superuser_field' => 'is_superuser', - ]; -``` - -If the current user 'superuser_field' is true, he'll get full permissions in your app. - -Note if you don't have superusers, you can disable the SuperuserAuthorize in AuthComponent initialization \ No newline at end of file diff --git a/Docs/Documentation/Translations.md b/Docs/Documentation/Translations.md index f6d98f83d..d9cc74def 100644 --- a/Docs/Documentation/Translations.md +++ b/Docs/Documentation/Translations.md @@ -7,7 +7,12 @@ The Plugin is translated into several languages: * Spanish (es) by @florenciohernandez * Brazillian Portuguese (pt_BR) by @andtxr * French (fr_FR) by @jtraulle +* Polish (pl) by @joulbex +* Hungarian (hu_HU) by @rrd108 +* Italian (it) by @arturmamedov +* 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/UserHelper.md b/Docs/Documentation/UserHelper.md index 2191f4c1f..920f37f98 100644 --- a/Docs/Documentation/UserHelper.md +++ b/Docs/Documentation/UserHelper.md @@ -30,6 +30,20 @@ echo $this->User->socialLogin($provider); //provider is 'facebook', 'twitter', e 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. +Connect Social Account +----------------- + +You can use the helper included with the plugin to create some 'Connect with Facebook/Twitter' buttons: + +In templates, call socialConnectLinkList method to get links for all social providers enabled +```php +echo $this->User->socialConnectLinkList($user->social_accounts); +``` + +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. + +The user must be allowed to access the urls "/link-social/[provider]" and "/callback-link-social/[provider]". + Logout link ----------------- 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 a67b3107b..4223b0e82 100644 --- a/Docs/Home.md +++ b/Docs/Home.md @@ -7,22 +7,242 @@ The plugin is thought as a base to extend your app specific users controller and That it works out of the box doesn't mean it is thought to be used exactly like it is but to provide you a kick start. -Requirements ------------- - -* CakePHP 3.1.* -* PHP 5.4.16+ - Documentation ------------- * [Overview](Documentation/Overview.md) * [Installation](Documentation/Installation.md) * [Configuration](Documentation/Configuration.md) -* [SimpleRbacAuthorize](Documentation/SimpleRbacAuthorize.md) -* [SuperuserAuthorize](Documentation/SuperuserAuthorize.md) -* [SocialAuthenticate](Documentation/SocialAuthenticate.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) +* [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/LICENSE.txt b/LICENSE.txt index 6ee7e8fc0..5448b2520 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,11 +1,11 @@ The MIT License -Copyright 2009-2014 +Copyright 2009-2018 Cake Development Corporation 1785 E. Sahara Avenue, Suite 490-423 Las Vegas, Nevada 89104 Phone: +1 702 425 5085 -http://cakedc.com +https://www.cakedc.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -23,4 +23,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index f369f656c..fbd5af5f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ CakeDC Users Plugin =================== -[![Bake 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) [![License](https://poser.pugx.org/CakeDC/users/license.svg)](https://packagist.org/packages/CakeDC/users) @@ -11,24 +12,31 @@ Versions and branches | CakePHP | CakeDC Users Plugin | Tag | Notes | | :-------------: | :------------------------: | :--: | :---- | -| 2.x | [2.x](https://github.com/cakedc/users/tree/2.x) | 2.1.2 | Note CakePHP 2.7 is currently not supported, we are working on it now | -| 3.2+ | [master](https://github.com/cakedc/users/tree/master) | 3.x | stable | -| 3.2+ | [develop](https://github.com/cakedc/users/tree/develop) | 3.x | unstable | -| 3.0 | [3.0.x](https://github.com/cakedc/users/tree/3.0.x) | 3.0.0 | stability is beta, but pretty stable now | -| 3.1 | [3.1.x](https://github.com/cakedc/users/tree/3.1.x) | 3.1.0 | stability is beta, but pretty stable now | +| ^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) -* Simple RBAC -* Remember me (Cookie) +* Simple RBAC via https://github.com/CakeDC/auth +* 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 @@ -44,8 +52,8 @@ Another decision made was limiting the plugin dependencies on other packages as Requirements ------------ -* CakePHP 3.2.9+ -* PHP 5.5.9+ +* CakePHP 4.0+ +* PHP 7.2+ Documentation ------------- @@ -57,16 +65,16 @@ Support For bugs and feature requests, please use the [issues](https://github.com/CakeDC/users/issues) section of this repository. -Commercial support is also available, [contact us](http://cakedc.com/contact) for more information. +Commercial support is also available, [contact us](https://www.cakedc.com/contact) for more information. Contributing ------------ -This repository follows the [CakeDC Plugin Standard](http://cakedc.com/plugin-standard). If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our [Contribution Guidelines](http://cakedc.com/contribution-guidelines) for detailed instructions. +This repository follows the [CakeDC Plugin Standard](https://www.cakedc.com/plugin-standard). If you'd like to contribute new features, enhancements or bug fixes to the plugin, please read our [Contribution Guidelines](https://www.cakedc.com/contribution-guidelines) for detailed instructions. License ------- -Copyright 2015 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 362d13241..3a94372d7 100644 --- a/composer.json +++ b/composer.json @@ -26,25 +26,40 @@ "issues": "https://github.com/CakeDC/users/issues", "source": "https://github.com/CakeDC/users" }, + "minimum-stability": "dev", + "prefer-stable": true, "require": { - "cakephp/cakephp": ">=3.2.9 <4.0.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": "@stable", - "cakephp/cakephp-codesniffer": "^2.0", + "phpunit/phpunit": "^9.5", "league/oauth2-facebook": "@stable", "league/oauth2-instagram": "@stable", "league/oauth2-google": "@stable", "league/oauth2-linkedin": "@stable", - "google/recaptcha": "@stable" + "luchianenco/oauth2-amazon": "^1.1", + "google/recaptcha": "@stable", + "robthree/twofactorauth": "^1.6", + "yubico/u2flib-server": "^1.0", + "php-coveralls/php-coveralls": "^2.1", + "league/oauth1-client": "^1.7", + "cakephp/cakephp-codesniffer": "^4.0", + "web-auth/webauthn-lib": "v3.3.x-dev" }, "suggest": { "league/oauth1-client": "Provides Social Authentication with Twitter", "league/oauth2-facebook": "Provides Social Authentication with Facebook", "league/oauth2-instagram": "Provides Social Authentication with Instagram", "league/oauth2-google": "Provides Social Authentication with Google+", + "luchianenco/oauth2-amazon": "Provides Social Authentication with Amazon", "league/oauth2-linkedin": "Provides Social Authentication with LinkedIn", - "google/recaptcha": "Provides reCAPTCHA validation for registration form" + "google/recaptcha": "Provides reCAPTCHA validation for registration form", + "robthree/twofactorauth": "Provides Google Authenticator functionality", + "cakephp/authorization": "Provide authorization for users" }, "autoload": { "psr-4": { @@ -54,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 b9e09fc69..2379afa67 100644 --- a/config/Migrations/20150513201111_initial.php +++ b/config/Migrations/20150513201111_initial.php @@ -1,15 +1,15 @@ table('users'); + /** + * Limiting secret field to 32 chars + * @see https://en.wikipedia.org/wiki/Google_Authenticator#Technical_description + */ + $table->addColumn('secret', 'string', [ + 'after' => 'activation_date', + 'default' => null, + 'limit' => 32, + 'null' => true, + ]); + $table->addColumn('secret_verified', 'boolean', [ + 'after' => 'secret', + 'default' => null, + 'null' => true, + ]); + $table->update(); + } +} diff --git a/config/Migrations/20190208174112_AddAdditionalDataToUsers.php b/config/Migrations/20190208174112_AddAdditionalDataToUsers.php new file mode 100644 index 000000000..731be0fc5 --- /dev/null +++ b/config/Migrations/20190208174112_AddAdditionalDataToUsers.php @@ -0,0 +1,32 @@ +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 new file mode 100644 index 000000000..9c437b3c1 Binary files /dev/null and b/config/Migrations/schema-dump-default.lock differ diff --git a/config/bootstrap.php b/config/bootstrap.php index 4592fb2e1..d78acc8b7 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -1,19 +1,16 @@ each(function ($file) { Configure::load($file); }); +UsersUrl::setupConfigUrls(); -TableRegistry::config('Users', ['className' => Configure::read('Users.table')]); -TableRegistry::config('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 0f8451259..5c52467fb 100644 --- a/config/permissions.php +++ b/config/permissions.php @@ -1,11 +1,11 @@ ['edit', 'delete'], 'allowed' => function(array $user, $role, Request $request) { $postId = Hash::get($request->params, 'pass.0'); - $post = TableRegistry::get('Posts')->get($postId); + $post = TableRegistry::getTableLocator()->get('Posts')->get($postId); $userId = Hash::get($user, 'id'); if (!empty($post->user_id) && !empty($userId)) { return $post->user_id === $userId; @@ -50,30 +50,95 @@ */ return [ - 'Users.SimpleRbac.permissions' => [ + 'CakeDC/Auth.permissions' => [ + //all bypass [ - 'role' => '*', + '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', + 'prefix' => '*', + 'extension' => '*', + 'plugin' => '*', 'controller' => '*', 'action' => '*', ], + //specific actions allowed for the all roles in Users plugin [ - 'role' => 'user', + 'role' => '*', 'plugin' => 'CakeDC/Users', 'controller' => 'Users', - 'action' => ['register', 'edit', 'view'], + 'action' => ['profile', 'logout', 'linkSocial', 'callbackLinkSocial'], ], [ - 'role' => 'user', + 'role' => '*', 'plugin' => 'CakeDC/Users', 'controller' => 'Users', - 'action' => '*', - 'allowed' => false, + '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)) { + return $userId === $user['id']; + } + + return false; + } ], + //all roles allowed to Pages/display [ - 'role' => ['user'], - 'controller' => ['Pages'], - 'action' => ['other', 'display'], - 'allowed' => true, + 'role' => '*', + 'controller' => 'Pages', + 'action' => 'display', + ], + [ + 'role' => '*', + 'plugin' => 'DebugKit', + 'controller' => '*', + 'action' => '*', + 'bypassAuth' => true, ], - ]]; + ] +]; diff --git a/config/routes.php b/config/routes.php index 6c4331798..81ac70cc1 100644 --- a/config/routes.php +++ b/config/routes.php @@ -1,31 +1,52 @@ connect('/accounts/validate/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => 'validate' +]); +// Google Authenticator related routes +if (Configure::read('OneTimePasswordAuthenticator.login')) { + $routes->connect('/verify', UsersUrl::actionRouteParams('verify')); + + $routes->connect('/resetOneTimePasswordAuthenticator', UsersUrl::actionRouteParams('resetOneTimePasswordAuthenticator')); +} + +$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')); -Router::plugin('CakeDC/Users', ['path' => '/users'], function ($routes) { - $routes->fallbacks('DashedRoute'); +$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')))] + ); }); +} -Router::connect('/auth/twitter', [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'twitterLogin', - 'provider' => 'twitter' - ]); -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']); \ No newline at end of file +$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 7d2662ec1..23dfeb9d1 100644 --- a/config/users.php +++ b/config/users.php @@ -1,157 +1,317 @@ getHost(); + if ($fullBaseHost) { + $allowedRedirectHosts[] = $fullBaseHost; + } + } catch (Exception $ex) { + Log::warning('Invalid host from App.fullBasedUrl in CakeDC/Users configuration: ' . $ex->getMessage()); + } +} $config = [ 'Users' => [ - //Table used to manage users + // Table used to manage users 'table' => 'CakeDC/Users.Users', - //configure Auth component - 'auth' => true, - //Password Hasher + // Controller used to manage users plugin features & actions + 'controller' => 'CakeDC/Users.Users', + // Password Hasher 'passwordHasher' => '\Cake\Auth\DefaultPasswordHasher', - //token expiration, 1 hour + 'middlewareQueueLoader' => \CakeDC\Users\Loader\MiddlewareQueueLoader::class, + // token expiration, 1 hour 'Token' => ['expiration' => 3600], 'Email' => [ - //determines if the user should include email + // determines if the user should include email 'required' => true, - //determines if registration workflow includes email validation + // determines if registration workflow includes email validation 'validate' => true, ], 'Registration' => [ - //determines if the register is enabled + // determines if the register is enabled 'active' => true, - //determines if the reCaptcha is enabled for registration + // determines if the reCaptcha is enabled for registration 'reCaptcha' => true, - //allow a logged in user to access the registration form + // allow a logged in user to access the registration form 'allowLoggedIn' => false, //ensure user is active (confirmed email) to reset his password - 'ensureActive' => false + 'ensureActive' => false, + // default role name used in registration + 'defaultRole' => 'user', + // show verbose error to users + 'showVerboseError' => false, ], 'reCaptcha' => [ - //reCaptcha key goes here + // reCaptcha key goes here 'key' => null, - //reCaptcha secret + // reCaptcha secret 'secret' => null, - //use reCaptcha in registration + // use reCaptcha in registration 'registration' => false, - //use reCaptcha in login, valid values are false, true + // use reCaptcha in login, valid values are false, true 'login' => false, ], 'Tos' => [ - //determines if the user should include tos accepted + // determines if the user should include tos accepted 'required' => true, ], 'Social' => [ - //enable social login + // enable social login 'login' => false, ], 'Profile' => [ - //Allow view other users profiles + // Allow view other users profiles 'viewOthers' => true, - 'route' => ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile'], ], 'Key' => [ 'Session' => [ - //session key to store the social auth data + // session key to store the social auth data 'social' => 'Users.social', - //userId key used in reset password workflow + // userId key used in reset password workflow 'resetPasswordUserId' => 'Users.resetPasswordUserId', ], - //form key to store the social auth data + // form key to store the social auth data 'Form' => [ - 'social' => 'social' + 'social' => 'social', ], 'Data' => [ - //data key to store the users email + // data key to store the users email 'email' => 'email', - //data key to store email coming from social networks + // data key to store email coming from social networks 'socialEmail' => 'info.email', - //data key to check if the remember me option is enabled + // data key to check if the remember me option is enabled 'rememberMe' => 'remember_me', ], ], - //Avatar placeholder + // Avatar placeholder 'Avatar' => ['placeholder' => 'CakeDC/Users.avatar_placeholder.png'], 'RememberMe' => [ - //configure Remember Me component + // configure Remember Me component 'active' => true, + 'checked' => true, '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, + ], + '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 + // 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' => null + 'Authentication' => [ + 'serviceLoader' => \CakeDC\Users\Loader\AuthenticationServiceLoader::class, + ], + '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', + ], + ], + ], + 'Authorization' => [ + 'enable' => true, + 'serviceLoader' => \CakeDC\Users\Loader\AuthorizationServiceLoader::class, ], - 'authenticate' => [ - 'all' => [ - 'finder' => 'auth', + 'AuthorizationMiddleware' => [ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', ], - 'CakeDC/Users.ApiKey', - 'CakeDC/Users.RememberMe', - 'Form', ], - 'authorize' => [ - 'CakeDC/Users.Superuser', - 'CakeDC/Users.SimpleRbac', + '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.5', + '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', + '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/resources/locales/es/users.po b/resources/locales/es/users.po new file mode 100644 index 000000000..96df31fdd --- /dev/null +++ b/resources/locales/es/users.po @@ -0,0 +1,839 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +msgid "" +msgstr "" +"Project-Id-Version: CakeDC Users\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" +"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.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: es\n" + +#: Auth/SocialAuthenticate.php:456 +msgid "Provider cannot be empty" +msgstr "El proveedor no puede ser vacío" + +#: Controller/SocialAccountsController.php:52 +msgid "Account validated successfully" +msgstr "Cuenta validada correctamente" + +#: Controller/SocialAccountsController.php:54 +msgid "Account could not be validated" +msgstr "No se pudo validar la cuenta" + +#: Controller/SocialAccountsController.php:57 +msgid "Invalid token and/or social account" +msgstr "Token o cuenta social incorrecta" + +#: Controller/SocialAccountsController.php:59;87 +msgid "Social Account already active" +msgstr "Cuenta social ya activa" + +#: Controller/SocialAccountsController.php:61 +msgid "Social Account could not be validated" +msgstr "No se pudo validar la cuenta social " + +#: Controller/SocialAccountsController.php:80 +msgid "Email sent successfully" +msgstr "Email enviado correctamente" + +#: Controller/SocialAccountsController.php:82 +msgid "Email could not be sent" +msgstr "No se puede enviar el email" + +#: Controller/SocialAccountsController.php:85 +msgid "Invalid account" +msgstr "Cuenta inválida" + +#: Controller/SocialAccountsController.php:89 +msgid "Email could not be resent" +msgstr "No se puede reenviar el Email" + +#: 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: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/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:109 Template/Users/social_email.ctp:16 +msgid "Please enter your email" +msgstr "Por favor, introduce tu email" + +#: Controller/Traits/LoginTrait.php:120 +msgid "" +"Your user has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"El usuario no se ha validado todavía. Instrucciones enviadas a tu bandeja de " +"entrada" + +#: Controller/Traits/LoginTrait.php:125 +msgid "" +"Your social account has not been validated yet. Please check your inbox for " +"instructions" +msgstr "" +"La cuenta social no se ha validado todavía. Instrucciones enviadas a tu " +"bandeja de entrada" + +#: 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:191 +msgid "You are already logged in" +msgstr "Ya has iniciado sesión" + +#: 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:363 +msgid "You've successfully logged out" +msgstr "Sesión finalizada correctamente" + +#: Controller/Traits/PasswordManagementTrait.php:49;82 +#: Controller/Traits/ProfileTrait.php:50 +msgid "User was not found" +msgstr "Usuario no encontrado" + +#: 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:74 +msgid "Password has been changed successfully" +msgstr "Contraseña cambiada correctamente" + +#: Controller/Traits/PasswordManagementTrait.php:84 +msgid "{0}" +msgstr "{0}" + +#: 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: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:136 +#: Controller/Traits/UserValidationTrait.php:107 +msgid "User {0} was not found" +msgstr "Usuario {0} no encontrado" + +#: Controller/Traits/PasswordManagementTrait.php:138 +msgid "The user is not active" +msgstr "El usuario no está activo" + +#: 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/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: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:89 +msgid "The user could not be saved" +msgstr "No se ha podido guardar el usuario" + +#: 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:125 +msgid "Please validate your account before log in" +msgstr "Por favor, valida tu cuenta antes de iniciar sesión" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "El {0} ha sido guardado" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "El {0} no ha podido guardarse" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "El {0} ha sido eliminado" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "El {0} no ha podido eliminarse" + +#: 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:43 +msgid "User account validated successfully" +msgstr "Cuenta de usuario validada correctamente" + +#: 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:48 +msgid "User already active" +msgstr "Usuario ya activo" + +#: 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:62 +msgid "Reset password token could not be validated" +msgstr "Restablecimiento del token de contraseña no pudo validarse" + +#: Controller/Traits/UserValidationTrait.php:66 +msgid "Invalid validation type" +msgstr "Tipo de validación inválido" + +#: 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:71 +msgid "Token already expired" +msgstr "Token ya expirado" + +#: 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:109 +msgid "User {0} is already active" +msgstr "El usuario {0} ya está activo" + +#: Mailer/UsersMailer.php:34 +msgid "Your account validation link" +msgstr "Enlace para validar tu cuenta" + +#: Mailer/UsersMailer.php:52 +msgid "{0}Your reset password link" +msgstr "{0} Enlace para restablecer tu contraseña" + +#: Mailer/UsersMailer.php:75 +msgid "{0}Your social account validation link" +msgstr "{0} Enlace de validación de tu cuenta social" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "La fecha de expiración del Token no puede estar vacía" + +#: Model/Behavior/PasswordBehavior.php:56;117 +msgid "User not found" +msgstr "Usuario no encontrado" + +#: 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:67 +msgid "User not active" +msgstr "El usuario no está activo" + +#: Model/Behavior/PasswordBehavior.php:122 +msgid "The current password does not match" +msgstr "La contraseña actual no coincide" + +#: 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: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:93 +msgid "Token has already expired user with no token" +msgstr "El token ha expirado usuario sin token" + +#: Model/Behavior/SocialAccountBehavior.php:103;130 +msgid "Account already validated" +msgstr "Cuenta ya activada" + +#: 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: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:121 +msgid "Email not present" +msgstr "No se encuentra el email" + +#: 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:173 +msgid "Username already exists" +msgstr "Nombre de usuario ya existente" + +#: Model/Table/UsersTable.php:179 +msgid "Email already exists" +msgstr "Email ya existente" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Utilidades para CakeDC Users Plugin" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Activar un usuario específico" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "Añadir un nuevo superadmin" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "Añadir un nuevo usuario" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Cambiar el rol de un usuario específico" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Desactivar un usuario" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Borrar un usuario" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Restablecer la contraseña vía email" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Restablecer la contraseña de todos los usuarios" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Restablecer la contraseña de un usuario concreto" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Por favor, introduce una contraseña." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Contraseña cambiada para todos los usuarios" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nueva contraseña: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Por favor introduce el nombre de usuario." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Contraseña cambiada para el usuario: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Por favor introduce el rol." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Rol cambiado para el usuario: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Nuevo rol: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Usuario activado: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Usuario desactivado: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Por favor introduce el nombre de usuario o email." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +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: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: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:369 +msgid "The user {0} was deleted successfully" +msgstr "El usuario {0} se borró correctamente" + +#: 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 "Hola {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +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 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: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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Activa tu acceso social aquí" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Activa tu cuenta aquí" + +#: 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 "Por favor copia la siguiente dirección en tu navegador {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 "" +"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: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:27 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Listar Usuarios" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:17 +msgid "Add User" +msgstr "Añadir Usuario" + +#: 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: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: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:27 +msgid "Last name" +msgstr "Apellidos" + +#: Template/Users/add.ctp:30 Template/Users/edit.ctp:53 +#: 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:36 +#: Template/Users/request_reset_password.ctp:8 +#: Template/Users/resend_token_validation.ctp:20 +#: Template/Users/social_email.ctp:19 +msgid "Submit" +msgstr "Enviar" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Por favor introduce la nueva contraseña" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Contraseña actual" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nueva contraseña" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:24 +msgid "Confirm password" +msgstr "Confirmar contraseña" + +#: Template/Users/edit.ctp:21 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Eliminar" + +#: 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}?" + +#: Template/Users/edit.ctp:33 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Editar Usuario" + +#: Template/Users/edit.ctp:39 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Token" + +#: Template/Users/edit.ctp:41 +msgid "Token expires" +msgstr "Token caduca" + +#: Template/Users/edit.ctp:44 +msgid "API token" +msgstr "Api Token" + +#: Template/Users/edit.ctp:47 +msgid "Activation date" +msgstr "Fecha de activación" + +#: Template/Users/edit.ctp:50 +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 +msgid "View" +msgstr "Ver" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Cambiar contraseña" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Editar" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "anterior" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "siguiente" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Por favor introduce tu usuario y contraseña" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Recuérdame" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Registrarse" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Cambiar contraseña" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Iniciar sesión" + +#: Template/Users/profile.ctp:21 View/Helper/UserHelper.php:54 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +#, fuzzy +msgid "Change Password" +msgstr "Cambiar contraseña" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Cuentas sociales" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Avatar" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Proveedor" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Enlace" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Enlace a {0}" + +#: Template/Users/register.ctp:29 +msgid "Accept TOS conditions?" +msgstr "¿Aceptas las condiciones del servicios?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Por favor introduce tu email para restablecer tu contraseña" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Reenviar email de validación" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Email o usuario" + +#: 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:24 +msgid "New User" +msgstr "Nuevo Usuario" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +#, fuzzy +msgid "First Name" +msgstr "Nombre" + +#: Template/Users/view.ctp:39 +#, fuzzy +msgid "Last Name" +msgstr "Apellidos" + +#: 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:53 +#, fuzzy +msgid "Token Expires" +msgstr "Token caduca" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "Fecha de activación" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Fecha Tos" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Creado" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Modificado" + +#: View/Helper/UserHelper.php:45 +msgid "Sign in with" +msgstr "Iniciar sesión con" + +#: 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 +msgid "Logout" +msgstr "Salir" + +#: View/Helper/UserHelper.php:123 +msgid "Welcome, {0}" +msgstr "Bienvenido/a, {0}" + +#: 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" + +#: View/Helper/UserHelper.php:205 +#, fuzzy +msgid "btn btn-social btn-{0}" +msgstr "btn btn-social btn-{0}" + +#: View/Helper/UserHelper.php:211 +msgid "Connected with {0}" +msgstr "Conectado con {0}" + +#: 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 59% rename from src/Locale/fr_FR/Users.po rename to resources/locales/fr_FR/users.po index f998c4787..ff98d4585 100644 --- a/src/Locale/fr_FR/Users.po +++ b/resources/locales/fr_FR/users.po @@ -1,469 +1,422 @@ # 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: 2016-08-18 12:28+0200\n" -"PO-Revision-Date: 2016-08-18 14:08+0200\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" "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.8\n" -"Last-Translator: Jean Traullé \n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" "Language: fr_FR\n" -#: Auth/ApiKeyAuthenticate.php:55 -msgid "Type {0} is not valid" -msgstr "Le type {0} est invalide" - -#: Auth/ApiKeyAuthenticate.php:59 -msgid "Type {0} has no associated callable" -msgstr "Le type {0} n'a aucune fonction de rappel associée" - -#: Auth/ApiKeyAuthenticate.php:68 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL est requis pour ApiKey Authentication" - -#: Auth/SimpleRbacAuthorize.php:141 -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:410 -msgid "Provider cannot be empty" -msgstr "Le fournisseur ne peut pas être vide" - -#: Auth/Rules/AbstractRule.php:77 -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}" - -#: 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;86 +#: 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:79 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Courriel envoyé avec succès" -#: Controller/SocialAccountsController.php:81 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "Le courriel n'a pas pu être envoyé" -#: Controller/SocialAccountsController.php:84 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Compte invalide" -#: Controller/SocialAccountsController.php:88 +#: 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:157 -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:95 -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:100 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:106 -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:108 -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:157 Controller/Traits/RegisterTrait.php:73 -msgid "Invalid reCaptcha" -msgstr "La validation reCaptcha a échoué" +#: 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:165 -msgid "You are already logged in" -msgstr "Vous êtes déjà identifié" +#: 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:209 -msgid "Username or password is incorrect" -msgstr "Le nom d'utilisateur ou le mot de passe est incorrect" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "" -#: Controller/Traits/LoginTrait.php:230 -msgid "You've successfully logged out" -msgstr "Vous avez été correctement déconnecté" +#: 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:53;60;68 +#: 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:57 +#: 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:64 -#: Controller/Traits/ProfileTrait.php:49 -msgid "User was not found" -msgstr "L'utilisateur n'a pas été trouvé" - -#: Controller/Traits/PasswordManagementTrait.php:66 -msgid "The current password does not match" -msgstr "Le mot de passe actuel ne correspond pas" - -#: Controller/Traits/PasswordManagementTrait.php:108 +#: 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:111 Shell/UsersShell.php:266 +#: 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:116 -#: Controller/Traits/UserValidationTrait.php:98 +#: 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:118 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "L'utilisateur n'est pas actif" -#: Controller/Traits/PasswordManagementTrait.php:120 -#: Controller/Traits/UserValidationTrait.php:94;102 +#: 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:52 +#: 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:79 +#: 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:111 +#: 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:113 +#: 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;105 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "Le {0} a été sauvegardé" -#: Controller/Traits/SimpleCrudTrait.php:79;108 +#: 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:128 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "Le {0} a été supprimé" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: 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:57 +#: 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:61 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Type de validation invalide" -#: Controller/Traits/UserValidationTrait.php:64 +#: 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:66 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Le jeton a déjà expiré" -#: Controller/Traits/UserValidationTrait.php:92 +#: 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:100 +#: 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;115 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Utilisateur non trouvé" -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:110 +#: 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:120 -msgid "The old password does not match" -msgstr "L'ancien mot de passe ne correspond pas" +#: 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: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:88 +#: 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:91 +#: 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/SocialAccountBehavior.php:101;128 +#: 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é" -#: Model/Behavior/SocialAccountBehavior.php:104;131 +#: Model/Behavior/SocialAccountBehavior.php:105;132 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:97 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Courriel non présent" -#: Model/Table/UsersTable.php:81 +#: 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:159 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Le nom d'utilisateur existe déjà" -#: Model/Table/UsersTable.php:165 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Le courriel existe déjà" -#: Model/Table/UsersTable.php:197 -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:97 -msgid "User added:" -msgstr "Utilisateur ajouté :" - -#: Shell/UsersShell.php:98;126 -msgid "Id: {0}" -msgstr "Identifiant : {0}" - -#: Shell/UsersShell.php:99;127 -msgid "Username: {0}" -msgstr "Nom d'utilisateur : {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Email: {0}" -msgstr "Courriel : {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Password: {0}" -msgstr "Mot de passe : {0}" - -#: Shell/UsersShell.php:125 -msgid "Superuser added:" -msgstr "Super-utilisateur ajouté :" - -#: Shell/UsersShell.php:131 -msgid "Superuser could not be added:" -msgstr "Le super-utilisateur n'a pas pu être ajouté :" - -#: Shell/UsersShell.php:134 -msgid "Field: {0} Error: {1}" -msgstr "Champ : {0} Erreur : {1}" - -#: Shell/UsersShell.php:152;178 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Veuillez saisir un mot de passe." -#: Shell/UsersShell.php:156 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Mot de passe changé pour tous les utilisateurs" -#: Shell/UsersShell.php:157;185 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nouveau mot de passe : {0}" -#: Shell/UsersShell.php:175;203;281;321 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Veuillez saisir un nom d'utilisateur." -#: Shell/UsersShell.php:184 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Mot de passe changé pour l'utilisateur : {0}" -#: Shell/UsersShell.php:206 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Veuillez saisir un rôle." -#: Shell/UsersShell.php:212 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Rôle changé pour l'utilisateur : {0}" -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Nouveau rôle : {0}" -#: Shell/UsersShell.php:228 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "L'utilisateur a été activé : {0}" -#: Shell/UsersShell.php:243 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "L'utilisateur a été désactivé : {0]" -#: Shell/UsersShell.php:255 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Veuillez saisir un nom d'utilisateur ou un courriel." -#: Shell/UsersShell.php:263 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" @@ -471,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:300 +#: 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:329 +#: 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:331 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "L'utilisateur {0} a été supprimé avec succès" @@ -498,19 +489,21 @@ 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" @@ -522,14 +515,6 @@ msgstr "Activez votre identification sociale ici" msgid "Activate your account here" msgstr "Activez votre compte ici" -#: 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 "" -"Si le lien n'est pas correctement affiché, veuillez copier l'adresse " -"suivante dans votre navigateur web {0}" - #: Template/Email/text/reset_password.ctp:22 #: Template/Email/text/validation.ctp:22 msgid "Please copy the following address in your web browser {0}" @@ -543,27 +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:13 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:13;77 +#: 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:21 -#: Template/Users/view.ctp:17 +#: 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:22 -#: Template/Users/view.ctp:19 -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:32 Template/Users/change_password.ctp:17 -#: Template/Users/edit.ctp:42 Template/Users/register.ctp:32 +#: 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: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: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: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: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: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 @@ -578,25 +591,61 @@ msgstr "Veuillez entrer le nouveau mot de passe" msgid "Current password" msgstr "Mot de passe actuel" -#: Template/Users/edit.ctp:16 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:99 +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nouveau mot de passe" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Confirmer le mot de passe" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Supprimer" -#: Template/Users/edit.ctp:18 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:16;99 +#: 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:28 Template/Users/view.ctp:15 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Modifier l'utilisateur" +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Jeton" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Expiration du jeton" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "Jeton d'API" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Date d'activiation" + +#: 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:95 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Voir" @@ -604,7 +653,7 @@ msgstr "Voir" msgid "Change password" msgstr "Changer le mot de passe" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Modifier" @@ -624,47 +673,48 @@ msgstr "Merci de saisir votre nom d'utilisateur et votre mot de passe" msgid "Remember me" msgstr "Se souvenir de moi" +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "S'inscrire" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Réinitialiser le mot de passe" + #: Template/Users/login.ctp:48 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:27 Template/Users/view.ctp:28;68 -msgid "Username" -msgstr "Nom d'utilisateur" - -#: Template/Users/profile.ctp:29 Template/Users/view.ctp:30 -msgid "Email" -msgstr "Courriel" - -#: 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:70 +#: 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:67 +#: 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:25 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Accepter les CGU ?" @@ -680,99 +730,94 @@ msgstr "Réenvoyer le courriel de validation" msgid "Email or username" msgstr "Courriel ou nom d'utilisateur" -#: Template/Users/view.ctp:16 +#: 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:18 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Nouvel utilisateur" -#: Template/Users/view.ctp:26;65 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Identifiant" -#: Template/Users/view.ctp:32 +#: Template/Users/view.ctp:37 +#, fuzzy msgid "First Name" msgstr "Prénom" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Nom" -#: Template/Users/view.ctp:36;71 -msgid "Token" -msgstr "Jeton" +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nouveau rôle : {0}" -#: Template/Users/view.ctp:38 +#: Template/Users/view.ctp:45 +#, fuzzy msgid "Api Token" msgstr "Jeton d'API" -#: Template/Users/view.ctp:42;73 -msgid "Active" -msgstr "Actif" - -#: Template/Users/view.ctp:46;72 +#: Template/Users/view.ctp:53 +#, fuzzy msgid "Token Expires" msgstr "Expiration du jeton" -#: Template/Users/view.ctp:48 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Date d'activiation" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:57 +#, fuzzy msgid "Tos Date" msgstr "Date CGU" -#: Template/Users/view.ctp:52;75 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Créé le" -#: Template/Users/view.ctp:54;76 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Modifié le" -#: Template/Users/view.ctp:61 -msgid "Related Accounts" -msgstr "Comptes liés" - -#: Template/Users/view.ctp:66 -msgid "User Id" -msgstr "Identifiant utilisateur" - -#: Template/Users/view.ctp:69 -msgid "Reference" -msgstr "Référence" - -#: Template/Users/view.ctp:74 -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:90 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Se déconnecter" -#: View/Helper/UserHelper.php:139 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Bienvenue, {0}" -#: View/Helper/UserHelper.php:161 +#: 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:147 -msgid "This field is required" -msgstr "Ce champ est requis" +#: View/Helper/UserHelper.php:215 +#, fuzzy +msgid "Connected with {0}" +msgstr "Identifiant : {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/resources/locales/hu_HU/users.po b/resources/locales/hu_HU/users.po new file mode 100644 index 000000000..5bb53a404 --- /dev/null +++ b/resources/locales/hu_HU/users.po @@ -0,0 +1,814 @@ +# 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 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.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: hu_HU\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "A fiók sikeresen ellenőrizve" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "A fiókot nem tudtam ellenőrizni" + +#: 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:57;85 +msgid "Social Account already active" +msgstr "A közösségi fiók már aktív" + +#: 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:78 +msgid "Email sent successfully" +msgstr "Az email sikeresen elküldve" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Az emailt nem tudtuk elküldeni" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Érvénytelen fiók" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Az emailt nem lehet újraküldeni" + +#: 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:76 +msgid "Social account was associated." +msgstr "A közösségi fiók társítva." + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Sikeresen kijelentkeztél" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Először engedélyezd a Google Authenticatort." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "A felhasználót ne tudtuk hozzáadni:" + +#: 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/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:53;91 +#: Controller/Traits/ProfileTrait.php:53 +msgid "User was not found" +msgstr "A felhasználó nem található" + +#: 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:83 +msgid "Password has been changed successfully" +msgstr "A jelszó siekresen módosítva" + +#: 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: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:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "A {0} felhasználó nem található" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "A felhasználó nem aktív" + +#: 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:174 +msgid "Google Authenticator token was successfully reset" +msgstr "A Google Authenticator token sikeresen visszaállítva" + +#: Controller/Traits/ProfileTrait.php:57 +msgid "Not authorized, please login first" +msgstr "Még nem jelentkeztél be. Jelentkezz be" + +#: 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:75;99 +msgid "The user could not be saved" +msgstr "A felhasználót nem lehet menteni" + +#: 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:135 +msgid "Please validate your account before log in" +msgstr "Kérlek jitelesítsd a fiókodat mielőtt belépnél" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "A {0} mentve" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "A {0} nincs mentve" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "A {0} törölve" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "A {0} nem lett törölve" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "A fiók sikeresen ellenőrizve" + +#: 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:49 +msgid "User already active" +msgstr "A fiók már aktív" + +#: 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:63 +msgid "Reset password token could not be validated" +msgstr "A jelszó helyreállító token ellenőrzése sikertelen" + +#: Controller/Traits/UserValidationTrait.php:67 +msgid "Invalid validation type" +msgstr "Helytelen ellenőrzés típus" + +#: 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:76 +msgid "Token already expired" +msgstr "A token már lejárt" + +#: 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:118 +msgid "User {0} is already active" +msgstr "{0} felhasználó már aktív" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0} A jelszó helyreállítás linkje" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} A közösségi fiókod ellenőrzési linkje" + +#: 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" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "A közösségi fiók már csatlakoztatva van egy másik felhasználóhoz" + +#: Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "A hivatkozás nem lehet null" + +#: Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "A token lejárat nme lehet üres" + +#: 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 +msgid "User account already validated" +msgstr "A felhasználói fiók már ellenőrizve van" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "A felhasználó nem aktív" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "A jelenlegi jelszó nem stimmel" + +#: 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" + +#: Model/Behavior/RegisterBehavior.php:90 +msgid "User not found for the given token and email." +msgstr "Nem található felhasználó ehhez a tokenhez és emailhez." + +#: Model/Behavior/RegisterBehavior.php:93 +msgid "Token has already expired user with no token" +msgstr "A token lejárt felhasználó token nélkül" + +#: 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: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:83 +msgid "Unable to login user with reference {0}" +msgstr "Nem tudom belépni a usert a {0} hivatkozással" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "Hiányzó email" + +#: 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:171 +msgid "Username already exists" +msgstr "A felhasználónév már foglalt" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Az email már foglalt" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Eszközök a CakeDC Users Puginhez" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Egy bizonyos felhasználó aktiválása" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "Új superadmin hozzáadása tesztelési céllal" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "Új felhasználó" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Egy felhasználói szerep módosítása" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Egy felhasználó deaktiválása" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Egy felhasználó törlése" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Jelszó helyreállítás emailben" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Minden feéhasználó jelszavának visszaállítása" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Egy felhasználó jelszavának visszaállítása" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Adj meg egy jelszót" + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Mindne felhasználó jelszava módosítva" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Új jelszó: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Adj meg egy felhasználónevet." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "A {0} felhasználó jelszava megváltoztatva" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Adj meg egy szerepet." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "{0} felhasználó szerepe módosítva" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Új szerep: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "{0} felhasználó aktiválva" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "{0} felhasználó deaktiválva" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Adj meg egy felhasználónevet vagy egy emailt." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Kérd meg a felhasználót, hogy nézze meg az emailjeit, hogy folytatni tudja a " +"jelszó visszaállítási folyamatot" + +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superuser hozzáadva:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "A felhasználó hozzáadva:" + +#: Shell/UsersShell.php:312 +#, fuzzy +msgid "Id: {0}" +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 "Az email sikeresen elküldve" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "Szerep: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Jelszó: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "A felhasználót ne tudtuk hozzáadni:" + +#: Shell/UsersShell.php:321 +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ó" + +#: Shell/UsersShell.php:367 +msgid "The user {0} was not deleted. Please try again" +msgstr "{0} felhasználó nem lett törölve. Próbáld meg újra" + +#: Shell/UsersShell.php:369 +msgid "The user {0} was deleted successfully" +msgstr "{0} felhasználó sikeresen törölve" + +#: 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 "Szia {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +msgstr "Jelszó visszaállítás" + +#: 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 "" +"Ha a link nem jelenik meg jól, akkor másold be ezt a címet a böngésződbe {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 "Köszi" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Aktiváld a közösségi fiókodat" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Aktiváld a fiókodat" + +#: 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 "Másold be a böngésződbe ezt a címet {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 "" +"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: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:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Felhasználók" + +#: 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: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:37 +#: Template/Users/index.ctp:23 Template/Users/profile.ctp:32 +#: Template/Users/register.ctp:21 Template/Users/view.ctp:35 +#, fuzzy +msgid "Email" +msgstr "Az email sikeresen elküldve" + +#: 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: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: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: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: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 "Ment" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Add meg az új jelszót" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Jelenlegi jelszó" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Új jelszó" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Jelszó megerősítése" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Törlés" + +#: 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:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Felhasználó módosítása" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +#, fuzzy +msgid "Token" +msgstr "A token lejárt felhasználó token nélkül" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Token lejárat" + +#: Template/Users/edit.ctp:45 +#, fuzzy +msgid "API token" +msgstr "Érvénytelen token és/vagy közösségi fiók" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Aktiválás dátuma" + +#: Template/Users/edit.ctp:51 +msgid "TOS date" +msgstr "Felhasználó feltételek dátuma" + +#: Template/Users/edit.ctp:64 +msgid "Reset Google Authenticator Token" +msgstr "Google Authenticator Token visszaállítása" + +#: 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?" + +#: Template/Users/index.ctp:15 +msgid "New {0}" +msgstr "Új {0}" + +#: Template/Users/index.ctp:37 +msgid "View" +msgstr "Mutat" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "JElszó módosítás" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Szerkeszt" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "előző" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "következő" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Add meg a felhasználói nevedet és a jelszavadat" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Emlékezz rám" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Regisztrálok" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Jelszó visszaállítás" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Belépés" + +#: Template/Users/profile.ctp:21 +#, fuzzy +msgid "{0} {1}" +msgstr "Mező: {0} Hiba: {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Jelszó módosítás" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Közösségi fiókok" + +#: 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 "Kiszolgáló" + +#: Template/Users/profile.ctp:44 +#, fuzzy +msgid "Link" +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:30 +msgid "Accept TOS conditions?" +msgstr "Elfogadod a felhasználó feltételeket?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Add meg az emailcímedet a jelszó helyreállításhoz" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Ellenörző email újraküldése" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Email vagy felhasználó név" + +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "Ellenörző kód" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" " +"Ellenőriz" + +#: Template/Users/view.ctp:19 +msgid "Delete User" +msgstr "Felhasználó törlése" + +#: Template/Users/view.ctp:24 +msgid "New User" +msgstr "Új felhasználó" + +#: Template/Users/view.ctp:31 +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" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Szerep" + +#: Template/Users/view.ctp:45 +#, fuzzy +msgid "Api Token" +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" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Felhasználói feltételek dátuma" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Létrehozva" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Módosítva" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Belépés ezzel" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Kilép" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Üdv {0}!" + +#: 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:215 +msgid "Connected with {0}" +msgstr "Összekapcsolódva ezzel: {0}" + +#: 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/resources/locales/it_IT/users.po b/resources/locales/it_IT/users.po new file mode 100644 index 000000000..5b651cc07 --- /dev/null +++ b/resources/locales/it_IT/users.po @@ -0,0 +1,822 @@ +# 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 13:15-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.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: it_IT\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Account convalidato con successo" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Non è stato possibile convalidare l'account" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Token e/o account social non valido" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Account Social già attivo" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Non è stato possibile convalidare l'Account Social" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "Email inviata con successo" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Impossibile inviare l'email" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Account non valido" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Non è stato possibile reinviare l'email" + +#: 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/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Account Social già attivo" + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Log out effettuato con successo" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Registrazione utente non riuscita" + +#: 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/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 "Utente non trovato" + +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 +msgid "Password could not be changed" +msgstr "Non è stato possibile cambiare la password" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "Password modificata con successo" + +#: 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" + +#: 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" + +#: 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:148 +msgid "The user is not active" +msgstr "Utente non attivo" + +#: 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/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: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:75;99 +msgid "The user could not be saved" +msgstr "Registrazione utente non riuscita" + +#: 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: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:77;107 +msgid "The {0} has been saved" +msgstr "{0} è stato salvato" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "Non è stato possibile salvare {0}" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "{0} è stato cancellato" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "Non è stato possibile cancellare {0}" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Account utente convalidato con successo" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "Non è stato possibile convalidare l'account utente" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "Utente già attivo" + +#: 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:63 +msgid "Reset password token could not be validated" +msgstr "" +"Non è stato possibile convalidare il token per il ripristino della password" + +#: Controller/Traits/UserValidationTrait.php:67 +msgid "Invalid validation type" +msgstr "Tipo di validazione non valido" + +#: 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:76 +msgid "Token already expired" +msgstr "Token già scaduto" + +#: 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." + +#: Controller/Traits/UserValidationTrait.php:118 +msgid "User {0} is already active" +msgstr "L'Utente {0} è già attivo" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0}Ecco il tuo link per ripristinare la password" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0}Ecco il tuo link per convalidare il tuo account social" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "La scadenza del Token non può essere vuota" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Utente non trovato" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "Account utente già convalidato" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "Utente non attivo" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "L'attuale password non corrisponde" + +#: 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: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: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" + +#: Model/Behavior/SocialAccountBehavior.php:105;132 +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:83 +msgid "Unable to login user with reference {0}" +msgstr "Impossibile effettuare il login per l'utente con referenza {0}" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "Email non presente" + +#: 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" + +#: Model/Table/UsersTable.php:171 +msgid "Username already exists" +msgstr "Username già esistente" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Email già esistente" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Utilità per Plugin CakeDC Users" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Attiva un utente specifico" + +#: 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:66 +msgid "Add a new user" +msgstr "Aggiungi un nuovo utente" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Cambia il ruolo per un utente specifico" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Disattiva un utente specifico" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Cancella un utente specifico" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Ripristina la password per email" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Ripristina la password per tutti gli utenti" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Ripristina la password per un utente specifico" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Per favore inserire una password." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Password cambiata per tutti gli utenti" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nuova password : {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Per favore inserire un nome utente." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Password cambiata per l'utente : {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Per favore inserire un ruolo." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Ruolo modificato per l'utente: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Nuovo ruolo: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Utente attivato: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Utente disattivato: {0]" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Per favore inserire un nome utente o email." + +#: 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" + +#: 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: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:367 +msgid "The user {0} was not deleted. Please try again" +msgstr "L'Utente {0} non è stato cancellato. Per favore riprovare" + +#: 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 +msgid "Hi {0}" +msgstr "Ciao {0}" + +#: Template/Email/html/reset_password.ctp:24 +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/validation.ctp:27 +#, 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: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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Attiva qui il tuo login social" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Attiva qui il tuo account" + +#: 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}" + +#: 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:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Lista utenti" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Aggiunti Utente" + +#: 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: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: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: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: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: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" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Per favore inserire la nuova password" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Password attuale" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nuova password" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Conferma password" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Cancellare" + +#: 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:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Modifica utente" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Token" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Scadenza Token" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "API Token" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Data di attivazione" + +#: 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 +msgid "View" +msgstr "Vedere" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Cambiare la password" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Modifier" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "precedente" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "successivo" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Per favore inserisci il tuo nome utente e la password" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Ricordami" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Iscriviti" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Ripristina Password" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Login" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Cambiare Password" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Account Social" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Foto profilo" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Provider" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Link" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Link a  {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Accetti le condizioni TOS?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Per favore inserisci la tua email per ripristinare la password" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Rispedire Email di Convalidazione" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Email o Username" + +#: 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:24 +msgid "New User" +msgstr "Nuovo Utente" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +#, fuzzy +msgid "First Name" +msgstr "Nome" + +#: Template/Users/view.ctp:39 +#, fuzzy +msgid "Last Name" +msgstr "Cognome" + +#: 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:53 +#, fuzzy +msgid "Token Expires" +msgstr "Scadenza Token" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "Data di attivazione" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Data Tos" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Creato il" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Modificato il" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Registrati con" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Logout" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Benvenuto, {0}" + +#: 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" + +#: View/Helper/UserHelper.php:215 +#, fuzzy +msgid "Connected with {0}" +msgstr "Identificatore : {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/resources/locales/pl/users.po b/resources/locales/pl/users.po new file mode 100644 index 000000000..5b3aba164 --- /dev/null +++ b/resources/locales/pl/users.po @@ -0,0 +1,810 @@ +# 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 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<12 " +"|| n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: pl\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Konto zostało aktywowane" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Aktywacja konta nie powiodła się" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Nieprawidłowy token lub konto społecznościowe" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Konto społecznościowe jest już aktywne" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Aktywacja nie powiodła się" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "E-mail został wysłany" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "E-mail nie mógł zostać wysłany" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Nieprawidłowe konto" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Nie można wysłać wiadomości e-mail" + +#: 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/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Konto społecznościowe jest już aktywne" + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Wylogowano" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Nie można utworzyć konta" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Nie można wygenerować tokenu. Spróbuj ponownie" + +#: 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 "Konto użytkownika nie istnieje" + +#: 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:83 +msgid "Password has been changed successfully" +msgstr "Hasło zostało zmienione" + +#: 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: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:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "Nie znaleziono użytkownika {0}" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "Konto użytkownika nie jest aktywne" + +#: 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/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:46 +msgid "You must log out to register a new user account" +msgstr "Wyloguj się aby utworzyć nowe konto" + +#: Controller/Traits/RegisterTrait.php:75;99 +msgid "The user could not be saved" +msgstr "Nie można utworzyć konta" + +#: 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:135 +msgid "Please validate your account before log in" +msgstr "Potwierdź swoje konto przed logowaniem" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "{0} został zapisany" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "{0} nie mógł zostać zapisany" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "{0} został usunięty" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "{0} nie mógł zostać usunięty" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Konto użytkownika zostało aktywowane" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "Aktywacja konta nie powiodła się" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "Konto jest już aktywne" + +#: Controller/Traits/UserValidationTrait.php:55 +msgid "Reset password token was validated successfully" +msgstr "Token zmiany hasła został zatwierdzony" + +#: 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:67 +msgid "Invalid validation type" +msgstr "Nieprawidłowy typ walidacji" + +#: 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:76 +msgid "Token already expired" +msgstr "Token wygasł" + +#: 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:118 +msgid "User {0} is already active" +msgstr "Użytkownik {0} jest już aktywny" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0}Twój link zmiany hasła" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} Twój link aktywacyjny" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "Parametr 'expiration' nie może być pusty" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Nie odnaleziono użytkownika" + +#: 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:67 +msgid "User not active" +msgstr "Użytkownik nie jest aktywny" + +#: 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: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: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: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" + +#: Model/Behavior/SocialAccountBehavior.php:105;132 +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:83 +msgid "Unable to login user with reference {0}" +msgstr "Nie można zalogować użytkownika o loginie {0}" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "Brak adresu e-mail" + +#: 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:171 +msgid "Username already exists" +msgstr "Nazwa użytkownika już istnieje" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Adres e-mail już istnieje" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Narzędzia dla CakeDC Users Plugin" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Aktywuj użytkownika" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "Dodaj konto administracyjne do celów testowych" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "Dodaj użytkownika" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Zmień rolę konkretnego użytkownika" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Deaktywuj użytkownika" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Usuń użytkownika" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Zresetuj hasło przez e-mail" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Zresetuj hasło dla wszystkich użytkowników" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Zresetuj hasło dla konkretnego użytkownika" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Podaj hasło." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Zmieniono hasło dla wszystkich użytkowników" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nowe hasło: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Podaj nazwę użytkownika." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Zmieniono hasło dla użytkownika: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Podaj rolę." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Zmieniono rolę dla użytkownika: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Nowa rola: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Użytkownik został aktywowany: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Użytkownik został zdeaktywowany: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Podaj nazwę użytkownika lub hasło." + +#: 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: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: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:369 +msgid "The user {0} was deleted successfully" +msgstr "Użytkownik {0} został usunięty" + +#: 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 "Witaj {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +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 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: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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Aktywuj konto" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Aktywuj konto" + +#: 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 "Skopiuj łącze do paska adresu przeglądarki {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 "Skopiuj łącze do paska adresu przeglądarki, aby aktywować konto {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 "Akcje" + +#: 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:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Dodaj użytkownika" + +#: 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: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: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: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: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: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 "Wyślij" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Podaj nowe hasło" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Obecne hasło" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nowe hasło" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Potwierdź hasło" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Usuń" + +#: 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:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Edytuj użytkownika" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Token" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Token wygasa" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "API token" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Data aktywacji" + +#: 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 +msgid "View" +msgstr "Szczegóły" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Zmień hasło" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Edytuj" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "poprzednia" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "następna" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Podaj nazwę użytkownika i hasło" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Zapamiętaj" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Rejestracja" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Zresetuj hasło" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Zaloguj" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +#, fuzzy +msgid "Change Password" +msgstr "Zmień hasło" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Konta społecznościowe" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Avatar" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Dostawca" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Link" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Link do {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Akceptuję warunki użytkowania" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "W celu zmiany hasła podaj adres e-mail" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Ponownie wyślij e-mail aktywacyjny" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "E-mail lub nazwa użytkownika" + +#: 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:24 +msgid "New User" +msgstr "Nowy użytkownik" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +#, fuzzy +msgid "First Name" +msgstr "Imię" + +#: Template/Users/view.ctp:39 +#, fuzzy +msgid "Last Name" +msgstr "Nazwisko" + +#: 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:53 +#, fuzzy +msgid "Token Expires" +msgstr "Token wygasa" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "Data aktywacji" + +#: Template/Users/view.ctp:57 +#, fuzzy +msgid "Tos Date" +msgstr "Data (TOS)" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Utworzono" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Zmodyfikowano" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Zaloguj się za pomocą" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Wyloguj" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Witaj, {0}" + +#: 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" + +#: 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 54% rename from src/Locale/pt_BR/Users.po rename to resources/locales/pt_BR/users.po index 18830c397..6c4e524a0 100644 --- a/src/Locale/pt_BR/Users.po +++ b/resources/locales/pt_BR/users.po @@ -1,453 +1,413 @@ # 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-04-19 10:17-0300\n" -"PO-Revision-Date: 2016-04-19 10:37-0300\n" -"Last-Translator: André Teixeira \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 1.8.7\n" - -#: Auth/ApiKeyAuthenticate.php:55 -msgid "Type {0} is not valid" -msgstr "Tipo {0} não é válido" - -#: Auth/ApiKeyAuthenticate.php:59 -msgid "Type {0} has no associated callable" -msgstr "Tipo {0} não tem chamada associada" - -#: Auth/ApiKeyAuthenticate.php:68 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL é requerido para autenticação por chave da API." - -#: Auth/SimpleRbacAuthorize.php:141 -msgid "" -"Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" -"Arquivo de configuração ausente: \"config/{0}.php\". Usando permissões padrão" - -#: Auth/SocialAuthenticate.php:410 -msgid "Provider cannot be empty" -msgstr "O provedor não pode ser vazio" - -#: Auth/Rules/AbstractRule.php:77 -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" - -#: Auth/Rules/Owner.php:67;70 -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}" +"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;86 +#: 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:79 +#: Controller/SocialAccountsController.php:78 msgid "Email sent successfully" msgstr "Email enviado com sucesso" -#: Controller/SocialAccountsController.php:81 +#: Controller/SocialAccountsController.php:80 msgid "Email could not be sent" msgstr "O email não pode ser enviado" -#: Controller/SocialAccountsController.php:84 +#: Controller/SocialAccountsController.php:83 msgid "Invalid account" msgstr "Conta inválida" -#: Controller/SocialAccountsController.php:88 +#: Controller/SocialAccountsController.php:87 msgid "Email could not be resent" msgstr "O email não pode ser reenviado" -#: Controller/Component/RememberMeComponent.php:69 -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:157 -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:52 +msgid "Could not associate account, please try again." +msgstr "Não foi possível associar a conta, tente novamente." -#: Controller/Traits/LoginTrait.php:95 -msgid "Issues trying to log in with your social account" -msgstr "Problemas ao tentar autenticar a partir da sua conta social" +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "A conta social foi associada." -#: Controller/Traits/LoginTrait.php:100 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Por favor indique seu email" - -#: Controller/Traits/LoginTrait.php:106 -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:73 +msgid "You've successfully logged out" +msgstr "Você desconectou-se com êxito" -#: Controller/Traits/LoginTrait.php:108 -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/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Por favor habilite o Google Authenticator primeiro." -#: Controller/Traits/LoginTrait.php:157 Controller/Traits/RegisterTrait.php:73 -msgid "Invalid reCaptcha" -msgstr "ReCaptcha inválido" +#: 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:165 -msgid "You are already logged in" -msgstr "Você já está autenticado" +#: 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:209 -msgid "Username or password is incorrect" -msgstr "Nome de usuário e/ou senha incorreto(s)" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Código de verificação é inválido. Tente novamente" -#: Controller/Traits/LoginTrait.php:230 -msgid "You've successfully logged out" -msgstr "Você desconectou-se com êxito" +#: 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:53;60;68 +#: 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:57 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Senha alterada com êxito" -#: Controller/Traits/PasswordManagementTrait.php:64 -#: Controller/Traits/ProfileTrait.php:49 -msgid "User was not found" -msgstr "O usuário não foi encontrado" - -#: Controller/Traits/PasswordManagementTrait.php:66 -msgid "The current password does not match" -msgstr "A senha atual não corresponde" - -#: Controller/Traits/PasswordManagementTrait.php:108 +#: 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:111 Shell/UsersShell.php:266 +#: 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:116 -#: Controller/Traits/UserValidationTrait.php:98 +#: 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:118 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "O usuário não está ativo" -#: Controller/Traits/PasswordManagementTrait.php:120 -#: Controller/Traits/UserValidationTrait.php:94;102 +#: 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/ProfileTrait.php:52 +#: 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:57 msgid "Not authorized, please login first" msgstr "Não autorizado, por favor, autentique-se primeiro" -#: Controller/Traits/RegisterTrait.php:79 +#: 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:75;99 msgid "The user could not be saved" msgstr "O usuário não pode ser salvo" -#: Controller/Traits/RegisterTrait.php:111 +#: 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:113 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Por favor, valide sua conta antes de autenticar" -#: Controller/Traits/SimpleCrudTrait.php:76;105 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "O {0} foi salvo" -#: Controller/Traits/SimpleCrudTrait.php:79;108 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "O {0} não pode ser salvo" -#: Controller/Traits/SimpleCrudTrait.php:128 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "O {0} foi deletado" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "O {0} não pode ser deletado" -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "O reCaptcha não pode ser validado" - -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Conta de usuário validada com êxito" -#: Controller/Traits/UserValidationTrait.php:44 +#: 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:47 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Usuário já ativo" -#: Controller/Traits/UserValidationTrait.php:53 +#: 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:57 +#: 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:61 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Tipo de validação inválido" -#: Controller/Traits/UserValidationTrait.php:64 +#: 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:66 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token expirado" -#: Controller/Traits/UserValidationTrait.php:92 +#: 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:100 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "O usuário {0} já está ativo" -#: Email/EmailSender.php:39 +#: 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:55 +#: Mailer/UsersMailer.php:51 msgid "{0}Your reset password link" msgstr "{0}Seu link de redefinição de senha" -#: Mailer/UsersMailer.php:78 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0}Seu link de validação da conta social" -#: Model/Behavior/PasswordBehavior.php:56 +#: 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" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "Conta social já está associada a outro usuário" + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "A referência não pode ser nula" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "A expiração do token não pode ser vazia" -#: Model/Behavior/PasswordBehavior.php:67;115 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Usuário não encontrado" -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:110 +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 msgid "User account already validated" msgstr "Conta de usuário já validada" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "Usuário inativo" -#: Model/Behavior/PasswordBehavior.php:120 -msgid "The old password does not match" -msgstr "A senha antiga não confere" +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "A senha atual não corresponde" + +#: 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" -#: Model/Behavior/RegisterBehavior.php:88 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Usuário não encontrado com a combinação de token e email" -#: Model/Behavior/RegisterBehavior.php:91 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "Token expirado, usuário sem token" -#: Model/Behavior/SocialAccountBehavior.php:101;128 +#: 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:104;131 +#: 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:56 +#: 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:97 +#: 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:159 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Nome de usuário em uso" -#: Model/Table/UsersTable.php:165 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "Email em uso" -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Utilitários para CakeDC Users Plugin" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Ativar um usuário específico" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "Adicionar um novo usuário superadmin para fins de testes" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Adicionar um novo usuário" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" -msgstr "Mudar o role de um usuário específico" +msgstr "Alterar o papel de um usuário específico" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Desativar um usuário específico" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Deletar um usuário específico" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Redefinir a senha via email" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Redefinir a senha de todos usuários" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Redefinir a senha de um usuário específico" -#: Shell/UsersShell.php:97 -msgid "User added:" -msgstr "Usuário adicionado:" - -#: Shell/UsersShell.php:98;126 -msgid "Id: {0}" -msgstr "Id: {0}" - -#: Shell/UsersShell.php:99;127 -msgid "Username: {0}" -msgstr "Nome de usuário: {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Email: {0}" -msgstr "Email: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Password: {0}" -msgstr "Senha: {0}" - -#: Shell/UsersShell.php:125 -msgid "Superuser added:" -msgstr "Superusuário adicionado:" - -#: Shell/UsersShell.php:131 -msgid "Superuser could not be added:" -msgstr "O superusuário não pode ser adicionado:" - -#: Shell/UsersShell.php:134 -msgid "Field: {0} Error: {1}" -msgstr "Campo: {0} Erro: {1}" - -#: Shell/UsersShell.php:152;178 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Por favor, indique uma senha." -#: Shell/UsersShell.php:156 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "As senhas de todos usuários foram alteradas" -#: Shell/UsersShell.php:157;185 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nova senha: {0}" -#: Shell/UsersShell.php:175;203;281;321 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Por favor, indique um nome de usuário." -#: Shell/UsersShell.php:184 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Senha alterada para usuário: {0}" -#: Shell/UsersShell.php:206 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Por favor, indique um papel." -#: Shell/UsersShell.php:212 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Papel alterado para usuário: {0}" -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Novo papel: {0}" -#: Shell/UsersShell.php:228 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "Usuário ativado: {0}" -#: Shell/UsersShell.php:243 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "Usuário desativado: {0}" -#: Shell/UsersShell.php:255 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Por favor, indique um nome de usuário ou senha." -#: Shell/UsersShell.php:263 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" @@ -455,15 +415,51 @@ msgstr "" "Por favor, peça ao usuário para verificar seu email para continuar o " "processo de redefinição de senha" -#: Shell/UsersShell.php:300 +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superusuário adicionado:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Usuário adicionado:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Nome de usuário: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Email: {0}" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "Papel: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Senha: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "Usuário não pôde ser adicionado:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Campo: {0} Erro: {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "O usuário não foi encontrado." -#: Shell/UsersShell.php:329 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "O usuário {0} não foi deletado. Por favor, tente novamente" -#: Shell/UsersShell.php:331 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "O usuário {0} foi deletado com êxito" @@ -482,19 +478,20 @@ msgstr "Redefina sua senha aqui" #: 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 " +"If the link is not correctly 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 " -"endereço a seguir no seu navegador {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 +"seguinte endereço em seu navegador {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 "Obrigado" @@ -506,14 +503,6 @@ msgstr "Ative sua autenticação social aqui" msgid "Activate your account here" msgstr "Ative sua conta aqui" -#: 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 "" -"Se o link não está exibido corretamente, por favor, copie o seguinte " -"endereço em seu navegador {0}" - #: Template/Email/text/reset_password.ctp:22 #: Template/Email/text/validation.ctp:22 msgid "Please copy the following address in your web browser {0}" @@ -527,27 +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:13 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:13;77 +#: 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:21 -#: Template/Users/view.ctp:17 +#: 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:16 Template/Users/edit.ctp:22 -#: Template/Users/view.ctp:19 -msgid "List Accounts" -msgstr "Listar contas" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Adicionar usuário" -#: Template/Users/add.ctp:32 Template/Users/change_password.ctp:17 -#: Template/Users/edit.ctp:42 Template/Users/register.ctp:32 +#: 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: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:25 Template/Users/login.ctp:21 +#: Template/Users/register.ctp:22 +msgid "Password" +msgstr "Senha" + +#: 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: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: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: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 @@ -562,33 +581,69 @@ msgstr "Por favor, indique a nova senha" msgid "Current password" msgstr "Senha atual" -#: Template/Users/edit.ctp:16 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:99 +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nova senha" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Confirmar senha" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 msgid "Delete" msgstr "Deletar" -#: Template/Users/edit.ctp:18 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:16;99 +#: 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:28 Template/Users/view.ctp:15 +#: Template/Users/edit.ctp:34 Template/Users/view.ctp:17 msgid "Edit User" msgstr "Editar usuário" +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Token" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Token expira" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "Token da API" + +#: Template/Users/edit.ctp:48 +#, fuzzy +msgid "Activation date" +msgstr "Data de ativação" + +#: Template/Users/edit.ctp:51 +msgid "TOS date" +msgstr "Data TOS" + +#: Template/Users/edit.ctp:64 +msgid "Reset Google Authenticator Token" +msgstr "Redefir Token do Google Authenticator" + +#: 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}”?" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Novo {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:95 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Visualizar" #: Template/Users/index.ctp:38 msgid "Change password" -msgstr "Mudar senha" +msgstr "Alterar senha" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Editar" @@ -608,49 +663,49 @@ msgstr "Por favor, indique seu nome de usuário e senha" msgid "Remember me" msgstr "Lembrar" +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Registrar" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Resetar Senha" + #: Template/Users/login.ctp:48 msgid "Login" msgstr "Autenticar" -#: 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 "Mudar senha" -#: Template/Users/profile.ctp:27 Template/Users/view.ctp:28;68 -msgid "Username" -msgstr "Nome de usuário" - -#: Template/Users/profile.ctp:29 Template/Users/view.ctp:30 -msgid "Email" -msgstr "Email" - -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Contas sociais" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:70 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Avatar" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:67 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Provedor" -#: 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 para {0}" -#: Template/Users/register.ctp:25 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" -msgstr "Concordar com TOS?" +msgstr "Concordar com Termos de Uso?" #: Template/Users/request_reset_password.ctp:5 msgid "Please enter your email to reset your password" @@ -664,119 +719,87 @@ msgstr "Reenviar email de validação" msgid "Email or username" msgstr "Email ou nome de usuário" -#: Template/Users/view.ctp:16 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "Código de verificação" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" Verificar" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Deletar usuário" -#: Template/Users/view.ctp:18 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Novo usuário" -#: Template/Users/view.ctp:26;65 +#: Template/Users/view.ctp:31 msgid "Id" msgstr "Id" -#: Template/Users/view.ctp:32 +#: Template/Users/view.ctp:37 msgid "First Name" msgstr "Nome" -#: Template/Users/view.ctp:34 +#: Template/Users/view.ctp:39 msgid "Last Name" msgstr "Sobrenome" -#: Template/Users/view.ctp:36;71 -msgid "Token" -msgstr "Token" +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Papel" -#: Template/Users/view.ctp:38 +#: Template/Users/view.ctp:45 msgid "Api Token" msgstr "Api Token" -#: Template/Users/view.ctp:42;73 -msgid "Active" -msgstr "Ativo" - -#: Template/Users/view.ctp:46;72 +#: Template/Users/view.ctp:53 msgid "Token Expires" msgstr "Expiração do token" -#: Template/Users/view.ctp:48 +#: Template/Users/view.ctp:55 msgid "Activation Date" msgstr "Data de ativação" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:57 +#, fuzzy msgid "Tos Date" msgstr "Data TOS" -#: Template/Users/view.ctp:52;75 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Criado" -#: Template/Users/view.ctp:54;76 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Modificado" -#: Template/Users/view.ctp:61 -msgid "Related Accounts" -msgstr "Contas relacionadas" - -#: Template/Users/view.ctp:66 -msgid "User Id" -msgstr "Id de usuário" - -#: Template/Users/view.ctp:69 -msgid "Reference" -msgstr "Referência" - -#: Template/Users/view.ctp:74 -msgid "Data" -msgstr "Dados" - -#: 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:46 +msgid "Sign in with" +msgstr "Logar com" -#: View/Helper/UserHelper.php:90 +#: View/Helper/UserHelper.php:103 msgid "Logout" msgstr "Desconectar" -#: View/Helper/UserHelper.php:139 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Bem-vindo(a), {0}" -#: View/Helper/UserHelper.php:161 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "" -"O reCaptcha não está configurado! Por favor, configure Users.reCaptcha.key" - -#~ 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" +"reCaptcha não está configurado! Por favor, configure Users.reCaptcha.key" -#~ msgid "Sign in with Facebook" -#~ msgstr "Inscrever-se com Facebook" +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "Conectador com {0}" -#~ msgid "Sign in with Twitter" -#~ msgstr "Inscrever-se com Twitter" +#: View/Helper/UserHelper.php:220 +msgid "Connect with {0}" +msgstr "Conectar com {0}" 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 58% rename from src/Locale/sv/Users.po rename to resources/locales/sv/users.po index fbbc50d89..4d952389f 100644 --- a/src/Locale/sv/Users.po +++ b/resources/locales/sv/users.po @@ -1,314 +1,303 @@ # 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: 2016-10-26 11:24+0200\n" -"PO-Revision-Date: 2016-10-26 11:30+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.11\n" - -#: Auth/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "Typ {0} är inte giltig" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "Typ {0} har inget tillhörande anrop" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL krävs för ApiKey-autentisering" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "" -"Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" -"Saknad konfigurationsfil: \"config / {0} .php\". Använder " -"standardbehörigheter" - -#: Auth/SocialAuthenticate.php:432 -msgid "Provider cannot be empty" -msgstr "Leverantör kan inte vara tomt" - -#: 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 "" -"Tabellalias är tomt, var vänlig ange ett tabellalias, vi kunde inte " -"extrahera någon standardtabell" - -#: Auth/Rules/Owner.php:67;70 -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}" +"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" +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:69 -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:178 -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:52 +msgid "Could not associate account, please try again." +msgstr "Kunde inte ansluta konto, försök igen." -#: Controller/Traits/LoginTrait.php:96 -msgid "Issues trying to log in with your social account" -msgstr "Problem vid inloggning med ditt sociala konto" +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "Socialt konto anslöts." -#: Controller/Traits/LoginTrait.php:101 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Lägg till din epost" - -#: Controller/Traits/LoginTrait.php:108 -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:110 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"Din sociala hänsyn har inte validerats ännu. Vänligen kontrollera din inbox " -"för instruktioner" +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Du har loggats ut" -#: Controller/Traits/LoginTrait.php:161 Controller/Traits/RegisterTrait.php:81 -msgid "Invalid reCaptcha" -msgstr "Ogiltig reCaptcha" +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Vänligen aktivera Google Authenticator först." -#: Controller/Traits/LoginTrait.php:171 -msgid "You are already logged in" -msgstr "Du är redan inloggad." +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Användare kunde inte läggas till:" -#: Controller/Traits/LoginTrait.php:217 -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:238 -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:47;76 -#: Controller/Traits/ProfileTrait.php:49 +#: 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:64;72;80 +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 msgid "Password could not be changed" msgstr "Lösenordet kunde inte ändras" -#: Controller/Traits/PasswordManagementTrait.php:68 +#: Controller/Traits/PasswordManagementTrait.php:83 msgid "Password has been changed successfully" msgstr "Lösenordsbytet lyckades!" -#: 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 "Kontrollera din epost för att fortsätta lösenordsåterställningen" -#: 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 "Lösenordstoken kunde inte skapas. Var god försök igen" -#: 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 "Användaren {0} hittades inte" -#: Controller/Traits/PasswordManagementTrait.php:131 +#: Controller/Traits/PasswordManagementTrait.php:148 msgid "The user is not active" msgstr "Användaren är inte aktiverad" -#: 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 "Token kunde inte återställas" -#: Controller/Traits/ProfileTrait.php:53 +#: Controller/Traits/PasswordManagementTrait.php:174 +msgid "Google Authenticator token was successfully reset" +msgstr "Google Authenticator token har återställts" + +#: Controller/Traits/ProfileTrait.php:57 msgid "Not authorized, please login first" msgstr "Inte auktoriserad, vänligen logga in först" -#: Controller/Traits/RegisterTrait.php:42 +#: 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:88 +#: Controller/Traits/RegisterTrait.php:75;99 msgid "The user could not be saved" msgstr "Användaren kunde inte sparas" -#: Controller/Traits/RegisterTrait.php:122 +#: 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:124 +#: Controller/Traits/RegisterTrait.php:135 msgid "Please validate your account before log in" msgstr "Vänligen validera ditt konto innan inloggning" -#: Controller/Traits/SimpleCrudTrait.php:76;106 +#: Controller/Traits/SimpleCrudTrait.php:77;107 msgid "The {0} has been saved" msgstr "{0} har sparats" -#: Controller/Traits/SimpleCrudTrait.php:80;110 +#: Controller/Traits/SimpleCrudTrait.php:81;111 msgid "The {0} could not be saved" msgstr "{0} kunde inte sparas" -#: Controller/Traits/SimpleCrudTrait.php:130 +#: Controller/Traits/SimpleCrudTrait.php:131 msgid "The {0} has been deleted" msgstr "{0} har tagits bort" -#: Controller/Traits/SimpleCrudTrait.php:132 +#: Controller/Traits/SimpleCrudTrait.php:133 msgid "The {0} could not be deleted" msgstr "{0} kunde inte tas bort" -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "reCAPTCHA angavs inte korrekt" - -#: Controller/Traits/UserValidationTrait.php:42 +#: Controller/Traits/UserValidationTrait.php:44 msgid "User account validated successfully" msgstr "Användarkontot har validerats" -#: Controller/Traits/UserValidationTrait.php:44 +#: Controller/Traits/UserValidationTrait.php:46 msgid "User account could not be validated" msgstr "Användarkontot kunde inte valideras" -#: Controller/Traits/UserValidationTrait.php:47 +#: Controller/Traits/UserValidationTrait.php:49 msgid "User already active" msgstr "Användaren redan aktiv" -#: Controller/Traits/UserValidationTrait.php:53 +#: Controller/Traits/UserValidationTrait.php:55 msgid "Reset password token was validated successfully" -msgstr "Återställning av lösenord lyckades." +msgstr "Återställning av lösenord lyckades" -#: Controller/Traits/UserValidationTrait.php:58 +#: 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:62 +#: Controller/Traits/UserValidationTrait.php:67 msgid "Invalid validation type" msgstr "Ogiltig valideringsmetod" -#: Controller/Traits/UserValidationTrait.php:65 +#: 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:67 +#: Controller/Traits/UserValidationTrait.php:76 msgid "Token already expired" msgstr "Token redan löpt ut" -#: Controller/Traits/UserValidationTrait.php:93 +#: 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:102 +#: Controller/Traits/UserValidationTrait.php:118 msgid "User {0} is already active" msgstr "Användaren {0} är redan aktiv" -#: Email/EmailSender.php:39 +#: 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:55 +#: 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:78 +#: Mailer/UsersMailer.php:74 msgid "{0}Your social account validation link" msgstr "{0} Din sociala konto svalideringslänk" -#: Model/Behavior/PasswordBehavior.php:56 +#: 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" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "Socialt konto är redan kopplat till en annan användare." + +#: Model/Behavior/PasswordBehavior.php:45 msgid "Reference cannot be null" msgstr "Referens får inte vara null" -#: Model/Behavior/PasswordBehavior.php:61 +#: Model/Behavior/PasswordBehavior.php:50 msgid "Token expiration cannot be empty" msgstr "Giltighet för token kan inte vara tom" -#: Model/Behavior/PasswordBehavior.php:67;116 +#: Model/Behavior/PasswordBehavior.php:56;138 msgid "User not found" msgstr "Användaren hittades inte" -#: 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 "Kontot är redan aktiverat!" -#: Model/Behavior/PasswordBehavior.php:78 +#: Model/Behavior/PasswordBehavior.php:67 msgid "User not active" msgstr "Användaren ej aktiv" -#: Model/Behavior/PasswordBehavior.php:121 +#: Model/Behavior/PasswordBehavior.php:143 msgid "The current password does not match" msgstr "Den nuvarande lösenord matchar inte" -#: Model/Behavior/PasswordBehavior.php:124 +#: 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" -#: Model/Behavior/RegisterBehavior.php:89 +#: Model/Behavior/RegisterBehavior.php:90 msgid "User not found for the given token and email." msgstr "Hittade ingen användare som matchar angivet token eller epost" -#: Model/Behavior/RegisterBehavior.php:92 +#: Model/Behavior/RegisterBehavior.php:93 msgid "Token has already expired user with no token" msgstr "Token har redan gått ut eller användare utan token" +#: 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!" @@ -316,147 +305,111 @@ msgstr "Kontot är redan aktiverat!" 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:56 +#: 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:98 +#: Model/Behavior/SocialBehavior.php:122 msgid "Email not present" msgstr "Epost saknas" -#: Model/Table/UsersTable.php:82 +#: 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:175 +#: Model/Table/UsersTable.php:171 msgid "Username already exists" msgstr "Användarnamnet är upptaget" -#: Model/Table/UsersTable.php:181 +#: Model/Table/UsersTable.php:177 msgid "Email already exists" msgstr "E-postadressen finns redan" -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "Saknas 'username' i alternativa uppgifter" - -#: Shell/UsersShell.php:54 +#: Shell/UsersShell.php:58 msgid "Utilities for CakeDC Users Plugin" msgstr "Verktyg för CakeDC User Plugin" -#: Shell/UsersShell.php:55 +#: Shell/UsersShell.php:60 msgid "Activate an specific user" msgstr "Aktivera en specifik användare" -#: Shell/UsersShell.php:56 +#: Shell/UsersShell.php:63 msgid "Add a new superadmin user for testing purposes" msgstr "Lägga till en ny superadmin användare för teständamål" -#: Shell/UsersShell.php:57 +#: Shell/UsersShell.php:66 msgid "Add a new user" msgstr "Ny användare" -#: Shell/UsersShell.php:58 +#: Shell/UsersShell.php:69 msgid "Change the role for an specific user" msgstr "Ändra rollen för en specifik användare" -#: Shell/UsersShell.php:59 +#: Shell/UsersShell.php:72 msgid "Deactivate an specific user" msgstr "Inaktivare en specifik användare" -#: Shell/UsersShell.php:60 +#: Shell/UsersShell.php:75 msgid "Delete an specific user" msgstr "Ta bort en specifik användare" -#: Shell/UsersShell.php:61 +#: Shell/UsersShell.php:78 msgid "Reset the password via email" msgstr "Återställ lösenord via e-post" -#: Shell/UsersShell.php:62 +#: Shell/UsersShell.php:81 msgid "Reset the password for all users" msgstr "Återställ lösenord för alla användare" -#: Shell/UsersShell.php:63 +#: Shell/UsersShell.php:84 msgid "Reset the password for an specific user" msgstr "Återställ lösenordet för en specifik användare" -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "Användare tillagd:" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "Id: {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "Användarnamn: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "Epost: {0}" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "Lösenord: {0}" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "Superanvändare tillagd:" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "Superanvändaren kunde inte läggas till:" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "Fält: {0} fel: {1}" - -#: Shell/UsersShell.php:153;179 +#: Shell/UsersShell.php:133;159 msgid "Please enter a password." msgstr "Ange ett lösenord." -#: Shell/UsersShell.php:157 +#: Shell/UsersShell.php:137 msgid "Password changed for all users" msgstr "Lösenordet ändrat för alla användare" -#: Shell/UsersShell.php:158;186 +#: Shell/UsersShell.php:138;166 msgid "New password: {0}" msgstr "Nytt lösenord: {0}" -#: Shell/UsersShell.php:176;204;282;324 +#: Shell/UsersShell.php:156;184;262;359 msgid "Please enter a username." msgstr "Ange ett användarnamn" -#: Shell/UsersShell.php:185 +#: Shell/UsersShell.php:165 msgid "Password changed for user: {0}" msgstr "Lösenordet ändrat för användare: {0}" -#: Shell/UsersShell.php:207 +#: Shell/UsersShell.php:187 msgid "Please enter a role." msgstr "Ange en roll." -#: Shell/UsersShell.php:213 +#: Shell/UsersShell.php:193 msgid "Role changed for user: {0}" msgstr "Rollen har ändrats för användare: {0}" -#: Shell/UsersShell.php:214 +#: Shell/UsersShell.php:194 msgid "New role: {0}" msgstr "Ny roll: {0}" -#: Shell/UsersShell.php:229 +#: Shell/UsersShell.php:209 msgid "User was activated: {0}" msgstr "Användaren aktiverades: {0}" -#: Shell/UsersShell.php:244 +#: Shell/UsersShell.php:224 msgid "User was de-activated: {0}" msgstr "Användaren avaktiverad: {0}" -#: Shell/UsersShell.php:256 +#: Shell/UsersShell.php:236 msgid "Please enter a username or email." msgstr "Skriv användarnamn eller epost" -#: Shell/UsersShell.php:264 +#: Shell/UsersShell.php:244 msgid "" "Please ask the user to check the email to continue with password reset " "process" @@ -464,15 +417,51 @@ msgstr "" "Be användaren kontrollera sin epost för att fortsätta " "lösenordsåterställningen" -#: Shell/UsersShell.php:302 +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Superanvändare tillagd:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Användare tillagd:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Användarnamn: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "Epost: {0}" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "Roll: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Lösenord: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "Användare kunde inte läggas till:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Fält: {0} fel: {1}" + +#: Shell/UsersShell.php:337 msgid "The user was not found." msgstr "Användaren hittades inte." -#: Shell/UsersShell.php:332 +#: Shell/UsersShell.php:367 msgid "The user {0} was not deleted. Please try again" msgstr "Användaren {0} raderades ej. Var god försök igen" -#: Shell/UsersShell.php:334 +#: Shell/UsersShell.php:369 msgid "The user {0} was deleted successfully" msgstr "Användaren {0} har tagits bort" @@ -491,19 +480,20 @@ msgstr "Återställ ditt lösenord här" #: 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 " +"If the link is not correctly displayed, please copy the following address in " "your web browser {0}" msgstr "" -"Om länken inte visas korrekt, vänligen kopiera följande adress till " -"webbläsaren {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 +"Om länken inte visas korrekt, vänligen kopiera följande adress till din " +"webbläsare {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 "Tack" @@ -515,14 +505,6 @@ msgstr "Aktivera ditt sociala konto här" msgid "Activate your account here" msgstr "Aktivera ditt konto här" -#: 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 "" -"Om länken inte visas korrekt, vänligen kopiera följande adress till din " -"webbläsare {0}" - #: Template/Email/text/reset_password.ctp:22 #: Template/Email/text/validation.ctp:22 msgid "Please copy the following address in your web browser {0}" @@ -536,54 +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: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 "Resurser" -#: 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 användare" -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "Lista konton" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 msgid "Add User" msgstr "Ny användare" -#: 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 "Användarnamn" -#: 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 "Epost" -#: 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 "Lösenord" + +#: 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: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 "Efternamn" -#: 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 "Aktivt" #: 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 @@ -602,49 +585,56 @@ msgstr "Nuvarande lösenord" msgid "New password" msgstr "Nytt lösenord" -#: 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 "Bekräfta lösenord" -#: 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 "Radera" -#: 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 "Ä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: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 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:64 +msgid "Reset Google Authenticator Token" +msgstr "Återställ Google Authenticator Token" + +#: 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}\"?" + #: Template/Users/index.ctp:15 msgid "New {0}" msgstr "Nytt {0}" -#: Template/Users/index.ctp:37 Template/Users/view.ctp:97 +#: Template/Users/index.ctp:37 msgid "View" msgstr "Visa" @@ -652,7 +642,7 @@ msgstr "Visa" msgid "Change password" msgstr "Byt lösenord" -#: Template/Users/index.ctp:39 Template/Users/view.ctp:99 +#: Template/Users/index.ctp:39 msgid "Edit" msgstr "Ändra" @@ -684,39 +674,35 @@ msgstr "Återställ Lösenord" msgid "Login" msgstr "Logga in" -#: 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 "Ändra Lösenord" -#: Template/Users/profile.ctp:34 +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 msgid "Social Accounts" msgstr "Sociala konton" -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:72 +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 msgid "Avatar" msgstr "Profilbild" -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:69 +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 msgid "Provider" msgstr "Utförare" -#: Template/Users/profile.ctp:40 +#: Template/Users/profile.ctp:44 msgid "Link" msgstr "Länk" -#: Template/Users/profile.ctp:47 +#: Template/Users/profile.ctp:51 msgid "Link to {0}" msgstr "Länk till {0}" -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "Lösenord" - -#: Template/Users/register.ctp:28 +#: Template/Users/register.ctp:30 msgid "Accept TOS conditions?" msgstr "Jag accepterar villkoren" @@ -732,111 +718,90 @@ msgstr "Skicka nytt bekräftelsemejl" msgid "Email or username" msgstr "E-post/Användarnamn" -#: Template/Users/view.ctp:18 +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "Verifieringskod" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" " +"Verifiera" + +#: Template/Users/view.ctp:19 msgid "Delete User" msgstr "Ta bort användare" -#: Template/Users/view.ctp:20 +#: Template/Users/view.ctp:24 msgid "New User" msgstr "Ny användare" -#: 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 "Förnamn" -#: Template/Users/view.ctp:36 +#: Template/Users/view.ctp:39 +#, fuzzy msgid "Last Name" msgstr "Efternamn" -#: Template/Users/view.ctp:40 +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Roll" + +#: 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 förfaller" -#: Template/Users/view.ctp:50 +#: Template/Users/view.ctp:55 +#, fuzzy msgid "Activation Date" msgstr "Aktiveringsdatum" -#: Template/Users/view.ctp:52 +#: Template/Users/view.ctp:57 msgid "Tos Date" msgstr "Tos datum" -#: Template/Users/view.ctp:54;77 +#: Template/Users/view.ctp:59;75 msgid "Created" msgstr "Skapad" -#: Template/Users/view.ctp:56;78 +#: Template/Users/view.ctp:61;76 msgid "Modified" msgstr "Ändrad" -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "Relaterade konton" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "Användar-ID" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "Referens" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "Data" - #: View/Helper/UserHelper.php:46 msgid "Sign in with" msgstr "Logga in med" -#: 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 "Logga ut" -#: View/Helper/UserHelper.php:108 +#: View/Helper/UserHelper.php:121 msgid "Welcome, {0}" msgstr "Välkommen, {0}" -#: View/Helper/UserHelper.php:131 +#: View/Helper/UserHelper.php:151 msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" msgstr "reCaptcha är inte konfigurerad! Konfigurera Users.reCaptcha.key" -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "Detta fält är obligatoriskt" - -#~ msgid "The old password does not match" -#~ msgstr "Det gamla lösenordet stämmer inte" - -#~ msgid "SocialAccount already active" -#~ msgstr "SocialAccount redan aktivt" - -#~ msgid "There was an error associating your social network account" -#~ msgstr "Det gick inte att associera ditt sociala nätverkskonto" - -#~ msgid "Invalid token and/or email" -#~ msgstr "Ogiltig token och / eller epost" - -#~ msgid "The \"tos\" property is not present" -#~ msgstr "\"tos\" fältet saknas" - -#~ msgid "+ {0} secs" -#~ msgstr "{0} sek" +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "Ansluten med {0}" -#~ msgid "Sign in with Facebook" -#~ msgstr "Logga in med Facebook" +#: View/Helper/UserHelper.php:220 +msgid "Connect with {0}" +msgstr "Anslut med {0}" 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/resources/locales/tr_TR/users.po b/resources/locales/tr_TR/users.po new file mode 100644 index 000000000..2e321dc67 --- /dev/null +++ b/resources/locales/tr_TR/users.po @@ -0,0 +1,806 @@ +# 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 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.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: tr_TR\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Hesap başarıyla doğrulandı" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Hesap doğrulanamadı" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Geçersiz jeton ve/veya sosyal hesap" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Sosyal Hesap zaten aktif" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Sosyal Hesap doğrulanamadı" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "E-posta başarıyla gönderildi" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "E-posta gönderilemedi" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Geçersiz hesap" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "E-posta tekrar gönderilemedi" + +#: 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:76 +msgid "Social account was associated." +msgstr "Sosyal hesap zaten eşleştirilmişti." + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Başarıyla çıkış yaptınız" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Lütfen önce Google Authenticator ‘ı aktif hale getirin." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Kullanıcı eklenemedi:" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Hesap eşleştirilemedi, lütfen tekrar deneyin." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Doğrulama kodu yanlış. Tekrar deneyin" + +#: Controller/Traits/PasswordManagementTrait.php:53;91 +#: Controller/Traits/ProfileTrait.php:53 +msgid "User was not found" +msgstr "Kullanıcı bulunamadı" + +#: Controller/Traits/PasswordManagementTrait.php:75;87;95 +msgid "Password could not be changed" +msgstr "Şifre değiştirilemedi" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "Şifre başarıyla değiştirildi" + +#: 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: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:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "Kullancı {0} bulunamadı" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "Kullanıcı aktif değil" + +#: 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:174 +msgid "Google Authenticator token was successfully reset" +msgstr "Google Authenticator jetonu başarıyla sıfırlandı" + +#: 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: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:75;99 +msgid "The user could not be saved" +msgstr "Kullanıcı kaydedilemedi" + +#: 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:135 +msgid "Please validate your account before log in" +msgstr "Lütfen giriş yapmadan önce hesabınızı doğrulayın" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "{0} kaydedildi" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "{0} kaydedilemedi" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "{0} silindi" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "{0} silinemedi" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Kullanıcı hesabı başarıyla doğrulandı" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "Kullanıcı hesabı doğrulanamadı" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "Kullanıcı zaten aktif" + +#: 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:63 +msgid "Reset password token could not be validated" +msgstr "Şifre sıfırlama jetonu doğrulanamadı" + +#: Controller/Traits/UserValidationTrait.php:67 +msgid "Invalid validation type" +msgstr "Geçersiz doğrulama cinsi" + +#: 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:76 +msgid "Token already expired" +msgstr "Jeton süresi zaten doldu" + +#: 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:118 +msgid "User {0} is already active" +msgstr "Kullanıcı {0} zaten aktif" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0} Şifre sıfırlama bağlantısı" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} Sosyal hesap doğrulama bağlantısı" + +#: 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" + +#: Model/Behavior/LinkSocialBehavior.php:53 +msgid "Social account already associated to another user" +msgstr "Sosyal hesap zaten başka bir kullanıcıyla eşleşmiş durumda" + +#: Model/Behavior/PasswordBehavior.php:45 +msgid "Reference cannot be null" +msgstr "Referans boş olamaz" + +#: Model/Behavior/PasswordBehavior.php:50 +msgid "Token expiration cannot be empty" +msgstr "Jeton son kullanma tarihi boş olamaz" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Kullanıcı bulunamadı" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "Kullanıcı hesabı zaten doğrulandı" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "Kullanıcı aktif durumda değil" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "Geçerli şifre uyuşmuyor" + +#: 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" + +#: Model/Behavior/RegisterBehavior.php:90 +msgid "User not found for the given token and email." +msgstr "Verilen jeton ve e-posta için kullanıcı bulunamadı." + +#: Model/Behavior/RegisterBehavior.php:93 +msgid "Token has already expired user with no token" +msgstr "Jeton son kullanma tarihi zaten doldu, kullanıcının jetonu yok" + +#: 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: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:83 +msgid "Unable to login user with reference {0}" +msgstr "Referans {0} ile giriş başarısız" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "E-posta yok" + +#: 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:171 +msgid "Username already exists" +msgstr "Kullanıcı adı zaten var" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "E-posta zaten var" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "CakeDC Users eklentisi için araçlar" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Belirli bir kullanıcıyı aktif hale getir" + +#: Shell/UsersShell.php:63 +msgid "Add a new superadmin user for testing purposes" +msgstr "Deneme için yeni bir süperyönetici kullanıcısı ekle" + +#: Shell/UsersShell.php:66 +msgid "Add a new user" +msgstr "Yeni bir kullanıcı ekle" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Belirli bir kullanıcının rolünü değiştir" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Belirli bir kullanıcıyı devre dışı bırak" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Belirli bir kullanıcıyı sil" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Şifreyi e-posta aracılığıyla sıfırla" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Tüm kullanıcıların şifrelerini sıfırla" + +#: Shell/UsersShell.php:84 +msgid "Reset the password for an specific user" +msgstr "Belirli bir kullanıcının şifresini sıfırla" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Lütfen bir şifre gir." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Tüm kullanıcıların şifreleri değiştirildi" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Yeni şifre: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Lütfen bir kullanıcı adı gir." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Kullanıcı için şifre değiştirildi: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Lütfen bir rol gir." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Kullanıcı rolü değiştirildi: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Yeni rol: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Kullanıcı aktif hale getirildi: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Kullanıcı devre dışı bırakıldı: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Lütfen kullanıcı adı veya e-posta girin." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Şifre sıfırlama işlemine devam etmek için, lütfen kullanıcıya e-posta " +"hesabını kontrol etmesini söyleyin" + +#: Shell/UsersShell.php:308 +msgid "Superuser added:" +msgstr "Süperkullanıcı eklendi:" + +#: Shell/UsersShell.php:310 +msgid "User added:" +msgstr "Kullanıcı eklendi:" + +#: Shell/UsersShell.php:312 +msgid "Id: {0}" +msgstr "Id: {0}" + +#: Shell/UsersShell.php:313 +msgid "Username: {0}" +msgstr "Kullanıcı Adı: {0}" + +#: Shell/UsersShell.php:314 +msgid "Email: {0}" +msgstr "E-posta: {0}" + +#: Shell/UsersShell.php:315 +msgid "Role: {0}" +msgstr "Rol: {0}" + +#: Shell/UsersShell.php:316 +msgid "Password: {0}" +msgstr "Şifre: {0}" + +#: Shell/UsersShell.php:318 +msgid "User could not be added:" +msgstr "Kullanıcı eklenemedi:" + +#: Shell/UsersShell.php:321 +msgid "Field: {0} Error: {1}" +msgstr "Alan: {0} Hata: {1}" + +#: Shell/UsersShell.php:337 +msgid "The user was not found." +msgstr "Kullanıcı bulunamadı." + +#: Shell/UsersShell.php:367 +msgid "The user {0} was not deleted. Please try again" +msgstr "Kullanıcı {0} silinemedi. Lütfen tekrar deneyin" + +#: Shell/UsersShell.php:369 +msgid "The user {0} was deleted successfully" +msgstr "Kullanıcı {0} başarıyla silindi" + +#: 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 "Merhaba {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +msgstr "Şifrenizi buradan sıfırlayın" + +#: 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 "" +"Eğer bağlantı düzgün şekilde gözükmüyorsa, lütfen adresi internet tarayıcına " +"kopyala {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 "Teşekkürler" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Sosyal girişi buradan aktif hale getirin" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Hesabınızı buradan aktif hale getirin" + +#: 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 "Lütfen adresi internet tarayıcınıza kopyalayın {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 "" +"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: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:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Kullanıcıları Listele" + +#: 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: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: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-posta" + +#: 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: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: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: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: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 "Gönder" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Lütfen yeni bir şifre gir" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Geçerli şifre" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Yeni şifre" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Şifre onayla" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Sil" + +#: 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:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Kullanıcıyı düzenle" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Jeton" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Jeton Son Kullanma Tarihi" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "API jetonu" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Aktivasyon tarihi" + +#: Template/Users/edit.ctp:51 +msgid "TOS date" +msgstr "TOS tarihi" + +#: Template/Users/edit.ctp:64 +msgid "Reset Google Authenticator Token" +msgstr "Google Authenticator Jetonunu Sıfırla" + +#: 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?" + +#: Template/Users/index.ctp:15 +msgid "New {0}" +msgstr "Yeni {0}" + +#: Template/Users/index.ctp:37 +msgid "View" +msgstr "Görünüm" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Şifre değiştir" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Düzenle" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "önceki" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "sonraki" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Lütfen kullanıcı adını ve şifreni gir" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Beni Hatırla" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Kayıt Ol" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Şifre Sıfırla" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Giriş" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Şifre Değiştir" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Sosyal Hesaplar" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Avatar" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Sağlayıcı" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Bağlantı" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Şuna bağlı {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "TOS koşullarını kabul ediyor musun?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Şifreni sıfırlamak için lütfen e-postanı gir" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Doğrulama e-postasını yeniden gönder" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "E-posta veta kullanıcı adı" + +#: Template/Users/verify.ctp:13 +msgid "Verification Code" +msgstr "Doğrulama Kodu" + +#: Template/Users/verify.ctp:15 +msgid "" +" " +"Verify" +msgstr "" +" Verify" + +#: Template/Users/view.ctp:19 +msgid "Delete User" +msgstr "Kullanıcıyı Sil" + +#: Template/Users/view.ctp:24 +msgid "New User" +msgstr "Yeni Kullanıcı" + +#: Template/Users/view.ctp:31 +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" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Rol" + +#: Template/Users/view.ctp:45 +msgid "Api Token" +msgstr "Api Jeton" + +#: Template/Users/view.ctp:53 +msgid "Token Expires" +msgstr "Jeton Bitiş Tarihi" + +#: Template/Users/view.ctp:55 +msgid "Activation Date" +msgstr "Aktivasyon Tarihi" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Tos Tarihi" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Oluşturulmuş" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Değiştirilmiş" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Şununla giriş yapın" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Çıkış" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Hoşgeldiniz, {0}" + +#: 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:215 +msgid "Connected with {0}" +msgstr "Şununla bağlanıldı {0}" + +#: 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/ApiKeyAuthenticate.php b/src/Auth/ApiKeyAuthenticate.php deleted file mode 100644 index 2a7d358bf..000000000 --- a/src/Auth/ApiKeyAuthenticate.php +++ /dev/null @@ -1,127 +0,0 @@ - self::TYPE_QUERYSTRING, - //name to retrieve the api key value from - 'name' => 'api_key', - //db field where the key is stored - 'field' => 'api_token', - //require SSL to pass the token. You should always require SSL to use tokens for Auth - 'require_ssl' => true, - ]; - - /** - * Authenticate callback - * Reads the API Key based on configuration and login the user - * - * @param Request $request Cake request object. - * @param Response $response Cake response object. - * @return mixed - */ - public function authenticate(Request $request, Response $response) - { - return $this->getUser($request); - } - - /** - * Stateless Authentication System - * http://book.cakephp.org/3.0/en/controllers/components/authentication.html#creating-stateless-authentication-systems - * - * Config: - * $this->Auth->config('storage', 'Memory'); - * $this->Auth->config('unauthorizedRedirect', 'false'); - * $this->Auth->config('checkAuthIn', 'Controller.initialize'); - * $this->Auth->config('loginAction', false); - * - * @param Request $request Cake request object. - * @return mixed - */ - public function getUser(Request $request) - { - $type = $this->config('type'); - if (!in_array($type, $this->types)) { - throw new OutOfBoundsException(__d('CakeDC/Users', 'Type {0} is not valid', $type)); - } - - if (!is_callable([$this, $type])) { - throw new OutOfBoundsException(__d('CakeDC/Users', 'Type {0} has no associated callable', $type)); - } - - $apiKey = $this->$type($request); - if (empty($apiKey)) { - return false; - } - - if ($this->config('require_ssl') && !$request->is('ssl')) { - throw new ForbiddenException(__d('CakeDC/Users', 'SSL is required for ApiKey Authentication', $type)); - } - - $this->_config['fields']['username'] = $this->config('field'); - $this->_config['userModel'] = Configure::read('Users.table'); - $this->_config['finder'] = 'all'; - $result = $this->_query($apiKey)->first(); - - if (empty($result)) { - return false; - } - - return $result->toArray(); - //idea: add array with checks to be passed to $request->is(...) - } - - /** - * Get the api key from the querystring - * - * @param Request $request request - * @return string api key - */ - public function querystring(Request $request) - { - $name = $this->config('name'); - - return $request->query($name); - } - - /** - * Get the api key from the header - * - * @param Request $request request - * @return string api key - */ - public function header(Request $request) - { - $name = $this->config('name'); - - return $request->header($name); - } -} diff --git a/src/Auth/Exception/InvalidProviderException.php b/src/Auth/Exception/InvalidProviderException.php deleted file mode 100644 index 1825d475c..000000000 --- a/src/Auth/Exception/InvalidProviderException.php +++ /dev/null @@ -1,10 +0,0 @@ -_registry->Cookie->read($cookieName); - if (empty($cookie)) { - return false; - } - $this->config('fields.username', 'id'); - $user = $this->_findUser($cookie['id']); - if ($user && - !empty($cookie['user_agent']) && - $request->header('User-Agent') === $cookie['user_agent']) { - return $user; - } - - return false; - } -} diff --git a/src/Auth/Rules/AbstractRule.php b/src/Auth/Rules/AbstractRule.php deleted file mode 100644 index 17dacc8a5..000000000 --- a/src/Auth/Rules/AbstractRule.php +++ /dev/null @@ -1,94 +0,0 @@ -config($config); - } - - /** - * Get a table from the alias, table object or inspecting the request for a default table - * - * @param Request $request request - * @param mixed $table table - * @return \Cake\ORM\Table - * @throw OutOfBoundsException if table alias is empty - */ - protected function _getTable(Request $request, $table = null) - { - if (empty($table)) { - return $this->_getTableFromRequest($request); - } - if ($table instanceof Table) { - return $table; - } - - return TableRegistry::get($table); - } - - /** - * Inspect the request and try to retrieve a table based on the current controller - * - * @param Request $request request - * @return Table - * @throws OutOfBoundsException if table alias can't be extracted from request - */ - protected function _getTableFromRequest(Request $request) - { - $plugin = Hash::get($request->params, 'plugin'); - $controller = Hash::get($request->params, 'controller'); - $modelClass = ($plugin ? $plugin . '.' : '') . $controller; - - $this->modelFactory('Table', [$this->tableLocator(), 'get']); - if (empty($modelClass)) { - throw new OutOfBoundsException(__d('CakeDC/Users', 'Table alias is empty, please define a table alias, we could not extract a default table from the request')); - } - - return $this->loadModel($modelClass); - } - - /** - * Check the current entity is owned by the logged in user - * - * @param array $user Auth array with the logged in data - * @param string $role role of the user - * @param Request $request current request, used to get a default table if not provided - * @return bool - * @throws OutOfBoundsException if table is not found or it doesn't have the expected fields - */ - abstract public function allowed(array $user, $role, Request $request); -} diff --git a/src/Auth/Rules/Owner.php b/src/Auth/Rules/Owner.php deleted file mode 100644 index c5373f001..000000000 --- a/src/Auth/Rules/Owner.php +++ /dev/null @@ -1,83 +0,0 @@ - 'user_id', - /* - * request key type to retrieve the table id, could be "params", "query", "data" to locate the table id - * example: - * yoursite.com/controller/action/XXX would be - * tableKeyType => 'params', 'tableIdParamsKey' => 'pass.0' - * yoursite.com/controlerr/action?post_id=XXX would be - * tableKeyType => 'query', 'tableIdParamsKey' => 'post_id' - * yoursite.com/controller/action [posted form with a field named post_id] would be - * tableKeyType => 'data', 'tableIdParamsKey' => 'post_id' - */ - 'tableKeyType' => 'params', - // request->params key path to retrieve the owned table id - 'tableIdParamsKey' => 'pass.0', - /* - * define table to use or pick it from controller name defaults if null - * if null, table used will be based on current controller's default table - * if string, TableRegistry::get will be used - * if Table, the table object will be used - */ - 'table' => null, - /* - * define the table id to be used to match the row id, this is useful when checking belongsToMany associations - * Example: If checking ownership in a PostsUsers table, we should use 'id' => 'post_id' - * If value is null, we'll use the $table->primaryKey() - */ - 'id' => null, - 'conditions' => [], - ]; - - /** - * {@inheritdoc} - */ - public function allowed(array $user, $role, Request $request) - { - $table = $this->_getTable($request, $this->config('table')); - //retrieve table id from request - $id = Hash::get($request->{$this->config('tableKeyType')}, $this->config('tableIdParamsKey')); - $userId = Hash::get($user, 'id'); - - try { - if (!$table->hasField($this->config('ownerForeignKey'))) { - throw new OutOfBoundsException(__d('CakeDC/Users', 'Missing column {0} in table {1} while checking ownership permissions for user {2}', $this->config('ownerForeignKey'), $table->alias(), $userId)); - } - } catch (Exception $ex) { - throw new OutOfBoundsException(__d('CakeDC/Users', 'Missing column {0} in table {1} while checking ownership permissions for user {2}', $this->config('ownerForeignKey'), $table->alias(), $userId)); - } - $idColumn = $this->config('id'); - if (empty($idColumn)) { - $idColumn = $table->primaryKey(); - } - $conditions = array_merge([ - $idColumn => $id, - $this->config('ownerForeignKey') => $userId - ], $this->config('conditions')); - - return $table->exists($conditions); - } -} diff --git a/src/Auth/Rules/Rule.php b/src/Auth/Rules/Rule.php deleted file mode 100644 index 7ba6c438d..000000000 --- a/src/Auth/Rules/Rule.php +++ /dev/null @@ -1,27 +0,0 @@ - 'permissions', - //role field in the Users table - 'role_field' => 'role', - //default role, used in new users registered and also as role matcher when no role is available - 'default_role' => 'user', - /* - * This is a quick roles-permissions implementation - * Rules are evaluated top-down, first matching rule will apply - * Each line define - * [ - * 'role' => 'admin', - * 'plugin', (optional, default = null) - * 'prefix', (optional, default = null) - * 'extension', (optional, default = null) - * 'controller', - * 'action', - * 'allowed' (optional, default = true) - * ] - * You could use '*' to match anything - * You could use [] to match an array of options, example 'role' => ['adm1', 'adm2'] - * You could use a callback in your 'allowed' to process complex authentication, like - * - ownership - * - permissions stored in your database - * - permission based on an external service API call - * You could use an instance of the \CakeDC\Users\Auth\Rules\Rule interface to reuse your custom rules - * - * Examples: - * 1. Callback to allow users editing their own Posts: - * - * 'allowed' => function (array $user, $role, Request $request) { - * $postId = Hash::get($request->params, 'pass.0'); - * $post = TableRegistry::get('Posts')->get($postId); - * $userId = Hash::get($user, 'id'); - * if (!empty($post->user_id) && !empty($userId)) { - * return $post->user_id === $userId; - * } - * return false; - * } - * 2. Using the Owner Rule - * 'allowed' => new Owner() //will pick by default the post id from the first pass param - * - * Check the Owner Rule docs for more details - * - * - */ - 'permissions' => [], - ]; - - /** - * Default permissions to be loaded if no provided permissions - * - * @var array - */ - protected $_defaultPermissions = [ - //admin role allowed to use CakeDC\Users plugin actions - [ - 'role' => 'admin', - 'plugin' => '*', - 'controller' => '*', - 'action' => '*', - ], - //specific actions allowed for the user role in Users plugin - [ - 'role' => 'user', - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => ['profile', 'logout'], - ], - //all roles allowed to Pages/display - [ - 'role' => '*', - 'plugin' => null, - 'controller' => ['Pages'], - 'action' => ['display'], - ], - ]; - - /** - * Autoload permission configuration - * @param ComponentRegistry $registry component registry - * @param array $config config - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - parent::__construct($registry, $config); - $autoload = $this->config('autoload_config'); - if ($autoload) { - $loadedPermissions = $this->_loadPermissions($autoload, 'default'); - $this->config('permissions', $loadedPermissions); - } - } - - /** - * Load config and retrieve permissions - * If the configuration file does not exist, or the permissions key not present, return defaultPermissions - * To be mocked - * - * @param string $key name of the configuration file to read permissions from - * @return array permissions - */ - protected function _loadPermissions($key) - { - try { - Configure::load($key, 'default'); - $permissions = Configure::read('Users.SimpleRbac.permissions'); - } catch (Exception $ex) { - $msg = __d('CakeDC/Users', 'Missing configuration file: "config/{0}.php". Using default permissions', $key); - $this->log($msg, LogLevel::WARNING); - } - - if (empty($permissions)) { - return $this->_defaultPermissions; - } - - return $permissions; - } - - /** - * Match the current plugin/controller/action against loaded permissions - * Set a default role if no role is provided - * - * @param array $user user data - * @param Request $request request - * @return bool - */ - public function authorize($user, Request $request) - { - $roleField = $this->config('role_field'); - $role = $this->config('default_role'); - if (Hash::check($user, $roleField)) { - $role = Hash::get($user, $roleField); - } - - $allowed = $this->_checkRules($user, $role, $request); - - return $allowed; - } - - /** - * Match against permissions, return if matched - * Permissions are processed based on the 'permissions' config values - * - * @param array $user current user array - * @param string $role effective role for the current user - * @param Request $request request - * @return bool true if there is a match in permissions - */ - protected function _checkRules(array $user, $role, Request $request) - { - $permissions = $this->config('permissions'); - foreach ($permissions as $permission) { - $allowed = $this->_matchRule($permission, $user, $role, $request); - if ($allowed !== null) { - return $allowed; - } - } - - return false; - } - - /** - * Match the rule for current permission - * - * @param array $permission configuration - * @param array $user current user - * @param string $role effective user role - * @param Request $request request - * @return bool if rule matched, null if rule not matched - */ - protected function _matchRule($permission, $user, $role, $request) - { - $plugin = $request->plugin; - $controller = $request->controller; - $action = $request->action; - $prefix = null; - $extension = null; - if (!empty($request->params['prefix'])) { - $prefix = $request->params['prefix']; - } - if (!empty($request->params['_ext'])) { - $extension = $request->params['_ext']; - } - if ($this->_matchOrAsterisk($permission, 'role', $role) && - $this->_matchOrAsterisk($permission, 'prefix', $prefix, true) && - $this->_matchOrAsterisk($permission, 'plugin', $plugin, true) && - $this->_matchOrAsterisk($permission, 'extension', $extension, true) && - $this->_matchOrAsterisk($permission, 'controller', $controller) && - $this->_matchOrAsterisk($permission, 'action', $action)) { - $allowed = Hash::get($permission, 'allowed'); - - if ($allowed === null) { - //allowed will be true by default - return true; - } elseif (is_callable($allowed)) { - return (bool)call_user_func($allowed, $user, $role, $request); - } elseif ($allowed instanceof Rule) { - return (bool)$allowed->allowed($user, $role, $request); - } else { - return (bool)$allowed; - } - } - - return null; - } - - /** - * Check if rule matched or '*' present in rule matching anything - * - * @param string $permission permission configuration - * @param string $key key to retrieve and check in permissions configuration - * @param string $value value to check with (coming from the request) We'll check the DASHERIZED value too - * @param bool $allowEmpty true if we allow - * @return bool - */ - protected function _matchOrAsterisk($permission, $key, $value, $allowEmpty = false) - { - $possibleValues = (array)Hash::get($permission, $key); - if ($allowEmpty && empty($possibleValues) && $value === null) { - return true; - } - if (Hash::get($permission, $key) === '*' || - in_array($value, $possibleValues) || - in_array(Inflector::camelize($value, '-'), $possibleValues)) { - return true; - } - - return false; - } -} diff --git a/src/Auth/Social/Mapper/AbstractMapper.php b/src/Auth/Social/Mapper/AbstractMapper.php deleted file mode 100644 index 16b3102d0..000000000 --- a/src/Auth/Social/Mapper/AbstractMapper.php +++ /dev/null @@ -1,115 +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/Facebook.php b/src/Auth/Social/Mapper/Facebook.php deleted file mode 100644 index 95f3c52c3..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 86e0717b8..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 267890f44..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 6edd447eb..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 cf2bfe37b..000000000 --- a/src/Auth/Social/Mapper/Twitter.php +++ /dev/null @@ -1,51 +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']); - } -} diff --git a/src/Auth/Social/Util/SocialUtils.php b/src/Auth/Social/Util/SocialUtils.php deleted file mode 100644 index c0c8278b9..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 45d097c1f..000000000 --- a/src/Auth/SocialAuthenticate.php +++ /dev/null @@ -1,460 +0,0 @@ - $options) { - if (!empty($options['options']['redirectUri']) && - !empty($options['options']['clientId']) && - !empty($options['options']['clientSecret'])) { - $providers[$provider] = $options; - } - } - $oauthConfig['providers'] = $providers; - Configure::write('OAuth2', $oauthConfig); - $config = $this->normalizeConfig(array_merge($config, $oauthConfig)); - parent::__construct($registry, $config); - } - - /** - * Normalizes providers' configuration. - * - * @param array $config Array of config to normalize. - * @return array - * @throws \Exception - */ - public function normalizeConfig(array $config) - { - $config = Hash::merge((array)Configure::read('OAuth2'), $config); - - if (empty($config['providers'])) { - throw new MissingProviderConfigurationException(); - } - - 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(); - } - - /** - * Get a user based on information in the request. - * - * @param \Cake\Network\Request $request Request object. - * @param \Cake\Network\Response $response Response object. - * @return bool - * @throws \RuntimeException If the `CakeDC/Users/OAuth2.newUser` event is missing or returns empty. - */ - public function authenticate(Request $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\Network\Request $request Request object. - * @return array|bool - */ - protected function _authenticate(Request $request) - { - if (!$this->_validate($request)) { - return false; - } - - $provider = $this->provider($request); - $code = $request->query('code'); - - try { - $token = $provider->getAccessToken('authorization_code', compact('code')); - - return compact('token') + $provider->getResourceOwner($token)->toArray(); - } catch (\Exception $e) { - return false; - } - } - - /** - * Validates OAuth2 request. - * - * @param \Cake\Network\Request $request Request object. - * @return bool - */ - protected function _validate(Request $request) - { - if (!array_key_exists('code', $request->query) || !$this->provider($request)) { - return false; - } - - $session = $request->session(); - $sessionKey = 'oauth2state'; - $state = $request->query('state'); - - if ($this->config('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->config('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\Network\Request $request Request object. - * @param \Cake\Network\Response $response Response object. - * @return \Cake\Network\Response|null - */ - public function unauthenticated(Request $request, Response $response) - { - $provider = $this->provider($request); - if (empty($provider) || !empty($request->query['code'])) { - return null; - } - - if ($this->config('options.state')) { - $request->session()->write('oauth2state', $provider->getState()); - } - - $response->location($provider->getAuthorizationUrl()); - - return $response; - } - - /** - * Returns the `$request`-ed provider. - * - * @param \Cake\Network\Request $request Current HTTP request. - * @return \League\Oauth2\Client\Provider\GenericProvider|false - */ - public function provider(Request $request) - { - if (!$alias = $request->param('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->config('providers.' . $alias)) { - return false; - } - - $this->config($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]; - $event = $this->_getController()->dispatchEvent(UsersAuthComponent::EVENT_FAILED_SOCIAL_LOGIN, $args); - if (method_exists($this->_getController(), 'failedSocialLogin')) { - $this->_getController()->failedSocialLogin($exception, $data, true); - } - - return $event->result; - } - - // 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\Network\Request $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(Request $request) - { - $data = $request->session()->read(Configure::read('Users.Key.Session.social')); - $requestDataEmail = $request->data('email'); - if (!empty($data) && (!empty($data['email']) || !empty($requestDataEmail))) { - if (!empty($requestDataEmail)) { - $data['email'] = $requestDataEmail; - } - $user = $data; - $request->session()->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); - } catch (MissingProviderException $ex) { - $request->session()->delete(Configure::read('Users.Key.Session.social')); - throw $ex; - } - if ($user['provider'] === SocialAccountsTable::PROVIDER_TWITTER) { - $request->session()->write(Configure::read('Users.Key.Session.social'), $user); - } - } - - if (!$user || !$this->config('userModel')) { - return false; - } - - if (!$result = $this->_touch($user)) { - return false; - } - - if ($request->session()->check(Configure::read('Users.Key.Session.social'))) { - $request->session()->delete(Configure::read('Users.Key.Session.social')); - } - - return $result; - } - - /** - * Get the provider name based on the request or on the provider set. - * - * @param \Cake\Network\Request $request Request object. - * @return mixed Either false or an array of user information - */ - protected function _getProviderName($request = null) - { - $provider = false; - if (!is_null($this->_provider)) { - $provider = SocialUtils::getProvider($this->_provider); - } elseif (!empty($request)) { - $provider = ucfirst($request->param('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 = "\\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::get($userModel); - $user = $User->socialLogin($data, $options); - - return $user; - } -} diff --git a/src/Auth/SuperuserAuthorize.php b/src/Auth/SuperuserAuthorize.php deleted file mode 100644 index 3a37f24c4..000000000 --- a/src/Auth/SuperuserAuthorize.php +++ /dev/null @@ -1,53 +0,0 @@ - 'is_superuser', - ]; - - /** - * Check if the user is superuser - * - * @param type $user User information object. - * @param Request $request Cake request object. - * @return bool - */ - public function authorize($user, Request $request) - { - $user = (array)$user; - $superuserField = $this->config('superuser_field'); - if (Hash::check($user, $superuserField)) { - return (bool)Hash::get($user, $superuserField); - } - - return false; - } -} 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 6aa6f5872..68c4951e2 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -1,11 +1,13 @@ loadComponent('Security'); - $this->loadComponent('Csrf'); - $this->loadComponent('CakeDC/Users.UsersAuth'); + if ($this->request->getParam('_csrfToken') === false) { + $this->loadComponent('Csrf'); + } + $this->loadComponent('CakeDC/Users.Setup'); } } 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 ae3c41201..000000000 --- a/src/Controller/Component/RememberMeComponent.php +++ /dev/null @@ -1,153 +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::salt(), '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->_registry->getController()->eventManager(); - $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->request->header('User-Agent'); - $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->request->is(['post', 'put']) || $this->request->action === 'logout' || $this->request->session()->check(Configure::read('Users.Key.Session.social')) || $this->request->param('provider')) { - return; - } - - $user = $this->Auth->identify(); - //No user no cookies - if (empty($user)) { - return; - } - $this->Auth->setUser($user); - $event = $this->_registry->getController()->dispatchEvent(UsersAuthComponent::EVENT_AFTER_COOKIE_LOGIN); - if (is_array($event->result)) { - return $this->_registry->getController()->redirect($event->result); - } - $url = $this->Auth->redirectUrl(); - - return $this->_registry->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 3257e607d..000000000 --- a/src/Controller/Component/UsersAuthComponent.php +++ /dev/null @@ -1,199 +0,0 @@ -_validateConfig(); - $this->_initAuth(); - - if (Configure::read('Users.Social.login')) { - $this->_loadSocialLogin(); - } - if (Configure::read('Users.RememberMe.active')) { - $this->_loadRememberMe(); - } - - $this->_attachPermissionChecker(); - } - - /** - * Load Social Auth object - * - * @return void - */ - protected function _loadSocialLogin() - { - $this->_registry->getController()->Auth->config('authenticate', [ - 'CakeDC/Users.Social' - ], true); - } - - /** - * Load RememberMe component and Auth objects - * - * @return void - */ - protected function _loadRememberMe() - { - $this->_registry->getController()->loadComponent('CakeDC/Users.RememberMe'); - } - - /** - * Attach the isUrlAuthorized event to allow using the Auth authorize from the UserHelper - * - * @return void - */ - protected function _attachPermissionChecker() - { - $this->_registry->getController()->eventManager()->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->_registry->getController()->loadComponent('Auth', Configure::read('Auth')); - } - - $this->_registry->getController()->Auth->allow([ - 'register', - 'validateEmail', - 'resendTokenValidation', - 'login', - 'twitterLogin', - 'socialEmail', - 'resetPassword', - 'requestResetPassword', - 'changePassword', - '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->data, 'url'); - if (empty($url)) { - return false; - } - - if (is_array($url)) { - $requestUrl = Router::reverse($url); - $requestParams = Router::parse($requestUrl); - } else { - try { - //remove base from $url if exists - $normalizedUrl = Router::normalize($url); - $requestParams = Router::parse($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->_registry->getController()->Auth->user(); - if (empty($user)) { - return false; - } - - $request = new Request($requestUrl); - $request->params = $requestParams; - - $isAuthorized = $this->_registry->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 - * @param array $requestParams request parameters - * @return bool - */ - protected function _isActionAllowed($requestParams = []) - { - if (empty($requestParams['action'])) { - return false; - } - $action = strtolower($requestParams['action']); - if (in_array($action, array_map('strtolower', $this->_registry->getController()->Auth->allowedActions))) { - return true; - } - - return false; - } -} diff --git a/src/Controller/SocialAccountsController.php b/src/Controller/SocialAccountsController.php index b33b5574c..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')); - } 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 already active')); + } catch (\Exception $exception) { + $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')); } /** @@ -69,26 +67,26 @@ public function validateAccount($provider, $reference, $token) * * @param string $provider provider * @param string $reference reference - * @return Response|void - * @throws \Users\Model\Table\AccountAlreadyActiveException + * @return mixed + * @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')); - } catch (Exception $exception) { - $this->Flash->error(__d('CakeDC/Users', 'Email could not be resent')); + $this->Flash->error(__d('cake_d_c/users', 'Social Account already active')); + } catch (\Exception $exception) { + $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 54f055600..c003996ca 100644 --- a/src/Controller/Traits/CustomUsersTableTrait.php +++ b/src/Controller/Traits/CustomUsersTableTrait.php @@ -1,11 +1,13 @@ _usersTable instanceof Table) { return $this->_usersTable; } - $this->_usersTable = TableRegistry::get(Configure::read('Users.table')); + $this->_usersTable = TableRegistry::getTableLocator()->get(Configure::read('Users.table')); return $this->_usersTable; } @@ -41,7 +45,7 @@ public function getUsersTable() /** * Set the users table * - * @param Table $table table + * @param \Cake\ORM\Table $table table * @return void */ public function setUsersTable(Table $table) diff --git a/src/Controller/Traits/LinkSocialTrait.php b/src/Controller/Traits/LinkSocialTrait.php new file mode 100644 index 000000000..a2fcf802f --- /dev/null +++ b/src/Controller/Traits/LinkSocialTrait.php @@ -0,0 +1,95 @@ +setRedirectUriField('callbackLinkSocialUri') + ->createFromProvider($alias) + ->getAuthorizationUrl($this->getRequest()); + + $this->dispatchEvent(Plugin::EVENT_BEFORE_SOCIAL_LOGIN_REDIRECT, [ + 'location' => $authUrl, + 'request' => $this->request, + ]); + + return $this->redirect($authUrl); + } + + /** + * 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('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']); + } + $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('cake_d_c/users', 'Social account was associated.')); + } + } catch (\Exception $e) { + $log = sprintf( + 'Error linking social account: %s %s', + $e->getMessage(), + $e + ); + $this->log($log); + + $this->Flash->error($message); + } + + return $this->redirect(['action' => 'profile']); + } +} diff --git a/src/Controller/Traits/LoginTrait.php b/src/Controller/Traits/LoginTrait.php index 1aa16833d..2b1c637ac 100644 --- a/src/Controller/Traits/LoginTrait.php +++ b/src/Controller/Traits/LoginTrait.php @@ -1,259 +1,82 @@ autoRender = false; - $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.redirectUri'), - ]); - $oauthToken = $this->request->query('oauth_token'); - $oauthVerifier = $this->request->query('oauth_verifier'); - if (!empty($oauthToken) && !empty($oauthVerifier)) { - $temporaryCredentials = $this->request->session()->read('temporary_credentials'); - $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $oauthToken, $oauthVerifier); - $user = (array)$server->getUserDetails($tokenCredentials); - $user['token'] = [ - 'accessToken' => $tokenCredentials->getIdentifier(), - 'tokenSecret' => $tokenCredentials->getSecret(), - ]; - $this->request->session()->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->session()->read(Configure::read('Users.Key.Session.social')), true); - } - } else { - $temporaryCredentials = $server->getTemporaryCredentials(); - $this->request->session()->write('temporary_credentials', $temporaryCredentials); - $url = $server->getAuthorizationUrl($temporaryCredentials); - - return $this->redirect($url); - } - } - - /** - * @param Event $event event - * @return void - */ - 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')); - } - $this->request->session()->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->Auth->config('authError', $msg); - $this->Auth->config('flash.params', ['class' => 'success']); - $this->request->session()->delete(Configure::read('Users.Key.Session.social')); - $this->Flash->success(__d('CakeDC/Users', $msg)); - } - - 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->param('provider'); - $socialUser = $this->request->session()->read(Configure::read('Users.Key.Session.social')); + $Login = LoginComponentLoader::forSocial($this); - if (empty($socialProvider) && empty($socialUser)) { - throw new NotFoundException(); - } - $user = $this->Auth->user(); - - 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(); - - 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); - } - if (!$this->request->is('post') && !$socialLogin) { - if ($this->Auth->user()) { - $msg = __d('CakeDC/Users', 'You are already logged in'); - $this->Flash->error($msg); - $url = $this->Auth->redirectUrl(); - - return $this->redirect($url); - } - } - } - - /** - * Check reCaptcha if enabled for login - * - * @return bool - */ - protected function _checkReCaptcha() - { - if (!Configure::read('Users.reCaptcha.login')) { - return true; - } + $this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY); + $Login = LoginComponentLoader::forForm($this); - return $this->validateReCaptcha( - $this->request->data('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 - * @return array - */ - protected function _afterIdentifyUser($user, $socialLogin = false) - { - if (!empty($user)) { - $this->Auth->setUser($user); - - $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'); - } - - return $this->redirect(Configure::read('Auth.loginAction')); - } + return $Login->handleLogin(true, false); } /** * Logout * - * @return type + * @return mixed */ public function logout() { - $eventBefore = $this->dispatchEvent(UsersAuthComponent::EVENT_BEFORE_LOGOUT); - if (is_array($eventBefore->result)) { - return $this->redirect($eventBefore->result); + $user = $this->getRequest()->getAttribute('identity'); + $user = $user ?? []; + + $eventBefore = $this->dispatchEvent(Plugin::EVENT_BEFORE_LOGOUT, ['user' => $user]); + if (is_array($eventBefore->getResult())) { + return $this->redirect($eventBefore->getResult()); } - $this->request->session()->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); - 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->session()->check(Configure::read('Users.Key.Session.social')); + 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 baa28ad93..09878ae2a 100644 --- a/src/Controller/Traits/PasswordManagementTrait.php +++ b/src/Controller/Traits/PasswordManagementTrait.php @@ -1,26 +1,30 @@ 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->session()->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->config('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->config('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); } - $user = $this->getUsersTable()->patchEntity($user, $this->request->data(), ['validate' => $validator]); - if ($user->errors()) { - $this->Flash->error(__d('CakeDC/Users', 'Password could not be changed')); + $this->getUsersTable()->setValidator('current', $validator); + $user = $this->getUsersTable()->patchEntity( + $user, + $this->getRequest()->getData(), + [ + 'validate' => 'current', + 'accessibleFields' => [ + 'current_password' => true, + 'password' => true, + 'password_confirm' => true, + ], + ] + ); + + if ($user->getErrors()) { + $this->Flash->error(__d('cake_d_c/users', 'Password could not be changed')); } else { - $user = $this->getUsersTable()->changePassword($user); - if ($user) { - $this->Flash->success(__d('CakeDC/Users', 'Password has been changed successfully')); + $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('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(__d('CakeDC/Users', '{0}', $wpe->getMessage())); + $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']); } @@ -98,39 +144,70 @@ public function resetPassword($token = null) /** * Reset password * - * @return void|\Cake\Network\Response + * @return void|\Cake\Http\Response */ 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->data('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()); } } + + /** + * resetOneTimePasswordAuthenticator + * + * Resets Google Authenticator token by setting secret_verified + * to false. + * + * @param mixed $id of the user record. + * @return mixed. + */ + public function resetOneTimePasswordAuthenticator($id = null) + { + if ($this->getRequest()->is('post')) { + try { + $query = $this->getUsersTable()->query(); + $query->update() + ->set(['secret_verified' => false, 'secret' => null]) + ->where(['id' => $id]); + $query->execute(); + + $message = __d('cake_d_c/users', 'Google Authenticator token was successfully reset'); + $this->Flash->success($message, 'default'); + } catch (\Exception $e) { + $this->Flash->error(__d('cake_d_c/users', 'Could not reset Google Authenticator'), 'default'); + } + } + + return $this->redirect($this->getRequest()->referer()); + } } diff --git a/src/Controller/Traits/ProfileTrait.php b/src/Controller/Traits/ProfileTrait.php index 6796d2f9f..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 394879d0e..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->data; - $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) { - if ($userSaved = $usersTable->register($user, $event->result->toArray(), $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); + } elseif (Configure::read('Users.Registration.showVerboseError') && count($errors) > 0) { + $this->set(compact('user')); + 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; } @@ -105,29 +134,30 @@ protected function _validateRegisterPost() } return $this->validateReCaptcha( - $this->request->data('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 afbdc54d8..33346d298 100644 --- a/src/Controller/Traits/SimpleCrudTrait.php +++ b/src/Controller/Traits/SimpleCrudTrait.php @@ -1,23 +1,24 @@ loadModel(); - $tableAlias = $table->alias(); + $tableAlias = $table->getAlias(); $this->set($tableAlias, $this->paginate($table)); $this->set('tableAlias', $tableAlias); $this->set('_serialize', [$tableAlias, 'tableAlias']); @@ -40,14 +41,14 @@ public function index() * * @param string|null $id User id. * @return void - * @throws NotFoundException When record not found. + * @throws \Cake\Http\Exception\NotFoundException When record not found. */ public function view($id = null) { $table = $this->loadModel(); - $tableAlias = $table->alias(); + $tableAlias = $table->getAlias(); $entity = $table->get($id, [ - 'contain' => [] + 'contain' => [], ]); $this->set($tableAlias, $entity); $this->set('tableAlias', $tableAlias); @@ -57,79 +58,79 @@ public function view($id = null) /** * Add method * - * @return void Redirects on successful add, renders view otherwise. + * @return mixed Redirects on successful add, renders view otherwise. */ public function add() { $table = $this->loadModel(); - $tableAlias = $table->alias(); - $entity = $table->newEntity(); + $tableAlias = $table->getAlias(); + $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->data); + $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)); } /** * Edit method * * @param string|null $id User id. - * @return void Redirects on successful edit, renders view otherwise. - * @throws NotFoundException When record not found. + * @return mixed Redirects on successful edit, renders view otherwise. + * @throws \Cake\Http\Exception\NotFoundException When record not found. */ public function edit($id = null) { $table = $this->loadModel(); - $tableAlias = $table->alias(); + $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->data); + $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->alias(); + $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 e922651b5..eddbb919f 100644 --- a/src/Controller/Traits/SocialTrait.php +++ b/src/Controller/Traits/SocialTrait.php @@ -1,48 +1,37 @@ request->session()->check(Configure::read('Users.Key.Session.social'))) { - throw new NotFoundException(); - } - $this->request->session()->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 7be8e21bc..16bcf90a0 100644 --- a/src/Controller/Traits/UserValidationTrait.php +++ b/src/Controller/Traits/UserValidationTrait.php @@ -1,26 +1,29 @@ 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->session()->write(Configure::read('Users.Key.Session.resetPasswordUserId'), $result->id); + $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']); @@ -77,31 +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->data('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' - ])) { - $this->Flash->success(__d('CakeDC/Users', 'Token has been reset successfully. Please check your email.')); + '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( + '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 996398a3c..096bbc9c1 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -1,39 +1,71 @@ components()->has('Security')) { + $this->Security->setConfig( + 'unlockedActions', + [ + 'login', + 'u2fRegister', + 'u2fRegisterFinish', + 'u2fAuthenticate', + 'u2fAuthenticateFinish', + 'webauthn2faRegister', + 'webauthn2faRegisterOptions', + 'webauthn2faAuthenticate', + 'webauthn2faAuthenticateOptions', + ] + ); + } + } } diff --git a/src/Email/EmailSender.php b/src/Email/EmailSender.php deleted file mode 100644 index 3b0fcb327..000000000 --- a/src/Email/EmailSender.php +++ /dev/null @@ -1,100 +0,0 @@ -getMailer( - 'CakeDC/Users.Users', - $this->_getEmailInstance($email) - ) - ->send('validation', [$user, __d('CakeDC/Users', 'Your account validation link')]); - } - - /** - * Send the reset password email - * - * @param EntityInterface $user User entity - * @param Email $email instance, if null the default email configuration with the - * @param string $template email template - * Users.validation template will be used, so set a ->template() if you pass an Email - * instance - * @return array email send result - */ - public function sendResetPasswordEmail(EntityInterface $user, Email $email = null, $template = 'CakeDC/Users.reset_password') - { - $this - ->getMailer( - 'CakeDC/Users.Users', - $this->_getEmailInstance($email) - ) - ->send('resetPassword', [$user, $template]); - } - - /** - * Send social validation email to the user - * - * @param EntityInterface $socialAccount social account - * @param EntityInterface $user user - * @param Email $email Email instance or null to use 'default' configuration - * @return mixed - */ - public function sendSocialValidationEmail(EntityInterface $socialAccount, EntityInterface $user, Email $email = null) - { - if (empty($email)) { - $template = 'CakeDC/Users.social_account_validation'; - } else { - $template = $email->template()['template']; - } - $this - ->getMailer( - 'CakeDC/Users.Users', - $this->_getEmailInstance($email) - ) - ->send('socialAccountValidation', [$user, $socialAccount, $template]); - } - - /** - * Get or initialize the email instance. Used for mocking. - * - * @param Email $email if email provided, we'll use the instance instead of creating a new one - * @return Email - */ - protected function _getEmailInstance(Email $email = null) - { - if ($email === null) { - $email = new Email('default'); - $email->emailFormat('both'); - } - - return $email; - } -} diff --git a/src/Exception/AccountAlreadyActiveException.php b/src/Exception/AccountAlreadyActiveException.php index 273c4de05..4cef96afd 100644 --- a/src/Exception/AccountAlreadyActiveException.php +++ b/src/Exception/AccountAlreadyActiveException.php @@ -1,18 +1,29 @@ '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 170beecb5..000000000 --- a/src/Locale/Users.pot +++ /dev/null @@ -1,815 +0,0 @@ -# LANGUAGE translation of CakePHP Application -# Copyright YEAR NAME -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2016-10-26 09:23+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/ApiKeyAuthenticate.php:73 -msgid "Type {0} is not valid" -msgstr "" - -#: Auth/ApiKeyAuthenticate.php:77 -msgid "Type {0} has no associated callable" -msgstr "" - -#: Auth/ApiKeyAuthenticate.php:86 -msgid "SSL is required for ApiKey Authentication" -msgstr "" - -#: Auth/SimpleRbacAuthorize.php:142 -msgid "Missing configuration file: \"config/{0}.php\". Using default permissions" -msgstr "" - -#: Auth/SocialAuthenticate.php:432 -msgid "Provider cannot be empty" -msgstr "" - -#: 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 "" - -#: Auth/Rules/Owner.php:67;70 -msgid "Missing column {0} in table {1} while checking ownership permissions for user {2}" -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:69 -msgid "Invalid app salt, app salt must be at least 256 bits (32 bytes) long" -msgstr "" - -#: Controller/Component/UsersAuthComponent.php:178 -msgid "You can't enable email validation workflow if use_email is false" -msgstr "" - -#: Controller/Traits/LoginTrait.php:96 -msgid "Issues trying to log in with your social account" -msgstr "" - -#: Controller/Traits/LoginTrait.php:101 -#: Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "" - -#: Controller/Traits/LoginTrait.php:108 -msgid "Your user has not been validated yet. Please check your inbox for instructions" -msgstr "" - -#: Controller/Traits/LoginTrait.php:110 -msgid "Your social account has not been validated yet. Please check your inbox for instructions" -msgstr "" - -#: Controller/Traits/LoginTrait.php:161 -#: Controller/Traits/RegisterTrait.php:81 -msgid "Invalid reCaptcha" -msgstr "" - -#: Controller/Traits/LoginTrait.php:171 -msgid "You are already logged in" -msgstr "" - -#: Controller/Traits/LoginTrait.php:217 -msgid "Username or password is incorrect" -msgstr "" - -#: Controller/Traits/LoginTrait.php:238 -msgid "You've successfully logged out" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:47;76 -#: Controller/Traits/ProfileTrait.php:49 -msgid "User was not found" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:64;72;80 -msgid "Password could not be changed" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:68 -msgid "Password has been changed successfully" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:78 -msgid "{0}" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:120 -msgid "Please check your email to continue with password reset process" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:123 -#: Shell/UsersShell.php:267 -msgid "The password token could not be generated. Please try again" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:129 -#: Controller/Traits/UserValidationTrait.php:100 -msgid "User {0} was not found" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:131 -msgid "The user is not active" -msgstr "" - -#: Controller/Traits/PasswordManagementTrait.php:133 -#: Controller/Traits/UserValidationTrait.php:95;104 -msgid "Token could not be reset" -msgstr "" - -#: Controller/Traits/ProfileTrait.php:53 -msgid "Not authorized, please login first" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:42 -msgid "You must log out to register a new user account" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:88 -msgid "The user could not be saved" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:122 -msgid "You have registered successfully, please log in" -msgstr "" - -#: Controller/Traits/RegisterTrait.php:124 -msgid "Please validate your account before log in" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:76;106 -msgid "The {0} has been saved" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:80;110 -msgid "The {0} could not be saved" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:130 -msgid "The {0} has been deleted" -msgstr "" - -#: Controller/Traits/SimpleCrudTrait.php:132 -msgid "The {0} could not be deleted" -msgstr "" - -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:42 -msgid "User account validated successfully" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:44 -msgid "User account could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:47 -msgid "User already active" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:53 -msgid "Reset password token was validated successfully" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:58 -msgid "Reset password token could not be validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:62 -msgid "Invalid validation type" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:65 -msgid "Invalid token or user account already validated" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:67 -msgid "Token already expired" -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:93 -msgid "Token has been reset successfully. Please check your email." -msgstr "" - -#: Controller/Traits/UserValidationTrait.php:102 -msgid "User {0} is already active" -msgstr "" - -#: Email/EmailSender.php:39 -msgid "Your account validation link" -msgstr "" - -#: Mailer/UsersMailer.php:55 -msgid "{0}Your reset password link" -msgstr "" - -#: Mailer/UsersMailer.php:78 -msgid "{0}Your social account validation link" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:56 -msgid "Reference cannot be null" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:61 -msgid "Token expiration cannot be empty" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:67;116 -msgid "User not found" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:111 -msgid "User account already validated" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:78 -msgid "User not active" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:121 -msgid "The current password does not match" -msgstr "" - -#: Model/Behavior/PasswordBehavior.php:124 -msgid "You cannot use the current password as the new one" -msgstr "" - -#: Model/Behavior/RegisterBehavior.php:89 -msgid "User not found for the given token and email." -msgstr "" - -#: Model/Behavior/RegisterBehavior.php:92 -msgid "Token has already expired user with no token" -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:56 -msgid "Unable to login user with reference {0}" -msgstr "" - -#: Model/Behavior/SocialBehavior.php:98 -msgid "Email not present" -msgstr "" - -#: Model/Table/UsersTable.php:82 -msgid "Your password does not match your confirm password. Please try again" -msgstr "" - -#: Model/Table/UsersTable.php:175 -msgid "Username already exists" -msgstr "" - -#: Model/Table/UsersTable.php:181 -msgid "Email already exists" -msgstr "" - -#: Model/Table/UsersTable.php:214 -msgid "Missing 'username' in options data" -msgstr "" - -#: Shell/UsersShell.php:54 -msgid "Utilities for CakeDC Users Plugin" -msgstr "" - -#: Shell/UsersShell.php:55 -msgid "Activate an specific user" -msgstr "" - -#: Shell/UsersShell.php:56 -msgid "Add a new superadmin user for testing purposes" -msgstr "" - -#: Shell/UsersShell.php:57 -msgid "Add a new user" -msgstr "" - -#: Shell/UsersShell.php:58 -msgid "Change the role for an specific user" -msgstr "" - -#: Shell/UsersShell.php:59 -msgid "Deactivate an specific user" -msgstr "" - -#: Shell/UsersShell.php:60 -msgid "Delete an specific user" -msgstr "" - -#: Shell/UsersShell.php:61 -msgid "Reset the password via email" -msgstr "" - -#: Shell/UsersShell.php:62 -msgid "Reset the password for all users" -msgstr "" - -#: Shell/UsersShell.php:63 -msgid "Reset the password for an specific user" -msgstr "" - -#: Shell/UsersShell.php:98 -msgid "User added:" -msgstr "" - -#: Shell/UsersShell.php:99;127 -msgid "Id: {0}" -msgstr "" - -#: Shell/UsersShell.php:100;128 -msgid "Username: {0}" -msgstr "" - -#: Shell/UsersShell.php:101;129 -msgid "Email: {0}" -msgstr "" - -#: Shell/UsersShell.php:102;130 -msgid "Password: {0}" -msgstr "" - -#: Shell/UsersShell.php:126 -msgid "Superuser added:" -msgstr "" - -#: Shell/UsersShell.php:132 -msgid "Superuser could not be added:" -msgstr "" - -#: Shell/UsersShell.php:135 -msgid "Field: {0} Error: {1}" -msgstr "" - -#: Shell/UsersShell.php:153;179 -msgid "Please enter a password." -msgstr "" - -#: Shell/UsersShell.php:157 -msgid "Password changed for all users" -msgstr "" - -#: Shell/UsersShell.php:158;186 -msgid "New password: {0}" -msgstr "" - -#: Shell/UsersShell.php:176;204;282;324 -msgid "Please enter a username." -msgstr "" - -#: Shell/UsersShell.php:185 -msgid "Password changed for user: {0}" -msgstr "" - -#: Shell/UsersShell.php:207 -msgid "Please enter a role." -msgstr "" - -#: Shell/UsersShell.php:213 -msgid "Role changed for user: {0}" -msgstr "" - -#: Shell/UsersShell.php:214 -msgid "New role: {0}" -msgstr "" - -#: Shell/UsersShell.php:229 -msgid "User was activated: {0}" -msgstr "" - -#: Shell/UsersShell.php:244 -msgid "User was de-activated: {0}" -msgstr "" - -#: Shell/UsersShell.php:256 -msgid "Please enter a username or email." -msgstr "" - -#: Shell/UsersShell.php:264 -msgid "Please ask the user to check the email to continue with password reset process" -msgstr "" - -#: Shell/UsersShell.php:302 -msgid "The user was not found." -msgstr "" - -#: Shell/UsersShell.php:332 -msgid "The user {0} was not deleted. Please try again" -msgstr "" - -#: Shell/UsersShell.php:334 -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 -msgid "If the link is not correcly displayed, please copy the following address in your web browser {0}" -msgstr "" - -#: 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 -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/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/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:15 -#: Template/Users/index.ctp:13;26 -#: Template/Users/view.ctp:15;79 -msgid "Actions" -msgstr "" - -#: Template/Users/add.ctp:15 -#: Template/Users/edit.ctp:26 -#: Template/Users/view.ctp:19 -msgid "List Users" -msgstr "" - -#: Template/Users/add.ctp:16 -#: Template/Users/edit.ctp:27 -#: Template/Users/view.ctp:21 -msgid "List Accounts" -msgstr "" - -#: Template/Users/add.ctp:22 -#: Template/Users/register.ctp:16 -msgid "Add User" -msgstr "" - -#: 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 -msgid "Username" -msgstr "" - -#: 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 -msgid "Email" -msgstr "" - -#: Template/Users/add.ctp:26 -#: Template/Users/edit.ctp:37 -#: Template/Users/index.ctp:24 -#: Template/Users/register.ctp:25 -msgid "First name" -msgstr "" - -#: Template/Users/add.ctp:27 -#: Template/Users/edit.ctp:38 -#: Template/Users/index.ctp:25 -#: Template/Users/register.ctp:26 -msgid "Last name" -msgstr "" - -#: Template/Users/add.ctp:30 -#: Template/Users/edit.ctp:53 -#: Template/Users/view.ctp:44;75 -msgid "Active" -msgstr "" - -#: 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 -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:23 -msgid "Confirm password" -msgstr "" - -#: Template/Users/edit.ctp:20 -#: Template/Users/index.ctp:40 -#: Template/Users/view.ctp:101 -msgid "Delete" -msgstr "" - -#: Template/Users/edit.ctp:22 -#: Template/Users/index.ctp:40 -#: Template/Users/view.ctp:18;101 -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:38;73 -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/index.ctp:15 -msgid "New {0}" -msgstr "" - -#: Template/Users/index.ctp:37 -#: Template/Users/view.ctp:97 -msgid "View" -msgstr "" - -#: Template/Users/index.ctp:38 -msgid "Change password" -msgstr "" - -#: Template/Users/index.ctp:39 -#: Template/Users/view.ctp:99 -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:18 -#: View/Helper/UserHelper.php:51 -msgid "{0} {1}" -msgstr "" - -#: Template/Users/profile.ctp:24 -msgid "Change Password" -msgstr "" - -#: Template/Users/profile.ctp:34 -msgid "Social Accounts" -msgstr "" - -#: Template/Users/profile.ctp:38 -#: Template/Users/view.ctp:72 -msgid "Avatar" -msgstr "" - -#: Template/Users/profile.ctp:39 -#: Template/Users/view.ctp:69 -msgid "Provider" -msgstr "" - -#: Template/Users/profile.ctp:40 -msgid "Link" -msgstr "" - -#: Template/Users/profile.ctp:47 -msgid "Link to {0}" -msgstr "" - -#: Template/Users/register.ctp:20 -msgid "Password" -msgstr "" - -#: Template/Users/register.ctp:28 -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/view.ctp:18 -msgid "Delete User" -msgstr "" - -#: Template/Users/view.ctp:20 -msgid "New User" -msgstr "" - -#: Template/Users/view.ctp:28;67 -msgid "Id" -msgstr "" - -#: Template/Users/view.ctp:34 -msgid "First Name" -msgstr "" - -#: Template/Users/view.ctp:36 -msgid "Last Name" -msgstr "" - -#: Template/Users/view.ctp:40 -msgid "Api Token" -msgstr "" - -#: Template/Users/view.ctp:48;74 -msgid "Token Expires" -msgstr "" - -#: Template/Users/view.ctp:50 -msgid "Activation Date" -msgstr "" - -#: Template/Users/view.ctp:52 -msgid "Tos Date" -msgstr "" - -#: Template/Users/view.ctp:54;77 -msgid "Created" -msgstr "" - -#: Template/Users/view.ctp:56;78 -msgid "Modified" -msgstr "" - -#: Template/Users/view.ctp:63 -msgid "Related Accounts" -msgstr "" - -#: Template/Users/view.ctp:68 -msgid "User Id" -msgstr "" - -#: Template/Users/view.ctp:71 -msgid "Reference" -msgstr "" - -#: Template/Users/view.ctp:76 -msgid "Data" -msgstr "" - -#: View/Helper/UserHelper.php:46 -msgid "Sign in with" -msgstr "" - -#: View/Helper/UserHelper.php:49 -msgid "fa fa-{0}" -msgstr "" - -#: View/Helper/UserHelper.php:52 -msgid "btn btn-social btn-{0} " -msgstr "" - -#: View/Helper/UserHelper.php:91 -msgid "Logout" -msgstr "" - -#: View/Helper/UserHelper.php:108 -msgid "Welcome, {0}" -msgstr "" - -#: View/Helper/UserHelper.php:131 -msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" -msgstr "" - -#: Model/Behavior/RegisterBehavior.php:148 -msgid "This field is required" -msgstr "" - diff --git a/src/Locale/es/Users.mo b/src/Locale/es/Users.mo deleted file mode 100644 index 3e447d987..000000000 Binary files a/src/Locale/es/Users.mo and /dev/null differ diff --git a/src/Locale/es/Users.po b/src/Locale/es/Users.po deleted file mode 100644 index 10aa3bcd6..000000000 --- a/src/Locale/es/Users.po +++ /dev/null @@ -1,802 +0,0 @@ -# LANGUAGE translation of CakePHP Application -# Copyright 2010 - 2015, Cake Development Corporation (http://cakedc.com) -# -msgid "" -msgstr "" -"Project-Id-Version: CakeDC Users\n" -"POT-Creation-Date: 2016-04-20 09:48-0300\n" -"Last-Translator: Jorge González \n" -"PO-Revision-Date: 2016-07-27 11:04+0100\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.8.4\n" -"X-Poedit-SourceCharset: UTF-8\n" - -#: Auth/ApiKeyAuthenticate.php:55 -msgid "Type {0} is not valid" -msgstr "El tipo {0} no es válido" - -#: Auth/ApiKeyAuthenticate.php:59 -msgid "Type {0} has no associated callable" -msgstr "El tipo {0} no tiene un callable asociado" - -#: Auth/ApiKeyAuthenticate.php:68 -msgid "SSL is required for ApiKey Authentication" -msgstr "SSL requerido para autenticación ApiKey" - -#: Auth/SimpleRbacAuthorize.php:141 -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:410 -msgid "Provider cannot be empty" -msgstr "El proveedor no puede ser vacío" - -#: Auth/Rules/AbstractRule.php:77 -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 es vacío, por favor defina un alias para la tabla ya " -"que no podemos extraer el nombre de la tabla del request" - -#: 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" - -#: Controller/SocialAccountsController.php:54 -msgid "Account could not be validated" -msgstr "No se pudo validar la cuenta" - -#: Controller/SocialAccountsController.php:57 -msgid "Invalid token and/or social account" -msgstr "Token o cuenta social incorrecta" - -#: Controller/SocialAccountsController.php:59;86 -msgid "Social Account already active" -msgstr "Cuenta social ya activa" - -#: Controller/SocialAccountsController.php:61 -msgid "Social Account could not be validated" -msgstr "No se pudo validar la cuenta social " - -#: Controller/SocialAccountsController.php:79 -msgid "Email sent successfully" -msgstr "Email enviado correctamente" - -#: Controller/SocialAccountsController.php:81 -msgid "Email could not be sent" -msgstr "No se puede enviar el email" - -#: Controller/SocialAccountsController.php:84 -msgid "Invalid account" -msgstr "Cuenta inválida" - -#: Controller/SocialAccountsController.php:88 -msgid "Email could not be resent" -msgstr "No se puede reenviar el Email" - -#: Controller/Component/RememberMeComponent.php:69 -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:157 -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:95 -msgid "Issues trying to log in with your social account" -msgstr "Hubo un problema al hacer login con su cuenta" - -#: Controller/Traits/LoginTrait.php:100 Template/Users/social_email.ctp:16 -msgid "Please enter your email" -msgstr "Por favor, introduzca su email" - -#: Controller/Traits/LoginTrait.php:106 -msgid "" -"Your user has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"El usuario no se ha validado todavía, Comprueba tu bandeja de entrada para " -"recuperar el enlace de validación" - -#: Controller/Traits/LoginTrait.php:108 -msgid "" -"Your social account has not been validated yet. Please check your inbox for " -"instructions" -msgstr "" -"La cuenta no se ha validado todavía, comprueba tu bandeja de entrada para " -"recuperar el enlace de validación" - -#: Controller/Traits/LoginTrait.php:157 Controller/Traits/RegisterTrait.php:73 -msgid "Invalid reCaptcha" -msgstr "El código reCaptcha no es válido" - -#: Controller/Traits/LoginTrait.php:165 -msgid "You are already logged in" -msgstr "Ya te has logueado en la aplicación" - -#: Controller/Traits/LoginTrait.php:209 -msgid "Username or password is incorrect" -msgstr "Usuario o contraseña incorrecta" - -#: Controller/Traits/LoginTrait.php:230 -msgid "You've successfully logged out" -msgstr "Ha cerrado sesión correctamente" - -#: Controller/Traits/PasswordManagementTrait.php:53;60;68 -msgid "Password could not be changed" -msgstr "No es posible cambiar la contraseña" - -#: Controller/Traits/PasswordManagementTrait.php:57 -msgid "Password has been changed successfully" -msgstr "Contraseña cambiada correctamente" - -#: Controller/Traits/PasswordManagementTrait.php:64 -#: Controller/Traits/ProfileTrait.php:49 -msgid "User was not found" -msgstr "Usuario no encontrado" - -#: Controller/Traits/PasswordManagementTrait.php:66 -msgid "The current password does not match" -msgstr "La contraseña actual no coincide" - -#: Controller/Traits/PasswordManagementTrait.php:108 -msgid "Please check your email to continue with password reset process" -msgstr "" -"Por favor, compruebe su correo para continuar con el proceso de " -"restablecimiento de contraseña" - -#: Controller/Traits/PasswordManagementTrait.php:111 Shell/UsersShell.php:266 -msgid "The password token could not be generated. Please try again" -msgstr "" -"No se pudo generar el token de contraseña. Por favor, inténtelo de nuevo" - -#: Controller/Traits/PasswordManagementTrait.php:116 -#: Controller/Traits/UserValidationTrait.php:98 -msgid "User {0} was not found" -msgstr "Usuario {0} no encontrado" - -#: Controller/Traits/PasswordManagementTrait.php:118 -msgid "The user is not active" -msgstr "El usuario no está activo" - -#: Controller/Traits/PasswordManagementTrait.php:120 -#: Controller/Traits/UserValidationTrait.php:94;102 -msgid "Token could not be reset" -msgstr "No se puede restablecer el token" - -#: Controller/Traits/ProfileTrait.php:52 -msgid "Not authorized, please login first" -msgstr "" -"No estás autorizado para realizar esta acción, por favor haz login primero" - -#: Controller/Traits/RegisterTrait.php:42 -msgid "You must log out to register a new user account" -msgstr "Debe cerrar sesión para registrar una nueva cuenta de usuario" - -#: Controller/Traits/RegisterTrait.php:79 -msgid "The user could not be saved" -msgstr "No se ha podido guardar el usuario" - -#: Controller/Traits/RegisterTrait.php:111 -msgid "You have registered successfully, please log in" -msgstr "Se ha registrado correctamente, por favor ingrese" - -#: Controller/Traits/RegisterTrait.php:113 -msgid "Please validate your account before log in" -msgstr "Por favor, valide su cuenta antes de ingresar" - -#: Controller/Traits/SimpleCrudTrait.php:76;105 -msgid "The {0} has been saved" -msgstr "El {0} ha sido guardado" - -#: Controller/Traits/SimpleCrudTrait.php:79;108 -msgid "The {0} could not be saved" -msgstr "El {0} no ha podido guardarse" - -#: Controller/Traits/SimpleCrudTrait.php:128 -msgid "The {0} has been deleted" -msgstr "El {0} ha sido eliminado" - -#: Controller/Traits/SimpleCrudTrait.php:130 -msgid "The {0} could not be deleted" -msgstr "El {0} no ha podido eliminarse" - -#: Controller/Traits/SocialTrait.php:39 -msgid "The reCaptcha could not be validated" -msgstr "El código reCaptcha no se pudo validar" - -#: Controller/Traits/UserValidationTrait.php:42 -msgid "User account validated successfully" -msgstr "Cuenta de usuario validada correctamente" - -#: Controller/Traits/UserValidationTrait.php:44 -msgid "User account could not be validated" -msgstr "No se pudo validar la cuenta de usuario" - -#: Controller/Traits/UserValidationTrait.php:47 -msgid "User already active" -msgstr "Usuario ya activo" - -#: Controller/Traits/UserValidationTrait.php:53 -msgid "Reset password token was validated successfully" -msgstr "Restablecimiento del token de contraseña validado correctamente" - -#: Controller/Traits/UserValidationTrait.php:57 -msgid "Reset password token could not be validated" -msgstr "Restablecimiento del token de contraseña no pudo validarse" - -#: Controller/Traits/UserValidationTrait.php:61 -msgid "Invalid validation type" -msgstr "Tipo de validación inválido" - -#: Controller/Traits/UserValidationTrait.php:64 -msgid "Invalid token or user account already validated" -msgstr "Token inválido, or tu cuenta ya se había validado anteriormente" - -#: Controller/Traits/UserValidationTrait.php:66 -msgid "Token already expired" -msgstr "Token ya expirado" - -#: Controller/Traits/UserValidationTrait.php:92 -msgid "Token has been reset successfully. Please check your email." -msgstr "" -"Se ha restablecido el token correctamente. Por favor compruebe su email." - -#: Controller/Traits/UserValidationTrait.php:100 -msgid "User {0} is already active" -msgstr "El usuario {0} ya está activo" - -#: Email/EmailSender.php:39 -msgid "Your account validation link" -msgstr "Enlace para validar su cuenta" - -#: Mailer/UsersMailer.php:55 -msgid "{0}Your reset password link" -msgstr "{0} Enlace para restablecer su contraseña" - -#: Mailer/UsersMailer.php:78 -msgid "{0}Your social account validation link" -msgstr "{0} Enlace de validación de su cuenta social" - -#: Model/Behavior/PasswordBehavior.php:56 -msgid "Reference cannot be null" -msgstr "La referencia no puede ser vacía" - -#: Model/Behavior/PasswordBehavior.php:61 -msgid "Token expiration cannot be empty" -msgstr "La fecha de expiración del Token no puede ser vacía" - -#: Model/Behavior/PasswordBehavior.php:67;115 -msgid "User not found" -msgstr "Usuario no encontrado" - -#: Model/Behavior/PasswordBehavior.php:71 -#: Model/Behavior/RegisterBehavior.php:110 -msgid "User account already validated" -msgstr "Tu usuario ya se había validado antes" - -#: Model/Behavior/PasswordBehavior.php:78 -msgid "User not active" -msgstr "El usuario no está activo" - -#: Model/Behavior/PasswordBehavior.php:120 -msgid "The old password does not match" -msgstr "La antigua contraseña no coincide" - -#: Model/Behavior/RegisterBehavior.php:88 -msgid "User not found for the given token and email." -msgstr "Usuario no encontrado para el token y email proporcionado" - -#: Model/Behavior/RegisterBehavior.php:91 -msgid "Token has already expired user with no token" -msgstr "El token ha expirado usuario sin token" - -#: Model/Behavior/SocialAccountBehavior.php:101;128 -msgid "Account already validated" -msgstr "Cuenta ya activada" - -#: Model/Behavior/SocialAccountBehavior.php:104;131 -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 -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:97 -msgid "Email not present" -msgstr "No se encuentra el email" - -#: Model/Table/UsersTable.php:81 -msgid "Your password does not match your confirm password. Please try again" -msgstr "" -"Su contraseña y la comprobación no concuerdan. Por favor inténtelo de nuevo" - -#: Model/Table/UsersTable.php:159 -msgid "Username already exists" -msgstr "Nombre de usuario ya existente" - -#: Model/Table/UsersTable.php:165 -msgid "Email already exists" -msgstr "Email ya existente" - -#: Model/Table/UsersTable.php:197 -msgid "Missing 'username' in options data" -msgstr "Falta 'username' en los datos de opciones" - -#: Shell/UsersShell.php:54 -msgid "Utilities for CakeDC Users Plugin" -msgstr "Utilidades para CakeDC Users Plugin" - -#: Shell/UsersShell.php:55 -msgid "Activate an specific user" -msgstr "Activar un usuario específico" - -#: Shell/UsersShell.php:56 -msgid "Add a new superadmin user for testing purposes" -msgstr "Añadir un nuevo superadmin" - -#: Shell/UsersShell.php:57 -msgid "Add a new user" -msgstr "Añadir un nuevo usuario" - -#: Shell/UsersShell.php:58 -msgid "Change the role for an specific user" -msgstr "Cambiar el rol de un usuario específico" - -#: Shell/UsersShell.php:59 -msgid "Deactivate an specific user" -msgstr "Desactivar un usuario" - -#: Shell/UsersShell.php:60 -msgid "Delete an specific user" -msgstr "Borrar un usuario" - -#: Shell/UsersShell.php:61 -msgid "Reset the password via email" -msgstr "Resetear la contraseña via email" - -#: Shell/UsersShell.php:62 -msgid "Reset the password for all users" -msgstr "Resetear la contraseña de todos los usuarios" - -#: Shell/UsersShell.php:63 -msgid "Reset the password for an specific user" -msgstr "Resetear la contraseña de un usuario concreto" - -#: Shell/UsersShell.php:97 -msgid "User added:" -msgstr "Usuario añadido:" - -#: Shell/UsersShell.php:98;126 -msgid "Id: {0}" -msgstr "Id: {0}" - -#: Shell/UsersShell.php:99;127 -msgid "Username: {0}" -msgstr "Nombre de usuario: {0}" - -#: Shell/UsersShell.php:100;128 -msgid "Email: {0}" -msgstr "Email: {0}" - -#: Shell/UsersShell.php:101;129 -msgid "Password: {0}" -msgstr "Contraseña: {0}" - -#: Shell/UsersShell.php:125 -msgid "Superuser added:" -msgstr "Superusuario añadido:" - -#: Shell/UsersShell.php:131 -msgid "Superuser could not be added:" -msgstr "No se pudo añadir un superusuario:" - -#: Shell/UsersShell.php:134 -msgid "Field: {0} Error: {1}" -msgstr "Campo: {0} Error: {1}" - -#: Shell/UsersShell.php:152;178 -msgid "Please enter a password." -msgstr "Por favor, introduzca una contraseña." - -#: Shell/UsersShell.php:156 -msgid "Password changed for all users" -msgstr "Contraseña cambiada para todos los usuarios" - -#: Shell/UsersShell.php:157;185 -msgid "New password: {0}" -msgstr "Nueva contraseña: {0}" - -#: Shell/UsersShell.php:175;203;281;321 -msgid "Please enter a username." -msgstr "Por favor introduzca nombre de usuario." - -#: Shell/UsersShell.php:184 -msgid "Password changed for user: {0}" -msgstr "Contraseña cambiada para el usuario: {0}" - -#: Shell/UsersShell.php:206 -msgid "Please enter a role." -msgstr "Por favor introduzca rol." - -#: Shell/UsersShell.php:212 -msgid "Role changed for user: {0}" -msgstr "Rol cambiado para el usuario: {0}" - -#: Shell/UsersShell.php:213 -msgid "New role: {0}" -msgstr "Nuevo rol: {0}" - -#: Shell/UsersShell.php:228 -msgid "User was activated: {0}" -msgstr "Usuario activado: {0}" - -#: Shell/UsersShell.php:243 -msgid "User was de-activated: {0}" -msgstr "Usuario desactivado: {0}" - -#: Shell/UsersShell.php:255 -msgid "Please enter a username or email." -msgstr "Por favor introduzca nombre de usuario o email." - -#: Shell/UsersShell.php:263 -msgid "" -"Please ask the user to check the email to continue with password reset " -"process" -msgstr "" -"Avise al usuario para que compruebe su bandeja de entrada, hemos enviado un " -"email con instrucciones para resetear la contraseña" - -#: Shell/UsersShell.php:300 -msgid "The user was not found." -msgstr "Usuario no encontrado." - -#: Shell/UsersShell.php:329 -msgid "The user {0} was not deleted. Please try again" -msgstr "El usuario {0} NO se ha borrado, por favor inténtelo de nuevo" - -#: Shell/UsersShell.php:331 -msgid "The user {0} was deleted successfully" -msgstr "El usuario {0} se borró correctamente" - -#: 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 "Hola {0}" - -#: Template/Email/html/reset_password.ctp:24 -msgid "Reset your password here" -msgstr "Restablezca su contraseña aquí" - -#: Template/Email/html/reset_password.ctp:27 -#: Template/Email/html/social_account_validation.ctp:32 -msgid "" -"If the link is not correcly displayed, please copy the following address in " -"your web browser {0}" -msgstr "" -"Si el enlace no se ve correctamente, por favor copie la siguente URL en su " -"navegador: {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 -msgid "Thank you" -msgstr "Gracias" - -#: Template/Email/html/social_account_validation.ctp:18 -msgid "Activate your social login here" -msgstr "Active su acceso social aquí" - -#: Template/Email/html/validation.ctp:24 -msgid "Activate your account here" -msgstr "Active su cuenta aquí" - -#: 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 "" -"Si el enlace no se muestra correctamente, por favor copie la siguiente " -"dirección en su navegador web {0}" - -#: 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 "Por favor copie la siguiente dirección en su navegador 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 "" -"Por favor copie la siguiente dirección en su navegador web para activar su " -"acceso social {0}" - -#: Template/Users/add.ctp:13 Template/Users/edit.ctp:13 -#: Template/Users/index.ctp:13;26 Template/Users/view.ctp:13;77 -msgid "Actions" -msgstr "Acciones" - -#: Template/Users/add.ctp:15 Template/Users/edit.ctp:21 -#: Template/Users/view.ctp:17 -msgid "List Users" -msgstr "Listar Usuarios" - -#: Template/Users/add.ctp:16 Template/Users/edit.ctp:22 -#: Template/Users/view.ctp:19 -msgid "List Accounts" -msgstr "Listar Cuentas" - -#: Template/Users/add.ctp:22 Template/Users/register.ctp:16 -msgid "Add User" -msgstr "Añadir Usuario" - -#: Template/Users/add.ctp:32 Template/Users/change_password.ctp:17 -#: Template/Users/edit.ctp:42 Template/Users/register.ctp:32 -#: Template/Users/request_reset_password.ctp:8 -#: Template/Users/resend_token_validation.ctp:20 -#: Template/Users/social_email.ctp:19 -msgid "Submit" -msgstr "Enviar" - -#: Template/Users/change_password.ctp:5 -msgid "Please enter the new password" -msgstr "Por favor introduzca la nueva contraseña" - -#: Template/Users/change_password.ctp:10 -msgid "Current password" -msgstr "Contraseña actual" - -#: Template/Users/edit.ctp:16 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:99 -msgid "Delete" -msgstr "Eliminar" - -#: Template/Users/edit.ctp:18 Template/Users/index.ctp:40 -#: Template/Users/view.ctp:16;99 -msgid "Are you sure you want to delete # {0}?" -msgstr "¿Está seguro que quiere eliminar # {0}?" - -#: Template/Users/edit.ctp:28 Template/Users/view.ctp:15 -msgid "Edit User" -msgstr "Editar Usuario" - -#: Template/Users/index.ctp:15 -msgid "New {0}" -msgstr "Nuevo {0}" - -#: Template/Users/index.ctp:37 Template/Users/view.ctp:95 -msgid "View" -msgstr "Ver" - -#: Template/Users/index.ctp:38 -msgid "Change password" -msgstr "Cambiar contraseña" - -#: Template/Users/index.ctp:39 Template/Users/view.ctp:97 -msgid "Edit" -msgstr "Editar" - -#: Template/Users/index.ctp:49 -msgid "previous" -msgstr "anterior" - -#: Template/Users/index.ctp:51 -msgid "next" -msgstr "siguiente" - -#: Template/Users/login.ctp:19 -msgid "Please enter your username and password" -msgstr "Por favor introduzca su usuario y contraseña" - -#: Template/Users/login.ctp:29 -msgid "Remember me" -msgstr "Recordarme" - -#: Template/Users/login.ctp:48 -msgid "Login" -msgstr "Ingresar" - -#: Template/Users/profile.ctp:18 View/Helper/UserHelper.php:51 -msgid "{0} {1}" -msgstr "{0} {1}" - -#: Template/Users/profile.ctp:24 -msgid "Change Password" -msgstr "Cambiar contraseña" - -#: Template/Users/profile.ctp:27 Template/Users/view.ctp:28;68 -msgid "Username" -msgstr "Usuario" - -#: Template/Users/profile.ctp:29 Template/Users/view.ctp:30 -msgid "Email" -msgstr "Email" - -#: Template/Users/profile.ctp:34 -msgid "Social Accounts" -msgstr "Cuentas sociales" - -#: Template/Users/profile.ctp:38 Template/Users/view.ctp:70 -msgid "Avatar" -msgstr "Avatar" - -#: Template/Users/profile.ctp:39 Template/Users/view.ctp:67 -msgid "Provider" -msgstr "Proveedor" - -#: Template/Users/profile.ctp:40 -msgid "Link" -msgstr "Enlace" - -#: Template/Users/profile.ctp:47 -msgid "Link to {0}" -msgstr "Enlace a {0}" - -#: Template/Users/register.ctp:25 -msgid "Accept TOS conditions?" -msgstr "¿Acepta las condiciones del servicios?" - -#: Template/Users/request_reset_password.ctp:5 -msgid "Please enter your email to reset your password" -msgstr "Por favor introduzca su email para restablecer su contraseña" - -#: Template/Users/resend_token_validation.ctp:15 -msgid "Resend Validation email" -msgstr "Reenviar email de validación" - -#: Template/Users/resend_token_validation.ctp:17 -msgid "Email or username" -msgstr "Email o usuario" - -#: Template/Users/view.ctp:16 -msgid "Delete User" -msgstr "Eliminar Usuario" - -#: Template/Users/view.ctp:18 -msgid "New User" -msgstr "Nuevo Usuario" - -#: Template/Users/view.ctp:26;65 -msgid "Id" -msgstr "Id" - -#: Template/Users/view.ctp:32 -msgid "First Name" -msgstr "Nombre" - -#: Template/Users/view.ctp:34 -msgid "Last Name" -msgstr "Apellidos" - -#: Template/Users/view.ctp:36;71 -msgid "Token" -msgstr "Token" - -#: Template/Users/view.ctp:38 -msgid "Api Token" -msgstr "Api Token" - -#: Template/Users/view.ctp:42;73 -msgid "Active" -msgstr "Activo" - -#: Template/Users/view.ctp:46;72 -msgid "Token Expires" -msgstr "Token caduca" - -#: Template/Users/view.ctp:48 -msgid "Activation Date" -msgstr "Fecha de activación" - -#: Template/Users/view.ctp:50 -msgid "Tos Date" -msgstr "Fecha Tos" - -#: Template/Users/view.ctp:52;75 -msgid "Created" -msgstr "Creado" - -#: Template/Users/view.ctp:54;76 -msgid "Modified" -msgstr "Modificado" - -#: Template/Users/view.ctp:61 -msgid "Related Accounts" -msgstr "Cuentas relacionadas" - -#: Template/Users/view.ctp:66 -msgid "User Id" -msgstr "Id Usuario" - -#: Template/Users/view.ctp:69 -msgid "Reference" -msgstr "Referencia" - -#: Template/Users/view.ctp:74 -msgid "Data" -msgstr "Datos" - -#: View/Helper/UserHelper.php:46 -msgid "Sign in with" -msgstr "Iniciar sesión 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:90 -msgid "Logout" -msgstr "Salir" - -#: View/Helper/UserHelper.php:139 -msgid "Welcome, {0}" -msgstr "Bienvenido, {0}" - -#: View/Helper/UserHelper.php:161 -msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" -msgstr "" -"reCaptcha no se ha configurado, por favor configure Users.reCaptcha.key" - -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" - -msgid "Sign in with Facebook" -msgstr "Login con Facebook" - -msgid "Sign in with Twitter" -msgstr "Login con Twitter" - -#: Model/Behavior/RegisterBehavior.php:147 -msgid "This field is required" -msgstr "Este campo es requerido" diff --git a/src/Locale/fr_FR/Users.mo b/src/Locale/fr_FR/Users.mo deleted file mode 100644 index 7c9623937..000000000 Binary files a/src/Locale/fr_FR/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 12049d19a..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 8b5fa6e20..000000000 Binary files a/src/Locale/sv/Users.mo and /dev/null differ diff --git a/src/Mailer/UsersMailer.php b/src/Mailer/UsersMailer.php index 3ef4c5c75..2e4070e96 100644 --- a/src/Mailer/UsersMailer.php +++ b/src/Mailer/UsersMailer.php @@ -1,85 +1,115 @@ hiddenProperties(['password', 'token_expires', 'api_token']); + $firstName = isset($user['first_name']) ? $user['first_name'] . ', ' : ''; + // un-hide the token to be able to send it in the email content + $user->setHidden(['password', 'token_expires', 'api_token']); + $subject = __d('cake_d_c/users', 'Your account validation link'); + $viewVars = [ + 'activationUrl' => UsersUrl::actionUrl('validateEmail', [ + '_full' => true, + $user['token'], + ]), + ] + $user->toArray(); $this - ->to($user['email']) - ->subject($firstName . $subject) - ->viewVars($user->toArray()) - ->template($template); + ->setTo($user['email']) + ->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 string $template string, note the first_name of the user will be prepended if exists - * - * @return array email send result + * @param \Cake\Datasource\EntityInterface $user User entity + * @return void */ - protected function resetPassword(EntityInterface $user, $template = 'CakeDC/Users.reset_password') + protected function resetPassword(EntityInterface $user) { - $firstName = isset($user['first_name'])? $user['first_name'] . ', ' : ''; - $subject = __d('CakeDC/Users', '{0}Your reset password link', $firstName); - $user->hiddenProperties(['password', 'token_expires', 'api_token']); + $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) + ->setEmailFormat(Message::MESSAGE_BOTH) + ->setViewVars($viewVars); $this - ->to($user['email']) - ->subject($subject) - ->viewVars($user->toArray()) - ->template($template); + ->viewBuilder() + ->setTemplate('CakeDC/Users.resetPassword'); } /** * Send account validation email to the user * - * @param EntityInterface $user User entity - * @param EntityInterface $socialAccount SocialAccount entity - * @param string $template string, note the first_name of the user will be prepended if exists - * - * @return array email send result + * @param \Cake\Datasource\EntityInterface $user User entity + * @param \Cake\Datasource\EntityInterface $socialAccount SocialAccount entity + * @return void */ - protected function socialAccountValidation(EntityInterface $user, EntityInterface $socialAccount, $template = 'CakeDC/Users.social_account_validation') + protected function socialAccountValidation(EntityInterface $user, EntityInterface $socialAccount) { - $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); + $firstName = isset($user['first_name']) ? $user['first_name'] . ', ' : ''; + // note: we control the space after the username in the previous line + $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) + ->setEmailFormat(Message::MESSAGE_BOTH) + ->setViewVars(['user' => $user, 'socialAccount' => $socialAccount, 'activationUrl' => $activationUrl]); $this - ->to($user['email']) - ->subject($subject) - ->viewVars(compact('user', 'socialAccount')) - ->template($template); + ->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 new file mode 100644 index 000000000..3dc19fe2b --- /dev/null +++ b/src/Model/Behavior/AuthFinderBehavior.php @@ -0,0 +1,62 @@ +where([$this->_table->aliasField('active') => 1]); + + return $query; + } + + /** + * Custom finder to log in users + * + * @param \Cake\ORM\Query $query Query object to modify + * @param array $options Query options + * @return \Cake\ORM\Query + * @throws \BadMethodCallException + */ + public function findAuth(Query $query, array $options = []) + { + $identifier = $options['username'] ?? null; + if (empty($identifier)) { + 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]); + + return $or->add($where); + }, [], true) + ->find('active', $options); + + return $query; + } +} diff --git a/src/Model/Behavior/Behavior.php b/src/Model/Behavior/BaseTokenBehavior.php similarity index 60% rename from src/Model/Behavior/Behavior.php rename to src/Model/Behavior/BaseTokenBehavior.php index a7e55b5fc..fbbe3cc89 100644 --- a/src/Model/Behavior/Behavior.php +++ b/src/Model/Behavior/BaseTokenBehavior.php @@ -1,32 +1,34 @@ 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 new file mode 100644 index 000000000..14096acea --- /dev/null +++ b/src/Model/Behavior/LinkSocialBehavior.php @@ -0,0 +1,140 @@ +_table->SocialAccounts->getAlias(); + $socialAccount = $this->_table->SocialAccounts->find() + ->where([ + $alias . '.reference' => $reference, + $alias . '.provider' => $data['provider'] ?? null, + ])->first(); + + if ($socialAccount && $user->id !== $socialAccount->user_id) { + $user->setErrors([ + 'social_accounts' => [ + '_existsIn' => __d('cake_d_c/users', 'Social account already associated to another user'), + ], + ]); + + return $user; + } + + return $this->createOrUpdateSocialAccount($user, $data, $socialAccount); + } + + /** + * Create or update a new social account linking to the user. + * + * @param \Cake\Datasource\EntityInterface $user User to link. + * @param array $data Social account information. + * @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([]); + } + + $data['user_id'] = $user->id; + $socialAccount = $this->populateSocialAccount($socialAccount, $data); + + $result = $this->_table->SocialAccounts->save($socialAccount); + + $accounts = (array)$user->social_accounts; + $found = false; + foreach ($accounts as $key => $account) { + if ($account->id == $socialAccount->id) { + $accounts[$key] = $socialAccount; + $found = true; + break; + } + } + + if (!$found) { + $accounts[] = $socialAccount; + } + $user->social_accounts = $accounts; + + if ($result && !$result->getErrors()) { + return $user; + } + + return $user; + } + + /** + * Populate the social account + * + * @param \Cake\Datasource\EntityInterface $socialAccount to populate. + * @param array $data Social account information. + * @return \Cake\Datasource\EntityInterface + */ + protected function populateSocialAccount($socialAccount, $data) + { + $accountData = $socialAccount->toArray(); + $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 = $data['credentials']['expires'] ?? null; + if (!empty($expires)) { + $expiresTime = new FrozenTime(); + $accountData['token_expires'] = $expiresTime->setTimestamp($expires)->format('Y-m-d H:i:s'); + } + + $accountData['data'] = serialize($data['raw'] ?? null); + $accountData['active'] = true; + + $socialAccount = $this->_table->SocialAccounts->patchEntity($socialAccount, $accountData); + //ensure provider is present in Entity + $socialAccount['provider'] = $data['provider'] ?? null; + + return $socialAccount; + } +} diff --git a/src/Model/Behavior/PasswordBehavior.php b/src/Model/Behavior/PasswordBehavior.php index 44c823c14..e307d15a0 100644 --- a/src/Model/Behavior/PasswordBehavior.php +++ b/src/Model/Behavior/PasswordBehavior.php @@ -1,93 +1,113 @@ Email = new EmailSender(); - } + use MailerAwareTrait; + /** * Resets user token * * @param string $reference User username or email * @param array $options checkActive, sendEmail, expiration - * * @return string - * @throws InvalidArgumentException - * @throws UserNotFoundException - * @throws UserAlreadyActiveException + * @throws \InvalidArgumentException + * @throws \CakeDC\Users\Exception\UserNotFoundException + * @throws \CakeDC\Users\Exception\UserAlreadyActiveException */ public function resetToken($reference, array $options = []) { if (empty($reference)) { - throw new InvalidArgumentException(__d('CakeDC/Users', "Reference cannot be null")); + throw new \InvalidArgumentException(__d('cake_d_c/users', 'Reference cannot be null')); } - $expiration = Hash::get($options, 'expiration'); + $expiration = $options['expiration'] ?? null; if (empty($expiration)) { - throw new InvalidArgumentException(__d('CakeDC/Users', "Token expiration cannot be empty")); + throw new \InvalidArgumentException(__d('cake_d_c/users', 'Token expiration cannot be empty')); } $user = $this->_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); - $template = !empty($options['emailTemplate']) ? $options['emailTemplate'] : 'CakeDC/Users.reset_password'; - if (Hash::get($options, 'sendEmail')) { - $this->Email->sendResetPasswordEmail($saveResult, null, $template); + if ($options['sendEmail'] ?? false) { + switch ($options['type'] ?? null) { + case 'email': + $this->_sendValidationEmail($user); + break; + case 'password': + $this->_sendResetPasswordEmail($user); + break; + } } return $saveResult; } + /** + * Send the reset password related email link + * + * @param \Cake\Datasource\EntityInterface $user user + * @return void + */ + 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 * @@ -102,26 +122,29 @@ 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', 'You cannot use the current password as the new one')); + throw new WrongPasswordException(__d( + 'cake_d_c/users', + 'You cannot use the current password as the new one' + )); } } $user = $this->_table->save($user); diff --git a/src/Model/Behavior/RegisterBehavior.php b/src/Model/Behavior/RegisterBehavior.php index 0a4cd21b3..b2fb4fb10 100644 --- a/src/Model/Behavior/RegisterBehavior.php +++ b/src/Model/Behavior/RegisterBehavior.php @@ -1,70 +1,88 @@ validateEmail = (bool)Configure::read('Users.Email.validate'); $this->useTos = (bool)Configure::read('Users.Tos.required'); - $this->Email = new EmailSender(); } /** * 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 - * @throws InvalidArgumentException + * @return bool|\Cake\Datasource\EntityInterface */ public function register($user, $data, $options) { - $validateEmail = Hash::get($options, 'validate_email'); - $tokenExpiration = Hash::get($options, 'token_expiration'); - $emailClass = Hash::get($options, 'email_class'); - $user = $this->_table->patchEntity($user, $data, ['validate' => Hash::get($options, 'validator') ?: $this->getRegisterValidators($options)]); + $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' => $validate] + ); + $user['role'] = Configure::read('Users.Registration.defaultRole') ?: 'user'; $user->validated = false; //@todo move updateActive to afterSave? $user = $this->_updateActive($user, $validateEmail, $tokenExpiration); $this->_table->isValidateEmail = $validateEmail; $userSaved = $this->_table->save($user); if ($userSaved && $validateEmail) { - $this->Email->sendValidationEmail($user, $emailClass); + $this->_sendValidationEmail($user); } return $userSaved; @@ -73,25 +91,25 @@ public function register($user, $data, $options) /** * Validates token and return user * - * @param type $token toke to be validated. + * @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 User $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; } @@ -101,51 +119,52 @@ 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->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); } + + return $validator; } /** * 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; }); @@ -155,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; } @@ -171,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); @@ -190,4 +209,18 @@ public function getRegisterValidators($options) return $validator; } + + /** + * 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]); + } } diff --git a/src/Model/Behavior/SocialAccountBehavior.php b/src/Model/Behavior/SocialAccountBehavior.php index 18f8e5229..10408a060 100644 --- a/src/Model/Behavior/SocialAccountBehavior.php +++ b/src/Model/Behavior/SocialAccountBehavior.php @@ -1,64 +1,62 @@ _table->belongsTo('Users', [ - 'foreignKey' => 'user_id', - 'joinType' => 'INNER', - 'className' => Configure::read('Users.table') - ]); - $this->Email = new EmailSender(); + $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 ArrayObject $options options + * @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; } @@ -69,15 +67,15 @@ public function afterSave(Event $event, Entity $entity, $options) /** * Send social validation email to the user * - * @param EntityInterface $socialAccount social account - * @param EntityInterface $user user - * @param Email $email Email instance or null to use 'default' configuration - * @return mixed + * @param \Cake\Datasource\EntityInterface $socialAccount social account + * @param \Cake\Datasource\EntityInterface $user user + * @return array */ - public function sendSocialValidationEmail(EntityInterface $socialAccount, EntityInterface $user, Email $email = null) + protected function sendSocialValidationEmail(EntityInterface $socialAccount, EntityInterface $user) { - $this->Email = new EmailSender(); - $this->Email->sendSocialValidationEmail($socialAccount, $user, $email); + return $this + ->getMailer(Configure::read('Users.Email.mailerClass') ?: 'CakeDC/Users.Users') + ->send('socialAccountValidation', [$user, $socialAccount]); } /** @@ -86,9 +84,9 @@ public function sendSocialValidationEmail(EntityInterface $socialAccount, Entity * @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) { @@ -99,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); @@ -113,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) { @@ -126,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); @@ -138,14 +142,13 @@ public function resendValidation($provider, $reference) /** * Activates an account * - * @param Account $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 d0c666a30..7911ed802 100644 --- a/src/Model/Behavior/SocialBehavior.php +++ b/src/Model/Behavior/SocialBehavior.php @@ -1,50 +1,78 @@ _username = $config['username']; + } + + parent::initialize($config); + } + /** * Performs social login * * @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' => $data['provider']]) + ->where([ + 'SocialAccounts.reference' => $reference, + 'SocialAccounts.provider' => $data['provider'] ?? null, + ]) ->contain(['Users']) ->first(); if (empty($existingAccount->user)) { @@ -53,30 +81,40 @@ 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) { - if ($user->active) { - return $user; - } else { - throw new UserNotActiveException([ - $existingAccount->provider, - $existingAccount->$user - ]); - } - } else { + if (!$existingAccount->active) { throw new AccountNotActiveException([ $existingAccount->provider, - $existingAccount->reference + $existingAccount->reference, + ]); + } + if (!$user->active) { + throw new UserNotActiveException([ + $existingAccount->provider, + $existingAccount->$user, ]); } } - return false; + return $user; } /** @@ -84,29 +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->alias() . '.email' => $email]) - ->first(); + $existingUser = $this->_table->find('existingForSocialLogin', ['email' => $email])->first(); } $user = $this->_populateUser($data, $existingUser, $useEmail, $validateEmail, $tokenExpiration); + + $event = $this->dispatchEvent(Plugin::EVENT_BEFORE_SOCIAL_LOGIN_USER_CREATE, [ + 'userEntity' => $user, + 'data' => $data, + ]); + $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); } /** @@ -114,75 +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); @@ -195,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; } @@ -211,9 +243,11 @@ public function generateUniqueUsername($username) { $i = 0; while (true) { - $existingUsername = $this->_table->find()->where([$this->_table->alias() . '.username' => $username])->count(); + $existingUsername = $this->_table->find() + ->where([$this->_table->aliasField($this->_username) => $username]) + ->count(); if ($existingUsername > 0) { - $username = $username . $i; + $username .= $i; $i++; continue; } @@ -222,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 dbf4709a1..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; @@ -89,7 +99,7 @@ public function hashPassword($password) { $PasswordHasher = $this->getPasswordHasher(); - return $PasswordHasher->hash($password); + return $PasswordHasher->hash((string)$password); } /** @@ -101,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(); } /** @@ -132,7 +142,7 @@ public function tokenExpired() return true; } - return new Time($this->token_expires) < Time::now(); + return new FrozenTime($this->token_expires) < FrozenTime::now(); } /** @@ -143,23 +153,41 @@ 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 = str_replace('-', '', Text::uuid()); + $this->token = bin2hex(Security::randomBytes(16)); } } diff --git a/src/Model/Table/SocialAccountsTable.php b/src/Model/Table/SocialAccountsTable.php index 0ebd54235..1d1578c19 100644 --- a/src/Model/Table/SocialAccountsTable.php +++ b/src/Model/Table/SocialAccountsTable.php @@ -1,34 +1,37 @@ table('social_accounts'); - $this->displayField('id'); - $this->primaryKey('id'); + $this->setTable('social_accounts'); + $this->setDisplayField('id'); + $this->setPrimaryKey('id'); $this->addBehavior('Timestamp'); $this->addBehavior('CakeDC/Users.SocialAccount'); } @@ -50,55 +53,55 @@ public function initialize(array $config) /** * 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 ->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; } @@ -107,13 +110,26 @@ 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')); return $rules; } + + /** + * Finder for active social accounts + * + * @param \Cake\ORM\Query $query query + * @return \Cake\ORM\Query + */ + public function findActive(Query $query) + { + return $query->where([ + $this->aliasField('active') => true, + ]); + } } diff --git a/src/Model/Table/UsersTable.php b/src/Model/Table/UsersTable.php index 2c221fe2b..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); - $this->table('users'); - $this->displayField('username'); - $this->primaryKey('id'); + $this->setTable('users'); + $this->setDisplayField('username'); + $this->setPrimaryKey('id'); $this->addBehavior('Timestamp'); $this->addBehavior('CakeDC/Users.Register'); $this->addBehavior('CakeDC/Users.Password'); $this->addBehavior('CakeDC/Users.Social'); - $this->hasMany('SocialAccounts', [ - 'foreignKey' => 'user_id', - 'className' => 'CakeDC/Users.SocialAccounts' - ]); + $this->addBehavior('CakeDC/Users.LinkSocial'); + $this->addBehavior('CakeDC/Users.AuthFinder'); + $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'); - - $validator->add('password', 'custom', [ - 'rule' => function ($value, $context) { - $confirm = Hash::get($context, 'data.password_confirm'); - if (!is_null($confirm) && $value != $confirm) { - return false; - } - - return true; - }, - 'message' => __d('CakeDC/Users', 'Your password does not match your confirm password. Please try again'), - 'on' => ['create', 'update'], - 'allowEmpty' => false - ]); + ->notBlank('password_confirm'); + + $validator + ->requirePresence('password', 'create') + ->notBlank('password') + ->add('password', [ + 'password_confirm_check' => [ + 'rule' => ['compareWith', 'password_confirm'], + 'message' => __d( + 'cake_d_c/users', + 'Your password does not match your confirm password. Please try again' + ), + 'allowEmpty' => false, + ]]); return $validator; } @@ -90,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; } @@ -104,120 +131,83 @@ 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'), ]); } return $rules; } - - /** - * Custom finder to filter active users - * - * @param Query $query Query object to modify - * @param array $options Query options - * @return Query - */ - public function findActive(Query $query, array $options = []) - { - $query->where([$this->aliasField('active') => 1]); - - return $query; - } - - /** - * Custom finder to log in users - * - * @param Query $query Query object to modify - * @param array $options Query options - * @return Query - * @throws \BadMethodCallException - */ - public function findAuth(Query $query, array $options = []) - { - $identifier = Hash::get($options, 'username'); - if (empty($identifier)) { - throw new \BadMethodCallException(__d('CakeDC/Users', 'Missing \'username\' in options data')); - } - - $query - ->orWhere([$this->aliasField('email') => $identifier]) - ->find('active', $options); - - return $query; - } } 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 8c11632bc..bd38e2e44 100644 --- a/src/Shell/UsersShell.php +++ b/src/Shell/UsersShell.php @@ -1,75 +1,100 @@ Users = $this->loadModel(Configure::read('Users.table')); - } - - /** - * - * @return OptionParser - */ - public function getOptionParser() + public function getOptionParser(): ConsoleOptionParser { $parser = parent::getOptionParser(); - $parser->description(__d('CakeDC/Users', 'Utilities for CakeDC Users Plugin')) - ->addSubcommand('activateUser')->description(__d('CakeDC/Users', 'Activate an specific user')) - ->addSubcommand('addSuperuser')->description(__d('CakeDC/Users', 'Add a new superadmin user for testing purposes')) - ->addSubcommand('addUser')->description(__d('CakeDC/Users', 'Add a new user')) - ->addSubcommand('changeRole')->description(__d('CakeDC/Users', 'Change the role for an specific user')) - ->addSubcommand('deactivateUser')->description(__d('CakeDC/Users', 'Deactivate an specific user')) - ->addSubcommand('deleteUser')->description(__d('CakeDC/Users', 'Delete an specific user')) - ->addSubcommand('passwordEmail')->description(__d('CakeDC/Users', 'Reset the password via email')) - ->addSubcommand('resetAllPasswords')->description(__d('CakeDC/Users', 'Reset the password for all users')) - ->addSubcommand('resetPassword')->description(__d('CakeDC/Users', 'Reset the password for an specific user')) + $parser->setDescription(__d('cake_d_c/users', 'Utilities for CakeDC Users Plugin')) + ->addSubcommand('activateUser', [ + 'help' => __d('cake_d_c/users', 'Activate an specific user'), + ]) + ->addSubcommand('addSuperuser', [ + 'help' => __d('cake_d_c/users', 'Add a new superadmin user for testing purposes'), + ]) + ->addSubcommand('addUser', [ + 'help' => __d('cake_d_c/users', 'Add a new user'), + ]) + ->addSubcommand('changeRole', [ + 'help' => __d('cake_d_c/users', 'Change the role for an specific user'), + ]) + ->addSubcommand('changeApiToken', [ + 'help' => __d('cake_d_c/users', 'Change the api token for an specific user'), + ]) + ->addSubcommand('deactivateUser', [ + 'help' => __d('cake_d_c/users', 'Deactivate an specific user'), + ]) + ->addSubcommand('deleteUser', [ + 'help' => __d('cake_d_c/users', 'Delete an specific user'), + ]) + ->addSubcommand('passwordEmail', [ + 'help' => __d('cake_d_c/users', 'Reset the password via email'), + ]) + ->addSubcommand('resetAllPasswords', [ + 'help' => __d('cake_d_c/users', 'Reset the password for all users'), + ]) + ->addSubcommand('resetPassword', [ + 'help' => __d('cake_d_c/users', 'Reset the password for an specific user'), + ]) ->addOptions([ 'username' => ['short' => 'u', 'help' => 'The username for the new user'], 'password' => ['short' => 'p', 'help' => 'The password for the new user'], - 'email' => ['short' => 'e', 'help' => 'The email for the new user'] + 'email' => ['short' => 'e', 'help' => 'The email for the new user'], + 'role' => ['short' => 'r', 'help' => 'The role for the new user'], ]); return $parser; } + /** + * initialize callback + * + * @return void + */ + public function initialize(): void + { + parent::initialize(); + $this->Users = $this->loadModel(Configure::read('Users.table')); + } + /** * Add a new user * @@ -77,28 +102,7 @@ public function getOptionParser() */ public function addUser() { - $username = (empty($this->params['username']) ? - $this->_generateRandomUsername() : $this->params['username']); - - $username = $this->Users->generateUniqueUsername($username); - $password = (empty($this->params['password']) ? - $this->_generateRandomPassword() : $this->params['password']); - $email = (empty($this->params['email']) ? $username . '@example.com' : $this->params['email']); - $user = [ - 'username' => $username, - 'email' => $email, - 'password' => $password, - 'active' => 1, - ]; - - $userEntity = $this->Users->newEntity($user); - $userEntity->role = 'user'; - $savedUser = $this->Users->save($userEntity); - $this->out(__d('CakeDC/Users', 'User added:')); - $this->out(__d('CakeDC/Users', 'Id: {0}', $savedUser->id)); - $this->out(__d('CakeDC/Users', 'Username: {0}', $username)); - $this->out(__d('CakeDC/Users', 'Email: {0}', $savedUser->email)); - $this->out(__d('CakeDC/Users', 'Password: {0}', $password)); + $this->_createUser(['role' => Configure::read('Users.Registration.defaultRole') ?: 'user']); } /** @@ -108,32 +112,11 @@ public function addUser() */ public function addSuperuser() { - $username = $this->Users->generateUniqueUsername('superadmin'); - $password = $this->_generateRandomPassword(); - $user = [ - 'username' => $username, - 'email' => $username . '@example.com', - 'password' => $password, - 'active' => 1, - ]; - - $userEntity = $this->Users->newEntity($user); - $userEntity->is_superuser = true; - $userEntity->role = 'superuser'; - $savedUser = $this->Users->save($userEntity); - if (!empty($savedUser)) { - $this->out(__d('CakeDC/Users', 'Superuser added:')); - $this->out(__d('CakeDC/Users', 'Id: {0}', $savedUser->id)); - $this->out(__d('CakeDC/Users', 'Username: {0}', $username)); - $this->out(__d('CakeDC/Users', 'Email: {0}', $savedUser->email)); - $this->out(__d('CakeDC/Users', 'Password: {0}', $password)); - } else { - $this->out(__d('CakeDC/Users', 'Superuser could not be added:')); - - collection($userEntity->errors())->each(function ($error, $field) { - $this->out(__d('CakeDC/Users', 'Field: {0} Error: {1}', $field, implode(',', $error))); - }); - } + $this->_createUser([ + 'username' => 'superadmin', + 'role' => 'superuser', + 'is_superuser' => true, + ]); } /** @@ -149,12 +132,12 @@ public function resetAllPasswords() { $password = Hash::get($this->args, 0); if (empty($password)) { - $this->error(__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)); } /** @@ -172,17 +155,17 @@ public function resetPassword() $username = Hash::get($this->args, 0); $password = Hash::get($this->args, 1); if (empty($username)) { - $this->error(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } if (empty($password)) { - $this->error(__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)); } /** @@ -200,17 +183,51 @@ public function changeRole() $username = Hash::get($this->args, 0); $role = Hash::get($this->args, 1); if (empty($username)) { - $this->error(__d('CakeDC/Users', 'Please enter a username.')); + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); } if (empty($role)) { - $this->error(__d('CakeDC/Users', 'Please enter a role.')); + $this->abort(__d('cake_d_c/users', 'Please enter a role.')); } $data = [ - 'role' => $role + 'role' => $role, ]; $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)); + $this->out(__d('cake_d_c/users', 'Role changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New role: {0}', $savedUser->role)); + } + + /** + * Change api token for a user + * + * Arguments: + * + * - Username + * - Token to be set + * + * @return void + */ + public function changeApiToken() + { + $username = Hash::get($this->args, 0); + $token = Hash::get($this->args, 1); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($token)) { + $this->abort(__d('cake_d_c/users', 'Please enter a token.')); + } + $data = [ + 'api_token' => $token, + ]; + $savedUser = $this->_updateUser($username, $data); + if (!$savedUser) { + $this->err(__d('cake_d_c/users', 'User was not saved, check validation errors')); + } + /** + * @var \CakeDC\Users\Model\Entity\User $savedUser + */ + $this->out(__d('cake_d_c/users', 'Api token changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New token: {0}', $savedUser->api_token)); } /** @@ -225,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)); } /** @@ -240,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)); } /** @@ -252,7 +269,7 @@ public function passwordEmail() { $reference = Hash::get($this->args, 0); if (empty($reference)) { - $this->error(__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'), @@ -260,11 +277,17 @@ 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'); - $this->error($msg); + $msg = __d( + 'cake_d_c/users', + 'The password token could not be generated. Please try again' + ); + $this->abort($msg); } } @@ -278,37 +301,94 @@ protected function _changeUserActive($active) { $username = Hash::get($this->args, 0); if (empty($username)) { - $this->error(__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); } + /** + * Create a new user or superuser + * + * @param array $template template with deafault user values + * @return void + */ + protected function _createUser($template) + { + if (!empty($this->params['username'])) { + $username = $this->params['username']; + } else { + $username = empty($template['username']) ? + $this->_generateRandomUsername() : $template['username']; + } + + $password = (empty($this->params['password']) ? + $this->_generateRandomPassword() : $this->params['password']); + $email = (empty($this->params['email']) ? + $username . '@example.com' : $this->params['email']); + $role = (empty($this->params['role']) ? + $template['role'] : $this->params['role']); + + $user = [ + 'username' => $this->Users->generateUniqueUsername($username), + 'email' => $email, + 'password' => $password, + 'active' => 1, + ]; + + $userEntity = $this->Users->newEntity($user); + $userEntity->is_superuser = empty($template['is_superuser']) ? + false : $template['is_superuser']; + $userEntity->role = $role; + $savedUser = $this->Users->save($userEntity); + + if (is_object($savedUser)) { + if ($savedUser->is_superuser) { + $this->out(__d('cake_d_c/users', 'Superuser added:')); + } else { + $this->out(__d('cake_d_c/users', 'User added:')); + } + $this->out(__d('cake_d_c/users', 'Id: {0}', $savedUser->id)); + $this->out(__d('cake_d_c/users', 'Username: {0}', $savedUser->username)); + $this->out(__d('cake_d_c/users', 'Email: {0}', $savedUser->email)); + $this->out(__d('cake_d_c/users', 'Role: {0}', $savedUser->role)); + $this->out(__d('cake_d_c/users', 'Password: {0}', $password)); + } else { + $this->out(__d('cake_d_c/users', 'User could not be added:')); + + collection($userEntity->getErrors())->each(function ($error, $field) { + $this->out(__d('cake_d_c/users', 'Field: {0} Error: {1}', $field, implode(',', $error))); + }); + } + } + /** * Update user by username * * @param string $username username * @param array $data data - * @return 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->error(__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->accessible($field); + 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); } /** @@ -320,17 +400,20 @@ public function deleteUser() { $username = Hash::get($this->args, 0); if (empty($username)) { - $this->error(__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->error(__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)); } /** @@ -361,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 5f585c3ad..000000000 --- a/src/Template/Email/html/reset_password.ctp +++ /dev/null @@ -1,31 +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 f6dbd7567..000000000 --- a/src/Template/Email/html/social_account_validation.ctp +++ /dev/null @@ -1,36 +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 0e722b118..000000000 --- a/src/Template/Email/html/validation.ctp +++ /dev/null @@ -1,31 +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 49018fb92..000000000 --- a/src/Template/Email/text/reset_password.ctp +++ /dev/null @@ -1,25 +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 72b1f1279..000000000 --- a/src/Template/Email/text/social_account_validation.ctp +++ /dev/null @@ -1,27 +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 a423ee9ea..000000000 --- a/src/Template/Email/text/validation.ctp +++ /dev/null @@ -1,25 +0,0 @@ - true, - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'validateEmail', - isset($token) ? $token : '' -]; -?> -, - -Url->build($activationUrl)) ?> - -, - diff --git a/src/Template/Layout/Email/html/default.ctp b/src/Template/Layout/Email/html/default.ctp deleted file mode 100644 index 2b4397008..000000000 --- a/src/Template/Layout/Email/html/default.ctp +++ /dev/null @@ -1,24 +0,0 @@ - - - - - <?= $this->fetch('title') ?> - - - fetch('content') ?> - - diff --git a/src/Template/Layout/Email/text/default.ctp b/src/Template/Layout/Email/text/default.ctp deleted file mode 100644 index 871dcfb48..000000000 --- a/src/Template/Layout/Email/text/default.ctp +++ /dev/null @@ -1,16 +0,0 @@ - -fetch('content') ?> diff --git a/src/Template/Users/add.ctp b/src/Template/Users/add.ctp deleted file mode 100644 index b22dd0e5f..000000000 --- a/src/Template/Users/add.ctp +++ /dev/null @@ -1,36 +0,0 @@ - -
-

-
    -
  • Html->link(__d('CakeDC/Users', 'List Users'), ['action' => 'index']) ?>
  • -
  • Html->link(__d('CakeDC/Users', 'List Accounts'), ['controller' => 'Accounts', 'action' => 'index']) ?>
  • -
-
-
- Form->create(${$tableAlias}); ?> -
- - Form->input('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->input('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->input('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->input('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); - echo $this->Form->input('active', [ - 'type' => 'checkbox', - 'label' => __d('CakeDC/Users', 'Active') - ]); - ?> -
- Form->button(__d('CakeDC/Users', 'Submit')) ?> - Form->end() ?> -
diff --git a/src/Template/Users/change_password.ctp b/src/Template/Users/change_password.ctp deleted file mode 100644 index 3a7530451..000000000 --- a/src/Template/Users/change_password.ctp +++ /dev/null @@ -1,27 +0,0 @@ -
- Flash->render('auth') ?> - Form->create($user) ?> -
- - - Form->input('current_password', [ - 'type' => 'password', - 'required' => true, - 'label' => __d('CakeDC/Users', 'Current password')]); - ?> - - Form->input('password', [ - 'type' => 'password', - 'required' => true, - 'label' => __d('CakeDC/Users', 'New password')]); - ?> - Form->input('password_confirm', [ - 'type' => 'password', - 'required' => true, - 'label' => __d('CakeDC/Users', 'Confirm password')]); - ?> - -
- Form->button(__d('CakeDC/Users', 'Submit')); ?> - Form->end() ?> -
\ No newline at end of file diff --git a/src/Template/Users/edit.ctp b/src/Template/Users/edit.ctp deleted file mode 100644 index a60ca1e8e..000000000 --- a/src/Template/Users/edit.ctp +++ /dev/null @@ -1,59 +0,0 @@ - -
-

-
    -
  • - Form->postLink( - __d('CakeDC/Users', 'Delete'), - ['action' => 'delete', $Users->id], - ['confirm' => __d('CakeDC/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', 'List Accounts'), ['controller' => 'Accounts', 'action' => 'index']) ?>
  • -
-
-
- Form->create($Users); ?> -
- - Form->input('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->input('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->input('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->input('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); - echo $this->Form->input('token', ['label' => __d('CakeDC/Users', 'Token')]); - echo $this->Form->input('token_expires', [ - 'label' => __d('CakeDC/Users', 'Token expires') - ]); - echo $this->Form->input('api_token', [ - 'label' => __d('CakeDC/Users', 'API token') - ]); - echo $this->Form->input('activation_date', [ - 'label' => __d('CakeDC/Users', 'Activation date') - ]); - echo $this->Form->input('tos_date', [ - 'label' => __d('CakeDC/Users', 'TOS date') - ]); - echo $this->Form->input('active', [ - 'label' => __d('CakeDC/Users', 'Active') - ]); - ?> -
- Form->button(__d('CakeDC/Users', 'Submit')) ?> - Form->end() ?> -
diff --git a/src/Template/Users/index.ctp b/src/Template/Users/index.ctp deleted file mode 100644 index 2b224b85d..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/login.ctp b/src/Template/Users/login.ctp deleted file mode 100644 index 60b36c2af..000000000 --- a/src/Template/Users/login.ctp +++ /dev/null @@ -1,50 +0,0 @@ - -
- Flash->render('auth') ?> - Form->create() ?> -
- - Form->input('username', ['required' => true]) ?> - Form->input('password', ['required' => true]) ?> - User->addReCaptcha(); - } - if (Configure::read('Users.RememberMe.active')) { - echo $this->Form->input(Configure::read('Users.Key.Data.rememberMe'), [ - 'type' => 'checkbox', - 'label' => __d('CakeDC/Users', 'Remember me'), - 'checked' => 'checked' - ]); - } - ?> - Html->link(__d('CakeDC/Users', 'Register'), ['action' => 'register']); - } - if (Configure::read('Users.Email.required')) { - if ($registrationActive) { - echo ' | '; - } - echo $this->Html->link(__d('CakeDC/Users', 'Reset Password'), ['action' => 'requestResetPassword']); - } - ?> -
- User->socialLoginList()); ?> - Form->button(__d('CakeDC/Users', 'Login')); ?> - Form->end() ?> -
diff --git a/src/Template/Users/register.ctp b/src/Template/Users/register.ctp deleted file mode 100644 index 4238cd603..000000000 --- a/src/Template/Users/register.ctp +++ /dev/null @@ -1,37 +0,0 @@ - -
- Form->create($user); ?> -
- - Form->input('username', ['label' => __d('CakeDC/Users', 'Username')]); - echo $this->Form->input('email', ['label' => __d('CakeDC/Users', 'Email')]); - echo $this->Form->input('password', ['label' => __d('CakeDC/Users', 'Password')]); - echo $this->Form->input('password_confirm', [ - 'type' => 'password', - 'label' => __d('CakeDC/Users', 'Confirm password') - ]); - echo $this->Form->input('first_name', ['label' => __d('CakeDC/Users', 'First name')]); - echo $this->Form->input('last_name', ['label' => __d('CakeDC/Users', 'Last name')]); - if (Configure::read('Users.Tos.required')) { - echo $this->Form->input('tos', ['type' => 'checkbox', 'label' => __d('CakeDC/Users', 'Accept TOS conditions?'), 'required' => true]); - } - if (Configure::read('Users.reCaptcha.registration')) { - echo $this->User->addReCaptcha(); - } - ?> -
- Form->button(__d('CakeDC/Users', 'Submit')) ?> - Form->end() ?> -
diff --git a/src/Template/Users/request_reset_password.ctp b/src/Template/Users/request_reset_password.ctp deleted file mode 100644 index 462e29bc6..000000000 --- a/src/Template/Users/request_reset_password.ctp +++ /dev/null @@ -1,10 +0,0 @@ -
- Flash->render('auth') ?> - Form->create('User') ?> -
- - Form->input('reference') ?> -
- Form->button(__d('CakeDC/Users', 'Submit')); ?> - Form->end() ?> -
diff --git a/src/Template/Users/resend_token_validation.ctp b/src/Template/Users/resend_token_validation.ctp deleted file mode 100644 index 90b8aa9d7..000000000 --- a/src/Template/Users/resend_token_validation.ctp +++ /dev/null @@ -1,22 +0,0 @@ - -
- Form->create($user); ?> -
- - Form->input('reference', ['label' => __d('CakeDC/Users', 'Email or username')]); - ?> -
- 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 7fb86c250..000000000 --- a/src/Template/Users/social_email.ctp +++ /dev/null @@ -1,21 +0,0 @@ - -
- Flash->render() ?> - Form->create('User') ?> -
- - Form->input('email') ?> -
- Form->button(__d('CakeDC/Users', 'Submit')); ?> - Form->end() ?> -
diff --git a/src/Template/Users/view.ctp b/src/Template/Users/view.ctp deleted file mode 100644 index e0fe84182..000000000 --- a/src/Template/Users/view.ctp +++ /dev/null @@ -1,110 +0,0 @@ - -
-

-
    -
  • Html->link(__d('CakeDC/Users', 'Edit User'), ['action' => 'edit', $Users->id]) ?>
  • -
  • Form->postLink(__d('CakeDC/Users', 'Delete User'), ['action' => 'delete', $Users->id], ['confirm' => __d('CakeDC/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('CakeDC/Users', 'List Accounts'), ['controller' => 'Accounts', 'action' => 'index']) ?>
  • -
-
-
-

id) ?>

-
-
-
-

id) ?>

-
-

username) ?>

-
-

email) ?>

-
-

first_name) ?>

-
-

last_name) ?>

-
-

token) ?>

-
-

api_token) ?>

-
-
-
-

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

-
-
-
-

token_expires) ?>

-
-

activation_date) ?>

-
-

tos_date) ?>

-
-

created) ?>

-
-

modified) ?>

-
-
-
- diff --git a/src/Traits/RandomStringTrait.php b/src/Traits/RandomStringTrait.php index f1ca7c4d3..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 648e64423..06274ae37 100644 --- a/src/View/Helper/AuthLinkHelper.php +++ b/src/View/Helper/AuthLinkHelper.php @@ -1,49 +1,83 @@ isAuthorized($url)) { - $linkOptions = $options; - unset($linkOptions['before'], $linkOptions['after']); + $linkOptions = $options; + unset($linkOptions['before'], $linkOptions['after'], $linkOptions['allowed']); + $allowed = $options['allowed'] ?? null; - return Hash::get($options, 'before') . parent::link($title, $url, $linkOptions) . Hash::get($options, 'after'); + if ($allowed === false) { + return ''; + } + if ($allowed === true || $this->isAuthorized($url)) { + return ($options['before'] ?? '') . + parent::link($title, $url, $linkOptions) . + ($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 = $this->_View->eventManager()->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 82c37e8d7..00d32bd93 100644 --- a/src/View/Helper/UserHelper.php +++ b/src/View/Helper/UserHelper.php @@ -1,29 +1,33 @@ Html->tag('i', '', [ - 'class' => __d('CakeDC/Users', 'fa fa-{0}', strtolower($name)), + 'class' => 'fa fa-' . strtolower($name), ]); - $providerTitle = __d('CakeDC/Users', '{0} {1}', Hash::get($options, 'label'), Inflector::camelize($name)); - $providerClass = __d('CakeDC/Users', 'btn btn-social btn-{0} ' . Hash::get($options, 'class') ?: '', strtolower($name)); + + if (isset($options['title'])) { + $providerTitle = $options['title']; + } else { + $providerTitle = ($options['label'] ?? '') . ' ' . Inflector::camelize($name); + } + + $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, ]); } /** * All available Social Login Icons * + * @param array $providerOptions Provider link options. * @return array Links to Social Login Urls */ - public function socialLoginList() + public function socialLoginList(array $providerOptions = []) { if (!Configure::read('Users.Social.login')) { return []; @@ -69,10 +84,16 @@ public function socialLoginList() $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'])) { - $outProviders[] = $this->socialLogin($provider); + !empty($options['options']['clientSecret']) + ) { + if (isset($providerOptions[$provider])) { + $options['options'] = Hash::merge($options['options'], $providerOptions[$provider]); + } + + $outProviders[] = $this->socialLogin($provider, $options['options']); } } @@ -88,30 +109,40 @@ public function socialLoginList() */ 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->session()->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->session()->read('Auth.User.first_name') ?: $this->request->session()->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() @@ -123,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', ]); } @@ -143,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. @@ -160,20 +203,66 @@ public function link($title, $url = null, array $options = []) } /** - * Returns true if the target url is authorized for the logged in user + * Create links for all social providers enabled social link (connect) * - * @deprecated Since 3.2.1. Use AuthLinkHelper::link() instead + * @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) + { + $optionClass = $provider['options']['class'] ?? null; + $linkClass = 'btn btn-social btn-' . strtolower($name) . ($optionClass ? ' ' . $optionClass : ''); + if ($isConnected) { + $title = __d('cake_d_c/users', 'Connected with {0}', Inflector::camelize($name)); + + return " $title"; + } + + $title = __d('cake_d_c/users', 'Connect with {0}', Inflector::camelize($name)); + + return $this->Html->link( + " $title", + "/link-social/$name", + [ + 'escape' => false, + 'class' => $linkClass, + ] + ); + } + + /** + * Create links for all social providers enabled social link (connect) * - * @param string|array|null $url url that the user is making request. - * @return bool + * @param array $socialAccounts All social accounts connected by a user. + * @return string */ - public function isAuthorized($url = null) + public function socialConnectLinkList($socialAccounts = []) { - trigger_error( - 'UserHelper::isAuthorized() deprecated since 3.2.1. Use AuthLinkHelper::isAuthorized() instead', - E_USER_DEPRECATED + if (!Configure::read('Users.Social.login')) { + return ''; + } + $html = ''; + $connectedProviders = array_map( + function ($item) { + return strtolower($item->provider); + }, + (array)$socialAccounts ); - return $this->AuthLink->isAuthorized($url); + $providers = Configure::read('OAuth.providers'); + foreach ($providers as $name => $provider) { + if ( + !empty($provider['options']['callbackLinkSocialUri']) && + !empty($provider['options']['linkSocialUri']) && + !empty($provider['options']['clientId']) && + !empty($provider['options']['clientSecret']) + ) { + $html .= $this->socialConnectLink($name, $provider, in_array($name, $connectedProviders)); + } + } + + return $html; } } 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/templates/Users/add.php b/templates/Users/add.php new file mode 100644 index 000000000..2b795d20b --- /dev/null +++ b/templates/Users/add.php @@ -0,0 +1,36 @@ + +
+

+
    +
  • Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ?>
  • +
+
+
+ Form->create(${$tableAlias}); ?> +
+ + 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('cake_d_c/users', 'Active') + ]); + ?> +
+ Form->button(__d('cake_d_c/users', 'Submit')) ?> + Form->end() ?> +
diff --git a/templates/Users/change_password.php b/templates/Users/change_password.php new file mode 100644 index 000000000..7efba8f10 --- /dev/null +++ b/templates/Users/change_password.php @@ -0,0 +1,27 @@ +
+ Flash->render('auth') ?> + Form->create($user) ?> +
+ + + Form->control('current_password', [ + 'type' => 'password', + 'required' => true, + 'label' => __d('cake_d_c/users', 'Current password')]); + ?> + + Form->control('password', [ + 'type' => 'password', + 'required' => true, + 'label' => __d('cake_d_c/users', 'New password')]); + ?> + Form->control('password_confirm', [ + 'type' => 'password', + 'required' => true, + 'label' => __d('cake_d_c/users', 'Confirm password')]); + ?> + +
+ Form->button(__d('cake_d_c/users', 'Submit')); ?> + Form->end() ?> +
\ No newline at end of file diff --git a/templates/Users/edit.php b/templates/Users/edit.php new file mode 100644 index 000000000..551076252 --- /dev/null +++ b/templates/Users/edit.php @@ -0,0 +1,78 @@ + +
+

+
    +
  • + Form->postLink( + __d('cake_d_c/users', 'Delete'), + ['action' => 'delete', $Users->id], + ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $Users->id)] + ); + ?> +
  • +
  • Html->link(__d('cake_d_c/users', 'List Users'), ['action' => 'index']) ?>
  • +
+
+
+ Form->create($Users); ?> +
+ + 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('cake_d_c/users', 'Token expires') + ]); + echo $this->Form->control('api_token', [ + 'label' => __d('cake_d_c/users', 'API token') + ]); + echo $this->Form->control('activation_date', [ + 'label' => __d('cake_d_c/users', 'Activation date') + ]); + echo $this->Form->control('tos_date', [ + 'label' => __d('cake_d_c/users', 'TOS date') + ]); + echo $this->Form->control('active', [ + 'label' => __d('cake_d_c/users', 'Active') + ]); + ?> +
+ Form->button(__d('cake_d_c/users', 'Submit')) ?> + Form->end() ?> + +
+ Reset Google Authenticator + Form->postLink( + __d('cake_d_c/users', 'Reset Google Authenticator Token'), [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'resetOneTimePasswordAuthenticator', $Users->id + ], [ + 'class' => 'btn btn-danger', + 'confirm' => __d( + '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/templates/Users/login.php b/templates/Users/login.php new file mode 100644 index 000000000..99aaf4edd --- /dev/null +++ b/templates/Users/login.php @@ -0,0 +1,50 @@ + +
+ Flash->render('auth') ?> + Form->create() ?> +
+ + 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(); + } + if (Configure::read('Users.RememberMe.active')) { + echo $this->Form->control(Configure::read('Users.Key.Data.rememberMe'), [ + 'type' => 'checkbox', + 'label' => __d('cake_d_c/users', 'Remember me'), + 'checked' => Configure::read('Users.RememberMe.checked') + ]); + } + ?> + Html->link(__d('cake_d_c/users', 'Register'), ['action' => 'register']); + } + if (Configure::read('Users.Email.required')) { + if ($registrationActive) { + echo ' | '; + } + echo $this->Html->link(__d('cake_d_c/users', 'Reset Password'), ['action' => 'requestResetPassword']); + } + ?> +
+ User->socialLoginList()); ?> + 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 55% rename from src/Template/Users/profile.ctp rename to templates/Users/profile.php index 65f09904a..bd786ffc1 100644 --- a/src/Template/Users/profile.ctp +++ b/templates/Users/profile.php @@ -1,50 +1,54 @@
-

Html->image(empty($user->avatar) ? $avatarPlaceholder : $user->avatar, ['width' => '180', 'height' => '180']); ?>

+

Html->image( + empty($user->avatar) ? $avatarPlaceholder : $user->avatar, + ['width' => '180', 'height' => '180'] + ); ?>

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) ?> + ) : '-' ?> -
provider) ?> Html->link( + $socialAccount->link && $socialAccount->link != '#' ? $this->Html->link( $linkText, $socialAccount->link, ['target' => '_blank'] - ) ?>
-
diff --git a/templates/Users/register.php b/templates/Users/register.php new file mode 100644 index 000000000..e82673b67 --- /dev/null +++ b/templates/Users/register.php @@ -0,0 +1,40 @@ + +
+ Form->create($user); ?> +
+ + 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('cake_d_c/users', 'Confirm 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')]); + if (Configure::read('Users.Tos.required')) { + 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('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/templates/Users/resend_token_validation.php b/templates/Users/resend_token_validation.php new file mode 100644 index 000000000..2023e3c18 --- /dev/null +++ b/templates/Users/resend_token_validation.php @@ -0,0 +1,22 @@ + +
+ Form->create($user); ?> +
+ + Form->control('reference', ['label' => __d('cake_d_c/users', 'Email or username')]); + ?> +
+ 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/templates/Users/verify.php b/templates/Users/verify.php new file mode 100644 index 000000000..92648a029 --- /dev/null +++ b/templates/Users/verify.php @@ -0,0 +1,20 @@ +
+
+
+
+ Form->create() ?> + + Flash->render('auth') ?> + Flash->render() ?> +
+ +

+ + Form->control('code', ['required' => true, 'label' => __d('cake_d_c/users', 'Verification Code')]) ?> +
+ Form->button(__d('cake_d_c/users', ' Verify'), ['class' => 'btn btn-primary', 'escapeTitle' => false]); ?> + Form->end() ?> +
+
+
+
diff --git a/templates/Users/view.php b/templates/Users/view.php new file mode 100644 index 000000000..9e0d28106 --- /dev/null +++ b/templates/Users/view.php @@ -0,0 +1,90 @@ + +
+

+
    +
  • Html->link(__d('cake_d_c/users', 'Edit User'), ['action' => 'edit', $Users->id]) ?>
  • +
  • Form->postLink( + __d('cake_d_c/users', 'Delete User'), + ['action' => 'delete', $Users->id], + ['confirm' => __d('cake_d_c/users', 'Are you sure you want to delete # {0}?', $Users->id)] + ) ?>
  • +
  • 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) ?>

+
+
+
+ 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/templates/layout/email/html/default.php b/templates/layout/email/html/default.php new file mode 100644 index 000000000..98d045608 --- /dev/null +++ b/templates/layout/email/html/default.php @@ -0,0 +1,20 @@ + + + + + <?= $this->fetch('title') ?> + + + fetch('content') ?> + + diff --git a/templates/layout/email/text/default.php b/templates/layout/email/text/default.php new file mode 100644 index 000000000..4d493b97c --- /dev/null +++ b/templates/layout/email/text/default.php @@ -0,0 +1,13 @@ + +fetch('content'); diff --git a/tests/App/Controller/AppController.php b/tests/App/Controller/AppController.php deleted file mode 100644 index 5899a4e41..000000000 --- a/tests/App/Controller/AppController.php +++ /dev/null @@ -1,24 +0,0 @@ -loadComponent('Flash'); - // $this->loadComponent('CakeDC/Users.UsersAuth'); - $this->loadComponent('RequestHandler'); - } -} diff --git a/tests/Fixture/PostsFixture.php b/tests/Fixture/PostsFixture.php index fadeb7179..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 f4cea2af2..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 7e014fa58..9789d2c45 100644 --- a/tests/Fixture/SocialAccountsFixture.php +++ b/tests/Fixture/SocialAccountsFixture.php @@ -1,46 +1,23 @@ ['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 * @@ -61,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', @@ -77,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', @@ -93,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', @@ -109,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', @@ -125,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 1577a0783..d48f6dc55 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -1,243 +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], - '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', - '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', - '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', - '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', - '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', - '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', - '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', - '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', - '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', - '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', - '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/ApiKeyAuthenticateTest.php b/tests/TestCase/Auth/ApiKeyAuthenticateTest.php deleted file mode 100644 index 90a95c28e..000000000 --- a/tests/TestCase/Auth/ApiKeyAuthenticateTest.php +++ /dev/null @@ -1,167 +0,0 @@ -getMockBuilder('Cake\Controller\Controller') - ->setMethods(null) - ->setConstructorArgs([$request, $response]) - ->getMock(); - $registry = new ComponentRegistry($controller); - $this->apiKey = new ApiKeyAuthenticate($registry, ['require_ssl' => false]); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - public function tearDown() - { - unset($this->apiKey, $this->controller); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateHappy() - { - $request = new Request('/?api_key=yyy'); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertEquals('user-1', $result['username']); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateFail() - { - $request = new Request('/'); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertFalse($result); - - $request = new Request('/?api_key=none'); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertFalse($result); - - $request = new Request('/?api_key='); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertFalse($result); - } - - /** - * test - * - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Type wrong is not valid - * - */ - public function testAuthenticateWrongType() - { - $this->apiKey->config('type', 'wrong'); - $request = new Request('/'); - $this->apiKey->authenticate($request, new Response()); - } - - /** - * test - * - * @expectedException \Cake\Network\Exception\ForbiddenException - * @expectedExceptionMessage SSL is required for ApiKey Authentication - * - */ - public function testAuthenticateRequireSSL() - { - $this->apiKey->config('require_ssl', true); - $request = new Request('/?api_key=test'); - $this->apiKey->authenticate($request, new Response()); - } - - /** - * test - * - */ - public function testAuthenticateRequireSSLNoKey() - { - $this->apiKey->config('require_ssl', true); - $request = new Request('/'); - $this->assertFalse($this->apiKey->authenticate($request, new Response())); - } - - - /** - * test - * - * @return void - */ - public function testHeaderHappy() - { - $request = $this->getMockBuilder('\Cake\Network\Request') - ->setMethods(['header']) - ->getMock(); - $request->expects($this->once()) - ->method('header') - ->with('api_key') - ->will($this->returnValue('yyy')); - $this->apiKey->config('type', 'header'); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertEquals('user-1', $result['username']); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateHeaderFail() - { - $request = $this->getMockBuilder('\Cake\Network\Request') - ->setMethods(['header']) - ->getMock(); - $request->expects($this->once()) - ->method('header') - ->with('api_key') - ->will($this->returnValue('wrong')); - $this->apiKey->config('type', 'header'); - $result = $this->apiKey->authenticate($request, new Response()); - $this->assertFalse($result); - } -} diff --git a/tests/TestCase/Auth/RememberMeAuthenticateTest.php b/tests/TestCase/Auth/RememberMeAuthenticateTest.php deleted file mode 100644 index 4a5eb09b7..000000000 --- a/tests/TestCase/Auth/RememberMeAuthenticateTest.php +++ /dev/null @@ -1,172 +0,0 @@ -controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(null) - ->setConstructorArgs([$request, $response]) - ->getMock(); - $registry = new ComponentRegistry($this->controller); - $this->rememberMe = new RememberMeAuthenticate($registry); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - public function tearDown() - { - unset($this->rememberMe, $this->controller); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateHappy() - { - $request = new Request('/'); - $request->env('HTTP_USER_AGENT', 'user-agent'); - $mockCookie = $this->getMockBuilder('Cake\Controller\Component\CookieComponent') - ->disableOriginalConstructor() - ->setMethods(['check', 'read']) - ->getMock(); - $mockCookie - ->expects($this->once()) - ->method('read') - ->with('remember_me') - ->will($this->returnValue([ - 'id' => '00000000-0000-0000-0000-000000000001', - 'user_agent' => 'user-agent' - ])); - $registry = new ComponentRegistry($this->controller); - $registry->set('Cookie', $mockCookie); - $this->rememberMe = new RememberMeAuthenticate($registry); - $result = $this->rememberMe->authenticate($request, new Response()); - $this->assertEquals('user-1', $result['username']); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateBadUser() - { - $request = new Request('/'); - $request->env('HTTP_USER_AGENT', 'user-agent'); - $mockCookie = $this->getMockBuilder('Cake\Controller\Component\CookieComponent') - ->disableOriginalConstructor() - ->setMethods(['check', 'read']) - ->getMock(); - $mockCookie - ->expects($this->once()) - ->method('read') - ->with('remember_me') - ->will($this->returnValue([ - //bad-user - 'id' => '00000000-0000-0000-0000-000000000000', - 'user_agent' => 'user-agent' - ])); - $registry = new ComponentRegistry($this->controller); - $registry->set('Cookie', $mockCookie); - $this->rememberMe = new RememberMeAuthenticate($registry); - $result = $this->rememberMe->authenticate($request, new Response()); - $this->assertFalse($result); - } - - - /** - * test - * - * @return void - */ - public function testAuthenticateBadAgent() - { - $request = new Request('/'); - $request->env('HTTP_USER_AGENT', 'user-agent'); - $mockCookie = $this->getMockBuilder('Cake\Controller\Component\CookieComponent') - ->disableOriginalConstructor() - ->setMethods(['check', 'read']) - ->getMock(); - $mockCookie - ->expects($this->once()) - ->method('read') - ->with('remember_me') - ->will($this->returnValue([ - 'id' => '00000000-0000-0000-0000-000000000001', - 'user_agent' => 'bad-agent' - ])); - $registry = new ComponentRegistry($this->controller); - $registry->set('Cookie', $mockCookie); - $this->rememberMe = new RememberMeAuthenticate($registry); - $result = $this->rememberMe->authenticate($request, new Response()); - $this->assertFalse($result); - } - - /** - * test - * - * @return void - */ - public function testAuthenticateNoCookie() - { - $request = new Request('/'); - $request->env('HTTP_USER_AGENT', 'user-agent'); - $mockCookie = $this->getMockBuilder('Cake\Controller\Component\CookieComponent') - ->disableOriginalConstructor() - ->setMethods(['check', 'read']) - ->getMock(); - $mockCookie - ->expects($this->once()) - ->method('read') - ->with('remember_me') - ->will($this->returnValue(null)); - - $registry = new ComponentRegistry($this->controller); - $registry->set('Cookie', $mockCookie); - $this->rememberMe = new RememberMeAuthenticate($registry); - $result = $this->rememberMe->authenticate($request, new Response()); - $this->assertFalse($result); - } -} diff --git a/tests/TestCase/Auth/Rules/OwnerTest.php b/tests/TestCase/Auth/Rules/OwnerTest.php deleted file mode 100644 index eac550ec8..000000000 --- a/tests/TestCase/Auth/Rules/OwnerTest.php +++ /dev/null @@ -1,263 +0,0 @@ -Owner = new Owner(); - $this->request = $this->getMockBuilder('\Cake\Network\Request') - ->getMock(); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - public function tearDown() - { - unset($this->Owner); - } - - /** - * test - * - * @return void - */ - public function testAllowed() - { - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Posts', - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertTrue($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - */ - public function testAllowedUsingTableAlias() - { - $this->Owner = new Owner([ - 'table' => 'Posts' - ]); - $this->request->params = [ - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertTrue($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - */ - public function testAllowedUsingTableInstance() - { - $this->Owner = new Owner([ - 'table' => TableRegistry::get('CakeDC/Users.Posts'), - ]); - $this->request->params = [ - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertTrue($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Table alias is empty, please define a table alias, we could not extract a default table from the request - */ - public function testAllowedShouldThrowExceptionBecauseEmptyAliasFromRequest() - { - $this->request->params = [ - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->Owner->allowed($user, 'user', $this->request); - } - - /** - * test - * - * @return void - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Missing column column_not_found in table Posts while checking ownership permissions for user 00000000-0000-0000-0000-000000000001 - */ - public function testAllowedShouldThrowExceptionBecauseForeignKeyNotPresentInTable() - { - $this->Owner = new Owner([ - 'table' => TableRegistry::get('CakeDC/Users.Posts'), - 'ownerForeignKey' => 'column_not_found', - ]); - $this->request->params = [ - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->Owner->allowed($user, 'user', $this->request); - } - - /** - * test - * - * @return void - */ - public function testNotAllowedBecauseNotOwner() - { - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Posts', - 'pass' => ['00000000-0000-0000-0000-000000000002'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertFalse($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - */ - public function testNotAllowedBecauseUserNotFound() - { - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Posts', - 'pass' => ['00000000-0000-0000-0000-000000000002'] - ]; - $user = [ - 'id' => '99999999-0000-0000-0000-000000000000', - ]; - $this->assertFalse($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - */ - public function testNotAllowedBecausePostNotFound() - { - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Posts', - 'pass' => ['99999999-0000-0000-0000-000000000000'] //not found - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertFalse($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * test - * - * @return void - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Missing column user_id in table NoDefaultTable while checking ownership permissions for user 00000000-0000-0000-0000-000000000001 - */ - public function testNotAllowedBecauseNoDefaultTable() - { - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'NoDefaultTable', - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertFalse($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * Test using the Owner rule in a belongsToMany association - * Posts belongsToMany Users - * @return void - */ - public function testAllowedBelongsToMany() - { - $this->Owner = new Owner([ - 'table' => 'PostsUsers', - 'id' => 'post_id', - ]); - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'IsNotUsed', - 'pass' => ['00000000-0000-0000-0000-000000000001'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertTrue($this->Owner->allowed($user, 'user', $this->request)); - } - - /** - * Test using the Owner rule in a belongsToMany association - * Posts belongsToMany Users - * @return void - */ - public function testNotAllowedBelongsToMany() - { - $this->Owner = new Owner([ - 'table' => 'PostsUsers', - 'id' => 'post_id', - ]); - $this->request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'IsNotUsed', - 'pass' => ['00000000-0000-0000-0000-000000000002'] - ]; - $user = [ - 'id' => '00000000-0000-0000-0000-000000000001', - ]; - $this->assertFalse($this->Owner->allowed($user, 'user', $this->request)); - } -} diff --git a/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php b/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php deleted file mode 100644 index 205064230..000000000 --- a/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php +++ /dev/null @@ -1,857 +0,0 @@ - 'admin', - 'plugin' => '*', - 'controller' => '*', - 'action' => '*', - ], - //specific actions allowed for the user role in Users plugin - [ - 'role' => 'user', - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => ['profile', 'logout'], - ], - //all roles allowed to Pages/display - [ - 'role' => '*', - 'plugin' => null, - 'controller' => ['Pages'], - 'action' => ['display'], - ], - ]; - - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - public function setUp() - { - $request = new Request(); - $response = new Response(); - - $this->controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(null) - ->setConstructorArgs([$request, $response]) - ->getMock(); - $this->registry = new ComponentRegistry($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->simpleRbacAuthorize, $this->controller); - } - - /** - * @covers CakeDC\Users\Auth\SimpleRbacAuthorize::__construct - */ - public function testConstruct() - { - //don't autoload config - $this->simpleRbacAuthorize = new SimpleRbacAuthorize($this->registry, ['autoload_config' => false]); - $this->assertEmpty($this->simpleRbacAuthorize->config('permissions')); - } - - /** - * test - * - * @return void - */ - public function testLoadPermissions() - { - $this->simpleRbacAuthorize = $this->getMockBuilder('CakeDC\Users\Auth\SimpleRbacAuthorize') - ->disableOriginalConstructor() - ->getMock(); - $reflectedClass = new ReflectionClass($this->simpleRbacAuthorize); - $loadPermissions = $reflectedClass->getMethod('_loadPermissions'); - $loadPermissions->setAccessible(true); - $permissions = $loadPermissions->invoke($this->simpleRbacAuthorize, 'missing'); - $this->assertEquals($this->defaultPermissions, $permissions); - } - - /** - * @covers CakeDC\Users\Auth\SimpleRbacAuthorize::__construct - */ - public function testConstructMissingPermissionsFile() - { - $this->simpleRbacAuthorize = $this->getMockBuilder('CakeDC\Users\Auth\SimpleRbacAuthorize') - ->setMethods(null) - ->setConstructorArgs([$this->registry, ['autoload_config' => 'does-not-exist']]) - ->getMock(); - //we should have the default permissions - $this->assertEquals($this->defaultPermissions, $this->simpleRbacAuthorize->config('permissions')); - } - - protected function assertConstructorPermissions($instance, $config, $permissions) - { - $reflectedClass = new ReflectionClass($instance); - $constructor = $reflectedClass->getConstructor(); - $constructor->invoke($this->simpleRbacAuthorize, $this->registry, $config); - - //we should have the default permissions - $resultPermissions = $this->simpleRbacAuthorize->config('permissions'); - $this->assertEquals($permissions, $resultPermissions); - } - - /** - * @covers CakeDC\Users\Auth\SimpleRbacAuthorize::__construct - */ - public function testConstructPermissionsFileHappy() - { - $permissions = [[ - 'controller' => 'Test', - 'action' => 'test' - ]]; - $className = 'CakeDC\Users\Auth\SimpleRbacAuthorize'; - $this->simpleRbacAuthorize = $this->getMockBuilder($className) - ->setMethods(['_loadPermissions']) - ->disableOriginalConstructor() - ->getMock(); - $this->simpleRbacAuthorize - ->expects($this->once()) - ->method('_loadPermissions') - ->with('permissions-happy') - ->will($this->returnValue($permissions)); - $this->assertConstructorPermissions($className, ['autoload_config' => 'permissions-happy'], $permissions); - } - - protected function preparePermissions($permissions) - { - $className = 'CakeDC\Users\Auth\SimpleRbacAuthorize'; - $simpleRbacAuthorize = $this->getMockBuilder($className) - ->setMethods(['_loadPermissions']) - ->disableOriginalConstructor() - ->getMock(); - $simpleRbacAuthorize->config('permissions', $permissions); - - return $simpleRbacAuthorize; - } - - /** - * @dataProvider providerAuthorize - */ - public function testAuthorize($permissions, $user, $requestParams, $expected, $msg = null) - { - $this->simpleRbacAuthorize = $this->preparePermissions($permissions); - $request = new Request(); - $request->plugin = Hash::get($requestParams, 'plugin'); - $request->controller = $requestParams['controller']; - $request->action = $requestParams['action']; - $prefix = Hash::get($requestParams, 'prefix'); - $request->params = []; - if ($prefix) { - $request->params['prefix'] = $prefix; - } - $extension = Hash::get($requestParams, '_ext'); - if ($extension) { - $request->params['_ext'] = $extension; - } - - $result = $this->simpleRbacAuthorize->authorize($user, $request); - $this->assertSame($expected, $result, $msg); - } - - public function providerAuthorize() - { - $trueRuleMock = $this->getMockBuilder(Rule::class) - ->setMethods(['allowed']) - ->getMock(); - $trueRuleMock->expects($this->any()) - ->method('allowed') - ->willReturn(true); - - return [ - 'happy-strict-all' => [ - //permissions - [[ - 'plugin' => 'Tests', - 'role' => 'test', - 'controller' => 'Tests', - 'action' => 'test', - 'allowed' => true, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-strict-all-deny' => [ - //permissions - [[ - 'plugin' => 'Tests', - 'role' => 'test', - 'controller' => 'Tests', - 'action' => 'test', - 'allowed' => false, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - false - ], - 'happy-plugin-null-allowed-null' => [ - //permissions - [[ - 'role' => 'test', - 'controller' => 'Tests', - 'action' => 'test', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => null, - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-plugin-asterisk' => [ - //permissions - [[ - 'plugin' => '*', - 'role' => 'test', - 'controller' => 'Tests', - 'action' => 'test', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Any', - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-plugin-asterisk-main-app' => [ - //permissions - [[ - 'plugin' => '*', - 'role' => 'test', - 'controller' => 'Tests', - 'action' => 'test', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => null, - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-role-asterisk' => [ - //permissions - [[ - 'role' => '*', - 'controller' => 'Tests', - 'action' => 'test', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'any-role', - ], - //request - [ - 'plugin' => null, - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-controller-asterisk' => [ - //permissions - [[ - 'plugin' => 'Tests', - 'role' => 'test', - 'controller' => '*', - 'action' => 'test', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'test' - ], - //expected - true - ], - 'happy-action-asterisk' => [ - //permissions - [[ - 'plugin' => 'Tests', - 'role' => 'test', - 'controller' => 'Tests', - 'action' => '*', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'any' - ], - //expected - true - ], - 'happy-some-asterisk-allowed' => [ - //permissions - [[ - 'plugin' => '*', - 'role' => 'test', - 'controller' => '*', - 'action' => '*', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'any' - ], - //expected - true - ], - 'happy-some-asterisk-deny' => [ - //permissions - [[ - 'plugin' => '*', - 'role' => 'test', - 'controller' => '*', - 'action' => '*', - 'allowed' => false, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'any' - ], - //expected - false - ], - 'all-deny' => [ - //permissions - [[ - 'plugin' => '*', - 'role' => '*', - 'controller' => '*', - 'action' => '*', - 'allowed' => false, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Any', - 'controller' => 'Any', - 'action' => 'any' - ], - //expected - false - ], - 'dasherized' => [ - //permissions - [[ - 'plugin' => 'Tests', - 'role' => 'test', - 'controller' => 'TestTests', - 'action' => 'TestAction', - 'allowed' => true, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'tests', - 'controller' => 'test-tests', - 'action' => 'test-action' - ], - //expected - true - ], - 'happy-array' => [ - //permissions - [[ - 'plugin' => ['Tests'], - 'role' => ['test'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'happy-array' => [ - //permissions - [[ - 'plugin' => ['Tests'], - 'role' => ['test'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'three' - ], - //expected - false - ], - 'happy-callback-check-params' => [ - //permissions - [[ - 'plugin' => ['Tests'], - 'role' => ['test'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - 'allowed' => function ($user, $role, $request) { - return $user['id'] === 1 && $role = 'test' && $request->plugin == 'Tests'; - } - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'happy-callback-deny' => [ - //permissions - [[ - 'plugin' => ['*'], - 'role' => ['test'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - 'allowed' => function ($user, $role, $request) { - return false; - } - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'plugin' => 'Tests', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - false - ], - 'happy-prefix' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => ['admin'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'deny-prefix' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => ['admin'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - false - ], - 'star-prefix' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => '*', - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'array-prefix' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => ['one', 'admin'], - 'controller' => '*', - 'action' => '*', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'array-prefix' => [ - //permissions - [ - [ - 'role' => ['test'], - 'prefix' => ['one', 'admin'], - 'controller' => '*', - 'action' => 'one', - 'allowed' => false, - ], - [ - 'role' => ['test'], - 'prefix' => ['one', 'admin'], - 'controller' => '*', - 'action' => '*', - ], - ], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - false - ], - 'happy-ext' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => ['admin'], - 'extension' => ['csv'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - '_ext' => 'csv', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'deny-ext' => [ - //permissions - [[ - 'role' => ['test'], - 'extension' => ['csv'], - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - 'allowed' => false, - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'controller' => 'Tests', - '_ext' => 'csv', - 'action' => 'one' - ], - //expected - false - ], - 'star-ext' => [ - //permissions - [[ - 'role' => ['test'], - 'prefix' => '*', - 'extension' => '*', - 'controller' => ['Tests'], - 'action' => ['one', 'two'], - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'admin', - '_ext' => 'other', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'array-ext' => [ - //permissions - [[ - 'role' => ['test'], - 'extension' => ['csv', 'pdf'], - 'controller' => '*', - 'action' => '*', - ]], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - '_ext' => 'csv', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - 'array-ext' => [ - //permissions - [ - [ - 'role' => ['test'], - 'extension' => ['csv', 'docx'], - 'controller' => '*', - 'action' => 'one', - 'allowed' => false, - ], - [ - 'role' => ['test'], - 'extension' => ['csv', 'docx'], - 'controller' => '*', - 'action' => '*', - ], - ], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'prefix' => 'csv', - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - false - ], - 'rule-class' => [ - //permissions - [ - [ - 'role' => ['test'], - 'controller' => '*', - 'action' => 'one', - 'allowed' => $trueRuleMock, - ], - ], - //user - [ - 'id' => 1, - 'username' => 'luke', - 'role' => 'test', - ], - //request - [ - 'controller' => 'Tests', - 'action' => 'one' - ], - //expected - true - ], - ]; - } -} diff --git a/tests/TestCase/Auth/SocialAuthenticateTest.php b/tests/TestCase/Auth/SocialAuthenticateTest.php deleted file mode 100644 index 392bdcb40..000000000 --- a/tests/TestCase/Auth/SocialAuthenticateTest.php +++ /dev/null @@ -1,485 +0,0 @@ -Table = TableRegistry::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 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\Network\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(['session']) - ->getMock(); - $this->Request->expects($this->any()) - ->method('session') - ->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); - } -} diff --git a/tests/TestCase/Auth/SuperuserAuthorizeTest.php b/tests/TestCase/Auth/SuperuserAuthorizeTest.php deleted file mode 100644 index 29d12d1b9..000000000 --- a/tests/TestCase/Auth/SuperuserAuthorizeTest.php +++ /dev/null @@ -1,92 +0,0 @@ -controller = $this->getMockBuilder('Cake\Controller\Controller') - ->setMethods(null) - ->setConstructorArgs([$request, $response]) - ->getMock(); - $registry = new ComponentRegistry($this->controller); - $this->superuserAuthorize = new SuperuserAuthorize($registry); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - public function tearDown() - { - unset($this->superuserAuthorize, $this->controller); - } - - /** - * @covers CakeDC\Users\Auth\SuperuserAuthorize::authorize - */ - public function testAuthorizeIsSuperuser() - { - $user = [ - 'is_superuser' => true, - ]; - $request = new Request(); - $result = $this->superuserAuthorize->authorize($user, $request); - $this->assertTrue($result); - } - - /** - * @covers CakeDC\Users\Auth\SuperuserAuthorize::authorize - */ - public function testAuthorizeIsNotSuperuser() - { - $user = [ - 'is_superuser' => false, - ]; - $request = new Request(); - $result = $this->superuserAuthorize->authorize($user, $request); - $this->assertFalse($result); - } - - /** - * @covers CakeDC\Users\Auth\SuperuserAuthorize::authorize - */ - public function testAuthorizeWeirdUser() - { - $request = new Request(); - $user = 'non array'; - $result = $this->superuserAuthorize->authorize($user, $request); - $this->assertFalse($result); - } -} 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/RememberMeComponentTest.php b/tests/TestCase/Controller/Component/RememberMeComponentTest.php deleted file mode 100644 index 701bdf587..000000000 --- a/tests/TestCase/Controller/Component/RememberMeComponentTest.php +++ /dev/null @@ -1,201 +0,0 @@ -request = new Request('controller_posts/index'); - $this->request->params['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::salt(); - Security::salt('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::salt($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 Request('/'))->env('HTTP_USER_AGENT', 'user-agent'); - $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->rememberMeComponent->Auth->expects($this->once()) - ->method('redirectUrl') - ->will($this->returnValue('/login')); - $this->controller->expects($this->once()) - ->method('redirect') - ->with('/login'); - $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 e6db00b79..000000000 --- a/tests/TestCase/Controller/Component/UsersAuthComponentTest.php +++ /dev/null @@ -1,436 +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::salt('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\Network\Response') - ->setMethods(['stop']) - ->getMock(); - $this->Controller = new Controller($this->request, $this->response); - $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->data = [ - '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->data = [ - '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->data = [ - '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->data = [ - '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->data = [ - '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])); - $request = new Request('/route'); - $request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - 'pass' => [], - '_matchedRoute' => '/route/*', - ]; - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $request) - ->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->data = [ - '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->data = [ - '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->data = [ - '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])); - $request = new Request('/route'); - $request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - 'pass' => [], - '_matchedRoute' => '/route/*', - ]; - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $request) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * test - * - * @return void - */ - public function testIsUrlAuthorizedUrlRelativeForCurrentAppString() - { - $event = new Event('event'); - $event->data = [ - '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])); - $request = new Request('/route'); - $request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - 'pass' => [], - '_matchedRoute' => '/route/*', - ]; - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $request) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } - - /** - * - * - * @return void - */ - public function testIsUrlAuthorizedUrlAbsoluteForOtherAppString() - { - $event = new Event('event'); - $event->data = [ - '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->data = [ - '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])); - $request = new Request('/route/pass-one'); - $request->params = [ - 'plugin' => 'CakeDC/Users', - 'controller' => 'Users', - 'action' => 'requestResetPassword', - 'pass' => ['pass-one'], - '_matchedRoute' => '/route/*', - ]; - $this->Controller->Auth->expects($this->once()) - ->method('isAuthorized') - ->with(null, $request) - ->will($this->returnValue(true)); - $result = $this->Controller->UsersAuth->isUrlAuthorized($event); - $this->assertTrue($result); - } -} diff --git a/tests/TestCase/Controller/SocialAccountsControllerTest.php b/tests/TestCase/Controller/SocialAccountsControllerTest.php index c85dc2e86..47a544d7e 100644 --- a/tests/TestCase/Controller/SocialAccountsControllerTest.php +++ b/tests/TestCase/Controller/SocialAccountsControllerTest.php @@ -1,23 +1,22 @@ 'Debug' - ]); - $this->configEmail = Email::config('default'); - Email::config('default', [ + 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 Request('/users/users/index'); - $request->params['plugin'] = 'CakeDC/Users'; + $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' - ]); } /** @@ -72,11 +67,11 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { Email::drop('default'); - Email::dropTransport('test'); - Email::config('default', $this->configEmail); + TransportFactory::drop('test'); + //Email::setConfig('default', $this->configEmail); Configure::write('Opauth', $this->configOpauth); Configure::write('Users.RememberMe.active', $this->configRememberMe); @@ -93,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Account validated successfully', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -107,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Invalid token and/or social account', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -121,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Social Account already active', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -134,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); @@ -143,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Email sent successfully', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -157,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); @@ -166,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Email could not be sent', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -181,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->session()->read('Flash.flash.0.message')); + $this->assertEquals('Invalid account', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); } /** @@ -195,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->session()->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 299de8ba5..693940e8a 100644 --- a/tests/TestCase/Controller/Traits/BaseTraitTest.php +++ b/tests/TestCase/Controller/Traits/BaseTraitTest.php @@ -1,22 +1,40 @@ loadPlugins(['CakeDC/Users' => ['routes' => true]]); $traitMockMethods = array_unique(array_merge(['getUsersTable'], $this->traitMockMethods)); - $this->table = TableRegistry::get('CakeDC/Users.Users'); + $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::configTransport('test', [ - 'className' => 'Debug' + TransportFactory::setConfig('test', [ + 'className' => 'Debug', ]); - $this->configEmail = Email::config('default'); - Email::config('default', [ + $this->configEmail = Email::getConfig('default'); + Email::drop('default'); + Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); } } @@ -76,13 +112,13 @@ 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'); - Email::config('default', $this->configEmail); + TransportFactory::drop('test'); + //Email::setConfig('default', $this->setConfigEmail); } parent::tearDown(); } @@ -90,20 +126,23 @@ public function tearDown() /** * Mock session and mock session attributes * - * @return void + * @return \Cake\Http\Session */ protected function _mockSession($attributes) { - $session = new \Cake\Network\Session(); + $session = new \Cake\Http\Session(); foreach ($attributes as $field => $value) { $session->write($field, $value); } - $this->Trait->request + $this->Trait + ->getRequest() ->expects($this->any()) - ->method('session') + ->method('getSession') ->willReturn($session); + + return $session; } /** @@ -113,19 +152,20 @@ protected function _mockSession($attributes) */ protected function _mockRequestGet($withSession = false) { - $methods = ['is', 'referer', 'data']; + $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); } /** @@ -149,13 +189,14 @@ protected function _mockFlash() */ protected function _mockRequestPost($with = 'post') { - $this->Trait->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['is', 'data', 'allow']) + $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); } /** @@ -165,47 +206,152 @@ 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 Auth component + * Mock the Authentication service * + * @param array $user + * @param array $failures + * @param \Authentication\Identifier\IdentifierCollection $identifiers custom identifiers collection * @return void */ - protected function _mockAuth() + protected function _mockAuthentication($user = null, $failures = [], $identifiers = null) { - $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['user', 'identify', 'setUser', 'redirectUrl']) - ->disableOriginalConstructor() - ->getMock(); + 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 Authentication service with a Password Rehash being required. + * + * @param array $user + * @param array $failures + * @return void + */ + protected function _mockAuthenticationWithPasswordRehash($user = null, $failures = []) + { + $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, + ]); } /** * mock utility * * @param Event $event event + * @param array $result array of data * @return void */ - protected function _mockDispatchEvent(Event $event = null) + protected function _mockDispatchEvent(?Event $event = null, $result = []) { if (is_null($event)) { $event = new Event('cool-name-here'); } + + if (!empty($result)) { + $event->setResult(new Entity($result)); + } $this->Trait->expects($this->any()) ->method('dispatchEvent') ->will($this->returnValue($event)); diff --git a/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php index 89c000651..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(); } @@ -33,7 +35,7 @@ public function tearDown() public function testGetUsersTable() { $table = $this->controller->Trait->getUsersTable(); - $this->assertEquals('CakeDC/Users.Users', $table->registryAlias()); + $this->assertEquals('CakeDC/Users.Users', $table->getRegistryAlias()); $newTable = new Table(); $this->controller->Trait->setUsersTable($newTable); $this->assertSame($newTable, $this->controller->Trait->getUsersTable()); 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('Facebook'); + $this->assertResponseContains('Twitter'); + $this->assertResponseNotContains('Google'); + $this->assertResponseNotContains('/link-social/amazon'); + $this->assertResponseNotContains('Amazon'); + $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('user-1'); + $this->assertResponseContains('user-1@test.com'); + $this->assertResponseContains('first1'); + $this->assertResponseContains('last1'); + $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('user-6'); + $this->assertResponseContains('6@example.com'); + $this->assertResponseContains('first-user-6'); + $this->assertResponseContains('firts name 6'); + $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 new file mode 100644 index 000000000..30ed9fe7b --- /dev/null +++ b/tests/TestCase/Controller/Traits/LinkSocialTraitTest.php @@ -0,0 +1,546 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + + parent::setUp(); + $request = new ServerRequest(); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set']) + ->getMock(); + + $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') + ->setMethods(['setConfig']) + ->disableOriginalConstructor() + ->getMock(); + + $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); + } + + /** + * test linkSocial method + * + * @return void + */ + 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\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', '_createSocialProvider', 'getUsersTable', 'log']) + ->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->_mockAuthLoggedIn(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockFlash(); + + $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')); + + $this->Trait->Flash->expects($this->never()) + ->method('error'); + + $this->Trait->Flash->expects($this->never()) + ->method('success'); + + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->equalTo('http://facebook.com/redirect/url')) + ->will($this->returnValue(new Response())); + + $this->Trait->linkSocial('facebook'); + } + + /** + * test + * + * @return void + */ + public function testCallbackLinkSocialHappy() + { + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + + $Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $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->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)); + + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->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->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($Table)); + + $this->_mockAuthLoggedIn(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->never()) + ->method('error'); + + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->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 FrozenTime(); + $tokenExpires = $expiresTime->setTimestamp($Token->getExpires())->format('Y-m-d H:i:s'); + + $expected = [ + 'provider' => 'facebook', + 'username' => 'mock_username', + 'reference' => '9999911112255', + 'avatar' => 'https://graph.facebook.com/9999911112255/picture?type=large', + '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, + ]; + foreach ($expected as $property => $value) { + $this->assertEquals($value, $actual->$property); + } + $this->assertEquals($tokenExpires, $actual->token_expires->format('Y-m-d H:i:s')); + } + + /** + * test + * + * @return void + */ + public function testCallbackLinkSocialWithValidationErrors() + { + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + $user = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->get('00000000-0000-0000-0000-000000000001'); + $user->setErrors([ + 'social_accounts' => [ + '_existsIn' => __d('cake_d_c/users', 'Social account already associated to another user'), + ], + ]); + $Table = $this->getMockForModel('CakeDC/Users.Users', ['linkSocialAccount', 'get']); + $Table->setAlias('Users'); + + $Table->expects($this->once()) + ->method('get') + ->will($this->returnValue($user)); + + $Table->expects($this->once()) + ->method('linkSocialAccount') + ->will($this->returnValue($user)); + + $Token = new \League\OAuth2\Client\Token\AccessToken([ + 'access_token' => 'test-token', + 'expires' => 1490988496, + ]); + + $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->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)); + + $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->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->_mockAuthLoggedIn(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error'); + + $this->Trait->Flash->expects($this->never()) + ->method('success'); + + $this->Trait->callbackLinkSocial('facebook'); + + $actual = $Table->SocialAccounts->exists(['reference' => '9999911112255']); + $this->assertFalse($actual); + } + + /** + * test + * + * @return void + */ + public function testCallbackLinkSocialQueryHasErrors() + { + 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->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->never()) + ->method('getAccessToken'); + + $this->Provider->expects($this->never()) + ->method('getResourceOwner'); + + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->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()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'facebook', + ])); + + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($Table)); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->equalTo([ + 'action' => 'profile', + ])) + ->will($this->returnValue(new Response())); + $this->_mockAuthLoggedIn(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->never()) + ->method('success'); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with(__d('cake_d_c/users', 'Could not associate account, please try again.')); + + $result = $this->Trait->callbackLinkSocial('facebook'); + $this->assertInstanceOf(Response::class, $result); + } + + /** + * test + * + * @return void + */ + public function testCallbackLinkSocialUnknownProvider() + { + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + + $this->Provider->expects($this->never()) + ->method('getAuthorizationUrl'); + + $this->Provider->expects($this->never()) + ->method('getState'); + + $this->Provider->expects($this->never()) + ->method('getAccessToken'); + + $this->Provider->expects($this->never()) + ->method('getResourceOwner'); + + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable', 'log']) + ->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()->withAttribute('params', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'linkSocial', + 'provider' => 'unknown', + ])); + + $this->Trait->expects($this->never()) + ->method('getUsersTable'); + + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->equalTo([ + 'action' => 'profile', + ])) + ->will($this->returnValue(new Response())); + $this->_mockAuthLoggedIn(); + $this->_mockDispatchEvent(new Event('event')); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->never()) + ->method('success'); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with(__d('cake_d_c/users', 'Could not associate account, please try again.')); + + $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 254d50f87..79d0a860b 100644 --- a/tests/TestCase/Controller/Traits/LoginTraitTest.php +++ b/tests/TestCase/Controller/Traits/LoginTraitTest.php @@ -1,27 +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 Request(); - $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\LoginTrait') - ->setMethods(['dispatchEvent', 'redirect']) - ->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(['config']) + ->setMethods(['setConfig']) ->disableOriginalConstructor() ->getMock(); - - $this->Trait->request = $request; } /** @@ -54,7 +56,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -66,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); } /** @@ -160,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); } /** @@ -220,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); } /** @@ -251,16 +286,15 @@ public function testLogout() { $this->_mockDispatchEvent(new Event('event')); $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') - ->setMethods(['logout']) + ->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() @@ -272,122 +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' - ] - ]; + $identifiers = new IdentifierCollection([ + 'CakeDC/Users.Social', + ]); + $FormAuth = new FormAuthenticator($identifiers); + $SessionAuth = new SessionAuthenticator($identifiers); + $SocialAuth = new $AuthClass($identifiers); + + $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->_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); + $this->_mockFlash(); - $this->_mockRequestGet(); + $this->_mockAuthentication(null, $failures); $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Your user has not been validated yet. Please check your inbox for instructions'); - - $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->method('error') + ->with($message); - $this->Trait->Auth->expects($this->at(0)) - ->method('config') - ->with('authError', 'Your user has not been validated yet. Please check your inbox for instructions'); + $registry = new ComponentRegistry(); + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $failureConfig]) + ->getMock(); - $this->Trait->Auth->expects($this->at(1)) - ->method('config') - ->with('flash.params', ['class' => 'success']); + $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)); - $this->Trait->failedSocialLogin($event->data['exception'], $event->data['rawData'], true); + 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 testFailedSocialUserAccountNotActive() + public function testSocialLoginSuccess() { - $event = new Entity(); - $event->data = [ - 'exception' => new AccountNotActiveException('Facebook user-1'), - 'rawData' => [ - 'id' => 111111, - 'username' => 'user-1' - ] - ]; - $this->_mockFlash(); - $this->_mockRequestGet(); - $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Your social account 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); - $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($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 testFailedSocialUserAccount() - { - $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'); - + $this->_mockAuthentication(['id' => 1], $failures); + $this->Trait->Flash->expects($this->never()) + ->method('error'); $this->Trait->expects($this->once()) ->method('redirect') - ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']); + ->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(); + + $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)); - $this->Trait->failedSocialLogin(null, $event->data['rawData'], true); + $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 b681227be..9a3d8a6ad 100644 --- a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php +++ b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php @@ -1,33 +1,39 @@ traitClassName = 'CakeDC\Users\Controller\Traits\PasswordManagementTrait'; - $this->traitMockMethods = ['set', 'redirect', 'validate']; + $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,18 +56,18 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'password' => 'new', 'password_confirm' => 'new', ])); $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,11 +84,11 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'password' => 'new', 'password_confirm' => 'wrong_new', @@ -93,6 +99,41 @@ public function testChangePasswordWithError() $this->Trait->changePassword(); } + /** + * test + * + * @return void + */ + public function testChangePasswordWithAfterChangeEvent() + { + $this->assertEquals('12345', $this->table->get('00000000-0000-0000-0000-000000000001')->password); + $this->_mockRequestPost(['post', 'put']); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'password' => 'new', + 'password_confirm' => 'new', + ])); + $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->Flash->expects($this->any()) + ->method('success') + ->with('Password has been changed successfully'); + $this->Trait->changePassword(); + $hasher = PasswordHasherFactory::build('Default'); + $this->assertTrue($hasher->check('new', $this->table->get('00000000-0000-0000-0000-000000000001')->password)); + } + /** * test * @@ -104,11 +145,11 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'current_password' => '12345', 'password' => '12345', @@ -127,11 +168,11 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'current_password' => '', 'password' => '54321', @@ -154,11 +195,11 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'current_password' => 'wrong-password', 'password' => '12345', @@ -177,11 +218,11 @@ 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()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->will($this->returnValue([ 'password' => 'new', 'password_confirm' => 'new', @@ -199,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') @@ -218,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') @@ -242,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') @@ -274,8 +331,8 @@ 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()) - ->method('data'); + $this->Trait->getRequest()->expects($this->never()) + ->method('getData'); $this->Trait->requestResetPassword(); } @@ -291,8 +348,8 @@ public function testRequestPasswordHappy() $this->_mockAuthLoggedIn(); $this->_mockFlash(); $reference = 'user-2'; - $this->Trait->request->expects($this->once()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->with('reference') ->will($this->returnValue($reference)); $this->Trait->Flash->expects($this->any()) @@ -313,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()) - ->method('data') + $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); } /** @@ -334,19 +395,22 @@ public function testRequestPasswordEmptyReference() $this->_mockAuthLoggedIn(['id' => 'invalid-id', 'password' => 'invalid-pass']); $this->_mockFlash(); $reference = ''; - $this->Trait->request->expects($this->once()) - ->method('data') + $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) @@ -362,8 +426,8 @@ public function testEnsureUserActiveForResetPasswordFeature($ensureActive) $this->_mockRequestPost(); $this->_mockFlash(); $reference = 'user-1'; - $this->Trait->request->expects($this->once()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->with('reference') ->will($this->returnValue($reference)); $this->Trait->Flash->expects($expectError) @@ -380,7 +444,61 @@ public function ensureUserActiveForResetPasswordFeature() return [ [$ensureActive], - [$defaultBehavior] + [$defaultBehavior], + ]; + } + + /** + * @dataProvider ensureOneTimePasswordAuthenticatorResets + * @return void + */ + public function testRequestGoogleAuthTokenResetWithValidUser($userId, $entityId, $method, $msg) + { + $this->_mockRequestPost(); + $this->_mockFlash(); + + $user = $this->table->get($userId); + + $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') + ->setMethods(['user', 'config']) + ->disableOriginalConstructor() + ->getMock(); + + $this->Trait->Auth->expects($this->any()) + ->method('user') + ->will($this->returnValue($user)); + + $this->Trait->Flash->expects($this->any()) + ->method($method) + ->with($msg); + $response = new \Cake\Http\Response(); + $response = $response->withLocation('/'); + $this->Trait->expects($this->any()) + ->method('redirect') + ->willReturn($response); + + $actual = $this->Trait->resetOneTimePasswordAuthenticator($entityId); + $this->assertSame($response, $actual); + } + + public function ensureOneTimePasswordAuthenticatorResets() + { + $error = 'error'; + $success = 'success'; + $errorMsg = 'Could not reset Google Authenticator'; + $successMsg = 'Google Authenticator token was successfully reset'; + + return [ + //is_superuser = true. + ['00000000-0000-0000-0000-000000000003', null, $success, $successMsg], + //is_superuser = true. + ['00000000-0000-0000-0000-000000000001', null, $success, $successMsg], + //is_superuser = false, and not his profile. + ['00000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000001', $error, $errorMsg], + //is_superuser = false, editing own record. + ['00000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000004', $success, $successMsg], + //is_superuser = false, and no entity-id given. + ['00000000-0000-0000-0000-000000000004', null, $error, $errorMsg], ]; } } diff --git a/tests/TestCase/Controller/Traits/ProfileTraitTest.php b/tests/TestCase/Controller/Traits/ProfileTraitTest.php index c88a45cb1..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 new file mode 100644 index 000000000..d05b17e49 --- /dev/null +++ b/tests/TestCase/Controller/Traits/ReCaptchaTraitTest.php @@ -0,0 +1,145 @@ +Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait') + ->setMethods(['_getReCaptchaInstance']) + ->getMockForTrait(); + } + + /** + * tearDown callback + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * testValidateValidReCaptcha + * + * @return void + */ + public function testValidateValidReCaptcha() + { + $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(true)); + $ReCaptcha->expects($this->once()) + ->method('verify') + ->with('value') + ->will($this->returnValue($Response)); + $this->Trait->expects($this->once()) + ->method('_getReCaptchaInstance') + ->will($this->returnValue($ReCaptcha)); + $actual = $this->Trait->validateReCaptcha('value', '255.255.255.255'); + $this->assertTrue($actual); + } + + /** + * testValidateInvalidReCaptcha + * + * @return void + */ + public function testValidateInvalidReCaptcha() + { + $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('invalid') + ->will($this->returnValue($Response)); + $this->Trait->expects($this->once()) + ->method('_getReCaptchaInstance') + ->will($this->returnValue($ReCaptcha)); + $actual = $this->Trait->validateReCaptcha('invalid', '255.255.255.255'); + $this->assertFalse($actual); + } + + public function testGetRecaptchaInstance() + { + Configure::write('Users.reCaptcha.secret', 'secret'); + $trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait')->getMockForTrait(); + $method = new ReflectionMethod(get_class($trait), '_getReCaptchaInstance'); + $method->setAccessible(true); + $method->invokeArgs($trait, []); + $this->assertNotEmpty($method->invoke($trait)); + } + + public function testGetRecaptchaInstanceNull() + { + $trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait')->getMockForTrait(); + $method = new ReflectionMethod(get_class($trait), '_getReCaptchaInstance'); + $method->setAccessible(true); + $method->invokeArgs($trait, []); + $this->assertNull($method->invoke($trait)); + } + + public function testValidateReCaptchaFalse() + { + $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/RecaptchaTraitTest.php b/tests/TestCase/Controller/Traits/RecaptchaTraitTest.php deleted file mode 100644 index 8ce79ab38..000000000 --- a/tests/TestCase/Controller/Traits/RecaptchaTraitTest.php +++ /dev/null @@ -1,96 +0,0 @@ -Trait = $this->getMockBuilder('CakeDC\Users\Controller\Traits\ReCaptchaTrait') - ->setMethods(['_getReCaptchaInstance']) - ->getMockForTrait(); - } - - /** - * tearDown callback - * - * @return void - */ - public function tearDown() - { - parent::tearDown(); - } - - /** - * testValidateValidReCaptcha - * - * @return void - */ - public function testValidateValidReCaptcha() - { - $ReCaptcha = $this->getMockBuilder('ReCaptcha\ReCaptcha') - ->setMethods(['verify']) - ->disableOriginalConstructor() - ->getMock(); - $Response = $this->getMockBuilder('ReCaptcha\Response') - ->setMethods(['isSuccess']) - ->disableOriginalConstructor() - ->getMock(); - $Response->expects($this->any()) - ->method('isSuccess') - ->will($this->returnValue(true)); - $ReCaptcha->expects($this->any()) - ->method('verify') - ->with('value') - ->will($this->returnValue($Response)); - $this->Trait->expects($this->any()) - ->method('_getReCaptchaInstance') - ->will($this->returnValue($ReCaptcha)); - $this->Trait->validateReCaptcha('value', '255.255.255.255'); - } - - /** - * testValidateInvalidReCaptcha - * - * @return void - */ - public function testValidateInvalidReCaptcha() - { - $ReCaptcha = $this->getMockBuilder('ReCaptcha\ReCaptcha') - ->setMethods(['verify']) - ->disableOriginalConstructor() - ->getMock(); - $Response = $this->getMockBuilder('ReCaptcha\Response') - ->setMethods(['isSuccess']) - ->disableOriginalConstructor() - ->getMock(); - $Response->expects($this->any()) - ->method('isSuccess') - ->will($this->returnValue(false)); - $ReCaptcha->expects($this->any()) - ->method('verify') - ->with('invalid') - ->will($this->returnValue($Response)); - $this->Trait->expects($this->any()) - ->method('_getReCaptchaInstance') - ->will($this->returnValue($ReCaptcha)); - $this->Trait->validateReCaptcha('invalid', '255.255.255.255'); - } -} diff --git a/tests/TestCase/Controller/Traits/RegisterTraitTest.php b/tests/TestCase/Controller/Traits/RegisterTraitTest.php index 54e8b6659..1ee7df055 100644 --- a/tests/TestCase/Controller/Traits/RegisterTraitTest.php +++ b/tests/TestCase/Controller/Traits/RegisterTraitTest.php @@ -1,21 +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'); } /** @@ -39,7 +38,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); } @@ -53,8 +52,8 @@ public function testValidateEmail() { $token = 'token'; $this->Trait->expects($this->once()) - ->method('validate') - ->with('email', $token); + ->method('validate') + ->with('email', $token); $this->Trait->validateEmail($token); } @@ -65,27 +64,103 @@ 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()) - ->method('success') - ->with('Please validate your account before log in'); + ->method('success') + ->with('Please validate your account before log in'); $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['action' => 'login']); - $this->Trait->request->data = [ + ->method('redirect') + ->with(['action' => 'login']); + $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(); + + $this->assertEquals(1, $this->table->find()->where(['username' => 'testRegistration'])->count()); + } + + /** + * Triggering beforeRegister event and not able to register the user + * + * @return void + */ + public function testRegisterWithEventFalseResult() + { + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(new Event('Users.Component.UsersAuth.beforeRegister'), ['username' => 'hello']); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('The user could not be saved'); + $this->Trait->expects($this->never()) + ->method('redirect'); + $this->Trait->getRequest()->expects($this->never()) + ->method('is'); + + $this->Trait->register(); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + } + + /** + * Triggering beforeRegister event and registering the user successfully + * + * @return void + */ + 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->Trait->register(); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(new Event('Users.Component.UsersAuth.beforeRegister'), $data); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue($data)); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('Please validate your account before log in'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->getRequest()->expects($this->never()) + ->method('is'); + $this->Trait->register(); $this->assertEquals(1, $this->table->find()->where(['username' => 'testRegistration'])->count()); } @@ -96,33 +171,42 @@ public function testRegister() */ 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()) - ->method('success') - ->with('Please validate your account before log in'); + ->method('success') + ->with('Please validate your account before log in'); $this->Trait->expects($this->once()) - ->method('validateRecaptcha') - ->will($this->returnValue(true)); + ->method('validateRecaptcha') + ->will($this->returnValue(true)); $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['action' => 'login']); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'password', - 'tos' => 1 - ]; + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->getRequest()->expects($this->any()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); $this->Trait->register(); $this->assertEquals(1, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.reCaptcha.registration', false); } /** @@ -135,29 +219,31 @@ 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()) - ->method('error') - ->with('The user could not be saved'); + ->method('error') + ->with('The user could not be saved'); $this->Trait->expects($this->once()) - ->method('validateRecaptcha') - ->will($this->returnValue(true)); + ->method('validateRecaptcha') + ->will($this->returnValue(true)); $this->Trait->expects($this->never()) - ->method('redirect'); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'not-matching', - 'tos' => 1 - ]; + ->method('redirect'); + $this->Trait->getRequest()->expects($this->any()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'not-matching', + 'tos' => 1, + ])); $this->Trait->register(); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.reCaptcha.registration', false); } /** @@ -170,27 +256,29 @@ 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()) - ->method('validateRecaptcha') - ->will($this->returnValue(false)); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'password', - 'tos' => 1 - ]; + ->method('error') + ->with('Invalid reCaptcha'); + $this->Trait->expects($this->any()) + ->method('validateRecaptcha') + ->will($this->returnValue(false)); + $this->Trait->getRequest()->expects($this->any()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); $this->Trait->register(); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.reCaptcha.registration', false); } /** @@ -202,15 +290,15 @@ 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()) - ->method('success'); + ->method('success'); $this->Trait->expects($this->never()) - ->method('validateRecaptcha'); + ->method('validateRecaptcha'); $this->Trait->expects($this->never()) - ->method('redirect'); + ->method('redirect'); $this->Trait->register(); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); @@ -223,51 +311,57 @@ public function testRegisterGet() */ public function testRegisterRecaptchaDisabled() { - $recaptcha = Configure::read('Users.Registration.reCaptcha'); + $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()) - ->method('success') - ->with('Please validate your account before log in'); + ->method('success') + ->with('Please validate your account before log in'); $this->Trait->expects($this->never()) - ->method('validateRecaptcha'); + ->method('validateRecaptcha'); $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['action' => 'login']); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'password', - 'tos' => 1 - ]; + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); $this->Trait->register(); $this->assertEquals(1, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.Registration.reCaptcha', $recaptcha); } /** * test * * @return void - * @expectedException Cake\Network\Exception\NotFoundException */ public function testRegisterNotEnabled() { - $active = Configure::read('Users.Registration.active'); + $this->expectException(NotFoundException::class); Configure::write('Users.Registration.active', false); $this->_mockRequestPost(); - $this->_mockAuth(); + $this->_mockAuthentication(); $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->register(); - Configure::write('Users.Registration.active', $active); } /** @@ -277,7 +371,13 @@ public function testRegisterNotEnabled() */ public function testRegisterLoggedInUserAllowed() { - $allowLoggedIn = Configure::read('Users.Registration.allowLoggedIn'); + $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(); @@ -285,23 +385,25 @@ public function testRegisterLoggedInUserAllowed() $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) - ->method('success') - ->with('Please validate your account before log in'); + ->method('success') + ->with('Please validate your account before log in'); $this->Trait->expects($this->once()) - ->method('redirect') - ->with(['action' => 'login']); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'password', - 'tos' => 1 - ]; + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); $this->Trait->register(); $this->assertEquals(1, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.Registration.allowLoggedIn', $allowLoggedIn); } /** @@ -311,7 +413,6 @@ public function testRegisterLoggedInUserAllowed() */ public function testRegisterLoggedInUserNotAllowed() { - $allowLoggedIn = Configure::read('Users.Registration.allowLoggedIn'); Configure::write('Users.Registration.allowLoggedIn', false); $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); $this->_mockRequestPost(); @@ -319,22 +420,78 @@ public function testRegisterLoggedInUserNotAllowed() $this->_mockFlash(); $this->_mockDispatchEvent(); $this->Trait->Flash->expects($this->once()) - ->method('error') - ->with('You must log out to register a new user account'); + ->method('error') + ->with('You must log out to register a new user account'); $this->Trait->expects($this->once()) - ->method('redirect') - ->with(Configure::read('Users.Profile.route')); - $this->Trait->request->data = [ - 'username' => 'testRegistration', - 'password' => 'password', - 'email' => 'test-registration@example.com', - 'password_confirm' => 'password', - 'tos' => 1 - ]; + ->method('redirect') + ->with(Configure::read('Users.Profile.route')); + $this->Trait->getRequest()->expects($this->never()) + ->method('getData') + ->with(); $this->Trait->register(); + } - $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); - Configure::write('Users.Registration.allowLoggedIn', $allowLoggedIn); + /** + * 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 2e807ebda..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; $this->Trait->expects($this->any()) - ->method('set') - ->will($this->returnCallback(function ($param1, $param2 = null) use ($viewVarsContainer) { - $viewVarsContainer->viewVars[$param1] = $param2; - })); + ->method('set') + ->will($this->returnCallback(function ($param1, $param2 = null) use ($viewVarsContainer) { + $viewVarsContainer->viewVars[$param1] = $param2; + })); $this->Trait->expects($this->once()) - ->method('loadModel') - ->will($this->returnValue($this->table)); + ->method('loadModel') + ->will($this->returnValue($this->table)); } /** @@ -44,7 +51,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { $this->viewVars = null; parent::tearDown(); @@ -58,17 +65,17 @@ public function tearDown() public function testIndex() { $this->Trait->expects($this->once()) - ->method('paginate') - ->with($this->table) - ->will($this->returnValue([])); + ->method('paginate') + ->with($this->table) + ->will($this->returnValue([])); $this->Trait->index(); $expected = [ 'Users' => [], '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,14 +152,20 @@ public function testAddPostHappy() $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->data = [ - 'username' => 'testuser', - 'email' => 'testuser@test.com', - 'password' => 'password', - 'first_name' => 'test', - 'last_name' => 'user', - ]; - + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(true)); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + ])); $this->Trait->Flash->expects($this->once()) ->method('success') ->with('The User has been saved'); @@ -172,12 +185,19 @@ public function testAddPostErrors() $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->data = [ - 'username' => 'testuser', - 'email' => 'testuser@test.com', - 'first_name' => 'test', - 'last_name' => 'user', - ]; + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(true)); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'first_name' => 'test', + 'last_name' => 'user', + ])); $this->Trait->Flash->expects($this->once()) ->method('error') @@ -198,9 +218,16 @@ 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->data = [ - 'email' => 'newtestuser@test.com', - ]; + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with(['patch', 'post', 'put']) + ->will($this->returnValue(true)); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'email' => 'newtestuser@test.com', + ])); $this->Trait->Flash->expects($this->once()) ->method('success') @@ -221,9 +248,16 @@ 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->data = [ - 'email' => 'not-an-email', - ]; + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with(['patch', 'post', 'put']) + ->will($this->returnValue(true)); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with() + ->will($this->returnValue([ + 'email' => 'not-an-email', + ])); $this->Trait->Flash->expects($this->once()) ->method('error') @@ -238,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()) @@ -263,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 584292905..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\Network\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(['session']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('session') - ->will($this->returnValue($session)); - - $this->controller->Trait->socialEmail(); + parent::tearDown(); } /** - * Test socialEmail + * test socialLogin success * - * @expectedException \Cake\Network\Exception\NotFoundException + * @return void */ - public function testSocialEmailInvalid() - { - $session = $this->getMockBuilder('Cake\Network\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(['session']) - ->getMock(); - $this->controller->Trait->request->expects($this->once()) - ->method('session') - ->will($this->returnValue($session)); - - $this->controller->Trait->socialEmail(); - } - - public function testSocialEmailPostValidateFalse() - { - $session = $this->getMockBuilder('Cake\Network\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(['session', 'is']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('session') - ->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\Network\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(['session', 'is']) - ->getMock(); - $this->controller->Trait->request->expects($this->any()) - ->method('session') - ->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 1a9d18a9b..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,8 +178,8 @@ public function testResendTokenValidationHappy() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) - ->method('data') + $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,8 +229,8 @@ public function testResendTokenValidationAlreadyActive() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) - ->method('data') + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') ->with('reference') ->will($this->returnValue('user-4')); @@ -178,8 +252,8 @@ public function testResendTokenValidationNotFound() { $this->_mockRequestPost(); $this->_mockFlash(); - $this->Trait->request->expects($this->once()) - ->method('data') + $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/Email/EmailSenderTest.php b/tests/TestCase/Email/EmailSenderTest.php deleted file mode 100644 index 5044a7c7a..000000000 --- a/tests/TestCase/Email/EmailSenderTest.php +++ /dev/null @@ -1,161 +0,0 @@ -EmailSender = $this->getMockBuilder('CakeDC\Users\Email\EmailSender') - ->setMethods(['_getEmailInstance', 'getMailer']) - ->getMock(); - - $this->UserMailer = $this->getMockBuilder('CakeDC\Users\Mailer\UsersMailer') - ->setMethods(['send']) - ->getMock(); - - $this->fullBaseBackup = Router::fullBaseUrl(); - Router::fullBaseUrl('http://users.test'); - - Email::configTransport('test', [ - 'className' => 'Debug' - ]); - } - - /** - * tearDown - * - * @return void - */ - public function tearDown() - { - Email::drop('default'); - Email::dropTransport('test'); - parent::tearDown(); - } - - /** - * test sendValidationEmail - * - * @return void - */ - public function testSendEmailValidation() - { - $table = TableRegistry::get('CakeDC/Users.Users'); - $user = $table->newEntity([ - 'first_name' => 'FirstName', - 'email' => 'test@example.com', - 'token' => '12345' - ]); - - $email = new Email([ - 'from' => 'test@example.com', - 'transport' => 'test', - 'emailFormat' => 'both', - ]); - - $this->EmailSender->expects($this->once()) - ->method('getMailer') - ->with('CakeDC/Users.Users') - ->will($this->returnValue($this->UserMailer)); - - $this->UserMailer->expects($this->once()) - ->method('send') - ->with('validation', [$user, 'Your account validation link']); - - $this->EmailSender->sendValidationEmail($user, $email); - } - - /** - * test sendResetPasswordEmail - * - * @return void - */ - public function testSendResetPasswordEmailMailer() - { - $table = TableRegistry::get('CakeDC/Users.Users'); - $user = $table->newEntity([ - 'first_name' => 'FirstName', - 'email' => 'test@example.com', - 'token' => '12345' - ]); - - $email = new Email([ - 'from' => 'test@example.com', - 'transport' => 'test', - 'template' => 'CakeDC/Users.reset_password', - 'emailFormat' => 'both', - ]); - - $this->EmailSender->expects($this->once()) - ->method('getMailer') - ->with('CakeDC/Users.Users') - ->will($this->returnValue($this->UserMailer)); - - $this->UserMailer->expects($this->once()) - ->method('send') - ->with('resetPassword', [$user, 'CakeDC/Users.reset_password']); - - $this->EmailSender->sendResetPasswordEmail($user, $email); - } - - /** - * test sendSocialValidationEmail - * - * @return void - */ - public function testSendSocialValidationEmailMailer() - { - $this->Table = TableRegistry::get('CakeDC/Users.SocialAccounts'); - $user = $this->Table->find()->contain('Users')->first(); - $email = new Email([ - 'from' => 'test@example.com', - 'transport' => 'test', - 'template' => 'CakeDC/Users.my_template', - 'emailFormat' => 'both', - ]); - - $this->EmailSender->expects($this->once()) - ->method('getMailer') - ->with('CakeDC/Users.Users') - ->will($this->returnValue($this->UserMailer)); - - $this->UserMailer->expects($this->once()) - ->method('send') - ->with('socialAccountValidation', [$user->user, $user, 'CakeDC/Users.my_template']); - - $this->EmailSender->sendSocialValidationEmail($user, $user->user, $email); - } -} diff --git a/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php b/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php new file mode 100644 index 000000000..e7951a877 --- /dev/null +++ b/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/AccountNotActiveExceptionTest.php b/tests/TestCase/Exception/AccountNotActiveExceptionTest.php new file mode 100644 index 000000000..f8666e059 --- /dev/null +++ b/tests/TestCase/Exception/AccountNotActiveExceptionTest.php @@ -0,0 +1,47 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/MissingEmailExceptionTest.php b/tests/TestCase/Exception/MissingEmailExceptionTest.php new file mode 100644 index 000000000..08a49fb99 --- /dev/null +++ b/tests/TestCase/Exception/MissingEmailExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/TokenExpiredExceptionTest.php b/tests/TestCase/Exception/TokenExpiredExceptionTest.php new file mode 100644 index 000000000..1b3fbf552 --- /dev/null +++ b/tests/TestCase/Exception/TokenExpiredExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/UserAlreadyActiveExceptionTest.php b/tests/TestCase/Exception/UserAlreadyActiveExceptionTest.php new file mode 100644 index 000000000..4fa6fb063 --- /dev/null +++ b/tests/TestCase/Exception/UserAlreadyActiveExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/UserNotActiveExceptionTest.php b/tests/TestCase/Exception/UserNotActiveExceptionTest.php new file mode 100644 index 000000000..9cd66e5ea --- /dev/null +++ b/tests/TestCase/Exception/UserNotActiveExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/UserNotFoundExceptionTest.php b/tests/TestCase/Exception/UserNotFoundExceptionTest.php new file mode 100644 index 000000000..5b52ad3b0 --- /dev/null +++ b/tests/TestCase/Exception/UserNotFoundExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Exception/WrongPasswordExceptionTest.php b/tests/TestCase/Exception/WrongPasswordExceptionTest.php new file mode 100644 index 000000000..62cd31664 --- /dev/null +++ b/tests/TestCase/Exception/WrongPasswordExceptionTest.php @@ -0,0 +1,45 @@ +assertEquals('message', $exception->getMessage()); + } +} diff --git a/tests/TestCase/Identifier/SocialIdentifierTest.php b/tests/TestCase/Identifier/SocialIdentifierTest.php new file mode 100644 index 000000000..bf93fd11d --- /dev/null +++ b/tests/TestCase/Identifier/SocialIdentifierTest.php @@ -0,0 +1,198 @@ + '']; + $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 1aae78b62..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(['to', 'subject', 'viewVars', 'template']) - ->getMock(); - - $this->UsersMailer = $this->getMockBuilder('CakeDC\Users\Mailer\UsersMailer') - ->setConstructorArgs([$this->Email]) - ->setMethods(['to', 'subject', 'viewVars', 'template']) - ->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(); } @@ -67,71 +66,34 @@ public function tearDown() */ public function testValidation() { - $table = TableRegistry::get('CakeDC/Users.Users'); - $data = [ + $table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $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('to') - ->with($user['email']) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('subject') - ->with('FirstName, Validate your Account') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('viewVars') - ->with($data) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('template') - ->with('CakeDC/Users.validation') - ->will($this->returnValue($this->Email)); - - $this->invokeMethod($this->UsersMailer, 'validation', [$user, 'Validate your Account']); - } - /** - * test sendValidationEmail including 'template' - * - * @return void - */ - public function testValidationWithTemplate() - { - $table = TableRegistry::get('CakeDC/Users.Users'); - $data = [ + $user = $table->newEntity([ 'first_name' => 'FirstName', + 'last_name' => 'Bond', 'email' => 'test@example.com', - 'token' => '12345' - ]; - $user = $table->newEntity($data); - $this->UsersMailer->expects($this->once()) - ->method('to') - ->with($user['email']) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('subject') - ->with('FirstName, Validate your Account') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('viewVars') - ->with($data) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('template') - ->with('myTemplate') - ->will($this->returnValue($this->Email)); - - $this->invokeMethod($this->UsersMailer, 'validation', [$user, 'Validate your Account', 'myTemplate']); + '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()); } /** @@ -141,26 +103,30 @@ public function testValidationWithTemplate() */ public function testSocialAccountValidation() { - $social = TableRegistry::get('CakeDC/Users.SocialAccounts') + $social = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts') ->get('00000000-0000-0000-0000-000000000001', ['contain' => 'Users']); - - $this->UsersMailer->expects($this->once()) - ->method('to') - ->with('user-1@test.com') - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('subject') - ->with('first1, Your social account validation link') - ->will($this->returnValue($this->Email)); - - - $this->Email->expects($this->once()) - ->method('viewVars') - ->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()); } /** @@ -170,35 +136,32 @@ public function testSocialAccountValidation() */ public function testResetPassword() { - $table = TableRegistry::get('CakeDC/Users.Users'); - $data = [ + $table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $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('to') - ->with($user['email']) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('subject') - ->with('FirstName, Your reset password link') - ->will($this->returnValue($this->Email)); - $this->Email->expects($this->once()) - ->method('viewVars') - ->with($data) - ->will($this->returnValue($this->Email)); - - $this->Email->expects($this->once()) - ->method('template') - ->with('myTemplate') - ->will($this->returnValue($this->Email)); - - - $this->invokeMethod($this->UsersMailer, 'resetPassword', [$user, 'myTemplate']); + $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()); } /** @@ -207,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 new file mode 100644 index 000000000..b98fa6dac --- /dev/null +++ b/tests/TestCase/Model/Behavior/AuthFinderBehaviorTest.php @@ -0,0 +1,99 @@ +table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Behavior = new AuthFinderBehavior($this->table); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->table, $this->Behavior); + parent::tearDown(); + } + + /** + * Test findActive method. + */ + public function testFindActive() + { + $actual = $this->table->find('active')->toArray(); + $this->assertCount(8, $actual); + $this->assertCount(8, Hash::extract($actual, '{n}[active=1]')); + $this->assertCount(0, Hash::extract($actual, '{n}[active=0]')); + } + + /** + * Test findAuth method. + */ + public function testFindAuthBadMethodCallException() + { + $this->expectException(\BadMethodCallException::class); + $this->expectExceptionMessage("Missing 'username' in options data"); + $this->table->find('auth'); + } + + /** + * Test findAuth method. + * + * @expected + */ + public function testFindAuth() + { + $user = $this->table + ->find('auth', ['username' => 'not-exist@email.com']) + ->toArray(); + $this->assertEmpty($user); + + $user = $this->table + ->find('auth', ['username' => 'user-2@test.com']) + ->first() + ->toArray(); + + $this->assertSame('00000000-0000-0000-0000-000000000002', Hash::get($user, 'id')); + $this->assertSame('user-2', Hash::get($user, 'username')); + } +} diff --git a/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php new file mode 100644 index 000000000..df6e5620e --- /dev/null +++ b/tests/TestCase/Model/Behavior/LinkSocialBehaviorTest.php @@ -0,0 +1,358 @@ +Table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Behavior = new LinkSocialBehavior($this->Table); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->Table, $this->Behavior); + parent::tearDown(); + } + + /** + * Test linkSocialAccount with facebook and not existing social account + * + * @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 + */ + public function testlinkSocialAccountFacebookProvider($data, $userId, $result) + { + $user = $this->Table->get($userId, [ + 'contain' => 'SocialAccounts', + ]); + $resultUser = $this->Behavior->linkSocialAccount($user, $data); + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); + $actual = $resultUser->social_accounts[2]; + + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\SocialAccount', $actual); + $actual->token_expires = $actual->token_expires->format('Y-m-d H:i:s'); + + foreach ($result as $property => $value) { + $this->assertEquals($value, $actual->$property); + } + + $result = $this->Table->SocialAccounts->exists(['id' => $actual->id]); + $this->assertTrue($result); + } + + /** + * Provider for linkSocialAccount with facebook and not existing social account + * + * @author Marcelo Rocha + * @return array + */ + public function providerFacebookLinkSocialAccount() + { + $expiresTime = new DateTime(); + $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); + + return [ + 'provider' => [ + 'data' => [ + 'id' => '9999911112255', //Reference existe mas provider google + 'username' => null, + 'full_name' => 'Full name', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'email' => 'user-1@test.com', + 'raw' => [ + 'id' => '9999911112255', + 'name' => 'Ful Name.', + 'first_name' => 'First Name', + 'last_name' => 'Last name', + 'email' => 'user-1@test.com', + 'picture' => [ + 'data' => [ + 'url' => 'data-url', + ], + ], + ], + 'credentials' => [ + 'token' => 'token', + 'secret' => null, + 'expires' => 1458423682, + ], + 'validated' => true, + 'link' => 'facebook-link-15579', + 'provider' => 'Facebook', + ], + 'user' => '00000000-0000-0000-0000-000000000001', + 'result' => [ + 'provider' => 'Facebook', + 'username' => null, + 'reference' => '9999911112255', + 'avatar' => '', + 'link' => 'facebook-link-15579', + 'description' => null, + 'token' => 'token', + 'token_secret' => null, + 'token_expires' => $tokenExpires, + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'active' => true, + + ], + ], + + ]; + } + + /** + * Test linkSocialAccount with facebook and could not save social account + * + * @param array $data Test input data + * @param string $userId User id to add social account + * @author Marcelo Rocha + * @return void + * @dataProvider providerFacebookLinkSocialAccountErrorSaving + */ + public function testlinkSocialAccountErrorSavingFacebookProvider($data, $userId) + { + $user = $this->Table->get($userId); + $resultUser = $this->Behavior->linkSocialAccount($user, $data); + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); + $actual = $resultUser->social_accounts[0]; + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\SocialAccount', $actual); + + $actual = $user->getErrors(); + + $expected = [ + 'social_accounts' => [ + [ + 'token' => [ + '_empty' => 'This field cannot be left empty', + '_required' => 'This field is required', + + ], + ], + ], + ]; + $this->assertEquals($expected, $actual); + } + + /** + * Provider for linkSocialAccount with facebook and could not save social account + * + * @author Marcelo Rocha + * @return array + */ + public function providerFacebookLinkSocialAccountErrorSaving() + { + $expiresTime = new DateTime(); + $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); + + return [ + 'provider' => [ + 'data' => [ + 'id' => '9999911112255', //Reference existe mas provider google + 'username' => null, + 'full_name' => 'Full name', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'email' => 'user-1@test.com', + 'raw' => [ + 'id' => '9999911112255', + 'name' => 'Ful Name.', + 'first_name' => 'First Name', + 'last_name' => 'Last name', + 'email' => 'user-1@test.com', + 'picture' => [ + 'data' => [ + 'url' => 'data-url', + ], + ], + ], + 'credentials' => [ + 'token' => '', + 'secret' => null, + 'expires' => 1458423682, + ], + 'validated' => true, + 'link' => 'facebook-link-15579', + 'provider' => 'Facebook', + ], + 'user' => '00000000-0000-0000-0000-000000000001', + 'result' => [ + 'provider' => 'Facebook', + 'username' => null, + 'reference' => '9999911112255', + 'avatar' => '', + 'link' => 'facebook-link-15579', + 'description' => null, + 'token' => 'token', + 'token_secret' => null, + 'token_expires' => $tokenExpires, + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'active' => true, + + ], + ], + + ]; + } + + /** + * Test linkSocialAccount with facebook when account already exists + * + * @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 + */ + public function testlinkSocialAccountFacebookProviderAccountExists($data, $userId, $result) + { + $user = $this->Table->get($userId); + $resultUser = $this->Behavior->linkSocialAccount($user, $data); + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); + $this->assertFalse($resultUser->has('social_accounts')); + $expected = [ + 'social_accounts' => [ + '_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 + $this->Table->SocialAccounts->find()->where([ + 'reference' => $data['id'], + 'provider' => $data['provider'], + ])->firstOrFail(); + + $userBase = $this->Table->get('00000000-0000-0000-0000-000000000002', [ + 'contain' => ['SocialAccounts'], + ]); + $resultUser = $this->Behavior->linkSocialAccount($userBase, $data); + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\User', $resultUser); + $this->assertEquals([], $userBase->getErrors()); + + $actual = $resultUser->social_accounts[0]; + + $this->assertInstanceOf('\CakeDC\Users\Model\Entity\SocialAccount', $actual); + + $actual->token_expires = $actual->token_expires->format('Y-m-d H:i:s'); + foreach ($result as $property => $value) { + $this->assertEquals($value, $actual->$property); + } + } + + /** + * Provider for linkSocialAccount with facebook when account already exists + * + * @author Marcelo Rocha + * @return array + */ + public function providerFacebookLinkSocialAccountAccountExists() + { + $expiresTime = new DateTime(); + $tokenExpires = $expiresTime->setTimestamp(1458423682)->format('Y-m-d H:i:s'); + + return [ + 'provider' => [ + 'data' => [ + 'id' => 'reference-2-1', + 'username' => null, + 'full_name' => 'Full name', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'email' => 'email@example.com', + 'raw' => [ + 'id' => 'reference-2-1', + 'name' => 'Ful Name.', + 'first_name' => 'First Name', + 'last_name' => 'Last name', + 'email' => 'email@example.com', + 'picture' => [ + 'data' => [ + 'url' => 'data-url', + ], + ], + ], + 'credentials' => [ + 'token' => 'token', + 'secret' => null, + 'expires' => 1458423682, + ], + 'validated' => true, + 'link' => 'facebook-link-15579', + 'provider' => 'Facebook', + ], + 'user' => '00000000-0000-0000-0000-000000000001', + 'result' => [ + 'id' => '00000000-0000-0000-0000-000000000003', + 'provider' => 'Facebook', + 'username' => null, + 'reference' => 'reference-2-1', + 'avatar' => '', + 'link' => 'facebook-link-15579', + 'description' => null, + 'token' => 'token', + 'token_secret' => null, + 'token_expires' => $tokenExpires, + 'user_id' => '00000000-0000-0000-0000-000000000002', + 'active' => true, + ], + ], + + ]; + } +} diff --git a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php index df14e10ce..23b12f02f 100644 --- a/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php @@ -1,26 +1,35 @@ table = TableRegistry::get('CakeDC/Users.Users'); + $this->table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\PasswordBehavior') - ->setMethods(['sendResetPasswordEmail']) + ->setMethods(['_sendResetPasswordEmail']) ->setConstructorArgs([$this->table]) ->getMock(); - $this->Behavior->Email = $this->getMockBuilder('CakeDC\Users\Email\EmailSender') - ->setMethods(['sendResetPasswordEmail']) - ->getMock(); + TransportFactory::drop('test'); + TransportFactory::setConfig('test', ['className' => 'Debug']); + //$this->configEmail = Email::getConfig('default'); + Mailer::drop('default'); + Mailer::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', + ]); } /** @@ -56,23 +70,23 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->table, $this->Behavior); + Email::drop('default'); + TransportFactory::drop('test'); parent::tearDown(); } - /** * Test resetToken - * */ public function testResetToken() { $user = $this->table->findByUsername('user-1')->first(); $token = $user->token; - $this->Behavior->Email->expects($this->never()) - ->method('sendResetPasswordEmail') + $this->Behavior->expects($this->never()) + ->method('_sendResetPasswordEmail') ->with($user); $result = $this->Behavior->resetToken('user-1', [ 'expiration' => 3600, @@ -85,19 +99,19 @@ public function testResetToken() /** * Test resetToken - * */ public function testResetTokenSendEmail() { $user = $this->table->findByUsername('user-1')->first(); $token = $user->token; $tokenExpires = $user->token_expires; - $this->Behavior->Email->expects($this->once()) - ->method('sendResetPasswordEmail'); + $this->Behavior->expects($this->once()) + ->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); @@ -107,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() { - $activeUser = TableRegistry::get('CakeDC/Users.Users')->findByUsername('user-4')->first(); + $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, @@ -160,15 +170,14 @@ public function testResetTokenUserAlreadyActive() /** * Test resetToken - * - * @expectedException CakeDC\Users\Exception\UserNotActiveException */ public function testResetTokenUserNotActive() { - $user = TableRegistry::get('CakeDC/Users.Users')->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, ]); } @@ -177,10 +186,10 @@ public function testResetTokenUserNotActive() */ public function testResetTokenUserActive() { - $user = TableRegistry::get('CakeDC/Users.Users')->findByUsername('user-2')->first(); + $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); } @@ -190,11 +199,44 @@ public function testResetTokenUserActive() */ public function testChangePassword() { - $user = TableRegistry::get('CakeDC/Users.Users')->findByUsername('user-6')->first(); + $user = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->findByUsername('user-6')->first(); $user->current_password = '12345'; $user->password = 'new'; $user->password_confirmation = 'new'; $result = $this->Behavior->changePassword($user); + $this->assertInstanceOf(User::class, $result); + $this->assertEmpty($result->getErrors()); + } + + /** + * test Email Override + */ + public function testEmailOverride() + { + $overrideMailer = $this->getMockBuilder(OverrideMailer::class) + ->setMethods(['send']) + ->getMock(); + Configure::write('Users.Email.mailerClass', OverrideMailer::class); + $this->Behavior = $this->getMockBuilder(PasswordBehavior::class) + ->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($responseEmail); + $this->Behavior->expects($this->once()) + ->method('getMailer') + ->with(OverrideMailer::class) + ->willReturn($overrideMailer); + $result = $this->Behavior->resetToken('user-1', [ + 'expiration' => 3600, + 'checkActive' => 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 e6d3657b3..317cc6a80 100644 --- a/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php @@ -1,53 +1,66 @@ get('CakeDC/Users.Users'); $table->addBehavior('CakeDC/Users/Register.Register'); $this->Table = $table; $this->Behavior = $table->behaviors()->Register; - Email::configTransport('test', [ - 'className' => 'Debug' + TransportFactory::setConfig('test', ['className' => 'Debug']); + Email::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', ]); - $this->Email = new Email(['from' => 'test@example.com', 'transport' => 'test']); } /** @@ -55,10 +68,11 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->Table, $this->Behavior); - Email::dropTransport('test'); + Email::drop('default'); + TransportFactory::drop('test'); parent::tearDown(); } @@ -76,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, 'email_class' => $this->Email]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); $this->assertTrue($result->active); } @@ -90,7 +104,7 @@ public function testValidateRegisterNoValidateEmail() public function testValidateRegisterEmptyUser() { $user = []; - $result = $this->Table->register($this->Table->newEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'email_class' => $this->Email]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); $this->assertFalse($result); } @@ -101,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', @@ -108,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, 'email_class' => $this->Email]); + $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); @@ -123,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') @@ -137,7 +165,7 @@ public function testValidateRegisterValidatorOption() 'password_confirm' => 'password', 'first_name' => 'test', 'last_name' => 'user', - 'tos' => 1 + 'tos' => 1, ]; $this->Behavior->expects($this->never()) @@ -151,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()) @@ -159,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, 'email_class' => $this->Email]); + $result = $this->Behavior->register($this->Table->newEmptyEntity(), $user, ['validator' => 'custom', 'validate_email' => 1]); $this->assertNotEmpty($result->tos_date); } /** * Test register method - * */ public function testValidateRegisterTosRequired() { @@ -177,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, 'email_class' => $this->Email]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); $this->assertFalse($result); } @@ -188,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', @@ -196,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, 'email_class' => $this->Email]); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); $this->assertNotEmpty($result); } @@ -228,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'); } @@ -239,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'); } @@ -254,14 +288,61 @@ 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(); + $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')); + } - $resultValidationToken = $user; - $resultValidationToken->token_expires = null; - $resultValidationToken->token = null; + /** + * Test register default role + * + * @return void + */ + public function testRegisterUsingDefaultRole() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + Configure::write('Users.Registration.defaultRole', false); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, [ + 'token_expiration' => 3600, + 'validate_email' => 0, + ]); + $this->assertSame('user', $result['role']); + } - $this->Behavior->activateUser($user); + /** + * Test register not default role + * + * @return void + */ + public function testRegisterUsingCustomRole() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + Configure::write('Users.Registration.defaultRole', 'emperor'); + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, [ + 'token_expiration' => 3600, + 'validate_email' => 0, + ]); + $this->assertSame('emperor', $result['role']); } } diff --git a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php index 8bfc5b5a5..5e35cf818 100644 --- a/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php @@ -1,23 +1,24 @@ Table = TableRegistry::get('CakeDC/Users.SocialAccounts'); + $this->Table = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts'); $this->Behavior = $this->Table->behaviors()->SocialAccount; } @@ -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 33f700836..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(); @@ -87,8 +92,40 @@ public function testSocialLoginFacebookProvider($data, $options, $dataUser) } /** - * Provider for socialLogin with facebook and not existing user + * Test socialLogin with facebook and not existing user * + * @dataProvider providerFacebookSocialLogin + */ + public function testSocialLoginFacebookProviderUsingEmail($data, $options, $dataUser) + { + $user = $this->Table->newEntity($dataUser, ['associated' => ['SocialAccounts']]); + $user->password = '$2y$10$0QzszaIEpW1pYpoKJVf4DeqEAHtg9whiLTX/l3TcHAoOLF1bC9U.6'; + + $this->Behavior->expects($this->once()) + ->method('generateUniqueUsername') + ->with('email') + ->will($this->returnValue('username')); + + $this->Behavior->expects($this->once()) + ->method('randomString') + ->will($this->returnValue('password')); + + $this->Behavior->expects($this->once()) + ->method('_updateActive') + ->will($this->returnValue($user)); + + $this->Table->expects($this->once()) + ->method('save') + ->with($user) + ->will($this->returnValue($user)); + + $this->Behavior->initialize(['username' => 'email']); + $result = $this->Behavior->socialLogin($data, $options); + $this->assertEquals($result, $user); + } + + /** + * Provider for socialLogin with facebook and not existing user */ public function providerFacebookSocialLogin() { @@ -109,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', @@ -148,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, - ] - ] + ], + ], ]; } @@ -164,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'); @@ -175,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() { @@ -190,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, ], - ] + ], ]; } @@ -205,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'); @@ -223,7 +294,6 @@ public function testSocialLoginExistingNotActiveReference($data, $options) /** * Provider for socialLogin with existing and active user and not active social account - * */ public function providerSocialLoginExistingAndNotActiveAccount() { @@ -231,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, ], - ] + ], ]; } @@ -246,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'); @@ -264,7 +334,6 @@ public function testSocialLoginExistingReferenceNotActiveUser($data, $options) /** * Provider for socialLogin with existing and active account but not active user - * */ public function providerSocialLoginExistingAccountNotActiveUser() { @@ -272,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, ], - ] + ], ]; } @@ -288,17 +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() { @@ -312,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, ], - ] + ], ]; } @@ -342,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 b5c6d5697..4f0981f71 100644 --- a/tests/TestCase/Model/Entity/UserTest.php +++ b/tests/TestCase/Model/Entity/UserTest.php @@ -1,11 +1,23 @@ now = Time::now(); - Time::setTestNow($this->now); + $this->now = FrozenTime::now(); + FrozenTime::setTestNow($this->now); $this->User = new User(); } @@ -30,10 +42,10 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->User); - Time::setTestNow(); + FrozenTime::setTestNow(); parent::tearDown(); } @@ -81,14 +93,14 @@ public function testTokenExpired() */ public function testTokenExpiredLocale() { - I18n::locale('es_AR'); + I18n::setLocale('es_AR'); $this->User->token_expires = '+1 day'; $isExpired = $this->User->tokenExpired(); $this->assertFalse($isExpired); $this->User->token_expires = '-1 day'; $isExpired = $this->User->tokenExpired(); $this->assertTrue($isExpired); - I18n::locale('en_US'); + I18n::setLocale('en_US'); } /** @@ -100,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)); } /** @@ -118,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')); } @@ -133,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 e57156cec..04999b7f5 100644 --- a/tests/TestCase/Model/Table/SocialAccountsTableTest.php +++ b/tests/TestCase/Model/Table/SocialAccountsTableTest.php @@ -1,22 +1,19 @@ SocialAccounts = TableRegistry::get('CakeDC/Users.SocialAccounts'); + $this->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); @@ -69,6 +65,6 @@ public function testValidationHappy() 'data' => 'test-data', ]; $entity = $this->SocialAccounts->newEntity($data); - $this->assertEmpty($entity->errors()); + $this->assertEmpty($entity->getErrors()); } } diff --git a/tests/TestCase/Model/Table/UsersTableTest.php b/tests/TestCase/Model/Table/UsersTableTest.php index b7f4459f3..58b1e7fd6 100644 --- a/tests/TestCase/Model/Table/UsersTableTest.php +++ b/tests/TestCase/Model/Table/UsersTableTest.php @@ -1,27 +1,25 @@ Users = TableRegistry::get('CakeDC/Users.Users'); + $this->Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $this->fullBaseBackup = Router::fullBaseUrl(); Router::fullBaseUrl('http://users.test'); - Email::configTransport('test', [ - 'className' => 'Debug' - ]); - $this->configEmail = Email::config('default'); - Email::config('default', [ + TransportFactory::drop('test'); + TransportFactory::setConfig('test', ['className' => 'Debug']); + Email::setConfig('default', [ 'transport' => 'test', - 'from' => 'cakedc@example.com' + 'from' => 'cakedc@example.com', ]); - $this->Email = new Email(['from' => 'test@example.com', 'transport' => 'test']); - Plugin::routes('CakeDC/Users'); } /** @@ -66,13 +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'); - Email::config('default', $this->configEmail); parent::tearDown(); } @@ -91,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); } @@ -105,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); } @@ -116,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', @@ -123,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); } @@ -143,16 +142,24 @@ 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->errors()); + $this->assertEquals(['tos' => ['_required' => 'This field is required']], $userEntity->getErrors()); } /** * Test register method - testValidateRegisterValidateEmail */ + * testValidateRegisterValidateEmail + */ 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', @@ -161,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); } @@ -191,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); @@ -206,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', @@ -222,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); @@ -236,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', @@ -259,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); @@ -267,7 +272,6 @@ public function testSocialLoginCreateNewAccountWithNoCredentials() /** * Test socialLogin - * */ public function testSocialLoginCreateNewAccount() { @@ -284,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); @@ -312,48 +316,4 @@ public function testSocialLoginCreateNewAccount() $this->assertEquals('First Name', $result->first_name); $this->assertEquals('Last Name', $result->last_name); } - - /** - * Test findActive method. - * - */ - public function testFindActive() - { - $actual = $this->Users->find('active')->toArray(); - $this->assertCount(8, $actual); - $this->assertCount(8, Hash::extract($actual, '{n}[active=1]')); - $this->assertCount(0, Hash::extract($actual, '{n}[active=0]')); - } - - /** - * Test findAuth method. - * - * @expectedException \BadMethodCallException - * @expectedExceptionMessage Missing 'username' in options data - */ - public function testFindAuthBadMethodCallException() - { - $user = $this->Users->find('auth'); - } - - /** - * Test findAuth method. - * - * @expected - */ - public function testFindAuth() - { - $user = $this->Users - ->find('auth', ['username' => 'not-exist@email.com']) - ->toArray(); - $this->assertEmpty($user); - - $user = $this->Users - ->find('auth', ['username' => 'user-2@test.com']) - ->first() - ->toArray(); - - $this->assertSame('00000000-0000-0000-0000-000000000002', Hash::get($user, 'id')); - $this->assertSame('user-2', Hash::get($user, 'username')); - } } 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 34d9c7e35..2ce3ea7c0 100644 --- a/tests/TestCase/Shell/UsersShellTest.php +++ b/tests/TestCase/Shell/UsersShellTest.php @@ -1,21 +1,24 @@ out = new ConsoleOutput(); $this->io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock(); - $this->Users = TableRegistry::get('CakeDC/Users.Users'); + $this->Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); $this->Shell = $this->getMockBuilder('CakeDC\Users\Shell\UsersShell') ->setMethods(['in', 'out', '_stop', 'clear', '_usernameSeed', '_generateRandomPassword', @@ -62,7 +65,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { parent::tearDown(); unset($this->Shell); @@ -70,7 +73,7 @@ public function tearDown() /** * Add user test - * Adding user with username, email and password + * Adding user with username, email, password and role * * @return void */ @@ -79,9 +82,10 @@ public function testAddUser() $user = [ 'username' => 'yeliparra', 'password' => '123', - 'email' => 'yeli.parra@gmail.com', + 'email' => 'yeli.parra@example.com', 'active' => 1, ]; + $role = 'tester'; $this->Shell->expects($this->never()) ->method('_generateRandomUsername'); @@ -95,6 +99,7 @@ public function testAddUser() ->will($this->returnValue($user['username'])); $entityUser = $this->Users->newEntity($user); + $entityUser->role = $role; $this->Shell->Users->expects($this->once()) ->method('newEntity') @@ -109,7 +114,7 @@ public function testAddUser() ->with($entityUser) ->will($this->returnValue($userSaved)); - $this->Shell->runCommand(['addUser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email']]); + $this->Shell->runCommand(['addUser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); } /** @@ -141,6 +146,7 @@ public function testAddUserWithNoParams() ->will($this->returnValue($user['username'])); $entityUser = $this->Users->newEntity($user); + $entityUser->role = 'user'; $this->Shell->Users->expects($this->once()) ->method('newEntity') @@ -161,11 +167,58 @@ public function testAddUserWithNoParams() } /** - * Add superadmin user + * Add user test + * Adding user with username, email, password and role * * @return void */ public function testAddSuperuser() + { + $user = [ + 'username' => 'yeliparra', + 'password' => '123', + 'email' => 'yeli.parra@example.com', + 'active' => 1, + ]; + $role = 'tester'; + + $this->Shell->expects($this->never()) + ->method('_generateRandomUsername'); + + $this->Shell->expects($this->never()) + ->method('_generateRandomPassword'); + + $this->Shell->Users->expects($this->once()) + ->method('generateUniqueUsername') + ->with($user['username']) + ->will($this->returnValue($user['username'])); + + $entityUser = $this->Users->newEntity($user); + $entityUser->role = $role; + + $this->Shell->Users->expects($this->once()) + ->method('newEntity') + ->with($user) + ->will($this->returnValue($entityUser)); + + $userSaved = $entityUser; + $userSaved->id = 'my-id'; + $userSaved->is_superuser = true; + + $this->Shell->Users->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($userSaved)); + + $this->Shell->runCommand(['addSuperuser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); + } + + /** + * Add superadmin user + * + * @return void + */ + public function testAddSuperuserWithNoParams() { $this->Shell->Users->expects($this->once()) ->method('generateUniqueUsername') @@ -222,15 +275,11 @@ public function testResetAllPasswords() /** * Reset all passwords - * - * @return void */ public function testResetAllPasswordsNoPassingParams() { - $this->Shell->expects($this->once()) - ->method('error') - ->with('Please enter a password.'); - + $this->expectException(StopException::class); + $this->expectExceptionMessage('Please enter a password.'); $this->Shell->runCommand(['resetAllPasswords']); } @@ -241,7 +290,7 @@ public function testResetAllPasswordsNoPassingParams() */ public function testResetPassword() { - $user = $this->Users->newEntity(); + $user = $this->Users->newEmptyEntity(); $user->username = 'user-1'; $user->password = 'password'; @@ -305,4 +354,67 @@ public function testDeleteUser() $this->Shell->runCommand(['deleteUser', 'user-5']); $this->assertEmpty($this->Users->findById('00000000-0000-0000-0000-000000000005')->first()); } + + /** + * test + * + * @return void + */ + public function testAddUserCustomRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + '--role=custom', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('custom', $user['role']); + } + + /** + * test + * + * @return void + */ + public function testAddUserDefaultRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + Configure::write('Users.Registration.defaultRole', false); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('user', $user['role']); + } + + /** + * test + * + * @return void + */ + public function testAddUserCustomDefaultRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + Configure::write('Users.Registration.defaultRole', 'emperor'); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('emperor', $user['role']); + } } diff --git a/tests/TestCase/Traits/RandomStringTraitTest.php b/tests/TestCase/Traits/RandomStringTraitTest.php index 58324c67b..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 165423aec..c69f5393c 100644 --- a/tests/TestCase/View/Helper/AuthLinkHelperTest.php +++ b/tests/TestCase/View/Helper/AuthLinkHelperTest.php @@ -1,18 +1,29 @@ AuthLink = new AuthLinkHelper($view); + $view = new View(new ServerRequest()); + $this->AuthLink = $this->getMockBuilder(AuthLinkHelper::class) + ->setMethods(['isAuthorized']) + ->setConstructorArgs([$view]) + ->getMock(); } /** @@ -37,7 +51,7 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { unset($this->AuthLink); @@ -49,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); } /** @@ -60,45 +84,151 @@ public function testLinkFalse() * * @return void */ - public function testLinkAuthorized() + public function testLinkAuthorizedHappy(): void { - $view = new View(); - $eventManagerMock = $this->getMockBuilder('Cake\Event\EventManager') - ->setMethods(['dispatch']) - ->getMock(); - $view->eventManager($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']); + $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); + } + + /** + * Test link + * + * @return void + */ + public function testLinkAuthorizedAllowedTrue(): void + { + $link = $this->AuthLink->link('title', '/', ['allowed' => true, 'before' => 'before_', 'after' => '_after', 'class' => 'link-class']); $this->assertSame('before_title_after', $link); } + /** + * Test link + * + * @return void + */ + public function testLinkAuthorizedAllowedFalse(): void + { + $link = $this->AuthLink->link('title', '/', ['allowed' => false, 'before' => 'before_', 'after' => '_after', 'class' => 'link-class']); + $this->assertEmpty($link); + } + + /** + * Test getRequest method + * + * @retunr void + */ + public function testGetRequest(): void + { + $actual = $this->AuthLink->getRequest(); + $this->assertInstanceOf(ServerRequest::class, $actual); + } /** - * Test isAuthorized + * 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(); - $view->eventManager($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 1c5292dcb..1ce9a8f58 100644 --- a/tests/TestCase/View/Helper/UserHelperTest.php +++ b/tests/TestCase/View/Helper/UserHelperTest.php @@ -1,43 +1,69 @@ oauthConfig === null) { + $this->oauthConfig = (array)Configure::read('OAuth'); + $this->socialLogin = Configure::read('Users.Social.login'); + } + parent::setUp(); - Plugin::routes('CakeDC/Users'); $this->View = $this->getMockBuilder('Cake\View\View') ->setMethods(['append']) ->getMock(); @@ -51,7 +77,7 @@ public function setUp() ->will($this->returnValue(true)); $this->User = new UserHelper($this->View); $this->User->AuthLink = $this->AuthLink; - $this->request = new Request(); + $this->request = new ServerRequest(); } /** @@ -59,8 +85,10 @@ public function setUp() * * @return void */ - public function tearDown() + public function tearDown(): void { + Configure::write('OAuth', $this->oauthConfig); + Configure::write('Users.Social.login', $this->socialLogin); unset($this->User); parent::tearDown(); @@ -73,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); @@ -85,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); @@ -97,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); @@ -109,27 +158,24 @@ public function testLogoutWithOptions() */ public function testWelcome() { - $session = $this->getMockBuilder('Cake\Network\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(['session']) - ->getMock(); - $this->User->request->expects($this->any()) - ->method('session') - ->will($this->returnValue($session)); + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); - $expected = 'Welcome, david'; + $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); } @@ -139,23 +185,38 @@ public function testWelcome() * * @return void */ - public function testWelcomeNotLoggedInUser() + public function testWelcomeWillDisplayUsernameInstead() { - $session = $this->getMockBuilder('Cake\Network\Session') - ->setMethods(['read']) - ->getMock(); - $session->expects($this->at(0)) - ->method('read') - ->with('Auth.User.id') - ->will($this->returnValue(null)); + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); - $this->User->request = $this->getMockBuilder('Cake\Network\Request') - ->setMethods(['session']) - ->getMock(); - $this->User->request->expects($this->any()) - ->method('session') - ->will($this->returnValue($session)); + $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); + } + /** + * Test link + * + * @return void + */ + public function testWelcomeNotLoggedInUser() + { + $request = new ServerRequest(); + $this->User->getView()->setRequest($request); $result = $this->User->welcome(); $this->assertEmpty($result); } @@ -168,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); } /** @@ -191,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(); @@ -205,10 +270,10 @@ public function testAddReCaptchaScript() public function testSocialLoginLink() { $result = $this->User->socialLogin('facebook'); - $this->assertEquals('Sign in with Facebook', $result); + $this->assertEquals('Sign in with Facebook', $result); $result = $this->User->socialLogin('twitter', ['label' => 'Register with']); - $this->assertEquals('Register with Twitter', $result); + $this->assertEquals('Register with Twitter', $result); } /** @@ -218,9 +283,139 @@ public function testSocialLoginLink() */ public function testSocialLoginTranslation() { - I18n::locale('es_ES'); + I18n::setLocale('es_ES'); $result = $this->User->socialLogin('facebook'); $this->assertEquals('Iniciar sesión con Facebook', $result); - I18n::locale('en_US'); + I18n::setLocale('en_US'); + } + + /** + * Test social connect link list + * + * @return void + */ + public function testSocialConnectLinkList() + { + Configure::write('Users.Social.login', true); + + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + + Configure::write('OAuth.providers.google.options.clientId', 'testclientidgoogtestclientidgoog'); + Configure::write('OAuth.providers.google.options.clientSecret', 'testclientsecretgoogtestclientsecretgoog'); + + $actual = $this->User->socialConnectLinkList(); + $expected = ' Connect with Facebook'; + $expected .= ' Connect with Google'; + $this->assertEquals($expected, $actual); + } + + /** + * Test social connect link list, user is connected with facebook + * + * @return void + */ + public function testSocialConnectLinkListIsConnectedWithFacebook() + { + Configure::write('Users.Social.login', true); + + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + + Configure::write('OAuth.providers.google.options.clientId', 'testclientidgoogtestclientidgoog'); + Configure::write('OAuth.providers.google.options.clientSecret', 'testclientsecretgoogtestclientsecretgoog'); + + $socialAccounts = [ + new SocialAccount([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'provider' => 'Facebook', + 'username' => 'user-1-fb', + 'reference' => 'reference-1-1234', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', + 'token' => 'token-1234', + 'token_secret' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-05-22 21:52:44', + 'active' => false, + 'data' => '', + 'created' => '2015-05-22 21:52:44', + 'modified' => '2015-05-22 21:52:44', + ]), + ]; + $actual = $this->User->socialConnectLinkList($socialAccounts); + $expected = ' Connected with Facebook'; + $expected .= ' Connect with Google'; + $this->assertEquals($expected, $actual); + } + + /** + * Test social connect link list, social is not enabled + * + * @return void + */ + public function testSocialConnectLinkListSocialIsNotEnabled() + { + Configure::write('Users.Social.login', false); + + Configure::write('OAuth.providers.facebook.options.clientId', 'testclientidtestclientid'); + Configure::write('OAuth.providers.facebook.options.clientSecret', 'testclientsecrettestclientsecret'); + + Configure::write('OAuth.providers.google.options.clientId', 'testclientidgoogtestclientidgoog'); + Configure::write('OAuth.providers.google.options.clientSecret', 'testclientsecretgoogtestclientsecretgoog'); + + $socialAccounts = [ + new SocialAccount([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'provider' => 'Facebook', + 'username' => 'user-1-fb', + 'reference' => 'reference-1-1234', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', + 'token' => 'token-1234', + 'token_secret' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-05-22 21:52:44', + 'active' => false, + 'data' => '', + 'created' => '2015-05-22 21:52:44', + 'modified' => '2015-05-22 21:52:44', + ]), + ]; + $actual = $this->User->socialConnectLinkList($socialAccounts); + $expected = ''; + $this->assertEquals($expected, $actual); + } + + /** + * Test social connect link list, social is enabled but any provider was configured + * + * @return void + */ + public function testSocialConnectLinkListSocialEnabledButNotConfiguredProvider() + { + Configure::write('Users.Social.login', true); + + $socialAccounts = [ + new SocialAccount([ + 'id' => '00000000-0000-0000-0000-000000000001', + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'provider' => 'Facebook', + 'username' => 'user-1-fb', + 'reference' => 'reference-1-1234', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', + 'token' => 'token-1234', + 'token_secret' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-05-22 21:52:44', + 'active' => false, + 'data' => '', + 'created' => '2015-05-22 21:52:44', + 'modified' => '2015-05-22 21:52:44', + ]), + ]; + $actual = $this->User->socialConnectLinkList($socialAccounts); + $expected = ''; + $this->assertEquals($expected, $actual); } } 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 efa649358..f5424bd41 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,16 @@ 'Users\Test\App']); - Cake\Core\Configure::write('debug', true); -Cake\Core\Configure::write('App.encoding', 'UTF-8'); ini_set('intl.default_locale', 'en_US'); @@ -72,34 +86,58 @@ ], ]; -Cake\Cache\Cache::config($cache); +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::config('test', [ +Cake\Datasource\ConnectionManager::setConfig('test', [ 'url' => getenv('db_dsn'), - 'timezone' => 'UTC' + 'timezone' => 'UTC', +]); + +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 1f8d915d6..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/test_app/TestApp/Controller/AppController.php b/tests/test_app/TestApp/Controller/AppController.php new file mode 100644 index 000000000..db11ec36d --- /dev/null +++ b/tests/test_app/TestApp/Controller/AppController.php @@ -0,0 +1,33 @@ +loadComponent('Flash'); + $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/test_app/TestApp/Mailer/OverrideMailer.php b/tests/test_app/TestApp/Mailer/OverrideMailer.php new file mode 100644 index 000000000..3dbe44fe8 --- /dev/null +++ b/tests/test_app/TestApp/Mailer/OverrideMailer.php @@ -0,0 +1,36 @@ +setSubject('This is the new subject'); + $this->setTemplate('custom-template-in-app-namespace'); + } +} diff --git a/tests/test_app/config/bootstrap.php b/tests/test_app/config/bootstrap.php new file mode 100644 index 000000000..5ff14cf06 --- /dev/null +++ b/tests/test_app/config/bootstrap.php @@ -0,0 +1,2 @@ +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); + } +}