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 new file mode 100644 index 000000000..f696b2eed --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/tmp +/vendor +/.idea +composer.lock +.php_cs* +/coverage +.phpunit.result.cache diff --git a/.semver b/.semver new file mode 100644 index 000000000..726c4d95e --- /dev/null +++ b/.semver @@ -0,0 +1,5 @@ +--- +:major: 11 +:minor: 0 +:patch: 0 +:special: '' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..78c464692 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,235 @@ +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+ + * Fixed several bugs, including regression issue with Facebook login & improvements + +* 3.2.2 + * Fix bug with socialLogin links not being displayed for unauthenticated users + +* 3.2.1 + * New Translations (see https://github.com/CakeDC/users/blob/master/Docs/Documentation/Translations.md) + * Stateless API Authenticate support + * Prefix and extension support in permission rules (RBAC) + * 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 + * Docs improvements + +* 3.1.4 + * SocialAuthenticate refactored to drop Opauth in favor of Muffin/OAuth2 and league/oauth2 + +* 3.1.3 + * UserHelper improvements + +* 3.1.2 + * Fixes in RBAC permission matchers + +* 3.1.0 Migration to CakePHP 3.0 + * Unit test coverage improvements + * Refactor UsersTable to Behavior + * Add google authentication + * Add reCaptcha + * Link social accounts in profile + +Releases for CakePHP 2 +------------- + +* 2.1.3 + * Fixed unit tests for compatibility with CakePHP 2.7 + +* 2.1.2 + * Minor improvements + * New translations (german and portuguese) + +* 2.1.1 + * Forgot password + +* 2.1.0 + * Bugfixes and improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..653b64f7c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +Contributing +============ + +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/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 new file mode 100644 index 000000000..a7118a99b --- /dev/null +++ b/Docs/Documentation/Configuration.md @@ -0,0 +1,254 @@ +Configuration +============= + +Overriding the default configuration +------------------------- + +For easier configuration, you can specify an array of config files to override the default plugin keys this way: + +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']); +``` + +Configuration for social login +--------------------- + +Create the facebook, twitter, etc applications you want to use and setup the configuration like this: +In this example, we are using 2 providers: facebook and twitter. Note you'll need to add the providers to +your composer.json file. + +``` +$ composer require league/oauth2-facebook:@stable +$ composer require league/oauth1-client:@stable +``` + +NOTE: twitter uses league/oauth1-client package + +And update your config/users.php file: + +```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 +--------------------- +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 +``` + + +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 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: + +``` +'Auth.Authorization.enable' => false, +``` + +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' => 'CakeDC/Users.Users', + // Controller used to manage users plugin features & actions + 'controller' => 'CakeDC/Users.Users', + 'Email' => [ + // determines if the user should include email + 'required' => true, + // determines if registration workflow includes email validation + 'validate' => true, + ], + 'Registration' => [ + // determines if the register is enabled + 'active' => true, + // determines if the reCaptcha is enabled for registration + 'reCaptcha' => true, + //ensure user is active (confirmed email) to reset his password + '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 + 'required' => true, + ], + 'Social' => [ + // enable social login + 'login' => false, + ], + // Avatar placeholder + 'Avatar' => ['placeholder' => 'CakeDC/Users.avatar_placeholder.png'], + 'RememberMe' => [ + // configure Remember Me component + 'active' => true, + ], + 'Superuser' => ['allowedToChangePasswords' => false], // able to reset any users password + ], + //Default authentication/authorization setup + 'Auth' => [ + 'Authentication' => [ + 'serviceLoader' => \CakeDC\Users\Loader\AuthenticationServiceLoader::class + ], + 'AuthenticationComponent' => [...], + 'Authenticators' => [...], + 'Identifiers' => [...], + "Authorization" => [ + 'enable' => true, + 'serviceLoader' => \CakeDC\Users\Loader\AuthorizationServiceLoader::class + ], + 'AuthorizationMiddleware' => [...], + 'AuthorizationComponent' => [...], + 'SocialLoginFailure' => [...], + 'FormLoginFailure' => [...], + 'RbacPolicy' => [ + 'adapter' => [ + 'role_field' => 'group_name', + ... + ] + ] + ], + 'SocialAuthMiddleware' => [...], + 'OAuth' => [...] +]; + +``` + +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. + +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 (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 +'Auth.Identifiers.Password.fields.username' => 'email', +'Auth.Authenticators.Form.fields.username' => 'email', +'Auth.Authenticators.Cookie.fields.username' => 'email', +``` + +* Override the login.php template to change the Form->control to "email". +Add (or copy from the [/templates/Users/login.php](../../templates/Users/login.php)) the file login.php to path /templates/plugin/CakeDC/Users/Users/login.php +and ensure it has the following content + +```php + // ... inside the Form + Form->control('email', ['required' => true]) ?> + Form->control('password', ['required' => true]) ?> + // ... rest of your login.php code +``` + + + + +Email Templates +--------------- + +To modify the templates as needed copy them to your application + +``` +cp -r vendor/cakedc/users/templates/email/ templates/plugin/CakeDC/Users/email/ +``` + +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 +templates/plugin/CakeDC/Users/[Controller]/[view].php + +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 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 new file mode 100644 index 000000000..61fcee79d --- /dev/null +++ b/Docs/Documentation/Events.md @@ -0,0 +1,568 @@ +Events +====== + +The events in this plugin follow these conventions `.`: + +* `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. + + +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', + ]; + } + + /** + * @param \Cake\Event\Event $event + */ + public function afterLogin(\Cake\Event\Event $event) + { + $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 + 'afterLogout', + ]; + } + + /** + * @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 new file mode 100644 index 000000000..4f8cfe77e --- /dev/null +++ b/Docs/Documentation/Extending-the-Plugin.md @@ -0,0 +1,252 @@ +Extending the Plugin +==================== + +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 https://book.cakephp.org/4/en/orm/entities.html#accessors-mutators + +Example: we are going to use a custom table ```my_users``` in our application , which has a field named ``is_active`` instead of the default ``active``. +* Create a new Table under src/Model/Table/MyUsersTable.php + +```php +namespace App\Model\Table; + +use CakeDC\Users\Model\Table\UsersTable; + +/** + * Application specific Users Table with non plugin conform field(s) + */ +class MyUsersTable extends UsersTable +{ +} +``` + +* Create a new Entity under src/Model/Entity/MyUser.php + +```php +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; + } +} +``` + +* Pass the new table configuration to Users Plugin Configuration + +config/bootstrap.php +``` +Configure::write('Users.config', ['users']); +Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]); +``` + +Then in your config/users.php +``` +return [ + 'Users.table' => 'MyUsers', +]; +``` + +Now the Users Plugin will use MyUsers Table and Entity to register and login user in. Use the +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` ( + `id` char(36) NOT NULL, + `username` varchar(255) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `password` varchar(255) NOT NULL, + `first_name` varchar(50) DEFAULT NULL, + `last_name` varchar(50) DEFAULT NULL, + `token` varchar(255) DEFAULT NULL, + `token_expires` datetime DEFAULT NULL, + `api_token` varchar(255) DEFAULT NULL, + `activation_date` datetime DEFAULT NULL, + `tos_date` datetime DEFAULT NULL, + `active` int(1) NOT NULL DEFAULT '0', + `is_superuser` int(1) NOT NULL DEFAULT '0', + `role` varchar(255) DEFAULT 'user', + `created` datetime NOT NULL, + `modified` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +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 +} +``` + +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 +----------------------------- + +You could use a new Trait. For example, you want to add an 'impersonate' feature + +```php +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 new file mode 100644 index 000000000..f9d8d8e8d --- /dev/null +++ b/Docs/Documentation/Installation.md @@ -0,0 +1,141 @@ +Installation +============ + +Composer +-------- + +``` +composer require cakedc/users +``` + +If you want to use social login features... + +``` +composer require league/oauth2-facebook:@stable +composer require league/oauth2-instagram:@stable +composer require league/oauth2-google:@stable +composer require league/oauth2-linkedin:@stable +composer require league/oauth1-client:@stable +``` + +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... + +``` +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: + +``` +bin/cake migrations migrate -p CakeDC/Users +``` + +Note you don't need to use the provided tables, you could customize the table names, fields etc in your +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 + +You can create the first user, the super user by issuing the following command + +``` +bin/cake users addSuperuser +``` + +Customization +------------- + +First, make sure to set the config `Users.config` at Application::bootstrap +``` +$this->addPlugin(\CakeDC\Users\Plugin::class); +Configure::write('Users.config', ['users']); +``` + +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', + 'OAuth.providers.twitter.options.clientSecret' => 'YOUR APP SECRET', + //etc +]; +``` +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` +* etc. + +Note: using social authentication is not required. + +For more details, check the Configuration doc page 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 new file mode 100644 index 000000000..cc47d0159 --- /dev/null +++ b/Docs/Documentation/Overview.md @@ -0,0 +1,19 @@ +Overview +======== + +You can use the plugin as it comes if you're happy with it or, more common, extend your app specific user implementation from the plugin. + +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, 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/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/SocialAuthentication.md b/Docs/Documentation/SocialAuthentication.md new file mode 100644 index 000000000..f74b36e7a --- /dev/null +++ b/Docs/Documentation/SocialAuthentication.md @@ -0,0 +1,171 @@ +Social Authentication +===================== + +We currently support the following providers to perform login as well as to link an existing account: + +* Facebook +* Twitter +* Google +* LinkedIn +* Instagram +* Amazon + +Please [contact us](https://cakedc.com/contact) if you need to support another provider. + +The main source code for social integration is provided by ['CakeDC/auth' plugin](https://github.com/cakedc/auth) + +Setup +----- +By default social login is disabled, to enable you need to create the +Facebook/Twitter applications you want to use and update your file config/users.php with: + +```php +//This enable social login (authentication) +'Users.Social.login' => true, +//This is the required config to setup facebook. +'OAuth.providers.facebook.options.clientId', 'YOUR APP ID'; +'OAuth.providers.facebook.options.clientSecret', 'YOUR APP SECRET'; +//This is the required config to setup twitter +'OAuth.providers.twitter.options.clientId', 'YOUR APP ID'; +'OAuth.providers.twitter.options.clientSecret', 'YOUR APP SECRET'; +``` +Check optional configs at [config/users.php](./../../config/users.php) inside 'OAuth' key + + +You can also change the default settings for social authenticate in your config/users.php file: + +```php + 'Users.Email' => [ + //determines if the user should include email + 'required' => true, + //determines if registration workflow includes email validation + 'validate' => true, + ], + 'Users.Social' => [ + //enable social login + 'login' => false, + ], + 'Users.Key' => [ + 'Session' => [ + //session key to store the social auth data + 'social' => 'Users.social', + ], + //form key to store the social auth data + 'Form' => [ + 'social' => 'social' + ], + 'Data' => [ + //data key to store email coming from social networks + 'socialEmail' => 'info.email', + ], + ], +``` + +If email is required and the social network does not return the user email then the user will be required to input the email. Additionally, validation could be enabled, in that case the user will be asked to validate the email before be able to login. There are some cases where the email address already exists onto database, if so, the user will receive an email and will be asked to validate the social account in the app. It is important to take into account that the user account itself will remain active and accessible by other ways (other social network account or username/password). + +In most situations you would not need to change any Oauth setting besides applications details. + +For new facebook aps you must use the graphApiVersion 2.8 or greater: + +```php +'OAuth.providers.facebook.options.graphApiVersion' => 'v2.8', +``` + +User Helper +----------- + +You can use the helper included with the plugin to create Facebook/Twitter buttons: + +In templates +```php +$this->User->facebookLogin(); + +$this->User->twitterLogin(); +``` + +We recommend the use of [Bootstrap Social](http://lipis.github.io/bootstrap-social/) in order to automatically apply styles to buttons. Anyway you can always add your own style to the buttons. + +Social Authentication was inspired by [UseMuffin/OAuth2](https://github.com/UseMuffin/OAuth2) library. + +Custom username field +--------------------- + +In your customized users table, add the SocialBehavior with the following configuration: + +```php +$this->addBehavior('CakeDC/Users.Social', [ + 'username' => 'email' +]); +``` +Or if you extend the users table, the behavior is already loaded, so just configure it with: +```php +$this->behaviors()->get('Social')->config(['username' => 'email']); +``` + +By default it will use `username` field. + + +Social Middlewares +------------------ +We provide two middleware to help us the integration with social providers, the SocialAuthMiddleware is +the main one, it is responsible to redirect the user to the social provider site and setup information +needed by the CakeDC/Users.Social authenticator. The second one SocialEmailMiddleware is used when social provider does +not returns user email. + +Social Authenticators +--------------------- +The social authentication works with cakephp/authentication, we have two authenticators they work +in combination with the two social middlewares: + - CakeDC/Users.Social, works with SocialAuthMiddleware + - CakeDC/Users.SocialPendingEmai, works with SocialEmailMiddleware + + +Social Indentifier +------------------ +The social identifier "CakeDC/Users.Social", works with data provider by both social authenticator, +it is responsible of finding or creating a user registry for the social user data request. +By default, it'll fetch user data with finder 'all', but you can use a custom one. Add this to your +config/users.php: + +```php +'Auth.Identifiers.Social.authFinder' => 'customSocialAuth', +``` + + +Handling Social Login Result +---------------------------- +We use a base component 'CakeDC/Users.Login' to handle login, it checks the result of authentication +service to redirects user to an internal page or show an authentication error. It provide some error messages for social login. +There are two custom messages (Auth.SocialLoginFailure.messages) and one default message (Auth.SocialLoginFailure.defaultMessage). + + +To use a custom component to handle the login add this to your config/users.php file: +```php +'Auth.SocialLoginFailure.component' => 'MyLoginA', +``` + +The default configuration is: +```php +[ + ... + 'Auth' => [ + ... + 'SocialLoginFailure' => [ + 'component' => 'CakeDC/Users.Login', + 'defaultMessage' => __d('cake_d_c/users', 'Could not proceed with social account. Please try again'), + 'messages' => [ + 'FAILURE_USER_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your user has not been validated yet. Please check your inbox for instructions' + ), + 'FAILURE_ACCOUNT_NOT_ACTIVE' => __d( + 'cake_d_c/users', + 'Your social account has not been validated yet. Please check your inbox for instructions' + ) + ], + 'targetAuthenticator' => 'CakeDC\Users\Authenticator\SocialAuthenticator' + ], + ... + ] +] +``` diff --git a/Docs/Documentation/Translations.md b/Docs/Documentation/Translations.md new file mode 100644 index 000000000..d9cc74def --- /dev/null +++ b/Docs/Documentation/Translations.md @@ -0,0 +1,18 @@ +Translations +============ + +The Plugin is translated into several languages: + +* Swedish (sv) by @digitalfotografen +* 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 '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 new file mode 100644 index 000000000..920f37f98 --- /dev/null +++ b/Docs/Documentation/UserHelper.md @@ -0,0 +1,85 @@ +UserHelper +============= + +The User 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. + +Setup +--------------- + +Enable the Helper in `src/view/AppView.php`: +```php +class AppView extends View +{ + public function initialize() + { + parent::initialize(); + $this->loadHelper('CakeDC/Users.User'); + } +} +``` + +Social Login +----------------- + +You can use the helper included with the plugin to create Facebook/Twitter buttons: + +In templates +```php +echo $this->User->socialLogin($provider); //provider is 'facebook', 'twitter', etc +``` + +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 +----------------- + +It allows to add a logout link anywhere in the app. + +```php +$this->User->logout(); +``` + +RBAC link +----------------- + +This function validates if you have access to a link and it displays it based on that. + +```php +$this->User->link(); +``` + +Welcome and profile link +----------------- + +It displays a welcome message for the user including the name and a link to the profile page + +```php +$this->User->welcome(); +``` + +reCaptcha +----------------- + +Handles the reCaptcha input display: + +```php +$this->User->addReCaptchaScript(); + +$this->User->addReCaptcha(); +``` + +Note reCaptcha script is added to script block when `addReCaptcha` method is called. 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 new file mode 100644 index 000000000..4223b0e82 --- /dev/null +++ b/Docs/Home.md @@ -0,0 +1,248 @@ +Home +==== + +The **Users** Plugin allow users to register and login, manage their profile, etc. It also allows admins to manage the users. + +The plugin is thought as a base to extend your app specific users controller and model from. + +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. + +Documentation +------------- + +* [Overview](Documentation/Overview.md) +* [Installation](Documentation/Installation.md) +* [Configuration](Documentation/Configuration.md) +* [Authentication](Documentation/Authentication.md) +* [Authorization](Documentation/Authorization.md) +* [SimpleRbacAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SimpleRbacAuthorize.md) +* [SuperuserAuthorize](https://github.com/CakeDC/auth/blob/master/Docs/Documentation/SuperuserAuthorize.md) +* [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 similarity index 92% rename from license.txt rename to LICENSE.txt index 25e5ee24c..5448b2520 100644 --- a/license.txt +++ b/LICENSE.txt @@ -1,10 +1,11 @@ The MIT License -Copyright 2009-2010 +Copyright 2009-2018 Cake Development Corporation 1785 E. Sahara Avenue, Suite 490-423 Las Vegas, Nevada 89104 -http://cakedc.com +Phone: +1 702 425 5085 +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"), @@ -22,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 new file mode 100644 index 000000000..fbd5af5f4 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +CakeDC Users Plugin +=================== + +[![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) + +Versions and branches +--------------------- + +| CakePHP | CakeDC Users Plugin | Tag | Notes | +| :-------------: | :------------------------: | :--: | :---- | +| ^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: + +* User registration +* Login/logout +* Social login (Facebook, Twitter, Instagram, Google, Linkedin, etc) +* 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 + * Use your own UsersTable + * Use your own Controller + +On the previous versions of the plugin, extensibility was an issue, and one of the main +objectives of the 3.0 rewrite is to guarantee all the pieces could be extended/reused as +easily. + +Another decision made was limiting the plugin dependencies on other packages as much as possible. + +Requirements +------------ + +* CakePHP 4.0+ +* PHP 7.2+ + +Documentation +------------- + +For documentation, as well as tutorials, see the [Docs](Docs/Home.md) directory of this repository. + +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](https://www.cakedc.com/contact) for more information. + +Contributing +------------ + +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 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 new file mode 100644 index 000000000..3a94372d7 --- /dev/null +++ b/composer.json @@ -0,0 +1,98 @@ +{ + "name": "cakedc/users", + "description": "Users Plugin for CakePHP", + "type": "cakephp-plugin", + "keywords": [ + "cakephp", + "oauth2", + "auth", + "authentication", + "cakedc" + ], + "homepage": "https://github.com/CakeDC/users", + "license": "MIT", + "authors": [ + { + "name": "CakeDC", + "homepage": "http://www.cakedc.com", + "role": "Author" + }, + { + "name": "Others", + "homepage": "https://github.com/CakeDC/users/graphs/contributors" + } + ], + "support": { + "issues": "https://github.com/CakeDC/users/issues", + "source": "https://github.com/CakeDC/users" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=7.2.0", + "cakephp/cakephp": "^4.0", + "cakedc/auth": "^7.0", + "cakephp/authorization": "^2.0.0", + "cakephp/authentication": "^2.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "league/oauth2-facebook": "@stable", + "league/oauth2-instagram": "@stable", + "league/oauth2-google": "@stable", + "league/oauth2-linkedin": "@stable", + "luchianenco/oauth2-amazon": "^1.1", + "google/recaptcha": "@stable", + "robthree/twofactorauth": "^1.6", + "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", + "robthree/twofactorauth": "Provides Google Authenticator functionality", + "cakephp/authorization": "Provide authorization for users" + }, + "autoload": { + "psr-4": { + "CakeDC\\Users\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "CakeDC\\Users\\Test\\": "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 new file mode 100644 index 000000000..2379afa67 --- /dev/null +++ b/config/Migrations/20150513201111_initial.php @@ -0,0 +1,152 @@ +table('users', ['id' => false, 'primary_key' => ['id']]); + $table + ->addColumn('id', 'uuid', [ + 'null' => false, + ]) + ->addColumn('username', 'string', [ + 'limit' => 255, + 'null' => false, + ]) + ->addColumn('email', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('password', 'string', [ + 'limit' => 255, + 'null' => false, + ]) + ->addColumn('first_name', 'string', [ + 'default' => null, + 'limit' => 50, + 'null' => true, + ]) + ->addColumn('last_name', 'string', [ + 'default' => null, + 'limit' => 50, + 'null' => true, + ]) + ->addColumn('token', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('token_expires', 'datetime', [ + 'default' => null, + 'null' => true, + ]) + ->addColumn('api_token', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('activation_date', 'datetime', [ + 'default' => null, + 'null' => true, + ]) + ->addColumn('tos_date', 'datetime', [ + 'default' => null, + 'null' => true, + ]) + ->addColumn('active', 'boolean', [ + 'default' => false, + 'null' => false, + ]) + ->addColumn('is_superuser', 'boolean', [ + 'default' => false, + 'null' => false, + ]) + ->addColumn('role', 'string', [ + 'default' => 'user', + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('created', 'datetime', [ + 'null' => false, + ]) + ->addColumn('modified', 'datetime', [ + 'null' => false, + ]) + ->create(); + + $table = $this->table('social_accounts', ['id' => false, 'primary_key' => ['id']]); + $table + ->addColumn('id', 'uuid', [ + 'null' => false, + ]) + ->addColumn('user_id', 'uuid', [ + 'null' => false, + ]) + ->addColumn('provider', 'string', [ + 'limit' => 255, + 'null' => false, + ]) + ->addColumn('username', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('reference', 'string', [ + 'limit' => 255, + 'null' => false, + ]) + ->addColumn('avatar', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => true, + ]) + ->addColumn('description', 'text', [ + 'default' => null, + 'null' => true, + ]) + ->addColumn('link', 'string', [ + 'limit' => 255, + 'null' => false, + ]) + ->addColumn('token', 'string', [ + 'limit' => 500, + 'null' => false, + ]) + ->addColumn('token_secret', 'string', [ + 'default' => null, + 'limit' => 500, + 'null' => true, + ]) + ->addColumn('token_expires', 'datetime', [ + 'default' => null, + 'null' => true, + ]) + ->addColumn('active', 'boolean', [ + 'default' => true, + 'null' => false, + ]) + ->addColumn('data', 'text', [ + 'null' => false, + ]) + ->addColumn('created', 'datetime', [ + 'null' => false, + ]) + ->addColumn('modified', 'datetime', [ + 'null' => false, + ]) + ->addForeignKey('user_id', 'users', 'id', array('delete' => 'CASCADE', 'update' => 'CASCADE')) + ->create(); + } +} diff --git a/config/Migrations/20161031101316_AddSecretToUsers.php b/config/Migrations/20161031101316_AddSecretToUsers.php new file mode 100644 index 000000000..f3e185840 --- /dev/null +++ b/config/Migrations/20161031101316_AddSecretToUsers.php @@ -0,0 +1,43 @@ +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 new file mode 100644 index 000000000..d78acc8b7 --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,41 @@ +each(function ($file) { + Configure::load($file); +}); +UsersUrl::setupConfigUrls(); + +$locator = TableRegistry::getTableLocator(); +foreach (['Users', 'CakeDC/Users.Users'] as $modelKey) { + if (!$locator->exists($modelKey)) { + $locator->setConfig($modelKey, ['className' => Configure::read('Users.table')]); + } +} +$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/migrations/001_initialize_users_schema.php b/config/migrations/001_initialize_users_schema.php deleted file mode 100644 index a7e5356e3..000000000 --- a/config/migrations/001_initialize_users_schema.php +++ /dev/null @@ -1,99 +0,0 @@ - array( - 'create_table' => array( - 'details' => array( - 'id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'primary'), - 'user_id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36), - 'position' => array('type'=>'float', 'null' => false, 'default' => '1', 'length' => 4), - 'field' => array('type'=>'string', 'null' => false, 'default' => NULL, 'key' => 'index'), - 'value' => array('type'=>'text', 'null' => true, 'default' => NULL), - 'input' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 16), - 'data_type' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 16), - 'label' => array('type'=>'string', 'null' => false, 'default' => '', 'length' => 128), - 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array( - 'PRIMARY' => array('column' => 'id', 'unique' => 1), - 'UNIQUE_PROFILE_PROPERTY' => array('column' => array('field', 'user_id'), 'unique' => 1)) - ), - 'users' => array( - 'id' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'primary'), - 'username' => array('type'=>'string', 'null' => false, 'default' => NULL), - 'slug' => array('type'=>'string', 'null' => false, 'default' => NULL), - 'passwd' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'password_token' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'email' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'email_authenticated' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'email_token' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'email_token_expires' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'tos' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'active' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'last_login' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'last_activity' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'is_admin' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'role' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array( - 'PRIMARY' => array('column' => 'id', 'unique' => 1), - 'BY_USERNAME' => array('column' => array('username'), 'unique' => 0), - 'BY_EMAIL' => array('column' => array('email'), 'unique' => 0) - ), - ), - ), - ), - 'down' => array( - 'drop_table' => array( - 'users', 'details'), - ) - ); - -/** - * before migration callback - * - * @param string $direction, up or down direction of migration process - */ - public function before($direction) { - return true; - } - -/** - * after migration callback - * - * @param string $direction, up or down direction of migration process - */ - public function after($direction) { - return true; - } - -} diff --git a/config/migrations/map.php b/config/migrations/map.php deleted file mode 100644 index 87895eaf5..000000000 --- a/config/migrations/map.php +++ /dev/null @@ -1,19 +0,0 @@ - array('001_initialize_users_schema' => 'M49c3417a54874a9d276811502cedc421') -); diff --git a/config/permissions.php b/config/permissions.php new file mode 100644 index 000000000..5c52467fb --- /dev/null +++ b/config/permissions.php @@ -0,0 +1,144 @@ + 'role' | ['roles'] | '*' + * 'prefix' => 'Prefix' | , (default = null) + * 'plugin' => 'Plugin' | , (default = null) + * 'controller' => 'Controller' | ['Controllers'] | '*', + * 'action' => 'action' | ['actions'] | '*', + * 'allowed' => true | false | callback (default = true) + * ] + * You could use '*' to match anything + * 'allowed' will be considered true if not defined. It allows a callable to manage complex + * permissions, like this + * 'allowed' => function (array $user, $role, Request $request) {} + * + * Example, using allowed callable to define permissions only for the owner of the Posts to edit/delete + * + * (remember to add the 'uses' at the top of the permissions.php file for Hash, TableRegistry and Request + [ + 'role' => ['user'], + 'controller' => ['Posts'], + 'action' => ['edit', 'delete'], + 'allowed' => function(array $user, $role, Request $request) { + $postId = Hash::get($request->params, 'pass.0'); + $post = TableRegistry::getTableLocator()->get('Posts')->get($postId); + $userId = Hash::get($user, 'id'); + if (!empty($post->user_id) && !empty($userId)) { + return $post->user_id === $userId; + } + return false; + } + ], + */ + +return [ + 'CakeDC/Auth.permissions' => [ + //all bypass + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => [ + // LoginTrait + 'socialLogin', + 'login', + 'logout', + 'socialEmail', + 'verify', + // RegisterTrait + 'register', + 'validateEmail', + // PasswordManagementTrait used in RegisterTrait + 'changePassword', + 'resetPassword', + 'requestResetPassword', + // UserValidationTrait used in PasswordManagementTrait + 'resendTokenValidation', + 'linkSocial', + //U2F actions + 'u2f', + 'u2fRegister', + 'u2fRegisterFinish', + 'u2fAuthenticate', + 'u2fAuthenticateFinish', + 'webauthn2fa', + 'webauthn2faRegister', + 'webauthn2faRegisterOptions', + 'webauthn2faAuthenticate', + 'webauthn2faAuthenticateOptions', + ], + 'bypassAuth' => true, + ], + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => [ + 'validateAccount', + 'resendValidation', + ], + 'bypassAuth' => true, + ], + //admin role allowed to all the things + [ + 'role' => 'admin', + 'prefix' => '*', + 'extension' => '*', + 'plugin' => '*', + 'controller' => '*', + 'action' => '*', + ], + //specific actions allowed for the all roles in Users plugin + [ + 'role' => '*', + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => ['profile', 'logout', 'linkSocial', 'callbackLinkSocial'], + ], + [ + 'role' => '*', + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + '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' => '*', + 'controller' => 'Pages', + 'action' => 'display', + ], + [ + 'role' => '*', + 'plugin' => 'DebugKit', + 'controller' => '*', + 'action' => '*', + 'bypassAuth' => true, + ], + ] +]; diff --git a/config/routes.php b/config/routes.php new file mode 100644 index 000000000..81ac70cc1 --- /dev/null +++ b/config/routes.php @@ -0,0 +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')); + +$oauthPath = Configure::read('OAuth.path'); +if (is_array($oauthPath)) { + $routes->scope('/auth', function (RouteBuilder $routes) use ($oauthPath) { + $routes->connect( + '/{provider}', + $oauthPath, + ['provider' => implode('|', array_keys(Configure::read('OAuth.providers')))] + ); + }); +} + +$routes->connect('/social-accounts/:action/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', +]); +$routes->connect('/users/{action}/*', UsersUrl::actionRouteParams(null)); diff --git a/config/schema/users.php b/config/schema/users.php deleted file mode 100644 index 343ba9346..000000000 --- a/config/schema/users.php +++ /dev/null @@ -1,60 +0,0 @@ - array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'primary'), - 'user_id' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 36), - 'position' => array('type' => 'float', 'null' => false, 'default' => '1'), - 'field' => array('type' => 'string', 'null' => false, 'default' => NULL, 'key' => 'index'), - 'value' => array('type' => 'text', 'null' => true, 'default' => NULL), - 'input' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 16), - 'data_type' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 16), - 'label' => array('type' => 'string', 'null' => false, 'length' => 128), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'UNIQUE_PROFILE_PROPERTY' => array('column' => array('field', 'user_id'), 'unique' => 1)) - ); - var $users = array( - 'id' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 36, 'key' => 'primary'), - 'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'key' => 'index'), - 'slug' => array('type' => 'string', 'null' => false, 'default' => NULL), - 'passwd' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'password_token' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'email' => array('type' => 'string', 'null' => true, 'default' => NULL, 'key' => 'index'), - 'email_authenticated' => array('type' => 'boolean', 'null' => true, 'default' => '0'), - 'email_token' => array('type' => 'string', 'null' => true, 'default' => NULL), - 'email_token_expires' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'tos' => array('type' => 'boolean', 'null' => true, 'default' => '0'), - 'active' => array('type' => 'boolean', 'null' => true, 'default' => '0'), - 'last_login' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'last_activity' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'is_admin' => array('type' => 'boolean', 'null' => true, 'default' => '0'), - 'role' => array('type' => 'string', 'null' => true, 'default' => NULL), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'BY_USERNAME' => array('column' => array('username'), 'unique' => 0), 'BY_EMAIL' => array('column' => array('email'), 'unique' => 0)) - ); -} diff --git a/config/users.php b/config/users.php new file mode 100644 index 000000000..23dfeb9d1 --- /dev/null +++ b/config/users.php @@ -0,0 +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' => 'CakeDC/Users.Users', + // Controller used to manage users plugin features & actions + 'controller' => 'CakeDC/Users.Users', + // Password Hasher + 'passwordHasher' => '\Cake\Auth\DefaultPasswordHasher', + 'middlewareQueueLoader' => \CakeDC\Users\Loader\MiddlewareQueueLoader::class, + // token expiration, 1 hour + 'Token' => ['expiration' => 3600], + 'Email' => [ + // determines if the user should include email + 'required' => true, + // determines if registration workflow includes email validation + 'validate' => true, + ], + 'Registration' => [ + // determines if the register is enabled + 'active' => true, + // determines if the reCaptcha is enabled for registration + 'reCaptcha' => true, + // allow a logged in user to access the registration form + 'allowLoggedIn' => false, + //ensure user is active (confirmed email) to reset his password + 'ensureActive' => false, + // default role name used in registration + 'defaultRole' => 'user', + // show verbose error to users + 'showVerboseError' => false, + ], + 'reCaptcha' => [ + // reCaptcha key goes here + 'key' => null, + // reCaptcha secret + 'secret' => null, + // use reCaptcha in registration + 'registration' => false, + // use reCaptcha in login, valid values are false, true + 'login' => false, + ], + 'Tos' => [ + // determines if the user should include tos accepted + 'required' => true, + ], + 'Social' => [ + // enable social login + 'login' => false, + ], + 'Profile' => [ + // Allow view other users profiles + 'viewOthers' => true, + ], + 'Key' => [ + 'Session' => [ + // session key to store the social auth data + 'social' => 'Users.social', + // userId key used in reset password workflow + 'resetPasswordUserId' => 'Users.resetPasswordUserId', + ], + // form key to store the social auth data + 'Form' => [ + 'social' => 'social', + ], + 'Data' => [ + // data key to store the users email + 'email' => 'email', + // data key to store email coming from social networks + 'socialEmail' => 'info.email', + // data key to check if the remember me option is enabled + 'rememberMe' => 'remember_me', + ], + ], + // Avatar placeholder + 'Avatar' => ['placeholder' => 'CakeDC/Users.avatar_placeholder.png'], + 'RememberMe' => [ + // configure Remember Me component + 'active' => true, + 'checked' => true, + 'Cookie' => [ + 'name' => 'remember_me', + 'Config' => [ + '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 + 'Auth' => [ + '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, + ], + 'AuthorizationMiddleware' => [ + 'unauthorizedHandler' => [ + 'className' => 'CakeDC/Users.DefaultRedirect', + ], + ], + 'AuthorizationComponent' => [ + 'enabled' => true, + ], + 'RbacPolicy' => [], + 'PasswordRehash' => [ + 'identifiers' => ['Password'], + ], + ], + 'OAuth' => [ + 'providers' => [ + 'facebook' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'League\OAuth2\Client\Provider\Facebook', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Facebook', + 'authParams' => ['scope' => ['public_profile', 'email', 'user_birthday', 'user_gender', 'user_link']], + 'options' => [ + 'graphApiVersion' => 'v2.8', //bio field was deprecated on >= v2.8 + 'redirectUri' => Router::fullBaseUrl() . '/auth/facebook', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/facebook', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/facebook', + ], + ], + 'twitter' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth1Service', + 'className' => 'League\OAuth1\Client\Server\Twitter', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Twitter', + 'options' => [ + 'redirectUri' => Router::fullBaseUrl() . '/auth/twitter', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/twitter', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/twitter', + ], + ], + 'linkedIn' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'League\OAuth2\Client\Provider\LinkedIn', + 'mapper' => 'CakeDC\Auth\Social\Mapper\LinkedIn', + 'options' => [ + 'redirectUri' => Router::fullBaseUrl() . '/auth/linkedIn', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedIn', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedIn', + ], + ], + 'instagram' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'League\OAuth2\Client\Provider\Instagram', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Instagram', + 'options' => [ + 'redirectUri' => Router::fullBaseUrl() . '/auth/instagram', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/instagram', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/instagram', + ], + ], + 'google' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'League\OAuth2\Client\Provider\Google', + 'mapper' => 'CakeDC\Auth\Social\Mapper\Google', + 'options' => [ + 'userFields' => ['url', 'aboutMe'], + 'redirectUri' => Router::fullBaseUrl() . '/auth/google', + 'linkSocialUri' => Router::fullBaseUrl() . '/link-social/google', + 'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/google', + ], + ], + 'amazon' => [ + 'service' => 'CakeDC\Auth\Social\Service\OAuth2Service', + 'className' => 'Luchianenco\OAuth2\Client\Provider\Amazon', + '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/controllers/details_controller.php b/controllers/details_controller.php deleted file mode 100644 index 164426b02..000000000 --- a/controllers/details_controller.php +++ /dev/null @@ -1,214 +0,0 @@ -Detail->find('all', array( - 'contain' => array(), - 'conditions' => array( - 'Detail.user_id' => $this->Auth->user('id'), - 'Detail.field LIKE' => 'user.%'), - 'order' => 'Detail.position DESC')); - $this->set('details', $details); - } - -/** - * View - * - * @param string $id Detail ID - * @return void - */ - public function view($id = null) { - if (!$id) { - $this->Session->setFlash(__d('users', 'Invalid Detail.', true)); - $this->redirect(array('action'=>'index')); - } - $this->set('detail', $this->Detail->read(null, $id)); - } - -/** - * Add - * - * @return void - */ - public function add() { - if (!empty($this->data)) { - $userId = $this->Auth->user('id'); - foreach($this->data as $group => $options) { - foreach($options as $key => $value) { - $field = $group . '.' . $key; - $this->Detail->updateAll( - array('Detail.value' => "'$value'"), - array('Detail.user_id' => $userId, 'Detail.field' => $field)); - } - } - $this->Session->setFlash(__d('users', 'Saved', true)); - } - $this->redirect(array('action' => 'index')); - } - -/** - * Edit - * - * Allows a logged in user to edit his own profile settings - * - * @param string $section Section name - * @return void - */ - public function edit($section = 'user') { - if (!isset($section)) { - $section = 'user'; - } - - if (!empty($this->data)) { - $this->Detail->saveSection($this->Auth->user('id'), $this->data, $section); - $this->data['Detail'] = $this->Detail->getSection($this->Auth->user('id'), $section); - $this->Session->setFlash(sprintf(__d('users', '%s details saved', true), ucfirst($section))); - } - - if (empty($this->data)) { - $this->data['Detail'] = $this->Detail->getSection($this->Auth->user('id'), $section); - } - - $this->set('section', $section); - } - -/** - * Delete - * - * @param string $id Detail ID - * @return void - */ - public function delete($id = null) { - if (!$id) { - $this->Session->setFlash(__d('users', 'Invalid id for Detail', true)); - $this->redirect(array('action'=>'index')); - } - if ($this->Detail->delete($id)) { - $this->Session->setFlash(__d('users', 'Detail deleted', true)); - $this->redirect(array('action'=>'index')); - } - } - -/** - * Admin Index - * - * @return void - */ - public function admin_index() { - $this->Detail->recursive = 0; - $this->set('details', $this->paginate()); - } - -/** - * Admin View - * - * @param string $id Detail ID - * @return void - */ - public function admin_view($id = null) { - if (!$id) { - $this->Session->setFlash(__d('users', 'Invalid Detail.', true)); - $this->redirect(array('action'=>'index')); - } - $this->set('detail', $this->Detail->read(null, $id)); - } - -/** - * Admin Add - * - * @return void - */ - public function admin_add() { - if (!empty($this->data)) { - $this->Detail->create(); - if ($this->Detail->save($this->data)) { - $this->Session->setFlash(__d('users', 'The Detail has been saved', true)); - $this->redirect(array('action'=>'index')); - } else { - $this->Session->setFlash(__d('users', 'The Detail could not be saved. Please, try again.', true)); - } - } - - $users = $this->Detail->User->find('list'); - $this->set(compact('users')); - } - -/** - * Admin edit - * - * @param string $id Detail ID - * @return void - */ - public function admin_edit($id = null) { - if (!$id && empty($this->data)) { - $this->Session->setFlash(__d('users', 'Invalid Detail', true)); - $this->redirect(array('action'=>'index')); - } - if (!empty($this->data)) { - if ($this->Detail->save($this->data)) { - $this->Session->setFlash(__d('users', 'The Detail has been saved', true)); - $this->redirect(array('action'=>'index')); - } else { - $this->Session->setFlash(__d('users', 'The Detail could not be saved. Please, try again.', true)); - } - } - if (empty($this->data)) { - $this->data = $this->Detail->read(null, $id); - } - - $users = $this->Detail->User->find('list'); - $this->set(compact('users')); - } - -/** - * Admin Delete - * - * @param string $id Detail ID - * @return void - */ - public function admin_delete($id = null) { - if (!$id) { - $this->Session->setFlash(__d('users', 'Invalid id for Detail', true)); - $this->redirect(array('action'=>'index')); - } - if ($this->Detail->delete($id)) { - $this->Session->setFlash(__d('users', 'Detail deleted', true)); - $this->redirect(array('action'=>'index')); - } - } -} diff --git a/controllers/users_controller.php b/controllers/users_controller.php deleted file mode 100644 index b681b7c0a..000000000 --- a/controllers/users_controller.php +++ /dev/null @@ -1,602 +0,0 @@ - 'search', 'type' => 'value'), - array('field' => 'username', 'type' => 'value'), - array('field' => 'email', 'type' => 'value')); - -/** - * beforeFilter callback - * - * @return void - */ - public function beforeFilter() { - parent::beforeFilter(); - $this->Auth->allow('register', 'reset', 'verify', 'logout', 'index', 'view', 'reset_password'); - - if ($this->action == 'register') { - $this->Auth->enabled = false; - } - - if ($this->action == 'login') { - $this->Auth->autoRedirect = false; - } - - $this->set('model', $this->modelClass); - - if (!Configure::read('App.defaultEmail')) { - Configure::write('App.defaultEmail', 'noreply@' . env('HTTP_HOST')); - } - } - -/** - * List of all users - * - * @return void - */ - public function index() { - //$this->User->contain('Detail'); - $searchTerm = ''; - $this->Prg->commonProcess($this->modelClass, $this->modelClass, 'index', false); - - if (!empty($this->params['named']['search'])) { - if (!empty($this->params['named']['search'])) { - $searchTerm = $this->params['named']['search']; - } - $this->data[$this->modelClass]['search'] = $searchTerm; - } - - $this->paginate[$this->modelClass] = array( - 'search', - 'limit' => 12, - 'order' => $this->modelClass . '.username ASC', - 'by' => $searchTerm, - 'conditions' => array( - 'OR' => array( - 'AND' => array( - $this->{$this->modelClass}->alias . '.active' => 1, - $this->{$this->modelClass}->alias . '.email_authenticated' => 1)))); - - - $this->set('users', $this->paginate($this->modelClass)); - $this->set('searchTerm', $searchTerm); - - if (!isset($this->params['named']['sort'])) { - $this->params['named']['sort'] = 'username'; - } - } - -/** - * The homepage of a users giving him an overview about everything - * - * @return void - */ - public function dashboard() { - $user = $this->User->read(null, $this->Auth->user('id')); - $this->set('user', $user); - } - -/** - * Shows a users profile - * - * @param string $slug User Slug - * @return void - */ - public function view($slug = null) { - try { - $this->set('user', $this->User->view($slug)); - } catch (Exception $e) { - $this->Session->setFlash($e->getMessage()); - $this->redirect('/'); - } - } - -/** - * Edit - * - * @param string $id User ID - * @return void - */ - public function edit() { - if (!empty($this->data)) { - if ($this->User->Detail->saveSection($this->Auth->user('id'), $this->data, 'User')) { - $this->Session->setFlash(__d('users', 'Profile saved.', true)); - } else { - $this->Session->setFlash(__d('users', 'Could not save your profile.', true)); - } - } else { - $this->data = $this->User->read(null, $this->Auth->user('id')); - } - - $this->_setLanguages(); - } - -/** - * Admin Index - * - * @return void - */ - public function admin_index() { - $this->Prg->commonProcess(); - $this->{$this->modelClass}->data[$this->modelClass] = $this->passedArgs; - $parsedConditions = $this->{$this->modelClass}->parseCriteria($this->passedArgs); - - $this->paginate[$this->modelClass]['conditions'] = $parsedConditions; - $this->paginate[$this->modelClass]['order'] = array($this->modelClass . '.created' => 'desc'); - - $this->{$this->modelClass}->recursive = 0; - $this->set('users', $this->paginate()); - } - -/** - * Admin view - * - * @param string $id User ID - * @return void - */ - public function admin_view($id = null) { - if (!$id) { - $this->Session->setFlash(__d('users', 'Invalid User.', true)); - $this->redirect(array('action'=>'index')); - } - $this->set('user', $this->User->read(null, $id)); - } - -/** - * Admin add - * - * @return void - */ - public function admin_add() { - if ($this->User->add($this->data)) { - $this->Session->setFlash(__d('users', 'The User has been saved', true)); - $this->redirect(array('action' => 'index')); - } - } - -/** - * Admin edit - * - * @param string $id User ID - * @return void - */ - public function admin_edit($userId = null) { - try { - $result = $this->User->edit($userId, $this->data); - if ($result === true) { - $this->Session->setFlash(__d('users', 'User saved', true)); - $this->redirect(array('action' => 'index')); - } else { - $this->data = $result; - } - } catch (OutOfBoundsException $e) { - $this->Session->setFlash($e->getMessage()); - $this->redirect(array('action' => 'index')); - } - - if (empty($this->data)) { - $this->data = $this->User->read(null, $userId); - } - } - -/** - * Delete a user account - * - * @param string $userId User ID - * @return void - */ - public function admin_delete($userId = null) { - if ($this->User->delete($userId)) { - $this->Session->setFlash(__d('users', 'User deleted', true)); - } else { - $this->Session->setFlash(__d('users', 'Invalid User', true)); - } - - $this->redirect(array('action' => 'index')); - } - -/** - * Search for a user - * - * @return void - */ - public function admin_search() { - $this->search(); - } - -/** - * User register action - * - * @return void - */ - public function register() { - if ($this->Auth->user()) { - $this->Session->setFlash(__d('users', 'You are already registered and logged in!', true)); - $this->redirect('/'); - } - - if (!empty($this->data)) { - $user = $this->User->register($this->data); - if ($user !== false) { - $this->set('user', $user); - $this->_sendVerificationEmail($user[$this->modelClass]['email']); - $this->Session->setFlash(__d('users', 'Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login.', true)); - $this->redirect(array('action'=> 'login')); - } else { - unset($this->data[$this->modelClass]['passwd']); - unset($this->data[$this->modelClass]['temppassword']); - $this->Session->setFlash(__d('users', 'Your account could not be created. Please, try again.', true), 'default', array('class' => 'message warning')); - } - } - - $this->_setLanguages(); - } - -/** - * Common login action - * - * @return void - */ - public function login() { - if ($this->Auth->user()) { - $this->User->id = $this->Auth->user('id'); - $this->User->saveField('last_login', date('Y-m-d H:i:s')); - - if ($this->here == $this->Auth->loginRedirect) { - $this->Auth->loginRedirect = '/'; - } - - $this->Session->setFlash(sprintf(__d('users', '%s you have successfully logged in', true), $this->Auth->user('username'))); - if (!empty($this->data)) { - $data = $this->data[$this->modelClass]; - $this->_setCookie(); - } - - if (empty($data['return_to'])) { - $data['return_to'] = null; - } - $this->redirect($this->Auth->redirect($data['return_to'])); - } - - if (isset($this->params['named']['return_to'])) { - $this->set('return_to', urldecode($this->params['named']['return_to'])); - } else { - $this->set('return_to', false); - } - } - -/** - * Search - * - * @return void - */ - public function search() { - $searchTerm = ''; - $this->Prg->commonProcess($this->modelClass, $this->modelClass, 'search', false); - - if (!empty($this->params['named']['search'])) { - $searchTerm = $this->params['named']['search']; - $by = 'any'; - } - if (!empty($this->params['named']['username'])) { - $searchTerm = $this->params['named']['username']; - $by = 'username'; - } - if (!empty($this->params['named']['email'])) { - $searchTerm = $this->params['named']['email']; - $by = 'email'; - } - $this->data[$this->modelClass]['search'] = $searchTerm; - - $this->paginate = array( - 'search', - 'limit' => 12, - 'by' => $by, - 'search' => $searchTerm, - 'conditions' => array( - 'AND' => array( - $this->modelClass . '.active' => 1, - $this->modelClass . '.email_authenticated' => 1))); - - $this->set('users', $this->paginate($this->modelClass)); - $this->set('searchTerm', $searchTerm); - } - -/** - * Common logout action - * - * @return void - */ - public function logout() { - $message = sprintf(__d('users', '%s you have successfully logged out', true), $this->Auth->user('username')); - $this->Session->destroy(); - $this->Cookie->destroy(); - - $this->Session->setFlash($message); - $this->redirect($this->Auth->logout()); - } - -/** - * Confirm email action - * - * @param string $type Type - * @return void - */ - public function verify($type = 'email') { - if (isset($this->passedArgs['1'])){ - $token = $this->passedArgs['1']; - } else { - $this->redirect(array('action' => 'login'), null, true); - } - - if ($type === 'email') { - $data = $this->User->validateToken($token); - } elseif($type === 'reset') { - $data = $this->User->validateToken($token, true); - } else { - $this->Session->setFlash(__d('users', 'There url you accessed is not longer valid', true)); - $this->redirect('/'); - } - - if ($data !== false) { - $email = $data[$this->modelClass]['email']; - unset($data[$this->modelClass]['email']); - - if ($type === 'reset') { - $newPassword = $data[$this->modelClass]['passwd']; - $data[$this->modelClass]['passwd'] = $this->Auth->password($newPassword); - } - - if ($type === 'email') { - $data[$this->modelClass]['active'] = 1; - } - - if ($this->User->save($data, false)) { - if ($type === 'reset') { - $this->Email->to = $email; - $this->Email->from = Configure::read('App.defaultEmail'); - $this->Email->replyTo = Configure::read('App.defaultEmail'); - $this->Email->return = Configure::read('App.defaultEmail'); - $this->Email->subject = env('HTTP_HOST') . ' ' . __d('users', 'Password Reset', true); - $this->Email->template = null; - $content[] = __d('users', 'Your password has been reset', true); - $content[] = __d('users', 'Please login using this password and change your password', true); - $content[] = $newPassword; - $this->Email->send($content); - $this->Session->setFlash(__d('users', 'Your password was sent to your registered email account', true)); - $this->redirect(array('action' => 'login')); - } else { - unset($data); - $data[$this->modelClass]['active'] = 1; - $this->User->save($data); - $this->Session->setFlash(__d('users', 'Your e-mail has been validated!', true)); - $this->redirect(array('action' => 'login')); - } - } else { - $this->Session->setFlash(__d('users', 'There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address.', true)); - $this->redirect('/'); - } - } else { - $this->Session->setFlash(__d('users', 'The url you accessed is not longer valid', true)); - $this->redirect('/'); - } - } - -/** - * Allows the user to enter a new password, it needs to be confirmed - * - * @return void - */ - public function change_password() { - if (!empty($this->data)) { - $this->data[$this->modelClass]['id'] = $this->Auth->user('id'); - if ($this->User->changePassword($this->data)) { - $this->Session->setFlash(__d('users', 'Password changed.', true)); - $this->redirect('/'); - } - } - } - -/** - * Reset Password Action - * - * Handles the trigger of the reset, also takes the token, validates it and let the user enter - * a new password. - * - * @param string $token Token - * @param string $user User Data - * @return void - */ - public function reset_password($token = null, $user = null) { - if (empty($token)) { - $admin = false; - if ($user) { - $this->data = $user; - $admin = true; - } - $this->_sendPasswordReset($admin); - } else { - $this->__resetPassword($token); - } - } - -/** - * Sets a list of languages to the view which can be used in selects - * - * @param string View variable name, default is languages - * @return void - */ - protected function _setLanguages($viewVar = 'languages') { - App::import('Lib', 'Utils.Languages'); - $Languages = new Languages(); - $this->set($viewVar, $Languages->lists('locale')); - } - -/** - * Sends the verification email - * - * This method is protected and not private so that classes that inherit this - * controller can override this method to change the varification mail sending - * in any possible way. - * - * @param string $to Receiver email address - * @param array $options EmailComponent options - * @return boolean Success - */ - protected function _sendVerificationEmail($to = null, $options = array()) { - $defaults = array( - 'from' => 'noreply@' . env('HTTP_HOST'), - 'subject' => __d('users', 'Account verification', true), - 'template' => 'account_verification'); - - $options = array_merge($defaults, $options); - - $this->Email->to = $to; - $this->Email->from = $options['from']; - $this->Email->subject = $options['subject']; - $this->Email->template = $options['template']; - - return $this->Email->send(); - } - -/** - * Checks if the email is in the system and authenticated, if yes create the token - * save it and send the user an email - * - * @param boolean $admin Admin boolean - * @param array $options Options - * @return void - */ - protected function _sendPasswordReset($admin = null, $options = array()) { - $defaults = array( - 'from' => 'noreply@' . env('HTTP_HOST'), - 'subject' => __d('users', 'Password Reset', true), - 'template' => 'password_reset_request'); - - $options = array_merge($defaults, $options); - - if (!empty($this->data)) { - $user = $this->User->passwordReset($this->data); - - if (!empty($user)) { - $this->set('token', $user[$this->modelClass]['password_token']); - $this->Email->to = $user[$this->modelClass]['email']; - $this->Email->from = $options['from']; - $this->Email->subject = $options['subject']; - $this->Email->template = $options['template']; - - $this->Email->send(); - if ($admin) { - $this->Session->setFlash(sprintf( - __d('users', '%s has been sent an email with instruction to reset their password.', true), - $user[$this->modelClass]['email'])); - $this->redirect(array('action' => 'index', 'admin' => true)); - } else { - $this->Session->setFlash(__d('users', 'You should receive an email with further instructions shortly', true)); - $this->redirect(array('action' => 'login')); - } - } else { - $this->Session->setFlash(__d('users', 'No user was found with that email.', true)); - $this->redirect($this->referer('/')); - } - } - $this->render('request_password_change'); - } - -/** - * Sets the cookie to remember the user - * - * @param array Cookie component properties as array, like array('domain' => 'yourdomain.com') - * @param string Cookie data keyname for the userdata, its default is "User". This is set to User and NOT using the model alias to make sure it works with different apps with different user models accross different (sub)domains. - * @return void - * @link http://api13.cakephp.org/class/cookie-component - */ - protected function _setCookie($options = array(), $cookieKey = 'User') { - if (empty($this->data[$this->modelClass]['remember_me'])) { - $this->Cookie->delete($cookieKey); - } else { - $validProperties = array('domain', 'key', 'name', 'path', 'secure', 'time'); - $defaults = array( - 'name' => 'rememberMe'); - - $options = array_merge($defaults, $options); - foreach ($options as $key => $value) { - if (in_array($key, $validProperties)) { - $this->Cookie->{$key} = $value; - } - } - - $cookieData = array(); - $cookieData[$this->Auth->fields['username']] = $this->data[$this->modelClass][$this->Auth->fields['username']]; - $cookieData[$this->Auth->fields['password']] = $this->data[$this->modelClass][$this->Auth->fields['password']]; - $this->Cookie->write($cookieKey, $cookieData, true, '1 Month'); - } - unset($this->data[$this->modelClass]['remember_me']); - } - -/** - * This method allows the user to change his password if the reset token is correct - * - * @param string $token Token - * @return void - */ - private function __resetPassword($token) { - $user = $this->User->checkPasswordToken($token); - if (empty($user)) { - $this->Session->setFlash(__d('users', 'Invalid password reset token, try again.', true)); - $this->redirect(array('action' => 'reset_password')); - } - - if (!empty($this->data)) { - if ($this->User->resetPassword(Set::merge($user, $this->data))) { - $this->Session->setFlash(__d('users', 'Password changed, you can now login with your new password.', true)); - $this->redirect($this->Auth->loginAction); - } - } - - $this->set('token', $token); - } -} diff --git a/locale/deu/LC_MESSAGES/users.mo b/locale/deu/LC_MESSAGES/users.mo deleted file mode 100644 index 67ba5d691..000000000 Binary files a/locale/deu/LC_MESSAGES/users.mo and /dev/null differ diff --git a/locale/deu/LC_MESSAGES/users.po b/locale/deu/LC_MESSAGES/users.po deleted file mode 100644 index 4fb1b4703..000000000 --- a/locale/deu/LC_MESSAGES/users.po +++ /dev/null @@ -1,723 +0,0 @@ -# LANGUAGE translation of the CakePHP Users plugin -# -# Copyright 2010, Cake Development Corporation (http://cakedc.com) -# -# Licensed under The MIT License -# Redistributions of files must retain the above copyright notice. -# -# @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com) -# @license MIT License (http://www.opensource.org/licenses/mit-license.php) -# -msgid "" -msgstr "" -"Project-Id-Version: CakeDC Users Plugin\n" -"POT-Creation-Date: 2010-09-15 15:48+0200\n" -"PO-Revision-Date: 2011-04-28 23:38+0100\n" -"Last-Translator: Florian Krämer \n" -"Language-Team: Cake Development Corporation \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-Poedit-Language: German\n" -"X-Poedit-Country: GERMANY\n" -"X-Poedit-SourceCharset: utf-8\n" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "Ungültiges Detail" - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "Gespeichert" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "%s Details gespeichert" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "" - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "" - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "" - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "" - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "Profil gespeichert." - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "Dein Profil konnte nicht gespeichert werden." - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "Ungültiger Benutzer." - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "Der Benutzer wurde gespeichert" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "Benutzer gespeichert." - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "Ungültiger Benutzer" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "Du bist bereits registriert und angemeldet!" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "Dein Account wurde angelegt. Du solltest in Kürze eine Email erhalten um Deinen Account zu bestötigen. Einmal bestätigt wirst Du Dich anmelden können." - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "Dein Account konnte nicht angelegt werden. Bitte versuch es erneut." - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "%s Du hast Dich erfolgreich angemeldet" - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "%s Du hast Dich erfolgreich abgemeldet" - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "Die angeforderte URL ist nicht mehr gültig" - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "Passwort Reset" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "Dein Passwort wurde resettet" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "Bitte log Dich mit diesem Passwort ein um Dein Passwort zu ändern" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "Dein Passwort wurde an Deine Email-Adresse versendet" - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "Deine Email-Adresse wurde bestätigt!" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "Es trat ein Fehler wärend der Überprüfung Deiner Email-Adresse auf. Bitte prüfe die URL in der Email welche Du zur überprüfung Deiner Email-Adresse benutzen solltest." - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "Die angeforderte URL ist nicht länger gültig" - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "Passwort geändert." - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "Accountverifizierung" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "" - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "Du solltest in Kürze eine Email erhalten mit weiteren Instruktionen" - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "Es wurde kein Benutzer mit Dieser Email gefunden." - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "Ungültiger Passwort-Reset-Token, bitte versuche es erneut." - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "Passwort geändert, Du kannst Dich nun mit Deinem neuen Passwort einloggen." - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "Bitte gib einen Benutzernamen ein" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "Der Benutzername muß alphanumerisch sein" - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "Der Benutzername ist bereits vergeben" - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "Der Benutzername muß mindestens 3 Zeichen lang sein." - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "Bitte gib eine gültige Emailadresse ein." - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "Diese Emailadresse wird bereits verwendet." - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "Das Passwort muß mindestens 8 Zeichen haben." - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "Bitte gib ein Passwort ein." - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "Die Passwort sind nicht gleich, bitte versuche es erneut." - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "Du mußt den Nutzungsbedingungen zustimmen." - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "Die Passwörter sind nicht gleich." - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "Ungültiges Passwort." - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "Ungültiges Datum" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "Diese Email-Adresse existiert wude aber nie bestätigt." - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "Diese Email-Adresse existiert nicht im System." - -#: /models/user.php:406 -msgid "$this->data['" -msgstr "" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "Der Benutzer existiert nicht." - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "Bitte gib Deine Email-Adresse an." - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "Diese Email-Adresse existiert nicht im System" - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "Dein Account ist bereits bestätigt." - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "Dein Account ist deaktiviert." - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "Sorry, aber Du mußt Dich anmelden um diese Seite zu besuchen." - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "Ungültige Email oder Passwort. Bitte versuch es noch mal." - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "Löschen" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "Bist Du Dir sicher das Du %s löschen möchtest?" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "Bearbeiten" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "Benutzer" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "Name" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "Beschreibung" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "Absenden" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "Email" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "Passwort" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "Anmeldung" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "Hallo %s," - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "um Deinen Account zu aktivieren mußt Du diese URL innerhalb von 24 Stunden aufrufen" - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "Eine Anfrage um Dein Passwort zu resetten wurde gesendet. Um Dein Passwort zu ändern klicke auf den unten stehenden Link." - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "Benutzer hinzufügen" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "Benutzer bearbeiten" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "Benutzer" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "Benutzername" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "Suche" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "Benutzer löschen" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "Ändere Dein Kennwort" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "Bitte gib Dein altes Passwort aus Sicherheitsgründen ein und dann gib Dein neues Kenntwort zweimal ein." - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "Altes Passwort" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "Neues Passwort" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "Bestätigen" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "Willkommen" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "Meine Gruppen" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "Lade einen Benutzer ein" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "Mitglieder" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "Benutzer einladen" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "eingeloggt bleiben" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "Account Registrierung" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "Bitte wähle einen Benutzernamen der nicht bereits verwendet wird" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "Es müssen mindestens 3 Zeichen eingegeben werden" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "Der Benutzername darf nur Zahlen und Buchstaben enthalten" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "Bitte wähle einen Benutzernamen" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "E-Mail (wird als Login verwendet)" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "Es mußt eine gültige Email-Adresse sein" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "Ein Account mit dieser Email-Adresse existiert bereits" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "Passwort (bestätigen)" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "Die Passwörter müssen gleich sein" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "" - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "AGB" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "Du must bestätigen das Du die Nutzungsbedingungen gelesen hast" - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "OpenID Identifikation" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "Passwort vergessen?" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "Bitte gib die Email ein welche Du zur Registierung verwendet hast und Du wirst eine Email mit weiteren Instruktionen bekommen." - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "Deine Email" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "Resette Dein Passwort" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "Nach Benutzern suchen" - diff --git a/locale/fre/LC_MESSAGES/users.po b/locale/fre/LC_MESSAGES/users.po deleted file mode 100644 index a3e8843bb..000000000 --- a/locale/fre/LC_MESSAGES/users.po +++ /dev/null @@ -1,710 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: CakePHP Users Plugin\n" -"POT-Creation-Date: 2010-09-15 15:48+0200\n" -"PO-Revision-Date: \n" -"Last-Translator: Pierre MARTIN \n" -"Language-Team: CakeDC \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: French\n" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "Détail invalide." - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "Sauvegardé" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "%s détails sauvegardés" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "Id invalide pour le Détail" - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "Détail supprimé" - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "Le Détail a été sauvegardé" - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "Le Détail ne peut pas être sauvegardé. Merci de réessayer." - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "Détail Invalide" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "Profil sauvegardé" - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "Le profil n'a pas pu être sauvegardé" - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "Utilisateur invalide." - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "L'utilisateur a été sauvegardé" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "Utilisateur sauvegardé" - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "Utilisateur supprimé" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "Utilisateur invalide" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "Vous êtes déjà enregistré et connecté !" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "Votre compte a été créé. Vous devriez recevoir un email sous peu pour authentifier votre compte. Une fois le compte validé vous pourrez vous connecter." - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "Votre compte n'a pas pu être créé. Merci de réessayer." - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "%s vous êtes désormais connecté" - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "%s vous avez été déconnecté avec succès" - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "L'url à laquelle vous essayez d'accéder n'est plus valide" - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "Réinitialisation du Mot de passe" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "Votre mot de passe a été réinitialisé" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "Merci de vous connecter en utilisant ce mot de passe et de le modifier ensuite" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "Votre mot de passe a été envoyé à l'adresse email associée à votre compte" - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "Votre e-mail a été validé !" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "Il y a eu une erreur lors de la validation de votre adresse email. Veuillez vérifier dans vos email que l'URL à utiliser pour valider votre adresse est correcte." - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "L'url à laquelle vous essayez d'accéder n'est plus valide" - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "Mot de passe modifé." - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "Vérification de compte" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "Un email a été envoyé à %s avec les instructions pour mettre à jour le mot de passe." - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "Vous devriez recevoir un email sous peu avec de plus amples instructions" - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "Aucun utilisateur ayant cet adresse email n'a été trouvé." - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "Jeton de réinitialisation de mot de passe invalide. merci de réessayer." - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "Mot de passe changé, vous pouvez désormais vous connecter avec votre nouveau mot de passe." - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "Veuillez saisir un nom d'utilisateur" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "Le nom d'utilisateur doit être alphanumérique" - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "Ce nom d'utilisateur est déjà utilisé." - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "Le nom d'utilisateur doit avoir au moins 3 caractères." - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "Merci de saisir une adresse email valide." - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "Cet email est déjà utilisé." - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "Le mot de passe doit contenir au moins 8 caractères." - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "Veuillez entrer un mot de passe." - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "Les mots de passe ne correspondent pas, merci de réessayer." - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "Vous devez accepter les conditions d'utilisation." - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "Les mots de passe ne sont pas égaux." - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "Mot de passe invalide." - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "Date invalide" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "Cette adresse email existe mais n'a jamais été validée." - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "Cette adresse email n'existe pas dans le système." - -#: /models/user.php:406 -msgid "$this->data['" -msgstr "$this->data['" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "L'utilisateur n'existe pas." - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "Veuillez entrer votre adresse email." - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "Cette adresse email n'existe pas dans le système." - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "Votre compte est déjà authentifié." - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "Votre compte est désactivé." - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "Désole, mais vous devez vous connecter pour accéder à cette page." - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "Combinaison e-mail / mot de passe incorrecte. Veuillez réessayer." - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "testuser you have successfully logged in" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "floriank you have successfully logged out" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "Ajouter un Détail" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "Lister les Détails" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "Lister les Utilisateurs" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "Nouvel Utilisateur" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "Lister les Groupes" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "Nouveau Groupe" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "Modifier le Détail" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "Effacer" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "Etes-vous sûr de vouloir supprimer # %s ?" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "Détails" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "Page %page% sur %pages%, affiche %current% enregistrements sur un total de %count%, commençant à l'enregistrement %start%, s'arrêtant au %end%" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "Actions" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "Voir" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "Modifier" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "précédent" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "suivant" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "Nouveau Détail" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "Détail" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "Id" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "Utilisateur" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "Position" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "Champ" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "Valeur" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "Créé" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "Modifié" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "Supprimer le Détail" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "Groupes liés" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "Id d'Utilisateur" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "Est Public" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "Nom" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "Description" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "Envoyer" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "Email" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "Mot de passe" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "Identifiant" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "Bonjour %s," - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "pour valider votre compte, vous devez visiter l'URL ci-dessous sous 24 heures" - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "Une requête de réinitialisation de mot de passe a été envoyée. Pour modifier votre mot de passe cliquez sur le lien ci-dessous." - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "Ajouter un Utilisateur" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "Modifier un Utilisateur" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "Utilisateurs" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "Filtre" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "Nom d'utilisateur" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "Recherche" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "Supprimer l'utilisateur" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "Modifier votre mot de passe" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "Merci de saisir votre ancien mot de passe pour des raisons de sécurité et ensuite votre nouveau mot de passe deux fois." - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "Ancien mot de passe" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "Nouveau mot de passe" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "Confirmer" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "Bienvenue" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "Récents broadcasts" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "Mes Groupes" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "Créer un nouveau groupe" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "Inviter un utilisateur" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "Requêtes pour joindre" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "Mes propres groupes" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "Membres" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "Inviter l'utilisateur" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "Gérer l'étendue du Broadcast" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "Accéder" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "Addons" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "Groupes dont je suis membre" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "Quitter le Groupe" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "Se rappeler de moi" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "Création de compte" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "Veuillez sélectionner un nom d'utilisateur qui n'est pas déjà utilisé" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "Doit avoir au moins 3 caractères" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "Le nom d'utilisateur ne doit contenir que des nombres et lettres" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "Veuillez choisir un nom d'utilisateur" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "Email (utilisé comme identifiant)" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "Doit être une adresse email valide" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "Un compte avec cette adresse email existe déjà" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "Doit avoir au moins 5 caractères" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "Mot de passe (confirmation)" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "Les mots de passe doivent correspondre" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "J'ai lu et accepte les " - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "Conditions d'utilisation" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "Vous devez vérifier que vous ayez lu les Conditions d'utilisation" - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "Identifiant Openid" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "Mot de passe oublié ?" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "Veuillez saisir l'adresse email utilisée lors de l'inscription afin de recevoir un email contenant les instructions." - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "Votre email" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "Réinitialiser votre mot de passe" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "Rechercher des utilisateurs" - diff --git a/locale/por/LC_MESSAGES/users.po b/locale/por/LC_MESSAGES/users.po deleted file mode 100644 index 92397305c..000000000 --- a/locale/por/LC_MESSAGES/users.po +++ /dev/null @@ -1,722 +0,0 @@ -# LANGUAGE translation of the CakePHP Users plugin -# -# Copyright 2010, Cake Development Corporation (http://cakedc.com) -# -# Licensed under The MIT License -# Redistributions of files must retain the above copyright notice. -# -# @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com) -# @license MIT License (http://www.opensource.org/licenses/mit-license.php) -# -msgid "" -msgstr "" -"Project-Id-Version: CakePHP Users Plugin\n" -"POT-Creation-Date: 2010-09-15 15:48+0200\n" -"PO-Revision-Date: 2010-09-23 19:54-0300\n" -"Last-Translator: Renan Gonçalves \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-Poedit-Language: Portuguese\n" -"X-Poedit-Country: BRAZIL\n" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "Detalhe inválido." - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "Salvo" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "%s detalha salvo" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "ID inválido para Detalhe" - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "Detalhe removido" - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "O Detalhe foi salvo" - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "O Detalhe não pode ser salvo. Por favor, tente novamente." - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "Detalhe inválido" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "Perfil salvo." - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "Não foi possível salvar o perfil." - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "Usuário inválido." - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "O Usuário foi saldo" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "Usuário salvo" - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "Usuário removido" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "Usuário inválido" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "Você já está registrado e logado!" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "Sua conta foi salva. Você logo receberá um e-mail para autenticar sua conta. Uma vez validada você poderá logar." - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "Sua conta não pode ser criada. Por favor, tente novamente." - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "%s você se logou com sucesso" - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "%s você saiu com sucesso" - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "Essa url que você acessou não é mais válida" - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "Resetar senha" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "Sua senha foi resetada" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "Por favor faça o login usando essa senha e então mude-a" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "Sua senha foi enviado para seu e-mail registrado em sua conta" - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "Seu e-mail foi validado!" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "Houve um erro ao tentar validar o seu endereço de e-mail. Por favor, verifique seu e-mail pela URL que você deve usar para verificar o seu endereço de e-mail." - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "A url que você acessou não é mais válida" - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "Senha alterada." - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "Verificação de conta" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "%s foi enviado um email com instruções para redefinir sua senha." - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "Você deverá receber um email com mais instruções em breve" - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "Nenhum usuário com esse e-mail foi encontrado." - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "Chave para redefinição de senha inválida, tente novamente." - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "Senha alterada, você pode agora logar com sua nova senha." - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "Por favor entre o nome de usuário" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "O nome de usuário deve ser alfanumérico" - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "Esse nome de usuário já está em uso." - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "O nome de usuário deve ter no mínimo 3 caracteres." - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "Por favor entre um endereço de e-mail válido." - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "Esse e-mail já está em uso." - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "A senha deve ter no mínimo 8 caracteres." - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "Por favor entre a senha." - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "As senhas devem ser iguais, por favor tente novamente." - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "Você deve aceitar os termos de uso." - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "As senhas não são iguais." - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "Senha inválida." - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "Data inválida" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "Esse endereço de e-mail existe mas nunca foi validado." - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "Esse endereço de e-mail não existe no sistema." - -#: /models/user.php:406 -msgid "$this->data['" -msgstr "$this->data['" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "O usuário não existe." - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "Por favor entre seu endereço de e-mail." - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "O endereço de e-mail não existe no sistema" - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "Sua conta já está autenticada." - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "Sua conta foi desativada." - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "Desculpe, mas você precisa logar para acessar esse local." - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "Combinação de e-mail/senha inválida. Por favor tente novamente." - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "Adicionar Detalhe" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "Listar Detalhes" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "Listar Usuários" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "Novo Usuário" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "Listar Grupos" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "Novo Grupo" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "Editar Detalhe" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "Remover" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "Você tem certeza que deseja remover # %s?" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "Detalhes" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "Página %page% de %pages%, mostrando %current% resultados de um total de %count%, começando em %start%, terminando em %end%" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "Ações" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "Visualizar" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "Editar" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "anterior" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "próximo" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "Novo Detalhe" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "Detalhe" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "Id" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "Usuário" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "Posição" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "Campo" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "Valor" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "Criação" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "Atualização" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "Remover Detalhe" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "Grupos relacionados" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "Id de Usuário" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "Público" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "Nome" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "Descrição" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "Enviar" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "E-mail" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "Senha" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "Login" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "Olá %s," - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "para validar a sua conta, você deve visitar a URL abaixo no prazo de 24 horas" - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "Uma solicitação para redefinir sua senha foi enviada. Para alterar sua senha, clique no link abaixo." - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "Adicionar Usuário" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "Editar Usuário" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "Usuários" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "Filtro" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "Nome de usuário" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "Buscar" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "Remover Usuário" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "Altera sua senha" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "Por favor digite sua senha antiga por razões de segurança e depois sua nova senha duas vezes." - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "Antiga senha" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "Nova senha" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "Confirmar" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "Bem-vindo" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "Atualizações recentes" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "Meus Grupos" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "Criar um novo grupo" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "Convide um usuário" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "Requisite para participar" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "Meus próprios grupos" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "Membros" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "Convite um usuário" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "Gerenciar escopo atualizações" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "Acesso" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "Módulos" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "Grupos em que sou membro" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "Deixar grupo" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "Lembre-me" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "Registro de conta" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "Por favor selecione um nome de usuário que não esteja em uso" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "Tem que ter no mínimo 3 caracteres" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "Nome de usuário devem conter apenas números e letras" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "Por favor escolha o nome de usuário" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "E-mail (usado para logar)" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "Deve ser um endereço de e-mail válido" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "Já existe uma conta com esse e-mail" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "Deve ter pelo menos 5 caracteres" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "Senha (confirmação)" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "As senhas devem ser iguais" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "Eu li e aceito em" - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "Termos de Serviço" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "Você deve verificar se você leu os Termos de Serviços" - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "Identificador OpenId" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "Esqueceu sua senha?" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "Por favor entre com o e-mail usado no registro e você irá receber um e-mail com mais instruções." - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "Seu E-mail" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "Redefinir sua senha" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "Procurar por usuários" - diff --git a/locale/rus/LC_MESSAGES/users.po b/locale/rus/LC_MESSAGES/users.po deleted file mode 100644 index 3c02b5e58..000000000 --- a/locale/rus/LC_MESSAGES/users.po +++ /dev/null @@ -1,711 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: CakePHP Users Plugin\n" -"POT-Creation-Date: 2010-09-16 16:33+0200\n" -"PO-Revision-Date: \n" -"Last-Translator: Evgeny Tomenko \n" -"Language-Team: CakeDC\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Russian\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "Неверные данные." - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "Сохренено" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "%s параметр соханен" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "Неверные данные." - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "Данные удалены." - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "Данные сохранены." - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "Данные не удалось сохранить." - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "Неверные данные" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "Профиль обновлен" - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "Профиль сохранить не удалось." - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "Неуществующий пользователь." - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "Пользователь был" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "Данные пользователя сохранены" - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "Данные о пользователе удалены" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "Неуществующий пользователь" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "Вы уже зарегистрированы и авторизованы!" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "Учетная запись успешно создана. Скоро вы получите e-mail, с помощью которого можно подтвердить аккаунт." - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "Учетную запись создать не удалось. Попробуйте позднее." - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "%s, вы успешно авторизовались." - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "%s, вы успешно покинули систему." - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "Данная ссылка не доступна." - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "Сбросить пароль" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "Ваш пароль был сброшен" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "Пожалуйста авторизуйтесь и измените пароль" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "Ваш пароль был отправлен на ваш почтовый адрес." - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "Ваш e-mail был подтвержен!" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "При попытке подтвердить ваш электронный адрес произошла ошибка. Убедитесь что вы используете правильную ссылку для подтверждения своего почтового адреса." - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "Данный адрес более не доступен." - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "Пароль изменен." - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "Проверка учетной записи" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "%s. Вам была отправлена инструкция для сброса пароля." - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "Вскоре вы получите письмо с дальнейшими инстукциями." - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "С данным почтовым адресом не зарегистрирован ни один пользователь." - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "Неверный код сброса пароля, попробуйте еще раз." - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "Пароль изменен, вы можете авторизоваться с вашим новым паролем." - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "Задайте имя пользователя" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "Имя пользователя должно snm буквенно-цифровым." - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "Данное имя пользователя уже используется." - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "Имя пользователя должено содержать как минимум 3 символа." - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "Введите допустимый почтовый адрес." - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "Данный почтовый адрес занят." - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "Длина пароль должена быть не менее 8 символов." - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "Введите пароль." - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "Пароли не совпадают, попробуйте еще раз." - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "Вы должны подтвердить согласие с правилами пользования сервисом." - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "Пароли не совпадают." - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "Неверный пароль." - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "Неверная дата" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "Почтовый адрес существует, но не подтвержден." - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "Почтовый адрес системе не известен." - -#: /models/user.php:406 -msgid "$this->data['" -msgstr "" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "Пользователь не существует." - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "Пожалуйста, заполните почтовый адрес" - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "Данный почтовый адрес системе не известен." - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "Ваша учетная запись уже подтверждена." - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "Ваша учетная запись заблокирована." - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "Вы должны авторизоваться, чтобы получить доступ к данной странице." - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "Неверный комбинация email и пароля, попробуйте еще раз." - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "testuser вы успешно авторизовались" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "floriank вы успешно завершили сессию" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "Добавить данные" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "Список данных" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "Список пользователей" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "Новый пользователь" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "Список групп" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "Новая группа" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "Редактировать данные" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "Удалить" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "Вы действительно хотите удалить %s?" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "Данные" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "Страница %page% из %pages%, показано %current% записей из %count%, начиная с %start%,и заканчивая %end%" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "Действия" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "Просмотр" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "Редактировать" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "назад" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "вперед" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "Новые данные" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "Данные" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "Id" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "Пользователь" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "Позиция" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "Поле" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "Значение" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "Дата создания" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "Дата изменения" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "Удалить данные" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "Связанные группы" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "Идентификатор пользователя" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "Публичное?" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "Название" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "Описание" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "Сохранить" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "Email" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "Пароль" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "Войти" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "Здравствуйте %s," - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "чтобы подтвердить вашу учетную запись, необходимо посетить слеждующую ссылку в течении 24 часов." - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "Запрос на сброс пароля отправлен. Чтобы сменить пароль воспользуйтесь следующей ссылкой:" - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "Добавить пользователя" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "Редактировать пользователя" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "Пользователи" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "Фильтр" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "Имя пользователя" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "Поиск" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "Удалить пользователя" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "Сменить ваш пароль" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "По правилам безопасности необходимо указать ваш старый пароль, а затем дважды новый пароль." - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "Старый пароль" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "Новый пароль" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "Подтвердить" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "Добро пожаловать" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "Свежие рассылки" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "Мои группы" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "Создать новую группу" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "Пригласить пользователя" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "Запрос на присоединение" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "Мои группы" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "Участники" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "Пригласить пользователя" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "Управление массовыми рассылками" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "Доступ" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "Плагины" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "Группы, в которых я состою" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "Покинуть группу" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "Запомнить меня" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "Регистрация учетной записи" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "Выберете не используемое имя пользователя" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "Как минимум 3 символа" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "Имя пользователя должно содержать только буквы и цифры" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "Выберете имя пользователя" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "Email (используется для входа)" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "Укажите верный адрес email" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "Учетная запись с данным email уже существует" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "Должно быть как минимум 5 символов" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "Пароль (подтверждение)" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "Пароли должны совпадать" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "Я прочитал и согласен с" - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "правилами пользования сервисом" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "Необходимо подтвердить, что вы прочитали правила пользования сервисом." - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "Идентификация Openid" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "Забыли пароль?" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "Укажите email который использовался при регистрации и вы получите дальнейшие инструкции." - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "Ваш почтовый адрес" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "Сбросить ваш пароль" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "Поиск пользователей" - diff --git a/locale/spa/LC_MESSAGES/users.po b/locale/spa/LC_MESSAGES/users.po deleted file mode 100644 index e947da423..000000000 --- a/locale/spa/LC_MESSAGES/users.po +++ /dev/null @@ -1,721 +0,0 @@ -# LANGUAGE translation of the CakePHP Users plugin -# -# Copyright 2010, Cake Development Corporation (http://cakedc.com) -# -# Licensed under The MIT License -# Redistributions of files must retain the above copyright notice. -# -# @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com) -# @license MIT License (http://www.opensource.org/licenses/mit-license.php) -# -msgid "" -msgstr "" -"Project-Id-Version: Users CakePHP plugin\n" -"POT-Creation-Date: 2010-09-15 15:48+0200\n" -"PO-Revision-Date: 2010-09-24 16:38-0400\n" -"Last-Translator: José Lorenzo Rodríguez \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=INTEGER; plural=EXPRESSION;\n" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "Detalle no váido" - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "Guardado" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "%s detalles guardados" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "Identificador no válido para detalle" - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "Detalle eliminado" - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "El detalle ha sido guardado" - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "El detalle no pudo ser guardado, intente de nuevo" - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "Detalle no válido" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "Perfil guardado" - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "No se pudo guardar su perfil" - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "Usuario no válido" - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "El usuario ha sido guardado" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "Usuario guardado" - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "Usuario elimianado" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "Usuario no válido" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "¡Ya estás registrado e iniciado la sesión!" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "Su cuenta ha sido creada. Debería recibir un correo para autenticar su cuenta. Una vez validada podrá iniciar sesión" - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "Su suenta no pudo ser creada, intente de nuevo" - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "%s usted ha iniciado sesión" - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "%s usted ha cerrado la sesión" - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "El url que intentó acceder ya no es válido" - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "Contraseña reiniciada" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "Su contraseña ha sido reiniciada" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "Por favor ingrese usando esta contraseña y luego cámbiela" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "Su contraseña fue enviada a su cuenta de correo registrada" - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "¡Su correo ha sido validado!" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "Ocurrió un error tratando de validar su dirección de correo. Por favor verifique su correo en busca del URL que debe usar para verificar su cuenta." - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "El URL que accedió no es válido" - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "Contraseña modificada" - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "Verificación de cuenta" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "%s ha sido enviado un correo con instrucciones para reiniciar su contraseña" - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "Debe resibir un correo con más instrucciones en breve" - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "Ningún usuario fue encontrado con dicho correo" - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "Contraseña no válida" - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "Contraseña modificada, ahora puede ingresar nuevamente" - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "Por favor ingrese un nombre de usuario" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "El nombre de usuario debe ser alfa-numérico" - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "Este nombre de usuario ya se encuentra en uso" - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "El nombre de usuario debe contener al menos 3 caracteres" - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "Por favor ingrese una dirección de correo electrónico válida" - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "Este correo ya se encuentra en uso" - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "La contraseña debe tener al menos 8 caracteres" - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "Por favor ingrese una contraseña" - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "Las contraseñas no coinciden, por favor intente de nuevo" - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "Debe estar de acuerdo con los términos de uso" - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "Las contraseñas no coinciden" - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "Contraseña no válida" - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "Fecha no válida" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "La dirección de correo existe, pero nunca fue validada" - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "Esta dirección de correo no existe en el sistema" - -# Error? -#: /models/user.php:406 -msgid "$this->data['" -msgstr "" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "El usuario no existe" - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "Por favor ingresa tu dirección de correo" - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "La dirección de correo no existe en el sistema" - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "Yu cuenta ya ha sido autenticada" - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "Tu cuenta está deshabilidata" - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "Lo sentimos, pero debe iniciar sesión para acceder a esta ubicación" - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "Combinación de correo / contraseña no válida. Intente de nuevo" - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "testuser has ingresado exitosamente" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "floriank you have successfully logged out" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "Agregar detalle" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "Listar detalles" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "Listar usuarios" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "Nuevo usuario" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "Listar grupos" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "Nuevo grupo" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "Editar detalle" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "Eliminar" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "¿Está seguro que desea eliminar %s?" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "Detalles" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "Página %page% de %pages%, mostrando %current% registros de %count% en total, empezando en %start% y terminando en %end%" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "Acciones" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "Ver" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "Editar" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "anterior" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "siguiente" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "Nuevo detalle" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "Detalle" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "Id" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "Usuario" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "Posición" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "Campo" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "Valor" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "Creado" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "Modificado" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "Eliminar detalle" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "Grupos relacionado" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "Usuario Id" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "Es Público" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "Nombre" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "Descripción" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "Enviar" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "Correo" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "Contraseña" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "Nombre de Usuario" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "Hola %s," - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "Para validar su cuenta, debe vistar el URL antes de 24 horas" - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "Una solicitud para reiniciar la contraseña fue enviada. Para cambiarla haga click en el siguiente enlace" - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "Agregar usuario" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "Editar Usuario" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "Usuarios" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "Filtrar" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "Nombre de Usuario" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "Buscar" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "Eliminar Usuario" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "Cambiar contraseña" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "Por favor ingrese su antigua contraseña por razones de seguridad y luego ingrese su nueva contraseña dos veces" - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "Contraseña anterior" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "Nueva Contraseña" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "Confirmar" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "Bienvenido" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "Mensajes recientes" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "Mis grupos" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "Crear un nuevo grupo" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "Invitar un usuario" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "Solicitar ingreso" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "Mis grupos" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "Miembros" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "Invitar usuario" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "Gestionar alcance de mensajes" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "Acceso" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "Complementos" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "Grupos en los que el miembro está" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "Dejar grupo" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "Recordarme" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "Registro de cuenta" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "Por favor seleccione un nombre de usuario que no esté en uso" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "Debe ser de al menos 3 caracteres" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "El nombre de usuario debe contener solo letras y números" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "Por favor escoja un nombre de usuario" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "Correo (usuado para ingresar)" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "Debe ser una cuenta de correo válida" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "Una cuenta con dicho correo ya existe" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "Debe ser de al menos 5 caracteres" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "Contraseña (confirmación)" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "Las contraseñas deben coincidir" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "He leido y estoy de acuerdo con" - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "Términos del Servicio" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "Debe verificar que ha leído los Términos de Servicio" - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "Identificador Openid" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "¿Olvidó su contraseña?" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "Por favor ingrese el correo que utilizó para registrarse y recibirá un correo con más instrucciones" - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "Su correo" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "Reiniciar contraseña" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "Buscar usuarios" - diff --git a/locale/users.pot b/locale/users.pot deleted file mode 100644 index c9d074bb4..000000000 --- a/locale/users.pot +++ /dev/null @@ -1,721 +0,0 @@ -# LANGUAGE translation of the CakePHP Users plugin -# -# Copyright 2010, Cake Development Corporation (http://cakedc.com) -# -# Licensed under The MIT License -# Redistributions of files must retain the above copyright notice. -# -# @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com) -# @license MIT License (http://www.opensource.org/licenses/mit-license.php) -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2010-09-15 15:48+0200\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" - -#: /controllers/details_controller.php:57;138 -msgid "Invalid Detail." -msgstr "" - -#: /controllers/details_controller.php:78 -msgid "Saved" -msgstr "" - -#: /controllers/details_controller.php:96 -msgid "%s details saved" -msgstr "" - -#: /controllers/details_controller.php:113;196 -msgid "Invalid id for Detail" -msgstr "" - -#: /controllers/details_controller.php:117;200 -msgid "Detail deleted" -msgstr "" - -#: /controllers/details_controller.php:152;175 -msgid "The Detail has been saved" -msgstr "" - -#: /controllers/details_controller.php:155;178 -msgid "The Detail could not be saved. Please, try again." -msgstr "" - -#: /controllers/details_controller.php:170 -msgid "Invalid Detail" -msgstr "" - -#: /controllers/users_controller.php:157 -msgid "Profile saved." -msgstr "" - -#: /controllers/users_controller.php:159 -msgid "Could not save your profile." -msgstr "" - -#: /controllers/users_controller.php:193 -msgid "Invalid User." -msgstr "" - -#: /controllers/users_controller.php:207 -msgid "The User has been saved" -msgstr "" - -#: /controllers/users_controller.php:223 -msgid "User saved" -msgstr "" - -#: /controllers/users_controller.php:247 -msgid "User deleted" -msgstr "" - -#: /controllers/users_controller.php:249 -#: /models/user.php:703 -msgid "Invalid User" -msgstr "" - -#: /controllers/users_controller.php:273 -msgid "You are already registered and logged in!" -msgstr "" - -#: /controllers/users_controller.php:282 -#: /tests/cases/controllers/users_controller.test.php:194 -msgid "Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login." -msgstr "" - -#: /controllers/users_controller.php:287 -#: /tests/cases/controllers/users_controller.test.php:205 -msgid "Your account could not be created. Please, try again." -msgstr "" - -#: /controllers/users_controller.php:309 -msgid "%s you have successfully logged in" -msgstr "" - -#: /controllers/users_controller.php:383 -msgid "%s you have successfully logged out" -msgstr "" - -#: /controllers/users_controller.php:409 -msgid "There url you accessed is not longer valid" -msgstr "" - -#: /controllers/users_controller.php:432;545 -msgid "Password Reset" -msgstr "" - -#: /controllers/users_controller.php:434 -msgid "Your password has been reset" -msgstr "" - -#: /controllers/users_controller.php:435 -msgid "Please login using this password and change your password" -msgstr "" - -#: /controllers/users_controller.php:438 -msgid "Your password was sent to your registered email account" -msgstr "" - -#: /controllers/users_controller.php:444 -#: /tests/cases/controllers/users_controller.test.php:219 -msgid "Your e-mail has been validated!" -msgstr "" - -#: /controllers/users_controller.php:448 -msgid "There was an error trying to validate your e-mail address. Please check your e-mail for the URL you should use to verify your e-mail address." -msgstr "" - -#: /controllers/users_controller.php:452 -#: /tests/cases/controllers/users_controller.test.php:224 -msgid "The url you accessed is not longer valid" -msgstr "" - -#: /controllers/users_controller.php:467 -msgid "Password changed." -msgstr "" - -#: /controllers/users_controller.php:521 -msgid "Account verification" -msgstr "" - -#: /controllers/users_controller.php:563 -msgid "%s has been sent an email with instruction to reset their password." -msgstr "" - -#: /controllers/users_controller.php:567 -msgid "You should receive an email with further instructions shortly" -msgstr "" - -#: /controllers/users_controller.php:571 -msgid "No user was found with that email." -msgstr "" - -#: /controllers/users_controller.php:588 -msgid "Invalid password reset token, try again." -msgstr "" - -#: /controllers/users_controller.php:594 -msgid "Password changed, you can now login with your new password." -msgstr "" - -#: /models/user.php:102 -msgid "Please enter a username" -msgstr "" - -#: /models/user.php:105 -msgid "The username must be alphanumeric" -msgstr "" - -#: /models/user.php:108 -msgid "This username is already in use." -msgstr "" - -#: /models/user.php:111 -msgid "The username must have at least 3 characters." -msgstr "" - -#: /models/user.php:116 -msgid "Please enter a valid email address." -msgstr "" - -#: /models/user.php:119 -msgid "This email is already in use." -msgstr "" - -#: /models/user.php:123 -msgid "The password must have at least 8 characters." -msgstr "" - -#: /models/user.php:126 -msgid "Please enter a password." -msgstr "" - -#: /models/user.php:129 -msgid "The passwords are not equal, please try again." -msgstr "" - -#: /models/user.php:132 -msgid "You must agree to the terms of use." -msgstr "" - -#: /models/user.php:137;357 -msgid "The passwords are not equal." -msgstr "" - -#: /models/user.php:139 -msgid "Invalid password." -msgstr "" - -#: /models/user.php:150 -msgid "Invalid date" -msgstr "" - -#: /models/user.php:315 -msgid "This Email Address exists but was never validated." -msgstr "" - -#: /models/user.php:317 -msgid "This Email Address does not exist in the system." -msgstr "" - -#: /models/user.php:406 -msgid "$this->data['" -msgstr "" - -#: /models/user.php:460 -msgid "The user does not exist." -msgstr "" - -#: /models/user.php:505 -msgid "Please enter your email address." -msgstr "" - -#: /models/user.php:515 -msgid "The email address does not exist in the system" -msgstr "" - -#: /models/user.php:520 -msgid "Your account is already authenticaed." -msgstr "" - -#: /models/user.php:525 -msgid "Your account is disabled." -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:34 -msgid "Sorry, but you need to login to access this location." -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:35;174 -msgid "Invalid e-mail / password combination. Please try again" -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:168 -msgid "testuser you have successfully logged in" -msgstr "" - -#: /tests/cases/controllers/users_controller.test.php:237 -msgid "floriank you have successfully logged out" -msgstr "" - -#: /views/details/add.ctp:4 -#: /views/details/admin_add.ctp:4 -msgid "Add Detail" -msgstr "" - -#: /views/details/add.ctp:17 -#: /views/details/admin_add.ctp:17 -#: /views/details/admin_edit.ctp:19 -#: /views/details/admin_view.ctp:45 -#: /views/details/view.ctp:45 -msgid "List Details" -msgstr "" - -#: /views/details/add.ctp:18 -#: /views/details/admin_add.ctp:18 -#: /views/details/admin_edit.ctp:20 -#: /views/details/admin_index.ctp:67 -#: /views/details/admin_view.ctp:47 -#: /views/details/view.ctp:47 -#: /views/users/add.ctp:15 -#: /views/users/admin_add.ctp:13 -#: /views/users/admin_edit.ctp:15 -#: /views/users/admin_view.ctp:25 -msgid "List Users" -msgstr "" - -#: /views/details/add.ctp:19 -#: /views/details/admin_add.ctp:19 -#: /views/details/admin_edit.ctp:21 -#: /views/details/admin_index.ctp:68 -#: /views/details/admin_view.ctp:48 -#: /views/details/view.ctp:48 -#: /views/users/admin_view.ctp:26 -#: /views/users/index.ctp:46 -msgid "New User" -msgstr "" - -#: /views/details/add.ctp:20 -#: /views/details/admin_add.ctp:20 -#: /views/details/admin_edit.ctp:22 -#: /views/details/admin_index.ctp:69 -#: /views/details/admin_view.ctp:49 -#: /views/details/view.ctp:49 -msgid "List Groups" -msgstr "" - -#: /views/details/add.ctp:21 -#: /views/details/admin_add.ctp:21 -#: /views/details/admin_edit.ctp:23 -#: /views/details/admin_index.ctp:70 -#: /views/details/admin_view.ctp:50;95 -#: /views/details/view.ctp:50;95 -msgid "New Group" -msgstr "" - -#: /views/details/admin_edit.ctp:4 -#: /views/details/admin_view.ctp:43 -#: /views/details/edit.ctp:5 -#: /views/details/view.ctp:43 -msgid "Edit Detail" -msgstr "" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:86 -#: /views/details/view.ctp:86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/groups.ctp:38 -#: /views/users/index.ctp:33 -msgid "Delete" -msgstr "" - -#: /views/details/admin_edit.ctp:18 -#: /views/details/admin_index.ctp:53 -#: /views/details/admin_view.ctp:44;86 -#: /views/details/view.ctp:44;86 -#: /views/users/admin_edit.ctp:14 -#: /views/users/admin_index.ctp:47 -#: /views/users/admin_view.ctp:24 -#: /views/users/index.ctp:33 -msgid "Are you sure you want to delete # %s?" -msgstr "" - -#: /views/details/admin_index.ctp:2 -#: /views/users/register.ctp:4 -msgid "Details" -msgstr "" - -#: /views/details/admin_index.ctp:6 -#: /views/users/index.ctp:6 -msgid "Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%" -msgstr "" - -#: /views/details/admin_index.ctp:18 -#: /views/details/admin_view.ctp:65 -#: /views/details/view.ctp:65 -#: /views/users/admin_index.ctp:21 -#: /views/users/groups.ctp:15;50 -#: /views/users/index.ctp:13 -msgid "Actions" -msgstr "" - -#: /views/details/admin_index.ctp:51 -#: /views/details/admin_view.ctp:84 -#: /views/details/view.ctp:84 -#: /views/users/admin_index.ctp:45 -#: /views/users/index.ctp:31 -msgid "View" -msgstr "" - -#: /views/details/admin_index.ctp:52 -#: /views/details/admin_view.ctp:85 -#: /views/details/view.ctp:85 -#: /views/users/admin_index.ctp:46 -#: /views/users/groups.ctp:33 -#: /views/users/index.ctp:32 -msgid "Edit" -msgstr "" - -#: /views/details/admin_index.ctp:60 -#: /views/users/index.ctp:40 -msgid "previous" -msgstr "" - -#: /views/details/admin_index.ctp:62 -#: /views/users/index.ctp:42 -msgid "next" -msgstr "" - -#: /views/details/admin_index.ctp:66 -#: /views/details/admin_view.ctp:46 -#: /views/details/view.ctp:46 -msgid "New Detail" -msgstr "" - -#: /views/details/admin_view.ctp:2 -#: /views/details/view.ctp:2 -msgid "Detail" -msgstr "" - -#: /views/details/admin_view.ctp:4;58 -#: /views/details/view.ctp:4;58 -msgid "Id" -msgstr "" - -#: /views/details/admin_view.ctp:9 -#: /views/details/view.ctp:9 -#: /views/users/admin_view.ctp:2 -#: /views/users/view.ctp:2 -msgid "User" -msgstr "" - -#: /views/details/admin_view.ctp:14 -#: /views/details/view.ctp:14 -msgid "Position" -msgstr "" - -#: /views/details/admin_view.ctp:19 -#: /views/details/view.ctp:19 -msgid "Field" -msgstr "" - -#: /views/details/admin_view.ctp:24 -#: /views/details/view.ctp:24 -msgid "Value" -msgstr "" - -#: /views/details/admin_view.ctp:29;63 -#: /views/details/view.ctp:29;63 -#: /views/users/admin_view.ctp:9 -#: /views/users/view.ctp:9 -msgid "Created" -msgstr "" - -#: /views/details/admin_view.ctp:34;64 -#: /views/details/view.ctp:34;64 -#: /views/users/admin_view.ctp:14 -msgid "Modified" -msgstr "" - -#: /views/details/admin_view.ctp:44 -#: /views/details/view.ctp:44 -msgid "Delete Detail" -msgstr "" - -#: /views/details/admin_view.ctp:54 -#: /views/details/view.ctp:54 -msgid "Related Groups" -msgstr "" - -#: /views/details/admin_view.ctp:59 -#: /views/details/view.ctp:59 -msgid "User Id" -msgstr "" - -#: /views/details/admin_view.ctp:60 -#: /views/details/view.ctp:60 -msgid "Is Public" -msgstr "" - -#: /views/details/admin_view.ctp:61 -#: /views/details/view.ctp:61 -#: /views/users/groups.ctp:13;47 -#: /views/users/search.ctp:9 -msgid "Name" -msgstr "" - -#: /views/details/admin_view.ctp:62 -#: /views/details/view.ctp:62 -#: /views/users/groups.ctp:48 -msgid "Description" -msgstr "" - -#: /views/details/index.ctp:17 -#: /views/users/change_password.ctp:16 -#: /views/users/login.ctp:13 -#: /views/users/register.ctp:33;89 -#: /views/users/request_password_change.ctp:12 -#: /views/users/reset_password.ctp:14 -msgid "Submit" -msgstr "" - -#: /views/elements/login.ctp:11 -#: /views/users/admin_index.ctp:10 -#: /views/users/login.ctp:8 -#: /views/users/register.ctp:71;78 -#: /views/users/search.ctp:7 -msgid "Email" -msgstr "" - -#: /views/elements/login.ctp:13 -#: /views/users/login.ctp:10 -#: /views/users/register.ctp:19 -msgid "Password" -msgstr "" - -#: /views/elements/login.ctp:15 -#: /views/users/login.ctp:1;3 -msgid "Login" -msgstr "" - -#: /views/elements/email/text/account_verification.ctp:2 -msgid "Hello %s," -msgstr "" - -#: /views/elements/email/text/account_verification.ctp:4 -msgid "to validate your account, you must visit the URL below within 24 hours" -msgstr "" - -#: /views/elements/email/text/password_reset_request.ctp:2 -msgid "A request to reset your password was sent. To change your password click the link below." -msgstr "" - -#: /views/users/add.ctp:4 -#: /views/users/admin_add.ctp:4 -msgid "Add User" -msgstr "" - -#: /views/users/admin_edit.ctp:4 -#: /views/users/admin_view.ctp:23 -#: /views/users/edit.ctp:3 -msgid "Edit User" -msgstr "" - -#: /views/users/admin_index.ctp:2 -#: /views/users/index.ctp:2 -msgid "Users" -msgstr "" - -#: /views/users/admin_index.ctp:4 -msgid "Filter" -msgstr "" - -#: /views/users/admin_index.ctp:8 -#: /views/users/admin_view.ctp:4 -#: /views/users/register.ctp:57;63 -#: /views/users/search.ctp:5 -#: /views/users/view.ctp:4 -msgid "Username" -msgstr "" - -#: /views/users/admin_index.ctp:11 -#: /views/users/search.ctp:10 -msgid "Search" -msgstr "" - -#: /views/users/admin_view.ctp:24 -msgid "Delete User" -msgstr "" - -#: /views/users/change_password.ctp:1 -msgid "Change your password" -msgstr "" - -#: /views/users/change_password.ctp:3 -msgid "Please enter your old password because of security reasons and then your new password twice." -msgstr "" - -#: /views/users/change_password.ctp:8 -msgid "Old Password" -msgstr "" - -#: /views/users/change_password.ctp:11 -#: /views/users/reset_password.ctp:9 -msgid "New Password" -msgstr "" - -#: /views/users/change_password.ctp:14 -#: /views/users/reset_password.ctp:12 -msgid "Confirm" -msgstr "" - -#: /views/users/dashboard.ctp:2 -msgid "Welcome" -msgstr "" - -#: /views/users/dashboard.ctp:3 -msgid "Recent broadcasts" -msgstr "" - -#: /views/users/groups.ctp:2 -msgid "My Groups" -msgstr "" - -#: /views/users/groups.ctp:5 -msgid "Create a new group" -msgstr "" - -#: /views/users/groups.ctp:6 -msgid "Invite a user" -msgstr "" - -#: /views/users/groups.ctp:7 -msgid "Requests to join" -msgstr "" - -#: /views/users/groups.ctp:10 -msgid "My own groups" -msgstr "" - -#: /views/users/groups.ctp:14;49 -msgid "Members" -msgstr "" - -#: /views/users/groups.ctp:34 -msgid "Invite user" -msgstr "" - -#: /views/users/groups.ctp:35 -msgid "Manage Broadcast Scope" -msgstr "" - -#: /views/users/groups.ctp:36 -msgid "Access" -msgstr "" - -#: /views/users/groups.ctp:37 -msgid "Addons" -msgstr "" - -#: /views/users/groups.ctp:44 -msgid "Groups im a member in" -msgstr "" - -#: /views/users/groups.ctp:68 -msgid "Leave group" -msgstr "" - -#: /views/users/login.ctp:11 -msgid "Remember Me" -msgstr "" - -#: /views/users/register.ctp:2 -msgid "Account registration" -msgstr "" - -#: /views/users/register.ctp:10 -msgid "Please select a username that is not already in use" -msgstr "" - -#: /views/users/register.ctp:11 -msgid "Must be at least 3 characters" -msgstr "" - -#: /views/users/register.ctp:12 -msgid "Username must contain numbers and letters only" -msgstr "" - -#: /views/users/register.ctp:13 -msgid "Please choose username" -msgstr "" - -#: /views/users/register.ctp:15 -msgid "E-mail (used as login)" -msgstr "" - -#: /views/users/register.ctp:16 -msgid "Must be a valid email address" -msgstr "" - -#: /views/users/register.ctp:17 -msgid "An account with that email already exists" -msgstr "" - -#: /views/users/register.ctp:21 -msgid "Must be at least 5 characters long" -msgstr "" - -#: /views/users/register.ctp:23 -msgid "Password (confirm)" -msgstr "" - -#: /views/users/register.ctp:25 -msgid "Passwords must match" -msgstr "" - -#: /views/users/register.ctp:29;85 -msgid "I have read and agreed to " -msgstr "" - -#: /views/users/register.ctp:29;85 -msgid "Terms of Service" -msgstr "" - -#: /views/users/register.ctp:30;86 -msgid "You must verify you have read the Terms of Service" -msgstr "" - -#: /views/users/register.ctp:46 -msgid "Openid Identifier" -msgstr "" - -#: /views/users/request_password_change.ctp:1 -msgid "Forgot your password?" -msgstr "" - -#: /views/users/request_password_change.ctp:3 -msgid "Please enter the email you used for registration and you'll get an email with further instructions." -msgstr "" - -#: /views/users/request_password_change.ctp:11 -msgid "Your Email" -msgstr "" - -#: /views/users/reset_password.ctp:1 -msgid "Reset your password" -msgstr "" - -#: /views/users/search.ctp:1 -msgid "Search for users" -msgstr "" - diff --git a/models/detail.php b/models/detail.php deleted file mode 100644 index d0a3273cf..000000000 --- a/models/detail.php +++ /dev/null @@ -1,241 +0,0 @@ -belongsTo['User'] = array( - 'className' => $userClass, - 'foreignKey' => 'user_id'); - parent::__construct($id, $table, $ds); - } - -/** - * Create the default fields for a user - * - * @param string $userId User ID - * @return void - */ - public function createDefaults($userId) { - $entries = array( - array( - 'field' => 'user.firstname', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.middlename', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.lastname', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.abbr-country-name', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.abbr-region', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.country-name', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.location', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.postal-code', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.region', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string'), - array( - 'field' => 'user.timeoffset', - 'value' => '', - 'input' => 'text', - 'data_type' => 'string')); - - $i = 0; - $data = array(); - foreach ($entries as $entry) { - $data[$this->alias] = $entry; - $data[$this->alias]['user_id'] = $userId; - $data[$this->alias]['position'] = $i++; - $this->create(); - $this->save($data); - } - } - -/** - * Returns details for named section - * - * @var string $userId User ID - * @var string $section Section name - * @return array - */ - public function getSection($userId = null, $section = null) { - $conditions = array( - "{$this->alias}.user_id" => $userId); - - if (!is_null($section)) { - $conditions["{$this->alias}.field LIKE"] = $section . '.%'; - } - - $results = $this->find('all', array( - 'recursive' => -1, - 'conditions' => $conditions, - 'fields' => array("{$this->alias}.field", "{$this->alias}.value"))); - - if (!empty($results)) { - foreach($results as $result) { - list($prefix, $field) = explode('.', $result[$this->alias]['field']); - $details[$prefix][$field] = $result[$this->alias]['value']; - } - $results = $details; - } - return $results; - } - -/** - * Save details for named section - * - * @var string $userId User ID - * @var array $data Data - * @var string $section Section name - * @return boolean True on successful validation and saving of the virtual fields - */ - public function saveSection($userId = null, $data = null, $section = null) { - if (!empty($this->sectionSchema[$section])) { - $tmpSchema = $this->_schema; - $this->_schema = $this->sectionSchema[$section]; - } - - if (!empty($this->sectionValidation[$section])) { - $tmpValidate = $this->validate; - $data = $this->set($data); - $this->validate = $this->sectionValidation[$section]; - if (!$this->validates()) { - return false; - } - $this->validate = $tmpValidate; - } - - if (isset($tmpSchema)) { - $this->_schema = $tmpSchema; - } - - if (!empty($data) && is_array($data)) { - foreach($data as $model => $details) { - if ($model == $this->alias) { - // Save the details - foreach($details as $key => $value) { - // Quickfix for date inputs - TODO Try to use $this->deconstruct()? - if (is_array($value) && array_keys($value) == array('month', 'day', 'year')) { - $value = $value['year'] . '-' . $value['month'] . '-' . $value['day']; - } - $newDetail = array(); - $field = $section . '.' . $key; - $detail = $this->find('first', array( - 'recursive' => -1, - 'conditions' => array( - 'user_id' => $userId, - 'field' => $field), - 'fields' => array('id', 'field'))); - if (empty($detail)) { - $this->create(); - $newDetail[$model]['user_id'] = $userId; - } else { - $newDetail[$model]['id'] = $detail['Detail']['id']; - } - - $newDetail[$model]['field'] = $field; - $newDetail[$model]['value'] = $value; - $newDetail[$model]['input'] = ''; - $newDetail[$model]['data_type'] = ''; - $newDetail[$model]['label'] = ''; - $this->save($newDetail, false); - } - } elseif (isset($this->{$model})) { - // Save other model data - $toSave[$model] = $details; - if (!empty($userId)) { - if ($model == 'User') { - $toSave[$model]['id'] = $userId; - } elseif ($this->{$model}->hasField('user_id')) { - $toSave[$model]['user_id'] = $userId; - } - } - $this->{$model}->save($toSave, false); - } - } - } - return true; - } -} diff --git a/models/user.php b/models/user.php deleted file mode 100644 index 5642a7a91..000000000 --- a/models/user.php +++ /dev/null @@ -1,699 +0,0 @@ - array( - 'label' => 'username', - 'method' => 'multibyteSlug')); - -/** - * Additional Find methods - * - * @var array - */ - public $_findMethods = array('search' => true); - -/** - * @todo comment me - * - * @var array - */ - public $filterArgs = array( - array('name' => 'username', 'type' => 'string'), - array('name' => 'email', 'type' => 'string')); - -/** - * Displayfield - * - * @var string $displayField - */ - public $displayField = 'username'; - -/** - * hasMany associations - * - * @var array - */ - public $hasMany = array( - 'Detail' => array( - 'className' => 'Users.Detail', - 'foreign_key' => 'user_id')); - -/** - * Validation parameters - * - * @var array - */ - public $validate = array(); - -/** - * Constructor - * - * @param string $id ID - * @param string $table Table - * @param string $ds Datasource - */ - public function __construct($id = false, $table = null, $ds = null) { - parent::__construct($id, $table, $ds); - $this->validate = array( - 'username' => array( - 'required' => array( - 'rule' => array('notEmpty'), - 'required' => true, 'allowEmpty' => false, - 'message' => __d('users', 'Please enter a username', true)), - 'alpha' => array( - 'rule'=>array('alphaNumeric'), - 'message' => __d('users', 'The username must be alphanumeric', true)), - 'unique_username' => array( - 'rule'=>array('isUnique','username'), - 'message' => __d('users', 'This username is already in use.', true)), - 'username_min' => array( - 'rule' => array('minLength', '3'), - 'message' => __d('users', 'The username must have at least 3 characters.', true))), - 'email' => array( - 'isValid' => array( - 'rule' => 'email', - 'required' => true, - 'message' => __d('users', 'Please enter a valid email address.', true)), - 'isUnique' => array( - 'rule' => array('isUnique','email'), - 'message' => __d('users', 'This email is already in use.', true))), - 'passwd' => array( - 'to_short' => array( - 'rule' => array('minLength', '6'), - 'message' => __d('users', 'The password must have at least 6 characters.', true)), - 'required' => array( - 'rule' => 'notEmpty', - 'message' => __d('users', 'Please enter a password.', true))), - 'temppassword' => array( - 'rule' => 'confirmPassword', - 'message' => __d('users', 'The passwords are not equal, please try again.', true)), - 'tos' => array( - 'rule' => array('custom','[1]'), - 'message' => __d('users', 'You must agree to the terms of use.', true))); - - $this->validatePasswordChange = array( - 'new_password' => $this->validate['passwd'], - 'confirm_password' => array( - 'required' => array('rule' => array('compareFields', 'new_password', 'confirm_password'), 'required' => true, 'message' => __d('users', 'The passwords are not equal.', true))), - 'old_password' => array( - 'to_short' => array('rule' => 'validateOldPassword', 'required' => true, 'message' => __d('users', 'Invalid password.', true)))); - } - -/** - * Sets some defaults for the detail model - * - * @return void - */ - public function setupDetail() { - $this->Detail->sectionSchema[$this->alias] = array( - 'birthday' => array( - 'type' => 'date', - 'null' => null, - 'default' => null, - 'length' => null)); - - $this->Detail->sectionValidation[$this->alias] = array( - 'birthday' => array( - 'validDate' => array( - 'rule' => array('date'), 'allowEmpty' => true, 'message' => __d('users', 'Invalid date', true)))); - } - -/** - * After save callback - * - * @param boolean $created - * @return void - */ - public function afterSave($created) { - if ($created) { - if (!empty($this->data[$this->alias]['slug'])) { - if ($this->hasField('url')) { - $this->saveField('url', '/user/' . $this->data[$this->alias]['slug'], false); - } - } - } - } - -/** - * afterFind callback - * - * @param array $results Result data - * @param mixed $primary Primary query - * @return array - */ - public function afterFind($results, $primary = false) { - foreach ($results as &$row) { - if (isset($row['Detail']) && (is_array($row))) { - $row['Detail'] = $this->Detail->getSection($row[$this->alias]['id'], $this->alias); - } - } - return $results; - } - -/** - * Custom validation method to ensure that the two entered passwords match - * - * @param string $password Password - * @return boolean Success - */ - public function confirmPassword($password = null) { - if ((isset($this->data[$this->alias]['passwd']) && isset($password['temppassword'])) - && !empty($password['temppassword']) - && ($this->data[$this->alias]['passwd'] === $password['temppassword'])) { - return true; - } - return false; - } - -/** - * Compares the email confirmation - * - * @param array $email Email data - * @return boolean - */ - public function confirmEmail($email = null) { - if ((isset($this->data[$this->alias]['email']) && isset($email['confirm_email'])) - && !empty($email['confirm_email']) - && (strtolower($this->data[$this->alias]['email']) === strtolower($email['confirm_email']))) { - return true; - } - return false; - } - -/** - * Validates the user token - * - * @param string $token Token - * @param boolean $reset Reset boolean - * @param boolean $now time() value - * @return mixed false or user data - */ - public function validateToken($token = null, $reset = false, $now = null) { - if (!$now) { - $now = time(); - } - - $this->recursive = -1; - $data = false; - $match = $this->find(array( - $this->alias . '.email_token' => $token), - 'id, email, email_token_expires, role'); - - if (!empty($match)){ - $expires = strtotime($match[$this->alias]['email_token_expires']); - if ($expires > $now) { - $data[$this->alias]['id'] = $match[$this->alias]['id']; - $data[$this->alias]['email'] = $match[$this->alias]['email']; - $data[$this->alias]['email_authenticated'] = '1'; - $data[$this->alias]['role'] = $match[$this->alias]['role']; - - if ($reset === true) { - $data[$this->alias]['passwd'] = $this->generatePassword(); - $data[$this->alias]['password_token'] = null; - } - - $data[$this->alias]['email_token'] = null; - $data[$this->alias]['email_token_expires'] = null; - } - } - return $data; - } - -/** - * Updates the last activity field of a user - * - * @param string $user User ID - * @return boolean True on success - */ - public function updateLastActivity($userId = null) { - if (!empty($userId)) { - $this->id = $userId; - } - if ($this->exists()) { - return $this->saveField('last_activity', date('Y-m-d H:i:s', time())); - } - return false; - } - -/** - * Checks if an email is in the system, validated and if the user is active so that the user is allowed to reste his password - * - * @param array $postData post data from controller - * @return mixed False or user data as array on success - */ - public function passwordReset($postData = array()) { - $user = $this->find('first', array( - 'conditions' => array( - $this->alias . '.active' => 1, - $this->alias . '.email' => $postData[$this->alias]['email']))); - - if (!empty($user) && $user[$this->alias]['email_authenticated'] == 1) { - $sixtyMins = time() + 43000; - $token = $this->generateToken(); - $user[$this->alias]['password_token'] = $token; - $user[$this->alias]['email_token_expires'] = date('Y-m-d H:i:s', $sixtyMins); - $user = $this->save($user, false); - return $user; - } elseif (!empty($user) && $user[$this->alias]['email_authenticated'] == 0){ - $this->invalidate('email', __d('users', 'This Email Address exists but was never validated.', true)); - } else { - $this->invalidate('email', __d('users', 'This Email Address does not exist in the system.', true)); - } - return false; - } - -/** - * Checks the token for a password change - * - * @param string $token Token - * @return mixed False or user data as array - */ - public function checkPasswordToken($token = null) { - $user = $this->find('first', array( - 'contain' => array(), - 'conditions' => array( - $this->alias . '.active' => 1, - $this->alias . '.password_token' => $token, - $this->alias . '.email_token_expires >=' => date('Y-m-d H:i:s')))); - if (empty($user)) { - return false; - } - return $user; - } - -/** - * Resets the password - * - * @param array $postData Post data from controller - * @return boolean True on success - */ - public function resetPassword($postData = array()) { - $result = false; - $tmp = $this->validate; - $this->validate = array( - 'new_password' => $this->validate['passwd'], - 'confirm_password' => array( - 'required' => array( - 'rule' => array('compareFields', 'new_password', 'confirm_password'), - 'message' => __d('users', 'The passwords are not equal.', true)))); - - $this->set($postData); - if ($this->validates()) { - App::import('Core', 'Security'); - $this->data[$this->alias]['passwd'] = Security::hash($this->data[$this->alias]['new_password'], null, true); - $this->data[$this->alias]['password_token'] = null; - $result = $this->save($this->data, false); - } - $this->validate = $tmp; - return $result; - } - -/** - * Changes the password for a user - * - * @param array $postData Post data from controller - * @return boolean True on success - */ - public function changePassword($postData = array()) { - $this->set($postData); - //$tmp = $this->validate; - $this->validate = $this->validatePasswordChange; - - if ($this->validates()) { - App::import('Core', 'Security'); - $this->data[$this->alias]['passwd'] = Security::hash($this->data[$this->alias]['new_password'], null, true); - $this->save($postData, array( - 'validate' => false, - 'callbacks' => false)); - //$this->validate = $tmp; - return true; - } - - //$this->validate = $tmp; - return false; - } - -/** - * Validation method to check the old password - * - * @param array $password - * @return boolean True on success - */ - public function validateOldPassword($password) { - if (!isset($this->data[$this->alias]['id']) || empty($this->data[$this->alias]['id'])) { - if (Configure::read('debug') > 0) { - throw new OutOfBoundsException(__d('users', '$this->data[\'' . $this->alias . '\'][\'id\'] has to be set and not empty', true)); - } - } - - $passwd = $this->field('passwd', array($this->alias . '.id' => $this->data[$this->alias]['id'])); - App::import('Core', 'Security'); - if ($passwd === Security::hash($password['old_password'], null, true)) { - return true; - } - return false; - } - -/** - * Validation method to compare two fields - * - * @param mixed $field1 Array or string, if array the first key is used as fieldname - * @param string $field2 Second fieldname - * @return boolean True on success - */ - public function compareFields($field1, $field2) { - if (is_array($field1)) { - $field1 = key($field1); - } - if (isset($this->data[$this->alias][$field1]) && isset($this->data[$this->alias][$field2]) && - $this->data[$this->alias][$field1] == $this->data[$this->alias][$field2]) { - return true; - } - return false; - } - -/** - * Returns all data about a user - * - * @param string $slug user slug - * @return array - */ - public function view($slug = null) { - $user = $this->find('first', array( - 'contain' => array( - //'Tag', - 'Detail'), - 'conditions' => array( - $this->alias . '.slug' => $slug, - 'OR' => array( - 'AND' => - array($this->alias . '.active' => 1, $this->alias . '.email_authenticated' => 1), - //array($this->alias . '.active' => 1, $this->alias . '.account_type' => 'remote') - )))); - - if (empty($user)) { - throw new Exception(__d('users', 'The user does not exist.', true)); - } - - return $user; - } - -/** - * Registers a new user - * - * @param array $postData Post data from controller - * @param boolean $useEmailVerification If set to true a token will be generated - * @return mixed - */ - public function register($postData = array(), $useEmailVerification = true) { - $postData = $this->_beforeRegistration($postData, $useEmailVerification); - - $this->_removeExpiredRegistrations(); - - $this->set($postData); - if ($this->validates()) { - App::import('Core', 'Security'); - $postData[$this->alias]['passwd'] = Security::hash($postData[$this->alias]['passwd'], 'sha1', true); - $this->create(); - return $this->save($postData, false); - } - - return false; - } - -/** - * Resends the verification if the user is not already validated or invalid - * - * @param array $postData Post data from controller - * @return mixed False or user data array on success - */ - public function resendVerification($postData = array()) { - if (!isset($postData[$this->alias]['email']) || empty($postData[$this->alias]['email'])) { - $this->invalidate('email', __d('users', 'Please enter your email address.', true)); - return false; - } - - $user = $this->find('first', array( - 'contain' => array(), - 'conditions' => array( - $this->alias . '.email' => $postData[$this->alias]['email']))); - - if (empty($user)) { - $this->invalidate('email', __d('users', 'The email address does not exist in the system', true)); - return false; - } - - if ($user[$this->alias]['email_authenticated'] == 1) { - $this->invalidate('email', __d('users', 'Your account is already authenticaed.', true)); - return false; - } - - if ($user[$this->alias]['active'] == 0) { - $this->invalidate('email', __d('users', 'Your account is disabled.', true)); - return false; - } - - $user[$this->alias]['email_token'] = $this->generateToken(); - $user[$this->alias]['email_token_expires'] = date('Y-m-d H:i:s', time() + 86400); - - return $this->save($user, false); - } - -/** - * Generates a password - * - * @param int $length Password length - * @return string - */ - public function generatePassword($length = 10) { - srand((double)microtime() * 1000000); - $password = ''; - $vowels = array("a", "e", "i", "o", "u"); - $cons = array("b", "c", "d", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "u", "v", "w", "tr", - "cr", "br", "fr", "th", "dr", "ch", "ph", "wr", "st", "sp", "sw", "pr", "sl", "cl"); - for ($i = 0; $i < $length; $i++) { - $password .= $cons[mt_rand(0, 31)] . $vowels[mt_rand(0, 4)]; - } - return substr($password, 0, $length); - } - -/** - * Generate token used by the user registration system - * - * @param int $length Token Length - * @return string - */ - public function generateToken($length = 10) { - $possible = '0123456789abcdefghijklmnopqrstuvwxyz'; - $token = ""; - $i = 0; - - while ($i < $length) { - $char = substr($possible, mt_rand(0, strlen($possible) - 1), 1); - if (!stristr($token, $char)) { - $token .= $char; - $i++; - } - } - return $token; - } - -/** - * Optional data manipulation before the registration record is saved - * - * @param array post data array - * @param boolean Use email generation, create token, default true - * @return array - */ - protected function _beforeRegistration($postData = array(), $useEmailVerification = true) { - if ($useEmailVerification == true) { - $postData[$this->alias]['email_token'] = $this->generateToken(); - $postData[$this->alias]['email_token_expires'] = date('Y-m-d H:i:s', time() + 86400); - } else { - $postData[$this->alias]['email_authenticated'] = 1; - } - $postData[$this->alias]['active'] = 1; - return $postData; - } - -/** - * Returns the search data - * - * @param string $state Find State - * @param string $query Query options - * @param string $results Result data - * @return array - */ - protected function _findSearch($state, $query, $results = array()) { - if ($state == 'before') { - $this->Behaviors->attach('Containable', array('autoFields' => false)); - $results = $query; - if (!empty($query['by'])) { - $by = $query['by']; - } - - if (empty($query['search'])) { - $query['search'] = ''; - } - - $db =& ConnectionManager::getDataSource($this->useDbConfig); - $by = $query['by']; - $search = $query['search']; - $byQuoted = $db->value($search); - $like = '%' . $query['search'] . '%'; - - switch ($by) { - case 'username': - $results['conditions'] = Set::merge( - $query['conditions'], - array($this->alias . '.username LIKE' => $like)); - break; - case 'email': - $results['conditions'] = Set::merge( - $query['conditions'], - array($this->alias . '.email LIKE' => $like)); - break; - case 'any': - $results['conditions'] = Set::merge( - $query['conditions'], - array('OR' => array( - array($this->alias . '.username LIKE' => $like), - array($this->alias . '.email LIKE' => $like)))); - break; - case '' : - $results['conditions'] = $query['conditions']; - break; - default : - $results['conditions'] = Set::merge( - $query['conditions'], - array($this->alias . '.username LIKE' => $like)); - break; - } - - if (isset($query['operation']) && $query['operation'] == 'count') { - $results['fields'] = array('COUNT(DISTINCT ' . $this->alias . '.id)'); - } else { - //$results['fields'] = array('DISTINCT User.*'); - } - return $results; - } elseif ($state == 'after') { - if (isset($query['operation']) && $query['operation'] == 'count') { - if (isset($query['group']) && is_array($query['group']) && !empty($query['group'])) { - return count($results); - } - return $results[0][0]['COUNT(DISTINCT ' . $this->alias . '.id)']; - } - return $results; - } - } - -/** - * Customized paginateCount method - * - * @param array $conditions Find conditions - * @param int $recursive Recursive level - * @param array $extra Extra options - * @return array - */ - function paginateCount($conditions = array(), $recursive = 0, $extra = array()) { - $parameters = compact('conditions'); - if ($recursive != $this->recursive) { - $parameters['recursive'] = $recursive; - } - if (isset($extra['type']) && isset($this->_findMethods[$extra['type']])) { - $extra['operation'] = 'count'; - return $this->find($extra['type'], array_merge($parameters, $extra)); - } else { - return $this->find('count', array_merge($parameters, $extra)); - } - } - -/** - * Adds a new user - * - * @param array post data, should be Controller->data - * @return array - */ - public function add($postData = null) { - if (!empty($postData)) { - $this->create(); - if ($this->save($postData)) { - return true; - } - } - } - -/** - * Edits an existing user - * - * @param string $userId User ID - * @param array $postData controller post data usually $this->data - * @return mixed True on successfully save else post data as array - */ - public function edit($userId = null, $postData = null) { - $user = $this->find('first', array( - 'contain' => array( - 'Detail'), - 'conditions' => array( - $this->alias . '.id' => $userId))); - - $this->set($user); - if (empty($user)) { - throw new OutOfBoundsException(__d('users', 'Invalid User', true)); - } - - if (!empty($postData)) { - $this->set($postData); - $result = $this->save(null, true); - if ($result) { - $this->data = $result; - return true; - } else { - return $postData; - } - } - } - -/** - * Removes all users from the user table that are outdated - * - * Override it as needed for your specific project - * - * @return void - */ - protected function _removeExpiredRegistrations() { - $this->deleteAll(array( - $this->alias . '.email_authenticated' => 0, - $this->alias . '.email_token_expires <' => date('Y-m-d H:i:s'))); - } -} 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 new file mode 100644 index 000000000..3a1684f67 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + ./src + + + + + + + + + + + ./tests/TestCase + + + + + + + + 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/readme.md b/readme.md deleted file mode 100644 index 1518832b8..000000000 --- a/readme.md +++ /dev/null @@ -1,120 +0,0 @@ -# Users Plugin for CakePHP # - -Version 1.1 - -The users plugin is for allowing users to register and login manage their profile. It also allows admins to manage the users. - -The plugin is thought as a base to extend your app specific users controller and model from. - -## Installation ## - -The plugin is pretty easy to set up, all you need to do is to copy it to you application plugins folder and load the needed tables. You can create database tables using either the schema shell or the [CakeDC Migrations plugin](http://github.com/CakeDC/migrations): - - cake schema create -plugin users -name users - -or - - cake migration all -plugin users - -You will also need the [CakeDC Search plugin](http://github.com/CakeDC/search), just grab it and put it into your application's plugin folder. - -Activate some necessary core components: - - public $components = array('RequestHandler', 'Session', 'Auth'); - -Add some lines to your `beforeFilter` and configure at your taste: - - $this->Auth->fields = array('username' => 'email', 'password' => 'passwd'); - $this->Auth->loginAction = array('plugin' => 'users', 'controller' => 'users', 'action' => 'login', 'admin' => false); - $this->Auth->loginRedirect = '/'; - $this->Auth->logoutRedirect = '/'; - $this->Auth->authError = __('Sorry, but you need to login to access this location.', true); - $this->Auth->loginError = __('Invalid e-mail / password combination. Please try again', true); - $this->Auth->autoRedirect = false; - $this->Auth->userModel = 'Users.User'; - $this->Auth->userScope = array('User.active' => 1); - -## What it is capable of ## - -You can use the plugin as it comes if you're happy with it or, more common, extend your app specific user implementation from the plugin. - -The plugin itself is already capable of: - -* User registration (http://localhost/users/users/register) -* Account verification by a token sent via email -* User login (email / password) (http://localhost/users/users/login) -* Password reset based on requesting a token by email and entering a new password -* Simple profiles for users -* User search -* User management using the "admin" section - -## How to extend the plugin ## - -### Extending the controller ### - -Declare the controller class - - App::import('Controller', 'Users.Users'); - AppUsersController extends UsersController - -In the case you want to extend also the user model it's required to set the right user class in the beforeFilter() because the controller will use the inherited model which would be Users.User. - - public function beforeFilter() { - parent::beforeFilter(); - $this->User = ClassRegistry::init('AppUser'); - } - -You can overwrite the render() method to fall back to the plugin views in the case you want to use some of them - - public function render($action = null, $layout = null, $file = null) { - if (is_null($action)) { - $action = $this->action; - } - if (!file_exists(VIEWS . $this->viewPath . DS . $action . '.ctp')) { - $file = App::pluginPath('users') . 'views' . DS . 'users' . DS . $action . '.ctp'; - } - return parent::render($action, $layout, $file); - } - -### Extending the model ### - -Declare the model - - App::import('Model', 'Users.User'); - AppUser extends User { - public $useTable = 'users'; - public $name = 'AppUser'; - } - -It's important to override the AppUser::useTable property with the 'users' table. - -You can override/extend all methods or properties like validation rules to suit your needs. - -## Requirements ## - -* PHP version: PHP 5.2+ -* CakePHP version: Cakephp 1.3 Stable -* [CakeDC Utils plugin](http://github.com/CakeDC/utils) -* [CakeDC Search plugin](http://github.com/CakeDC/search) - -## Support ## - -For support and feature request, please visit the [Users Plugin Support Site](http://cakedc.lighthouseapp.com/projects/60126-users-plugin/). - -For more information about our Professional CakePHP Services please visit the [Cake Development Corporation website](http://cakedc.com). - -## License ## - -Copyright 2009-2010, [Cake Development Corporation](http://cakedc.com) - -Licensed under [The MIT License](http://www.opensource.org/licenses/mit-license.php)
-Redistributions of files must retain the above copyright notice. - -## Copyright ### - -Copyright 2009-2010
-[Cake Development Corporation](http://cakedc.com)
-1785 E. Sahara Avenue, Suite 490-423
-Las Vegas, Nevada 89104
-http://cakedc.com
- 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/resources/locales/fr_FR/users.po b/resources/locales/fr_FR/users.po new file mode 100644 index 000000000..ff98d4585 --- /dev/null +++ b/resources/locales/fr_FR/users.po @@ -0,0 +1,823 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +msgid "" +msgstr "" +"Project-Id-Version: CakeDC Users\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 2.2\n" +"Last-Translator: Marcelo Rocha \n" +"Language: fr_FR\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Le compte a été validé avec succès" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Le compte n'a pas pu être validé" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Jeton et/ou compte de réseau social invalide" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Compte de réseau social déjà actif" + +#: 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:78 +msgid "Email sent successfully" +msgstr "Courriel envoyé avec succès" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Le courriel n'a pas pu être envoyé" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Compte invalide" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "Le courriel n'a pas pu être réenvoyé" + +#: 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/LinkSocialTrait.php:76 +#, fuzzy +msgid "Social account was associated." +msgstr "Compte de réseau social déjà actif" + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Vous avez été correctement déconnecté" + +#: 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 "L'utilisateur n'a pas pu être sauvegardé" + +#: 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/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 "L'utilisateur n'a pas été trouvé" + +#: 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:83 +msgid "Password has been changed successfully" +msgstr "Le mot de passe a été changé avec succès" + +#: 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: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: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:148 +msgid "The user is not active" +msgstr "L'utilisateur n'est pas actif" + +#: 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/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: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:75;99 +msgid "The user could not be saved" +msgstr "L'utilisateur n'a pas pu être sauvegardé" + +#: 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:135 +msgid "Please validate your account before log in" +msgstr "Merci de valider votre compte avant de vous identifier" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "Le {0} a été sauvegardé" + +#: 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:131 +msgid "The {0} has been deleted" +msgstr "Le {0} a été supprimé" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "Le {0} n'a pas pu être supprimé" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Compte utilisateur validé avec succès" + +#: 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:49 +msgid "User already active" +msgstr "Utilisateur déjà actif" + +#: 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: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:67 +msgid "Invalid validation type" +msgstr "Type de validation invalide" + +#: 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:76 +msgid "Token already expired" +msgstr "Le jeton a déjà expiré" + +#: 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:118 +msgid "User {0} is already active" +msgstr "L'utilisateur {0} est déjà actif" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0}Votre lien de réinitialisation de mot de passe" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0}Votre lien de validation de compte social" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "L'expiration du jeton ne peut pas être vide" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Utilisateur non trouvé" + +#: 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:67 +msgid "User not active" +msgstr "Utilisateur non activé" + +#: 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: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:93 +msgid "Token has already expired user with no token" +msgstr "Le jeton a déjà expiré utilisateur sans jeton" + +#: Model/Behavior/RegisterBehavior.php:151 +msgid "This field is required" +msgstr "Ce champ est requis" + +#: Model/Behavior/SocialAccountBehavior.php:102;129 +msgid "Account already validated" +msgstr "Compte déjà validé" + +#: 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: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:122 +msgid "Email not present" +msgstr "Courriel non présent" + +#: 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:171 +msgid "Username already exists" +msgstr "Le nom d'utilisateur existe déjà" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Le courriel existe déjà" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Utilitaires pour le Plugin CakeDC Users" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Activer un utilisateur spécifique" + +#: 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:66 +msgid "Add a new user" +msgstr "Ajouter un nouvel utilisateur" + +#: 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:72 +msgid "Deactivate an specific user" +msgstr "Désactiver un utilisateur spécifique" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Supprimer un utilisateur spécifique" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Réinitialiser le mot de passe par courriel" + +#: 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:84 +msgid "Reset the password for an specific user" +msgstr "Réinitialiser le mot de passe pour un utilisateur spécifique" + +#: Shell/UsersShell.php:133;159 +msgid "Please enter a password." +msgstr "Veuillez saisir un mot de passe." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Mot de passe changé pour tous les utilisateurs" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nouveau mot de passe : {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Veuillez saisir un nom d'utilisateur." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Mot de passe changé pour l'utilisateur : {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Veuillez saisir un rôle." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Rôle changé pour l'utilisateur : {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Nouveau rôle : {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "L'utilisateur a été activé : {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "L'utilisateur a été désactivé : {0]" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Veuillez saisir un nom d'utilisateur ou un courriel." + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +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: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: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:369 +msgid "The user {0} was deleted successfully" +msgstr "L'utilisateur {0} a été supprimé avec succès" + +#: 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 "Bonjour {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +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 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: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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Activez votre identification sociale ici" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Activez votre compte ici" + +#: 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 "Veuillez copier l'adresse suivante dans votre navigateur 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 "" +"Veuillez copier l'adresse suivante dans votre navigateur web pour activer " +"votre identification sociale {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 "Actions" + +#: 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:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Ajouter un utilisateur" + +#: 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 +msgid "Submit" +msgstr "Envoyer" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Veuillez entrer le nouveau mot de passe" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Mot de passe actuel" + +#: 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: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: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 +msgid "View" +msgstr "Voir" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Changer le mot de passe" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Modifier" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "précédent" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "suivant" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Merci de saisir votre nom d'utilisateur et votre mot de passe" + +#: Template/Users/login.ctp:29 +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:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +#, fuzzy +msgid "Change Password" +msgstr "Changer le mot de passe" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Comptes sociaux" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Photo de profil" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Fournisseur" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Lien" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Lier à {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Accepter les CGU ?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Merci de saisir votre email pour réinitialiser votre mot de passe" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Réenvoyer le courriel de validation" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Courriel ou nom d'utilisateur" + +#: 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:24 +msgid "New User" +msgstr "Nouvel utilisateur" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Identifiant" + +#: Template/Users/view.ctp:37 +#, fuzzy +msgid "First Name" +msgstr "Prénom" + +#: Template/Users/view.ctp:39 +#, fuzzy +msgid "Last Name" +msgstr "Nom" + +#: Template/Users/view.ctp:41 +#, fuzzy +msgid "Role" +msgstr "Nouveau rôle : {0}" + +#: Template/Users/view.ctp:45 +#, fuzzy +msgid "Api Token" +msgstr "Jeton d'API" + +#: Template/Users/view.ctp:53 +#, fuzzy +msgid "Token Expires" +msgstr "Expiration du jeton" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "Date d'activiation" + +#: Template/Users/view.ctp:57 +#, fuzzy +msgid "Tos Date" +msgstr "Date CGU" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Créé le" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Modifié le" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "S'identifier avec" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Se déconnecter" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Bienvenue, {0}" + +#: 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" + +#: 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/resources/locales/pt_BR/users.po b/resources/locales/pt_BR/users.po new file mode 100644 index 000000000..6c4e524a0 --- /dev/null +++ b/resources/locales/pt_BR/users.po @@ -0,0 +1,805 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# +msgid "" +msgstr "" +"Project-Id-Version: CakeDC Users\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" +"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: pt_BR\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Conta validada com êxito" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "A conta não pode ser validada" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Token e/ou conta social inválido(s)" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Conta social já ativa" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "A conta social não pode ser validada" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "Email enviado com sucesso" + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "O email não pode ser enviado" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Conta inválida" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "O email não pode ser reenviado" + +#: Controller/Traits/LinkSocialTrait.php:52 +msgid "Could not associate account, please try again." +msgstr "Não foi possível associar a conta, tente novamente." + +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "A conta social foi associada." + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Você desconectou-se com êxito" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Por favor habilite o Google Authenticator primeiro." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +msgid "Could not find user data" +msgstr "Não foi possivel verificar os dados de usuário" + +#: 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/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Código de verificação é inválido. Tente novamente" + +#: 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:75;87;95 +msgid "Password could not be changed" +msgstr "A senha não pode ser alterada" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "Senha alterada com êxito" + +#: 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: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: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:148 +msgid "The user is not active" +msgstr "O usuário não está ativo" + +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 +msgid "Token could not be reset" +msgstr "O token não pode ser redefinido" + +#: Controller/Traits/PasswordManagementTrait.php: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: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: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:135 +msgid "Please validate your account before log in" +msgstr "Por favor, valide sua conta antes de autenticar" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "O {0} foi salvo" + +#: 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:131 +msgid "The {0} has been deleted" +msgstr "O {0} foi deletado" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "O {0} não pode ser deletado" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Conta de usuário validada com êxito" + +#: 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:49 +msgid "User already active" +msgstr "Usuário já ativo" + +#: 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: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:67 +msgid "Invalid validation type" +msgstr "Tipo de validação inválido" + +#: 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:76 +msgid "Token already expired" +msgstr "Token expirado" + +#: 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:118 +msgid "User {0} is already active" +msgstr "O usuário {0} já está ativo" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0}Seu link de redefinição de senha" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0}Seu link de validação da conta social" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "A expiração do token não pode ser vazia" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Usuário não encontrado" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "Conta de usuário já validada" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "Usuário inativo" + +#: 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: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:93 +msgid "Token has already expired user with no token" +msgstr "Token expirado, usuário sem token" + +#: 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: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:83 +msgid "Unable to login user with reference {0}" +msgstr "Incapaz de autenticar usuário com a referência {0}" + +#: Model/Behavior/SocialBehavior.php:122 +msgid "Email not present" +msgstr "Email ausente" + +#: 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:171 +msgid "Username already exists" +msgstr "Nome de usuário em uso" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "Email em uso" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Utilitários para CakeDC Users Plugin" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Ativar um usuário específico" + +#: 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:66 +msgid "Add a new user" +msgstr "Adicionar um novo usuário" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Alterar o papel de um usuário específico" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Desativar um usuário específico" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Deletar um usuário específico" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Redefinir a senha via email" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Redefinir a senha de todos usuários" + +#: 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:133;159 +msgid "Please enter a password." +msgstr "Por favor, indique uma senha." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "As senhas de todos usuários foram alteradas" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nova senha: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Por favor, indique um nome de usuário." + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Senha alterada para usuário: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Por favor, indique um papel." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Papel alterado para usuário: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Novo papel: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Usuário ativado: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Usuário desativado: {0}" + +#: 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:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Por favor, peça ao usuário para verificar seu email para continuar o " +"processo de redefinição de senha" + +#: 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: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:369 +msgid "The user {0} was deleted successfully" +msgstr "O usuário {0} foi deletado com êxito" + +#: 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 "Olá {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +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 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 " +"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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Ative sua autenticação social aqui" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Ative sua conta aqui" + +#: 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 o endereço a seguir em seu 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, 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: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:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Listar usuários" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Adicionar usuário" + +#: Template/Users/add.ctp:23 Template/Users/edit.ctp: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 +msgid "Submit" +msgstr "Enviar" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Por favor, indique a nova senha" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Senha atual" + +#: 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: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: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 +msgid "View" +msgstr "Visualizar" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Alterar senha" + +#: 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 "próximo" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Por favor, indique seu nome de usuário e senha" + +#: Template/Users/login.ctp:29 +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:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Mudar senha" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Contas sociais" + +#: 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 "Provedor" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Link" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Link para {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Concordar com Termos de Uso?" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Por favor, indique seu email para redefinir sua senha" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Reenviar email de validação" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "Email ou nome de usuário" + +#: 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:24 +msgid "New User" +msgstr "Novo usuário" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +msgid "First Name" +msgstr "Nome" + +#: Template/Users/view.ctp:39 +msgid "Last Name" +msgstr "Sobrenome" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Papel" + +#: Template/Users/view.ctp:45 +msgid "Api Token" +msgstr "Api Token" + +#: Template/Users/view.ctp:53 +msgid "Token Expires" +msgstr "Expiração do token" + +#: Template/Users/view.ctp:55 +msgid "Activation Date" +msgstr "Data de ativação" + +#: Template/Users/view.ctp:57 +#, fuzzy +msgid "Tos Date" +msgstr "Data TOS" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Criado" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Modificado" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Logar com" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Desconectar" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Bem-vindo(a), {0}" + +#: View/Helper/UserHelper.php:151 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "" +"reCaptcha não está configurado! Por favor, configure Users.reCaptcha.key" + +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "Conectador com {0}" + +#: 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/resources/locales/sv/users.po b/resources/locales/sv/users.po new file mode 100644 index 000000000..4d952389f --- /dev/null +++ b/resources/locales/sv/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 13:21-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: sv\n" + +#: Controller/SocialAccountsController.php:50 +msgid "Account validated successfully" +msgstr "Kontot har validerats" + +#: Controller/SocialAccountsController.php:52 +msgid "Account could not be validated" +msgstr "Konto kunde inte valideras" + +#: Controller/SocialAccountsController.php:55 +msgid "Invalid token and/or social account" +msgstr "Ogiltig token och/eller social konto" + +#: Controller/SocialAccountsController.php:57;85 +msgid "Social Account already active" +msgstr "Socialt konto redan aktivt" + +#: Controller/SocialAccountsController.php:59 +msgid "Social Account could not be validated" +msgstr "Socialt konto kunde inte valideras" + +#: Controller/SocialAccountsController.php:78 +msgid "Email sent successfully" +msgstr "Epost har skickats." + +#: Controller/SocialAccountsController.php:80 +msgid "Email could not be sent" +msgstr "Epost kunde inte skickas" + +#: Controller/SocialAccountsController.php:83 +msgid "Invalid account" +msgstr "Ogiltigt konto" + +#: Controller/SocialAccountsController.php:87 +msgid "Email could not be resent" +msgstr "E-post kunde inte skickas om" + +#: Controller/Traits/LinkSocialTrait.php:52 +msgid "Could not associate account, please try again." +msgstr "Kunde inte ansluta konto, försök igen." + +#: Controller/Traits/LinkSocialTrait.php:76 +msgid "Social account was associated." +msgstr "Socialt konto anslöts." + +#: Controller/Traits/LoginTrait.php:73 +msgid "You've successfully logged out" +msgstr "Du har loggats ut" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:71 +msgid "Please enable Google Authenticator first." +msgstr "Vänligen aktivera Google Authenticator först." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:80 +#, fuzzy +msgid "Could not find user data" +msgstr "Användare kunde inte läggas till:" + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:116 +#, fuzzy +msgid "Could not verify, please try again" +msgstr "Kunde inte ansluta konto, försök igen." + +#: Controller/Traits/OneTimePasswordVerifyTrait.php:144 +msgid "Verification code is invalid. Try again" +msgstr "Valideringskoden är ogiltig. Försök igen" + +#: 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:75;87;95 +msgid "Password could not be changed" +msgstr "Lösenordet kunde inte ändras" + +#: Controller/Traits/PasswordManagementTrait.php:83 +msgid "Password has been changed successfully" +msgstr "Lösenordsbytet lyckades!" + +#: 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: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:146 +#: Controller/Traits/UserValidationTrait.php:116 +msgid "User {0} was not found" +msgstr "Användaren {0} hittades inte" + +#: Controller/Traits/PasswordManagementTrait.php:148 +msgid "The user is not active" +msgstr "Användaren är inte aktiverad" + +#: Controller/Traits/PasswordManagementTrait.php:150 +#: Controller/Traits/UserValidationTrait.php:111;120 +msgid "Token could not be reset" +msgstr "Token kunde inte återställas" + +#: Controller/Traits/PasswordManagementTrait.php: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: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:75;99 +msgid "The user could not be saved" +msgstr "Användaren kunde inte sparas" + +#: 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:135 +msgid "Please validate your account before log in" +msgstr "Vänligen validera ditt konto innan inloggning" + +#: Controller/Traits/SimpleCrudTrait.php:77;107 +msgid "The {0} has been saved" +msgstr "{0} har sparats" + +#: Controller/Traits/SimpleCrudTrait.php:81;111 +msgid "The {0} could not be saved" +msgstr "{0} kunde inte sparas" + +#: Controller/Traits/SimpleCrudTrait.php:131 +msgid "The {0} has been deleted" +msgstr "{0} har tagits bort" + +#: Controller/Traits/SimpleCrudTrait.php:133 +msgid "The {0} could not be deleted" +msgstr "{0} kunde inte tas bort" + +#: Controller/Traits/UserValidationTrait.php:44 +msgid "User account validated successfully" +msgstr "Användarkontot har validerats" + +#: Controller/Traits/UserValidationTrait.php:46 +msgid "User account could not be validated" +msgstr "Användarkontot kunde inte valideras" + +#: Controller/Traits/UserValidationTrait.php:49 +msgid "User already active" +msgstr "Användaren redan aktiv" + +#: Controller/Traits/UserValidationTrait.php:55 +msgid "Reset password token was validated successfully" +msgstr "Återställning av lösenord lyckades" + +#: 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:67 +msgid "Invalid validation type" +msgstr "Ogiltig valideringsmetod" + +#: 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:76 +msgid "Token already expired" +msgstr "Token redan löpt ut" + +#: 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:118 +msgid "User {0} is already active" +msgstr "Användaren {0} är redan aktiv" + +#: 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:51 +msgid "{0}Your reset password link" +msgstr "{0} Din länk för att återställa lösenord" + +#: Mailer/UsersMailer.php:74 +msgid "{0}Your social account validation link" +msgstr "{0} Din sociala konto svalideringslänk" + +#: 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:50 +msgid "Token expiration cannot be empty" +msgstr "Giltighet för token kan inte vara tom" + +#: Model/Behavior/PasswordBehavior.php:56;138 +msgid "User not found" +msgstr "Användaren hittades inte" + +#: Model/Behavior/PasswordBehavior.php:60 +#: Model/Behavior/RegisterBehavior.php:112 +msgid "User account already validated" +msgstr "Kontot är redan aktiverat!" + +#: Model/Behavior/PasswordBehavior.php:67 +msgid "User not active" +msgstr "Användaren ej aktiv" + +#: Model/Behavior/PasswordBehavior.php:143 +msgid "The current password does not match" +msgstr "Den nuvarande lösenord matchar inte" + +#: 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: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: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!" + +#: Model/Behavior/SocialAccountBehavior.php:105;132 +msgid "Account not found for the given token and email." +msgstr "Kontot hittades inte för givet token och e-post." + +#: Model/Behavior/SocialBehavior.php: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:122 +msgid "Email not present" +msgstr "Epost saknas" + +#: 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:171 +msgid "Username already exists" +msgstr "Användarnamnet är upptaget" + +#: Model/Table/UsersTable.php:177 +msgid "Email already exists" +msgstr "E-postadressen finns redan" + +#: Shell/UsersShell.php:58 +msgid "Utilities for CakeDC Users Plugin" +msgstr "Verktyg för CakeDC User Plugin" + +#: Shell/UsersShell.php:60 +msgid "Activate an specific user" +msgstr "Aktivera en specifik användare" + +#: 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:66 +msgid "Add a new user" +msgstr "Ny användare" + +#: Shell/UsersShell.php:69 +msgid "Change the role for an specific user" +msgstr "Ändra rollen för en specifik användare" + +#: Shell/UsersShell.php:72 +msgid "Deactivate an specific user" +msgstr "Inaktivare en specifik användare" + +#: Shell/UsersShell.php:75 +msgid "Delete an specific user" +msgstr "Ta bort en specifik användare" + +#: Shell/UsersShell.php:78 +msgid "Reset the password via email" +msgstr "Återställ lösenord via e-post" + +#: Shell/UsersShell.php:81 +msgid "Reset the password for all users" +msgstr "Återställ lösenord för alla användare" + +#: 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:133;159 +msgid "Please enter a password." +msgstr "Ange ett lösenord." + +#: Shell/UsersShell.php:137 +msgid "Password changed for all users" +msgstr "Lösenordet ändrat för alla användare" + +#: Shell/UsersShell.php:138;166 +msgid "New password: {0}" +msgstr "Nytt lösenord: {0}" + +#: Shell/UsersShell.php:156;184;262;359 +msgid "Please enter a username." +msgstr "Ange ett användarnamn" + +#: Shell/UsersShell.php:165 +msgid "Password changed for user: {0}" +msgstr "Lösenordet ändrat för användare: {0}" + +#: Shell/UsersShell.php:187 +msgid "Please enter a role." +msgstr "Ange en roll." + +#: Shell/UsersShell.php:193 +msgid "Role changed for user: {0}" +msgstr "Rollen har ändrats för användare: {0}" + +#: Shell/UsersShell.php:194 +msgid "New role: {0}" +msgstr "Ny roll: {0}" + +#: Shell/UsersShell.php:209 +msgid "User was activated: {0}" +msgstr "Användaren aktiverades: {0}" + +#: Shell/UsersShell.php:224 +msgid "User was de-activated: {0}" +msgstr "Användaren avaktiverad: {0}" + +#: Shell/UsersShell.php:236 +msgid "Please enter a username or email." +msgstr "Skriv användarnamn eller epost" + +#: Shell/UsersShell.php:244 +msgid "" +"Please ask the user to check the email to continue with password reset " +"process" +msgstr "" +"Be användaren kontrollera sin epost för att fortsätta " +"lösenordsåterställningen" + +#: 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: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:369 +msgid "The user {0} was deleted successfully" +msgstr "Användaren {0} har tagits bort" + +#: 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 "Hej {0}" + +#: Template/Email/html/reset_password.ctp:24 +msgid "Reset your password here" +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 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/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" + +#: Template/Email/html/social_account_validation.ctp:18 +msgid "Activate your social login here" +msgstr "Aktivera ditt sociala konto här" + +#: Template/Email/html/validation.ctp:24 +msgid "Activate your account here" +msgstr "Aktivera ditt konto här" + +#: 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 "Kopiera följande adress till webbläsaren {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 "" +"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: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:28 +#: Template/Users/view.ctp:23 +msgid "List Users" +msgstr "Lista användare" + +#: Template/Users/add.ctp:21 Template/Users/register.ctp:18 +msgid "Add User" +msgstr "Ny användare" + +#: Template/Users/add.ctp:23 Template/Users/edit.ctp:36 +#: Template/Users/index.ctp:22 Template/Users/login.ctp:20 +#: Template/Users/profile.ctp:30 Template/Users/register.ctp:20 +#: Template/Users/view.ctp:33 +msgid "Username" +msgstr "Användarnamn" + +#: Template/Users/add.ctp:24 Template/Users/edit.ctp: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: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: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: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: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 "Skicka" + +#: Template/Users/change_password.ctp:5 +msgid "Please enter the new password" +msgstr "Ange ditt nya lösenord" + +#: Template/Users/change_password.ctp:10 +msgid "Current password" +msgstr "Nuvarande lösenord" + +#: Template/Users/change_password.ctp:16 +msgid "New password" +msgstr "Nytt lösenord" + +#: Template/Users/change_password.ctp:21 Template/Users/register.ctp:25 +msgid "Confirm password" +msgstr "Bekräfta lösenord" + +#: Template/Users/edit.ctp:22 Template/Users/index.ctp:40 +msgid "Delete" +msgstr "Radera" + +#: 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:34 Template/Users/view.ctp:17 +msgid "Edit User" +msgstr "Redigera användare" + +#: Template/Users/edit.ctp:40 Template/Users/view.ctp:43 +msgid "Token" +msgstr "Token" + +#: Template/Users/edit.ctp:42 +msgid "Token expires" +msgstr "Token förfaller" + +#: Template/Users/edit.ctp:45 +msgid "API token" +msgstr "API-token" + +#: Template/Users/edit.ctp:48 +msgid "Activation date" +msgstr "Aktiveringsdatum" + +#: 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 +msgid "View" +msgstr "Visa" + +#: Template/Users/index.ctp:38 +msgid "Change password" +msgstr "Byt lösenord" + +#: Template/Users/index.ctp:39 +msgid "Edit" +msgstr "Ändra" + +#: Template/Users/index.ctp:49 +msgid "previous" +msgstr "föregående" + +#: Template/Users/index.ctp:51 +msgid "next" +msgstr "nästa" + +#: Template/Users/login.ctp:19 +msgid "Please enter your username and password" +msgstr "Ange ditt användarnamn och lösenord" + +#: Template/Users/login.ctp:29 +msgid "Remember me" +msgstr "Kom ihåg mig" + +#: Template/Users/login.ctp:37 +msgid "Register" +msgstr "Registrera" + +#: Template/Users/login.ctp:43 +msgid "Reset Password" +msgstr "Återställ Lösenord" + +#: Template/Users/login.ctp:48 +msgid "Login" +msgstr "Logga in" + +#: Template/Users/profile.ctp:21 +msgid "{0} {1}" +msgstr "{0} {1}" + +#: Template/Users/profile.ctp:27 +msgid "Change Password" +msgstr "Ändra Lösenord" + +#: Template/Users/profile.ctp:38 Template/Users/view.ctp:68 +msgid "Social Accounts" +msgstr "Sociala konton" + +#: Template/Users/profile.ctp:42 Template/Users/view.ctp:73 +msgid "Avatar" +msgstr "Profilbild" + +#: Template/Users/profile.ctp:43 Template/Users/view.ctp:72 +msgid "Provider" +msgstr "Utförare" + +#: Template/Users/profile.ctp:44 +msgid "Link" +msgstr "Länk" + +#: Template/Users/profile.ctp:51 +msgid "Link to {0}" +msgstr "Länk till {0}" + +#: Template/Users/register.ctp:30 +msgid "Accept TOS conditions?" +msgstr "Jag accepterar villkoren" + +#: Template/Users/request_reset_password.ctp:5 +msgid "Please enter your email to reset your password" +msgstr "Ange din e-postadress för att återställa ditt lösenord" + +#: Template/Users/resend_token_validation.ctp:15 +msgid "Resend Validation email" +msgstr "Skicka nytt bekräftelsemejl" + +#: Template/Users/resend_token_validation.ctp:17 +msgid "Email or username" +msgstr "E-post/Användarnamn" + +#: 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:24 +msgid "New User" +msgstr "Ny användare" + +#: Template/Users/view.ctp:31 +msgid "Id" +msgstr "Id" + +#: Template/Users/view.ctp:37 +#, fuzzy +msgid "First Name" +msgstr "Förnamn" + +#: Template/Users/view.ctp:39 +#, fuzzy +msgid "Last Name" +msgstr "Efternamn" + +#: Template/Users/view.ctp:41 +msgid "Role" +msgstr "Roll" + +#: Template/Users/view.ctp:45 +msgid "Api Token" +msgstr "Api-token" + +#: Template/Users/view.ctp:53 +#, fuzzy +msgid "Token Expires" +msgstr "Token förfaller" + +#: Template/Users/view.ctp:55 +#, fuzzy +msgid "Activation Date" +msgstr "Aktiveringsdatum" + +#: Template/Users/view.ctp:57 +msgid "Tos Date" +msgstr "Tos datum" + +#: Template/Users/view.ctp:59;75 +msgid "Created" +msgstr "Skapad" + +#: Template/Users/view.ctp:61;76 +msgid "Modified" +msgstr "Ändrad" + +#: View/Helper/UserHelper.php:46 +msgid "Sign in with" +msgstr "Logga in med" + +#: View/Helper/UserHelper.php:103 +msgid "Logout" +msgstr "Logga ut" + +#: View/Helper/UserHelper.php:121 +msgid "Welcome, {0}" +msgstr "Välkommen, {0}" + +#: View/Helper/UserHelper.php:151 +msgid "reCaptcha is not configured! Please configure Users.reCaptcha.key" +msgstr "reCaptcha är inte konfigurerad! Konfigurera Users.reCaptcha.key" + +#: View/Helper/UserHelper.php:215 +msgid "Connected with {0}" +msgstr "Ansluten med {0}" + +#: 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/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 new file mode 100644 index 000000000..68c4951e2 --- /dev/null +++ b/src/Controller/AppController.php @@ -0,0 +1,37 @@ +loadComponent('Security'); + 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/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/SocialAccountsController.php b/src/Controller/SocialAccountsController.php new file mode 100644 index 000000000..e69a14869 --- /dev/null +++ b/src/Controller/SocialAccountsController.php @@ -0,0 +1,92 @@ +SocialAccounts->validateAccount($provider, $reference, $token); + if ($result) { + $this->Flash->success(__d('cake_d_c/users', 'Account validated successfully')); + } else { + $this->Flash->error(__d('cake_d_c/users', 'Account could not be validated')); + } + } catch (RecordNotFoundException $exception) { + $this->Flash->error(__d('cake_d_c/users', 'Invalid token and/or social account')); + } catch (AccountAlreadyActiveException $exception) { + $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(UsersUrl::actionUrl('login')); + } + + /** + * Resends validation email if required + * + * @param string $provider provider + * @param string $reference reference + * @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('cake_d_c/users', 'Email sent successfully')); + } else { + $this->Flash->error(__d('cake_d_c/users', 'Email could not be sent')); + } + } catch (RecordNotFoundException $exception) { + $this->Flash->error(__d('cake_d_c/users', 'Invalid account')); + } catch (AccountAlreadyActiveException $exception) { + $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(UsersUrl::actionUrl('login')); + } +} diff --git a/src/Controller/Traits/CustomUsersTableTrait.php b/src/Controller/Traits/CustomUsersTableTrait.php new file mode 100644 index 000000000..c003996ca --- /dev/null +++ b/src/Controller/Traits/CustomUsersTableTrait.php @@ -0,0 +1,55 @@ +_usersTable instanceof Table) { + return $this->_usersTable; + } + $this->_usersTable = TableRegistry::getTableLocator()->get(Configure::read('Users.table')); + + return $this->_usersTable; + } + + /** + * Set the users table + * + * @param \Cake\ORM\Table $table table + * @return void + */ + public function setUsersTable(Table $table) + { + $this->_usersTable = $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 new file mode 100644 index 000000000..2b1c637ac --- /dev/null +++ b/src/Controller/Traits/LoginTrait.php @@ -0,0 +1,82 @@ +handleLogin(false, true); + } + + /** + * Login user + * + * @return mixed + * @throws \Exception + */ + public function login() + { + $this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY); + $Login = LoginComponentLoader::forForm($this); + + return $Login->handleLogin(true, false); + } + + /** + * Logout + * + * @return mixed + */ + public function logout() + { + $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->getRequest()->getSession()->destroy(); + $this->Flash->success(__d('cake_d_c/users', 'You\'ve successfully logged out')); + + $eventAfter = $this->dispatchEvent(Plugin::EVENT_AFTER_LOGOUT, ['user' => $user]); + if (is_array($eventAfter->getResult())) { + return $this->redirect($eventAfter->getResult()); + } + + 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 new file mode 100644 index 000000000..09878ae2a --- /dev/null +++ b/src/Controller/Traits/PasswordManagementTrait.php @@ -0,0 +1,213 @@ +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 { + // 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('cake_d_c/users', 'User was not found')); + $this->redirect($redirect); + + return; + } + } + $this->set('validatePassword', $validatePassword); + if ($this->getRequest()->is(['post', 'put'])) { + try { + $validator = $this->getUsersTable()->validationPasswordConfirm(new Validator()); + if ($validatePassword) { + $validator = $this->getUsersTable()->validationCurrentPassword($validator); + } + $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 { + $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('cake_d_c/users', 'Password could not be changed')); + } + } + } catch (UserNotFoundException $exception) { + $this->Flash->error(__d('cake_d_c/users', 'User was not found')); + } catch (WrongPasswordException $wpe) { + $this->Flash->error($wpe->getMessage()); + } catch (Exception $exception) { + $this->Flash->error(__d('cake_d_c/users', 'Password could not be changed')); + $this->log($exception->getMessage()); + } + } + $this->set(['user' => $user]); + $this->set('_serialize', ['user']); + } + + /** + * Reset password + * + * @param null $token token data. + * @return void + */ + public function resetPassword($token = null) + { + $this->validate('password', $token); + } + + /** + * Reset password + * + * @return void|\Cake\Http\Response + */ + public function requestResetPassword() + { + $this->set('user', $this->getUsersTable()->newEntity([], ['validate' => false])); + $this->set('_serialize', ['user']); + if (!$this->getRequest()->is('post')) { + return; + } + + $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'), + 'type' => 'password', + ]); + if ($resetUser) { + $msg = __d('cake_d_c/users', 'Please check your email to continue with password reset process'); + $this->Flash->success($msg); + } else { + $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('cake_d_c/users', 'User {0} was not found', $reference)); + } catch (UserNotActiveException $exception) { + $this->Flash->error(__d('cake_d_c/users', 'The user is not active')); + } catch (Exception $exception) { + $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 new file mode 100644 index 000000000..a67ac991c --- /dev/null +++ b/src/Controller/Traits/ProfileTrait.php @@ -0,0 +1,65 @@ +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'] : []; + $user = $this->getUsersTable()->get($id, [ + '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('cake_d_c/users', 'User was not found')); + + return $this->redirect($this->getRequest()->referer()); + } catch (InvalidPrimaryKeyException $ex) { + $this->Flash->error(__d('cake_d_c/users', 'Not authorized, please login first')); + + return $this->redirect($this->getRequest()->referer()); + } + $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 new file mode 100644 index 000000000..410511b6e --- /dev/null +++ b/src/Controller/Traits/ReCaptchaTrait.php @@ -0,0 +1,58 @@ +_getReCaptchaInstance(); + if (!empty($recaptcha)) { + $response = $recaptcha->verify($recaptchaResponse, $clientIp); + + return $response->isSuccess(); + } + + return false; + } + + /** + * Create reCaptcha instance if enabled in configuration + * + * @return \ReCaptcha\ReCaptcha|null + */ + protected function _getReCaptchaInstance() + { + $reCaptchaSecret = Configure::read('Users.reCaptcha.secret'); + if (!empty($reCaptchaSecret)) { + return new \ReCaptcha\ReCaptcha($reCaptchaSecret); + } + + return null; + } +} diff --git a/src/Controller/Traits/RegisterTrait.php b/src/Controller/Traits/RegisterTrait.php new file mode 100644 index 000000000..af9d6d0f7 --- /dev/null +++ b/src/Controller/Traits/RegisterTrait.php @@ -0,0 +1,177 @@ +getRequest()->getAttribute('identity'); + $identity = $identity ?? []; + $userId = $identity['id'] ?? null; + if (!empty($userId) && !Configure::read('Users.Registration.allowLoggedIn')) { + $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->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, + ]; + $requestData = $this->getRequest()->getData(); + $event = $this->dispatchEvent(Plugin::EVENT_BEFORE_REGISTER, [ + 'usersTable' => $usersTable, + 'options' => $options, + 'userEntity' => $user, + ]); + + $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->getResult()); + } + + $this->set(['user' => $user]); + $this->set('_serialize', ['user']); + + if (!$this->getRequest()->is('post')) { + return; + } + + if (!$this->_validateRegisterPost()) { + $this->Flash->error(__d('cake_d_c/users', 'Invalid reCaptcha')); + + return; + } + + $userSaved = $usersTable->register($user, $requestData, $options); + $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; + } + + return $this->_afterRegister($userSaved); + } + + /** + * Check the POST and validate it for registration, for now we check the reCaptcha + * + * @return bool + */ + protected function _validateRegisterPost() + { + if (!Configure::read('Users.reCaptcha.registration')) { + return true; + } + + return $this->validateReCaptcha( + $this->getRequest()->getData('g-recaptcha-response'), + $this->getRequest()->clientIp() + ); + } + + /** + * Prepare flash messages after registration, and dispatch afterRegister event + * + * @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('cake_d_c/users', 'You have registered successfully, please log in'); + if ($validateEmail) { + $message = __d('cake_d_c/users', 'Please validate your account before log in'); + } + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_REGISTER, [ + 'user' => $userSaved, + ]); + $result = $event->getResult(); + if ($result instanceof Response) { + return $result; + } + $this->Flash->success($message); + + return $this->redirect(['action' => 'login']); + } + + /** + * Validate an email + * + * @param string $token token + * @return void + */ + public function validateEmail($token = null) + { + $this->validate('email', $token); + } +} diff --git a/src/Controller/Traits/SimpleCrudTrait.php b/src/Controller/Traits/SimpleCrudTrait.php new file mode 100644 index 000000000..33346d298 --- /dev/null +++ b/src/Controller/Traits/SimpleCrudTrait.php @@ -0,0 +1,138 @@ +loadModel(); + $tableAlias = $table->getAlias(); + $this->set($tableAlias, $this->paginate($table)); + $this->set('tableAlias', $tableAlias); + $this->set('_serialize', [$tableAlias, 'tableAlias']); + } + + /** + * View method + * + * @param string|null $id User id. + * @return void + * @throws \Cake\Http\Exception\NotFoundException When record not found. + */ + public function view($id = null) + { + $table = $this->loadModel(); + $tableAlias = $table->getAlias(); + $entity = $table->get($id, [ + 'contain' => [], + ]); + $this->set($tableAlias, $entity); + $this->set('tableAlias', $tableAlias); + $this->set('_serialize', [$tableAlias, 'tableAlias']); + } + + /** + * Add method + * + * @return mixed Redirects on successful add, renders view otherwise. + */ + public function add() + { + $table = $this->loadModel(); + $tableAlias = $table->getAlias(); + $entity = $table->newEmptyEntity(); + $this->set($tableAlias, $entity); + $this->set('tableAlias', $tableAlias); + $this->set('_serialize', [$tableAlias, 'tableAlias']); + if (!$this->getRequest()->is('post')) { + return; + } + $entity = $table->patchEntity($entity, $this->getRequest()->getData()); + $singular = Inflector::singularize(Inflector::humanize($tableAlias)); + if ($table->save($entity)) { + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been saved', $singular)); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__d('cake_d_c/users', 'The {0} could not be saved', $singular)); + } + + /** + * Edit method + * + * @param string|null $id User id. + * @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->getAlias(); + $entity = $table->get($id, [ + 'contain' => [], + ]); + $this->set($tableAlias, $entity); + $this->set('tableAlias', $tableAlias); + $this->set('_serialize', [$tableAlias, 'tableAlias']); + if (!$this->getRequest()->is(['patch', 'post', 'put'])) { + return; + } + $entity = $table->patchEntity($entity, $this->getRequest()->getData()); + $singular = Inflector::singularize(Inflector::humanize($tableAlias)); + if ($table->save($entity)) { + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been saved', $singular)); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__d('cake_d_c/users', 'The {0} could not be saved', $singular)); + } + + /** + * Delete method + * + * @param string|null $id User id. + * @return \Cake\Http\Response Redirects to index. + * @throws \Cake\Http\Exception\NotFoundException When record not found. + */ + public function delete($id = null) + { + $this->getRequest()->allowMethod(['post', 'delete']); + $table = $this->loadModel(); + $tableAlias = $table->getAlias(); + $entity = $table->get($id, [ + 'contain' => [], + ]); + $singular = Inflector::singularize(Inflector::humanize($tableAlias)); + if ($table->delete($entity)) { + $this->Flash->success(__d('cake_d_c/users', 'The {0} has been deleted', $singular)); + } else { + $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 new file mode 100644 index 000000000..eddbb919f --- /dev/null +++ b/src/Controller/Traits/SocialTrait.php @@ -0,0 +1,37 @@ +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 new file mode 100644 index 000000000..16bcf90a0 --- /dev/null +++ b/src/Controller/Traits/UserValidationTrait.php @@ -0,0 +1,131 @@ +getUsersTable()->validate($token, 'activateUser'); + if ($result) { + $event = $this->dispatchEvent(Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION, ['user' => $result]); + if (!empty($event) && is_array($event->getResult())) { + return $this->redirect($event->getResult()); + } + $this->Flash->success(__d('cake_d_c/users', 'User account validated successfully')); + } else { + $this->Flash->error(__d('cake_d_c/users', 'User account could not be validated')); + } + } catch (UserAlreadyActiveException $exception) { + $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('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('cake_d_c/users', 'Reset password token could not be validated')); + } + break; + default: + $this->Flash->error(__d('cake_d_c/users', 'Invalid validation type')); + } + } catch (UserNotFoundException $ex) { + $this->Flash->error(__d('cake_d_c/users', 'Invalid token or user account already validated')); + } catch (TokenExpiredException $ex) { + $event = $this->dispatchEvent(Plugin::EVENT_ON_EXPIRED_TOKEN, ['type' => $type]); + if (!empty($event) && is_array($event->getResult())) { + return $this->redirect($event->getResult()); + } + $this->Flash->error(__d('cake_d_c/users', 'Token already expired')); + } + + return $this->redirect(['action' => 'login']); + } + + /** + * Resend Token validation + * + * @return mixed + */ + public function resendTokenValidation() + { + $this->set('user', $this->getUsersTable()->newEntity([], ['validate' => false])); + $this->set('_serialize', ['user']); + if (!$this->getRequest()->is('post')) { + return; + } + $reference = $this->getRequest()->getData('reference'); + try { + if ( + $this->getUsersTable()->resetToken($reference, [ + 'expiration' => Configure::read('Users.Token.expiration'), + 'checkActive' => true, + 'sendEmail' => true, + '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('cake_d_c/users', 'Token could not be reset')); + } + + return $this->redirect(['action' => 'login']); + } catch (UserNotFoundException $ex) { + $this->Flash->error(__d('cake_d_c/users', 'User {0} was not found', $reference)); + } catch (UserAlreadyActiveException $ex) { + $this->Flash->error(__d('cake_d_c/users', 'User {0} is already active', $reference)); + } catch (Exception $ex) { + $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 new file mode 100644 index 000000000..096bbc9c1 --- /dev/null +++ b/src/Controller/UsersController.php @@ -0,0 +1,71 @@ +components()->has('Security')) { + $this->Security->setConfig( + 'unlockedActions', + [ + 'login', + 'u2fRegister', + 'u2fRegisterFinish', + 'u2fAuthenticate', + 'u2fAuthenticateFinish', + 'webauthn2faRegister', + 'webauthn2faRegisterOptions', + 'webauthn2faAuthenticate', + 'webauthn2faAuthenticateOptions', + ] + ); + } + } +} diff --git a/src/Exception/AccountAlreadyActiveException.php b/src/Exception/AccountAlreadyActiveException.php new file mode 100644 index 000000000..4cef96afd --- /dev/null +++ b/src/Exception/AccountAlreadyActiveException.php @@ -0,0 +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/Mailer/UsersMailer.php b/src/Mailer/UsersMailer.php new file mode 100644 index 000000000..2e4070e96 --- /dev/null +++ b/src/Mailer/UsersMailer.php @@ -0,0 +1,115 @@ +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 + ->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 \Cake\Datasource\EntityInterface $user User entity + * @return void + */ + protected function resetPassword(EntityInterface $user) + { + $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 + ->viewBuilder() + ->setTemplate('CakeDC/Users.resetPassword'); + } + + /** + * Send account validation email to the user + * + * @param \Cake\Datasource\EntityInterface $user User entity + * @param \Cake\Datasource\EntityInterface $socialAccount SocialAccount entity + * @return void + */ + protected function socialAccountValidation(EntityInterface $user, EntityInterface $socialAccount) + { + $firstName = isset($user['first_name']) ? $user['first_name'] . ', ' : ''; + // 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 + ->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/BaseTokenBehavior.php b/src/Model/Behavior/BaseTokenBehavior.php new file mode 100644 index 000000000..fbbe3cc89 --- /dev/null +++ b/src/Model/Behavior/BaseTokenBehavior.php @@ -0,0 +1,60 @@ +updateToken($tokenExpiration); + } else { + $user['active'] = true; + $user['activation_date'] = new FrozenTime(); + } + + return $user; + } + + /** + * Remove user token for validation + * + * @param \Cake\Datasource\EntityInterface $user user object. + * @return \Cake\Datasource\EntityInterface + */ + protected function _removeValidationToken(EntityInterface $user) + { + $user['token'] = null; + $user['token_expires'] = null; + + 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 new file mode 100644 index 000000000..e307d15a0 --- /dev/null +++ b/src/Model/Behavior/PasswordBehavior.php @@ -0,0 +1,157 @@ +_getUser($reference); + + if (empty($user)) { + throw new UserNotFoundException(__d('cake_d_c/users', 'User not found')); + } + if ($options['checkActive'] ?? false) { + if ($user->active) { + throw new UserAlreadyActiveException(__d('cake_d_c/users', 'User account already validated')); + } + $user->active = false; + $user->activation_date = null; + } + if (($options['ensureActive'] ?? false) && !$user['active']) { + throw new UserNotActiveException(__d('cake_d_c/users', 'User not active')); + } + $user->updateToken($expiration); + $saveResult = $this->_table->save($user); + if ($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 + * + * @param string $reference reference could be either an email or username + * @return mixed user entity if found + */ + protected function _getUser($reference) + { + return $this->_table->findByUsernameOrEmail($reference, $reference)->first(); + } + + /** + * Change password method + * + * @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' => [], + ]); + } catch (RecordNotFoundException $e) { + 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('cake_d_c/users', 'The current password does not match')); + } + if ($user->current_password === $user->password_confirm) { + throw new WrongPasswordException(__d( + 'cake_d_c/users', + 'You cannot use the current password as the new one' + )); + } + } + $user = $this->_table->save($user); + if (!empty($user)) { + $user = $this->_removeValidationToken($user); + } + + return $user; + } +} diff --git a/src/Model/Behavior/RegisterBehavior.php b/src/Model/Behavior/RegisterBehavior.php new file mode 100644 index 000000000..b2fb4fb10 --- /dev/null +++ b/src/Model/Behavior/RegisterBehavior.php @@ -0,0 +1,226 @@ +validateEmail = (bool)Configure::read('Users.Email.validate'); + $this->useTos = (bool)Configure::read('Users.Tos.required'); + } + + /** + * Registers an user. + * + * @param \Cake\Datasource\EntityInterface $user User information + * @param array $data User information + * @param array $options ['tokenExpiration] + * @return bool|\Cake\Datasource\EntityInterface + */ + public function register($user, $data, $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->_sendValidationEmail($user); + } + + return $userSaved; + } + + /** + * Validates token and return user + * + * @param string $token toke to be validated. + * @param null $callback function that will be returned. + * @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 = $token ? $this->_table->find() + ->select(['token_expires', 'id', 'active', 'token']) + ->where(['token' => $token]) + ->first() : null; + if (empty($user)) { + throw new UserNotFoundException(__d('cake_d_c/users', 'User not found for the given token and email.')); + } + if ($user->tokenExpired()) { + throw new TokenExpiredException(__d('cake_d_c/users', 'Token has already expired user with no token')); + } + if (!method_exists($this, (string)$callback)) { + return $user; + } + + return $this->_table->{$callback}($user); + } + + /** + * Activates an user + * + * @param \Cake\Datasource\EntityInterface $user user object. + * @return mixed User entity or bool false if the user could not be activated + * @throws \CakeDC\Users\Exception\UserAlreadyActiveException + */ + public function activateUser(EntityInterface $user) + { + if ($user->active) { + throw new UserAlreadyActiveException(__d('cake_d_c/users', 'User account already validated')); + } + $user->activation_date = new \DateTime(); + $user->token_expires = null; + $user->active = true; + + return $this->_table->save($user); + } + + /** + * buildValidator + * + * @param \Cake\Event\Event $event event + * @param \Cake\Validation\Validator $validator validator + * @param string $name name + * @return \Cake\Validation\Validator + */ + public function buildValidator(\Cake\Event\EventInterface $event, Validator $validator, $name) + { + if ($name === 'default') { + return $this->_emailValidator($validator, $this->validateEmail); + } + + return $validator; + } + + /** + * Email validator + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @param bool $validateEmail true when email needs to be required + * @return \Cake\Validation\Validator + */ + protected function _emailValidator(Validator $validator, $validateEmail) + { + $this->validateEmail = $validateEmail; + $validator + ->add('email', 'valid', ['rule' => 'email']) + ->notBlank('email', __d('cake_d_c/users', 'This field is required'), function ($context) { + return $this->validateEmail; + }); + + return $validator; + } + + /** + * Tos validator + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator + */ + protected function _tosValidator(Validator $validator) + { + $validator + ->requirePresence('tos', 'create') + ->notBlank('tos'); + + return $validator; + } + + /** + * Returns the list of validators + * + * @param array $options Array of options ['validate_email' => true/false, 'use_tos' => true/false] + * @return \Cake\Validation\Validator + */ + public function getRegisterValidators($options) + { + $validateEmail = $options['validate_email'] ?? null; + $useTos = $options['use_tos'] ?? null; + + $validator = $this->_table->validationDefault(new Validator()); + $validator = $this->_table->validationRegister($validator); + if ($useTos) { + $validator = $this->_tosValidator($validator); + } + + if ($validateEmail) { + $validator = $this->_emailValidator($validator, $validateEmail); + } + + 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 new file mode 100644 index 000000000..10408a060 --- /dev/null +++ b/src/Model/Behavior/SocialAccountBehavior.php @@ -0,0 +1,154 @@ +_table->belongsTo('Users')->setForeignKey('user_id')->setJoinType('INNER')->setClassName(Configure::read('Users.table')); + } + + /** + * After save callback + * + * @param \Cake\Event\EventInterface $event event + * @param \Cake\Datasource\EntityInterface $entity entity + * @param \ArrayObject $options options + * @return mixed + */ + public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + { + if ($entity->get('active')) { + return true; + } + $user = $this->_table->getAssociation('Users')->find() + ->where(['Users.id' => $entity->get('user_id'), 'Users.active' => true]) + ->first(); + if (empty($user)) { + return true; + } + + return $this->sendSocialValidationEmail($entity, $user); + } + + /** + * Send social validation email to the user + * + * @param \Cake\Datasource\EntityInterface $socialAccount social account + * @param \Cake\Datasource\EntityInterface $user user + * @return array + */ + protected function sendSocialValidationEmail(EntityInterface $socialAccount, EntityInterface $user) + { + return $this + ->getMailer(Configure::read('Users.Email.mailerClass') ?: 'CakeDC/Users.Users') + ->send('socialAccountValidation', [$user, $socialAccount]); + } + + /** + * Validates the social account + * + * @param string $provider provider + * @param string $reference reference + * @param string $token token + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * @throws \CakeDC\Users\Exception\AccountAlreadyActiveException + * @return \CakeDC\Users\Model\Entity\User + */ + public function validateAccount($provider, $reference, $token) + { + $socialAccount = $this->_table->find() + ->select(['id', 'provider', 'reference', 'active', 'token']) + ->where(['provider' => $provider, 'reference' => $reference]) + ->first(); + + if (!empty($socialAccount) && $socialAccount->token === $token) { + if ($socialAccount->active) { + throw new AccountAlreadyActiveException(__d('cake_d_c/users', 'Account already validated')); + } + } else { + throw new RecordNotFoundException( + __d('cake_d_c/users', 'Account not found for the given token and email.') + ); + } + + return $this->_activateAccount($socialAccount); + } + + /** + * Validates the social account + * + * @param string $provider provider + * @param string $reference reference + * @throws \Cake\Datasource\Exception\RecordNotFoundException + * @throws \CakeDC\Users\Exception\AccountAlreadyActiveException + * @return \CakeDC\Users\Model\Entity\User + */ + public function resendValidation($provider, $reference) + { + $socialAccount = $this->_table->find() + ->where(['provider' => $provider, 'reference' => $reference]) + ->contain('Users') + ->first(); + + if (!empty($socialAccount)) { + if ($socialAccount->active) { + throw new AccountAlreadyActiveException( + __d('cake_d_c/users', 'Account already validated') + ); + } + } else { + throw new RecordNotFoundException( + __d('cake_d_c/users', 'Account not found for the given token and email.') + ); + } + + return $this->sendSocialValidationEmail($socialAccount, $socialAccount->user); + } + + /** + * Activates an account + * + * @param \CakeDC\Users\Model\Entity\SocialAccount $socialAccount social account + * @return \Cake\Datasource\EntityInterface + */ + protected function _activateAccount($socialAccount) + { + $socialAccount->active = true; + + return $this->_table->save($socialAccount); + } +} diff --git a/src/Model/Behavior/SocialBehavior.php b/src/Model/Behavior/SocialBehavior.php new file mode 100644 index 000000000..7911ed802 --- /dev/null +++ b/src/Model/Behavior/SocialBehavior.php @@ -0,0 +1,306 @@ +_username = $config['username']; + } + + parent::initialize($config); + } + + /** + * Performs social login + * + * @param array $data Array social login. + * @param array $options Array option data. + * @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 = $data['id'] ?? null; + $existingAccount = $this->_table->SocialAccounts->find() + ->where([ + 'SocialAccounts.reference' => $reference, + 'SocialAccounts.provider' => $data['provider'] ?? null, + ]) + ->contain(['Users']) + ->first(); + if (empty($existingAccount->user)) { + $user = $this->_createSocialUser($data, $options); + if (!empty($user->social_accounts[0])) { + $existingAccount = $user->social_accounts[0]; + } else { + //@todo: what if we don't have a social account after createSocialUser? + throw new InvalidArgumentException( + __d('cake_d_c/users', 'Unable to login user with reference {0}', $reference) + ); + } + } else { + $user = $existingAccount->user; + $accountData = $this->extractAccountData($data); + $this->_table->SocialAccounts->patchEntity($existingAccount, $accountData); + $this->_table->SocialAccounts->save($existingAccount); + $event = $this->dispatchEvent(Plugin::EVENT_SOCIAL_LOGIN_EXISTING_ACCOUNT, [ + 'userEntity' => $user, + 'data' => $data, + ]); + + if ($event->getResult() instanceof EntityInterface) { + $user = $this->_table->save($event->getResult()); + } + } + if (!empty($existingAccount)) { + if (!$existingAccount->active) { + throw new AccountNotActiveException([ + $existingAccount->provider, + $existingAccount->reference, + ]); + } + if (!$user->active) { + throw new UserNotActiveException([ + $existingAccount->provider, + $existingAccount->$user, + ]); + } + } + + return $user; + } + + /** + * Creates social user, populate the user data based on the social login data first and save it + * + * @param array $data Array social user. + * @param array $options Array option data. + * @throws \CakeDC\Users\Exception\MissingEmailException + * @return bool|\Cake\Datasource\EntityInterface|mixed result of the save operation + */ + protected function _createSocialUser($data, $options = []) + { + $useEmail = $options['use_email'] ?? null; + $validateEmail = $options['validate_email'] ?? null; + $tokenExpiration = $options['token_expiration'] ?? null; + $existingUser = null; + $email = $data['email'] ?? null; + if ($useEmail && empty($email)) { + throw new MissingEmailException(__d('cake_d_c/users', 'Email not present')); + } else { + $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; + + return $this->_table->save($user); + } + + /** + * Build new user entity either by using an existing user or extracting the data from the social login + * data to create a new one + * + * @param array $data Array social login. + * @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 \Cake\Datasource\EntityInterface + * @todo refactor + */ + protected function _populateUser($data, $existingUser, $useEmail, $validateEmail, $tokenExpiration) + { + $userData = []; + $accountData = $this->extractAccountData($data); + $accountData['active'] = true; + + $dataValidated = $data['validated'] ?? null; + + if (empty($existingUser)) { + $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(' ', $data['full_name'] ?? ''); + $userData['first_name'] = Hash::get($name, 0); + array_shift($name); + $userData['last_name'] = implode(' ', $name); + } + $userData['username'] = $data['username'] ?? null; + $username = $userData['username'] ?? null; + if (empty($username)) { + $dataEmail = $data['email'] ?? null; + if (!empty($dataEmail)) { + $email = explode('@', $dataEmail); + $userData['username'] = Hash::get($email, 0); + } else { + $firstName = $userData['first_name'] ?? null; + $lastName = $userData['last_name'] ?? null; + $userData['username'] = strtolower($firstName . $lastName); + $userData['username'] = preg_replace('/[^A-Za-z0-9]/i', '', $userData['username'] ?? null); + } + } + + $userData['username'] = $this->generateUniqueUsername($userData['username'] ?? null); + if ($useEmail) { + $userData['email'] = $data['email'] ?? null; + if (empty($dataValidated)) { + $accountData['active'] = false; + } + } + + $userData['password'] = $this->randomString(); + $userData['avatar'] = $data['avatar'] ?? null; + $userData['validated'] = !empty($dataValidated); + $userData['tos_date'] = date('Y-m-d H:i:s'); + $userData['gender'] = $data['gender'] ?? null; + $userData['social_accounts'][] = $accountData; + + $user = $this->_table->newEntity($userData); + $user = $this->_updateActive($user, false, $tokenExpiration); + } else { + if ($useEmail && empty($dataValidated)) { + $accountData['active'] = false; + } + $user = $existingUser; + } + $socialAccount = $this->_table->SocialAccounts->newEntity($accountData); + //ensure provider is present in Entity + $socialAccount['provider'] = $data['provider'] ?? null; + $user['social_accounts'] = [$socialAccount]; + $user['role'] = Configure::read('Users.Registration.defaultRole') ?: 'user'; + + return $user; + } + + /** + * Checks if username exists and generate a new one + * + * @param string $username username data. + * @return string + */ + public function generateUniqueUsername($username) + { + $i = 0; + while (true) { + $existingUsername = $this->_table->find() + ->where([$this->_table->aliasField($this->_username) => $username]) + ->count(); + if ($existingUsername > 0) { + $username .= $i; + $i++; + continue; + } + break; + } + + 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 new file mode 100644 index 000000000..c8429655f --- /dev/null +++ b/src/Model/Entity/SocialAccount.php @@ -0,0 +1,43 @@ + true, + 'id' => false, + ]; + + /** + * Fields that are excluded from JSON an array versions of the entity. + * + * @var array + */ + protected $_hidden = [ + 'token', + 'token_secret', + 'token_expires', + ]; +} diff --git a/src/Model/Entity/User.php b/src/Model/Entity/User.php new file mode 100644 index 000000000..e007fc912 --- /dev/null +++ b/src/Model/Entity/User.php @@ -0,0 +1,193 @@ + true, + 'id' => false, + 'is_superuser' => false, + 'role' => false, + ]; + + /** + * Fields that are excluded from JSON an array versions of the entity. + * + * @var array + */ + protected $_hidden = [ + 'password', + 'token', + 'token_expires', + 'api_token', + ]; + + /** + * @param string $password password that will be set. + * @return bool|string + */ + protected function _setPassword($password) + { + return $this->hashPassword($password); + } + + /** + * @param string $password password that will be confirm. + * @return bool|string + */ + protected function _setConfirmPassword($password) + { + return $this->hashPassword($password); + } + + /** + * @param string $tos tos option. It will be set the tos_date + * @return bool + */ + protected function _setTos($tos) + { + if ((bool)$tos) { + $this->set('tos_date', FrozenTime::now()); + } + + return $tos; + } + + /** + * Hash a password using the configured password hasher, + * use DefaultPasswordHasher if no one was configured + * + * @param string $password password to be hashed + * @return mixed + */ + public function hashPassword($password) + { + $PasswordHasher = $this->getPasswordHasher(); + + return $PasswordHasher->hash((string)$password); + } + + /** + * Return the configured Password Hasher + * + * @return mixed + */ + public function getPasswordHasher() + { + $passwordHasher = Configure::read('Users.passwordHasher'); + if (!class_exists($passwordHasher)) { + $passwordHasher = \Cake\Auth\DefaultPasswordHasher::class; + } + + return new $passwordHasher(); + } + + /** + * Checks if a password is correctly hashed + * + * @param string $password password that will be check. + * @param string $hashedPassword hash used to check password. + * @return bool + */ + public function checkPassword($password, $hashedPassword) + { + $PasswordHasher = $this->getPasswordHasher(); + + return $PasswordHasher->check($password, $hashedPassword); + } + + /** + * Returns if the token has already expired + * + * @return bool + */ + public function tokenExpired() + { + if (empty($this->token_expires)) { + return true; + } + + return new FrozenTime($this->token_expires) < FrozenTime::now(); + } + + /** + * Getter for user avatar + * + * @return string|null avatar + */ + protected function _getAvatar() + { + $avatar = null; + 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 FrozenTime('now'); + $this->token_expires = $expiration->addSeconds($tokenExpiration); + $this->token = bin2hex(Security::randomBytes(16)); + } +} diff --git a/src/Model/Table/SocialAccountsTable.php b/src/Model/Table/SocialAccountsTable.php new file mode 100644 index 000000000..1d1578c19 --- /dev/null +++ b/src/Model/Table/SocialAccountsTable.php @@ -0,0 +1,135 @@ +setTable('social_accounts'); + $this->setDisplayField('id'); + $this->setPrimaryKey('id'); + $this->addBehavior('Timestamp'); + $this->addBehavior('CakeDC/Users.SocialAccount'); + } + + /** + * Default validation rules. + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator): Validator + { + $validator + ->add('id', 'valid', ['rule' => 'uuid']) + ->allowEmptyString('id', null, 'create'); + + $validator + ->requirePresence('provider', 'create') + ->notEmptyString('provider'); + + $validator + ->allowEmptyString('username'); + + $validator + ->requirePresence('reference', 'create') + ->notEmptyString('reference'); + + $validator + ->requirePresence('link', 'create') + ->notEmptyString('reference'); + + $validator + ->allowEmptyString('avatar'); + + $validator + ->allowEmptyString('description'); + + $validator + ->requirePresence('token', 'create') + ->notEmptyString('token'); + + $validator + ->allowEmptyString('token_secret'); + + $validator + ->add('token_expires', 'valid', ['rule' => 'datetime']) + ->allowEmptyString('token_expires'); + + $validator + ->add('active', 'valid', ['rule' => 'boolean']) + ->requirePresence('active', 'create') + ->notBlank('active'); + + $validator + ->requirePresence('data', 'create') + ->notBlank('data'); + + return $validator; + } + + /** + * Returns a rules checker object that will be used for validating + * application integrity. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + 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 new file mode 100644 index 000000000..4e6954348 --- /dev/null +++ b/src/Model/Table/UsersTable.php @@ -0,0 +1,213 @@ +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): void + { + parent::initialize($config); + + $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->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 \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator + */ + public function validationPasswordConfirm(Validator $validator) + { + $validator + ->requirePresence('password_confirm', 'create') + ->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; + } + + /** + * Adds rules for current password + * + * @param \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator + */ + public function validationCurrentPassword(Validator $validator) + { + $validator + ->notBlank('current_password'); + + return $validator; + } + + /** + * Default validation rules. + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator): Validator + { + $validator + ->allowEmptyString('id', null, 'create'); + + $validator + ->requirePresence('username', 'create') + ->notEmptyString('username'); + + $validator + ->requirePresence('password', 'create') + ->notEmptyString('password'); + + $validator + ->allowEmptyString('first_name'); + + $validator + ->allowEmptyString('last_name'); + + $validator + ->allowEmptyString('token'); + + $validator + ->add('token_expires', 'valid', ['rule' => 'datetime']) + ->allowEmptyDateTime('token_expires'); + + $validator + ->allowEmptyString('api_token'); + + $validator + ->add('activation_date', 'valid', ['rule' => 'datetime']) + ->allowEmptyDateTime('activation_date'); + + $validator + ->add('tos_date', 'valid', ['rule' => 'datetime']) + ->allowEmptyDateTime('tos_date'); + + return $validator; + } + + /** + * Wrapper for all validation rules for register + * + * @param \Cake\Validation\Validator $validator Cake validator object. + * @return \Cake\Validation\Validator + */ + public function validationRegister(Validator $validator) + { + $validator = $this->validationDefault($validator); + + return $this->validationPasswordConfirm($validator); + } + + /** + * Returns a rules checker object that will be used for validating + * application integrity. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + public function buildRules(RulesChecker $rules): RulesChecker + { + $rules->add($rules->isUnique(['username']), '_isUnique', [ + 'errorField' => 'username', + 'message' => __d('cake_d_c/users', 'Username already exists'), + ]); + + if ($this->isValidateEmail) { + $rules->add($rules->isUnique(['email']), '_isUnique', [ + 'errorField' => 'email', + 'message' => __d('cake_d_c/users', 'Email already exists'), + ]); + } + + return $rules; + } +} 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 new file mode 100644 index 000000000..bd38e2e44 --- /dev/null +++ b/src/Shell/UsersShell.php @@ -0,0 +1,452 @@ +setDescription(__d('cake_d_c/users', 'Utilities for CakeDC Users Plugin')) + ->addSubcommand('activateUser', [ + 'help' => __d('cake_d_c/users', 'Activate an specific user'), + ]) + ->addSubcommand('addSuperuser', [ + 'help' => __d('cake_d_c/users', 'Add a new superadmin user for testing purposes'), + ]) + ->addSubcommand('addUser', [ + 'help' => __d('cake_d_c/users', 'Add a new user'), + ]) + ->addSubcommand('changeRole', [ + 'help' => __d('cake_d_c/users', 'Change the role for an specific user'), + ]) + ->addSubcommand('changeApiToken', [ + 'help' => __d('cake_d_c/users', 'Change the api token for an specific user'), + ]) + ->addSubcommand('deactivateUser', [ + 'help' => __d('cake_d_c/users', 'Deactivate an specific user'), + ]) + ->addSubcommand('deleteUser', [ + 'help' => __d('cake_d_c/users', 'Delete an specific user'), + ]) + ->addSubcommand('passwordEmail', [ + 'help' => __d('cake_d_c/users', 'Reset the password via email'), + ]) + ->addSubcommand('resetAllPasswords', [ + 'help' => __d('cake_d_c/users', 'Reset the password for all users'), + ]) + ->addSubcommand('resetPassword', [ + 'help' => __d('cake_d_c/users', 'Reset the password for an specific user'), + ]) + ->addOptions([ + 'username' => ['short' => 'u', 'help' => 'The username for the new user'], + 'password' => ['short' => 'p', 'help' => 'The password for the new user'], + 'email' => ['short' => 'e', 'help' => 'The email for the new user'], + 'role' => ['short' => 'r', 'help' => 'The role for the new user'], + ]); + + return $parser; + } + + /** + * initialize callback + * + * @return void + */ + public function initialize(): void + { + parent::initialize(); + $this->Users = $this->loadModel(Configure::read('Users.table')); + } + + /** + * Add a new user + * + * @return void + */ + public function addUser() + { + $this->_createUser(['role' => Configure::read('Users.Registration.defaultRole') ?: 'user']); + } + + /** + * Add a new superadmin user + * + * @return void + */ + public function addSuperuser() + { + $this->_createUser([ + 'username' => 'superadmin', + 'role' => 'superuser', + 'is_superuser' => true, + ]); + } + + /** + * Reset password for all user + * + * Arguments: + * + * - Password to be set + * + * @return void + */ + public function resetAllPasswords() + { + $password = Hash::get($this->args, 0); + if (empty($password)) { + $this->abort(__d('cake_d_c/users', 'Please enter a password.')); + } + $hashedPassword = $this->_generatedHashedPassword($password); + $this->Users->updateAll(['password' => $hashedPassword], ['id IS NOT NULL']); + $this->out(__d('cake_d_c/users', 'Password changed for all users')); + $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); + } + + /** + * Reset password for a user + * + * Arguments: + * + * - Username + * - Password to be set + * + * @return void + */ + public function resetPassword() + { + $username = Hash::get($this->args, 0); + $password = Hash::get($this->args, 1); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($password)) { + $this->abort(__d('cake_d_c/users', 'Please enter a password.')); + } + $data = [ + 'password' => $password, + ]; + $this->_updateUser($username, $data); + $this->out(__d('cake_d_c/users', 'Password changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New password: {0}', $password)); + } + + /** + * Change role for a user + * + * Arguments: + * + * - Username + * - Role to be set + * + * @return void + */ + public function changeRole() + { + $username = Hash::get($this->args, 0); + $role = Hash::get($this->args, 1); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($role)) { + $this->abort(__d('cake_d_c/users', 'Please enter a role.')); + } + $data = [ + 'role' => $role, + ]; + $savedUser = $this->_updateUser($username, $data); + $this->out(__d('cake_d_c/users', 'Role changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New role: {0}', $savedUser->role)); + } + + /** + * Change api token for a user + * + * Arguments: + * + * - Username + * - Token to be set + * + * @return void + */ + public function changeApiToken() + { + $username = Hash::get($this->args, 0); + $token = Hash::get($this->args, 1); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + if (empty($token)) { + $this->abort(__d('cake_d_c/users', 'Please enter a token.')); + } + $data = [ + 'api_token' => $token, + ]; + $savedUser = $this->_updateUser($username, $data); + if (!$savedUser) { + $this->err(__d('cake_d_c/users', 'User was not saved, check validation errors')); + } + /** + * @var \CakeDC\Users\Model\Entity\User $savedUser + */ + $this->out(__d('cake_d_c/users', 'Api token changed for user: {0}', $username)); + $this->out(__d('cake_d_c/users', 'New token: {0}', $savedUser->api_token)); + } + + /** + * Activate an specific user + * + * Arguments: + * + * - Username + * + * @return void + */ + public function activateUser() + { + $user = $this->_changeUserActive(true); + $this->out(__d('cake_d_c/users', 'User was activated: {0}', $user->username)); + } + + /** + * De-activate an specific user + * + * Arguments: + * + * - Username + * + * @return void + */ + public function deactivateUser() + { + $user = $this->_changeUserActive(false); + $this->out(__d('cake_d_c/users', 'User was de-activated: {0}', $user->username)); + } + + /** + * Reset password via email for user + * + * @return void + */ + public function passwordEmail() + { + $reference = Hash::get($this->args, 0); + if (empty($reference)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username or email.')); + } + $resetUser = $this->Users->resetToken($reference, [ + 'expiration' => Configure::read('Users.Token.expiration'), + 'checkActive' => false, + 'sendEmail' => true, + ]); + if ($resetUser) { + $msg = __d( + 'cake_d_c/users', + 'Please ask the user to check the email to continue with password reset process' + ); + $this->out($msg); + } else { + $msg = __d( + 'cake_d_c/users', + 'The password token could not be generated. Please try again' + ); + $this->abort($msg); + } + } + + /** + * Change user active field + * + * @param bool $active active value + * @return bool + */ + protected function _changeUserActive($active) + { + $username = Hash::get($this->args, 0); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + $data = [ + 'active' => $active, + ]; + + return $this->_updateUser($username, $data); + } + + /** + * Create a new user or superuser + * + * @param array $template template with deafault user values + * @return void + */ + protected function _createUser($template) + { + if (!empty($this->params['username'])) { + $username = $this->params['username']; + } else { + $username = empty($template['username']) ? + $this->_generateRandomUsername() : $template['username']; + } + + $password = (empty($this->params['password']) ? + $this->_generateRandomPassword() : $this->params['password']); + $email = (empty($this->params['email']) ? + $username . '@example.com' : $this->params['email']); + $role = (empty($this->params['role']) ? + $template['role'] : $this->params['role']); + + $user = [ + 'username' => $this->Users->generateUniqueUsername($username), + 'email' => $email, + 'password' => $password, + 'active' => 1, + ]; + + $userEntity = $this->Users->newEntity($user); + $userEntity->is_superuser = empty($template['is_superuser']) ? + false : $template['is_superuser']; + $userEntity->role = $role; + $savedUser = $this->Users->save($userEntity); + + if (is_object($savedUser)) { + if ($savedUser->is_superuser) { + $this->out(__d('cake_d_c/users', 'Superuser added:')); + } else { + $this->out(__d('cake_d_c/users', 'User added:')); + } + $this->out(__d('cake_d_c/users', 'Id: {0}', $savedUser->id)); + $this->out(__d('cake_d_c/users', 'Username: {0}', $savedUser->username)); + $this->out(__d('cake_d_c/users', 'Email: {0}', $savedUser->email)); + $this->out(__d('cake_d_c/users', 'Role: {0}', $savedUser->role)); + $this->out(__d('cake_d_c/users', 'Password: {0}', $password)); + } else { + $this->out(__d('cake_d_c/users', 'User could not be added:')); + + collection($userEntity->getErrors())->each(function ($error, $field) { + $this->out(__d('cake_d_c/users', 'Field: {0} Error: {1}', $field, implode(',', $error))); + }); + } + } + + /** + * Update user by username + * + * @param string $username username + * @param array $data data + * @return \CakeDC\Users\Model\Entity\User|bool + */ + protected function _updateUser($username, $data) + { + $user = $this->Users->find()->where(['username' => $username])->first(); + if (!is_object($user)) { + $this->abort(__d('cake_d_c/users', 'The user was not found.')); + } + /** + * @var \Cake\Datasource\EntityInterface $user + */ + $user = $this->Users->patchEntity($user, $data); + collection($data)->filter(function ($value, $field) use ($user) { + return !$user->isAccessible($field); + })->each(function ($value, $field) use (&$user) { + $user->{$field} = $value; + }); + + return $this->Users->save($user); + } + + /** + * Delete an specific user and associated social accounts + * + * @return void + */ + public function deleteUser() + { + $username = Hash::get($this->args, 0); + if (empty($username)) { + $this->abort(__d('cake_d_c/users', 'Please enter a username.')); + } + /** + * @var \Cake\Datasource\EntityInterface $user + */ + $user = $this->Users->find()->where(['username' => $username])->firstOrFail(); + if (isset($this->Users->SocialAccounts)) { + $this->Users->SocialAccounts->deleteAll(['user_id' => $user->id]); + } + $deleteUser = $this->Users->delete($user); + if (!$deleteUser) { + $this->abort(__d('cake_d_c/users', 'The user {0} was not deleted. Please try again', $username)); + } + $this->out(__d('cake_d_c/users', 'The user {0} was deleted successfully', $username)); + } + + /** + * Generates a random password. + * + * @return string + */ + protected function _generateRandomPassword() + { + return str_replace('-', '', Text::uuid()); + } + + /** + * Generates a random username based on a list of preexisting ones. + * + * @return string + */ + protected function _generateRandomUsername() + { + return $this->_usernameSeed[array_rand($this->_usernameSeed)]; + } + + /** + * Hash a password + * + * @param string $password password + * @return string + */ + protected function _generatedHashedPassword($password) + { + return (new User())->hashPassword($password); + } + + //add filters LIKE in username and email to some tasks + // --force to ignore "you are about to do X to Y users" +} diff --git a/src/Traits/RandomStringTrait.php b/src/Traits/RandomStringTrait.php new file mode 100644 index 000000000..6caffad7b --- /dev/null +++ b/src/Traits/RandomStringTrait.php @@ -0,0 +1,33 @@ + $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 new file mode 100644 index 000000000..06274ae37 --- /dev/null +++ b/src/View/Helper/AuthLinkHelper.php @@ -0,0 +1,83 @@ +isAuthorized($url)) { + return ($options['before'] ?? '') . + parent::link($title, $url, $linkOptions) . + ($options['after'] ?? ''); + } + + return ''; + } + + /** + * Wrapper for FormHelper.postLink. + * Write the link only if user is authorized. + * + * @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 postLink($title, $url = null, array $options = []): string + { + return $this->isAuthorized($url) + ? $this->Form->postLink($title, $url, $options) + : ''; + } + + /** + * 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 new file mode 100644 index 000000000..00d32bd93 --- /dev/null +++ b/src/View/Helper/UserHelper.php @@ -0,0 +1,268 @@ +Html->tag('i', '', [ + 'class' => 'fa fa-' . 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, + ]); + } + + /** + * All available Social Login Icons + * + * @param array $providerOptions Provider link options. + * @return array Links to Social Login Urls + */ + public function socialLoginList(array $providerOptions = []) + { + if (!Configure::read('Users.Social.login')) { + return []; + } + $outProviders = []; + $providers = Configure::read('OAuth.providers'); + foreach ($providers as $provider => $options) { + if ( + !empty($options['options']['redirectUri']) && + !empty($options['options']['clientId']) && + !empty($options['options']['clientSecret']) + ) { + if (isset($providerOptions[$provider])) { + $options['options'] = Hash::merge($options['options'], $providerOptions[$provider]); + } + + $outProviders[] = $this->socialLogin($provider, $options['options']); + } + } + + return $outProviders; + } + + /** + * Logout link + * + * @param null $message logout message info. + * @param array $options Array with option data. + * @return string + */ + public function logout($message = null, $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 string|null + */ + public function welcome() + { + $identity = $this->getView()->getRequest()->getAttribute('identity'); + if (!$identity) { + return null; + } + + $profileUrl = Configure::read('Users.Profile.route'); + $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() + { + $this->Html->script('https://www.google.com/recaptcha/api.js', [ + 'block' => 'script', + ]); + } + + /** + * Add reCaptcha to the form + * + * @return mixed + */ + public function addReCaptcha() + { + if (!Configure::read('Users.reCaptcha.key')) { + return $this->Html->tag( + 'p', + __d( + 'cake_d_c/users', + 'reCaptcha is not configured! Please configure Users.reCaptcha.key' + ) + ); + } + $this->addReCaptchaScript(); + 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-theme' => Configure::read('Users.reCaptcha.theme') ?: 'light', + 'data-size' => Configure::read('Users.reCaptcha.size') ?: 'normal', + 'data-tabindex' => Configure::read('Users.reCaptcha.tabindex') ?: '3', + ]); + } + + /** + * 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. + * @return string + */ + public function link($title, $url = null, array $options = []) + { + trigger_error( + 'UserHelper::link() deprecated since 3.2.1. Use AuthLinkHelper::link() instead', + E_USER_DEPRECATED + ); + + return $this->AuthLink->link($title, $url, $options); + } + + /** + * Create links for all social providers enabled social link (connect) + * + * @param string $name Provider name in lowercase + * @param array $provider Provider configuration + * @param bool $isConnected User is connected with this provider + * @return string + */ + public function socialConnectLink($name, $provider, $isConnected = false) + { + $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 array $socialAccounts All social accounts connected by a user. + * @return string + */ + public function socialConnectLinkList($socialAccounts = []) + { + if (!Configure::read('Users.Social.login')) { + return ''; + } + $html = ''; + $connectedProviders = array_map( + function ($item) { + return strtolower($item->provider); + }, + (array)$socialAccounts + ); + + $providers = Configure::read('OAuth.providers'); + foreach ($providers as $name => $provider) { + if ( + !empty($provider['options']['callbackLinkSocialUri']) && + !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/templates/Users/profile.php b/templates/Users/profile.php new file mode 100644 index 000000000..bd786ffc1 --- /dev/null +++ b/templates/Users/profile.php @@ -0,0 +1,78 @@ + +
+

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

+

+ Html->tag( + 'span', + __d('cake_d_c/users', '{0} {1}', $user->first_name, $user->last_name), + ['class' => 'full_name'] + ) + ?> +

+ + 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('cake_d_c/users', 'Link to {0}', h($socialAccount->provider)) : h($socialAccount->username) + ?> + + + + + + + +
Html->image( + $socialAccount->avatar, + ['width' => '90', 'height' => '90'] + ) ?> + provider) ?>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/Fixture/PostsFixture.php b/tests/Fixture/PostsFixture.php new file mode 100644 index 000000000..4057cbbb8 --- /dev/null +++ b/tests/Fixture/PostsFixture.php @@ -0,0 +1,38 @@ + '00000000-0000-0000-0000-000000000001', + 'title' => 'post-1', + 'user_id' => '00000000-0000-0000-0000-000000000001', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000002', + 'title' => 'post-2', + 'user_id' => '00000000-0000-0000-0000-000000000002', + ], + ]; +} diff --git a/tests/Fixture/PostsUsersFixture.php b/tests/Fixture/PostsUsersFixture.php new file mode 100644 index 000000000..5f5350e9e --- /dev/null +++ b/tests/Fixture/PostsUsersFixture.php @@ -0,0 +1,38 @@ + '00000000-0000-0000-0000-000000000011', + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'post_id' => '00000000-0000-0000-0000-000000000001', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000012', + 'user_id' => '00000000-0000-0000-0000-000000000002', + 'post_id' => '00000000-0000-0000-0000-000000000002', + ], + ]; +} diff --git a/tests/Fixture/SocialAccountsFixture.php b/tests/Fixture/SocialAccountsFixture.php new file mode 100644 index 000000000..9789d2c45 --- /dev/null +++ b/tests/Fixture/SocialAccountsFixture.php @@ -0,0 +1,108 @@ + '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', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000002', + 'user_id' => '00000000-0000-0000-0000-000000000001', + 'provider' => 'Twitter', + 'username' => 'user-1-tw', + 'reference' => 'reference-1-1234', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => '', + 'token' => 'token-1234', + 'token_secret' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-05-22 21:52:44', + 'active' => true, + 'data' => '', + 'created' => '2015-05-22 21:52:44', + 'modified' => '2015-05-22 21:52:44', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000003', + 'user_id' => '00000000-0000-0000-0000-000000000002', + 'provider' => 'Facebook', + 'username' => 'user-2-fb', + 'reference' => 'reference-2-1', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => '', + 'token' => 'token-reference-2-1', + 'token_secret' => 'Lorem ipsum dolor sit amet', + 'token_expires' => '2015-05-22 21:52:44', + 'active' => true, + 'data' => '', + 'created' => '2015-05-22 21:52:44', + 'modified' => '2015-05-22 21:52:44', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000004', + 'user_id' => '00000000-0000-0000-0000-000000000003', + 'provider' => 'Twitter', + 'username' => 'user-2-tw', + 'reference' => 'reference-2-2', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => '', + 'token' => 'token-reference-2-2', + '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', + ], + [ + 'id' => '00000000-0000-0000-0000-000000000005', + 'user_id' => '00000000-0000-0000-0000-000000000004', + 'provider' => 'Twitter', + 'username' => 'user-2-tw', + 'reference' => 'reference-2-2', + 'avatar' => 'Lorem ipsum dolor sit amet', + 'description' => '', + 'token' => 'token-reference-2-2', + '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', + ], + ]; +} diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php new file mode 100644 index 000000000..d48f6dc55 --- /dev/null +++ b/tests/Fixture/UsersFixture.php @@ -0,0 +1,262 @@ +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', + ], + ]; + + parent::init(); + } +} 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/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/SocialAccountsControllerTest.php b/tests/TestCase/Controller/SocialAccountsControllerTest.php new file mode 100644 index 000000000..47a544d7e --- /dev/null +++ b/tests/TestCase/Controller/SocialAccountsControllerTest.php @@ -0,0 +1,197 @@ +configOpauth = Configure::read('Opauth'); + $this->configRememberMe = Configure::read('Users.RememberMe.active'); + Configure::write('Opauth', null); + Configure::write('Users.RememberMe.active', false); + + TransportFactory::setConfig('test', ['className' => 'Debug']); + $this->configEmail = Email::getConfig('default'); + Email::drop('default'); + Email::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', + ]); + + $request = new ServerRequest(['url' => '/users/users/index']); + $request = $request->withParam('plugin', 'CakeDC/Users'); + + $this->Controller = $this->getMockBuilder('CakeDC\Users\Controller\SocialAccountsController') + ->onlyMethods(['redirect', 'render']) + ->setConstructorArgs([$request, null, 'SocialAccounts']) + ->getMock(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + Email::drop('default'); + TransportFactory::drop('test'); + //Email::setConfig('default', $this->configEmail); + + Configure::write('Opauth', $this->configOpauth); + Configure::write('Users.RememberMe.active', $this->configRememberMe); + + parent::tearDown(); + } + + /** + * test + * + * @return void + */ + public function testValidateAccountHappy() + { + $this->Controller->expects($this->once()) + ->method('redirect') + ->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->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testValidateAccountInvalidToken() + { + $this->Controller->expects($this->once()) + ->method('redirect') + ->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->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testValidateAccountAlreadyActive() + { + $this->Controller->expects($this->once()) + ->method('redirect') + ->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->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testResendValidationHappy() + { + $behaviorMock = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialAccountBehavior') + ->onlyMethods(['sendSocialValidationEmail']) + ->setConstructorArgs([$this->Controller->SocialAccounts]) + ->getMock(); + $this->Controller->SocialAccounts->behaviors()->set('SocialAccount', $behaviorMock); + $behaviorMock->expects($this->once()) + ->method('sendSocialValidationEmail') + ->will($this->returnValue(true)); + $this->Controller->expects($this->once()) + ->method('redirect') + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); + + $this->Controller->resendValidation('Facebook', 'reference-1-1234'); + $this->assertEquals('Email sent successfully', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testResendValidationEmailError() + { + $behaviorMock = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialAccountBehavior') + ->onlyMethods(['sendSocialValidationEmail']) + ->setConstructorArgs([$this->Controller->SocialAccounts]) + ->getMock(); + $this->Controller->SocialAccounts->behaviors()->set('SocialAccount', $behaviorMock); + $behaviorMock->expects($this->once()) + ->method('sendSocialValidationEmail') + ->will($this->returnValue(false)); + $this->Controller->expects($this->once()) + ->method('redirect') + ->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->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testResendValidationInvalid() + { + $this->Controller->expects($this->once()) + ->method('redirect') + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]); + $this->Controller->resendValidation('Facebook', 'reference-invalid'); + $this->assertEquals('Invalid account', $this->Controller->getRequest()->getSession()->read('Flash.flash.0.message')); + } + + /** + * test + * + * @return void + */ + public function testResendValidationAlreadyActive() + { + $this->Controller->expects($this->once()) + ->method('redirect') + ->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->getRequest()->getSession()->read('Flash.flash.0.message')); + } +} diff --git a/tests/TestCase/Controller/Traits/BaseTraitTest.php b/tests/TestCase/Controller/Traits/BaseTraitTest.php new file mode 100644 index 000000000..693940e8a --- /dev/null +++ b/tests/TestCase/Controller/Traits/BaseTraitTest.php @@ -0,0 +1,359 @@ +loadPlugins(['CakeDC/Users' => ['routes' => true]]); + $traitMockMethods = array_unique(array_merge(['getUsersTable'], $this->traitMockMethods)); + $this->table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + try { + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->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()'); + } + + if ($this->mockDefaultEmail) { + TransportFactory::setConfig('test', [ + 'className' => 'Debug', + ]); + $this->configEmail = Email::getConfig('default'); + Email::drop('default'); + Email::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', + ]); + } + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->table, $this->Trait); + if ($this->mockDefaultEmail) { + Email::drop('default'); + TransportFactory::drop('test'); + //Email::setConfig('default', $this->setConfigEmail); + } + parent::tearDown(); + } + + /** + * 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; + } + + /** + * mock request for GET + * + * @return void + */ + protected function _mockRequestGet($withSession = false) + { + $methods = ['is', 'referer', 'getData']; + + if ($withSession) { + $methods[] = 'getSession'; + } + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods($methods) + ->getMock(); + $request->expects($this->any()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + $this->Trait->setRequest($request); + } + + /** + * mock Flash Component + * + * @return void + */ + protected function _mockFlash() + { + $this->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') + ->setMethods(['error', 'success']) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * mock Request for POST, is and allow methods + * + * @param mixed $with used in with + * @return void + */ + protected function _mockRequestPost($with = 'post') + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'getData', 'allow']) + ->getMock(); + $request->expects($this->any()) + ->method('is') + ->with($with) + ->will($this->returnValue(true)); + $this->Trait->setRequest($request); + } + + /** + * Mock Auth and retur user id 1 + * + * @return void + */ + protected function _mockAuthLoggedIn($user = []) + { + $user += [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'password' => '12345', + ]; + + $this->_mockAuthentication($user); + } + + /** + * Mock the Authentication service + * + * @param array $user + * @param array $failures + * @param \Authentication\Identifier\IdentifierCollection $identifiers custom identifiers collection + * @return void + */ + protected function _mockAuthentication($user = null, $failures = [], $identifiers = null) + { + if ($identifiers === null) { + $passwordIdentifier = $this->getMockBuilder(PasswordIdentifier::class) + ->setMethods(['needsPasswordRehash']) + ->getMock(); + $passwordIdentifier->expects($this->any()) + ->method('needsPasswordRehash') + ->willReturn(false); + $identifiers = new IdentifierCollection([]); + $identifiers->set('Password', $passwordIdentifier); + } + + $config = [ + 'identifiers' => [ + 'Authentication.Password', + ], + 'authenticators' => [ + 'Authentication.Session', + 'Authentication.Form', + ], + ]; + $authentication = $this->getMockBuilder(AuthenticationService::class)->setConstructorArgs([$config])->setMethods([ + 'getResult', + 'getFailures', + 'identifiers', + ])->getMock(); + + if ($user) { + $user = new User($user); + $identity = new Identity($user); + $result = new Result($user, Result::SUCCESS); + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('identity', $identity)); + } else { + $result = new Result($user, Result::FAILURE_CREDENTIALS_MISSING); + } + + $authentication->expects($this->any()) + ->method('getResult') + ->will($this->returnValue($result)); + + $authentication->expects($this->any()) + ->method('getFailures') + ->will($this->returnValue($failures)); + + $authentication->expects($this->any()) + ->method('identifiers') + ->will($this->returnValue($identifiers)); + + $this->Trait->setRequest($this->Trait->getRequest()->withAttribute('authentication', $authentication)); + + $controller = new Controller($this->Trait->getRequest()); + $registry = new ComponentRegistry($controller); + $this->Trait->Authentication = new AuthenticationComponent($registry, [ + 'loginRedirect' => $this->successLoginRedirect, + 'logoutRedirect' => $this->logoutRedirect, + 'loginAction' => $this->loginAction, + ]); + } + + /** + * Mock the 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, $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 new file mode 100644 index 000000000..8bad2e04b --- /dev/null +++ b/tests/TestCase/Controller/Traits/CustomUsersTableTraitTest.php @@ -0,0 +1,43 @@ +controller = $this->getMockBuilder('Cake\Controller\Controller') + ->setMethods(['header', 'redirect', 'render', '_stop']) + ->getMock(); + $this->controller->Trait = $this->getMockForTrait('CakeDC\Users\Controller\Traits\CustomUsersTableTrait'); + } + + public function tearDown(): void + { + parent::tearDown(); + } + + public function testGetUsersTable() + { + $table = $this->controller->Trait->getUsersTable(); + $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 new file mode 100644 index 000000000..79d0a860b --- /dev/null +++ b/tests/TestCase/Controller/Traits/LoginTraitTest.php @@ -0,0 +1,536 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + + parent::setUp(); + $this->Trait->setRequest(new ServerRequest()); + $this->Trait = $this->getMockBuilder('CakeDC\Users\Controller\UsersController') + ->setMethods(['dispatchEvent', 'redirect', 'set', 'loadComponent']) + ->getMock(); + + $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') + ->setMethods(['setConfig']) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * test + * + * @return void + */ + 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')); + + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is']) + ->getMock(); + + $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($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->assertSame($passwordBefore, $passwordAfter); + $lastLoginAfter = $userAfter['last_login']; + $this->assertNotEmpty($lastLoginAfter); + $now = \Cake\I18n\FrozenTime::now(); + $this->assertEqualsWithDelta($lastLoginAfter->timestamp, $now->timestamp, 2); + } + + /** + * test + * + * @return void + */ + public function testLoginRehash() + { + $passwordIdentifier = $this->getMockBuilder(PasswordIdentifier::class) + ->setMethods(['needsPasswordRehash']) + ->getMock(); + $passwordIdentifier->expects($this->once()) + ->method('needsPasswordRehash') + ->willReturn(true); + $identifiers = new IdentifierCollection([]); + $identifiers->set('Password', $passwordIdentifier); + + $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->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); + } + + /** + * test + * + * @return void + */ + public function testLoginGet() + { + $this->_mockDispatchEvent(new Event('event')); + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is']) + ->getMock(); + $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(); + } + + /** + * test + * + * @return void + */ + public function testLogout() + { + $this->_mockDispatchEvent(new Event('event')); + $this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent') + ->setMethods(['logout', 'user']) + ->disableOriginalConstructor() + ->getMock(); + $this->_mockAuthentication([ + 'id' => 1, + ]); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->logoutRedirect); + $this->Trait->Flash = $this->getMockBuilder('Cake\Controller\Component\FlashComponent') + ->setMethods(['success']) + ->disableOriginalConstructor() + ->getMock(); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('You\'ve successfully logged out'); + $this->Trait->logout(); + } + + /** + * Data provider for testLogin + */ + public function dataProviderLogin() + { + $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, + ]; + + 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 socialLogin/login failure + * + * @dataProvider dataProviderLogin + * @return void + */ + public function testLogin($AuthClass, $resultStatus, $message, $method, $failureConfig) + { + $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->_mockAuthentication(null, $failures); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with($message); + + $registry = new ComponentRegistry(); + $Login = $this->getMockBuilder(LoginComponent::class) + ->setMethods(['getController']) + ->setConstructorArgs([$registry, $failureConfig]) + ->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($failureConfig) + ) + ->will($this->returnValue($Login)); + + if ($method === 'login') { + $this->Trait->expects($this->never()) + ->method('redirect'); + $result = $this->Trait->$method(); + $this->assertNull($result); + } else { + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login', 'prefix' => false]) + ->will($this->returnValue(new Response())); + $result = $this->Trait->$method(); + $this->assertInstanceOf(Response::class, $result); + } + } + + /** + * test socialLogin success + * + * @return void + */ + public function testSocialLoginSuccess() + { + $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->_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->_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(); + + $Login->expects($this->any()) + ->method('getController') + ->will($this->returnValue($this->Trait)); + $this->Trait->expects($this->any()) + ->method('loadComponent') + ->with( + $this->equalTo('CakeDC/Users.Login'), + $this->equalTo($config) + ) + ->will($this->returnValue($Login)); + + $result = $this->Trait->socialLogin(); + $this->assertInstanceOf(Response::class, $result); + } +} diff --git a/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php new file mode 100644 index 000000000..683bfc0c7 --- /dev/null +++ b/tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php @@ -0,0 +1,271 @@ + 'CakeDC/Users', + 'prefix' => false, + 'controller' => 'users', + 'action' => 'login', + ]; + + /** + * setup + * + * @return void + */ + public function setUp(): void + { + $this->traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + + parent::setUp(); + $request = new ServerRequest(); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods(['dispatchEvent', 'redirect', 'set', 'getUsersTable']) + ->getMock(); + + $this->Trait->setRequest($request); + Configure::write('Auth.AuthenticationComponent.loginAction', $this->loginPage); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * testVerifyHappy + */ + public function testVerifyHappy() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + $this->Trait->expects($this->never()) + ->method('redirect'); + + $this->_mockSession([ + 'temporarySession' => [ + 'id' => 1, + 'secret_verified' => 1, + ], + ]); + + $this->Trait->verify(); + } + + /** + * testVerifyHappy + */ + public function testVerifyNotEnabled() + { + $this->_mockFlash(); + Configure::write('OneTimePasswordAuthenticator.login', false); + $this->Trait->setRequest($this->Trait->getRequest()->withQueryParams(['redirect' => 'dashboard/list'])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Please enable Google Authenticator first.'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with($this->loginPage + ['?' => ['redirect' => 'dashboard/list']]); + + $this->Trait->verify(); + } + + /** + * testVerifyHappy + */ + public function testVerifyGetShowQR() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + $this->Trait->OneTimePasswordAuthenticator = $this->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + + $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => 0, + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + + $this->Trait->getRequest()->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + $this->Trait->OneTimePasswordAuthenticator->expects($this->once()) + ->method('createSecret') + ->will($this->returnValue('newSecret')); + $this->Trait->OneTimePasswordAuthenticator->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'newSecret') + ->will($this->returnValue('newDataUriGenerated')); + $this->Trait->expects($this->once()) + ->method('set') + ->with(['secretDataUri' => 'newDataUriGenerated']); + + $this->Trait->verify(); + $user = $this->Trait->getUsersTable()->findById('00000000-0000-0000-0000-000000000001')->firstOrFail(); + $this->assertEquals('newSecret', $user->secret); + } + + /** + * Tests that a GET request causes a a new secret to be generated in case it's + * not already present in the session. + */ + public function testVerifyGetGeneratesNewSecret() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + + $this->Trait->OneTimePasswordAuthenticator = $this + ->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait + ->getRequest() + ->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('createSecret') + ->will($this->returnValue('newSecret')); + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'newSecret') + ->will($this->returnValue('newDataUriGenerated')); + + $session = $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + $this->Trait->verify(); + + $this->assertEquals( + [ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'newSecret', + ], + ], + $session->read() + ); + } + + /** + * Tests that a GET request does not cause a new secret to be generated in case + * it's already present in the session. + */ + public function testVerifyGetDoesNotGenerateNewSecret() + { + Configure::write('OneTimePasswordAuthenticator.login', true); + + $this->Trait->OneTimePasswordAuthenticator = $this + ->getMockBuilder(OneTimePasswordAuthenticatorComponent::class) + ->disableOriginalConstructor() + ->setMethods(['createSecret', 'getQRCodeImageAsDataUri']) + ->getMock(); + + $request = $this->getMockBuilder(ServerRequest::class) + ->setMethods(['is', 'getData', 'allow', 'getSession']) + ->getMock(); + $this->Trait->setRequest($request); + $this->Trait + ->getRequest() + ->expects($this->once()) + ->method('is') + ->with('post') + ->will($this->returnValue(false)); + + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->never()) + ->method('createSecret'); + $this->Trait->OneTimePasswordAuthenticator + ->expects($this->once()) + ->method('getQRCodeImageAsDataUri') + ->with('email@example.com', 'alreadyPresentSecret') + ->will($this->returnValue('newDataUriGenerated')); + + $session = $this->_mockSession([ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'alreadyPresentSecret', + ], + ]); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue(TableRegistry::getTableLocator()->get('CakeDC/Users.Users'))); + $this->Trait->verify(); + + $this->assertEquals( + [ + 'temporarySession' => [ + 'id' => '00000000-0000-0000-0000-000000000001', + 'email' => 'email@example.com', + 'secret_verified' => false, + 'secret' => 'alreadyPresentSecret', + ], + ], + $session->read() + ); + } +} diff --git a/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php new file mode 100644 index 000000000..9a3d8a6ad --- /dev/null +++ b/tests/TestCase/Controller/Traits/PasswordManagementTraitTest.php @@ -0,0 +1,504 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['set', 'redirect', 'validate', 'log', 'dispatchEvent']; + $this->mockDefaultEmail = true; + parent::setUp(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordHappy() + { + $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', + ])); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['prefix' => false, 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']); + $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 + * + * @return void + */ + public function testChangePasswordWithError() + { + $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' => 'wrong_new', + ])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Password could not be changed'); + $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 + * + * @return void + */ + public function testChangePasswordWithSamePassword() + { + $this->assertEquals( + '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', + $this->table->get('00000000-0000-0000-0000-000000000006')->password + ); + $this->_mockRequestPost(['post', 'put']); + $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'current_password' => '12345', + 'password' => '12345', + 'password_confirm' => '12345', + ])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('You cannot use the current password as the new one'); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordWithEmptyCurrentPassword() + { + $this->_mockRequestPost(['post', 'put']); + $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'current_password' => '', + 'password' => '54321', + 'password_confirm' => '54321', + ])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Password could not be changed'); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordWithWrongCurrentPassword() + { + $this->assertEquals( + '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC', + $this->table->get('00000000-0000-0000-0000-000000000006')->password + ); + $this->_mockRequestPost(['post', 'put']); + $this->_mockAuthLoggedIn(['id' => '00000000-0000-0000-0000-000000000006', 'password' => '$2y$10$IPPgJNSfvATsMBLbv/2r8OtpyTBibyM1g5GDxD4PivW9qBRwRkRbC']); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'current_password' => 'wrong-password', + 'password' => '12345', + 'password_confirm' => '12345', + ])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('The current password does not match'); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordWithInvalidUser() + { + $this->_mockRequestPost(['post', 'put']); + $this->_mockAuthLoggedIn(['id' => '12312312-0000-0000-0000-000000000002', 'password' => 'invalid-pass']); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'password' => 'new', + 'password_confirm' => 'new', + ])); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User was not found'); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordGetLoggedIn() + { + $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') + ->will($this->returnCallback(function ($param1, $param2 = null) { + if ($param1 === 'validatePassword') { + TestCase::assertEquals($param2, true); + } + })); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordGetNotLoggedInInsideResetPasswordFlow() + { + $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', + ]); + $this->Trait->expects($this->any()) + ->method('set') + ->will($this->returnCallback(function ($param1, $param2 = null) { + if ($param1 === 'validatePassword') { + TestCase::assertEquals($param2, false); + } + })); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testChangePasswordGetNotLoggedInOutsideResetPasswordFlow() + { + $this->_mockRequestGet(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User was not found'); + $this->Trait->changePassword(); + } + + /** + * test + * + * @return void + */ + public function testResetPassword() + { + $token = 'token'; + $this->Trait->expects($this->once()) + ->method('validate') + ->with('password', $token); + $this->Trait->resetPassword($token); + } + + /** + * test + * + * @return void + */ + public function testRequestResetPasswordGet() + { + $this->assertEquals('ae93ddbe32664ce7927cf0c5c5a5e59d', $this->table->get('00000000-0000-0000-0000-000000000001')->token); + $this->_mockRequestGet(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->never()) + ->method('getData'); + $this->Trait->requestResetPassword(); + } + + /** + * test + * + * @return void + */ + public function testRequestPasswordHappy() + { + $this->assertEquals('6614f65816754310a5f0553436dd89e9', $this->table->get('00000000-0000-0000-0000-000000000002')->token); + $this->_mockRequestPost(); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $reference = 'user-2'; + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue($reference)); + $this->Trait->Flash->expects($this->any()) + ->method('success') + ->with('Please check your email to continue with password reset process'); + $this->Trait->requestResetPassword(); + $this->assertNotEquals('xxx', $this->table->get('00000000-0000-0000-0000-000000000002')->token); + } + + /** + * test + * + * @return void + */ + public function testRequestPasswordInvalidUser() + { + $this->_mockRequestPost(); + $this->_mockAuthLoggedIn(['id' => 'invalid-id', 'password' => 'invalid-pass']); + $this->_mockFlash(); + $reference = '12312312-0000-0000-0000-000000000002'; + $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->expects($this->never()) + ->method('redirect'); + $actual = $this->Trait->requestResetPassword(); + $this->assertNull($actual); + } + + /** + * test + * + * @return void + */ + public function testRequestPasswordEmptyReference() + { + $this->_mockRequestPost(); + $this->_mockAuthLoggedIn(['id' => 'invalid-id', 'password' => 'invalid-pass']); + $this->_mockFlash(); + $reference = ''; + $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->expects($this->never()) + ->method('redirect'); + $actual = $this->Trait->requestResetPassword(); + $this->assertNull($actual); + } + + /** + * @dataProvider ensureUserActiveForResetPasswordFeature + * @return void + */ + public function testEnsureUserActiveForResetPasswordFeature($ensureActive) + { + $expectError = $this->never(); + + if ($ensureActive) { + Configure::write('Users.Registration.ensureActive', true); + $expectError = $this->once(); + } + + $this->assertEquals('ae93ddbe32664ce7927cf0c5c5a5e59d', $this->table->get('00000000-0000-0000-0000-000000000001')->token); + $this->_mockRequestPost(); + $this->_mockFlash(); + $reference = 'user-1'; + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue($reference)); + $this->Trait->Flash->expects($expectError) + ->method('error') + ->with('The user is not active'); + $this->Trait->requestResetPassword(); + $this->assertNotEquals('xxx', $this->table->get('00000000-0000-0000-0000-000000000001')->token); + } + + public function ensureUserActiveForResetPasswordFeature() + { + $ensureActive = true; + $defaultBehavior = false; + + return [ + [$ensureActive], + [$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 new file mode 100644 index 000000000..24dd1912c --- /dev/null +++ b/tests/TestCase/Controller/Traits/ProfileTraitTest.php @@ -0,0 +1,111 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['set', 'getUsersTable', 'redirect', 'validate']; + parent::setUp(); + } + + /** + * test + * + * @return void + */ + public function testProfileGetNotLoggedInUserNotFound() + { + $userId = '00000000-0000-0000-0000-000000000000'; //not found + $this->_mockRequestGet(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User was not found'); + $this->Trait->profile($userId); + } + + /** + * test + * + * @return void + */ + public function testProfileGetLoggedInUserNotFound() + { + $userId = '00000000-0000-0000-0000-000000000000'; //not found + $this->_mockRequestGet(); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User was not found'); + $this->Trait->profile($userId); + } + + /** + * test + * + * @return void + */ + public function testProfileGetNotLoggedInEmptyId() + { + $this->_mockRequestGet(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Not authorized, please login first'); + $this->Trait->profile(); + } + + /** + * test + * + * @return void + */ + public function testProfileGetLoggedInMyProfile() + { + $this->_mockRequestGet(); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $this->Trait->expects($this->any()) + ->method('set') + ->will($this->returnCallback(function ($param1, $param2 = null) { + if ($param1 === 'avatarPlaceholder') { + BaseTraitTest::assertEquals('CakeDC/Users.avatar_placeholder.png', $param2); + } elseif (is_array($param1)) { + BaseTraitTest::assertEquals('user-1', $param1['user']->username); + } + })); + $this->Trait->profile(); + } +} 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/RegisterTraitTest.php b/tests/TestCase/Controller/Traits/RegisterTraitTest.php new file mode 100644 index 000000000..1ee7df055 --- /dev/null +++ b/tests/TestCase/Controller/Traits/RegisterTraitTest.php @@ -0,0 +1,497 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['validate', 'dispatchEvent', 'set', 'validateReCaptcha', 'redirect']; + $this->mockDefaultEmail = true; + parent::setUp(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * test + * + * @return void + */ + public function testValidateEmail() + { + $token = 'token'; + $this->Trait->expects($this->once()) + ->method('validate') + ->with('email', $token); + $this->Trait->validateEmail($token); + } + + /** + * test + * + * @return void + */ + 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->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $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->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, + ]; + + $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()); + } + + /** + * test + * + * @return void + */ + 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->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('Please validate your account before log in'); + $this->Trait->expects($this->once()) + ->method('validateRecaptcha') + ->will($this->returnValue(true)); + $this->Trait->expects($this->once()) + ->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()); + } + + /** + * test + * + * @return void + */ + public function testRegisterValidationErrors() + { + Configure::write('Users.reCaptcha.registration', true); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $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->expects($this->once()) + ->method('validateRecaptcha') + ->will($this->returnValue(true)); + $this->Trait->expects($this->never()) + ->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()); + } + + /** + * test + * + * @return void + */ + public function testRegisterRecaptchaNotValid() + { + Configure::write('Users.reCaptcha.registration', true); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->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()); + } + + /** + * test + * + * @return void + */ + public function testRegisterGet() + { + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestGet(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->never()) + ->method('success'); + $this->Trait->expects($this->never()) + ->method('validateRecaptcha'); + $this->Trait->expects($this->never()) + ->method('redirect'); + $this->Trait->register(); + + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + } + + /** + * test + * + * @return void + */ + public function testRegisterRecaptchaDisabled() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + + Configure::write('Users.Registration.reCaptcha', false); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('Please validate your account before log in'); + $this->Trait->expects($this->never()) + ->method('validateRecaptcha'); + $this->Trait->expects($this->once()) + ->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()); + } + + /** + * test + * + * @return void + */ + public function testRegisterNotEnabled() + { + $this->expectException(NotFoundException::class); + Configure::write('Users.Registration.active', false); + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->register(); + } + + /** + * test + * + * @return void + */ + public function testRegisterLoggedInUserAllowed() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/users/validate-email/*', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'validateEmail', + ]); + + Configure::write('Users.Registration.allowLoggedIn', true); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $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->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()); + } + + /** + * test + * + * @return void + */ + public function testRegisterLoggedInUserNotAllowed() + { + Configure::write('Users.Registration.allowLoggedIn', false); + $this->assertEquals(0, $this->table->find()->where(['username' => 'testRegistration'])->count()); + $this->_mockRequestPost(); + $this->_mockAuthLoggedIn(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->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->getRequest()->expects($this->never()) + ->method('getData') + ->with(); + + $this->Trait->register(); + } + + /** + * test + * + * @return void + */ + public function testNotShowingVerboseErrorOnRegisterWithDefaultConfig() + { + //register user and not validate the email + $this->testRegister(); + + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('The user could not be saved'); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'username' => 'testRegistration', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); + + $this->Trait->register(); + } + + /** + * test + * + * @return void + */ + public function testShowingVerboseErrorOnRegisterWithUpdatedConfig() + { + //register user and not validate the email + $this->testRegister(); + + $this->_mockRequestPost(); + $this->_mockAuthentication(); + $this->_mockFlash(); + $this->_mockDispatchEvent(); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Email already exists'); + + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->will($this->returnValue([ + 'username' => 'testRegistration1', + 'password' => 'password', + 'email' => 'test-registration@example.com', + 'password_confirm' => 'password', + 'tos' => 1, + ])); + Configure::write('Users.Registration.showVerboseError', true); + $this->Trait->register(); + } +} diff --git a/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php new file mode 100644 index 000000000..dc792ee49 --- /dev/null +++ b/tests/TestCase/Controller/Traits/SimpleCrudTraitTest.php @@ -0,0 +1,322 @@ +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; + })); + $this->Trait->expects($this->once()) + ->method('loadModel') + ->will($this->returnValue($this->table)); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + $this->viewVars = null; + parent::tearDown(); + } + + /** + * test + * + * @return void + */ + public function testIndex() + { + $this->Trait->expects($this->once()) + ->method('paginate') + ->with($this->table) + ->will($this->returnValue([])); + $this->Trait->index(); + $expected = [ + 'Users' => [], + 'tableAlias' => 'Users', + '_serialize' => [ + 'Users', + 'tableAlias', + ], + ]; + $this->assertSame($expected, $this->viewVars); + } + + /** + * test + * + * @return void + */ + public function testView() + { + $id = '00000000-0000-0000-0000-000000000001'; + $this->Trait->view($id); + $expected = [ + 'Users' => $this->table->get($id), + 'tableAlias' => 'Users', + '_serialize' => [ + 'Users', + 'tableAlias', + ], + ]; + $this->assertEquals($expected, $this->viewVars); + } + + /** + * test + * + * @return void + */ + public function testViewNotFound() + { + $this->expectException(RecordNotFoundException::class); + $this->Trait->view('00000000-0000-0000-0000-000000000000'); + } + + /** + * test + * + * @return void + */ + public function testViewInvalidPK() + { + $this->expectException(InvalidPrimaryKeyException::class); + $this->Trait->view(); + } + + /** + * test + * + * @return void + */ + public function testAddGet() + { + $this->_mockRequestGet(); + $this->Trait->add(); + $expected = [ + 'Users' => $this->table->newEmptyEntity(), + 'tableAlias' => 'Users', + '_serialize' => [ + 'Users', + 'tableAlias', + ], + ]; + $this->assertEquals($expected, $this->viewVars); + } + + /** + * test + * + * @return void + */ + public function testAddPostHappy() + { + $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); + $this->_mockRequestPost(); + $this->_mockFlash(); + $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'); + + $this->Trait->add(); + + $this->assertSame(1, $this->table->find()->where(['username' => 'testuser'])->count()); + } + + /** + * test + * + * @return void + */ + public function testAddPostErrors() + { + $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); + $this->_mockRequestPost(); + $this->_mockFlash(); + $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') + ->with('The User could not be saved'); + + $this->Trait->add(); + + $this->assertSame(0, $this->table->find()->where(['username' => 'testuser'])->count()); + } + + /** + * test + * + * @return void + */ + 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->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') + ->with('The User has been saved'); + + $this->Trait->edit('00000000-0000-0000-0000-000000000001'); + + $this->assertEquals('newtestuser@test.com', $this->table->get('00000000-0000-0000-0000-000000000001')->email); + } + + /** + * test + * + * @return void + */ + 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->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') + ->with('The User could not be saved'); + + $this->Trait->edit('00000000-0000-0000-0000-000000000001'); + + $this->assertEquals('user-1@test.com', $this->table->get('00000000-0000-0000-0000-000000000001')->email); + } + + /** + * test + * + * @return void + */ + public function testDeleteHappy() + { + $this->expectException(RecordNotFoundException::class); + $this->assertNotEmpty($this->table->get('00000000-0000-0000-0000-000000000001')); + $this->_mockRequestPost(); + $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()) + ->method('success') + ->with('The User has been deleted'); + + $this->Trait->delete('00000000-0000-0000-0000-000000000001'); + + $this->table->get('00000000-0000-0000-0000-000000000001'); + } + + /** + * test + * + * @return void + */ + public function testDeleteNotFound() + { + $this->expectException(RecordNotFoundException::class); + $this->assertNotEmpty($this->table->get('00000000-0000-0000-0000-000000000001')); + $this->_mockRequestPost(); + + $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 new file mode 100644 index 000000000..b86d5a1a6 --- /dev/null +++ b/tests/TestCase/Controller/Traits/SocialTraitTest.php @@ -0,0 +1,137 @@ +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', 'loadComponent']) + ->getMock(); + + $this->Trait->request = $request; + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * test socialLogin success + * + * @return void + */ + public function testSocialEmailSuccess() + { + $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->_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->_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(); + + $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 new file mode 100644 index 000000000..584d8596c --- /dev/null +++ b/tests/TestCase/Controller/Traits/UserValidationTraitTest.php @@ -0,0 +1,268 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set']; + $this->mockDefaultEmail = true; + parent::setUp(); + } + + /** + * test + * + * @return void + */ + public function testValidateHappyEmail() + { + $this->_mockFlash(); + $user = $this->table->findByToken('token-3')->first(); + $this->assertFalse($user->active); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('User account validated successfully'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->validate('email', 'token-3'); + $user = $this->table->findById($user->id)->first(); + $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 + * + * @return void + */ + public function testValidateUserNotFound() + { + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Invalid token or user account already validated'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->validate('email', 'not-found'); + } + + /** + * test + * + * @return void + */ + public function testValidateTokenExpired() + { + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Token already expired'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $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 + * + * @return void + */ + public function testValidateInvalidOp() + { + $this->_mockFlash(); + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('Invalid validation type'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->validate('invalid-op', '6614f65816754310a5f0553436dd89e9'); + } + + /** + * test + * + * @return void + */ + public function testValidateHappyPassword() + { + $this->_mockRequestGet(); + $this->_mockFlash(); + $user = $this->table->findByToken('token-4')->first(); + $this->assertTrue($user->active); + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('Reset password token was validated successfully'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'changePassword']); + $this->Trait->validate('password', 'token-4'); + $user = $this->table->findById($user->id)->first(); + $this->assertTrue($user->active); + } + + /** + * test + * + * @return void + */ + public function testResendTokenValidationHappy() + { + $this->_mockRequestPost(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue('user-3')); + + $this->Trait->Flash->expects($this->once()) + ->method('success') + ->with('Token has been reset successfully. Please check your email.'); + $this->Trait->expects($this->once()) + ->method('redirect') + ->with(['action' => 'login']); + $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 + * + * @return void + */ + public function testResendTokenValidationAlreadyActive() + { + $this->_mockRequestPost(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue('user-4')); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User user-4 is already active'); + $this->Trait->expects($this->never()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->resendTokenValidation(); + } + + /** + * test + * + * @return void + */ + public function testResendTokenValidationNotFound() + { + $this->_mockRequestPost(); + $this->_mockFlash(); + $this->Trait->getRequest()->expects($this->once()) + ->method('getData') + ->with('reference') + ->will($this->returnValue('not-found')); + + $this->Trait->Flash->expects($this->once()) + ->method('error') + ->with('User not-found was not found'); + $this->Trait->expects($this->never()) + ->method('redirect') + ->with(['action' => 'login']); + $this->Trait->resendTokenValidation(); + } +} diff --git a/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php new file mode 100644 index 000000000..e47f2c661 --- /dev/null +++ b/tests/TestCase/Controller/Traits/Webauthn2faTraitTest.php @@ -0,0 +1,493 @@ +traitClassName = 'CakeDC\Users\Controller\UsersController'; + $this->traitMockMethods = ['dispatchEvent', 'isStopped', 'redirect', 'getUsersTable', 'set', 'createU2fLib', 'getData', 'getU2fAuthenticationChecker']; + + parent::setUp(); + + $request = new ServerRequest(); + $this->Trait->setRequest($request); + Configure::write('Webauthn2fa.enabled', true); + Configure::write('Webauthn2fa.appName', 'ACME Webauthn Server'); + Configure::write('Webauthn2fa.id', 'localhost'); + } + + /** + * Mock session and mock session attributes + * + * @return \Cake\Http\Session + */ + protected function _mockSession($attributes) + { + $session = new \Cake\Http\Session(); + + foreach ($attributes as $field => $value) { + $session->write($field, $value); + } + + $this->Trait + ->getRequest() + ->expects($this->any()) + ->method('getSession') + ->willReturn($session); + + return $session; + } + + /** + * Test webauthn2fa method when requires register + * + * @return void + */ + public function testWebauthn2faIsRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->Trait + ->expects($this->exactly(2)) + ->method('set') + ->withConsecutive(['isRegister', true], ['username', 'user-2']); + $this->Trait->webauthn2fa(); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2fa method when DON'T require register + * + * @return void + */ + public function testWebauthn2faDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->Trait + ->expects($this->exactly(2)) + ->method('set') + ->withConsecutive(['isRegister', false], ['username', 'user-1']); + $this->Trait->webauthn2fa(); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faRegisterOptions method when DON'T require register + * + * @return void + */ + public function testWebauthn2faRegisterOptionsDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('User already has configured webauthn2fa'); + $this->Trait->webauthn2faRegisterOptions(); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegisterOptions() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + + $response = $this->Trait->webauthn2faRegisterOptions(); + $data = json_decode((string)$response->getBody(), true); + $this->assertArrayHasKey('rp', $data); + $this->assertArrayHasKey('user', $data); + $this->assertSame('user-2', $data['user']['name']); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faRegister method when DON'T require register + * + * @return void + */ + public function testWebauthn2faRegisterDontRequireRegister() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('User already has configured webauthn2fa'); + $this->Trait->webauthn2faRegister(); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegister() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(RegisterAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000002'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willReturn($credential); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faRegisterAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + $request = $request->withParsedBody( + json_decode($data, true) + ); + $this->Trait->setRequest($request); + $response = $this->Trait->webauthn2faRegister(); + $this->assertEquals('{"success":true}', (string)$response->getBody()); + } + + /** + * Test webauthn2faRegisterOptions method + * + * @return void + */ + public function testWebauthn2faRegisterError() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(RegisterAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willThrowException(new \Exception('Testing error exception for webauthn2faRegister')); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faRegisterAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faRegisterAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $data = '{"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA","response":{"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9","attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"},"type":"public-key"}'; + $request = $request->withParsedBody( + json_decode($data, true) + ); + $this->Trait->setRequest($request); + $this->expectException(\Exception::class); + $this->expectErrorMessage('Testing error exception for webauthn2faRegister'); + $this->Trait->webauthn2faRegister(); + } + + /** + * Test webauthn2faAuthenticateOptions + * + * @return void + */ + public function testWebauthn2faAuthenticateOptions() + { + $request = $this->getMockBuilder('Cake\Http\ServerRequest') + ->setMethods(['getSession', 'is']) + ->getMock(); + $this->Trait->setRequest($request); + $request->expects($this->any()) + ->method('is') + ->with( + $this->equalTo('ssl') + )->will($this->returnValue(true)); + + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000001'); + $this->_mockSession([ + 'Webauthn2fa.User' => $user, + ]); + + $response = $this->Trait->webauthn2faAuthenticateOptions(); + $data = json_decode((string)$response->getBody(), true); + $this->assertArrayHasKey('challenge', $data); + $this->assertArrayHasKey('userVerification', $data); + $this->assertArrayHasKey('allowCredentials', $data); + $expectedCredentials = [ + [ + 'type' => 'public-key', + 'id' => '12b37486-9299-4331-ac33-85b2d985b6fe', + ], + ]; + $this->assertEquals($expectedCredentials, $data['allowCredentials']); + $this->assertSame( + $user, + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + } + + /** + * Test webauthn2faAuthenticateOptions method + * + * @return void + */ + public function testWebauthn2faAuthenticate() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(AuthenticateAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + $publicKeyCredentialId = '12b37486-9299-4331-ac33-85b2d985b6fe'; + $userId = '00000000-0000-0000-0000-000000000002'; + $credentialData = [ + 'publicKeyCredentialId' => $publicKeyCredentialId, + 'type' => 'public-key', + 'transports' => [], + 'attestationType' => 'none', + 'trustPath' => [ + 'type' => 'Webauthn\TrustPath\EmptyTrustPath', + ], + 'aaguid' => '00000000-0000-0000-0000-000000000000', + 'credentialPublicKey' => Base64Url::encode('000000000000000000000000000000000000-9999999999999999999999999999999999999999-XXXXXXXXXXXXX-YYYYYYYYYYY'), + 'userHandle' => Base64Url::encode($userId), + 'counter' => 191, + 'otherUI' => null, + ]; + $credential = PublicKeyCredentialSource::createFromArray($credentialData); + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willReturn($credential); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faAuthenticateAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $this->Trait->setRequest($request); + $response = $this->Trait->webauthn2faAuthenticate(); + $expected = [ + 'success' => true, + 'redirectUrl' => '/login', + ]; + $actual = json_decode((string)$response->getBody(), true); + $this->assertEquals($expected, $actual); + + $this->assertNull( + $this->Trait->getRequest()->getSession()->read('Webauthn2fa.User') + ); + $userSession = $this->Trait->getRequest()->getSession()->read('TwoFactorAuthenticator.User'); + $this->assertInstanceOf( + User::class, + $userSession + ); + $this->assertEquals($userSession->toArray(), $user->toArray()); + } + + /** + * Test webauthn2faAuthenticateOptions method + * + * @return void + */ + public function testWebauthn2faAuthenticateError() + { + $table = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users'); + $user = $table->get('00000000-0000-0000-0000-000000000002'); + $request = ServerRequestFactory::fromGlobals(); + $request->getSession()->write('Webauthn2fa.User', $user); + + $this->Trait->setRequest($request); + $adapter = $this->getMockBuilder(AuthenticateAdapter::class) + ->onlyMethods(['verifyResponse']) + ->setConstructorArgs([$request]) + ->getMock(); + + $adapter->expects($this->once()) + ->method('verifyResponse') + ->willThrowException(new \Exception('Test exception error for webauthn2faAuthenticate')); + + $traitMockMethods = array_unique(array_merge(['getUsersTable', 'getWebauthn2faAuthenticateAdapter'], $this->traitMockMethods)); + $this->Trait = $this->getMockBuilder($this->traitClassName) + ->setMethods($traitMockMethods) + ->getMock(); + $this->Trait->expects($this->once()) + ->method('getWebauthn2faAuthenticateAdapter') + ->willReturn($adapter); + $this->Trait->expects($this->any()) + ->method('getUsersTable') + ->will($this->returnValue($this->table)); + $this->Trait->setRequest($request); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Test exception error for webauthn2faAuthenticate'); + $this->Trait->webauthn2faAuthenticate(); + } +} diff --git a/tests/TestCase/Controller/UsersControllerTest.php b/tests/TestCase/Controller/UsersControllerTest.php new file mode 100644 index 000000000..9cebd1f89 --- /dev/null +++ b/tests/TestCase/Controller/UsersControllerTest.php @@ -0,0 +1,94 @@ +assertInstanceOf(ServerRequest::class, $request); + $this->assertIsArray($options); + + return '/my/custom/url/'; + }); + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://localhost/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/my/custom/url/'); + } + + /** + * Test unathorize redirect when user is NOT logged + * + * @return void + */ + public function testUnauthorizedRedirectNotLogged() + { + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://localhost/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/login?redirect=http%3A%2F%2Flocalhost%2Fusers%2Findex'); + } + + /** + * Test unathorize redirect when user is logged + * + * @return void + */ + public function testUnauthorizedRedirectLogged() + { + $userId = '00000000-0000-0000-0000-000000000004'; + $user = TableRegistry::getTableLocator() + ->get('CakeDC/Users.Users') + ->get($userId); + + $this->session(['Auth' => $user]); + $this->configRequest([ + 'headers' => [ + 'REFERER' => 'http://example.com/profile', + ], + ]); + $this->get('/users/index'); + $this->assertRedirectContains('/profile'); + } +} diff --git a/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php b/tests/TestCase/Exception/AccountAlreadyActiveExceptionTest.php 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 new file mode 100644 index 000000000..294c245e9 --- /dev/null +++ b/tests/TestCase/Mailer/UsersMailerTest.php @@ -0,0 +1,183 @@ +UsersMailer = new UsersMailer(); + parent::setUp(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->UsersMailer); + parent::tearDown(); + } + + /** + * test sendValidationEmail + * + * @return void + */ + public function testValidation() + { + $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' => '12345678', + ]; + + $user = $table->newEntity([ + 'first_name' => 'FirstName', + 'last_name' => 'Bond', + 'email' => 'test@example.com', + 'token' => '12345678', + ]); + $this->invokeMethod($this->UsersMailer, 'validation', [$user]); + $this->assertSame(['test@example.com' => 'test@example.com'], $this->UsersMailer->getTo()); + $this->assertSame('FirstName, Your account validation link', $this->UsersMailer->getSubject()); + $this->assertSame(Message::MESSAGE_BOTH, $this->UsersMailer->getEmailFormat()); + $this->assertSame($expectedViewVars, $this->UsersMailer->viewBuilder()->getVars()); + $this->assertSame('CakeDC/Users.validation', $this->UsersMailer->viewBuilder()->getTemplate()); + } + + /** + * test SocialAccountValidation + * + * @return void + */ + public function testSocialAccountValidation() + { + $social = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts') + ->get('00000000-0000-0000-0000-000000000001', ['contain' => 'Users']); + $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()); + } + + /** + * test sendValidationEmail including 'template' + * + * @return void + */ + public function testResetPassword() + { + $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', + ]; + + $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()); + } + + /** + * Call protected/private method of a class. + * + * @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 = []) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $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 new file mode 100644 index 000000000..23b12f02f --- /dev/null +++ b/tests/TestCase/Model/Behavior/PasswordBehaviorTest.php @@ -0,0 +1,242 @@ +table = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\PasswordBehavior') + ->setMethods(['_sendResetPasswordEmail']) + ->setConstructorArgs([$this->table]) + ->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', + ]); + } + + /** + * tearDown + * + * @return void + */ + 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->expects($this->never()) + ->method('_sendResetPasswordEmail') + ->with($user); + $result = $this->Behavior->resetToken('user-1', [ + 'expiration' => 3600, + 'checkActive' => true, + ]); + $this->assertNotEquals($token, $result->token); + $this->assertEmpty($result->activation_date); + $this->assertFalse($result->active); + } + + /** + * Test resetToken + */ + public function testResetTokenSendEmail() + { + $user = $this->table->findByUsername('user-1')->first(); + $token = $user->token; + $tokenExpires = $user->token_expires; + $this->Behavior->expects($this->once()) + ->method('_sendResetPasswordEmail'); + $result = $this->Behavior->resetToken('user-1', [ + 'expiration' => 3600, + 'checkActive' => true, + 'sendEmail' => true, + 'type' => 'password', + ]); + $this->assertNotEquals($token, $result->token); + $this->assertNotEquals($tokenExpires, $result->token_expires); + $this->assertEmpty($result->activation_date); + $this->assertFalse($result->active); + } + + /** + * Test resetToken + */ + public function testResetTokenWithNullParams() + { + $this->expectException(\InvalidArgumentException::class); + $this->Behavior->resetToken(null); + } + + /** + * Test resetTokenNoExpiration + */ + public function testResetTokenNoExpiration() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Token expiration cannot be empty'); + $this->Behavior->resetToken('ref'); + } + + /** + * Test resetToken + */ + public function testResetTokenNotExistingUser() + { + $this->expectException(UserNotFoundException::class); + $this->Behavior->resetToken('user-not-found', [ + 'expiration' => 3600, + ]); + } + + /** + * Test resetToken + */ + public function testResetTokenUserAlreadyActive() + { + $this->expectException(UserAlreadyActiveException::class); + $activeUser = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->findByUsername('user-4')->first(); + $this->assertTrue($activeUser->active); + $this->table = $this->getMockForModel('CakeDC/Users.Users', ['save']); + $this->table->expects($this->never()) + ->method('save'); + $this->Behavior->expects($this->never()) + ->method('_sendResetPasswordEmail'); + $this->Behavior->resetToken('user-4', [ + 'expiration' => 3600, + 'checkActive' => true, + ]); + } + + /** + * Test resetToken + */ + public function testResetTokenUserNotActive() + { + $this->expectException(UserNotActiveException::class); + $this->table->findByUsername('user-1')->firstOrFail(); + $this->Behavior->resetToken('user-1', [ + 'ensureActive' => true, + 'expiration' => 3600, + ]); + } + + /** + * Test resetToken + */ + public function testResetTokenUserActive() + { + $user = TableRegistry::getTableLocator()->get('CakeDC/Users.Users')->findByUsername('user-2')->first(); + $result = $this->Behavior->resetToken('user-2', [ + 'ensureActive' => true, + 'expiration' => 3600, + ]); + $this->assertEquals($user->id, $result->id); + } + + /** + * Test changePassword + */ + public function testChangePassword() + { + $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 new file mode 100644 index 000000000..317cc6a80 --- /dev/null +++ b/tests/TestCase/Model/Behavior/RegisterBehaviorTest.php @@ -0,0 +1,348 @@ +get('CakeDC/Users.Users'); + $table->addBehavior('CakeDC/Users/Register.Register'); + $this->Table = $table; + $this->Behavior = $table->behaviors()->Register; + TransportFactory::setConfig('test', ['className' => 'Debug']); + Email::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', + ]); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->Table, $this->Behavior); + Email::drop('default'); + TransportFactory::drop('test'); + parent::tearDown(); + } + + /** + * Test register method + * + * @return void + */ + public function testValidateRegisterNoValidateEmail() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); + $this->assertTrue($result->active); + } + + /** + * Test register method + * + * @return void + */ + public function testValidateRegisterEmptyUser() + { + $user = []; + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $this->assertFalse($result); + } + + /** + * Test register method + * + * @return void + */ + 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', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $this->assertNotEmpty($result); + $this->assertFalse($result->active); + $this->assertNotEmpty($result->tos_date); + } + + /** + * Test register method + * + * @return void + */ + 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') + ->setMethods(['getValidators', '_updateActive']) + ->setConstructorArgs([$this->Table]) + ->getMock(); + + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + + $this->Behavior->expects($this->never()) + ->method('getValidators'); + + $entityUser = $this->Table->newEntity($user); + + $this->Behavior->expects($this->once()) + ->method('_updateActive') + ->will($this->returnValue($entityUser)); + + $this->Table->expects($this->once()) + ->method('patchEntity') + ->with($this->Table->newEmptyEntity(), $user, ['validate' => 'custom']) + ->will($this->returnValue($entityUser)); + + $this->Table->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($entityUser)); + + $result = $this->Behavior->register($this->Table->newEmptyEntity(), $user, ['validator' => 'custom', 'validate_email' => 1]); + $this->assertNotEmpty($result->tos_date); + } + + /** + * Test register method + */ + public function testValidateRegisterTosRequired() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + ]; + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); + $this->assertFalse($result); + } + + /** + * Test register method + * + * @return void + */ + 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', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + ]; + $result = $this->Table->register($this->Table->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); + $this->assertNotEmpty($result); + } + + /** + * Test ActivateUser method + * + * @return void + */ + public function testActivateUser() + { + $user = $this->Table->find()->where(['id' => '00000000-0000-0000-0000-000000000001'])->first(); + $result = $this->Table->activateUser($user); + $this->assertTrue($result->active); + } + + /** + * Test Validate method + * + * @return void + */ + public function testValidate() + { + $result = $this->Table->validate('ae93ddbe32664ce7927cf0c5c5a5e59d', 'activateUser'); + $this->assertTrue($result->active); + $this->assertEmpty($result->token_expires); + } + + /** + * Test Validate method + * + * @return void + */ + public function testValidateUserWithExpiredToken() + { + $this->expectException(TokenExpiredException::class); + $this->Table->validate('token-5', 'activateUser'); + } + + /** + * Test Validate method + * + * @return void + */ + public function testValidateNotExistingUser() + { + $this->expectException(UserNotFoundException::class); + $this->Table->validate('not-existing-token', 'activateUser'); + } + + /** + * Test activateUser method + * + * @return void + */ + public function testActiveUserRemoveValidationToken() + { + $user = $this->Table->find()->where(['id' => '00000000-0000-0000-0000-000000000001'])->first(); + $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')); + } + + /** + * 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']); + } + + /** + * 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 new file mode 100644 index 000000000..5e35cf818 --- /dev/null +++ b/tests/TestCase/Model/Behavior/SocialAccountBehaviorTest.php @@ -0,0 +1,139 @@ +Table = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts'); + $this->Behavior = $this->Table->behaviors()->SocialAccount; + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->Table, $this->Behavior, $this->Email); + parent::tearDown(); + } + + /** + * Test validateEmail method + * + * @return void + */ + public function testValidateEmail() + { + $token = 'token-1234'; + $result = $this->Behavior->validateAccount(SocialAccountsTable::PROVIDER_FACEBOOK, 'reference-1-1234', $token); + $this->assertTrue($result->active); + $this->assertEquals($token, $result->token); + } + + /** + * Test validateEmail method + */ + public function testValidateEmailInvalidToken() + { + $this->expectException(RecordNotFoundException::class); + $this->Behavior->validateAccount(1, 'reference-1234', 'invalid-token'); + } + + /** + * Test validateEmail method + */ + public function testValidateEmailInvalidUser() + { + $this->expectException(RecordNotFoundException::class); + $this->Behavior->validateAccount(1, 'invalid-user', 'token-1234'); + } + + /** + * Test validateEmail method + */ + public function testValidateEmailActiveAccount() + { + $this->expectException(AccountAlreadyActiveException::class); + $this->Behavior->validateAccount(SocialAccountsTable::PROVIDER_TWITTER, 'reference-1-1234', 'token-1234'); + } + + /** + * testAfterSaveSocialNotActiveUserNotActive + * don't send email, user is not active + * + * @return void + */ + public function testAfterSaveSocialNotActiveUserNotActive() + { + $event = new Event('eventName'); + $entity = $this->Table->find()->first(); + $this->assertTrue($this->Behavior->afterSave($event, $entity, new \ArrayObject([]))); + } + + /** + * testAfterSaveSocialActiveUserActive + * social account is active, don't send email + * + * @return void + */ + public function testAfterSaveSocialActiveUserActive() + { + $event = new Event('eventName'); + $entity = $this->Table->findById('00000000-0000-0000-0000-000000000003')->first(); + $this->assertTrue($this->Behavior->afterSave($event, $entity, new \ArrayObject([]))); + } + + /** + * testAfterSaveSocialActiveUserNotActive + * social account is active, don't send email + * + * @return void + */ + public function testAfterSaveSocialActiveUserNotActive() + { + $event = new Event('eventName'); + $entity = $this->Table->findById('00000000-0000-0000-0000-000000000002')->first(); + $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 new file mode 100644 index 000000000..684f6c4db --- /dev/null +++ b/tests/TestCase/Model/Behavior/SocialBehaviorTest.php @@ -0,0 +1,422 @@ +Table = $this->getMockForModel('CakeDC/Users.Users', ['save']); + $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialBehavior') + ->setMethods(['randomString', '_updateActive', 'generateUniqueUsername']) + ->setConstructorArgs([$this->Table]) + ->getMock(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown(): void + { + unset($this->Table, $this->Behavior, $this->Email); + parent::tearDown(); + } + + /** + * Test socialLogin with facebook and not existing user + * + * @dataProvider providerFacebookSocialLogin + */ + public function testSocialLoginFacebookProvider($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)); + + $result = $this->Behavior->socialLogin($data, $options); + $this->assertEquals($result, $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() + { + return [ + 'provider' => [ + 'data' => [ + 'id' => 'facebook-id', + 'username' => null, + 'full_name' => 'Full name', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'email' => 'email@example.com', + 'raw' => [ + 'id' => '10153521527396318', + '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', + 'provider' => 'Facebook', + ], + 'options' => [ + 'use_email' => true, + 'validate_email' => true, + 'token_expiration' => 3600, + ], + 'result' => [ + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'username' => 'username', + 'email' => 'email@example.com', + 'password' => '$2y$10$oLPxCkKJ1TUCR6xJ1t0Wj.7Fznx49Wn4NZB2aJCmVvRMucaHuNyyO', + 'avatar' => null, + 'tos_date' => '2016-01-20 15:45:09', + 'gender' => null, + 'social_accounts' => [ + [ + 'provider' => 'Facebook', + 'username' => null, + 'reference' => '10153521527396318', + 'avatar' => '', + 'link' => 'facebook-link', + 'description' => null, + 'token' => 'token', + 'token_secret' => null, + 'token_expires' => '2016-03-19 21:41:22', + 'data' => '-', + 'active' => true, + ], + ], + 'activation_date' => '2016-01-20 15:45:09', + 'active' => true, + ], + ], + + ]; + } + + /** + * Test socialLogin with facebook with existing and active user + * + * @dataProvider providerFacebookSocialLoginExistingReference + */ + public function testSocialLoginExistingReferenceOkay($data, $options) + { + $this->Behavior->expects($this->never()) + ->method('generateUniqueUsername'); + + $this->Behavior->expects($this->never()) + ->method('randomString'); + + $this->Behavior->expects($this->never()) + ->method('_updateActive'); + + $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() + { + return [ + 'provider' => [ + 'data' => [ + 'id' => 'reference-2-1', + 'provider' => 'Facebook', + ], + 'options' => [ + 'use_email' => true, + 'validate_email' => true, + 'token_expiration' => 3600, + ], + ], + + ]; + } + + /** + * Test socialLogin with existing and active user and not active social account + * + * @dataProvider providerSocialLoginExistingAndNotActiveAccount + */ + public function testSocialLoginExistingNotActiveReference($data, $options) + { + $this->expectException(AccountNotActiveException::class); + $this->Behavior->expects($this->never()) + ->method('generateUniqueUsername'); + + $this->Behavior->expects($this->never()) + ->method('randomString'); + + $this->Behavior->expects($this->never()) + ->method('_updateActive'); + $this->Behavior->socialLogin($data, $options); + } + + /** + * Provider for socialLogin with existing and active user and not active social account + */ + public function providerSocialLoginExistingAndNotActiveAccount() + { + return [ + 'provider' => [ + 'data' => [ + 'id' => 'reference-1-1234', + 'provider' => 'Facebook', + ], + 'options' => [ + 'use_email' => true, + 'validate_email' => true, + 'token_expiration' => 3600, + ], + ], + + ]; + } + + /** + * Test socialLogin with existing and active account but not active user + * + * @dataProvider providerSocialLoginExistingAccountNotActiveUser + */ + public function testSocialLoginExistingReferenceNotActiveUser($data, $options) + { + $this->expectException(UserNotActiveException::class); + $this->Behavior->expects($this->never()) + ->method('generateUniqueUsername'); + + $this->Behavior->expects($this->never()) + ->method('randomString'); + + $this->Behavior->expects($this->never()) + ->method('_updateActive'); + $this->Behavior->socialLogin($data, $options); + } + + /** + * Provider for socialLogin with existing and active account but not active user + */ + public function providerSocialLoginExistingAccountNotActiveUser() + { + return [ + 'provider' => [ + 'data' => [ + 'id' => 'reference-1-1234', + 'provider' => 'Twitter', + ], + 'options' => [ + 'use_email' => true, + 'validate_email' => true, + 'token_expiration' => 3600, + ], + ], + + ]; + } + + /** + * Test socialLogin with facebook and not existing user + * + * @dataProvider providerFacebookSocialLoginNoEmail + */ + 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() + { + return [ + 'provider' => [ + 'data' => [ + 'id' => 'facebook-id', + 'username' => null, + 'full_name' => 'Full name', + 'first_name' => 'First name', + 'last_name' => 'Last name', + 'validated' => true, + 'link' => 'facebook-link', + 'provider' => 'Facebook', + ], + 'options' => [ + 'use_email' => true, + 'validate_email' => true, + 'token_expiration' => 3600, + ], + ], + + ]; + } + + /** + * Test socialLogin with facebook and not existing user + * + * @dataProvider providerGenerateUsername + */ + public function testGenerateUniqueUsername($param, $expected) + { + $this->Behavior = $this->getMockBuilder('CakeDC\Users\Model\Behavior\SocialBehavior') + ->setMethods(['randomString', '_updateActive']) + ->setConstructorArgs([$this->Table]) + ->getMock(); + + $result = $this->Behavior->generateUniqueUsername($param); + $this->assertEquals($expected, $result); + } + + /** + * Provider for socialLogin with facebook and not existing user + */ + public function providerGenerateUsername() + { + return [ + ['username', 'username'], + ['user-1', 'user-10'], + ['user-5', 'user-50'], + + ]; + } +} diff --git a/tests/TestCase/Model/Entity/UserTest.php b/tests/TestCase/Model/Entity/UserTest.php new file mode 100644 index 000000000..4f0981f71 --- /dev/null +++ b/tests/TestCase/Model/Entity/UserTest.php @@ -0,0 +1,208 @@ +now = FrozenTime::now(); + FrozenTime::setTestNow($this->now); + $this->User = new User(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + unset($this->User); + FrozenTime::setTestNow(); + + parent::tearDown(); + } + + /** + * Test tokenExpired method + * + * @return void + */ + public function testTokenExpiredEmpty() + { + $this->User->token_expires = null; + $isExpired = $this->User->tokenExpired(); + $this->assertTrue($isExpired); + } + + /** + * Test tokenExpired method + * + * @return void + */ + public function testTokenExpiredNotYet() + { + $this->User->token_expires = '-1 day'; + $isExpired = $this->User->tokenExpired(); + $this->assertTrue($isExpired); + } + + /** + * Test tokenExpired method + * + * @return void + */ + public function testTokenExpired() + { + $this->User->token_expires = '+1 day'; + $isExpired = $this->User->tokenExpired(); + $this->assertFalse($isExpired); + } + + /** + * Test tokenExpired another locale + * + * @return void + */ + public function testTokenExpiredLocale() + { + 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::setLocale('en_US'); + } + + /** + * test + * + * @return void + */ + public function testPasswordsAreEncrypted() + { + $pw = 'password'; + $this->User->password = $pw; + $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)); + } + + /** + * test + * + * @return void + */ + public function testCheckPassword() + { + $pw = 'password'; + $this->assertTrue($this->User->checkPassword($pw, (new DefaultPasswordHasher())->hash($pw))); + $this->assertFalse($this->User->checkPassword($pw, 'fail')); + } + + /** + * test + * + * @return void + */ + public function testGetAvatar() + { + $this->assertNull($this->User->avatar); + $avatar = 'first-avatar'; + $this->User->social_accounts = [ + ['avatar' => 'first-avatar'], + ['avatar' => 'second-avatar'], + ]; + $this->assertSame($avatar, $this->User->avatar); + } + + /** + * test + * + * @return void + */ + public function testUpdateToken() + { + $this->assertNull($this->User['token']); + $this->assertNull($this->User['token_expires']); + $this->User->updateToken(); + $this->assertEquals($this->now, $this->User['token_expires']); + $this->assertNotNull($this->User['token']); + } + + /** + * test + * + * @return void + */ + public function testUpdateTokenExisting() + { + $this->User['token'] = 'aaa'; + $this->User['token_expires'] = $this->now; + $this->User->updateToken(); + $this->assertEquals($this->now, $this->User['token_expires']); + $this->assertNotEquals('aaa', $this->User['token']); + } + + /** + * test + * + * @return void + */ + public function testUpdateTokenAdd() + { + $this->assertNull($this->User['token']); + $this->assertNull($this->User['token_expires']); + $this->User->updateToken(20); + $this->assertEquals('20 seconds after', $this->User['token_expires']->diffForHumans($this->now)); + $this->assertNotNull($this->User['token']); + } + + /** + * test + * + * @return void + */ + public function testUpdateTokenExistingAdd() + { + $this->User['token'] = 'aaa'; + $this->User['token_expires'] = $this->now; + $this->User->updateToken(20); + $this->assertEquals('20 seconds after', $this->User['token_expires']->diffForHumans($this->now)); + $this->assertNotEquals('aaa', $this->User['token']); + } +} diff --git a/tests/TestCase/Model/Table/SocialAccountsTableTest.php b/tests/TestCase/Model/Table/SocialAccountsTableTest.php new file mode 100644 index 000000000..04999b7f5 --- /dev/null +++ b/tests/TestCase/Model/Table/SocialAccountsTableTest.php @@ -0,0 +1,70 @@ +SocialAccounts = TableRegistry::getTableLocator()->get('CakeDC/Users.SocialAccounts'); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + unset($this->SocialAccounts); + + parent::tearDown(); + } + + public function testValidationHappy() + { + $data = [ + 'provider' => 'Facebook', + 'reference' => 'test-reference', + 'link' => 'test-link', + 'token' => 'test-token', + 'active' => 0, + 'data' => 'test-data', + ]; + $entity = $this->SocialAccounts->newEntity($data); + $this->assertEmpty($entity->getErrors()); + } +} diff --git a/tests/TestCase/Model/Table/UsersTableTest.php b/tests/TestCase/Model/Table/UsersTableTest.php new file mode 100644 index 000000000..58b1e7fd6 --- /dev/null +++ b/tests/TestCase/Model/Table/UsersTableTest.php @@ -0,0 +1,319 @@ +Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + $this->fullBaseBackup = Router::fullBaseUrl(); + Router::fullBaseUrl('http://users.test'); + TransportFactory::drop('test'); + TransportFactory::setConfig('test', ['className' => 'Debug']); + Email::setConfig('default', [ + 'transport' => 'test', + 'from' => 'cakedc@example.com', + ]); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + unset($this->Users); + Router::fullBaseUrl($this->fullBaseBackup); + Email::drop('default'); + + parent::tearDown(); + } + + /** + * Test register method + * + * @return void + */ + public function testValidateRegisterNoValidateEmail() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 0]); + $this->assertTrue($result->active); + } + + /** + * Test register method + * + * @return void + */ + public function testValidateRegisterEmptyUser() + { + $user = []; + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $this->assertFalse($result); + } + + /** + * Test register method + * + * @return void + */ + 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', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + 'tos' => 1, + ]; + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1]); + $this->assertNotEmpty($result); + $this->assertFalse($result->active); + } + + /** + * test + */ + public function testValidateRegisterTosRequired() + { + $user = [ + 'username' => 'testuser', + 'email' => 'testuser@test.com', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + ]; + $userEntity = $this->Users->newEmptyEntity(); + $this->Users->register($userEntity, $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 1]); + $this->assertEquals(['tos' => ['_required' => 'This field is required']], $userEntity->getErrors()); + } + + /** + * Test register method + * 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', + 'password' => 'password', + 'password_confirm' => 'password', + 'first_name' => 'test', + 'last_name' => 'user', + ]; + $result = $this->Users->register($this->Users->newEmptyEntity(), $user, ['token_expiration' => 3600, 'validate_email' => 1, 'use_tos' => 0]); + $this->assertNotEmpty($result); + } + + /** + * Test ActivateUser method + * + * @return void + */ + public function testActivateUser() + { + $user = $this->Users->find()->where(['id' => '00000000-0000-0000-0000-000000000001'])->first(); + $result = $this->Users->activateUser($user); + $this->assertTrue($result->active); + } + + public function testSocialLogin() + { + $data = [ + 'provider' => SocialAccountsTable::PROVIDER_FACEBOOK, + 'email' => 'user-2@test.com', + 'id' => 'reference-2-1', + 'link' => 'link', + 'raw' => [ + 'id' => 'reference-2-1', + 'token' => 'token', + 'first_name' => 'User 2', + 'gender' => 'female', + 'verified' => 1, + 'user_email' => 'user-2@test.com', + 'link' => 'link', + ], + ]; + $options = [ + 'use_email' => 1, + 'validate_email' => 1, + 'token_expiration' => 3600, + ]; + $result = $this->Users->socialLogin($data, $options); + $this->assertEquals('user-2@test.com', $result->email); + $this->assertTrue($result->active); + } + + /** + * Test socialLogin + */ + public function testSocialLoginInactiveAccount() + { + $this->expectException(AccountNotActiveException::class); + $data = [ + 'provider' => SocialAccountsTable::PROVIDER_TWITTER, + 'email' => 'hello@test.com', + 'id' => 'reference-2-2', + 'link' => 'link', + 'raw' => [ + 'id' => 'reference-2-2', + 'first_name' => 'User 2', + 'gender' => 'female', + 'verified' => 1, + 'user_email' => 'hello@test.com', + ], + ]; + $options = [ + 'use_email' => 1, + 'validate_email' => 1, + 'token_expiration' => 3600, + ]; + $result = $this->Users->socialLogin($data, $options); + $this->assertEquals('user-2@test.com', $result->email); + $this->assertFalse($result->active); + } + + /** + * Test socialLogin + */ + public function testSocialLoginCreateNewAccountWithNoCredentials() + { + $this->expectException(\InvalidArgumentException::class); + $data = [ + 'provider' => SocialAccountsTable::PROVIDER_TWITTER, + 'email' => 'user@test.com', + 'id' => 'reference-not-existing', + 'link' => 'link', + 'raw' => [ + 'id' => 'reference-not-existing', + 'first_name' => 'Not existing user', + 'gender' => 'male', + 'user_email' => 'user@test.com', + ], + 'credentials' => [], + 'name' => '', + ]; + + $options = [ + 'use_email' => 0, + 'validate_email' => 1, + 'token_expiration' => 3600, + ]; + $result = $this->Users->socialLogin($data, $options); + $this->assertFalse($result); + } + + /** + * Test socialLogin + */ + public function testSocialLoginCreateNewAccount() + { + $data = [ + 'provider' => SocialAccountsTable::PROVIDER_TWITTER, + 'email' => 'username@test.com', + 'id' => 'no-existing-reference', + 'link' => 'link', + 'first_name' => 'First Name', + 'last_name' => 'Last Name', + 'raw' => [ + 'id' => 'no-existing-reference', + 'first_name' => 'First Name', + 'last_name' => 'Last Name', + 'gender' => 'male', + 'user_email' => 'user@test.com', + 'twitter' => 'link', + ], + 'info' => [ + 'first_name' => 'First Name', + 'last_name' => 'Last Name', + 'urls' => ['twitter' => 'twitter'], + ], + 'validated' => true, + 'credentials' => [ + 'token' => 'token', + 'token_secret' => 'secret', + 'token_expires' => '', + ], + ]; + + $options = [ + 'use_email' => 0, + 'validate_email' => 0, + 'token_expiration' => 3600, + ]; + $result = $this->Users->socialLogin($data, $options); + $this->assertNotEmpty($result); + $this->assertEquals('no-existing-reference', $result->social_accounts[0]->reference); + $this->assertEquals(1, count($result->social_accounts)); + $this->assertEquals('username', $result->username); + $this->assertEquals('First Name', $result->first_name); + $this->assertEquals('Last Name', $result->last_name); + } +} 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 new file mode 100644 index 000000000..2ce3ea7c0 --- /dev/null +++ b/tests/TestCase/Shell/UsersShellTest.php @@ -0,0 +1,420 @@ +out = new ConsoleOutput(); + $this->io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock(); + $this->Users = TableRegistry::getTableLocator()->get('CakeDC/Users.Users'); + + $this->Shell = $this->getMockBuilder('CakeDC\Users\Shell\UsersShell') + ->setMethods(['in', 'out', '_stop', 'clear', '_usernameSeed', '_generateRandomPassword', + '_generateRandomUsername', '_generatedHashedPassword', 'error', '_updateUser']) + ->setConstructorArgs([$this->io]) + ->getMock(); + + $this->Shell->Users = $this->getMockBuilder('CakeDC\Users\Model\Table\UsersTable') + ->setMethods(['generateUniqueUsername', 'newEntity', 'save', 'updateAll']) + ->getMock(); + + $this->Shell->Command = $this->getMockBuilder('Cake\Shell\Task\CommandTask') + ->setMethods(['in', '_stop', 'clear', 'out']) + ->setConstructorArgs([$this->io]) + ->getMock(); + } + + /** + * Tear Down + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + unset($this->Shell); + } + + /** + * Add user test + * Adding user with username, email, password and role + * + * @return void + */ + public function testAddUser() + { + $user = [ + 'username' => 'yeliparra', + 'password' => '123', + 'email' => 'yeli.parra@example.com', + 'active' => 1, + ]; + $role = 'tester'; + + $this->Shell->expects($this->never()) + ->method('_generateRandomUsername'); + + $this->Shell->expects($this->never()) + ->method('_generateRandomPassword'); + + $this->Shell->Users->expects($this->once()) + ->method('generateUniqueUsername') + ->with($user['username']) + ->will($this->returnValue($user['username'])); + + $entityUser = $this->Users->newEntity($user); + $entityUser->role = $role; + + $this->Shell->Users->expects($this->once()) + ->method('newEntity') + ->with($user) + ->will($this->returnValue($entityUser)); + + $userSaved = $entityUser; + $userSaved->id = 'my-id'; + + $this->Shell->Users->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($userSaved)); + + $this->Shell->runCommand(['addUser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); + } + + /** + * Add user test + * Adding user passing no params + * + * @return void + */ + public function testAddUserWithNoParams() + { + $user = [ + 'username' => 'anakin', + 'password' => 'mypassword', + 'email' => 'anakin@example.com', + 'active' => 1, + ]; + + $this->Shell->Users->expects($this->once()) + ->method('generateUniqueUsername') + ->with($user['username']) + ->will($this->returnValue($user['username'])); + + $this->Shell->expects($this->once()) + ->method('_generateRandomPassword') + ->will($this->returnValue($user['password'])); + + $this->Shell->expects($this->once()) + ->method('_generateRandomUsername') + ->will($this->returnValue($user['username'])); + + $entityUser = $this->Users->newEntity($user); + $entityUser->role = 'user'; + + $this->Shell->Users->expects($this->once()) + ->method('newEntity') + ->with($user) + ->will($this->returnValue($entityUser)); + + $userSaved = $entityUser; + $userSaved->id = 'my-id'; + + $this->Shell->Users->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($userSaved)); + + //TODO: Add assertions with 'out' + + $this->Shell->runCommand(['addUser']); + } + + /** + * Add user test + * Adding user with username, email, password and role + * + * @return void + */ + public function testAddSuperuser() + { + $user = [ + 'username' => 'yeliparra', + 'password' => '123', + 'email' => 'yeli.parra@example.com', + 'active' => 1, + ]; + $role = 'tester'; + + $this->Shell->expects($this->never()) + ->method('_generateRandomUsername'); + + $this->Shell->expects($this->never()) + ->method('_generateRandomPassword'); + + $this->Shell->Users->expects($this->once()) + ->method('generateUniqueUsername') + ->with($user['username']) + ->will($this->returnValue($user['username'])); + + $entityUser = $this->Users->newEntity($user); + $entityUser->role = $role; + + $this->Shell->Users->expects($this->once()) + ->method('newEntity') + ->with($user) + ->will($this->returnValue($entityUser)); + + $userSaved = $entityUser; + $userSaved->id = 'my-id'; + $userSaved->is_superuser = true; + + $this->Shell->Users->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($userSaved)); + + $this->Shell->runCommand(['addSuperuser', '--username=' . $user['username'], '--password=' . $user['password'], '--email=' . $user['email'], '--role=' . $role]); + } + + /** + * Add superadmin user + * + * @return void + */ + public function testAddSuperuserWithNoParams() + { + $this->Shell->Users->expects($this->once()) + ->method('generateUniqueUsername') + ->with('superadmin') + ->will($this->returnValue('superadmin')); + + $this->Shell->expects($this->once()) + ->method('_generateRandomPassword') + ->will($this->returnValue('password')); + + $user = [ + 'username' => 'superadmin', + 'password' => 'password', + 'email' => 'superadmin@example.com', + 'active' => 1, + ]; + $entityUser = $this->Users->newEntity($user); + + $this->Shell->Users->expects($this->once()) + ->method('newEntity') + ->with($user) + ->will($this->returnValue($entityUser)); + + $userSaved = $entityUser; + $userSaved->id = 'my-id'; + $userSaved->is_superuser = true; + $userSaved->role = 'superuser'; + + $this->Shell->Users->expects($this->once()) + ->method('save') + ->with($entityUser) + ->will($this->returnValue($userSaved)); + + $this->Shell->runCommand(['addSuperuser']); + } + + /** + * Reset all passwords + * + * @return void + */ + public function testResetAllPasswords() + { + $this->Shell->expects($this->once()) + ->method('_generatedHashedPassword') + ->will($this->returnValue('hashedPasssword')); + + $this->Shell->Users->expects($this->once()) + ->method('updateAll') + ->with(['password' => 'hashedPasssword'], ['id IS NOT NULL']); + + $this->Shell->runCommand(['resetAllPasswords', '123']); + } + + /** + * Reset all passwords + */ + public function testResetAllPasswordsNoPassingParams() + { + $this->expectException(StopException::class); + $this->expectExceptionMessage('Please enter a password.'); + $this->Shell->runCommand(['resetAllPasswords']); + } + + /** + * Reset password + * + * @return void + */ + public function testResetPassword() + { + $user = $this->Users->newEmptyEntity(); + $user->username = 'user-1'; + $user->password = 'password'; + + $this->Shell->expects($this->once()) + ->method('_updateUser') + ->will($this->returnValue($user)); + + $this->Shell->runCommand(['resetPassword', 'user-1', 'password']); + } + + /** + * Change role + * + * @return void + */ + public function testChangeRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); + $this->assertSame('admin', $user['role']); + $this->Shell->runCommand(['changeRole', 'user-1', 'another-role']); + $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); + $this->assertSame('another-role', $user['role']); + } + + /** + * Activate user + * + * @return void + */ + public function testActivateUser() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); + $this->assertFalse($user['active']); + $this->Shell->runCommand(['activateUser', 'user-1']); + $user = $this->Users->get('00000000-0000-0000-0000-000000000001'); + $this->assertTrue($user['active']); + } + + /** + * Delete user + * + * @return void + * @expected + */ + public function testDeleteUser() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + + $this->assertNotEmpty($this->Users->findById('00000000-0000-0000-0000-000000000001')->first()); + $this->assertNotEmpty($this->Users->SocialAccounts->findByUserId('00000000-0000-0000-0000-000000000001')->toArray()); + $this->Shell->runCommand(['deleteUser', 'user-1']); + $this->assertEmpty($this->Users->findById('00000000-0000-0000-0000-000000000001')->first()); + $this->assertEmpty($this->Users->SocialAccounts->findByUserId('00000000-0000-0000-0000-000000000001')->toArray()); + + $this->assertNotEmpty($this->Users->findById('00000000-0000-0000-0000-000000000005')->first()); + $this->Shell->runCommand(['deleteUser', 'user-5']); + $this->assertEmpty($this->Users->findById('00000000-0000-0000-0000-000000000005')->first()); + } + + /** + * test + * + * @return void + */ + public function testAddUserCustomRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + '--role=custom', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('custom', $user['role']); + } + + /** + * test + * + * @return void + */ + public function testAddUserDefaultRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + Configure::write('Users.Registration.defaultRole', false); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('user', $user['role']); + } + + /** + * test + * + * @return void + */ + public function testAddUserCustomDefaultRole() + { + $this->Shell = new UsersShell($this->io); + $this->Shell->Users = $this->Users; + $this->assertEmpty($this->Users->findByUsername('custom')->first()); + Configure::write('Users.Registration.defaultRole', 'emperor'); + $this->Shell->runCommand([ + 'addUser', + '--username=custom', + '--password=12345678', + '--email=custom@example.com', + ]); + $user = $this->Users->findByUsername('custom')->first(); + $this->assertSame('emperor', $user['role']); + } +} diff --git a/tests/TestCase/Traits/RandomStringTraitTest.php b/tests/TestCase/Traits/RandomStringTraitTest.php new file mode 100644 index 000000000..e3d9397ab --- /dev/null +++ b/tests/TestCase/Traits/RandomStringTraitTest.php @@ -0,0 +1,45 @@ +Trait = $this->getMockForTrait('CakeDC\Users\Traits\RandomStringTrait'); + } + + public function tearDown(): void + { + parent::tearDown(); + } + + public function testRandomString() + { + $result = $this->Trait->randomString(); + $this->assertEquals(10, strlen($result)); + + $result = $this->Trait->randomString(30); + $this->assertEquals(30, strlen($result)); + + $result = $this->Trait->randomString('-300'); + $this->assertEquals(10, strlen($result)); + + $result = $this->Trait->randomString('text'); + $this->assertEquals(10, strlen($result)); + } +} 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 new file mode 100644 index 000000000..c69f5393c --- /dev/null +++ b/tests/TestCase/View/Helper/AuthLinkHelperTest.php @@ -0,0 +1,234 @@ +AuthLink = $this->getMockBuilder(AuthLinkHelper::class) + ->setMethods(['isAuthorized']) + ->setConstructorArgs([$view]) + ->getMock(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + unset($this->AuthLink); + + parent::tearDown(); + } + + /** + * Test link + * + * @return void + */ + public function testLinkFalseWithMock(): void + { + $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); + } + + /** + * Test link + * + * @return void + */ + public function testLinkAuthorizedHappy(): void + { + $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 post link with delete user method + * Logged as Super user + * + * @return void + */ + public function testPostLinkAuthorizedAllowedTrueLoggedAsAdmin(): void + { + $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 new file mode 100644 index 000000000..1ce9a8f58 --- /dev/null +++ b/tests/TestCase/View/Helper/UserHelperTest.php @@ -0,0 +1,421 @@ +oauthConfig === null) { + $this->oauthConfig = (array)Configure::read('OAuth'); + $this->socialLogin = Configure::read('Users.Social.login'); + } + + parent::setUp(); + $this->View = $this->getMockBuilder('Cake\View\View') + ->setMethods(['append']) + ->getMock(); + //Assuming all these url's are authorized + $this->AuthLink = $this->getMockBuilder('CakeDC\Users\View\Helper\AuthLinkHelper') + ->setMethods(['isAuthorized']) + ->setConstructorArgs([$this->View]) + ->getMock(); + $this->AuthLink->expects($this->any()) + ->method('isAuthorized') + ->will($this->returnValue(true)); + $this->User = new UserHelper($this->View); + $this->User->AuthLink = $this->AuthLink; + $this->request = new ServerRequest(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown(): void + { + Configure::write('OAuth', $this->oauthConfig); + Configure::write('Users.Social.login', $this->socialLogin); + unset($this->User); + + parent::tearDown(); + } + + /** + * Test twitterLogin + * + * @return void + */ + 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); + } + + /** + * Test twitterLogin + * + * @return void + */ + 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); + } + + /** + * Test twitterLogin + * + * @return void + */ + 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); + } + + /** + * Test link + * + * @return void + */ + public function testWelcome() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); + + $user = [ + 'id' => 2, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'username' => 'j04n.d09', + ]; + $identity = new Identity($user); + $request = new ServerRequest(); + $request = $request->withAttribute('identity', $identity); + $this->User->getView()->setRequest($request); + $expected = 'Welcome, John'; + $result = $this->User->welcome(); + $this->assertEquals($expected, $result); + } + + /** + * Test link + * + * @return void + */ + public function testWelcomeWillDisplayUsernameInstead() + { + $builder = Router::createRouteBuilder('/'); + $builder->connect('/profile', [ + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'profile', + ]); + + $user = [ + 'id' => 2, + 'last_name' => 'Doe', + 'username' => 'j04n.d09', + ]; + $identity = new Identity($user); + $request = new ServerRequest(); + $request = $request->withAttribute('identity', $identity); + $this->User->getView()->setRequest($request); + $expected = 'Welcome, j04n.d09'; + $result = $this->User->welcome(); + $this->assertEquals($expected, $result); + } + + /** + * Test link + * + * @return void + */ + public function testWelcomeNotLoggedInUser() + { + $request = new ServerRequest(); + $this->User->getView()->setRequest($request); + $result = $this->User->welcome(); + $this->assertEmpty($result); + } + + /** + * Test add ReCaptcha field + * + * @return void + */ + 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); + } + + /** + * Test add ReCaptcha field + * + * @return void + */ + public function testAddReCaptchaEmpty() + { + $result = $this->User->addReCaptcha(); + $expected = '

reCaptcha is not configured! Please configure Users.reCaptcha.key

'; + $this->assertEquals($expected, $result); + } + + /** + * Test add ReCaptcha field + * + * @return void + */ + public function testAddReCaptchaScript() + { + $this->View->expects($this->once()) + ->method('append') + ->with('script', $this->stringContains('https://www.google.com/recaptcha/api.js')); + $this->User->addReCaptchaScript(); + } + + /** + * Test social login link + * + * @return void + */ + public function testSocialLoginLink() + { + $result = $this->User->socialLogin('facebook'); + $this->assertEquals('Sign in with Facebook', $result); + + $result = $this->User->socialLogin('twitter', ['label' => 'Register with']); + $this->assertEquals('Register with Twitter', $result); + } + + /** + * test + * + * @return void + */ + public function testSocialLoginTranslation() + { + I18n::setLocale('es_ES'); + $result = $this->User->socialLogin('facebook'); + $this->assertEquals('Iniciar sesión con Facebook', $result); + 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 new file mode 100644 index 000000000..f5424bd41 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,143 @@ +create(TMP . 'cache/models', 0777); +$TMP->create(TMP . 'cache/persistent', 0777); +$TMP->create(TMP . 'cache/views', 0777); + +$cache = [ + 'default' => [ + 'engine' => 'File', + ], + '_cake_core_' => [ + 'className' => 'File', + 'prefix' => 'users_myapp_cake_core_', + 'path' => CACHE . 'persistent/', + 'serialize' => true, + 'duration' => '+10 seconds', + ], + '_cake_model_' => [ + 'className' => 'File', + 'prefix' => 'users_app_cake_model_', + 'path' => CACHE . 'models/', + 'serialize' => 'File', + 'duration' => '+10 seconds', + ], +]; + +Cake\Cache\Cache::setConfig($cache); +Cake\Core\Configure::write('Session', [ + 'defaults' => 'php', +]); + +Plugin::getCollection()->add(new \CakeDC\Users\Plugin([ + 'path' => dirname(dirname(__FILE__)) . DS, + 'routes' => true, +])); +if (file_exists($root . '/config/bootstrap.php')) { + require $root . '/config/bootstrap.php'; +} + +if (!getenv('db_dsn')) { + putenv('db_dsn=sqlite:///:memory:'); +} + +Cake\Datasource\ConnectionManager::setConfig('test', [ + 'url' => getenv('db_dsn'), + '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/cases/controllers/details_controller.test.php b/tests/cases/controllers/details_controller.test.php deleted file mode 100644 index 239f3fb08..000000000 --- a/tests/cases/controllers/details_controller.test.php +++ /dev/null @@ -1,45 +0,0 @@ -Details = new TestDetails(); - } - - function testDetailsControllerInstance() { - $this->assertTrue(is_a($this->Details, 'DetailsController')); - } - - function tearDown() { - unset($this->Details); - } -} diff --git a/tests/cases/controllers/users_controller.test.php b/tests/cases/controllers/users_controller.test.php deleted file mode 100644 index a6fe795f2..000000000 --- a/tests/cases/controllers/users_controller.test.php +++ /dev/null @@ -1,429 +0,0 @@ -Auth->authorize = 'controller'; - $this->Auth->fields = array('username' => 'email', 'password' => 'passwd'); - $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login', 'prefix' => 'admin', 'admin' => false, 'plugin' => 'users'); - $this->Auth->loginRedirect = $this->Session->read('Auth.redirect'); - $this->Auth->logoutRedirect = '/'; - $this->Auth->authError = __d('users', 'Sorry, but you need to login to access this location.', true); - $this->Auth->loginError = __d('users', 'Invalid e-mail / password combination. Please try again', true); - $this->Auth->autoRedirect = true; - $this->Auth->userModel = 'User'; - $this->Auth->userScope = array( - 'OR' => array( - 'AND' => - array('User.active' => 1, 'User.email_authenticated' => 1))); - } - -/** - * Public interface to _setCookie - */ - public function setCookie($options = array()) { - parent::_setCookie($options); - } - -/** - * Auto render - * - * @var boolean - */ - public $autoRender = false; - -/** - * Redirect URL - * - * @var mixed - */ - public $redirectUrl = null; - -/** - * Override controller method for testing - */ - public function redirect($url, $status = null, $exit = true) { - $this->redirectUrl = $url; - } - -/** - * Override controller method for testing - */ - public function render($action = null, $layout = null, $file = null) { - $this->renderedView = $action; - } -} - -class UsersControllerTestCase extends CakeTestCase { - -/** - * Instance of the controller - * - * @var mixed - */ - public $Users = null; - -/** - * Fixtures - * - * @var array - */ - public $fixtures = array( - 'plugin.users.user', - 'plugin.users.detail', - 'plugin.users.identity'); - -/** - * Sampletdata used for post data - * - * @var array - */ - public $usersData = array( - 'admin' => array('email' => 'larry.masters@cakedc.com', 'username' => 'phpnut', 'passwd' => 'test'), - 'validUser' => array('email' => 'florian.kraemer@cakedc.com', 'username' => 'floriank', 'passwd' => 'secretkey', 'redirect' => '/user/burzum'), - 'invalidUser' => array('email' => 'wronguser@wronguser.com', 'username' => 'invalidUser', 'passwd' => 'invalid-password!')); - -/** - * Start test - * - * @return void - */ - public function startTest() { - Configure::write('App.UserClass', null); - $this->Users = new TestUsersController(); - $this->Users->constructClasses(); - $this->Users->Component->init($this->Users); - $this->Users->Component->initialize($this->Users); - $this->Users->params = array( - 'pass' => array(), - 'named' => array(), - 'controller' => 'users', - 'admin' => false, - 'plugin' => 'users', - 'url' => array()); - $this->Users->Email->delivery = 'debug'; - } - -/** - * Test controller instance - * - * @return void - */ - public function testUsersControllerInstance() { - $this->assertTrue(is_a($this->Users, 'UsersController')); - } - -/** - * Test the user login - * - * @return void - */ - public function testUserLogin() { - $this->Users->params['action'] = 'login'; - $this->Users->Component->startup($this->Users); - - $this->Users->User->save(array( - 'User' => array( - 'id' => '1', - 'username' => 'testuser', - 'slug' => 'testuser', - 'passwd' => Security::hash('test', null, true), - )), false); - - $this->__setPost(array('User' => $this->usersData['admin'])); - $this->Users->beforeFilter(); - $this->Users->params = array( - 'controller' => 'users', - 'action' => 'login', - 'admin' => false, - 'plugin' => 'users', - 'url' => array( - 'url' => '/users/users/login')); - - $this->Users->Component->startup($this->Users); - $this->Users->login(); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'testuser you have successfully logged in', true)); - $this->assertEqual(Router::normalize($this->Users->redirectUrl), Router::normalize(Router::url($this->Users->Auth->loginRedirect))); - - $this->__setPost(array('User' => $this->usersData['invalidUser'])); - $this->Users->beforeFilter(); - $this->Users->login(); - $this->assertEqual($this->Users->Session->read('Message.auth.message'), __d('users', 'Invalid e-mail / password combination. Please try again', true)); - } - -/** - * Test user registration - * - */ - public function testRegister() { - $this->Users->params['action'] = 'register'; - - $this->__setPost(array( - 'User' => array( - 'username' => 'newUser', - 'email' => 'newUser@newemail.com', - 'passwd' => 'password', - 'temppassword' => 'password', - 'tos' => 1))); - $this->Users->beforeFilter(); - $this->Users->register(); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'Your account has been created. You should receive an e-mail shortly to authenticate your account. Once validated you will be able to login.', true)); - - $this->__setPost(array( - 'User' => array( - 'username' => 'newUser', - 'email' => '', - 'passwd' => '', - 'temppassword' => '', - 'tos' => 0))); - $this->Users->beforeFilter(); - $this->Users->register(); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'Your account could not be created. Please, try again.', true)); - } - -/** - * Test - * - */ - public function testVerify() { - $this->Users->beforeFilter(); - $this->Users->passedArgs[1] = 'testtoken2'; - $this->Users->User->id = '37ea303a-3bdc-4251-b315-1316c0b300fa'; - $this->Users->User->saveField('email_token_expires', date('Y-m-d H:i:s', strtotime('+1 year'))); - $this->Users->verify($type = 'email'); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'Your e-mail has been validated!', true)); - - $this->Users->beforeFilter(); - $this->Users->passedArgs[1] = 'invalid-token'; - $this->Users->verify($type = 'email'); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'The url you accessed is not longer valid', true)); - } - -/** - * Test logout - * - * @return void - */ - public function testLogout() { - $this->Users->beforeFilter(); - $this->Users->Session->write('Auth.User', $this->usersData['validUser']); - $this->Users->logout(); - $this->assertEqual($this->Users->Session->read('Message.flash.message'), __d('users', 'floriank you have successfully logged out', true)); - $this->assertEqual($this->Users->redirectUrl, '/'); - } - -/** - * testIndex - * - * @return void - */ - public function testIndex() { - $this->Users->params = array( - 'url' => array()); - $this->Users->passedArgs = array(); - $this->Users->index(); - $this->assertTrue(isset($this->Users->viewVars['users'])); - } - -/** - * testView - * - * @return void - */ - public function testView() { - $this->Users->view('phpnut'); - $this->assertTrue(isset($this->Users->viewVars['user'])); - - $this->Users->view('INVALID-SLUG'); - $this->assertEqual($this->Users->redirectUrl, '/'); - } - -/** - * testSearch - * - * @return void - */ - public function testSearch() { - $this->Users->params = array( - 'url' => array(), - 'named' => array( - 'search' => 'phpnut')); - $this->Users->passedArgs = array(); - $this->Users->search(); - $this->assertTrue(isset($this->Users->viewVars['users'])); - } - -/** - * change_password - * - * @return void - */ - public function testChangePassword() { - $this->Users->Session->write('Auth.User.id', '1'); - $this->Users->data = array( - 'User' => array( - 'new_password' => 'newpassword', - 'confirm_password' => 'newpassword', - 'old_password' => 'test')); - $this->Users->change_password(); - $this->assertEqual($this->Users->redirectUrl, '/'); - } - -/** - * testEdit - * - * @return void - */ - public function testEdit() { - $this->Users->Session->write('Auth.User.id', '1'); - $this->Users->edit(); - $this->assertTrue(!empty($this->Users->data)); - } - -/** - * testResetPassword - * - * @return void - */ - public function testResetPassword() { - $this->Users->User->id = '1'; - $this->Users->User->saveField('email_token_expires', date('Y-m-d H:i:s', strtotime('+1 year'))); - $this->Users->data = array( - 'User' => array( - 'email' => 'larry.masters@cakedc.com')); - $this->Users->reset_password(); - $this->assertEqual($this->Users->redirectUrl, array('action' => 'login')); - - - $this->Users->data = array( - 'User' => array( - 'new_password' => 'newpassword', - 'confirm_password' => 'newpassword')); - $this->Users->reset_password('testtoken'); - $this->assertEqual($this->Users->redirectUrl, $this->Users->Auth->loginAction); - } - -/** - * testAdminIndex - * - * @return void - */ - public function testAdminIndex() { - $this->Users->params = array( - 'url' => array(), - 'named' => array( - 'search' => 'phpnut')); - $this->Users->passedArgs = array(); - $this->Users->admin_index(); - $this->assertTrue(isset($this->Users->viewVars['users'])); - } - -/** - * testAdminView - * - * @return void - */ - public function testAdminView() { - $this->Users->admin_view('1'); - $this->assertTrue(isset($this->Users->viewVars['user'])); - } - -/** - * testAdminDelete - * - * @return void - */ - public function testAdminDelete() { - $this->Users->User->id = '1'; - $this->assertTrue($this->Users->User->exists(true)); - $this->Users->admin_delete('1'); - $this->assertEqual($this->Users->redirectUrl, array('action' => 'index')); - $this->assertFalse($this->Users->User->exists(true)); - - $this->Users->admin_delete('INVALID-ID'); - $this->assertEqual($this->Users->redirectUrl, array('action' => 'index')); - } - -/** - * Test setting the cookie - * - */ - public function testSetCookie() { - $this->Users->data['User'] = array( - 'remember_me' => 1, - 'username' => 'test', - 'password' => 'testtest'); - $this->Users->setCookie(array( - 'name' => 'userTestCookie')); - $this->Users->Cookie->name = 'userTestCookie'; - $result = $this->Users->Cookie->read('User'); - $this->assertEqual($result, array( - 'username' => 'test', - 'password' => 'testtest')); - } - -/** - * Test - * - */ - private function __setPost($data = array()) { - $_SERVER['REQUEST_METHOD'] = 'POST'; - $this->Users->data = am($data, array('_method' => 'POST')); - } - -/** - * Test - * - */ - private function __setGet() { - $_SERVER['REQUEST_METHOD'] = 'GET'; - } - -/** - * Test - * - */ - public function endTest() { - $this->Users->Session->destroy(); - unset($this->Users); - ClassRegistry::flush(); - } - -} diff --git a/tests/cases/models/detail.test.php b/tests/cases/models/detail.test.php deleted file mode 100644 index f477d6c70..000000000 --- a/tests/cases/models/detail.test.php +++ /dev/null @@ -1,148 +0,0 @@ -Detail =& ClassRegistry::init('Users.Detail'); - } - -/** - * testDetailInstance - * - * @return void - */ - public function testDetailInstance() { - $this->assertTrue(is_a($this->Detail, 'Detail')); - } - -/** - * testDetailFind - * - * @return void - */ - public function testDetailFind() { - $this->Detail->recursive = -1; - $results = $this->Detail->find('all'); - $this->assertTrue(!empty($results)); - $this->assertTrue(is_array($results)); - } - -/** - * testGetSection - * - * @return void - */ - public function testGetSection() { - $result = $this->Detail->getSection('47ea303a-3b2c-4251-b313-4816c0a800fa', 'User'); // phpnut - $this->assertTrue(is_array($result)); - $this->assertTrue(!empty($result)); - $this->assertEqual($result, array( - 'User' => array( - 'firstname' => 'Larry', - 'middlename' => 'E', - 'lastname' => 'Masters'))); - - - $result = $this->Detail->getSection('47ea303a-3b2c-4251-b313-4816c0a800fa', 'Blog'); // phpnut - $this->assertTrue(is_array($result)); - $this->assertTrue(!empty($result)); - $this->assertEqual($result, array( - 'Blog' => array( - 'name' => 'My blog'))); - - - $result = $this->Detail->getSection('47ea303a-3b2c-4251-b313-4816c0a800fa'); // phpnut - $this->assertTrue(is_array($result)); - $this->assertTrue(!empty($result)); - $this->assertEqual($result, array( - 'User' => array( - 'firstname' => 'Larry', - 'middlename' => 'E', - 'lastname' => 'Masters'), - 'Blog' => array( - 'name' => 'My blog'))); - } - -/** - * testSaveSection - * - * @return void - */ - public function testSaveSection() { - $data = array( - 'Detail' => array( - 'biography' => 'Lipsum...', - 'firstname' => 'Florian', - 'lastname' => 'Krämer')); - $this->Detail->saveSection('47ea303a-3cyc-k251-b313-4811c0a800bf', $data, 'User'); - $result = $this->Detail->getSection('47ea303a-3cyc-k251-b313-4811c0a800bf', 'User'); - $this->assertEqual($result, array( - 'User' => array( - 'biography' => 'Lipsum...', - 'firstname' => 'Florian', - 'lastname' => 'Krämer'))); - - - $data = array( - 'Detail' => array( - 'biography' => 'Lipsum...', - 'firstname' => 'Foo', - 'lastname' => 'Bar')); - $this->Detail->saveSection('47ea303a-3cyc-k251-b313-4811c0a800bf', $data, 'User'); - $result = $this->Detail->getSection('47ea303a-3cyc-k251-b313-4811c0a800bf', 'User'); - $this->assertEqual($result, array( - 'User' => array( - 'biography' => 'Lipsum...', - 'firstname' => 'Foo', - 'lastname' => 'Bar'))); - - - $data = array( - 'User' => array( - 'email' => 'foo@bar.com')); - $this->Detail->saveSection('47ea303a-3cyc-k251-b313-4811c0a800bf', $data, 'User'); - $this->Detail->User->id = '47ea303a-3cyc-k251-b313-4811c0a800bf'; - $result = $this->Detail->User->field('User.email'); - $this->assertEqual($result, 'foo@bar.com'); - } - -} diff --git a/tests/cases/models/user.test.php b/tests/cases/models/user.test.php deleted file mode 100644 index c19c01846..000000000 --- a/tests/cases/models/user.test.php +++ /dev/null @@ -1,459 +0,0 @@ -User = ClassRegistry::init('Users.User'); - } - -/** - * - * - * @return void - */ - public function endTest() { - unset($this->User); - ClassRegistry::flush(); - } -/** - * - * - * @return void - */ - public function testUserInstance() { - $this->assertTrue(is_a($this->User, 'User')); - } - -/** - * Test to compare the passwords when a user registers - * - * @return void - */ - public function testConfirmPassword() { - $this->User->data['User']['passwd'] = 'password'; - $result = $this->User->confirmPassword(array('temppassword' => 'password')); - $this->assertTrue($result); - - $this->User->data['User']['passwd'] = 'different_password'; - $result = $this->User->confirmPassword(array('temppassword' => 'password')); - $this->assertFalse($result); - } - -/** - * testValidateEmailConfirmation - * - * @return void - */ - public function testConfirmEmail() { - $this->User->data['User'] = array( - 'email' => 'test@email.com'); - $this->assertFalse($this->User->confirmEmail(array('confirm_email' => 'test@wrong.com'))); - - $this->User->data['User'] = array( - 'email' => 'test@email.com'); - $this->assertTrue($this->User->confirmEmail(array('confirm_email' => 'test@email.com'))); - } - -/** - * Test if the generated token is a string - * - * @return void - */ - function testGenerateToken() { - $result = $this->User->generateToken(); - $this->assertIsA($result, 'string'); - } - -/** - * - * - * @return void - */ - function testValidateToken() { - $result = $this->User->validateToken('no valid token'); - $this->assertFalse($result); - - $now = strtotime('2008-03-25 02:48:46'); - $result = $this->User->validateToken('testtoken2', false, $now); - $this->assertIsA($result, 'array'); - - $now = strtotime('2008-03-29 02:48:46'); - $result = $this->User->validateToken('testtoken2', false, $now); - $this->assertFalse($result); - } - -/** - * - * - * @return void - */ - public function testUpdateLastActivity() { - $id = '1'; - $this->User->id = $id; - $lastDate = $this->User->field('last_activity'); - $this->User->updateLastActivity($id); - $newDate = $this->User->field('last_activity'); - $this->assertTrue($lastDate < $newDate); - $this->assertFalse($this->User->updateLastActivity('invalid-id!')); - } - -/** - * - * - * @return void - */ - public function testResetPassword() { - $data = array( - 'User' => array( - 'id' => 1, - 'new_password' => '', - 'confirm_password' => 'dsgdsgsdg')); - $this->assertFalse($this->User->resetPassword($data)); - - - $data = array( - 'User' => array( - 'id' => 1, - 'new_password' => '', - 'confirm_password' => '')); - $this->assertFalse($this->User->resetPassword($data)); - - - $data = array( - 'User' => array( - 'id' => 1, - 'new_password' => 'newpassword', - 'confirm_password' => 'newpassword')); - $this->assertTrue($this->User->resetPassword($data)); - } - -/** - * - * - * @return void - */ - public function testCheckPasswordToken() { - $this->User->id = '1'; - $this->User->saveField('email_token_expires', date('Y-m-d H:i:s', strtotime('+1 year'))); - $this->assertTrue($this->User->checkPasswordToken('testtoken')); - $this->assertFalse($this->User->checkPasswordToken('something-wrong-here')); - } - -/** - * - * - * @return void - */ - public function testPasswordReset() { - $data = array( - 'User' => array( - 'id' => 1, - 'email' => 'somethingwrong in here!')); - $this->assertFalse($this->User->passwordReset($data)); - - - $this->User->id = '1'; - $this->User->saveField('email_token_expires', date('Y-m-d H:i:s', strtotime('+1 year'))); - $data = array( - 'User' => array( - 'id' => 1, - 'email' => 'larry.masters@cakedc.com')); - $this->assertTrue($this->User->passwordReset($data)); - } - -/** - * - * - * @return void - */ - public function testValidateOldPassword() { - App::import('Core', 'Security'); - $password = Security::hash('password', null, true); - $this->User->id = '1'; - $this->User->saveField('passwd', $password); - $this->User->data = array( - 'User' => array( - 'id' => '1', - 'passwd')); - $this->assertTrue($this->User->validateOldPassword(array('old_password' => 'password'))); - $this->assertFalse($this->User->validateOldPassword(array('old_password' => 'FAIL!'))); - } - -/** - * - * - * @return void - */ - public function testView() { - $result = $this->User->view('phpnut'); - $this->assertTrue(is_array($result) && !empty($result)); - - $this->expectException('Exception'); - $result = $this->User->view('non-existing-user-slug'); - } - -/** - * Test the user registration method - * - * @return void - */ - public function testRegister() { - App::import('Core', 'Security'); - $postData = array(); - $result = $this->User->register($postData); - $this->assertFalse($result); - - - $postData = array('User' => array( - 'username' => '#236236326sdg!!!.s#invalid', - 'email' => 'invalid', - 'passwd' => 'password', - 'temppassword' => 'wrong', - 'tos' => 0)); - $result = $this->User->register($postData); - $this->assertFalse($result); - $this->assertEqual(array_keys($this->User->invalidFields()), array( - 'username', 'email', 'temppassword', 'tos')); - - - $postData = array('User' => array( - 'username' => 'validusername', - 'email' => 'test@test.com', - 'passwd' => '12345', - 'temppassword' => '12345', - 'tos' => 1)); - $result = $this->User->register($postData); - $this->assertFalse($result); - $this->assertEqual(array_keys($this->User->invalidFields()), array( - 'passwd')); - - - $postData = array('User' => array( - 'username' => 'imanewuser', - 'email' => 'foo@bar.com', - 'passwd' => 'password', - 'temppassword' => 'password', - 'tos' => 1)); - $result = $this->User->register($postData); - $this->assertTrue(is_array($result)); - $this->assertEqual($result['User']['active'], 1); - $this->assertEqual($result['User']['slug'], 'imanewuser'); - $this->assertEqual($result['User']['passwd'], Security::hash('password', 'sha1', true)); - $this->assertTrue(is_string($result['User']['email_token'])); - - $result = $this->User->findById($this->User->id); - $this->assertEqual($result['User']['id'], $this->User->id); - } - -/** - * testChangePassword - * - * @return void - */ - public function testChangePassword() { - $postData = array(); - $result = $this->User->changePassword($postData); - $this->assertFalse($result); - - - $postData = array( - 'User' => array( - 'id' => 1, - 'old_password' => 'test', - 'new_password' => 'not', - 'confirm_password' => 'equal')); - - $result = $this->User->changePassword($postData); - $this->assertFalse($result); - $this->assertEqual(array('new_password', 'confirm_password'), array_keys($this->User->invalidFields())); - - - $postData = array( - 'User' => array( - 'id' => 1, - 'old_password' => 'test', - 'new_password' => 'testtest', - 'confirm_password' => 'testtest')); - $result = $this->User->changePassword($postData); - $this->assertTrue($result); - $ressult = $this->User->find('first', array( - 'recursive' => -1, - 'conditions' => array( - 'User.id' => 1))); - $this->assertEqual($ressult['User']['passwd'], Security::hash('testtest', null, true)); - } - -/** - * Test validation method to compare two fields - * - * @return void - */ - public function testCompareFields() { - $this->User->data = array( - 'User' => array( - 'field1' => 'foo', - 'field2' => 'bar')); - $this->assertFalse($this->User->compareFields('field1', 'field2')); - - - $this->User->data = array( - 'User' => array( - 'field1' => 'foo', - 'field2' => 'foo')); - $this->assertTrue($this->User->compareFields('field1', 'field2')); - } - -/** - * Test resending of the email authentication - * - * @return void - */ - public function testResendVerification() { - $postData = array( - 'User' => array()); - $this->assertFalse($this->User->resendVerification($postData)); - - - $postData = array( - 'User' => array( - 'email' => 'doesnotexist!')); - $this->assertFalse($this->User->resendVerification($postData)); - - - $postData = array( - 'User' => array( - 'email' => 'larry.masters@cakedc.com')); - $this->assertFalse($this->User->resendVerification($postData)); - - - $postData = array( - 'User' => array( - 'email' => 'oidtest2@testuser.com')); - $result = $this->User->resendVerification($postData); - $this->assertTrue(is_array($result)); - } - -/** - * Test resending of the email authentication - * - * @return void - */ - public function testGeneratePassword() { - $result = $this->User->generatePassword(); - $this->assertIsA($result, 'string'); - $this->assertEqual(strlen($result), 10); - - - $result = $this->User->generatePassword(15); - $this->assertIsA($result, 'string'); - $this->assertEqual(strlen($result), 15); - } - -/** - * testDelete - * - * @return void - */ - public function testDelete() { - $this->User->id = '1'; - $this->assertTrue($this->User->exists()); - $this->assertTrue($this->User->delete('1')); - $this->assertFalse($this->User->exists()); - } - -/** - * testFindSearch - * - * @return void - */ - public function testFindSearch() { - $result = $this->User->find('search', array('by' => 'username', 'search' => 'php')); - $this->assertTrue(!empty($result)); - $this->assertEqual($result[0]['User']['username'], 'phpnut'); - } - -/** - * testAdd - * - * @return void - */ - public function testAdd() { - $postData = array( - 'User' => array( - 'username' => 'newusername', - 'email' => 'newusername@newusername.com', - 'passwd' => 'password', - 'temppassword' => 'password', - 'tos' => 1)); - $result = $this->User->add($postData); - $this->assertTrue($result); - } - -/** - * testEdit - * - * @return void - **/ - public function testEdit() { - $userId = '1'; - $data = $this->User->read(null, $userId); - $data['User']['email'] = 'anotherNewEmail@anothernewemail.com'; - - $result = $this->User->edit(1, $data); - $this->assertTrue($result); - - $result = $this->User->read(null, 1); - $this->assertEqual($result['User']['username'], $data['User']['username']); - $this->assertEqual($result['User']['email'], $data['User']['email']); - - try { - $this->User->edit('bogus id', $userId, $data); - $this->fail('No exception'); - } catch (OutOfBoundsException $e) { - $this->pass('Correct exception thrown'); - } - } -} diff --git a/tests/fixtures/detail_fixture.php b/tests/fixtures/detail_fixture.php deleted file mode 100644 index 3067c3039..000000000 --- a/tests/fixtures/detail_fixture.php +++ /dev/null @@ -1,92 +0,0 @@ - array('type'=>'string', 'null' => false, 'length' => 36, 'key' => 'primary'), - 'user_id' => array('type'=>'string', 'null' => false, 'length' => 36), - 'position' => array('type'=>'float', 'null' => false, 'default' => '1', 'length' => 4), - 'field' => array('type'=>'string', 'null' => false, 'key' => 'index'), - 'value' => array('type'=>'text', 'null' => true, 'default' => NULL), - 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array( - 'PRIMARY' => array('column' => 'id', 'unique' => 1), - 'UNIQUE_PROFILE_PROPERTY' => array('column' => array('field', 'user_id'), 'unique' => 1) - ) - ); - -/** - * Records - * - * @var array $records - */ - public $records = array( - array( - 'id' => '491d06d1-0648-407b-81f5-347182f0cb67', - 'user_id' => '47ea303a-3b2c-4251-b313-4816c0a800fa', //phpnut - 'position' => 2, - 'field' => 'User.firstname', - 'value' => 'Larry', - 'created' => '2008-03-25 01:47:31', - 'modified' => '2008-03-25 01:47:31'), - array( - 'id' => '491d06f0-b93c-43ba-9b79-346082f0cb67', - 'user_id' => '47ea303a-3b2c-4251-b313-4816c0a800fa',//phpnut - 'position' => 3, - 'field' => 'User.middlename', - 'value' => 'E', - 'created' => '2008-03-25 01:47:31', - 'modified' => '2008-03-25 01:47:31'), - array( - 'id' => '491d0704-5e68-4de2-92c7-345c82f0cb67', - 'user_id' => '47ea303a-3b2c-4251-b313-4816c0a800fa',//phpnut - 'position' => 4, - 'field' => 'User.lastname', - 'value' => 'Masters', - 'created' => '2008-03-25 01:47:31', - 'modified' => '2008-03-25 01:47:31'), - array( - 'id' => '491d0704-5e68-4de3-92c7-345c82f0cb67', - 'user_id' => '47ea303a-3b2c-4251-b313-4816c0a800fa',//phpnut - 'position' => 5, - 'field' => 'Blog.name', - 'value' => 'My blog', - 'created' => '2008-03-25 01:47:31', - 'modified' => '2008-03-25 01:47:31') - ); -} diff --git a/tests/fixtures/user_fixture.php b/tests/fixtures/user_fixture.php deleted file mode 100644 index edbb70ddf..000000000 --- a/tests/fixtures/user_fixture.php +++ /dev/null @@ -1,194 +0,0 @@ - array('type'=>'string', 'null' => false, 'length' => 36, 'key' => 'primary'), - 'username' => array('type'=>'string', 'null' => false, 'default' => NULL), - 'slug' => array('type'=>'string', 'null' => false, 'default' => NULL), - 'passwd' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'password_token' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 128), - 'email' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'email_authenticated' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'email_token' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'email_token_expires' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'tos' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'active' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'last_activity' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'last_login' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'is_admin' => array('type'=>'boolean', 'null' => true, 'default' => '0'), - 'role' => array('type'=>'string', 'null' => true, 'default' => NULL), - 'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL), - 'indexes' => array( - 'PRIMARY' => array('column' => 'id', 'unique' => 1)) - ); - -/** - * Records - * - * @var array - */ - public $records = array( - array( - 'id' => '1', - 'username' => 'phpnut', - 'slug' => 'phpnut', - 'passwd' => 'test', // test - 'password_token' => 'testtoken', - 'email' => 'larry.masters@cakedc.com', - 'email_authenticated' => 1, - 'email_token' => 'testtoken', - 'email_token_expires' => '2008-03-25 02:45:46', - 'tos' => 1, - 'active' => 1, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 1, - 'role' => 'admin', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ), - array( - 'id' => '47ea303a-3cyc-k251-b313-4811c0a800bf', - 'username' => 'floriank', - 'slug' => 'floriank', - 'passwd' => 'secretkey', // secretkey - 'password_token' => '', - 'email' => 'florian.kraemer@cakedc.com', - 'email_authenticated' => '1', - 'email_token' => '', - 'email_token_expires' => '2008-03-25 02:45:46', - 'tos' => 1, - 'active' => 1, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 0, - 'role' => 'user', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ), - array( - 'id' => '37ea303a-3bdc-4251-b315-1316c0b300fa', - 'username' => 'user1', - 'slug' => 'user1', - 'passwd' => 'newpass', // newpass - 'password_token' => '', - 'email' => 'testuser1@testuser.com', - 'email_authenticated' => 0, - 'email_token' => 'testtoken2', - 'email_token_expires' => '2008-03-28 02:45:46', - 'tos' => 0, - 'active' => 0, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 0, - 'role' => 'user', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ), - array( - 'id' => '495e36a2-1f00-46b9-8247-58a367265f11', - 'username' => 'oidtest', - 'slug' => 'oistest', - 'passwd' => 'newpass', // newpass - 'password_token' => '', - 'email' => 'oidtest@testuser.com', - 'email_authenticated' => 0, - 'email_token' => 'testtoken2', - 'email_token_expires' => '2008-03-28 02:45:46', - 'tos' => 0, - 'active' => 0, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 0, - 'role' => 'user', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ), - array( - 'id' => '315e36a2-1fxj-46b9-8247-58a367265f11', - 'username' => 'oidtest2', - 'slug' => 'oistest', - 'passwd' => 'newpass', // newpass - 'password_token' => '', - 'email' => 'oidtest2@testuser.com', - 'email_authenticated' => 0, - 'email_token' => 'testtoken2', - 'email_token_expires' => '2008-03-28 02:45:46', - 'tos' => 1, - 'active' => 1, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 0, - 'role' => 'user', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ), - array( - 'id' => '515e36a2-5fjj-46b9-8247-584367265f11', - 'username' => 'resetuser', - 'slug' => 'resetuser', - 'passwd' => 'newpass', // newpass - 'password_token' => 'testtoken', - 'email' => 'resetuser@testuser.com', - 'email_authenticated' => 1, - 'email_token' => 'testtoken', - 'email_token_expires' => '2008-03-28 02:45:46', - 'tos' => 1, - 'active' => 1, - 'last_activity' => '2008-03-25 02:45:46', - 'last_login' => '2008-03-25 02:45:46', - 'is_admin' => 0, - 'role' => 'user', - 'created' => '2008-03-25 02:45:46', - 'modified' => '2008-03-25 02:45:46' - ) - ); - -/** - * - */ - public function __construct() { - parent::__construct(); - App::import('Core', 'Security'); - foreach ($this->records as &$record) { - $record['passwd'] = Security::hash($record['passwd'], null, true); - } - } - -} 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/users_app_controller.php b/users_app_controller.php deleted file mode 100644 index 92b5400ae..000000000 --- a/users_app_controller.php +++ /dev/null @@ -1,19 +0,0 @@ -recursive) { - $parameters['recursive'] = $recursive; - } - if (isset($extra['type']) && isset($this->_findMethods[$extra['type']])) { - $extra['operation'] = 'count'; - return $this->find($extra['type'], array_merge($parameters, $extra)); - } else { - return $this->find('count', array_merge($parameters, $extra)); - } - } - -} diff --git a/views/details/add.ctp b/views/details/add.ctp deleted file mode 100644 index 766d384c7..000000000 --- a/views/details/add.ctp +++ /dev/null @@ -1,31 +0,0 @@ - -
-Form->create('Detail');?> -
- - Form->input('user_id'); - echo $this->Form->input('position'); - echo $this->Form->input('field'); - echo $this->Form->input('value'); - ?> -
-Form->end('Submit');?> -
-
-
    -
  • Html->link(__d('users', 'List Details', true), array('action'=>'index'));?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
diff --git a/views/details/admin_add.ctp b/views/details/admin_add.ctp deleted file mode 100644 index 766d384c7..000000000 --- a/views/details/admin_add.ctp +++ /dev/null @@ -1,31 +0,0 @@ - -
-Form->create('Detail');?> -
- - Form->input('user_id'); - echo $this->Form->input('position'); - echo $this->Form->input('field'); - echo $this->Form->input('value'); - ?> -
-Form->end('Submit');?> -
-
-
    -
  • Html->link(__d('users', 'List Details', true), array('action'=>'index'));?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
diff --git a/views/details/admin_edit.ctp b/views/details/admin_edit.ctp deleted file mode 100644 index 4ee6be0a2..000000000 --- a/views/details/admin_edit.ctp +++ /dev/null @@ -1,33 +0,0 @@ - -
-Form->create('Detail');?> -
- - Form->input('id'); - echo $this->Form->input('user_id'); - echo $this->Form->input('position'); - echo $this->Form->input('field'); - echo $this->Form->input('value'); - ?> -
-Form->end('Submit');?> -
-
-
    -
  • Html->link(__d('users', 'Delete', true), array('action'=>'delete', $this->Form->value('Detail.id')), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $this->Form->value('Detail.id'))); ?>
  • -
  • Html->link(__d('users', 'List Details', true), array('action'=>'index'));?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
diff --git a/views/details/admin_index.ctp b/views/details/admin_index.ctp deleted file mode 100644 index 28d85b202..000000000 --- a/views/details/admin_index.ctp +++ /dev/null @@ -1,81 +0,0 @@ - -
-

-

-Paginator->counter(array( -'format' => __d('users', 'Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) -)); -?>

- - - - - - - - - - - - - > - - - - - - - - - - -
Paginator->sort('id');?>Paginator->sort('user_id');?>Paginator->sort('position');?>Paginator->sort('field');?>Paginator->sort('value');?>Paginator->sort('created');?>Paginator->sort('modified');?>
- - - Html->link($detail['User']['id'], array('controller'=> 'users', 'action'=>'view', $detail['User']['id'])); ?> - - - - - - - - - - - - Html->link(__d('users', 'View', true), array('action'=>'view', $detail['Detail']['id'])); ?> - Html->link(__d('users', 'Edit', true), array('action'=>'edit', $detail['Detail']['id'])); ?> - Html->link(__d('users', 'Delete', true), array('action'=>'delete', $detail['Detail']['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $detail['Detail']['id'])); ?> -
-
-
- Paginator->prev('<< '.__d('users', 'previous', true), array(), null, array('class'=>'disabled'));?> - | Paginator->numbers();?> - Paginator->next(__d('users', 'next', true).' >>', array(), null, array('class'=>'disabled'));?> -
-
-
    -
  • Html->link(__d('users', 'New Detail', true), array('action'=>'add')); ?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
diff --git a/views/details/admin_view.ctp b/views/details/admin_view.ctp deleted file mode 100644 index ec425b0c3..000000000 --- a/views/details/admin_view.ctp +++ /dev/null @@ -1,61 +0,0 @@ - -
-

-
- > - > - -   - - > - > - Html->link($detail['User']['id'], array('controller'=> 'users', 'action'=>'view', $detail['User']['id'])); ?> -   - - > - > - -   - - > - > - -   - - > - > - -   - - > - > - -   - - > - > - -   - -
-
-
-
    -
  • Html->link(__d('users', 'Edit Detail', true), array('action'=>'edit', $detail['Detail']['id'])); ?>
  • -
  • Html->link(__d('users', 'Delete Detail', true), array('action'=>'delete', $detail['Detail']['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $detail['Detail']['id'])); ?>
  • -
  • Html->link(__d('users', 'List Details', true), array('action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New Detail', true), array('action'=>'add')); ?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
\ No newline at end of file diff --git a/views/details/edit.ctp b/views/details/edit.ctp deleted file mode 100644 index 9dae678bf..000000000 --- a/views/details/edit.ctp +++ /dev/null @@ -1,26 +0,0 @@ - -
-Form->create('Detail', array( - 'action' => 'edit'));?> -
- - Form->input('firstname'); - echo $this->Form->input('middlename'); - echo $this->Form->input('lastname'); - echo $this->Form->input('biography'); - echo $this->Form->input('birthday'); - ?> -
-Form->end('Submit');?> -
\ No newline at end of file diff --git a/views/details/index.ctp b/views/details/index.ctp deleted file mode 100644 index bb23d05c9..000000000 --- a/views/details/index.ctp +++ /dev/null @@ -1,28 +0,0 @@ -Form->create('Detail'); - foreach ($details as $detail) { - $options = array(); - $options['type'] = $detail['Detail']['input']; - if ($detail['Detail']['input'] == 'checkbox') { - if ($detail['Detail']['value'] == 1) { - $options['checked'] = true; - } - } - if ($detail['Detail']['input'] == 'text' || $detail['Detail']['input'] == 'textarea' ) { - $options['value'] = $detail['Detail']['value'];; - } - echo $this->Form->input($detail['Detail']['field'], ($options)); - } - echo $this->Form->end(__d('users', 'Submit', true)); -} diff --git a/views/details/view.ctp b/views/details/view.ctp deleted file mode 100644 index dce0fa870..000000000 --- a/views/details/view.ctp +++ /dev/null @@ -1,62 +0,0 @@ - -
-

-
- > - > - -   - - > - > - Html->link($detail['User']['id'], array('controller'=> 'users', 'action'=>'view', $detail['User']['id'])); ?> -   - - > - > - -   - - > - > - -   - - > - > - -   - - > - > - -   - - > - > - -   - -
-
- -
-
    -
  • Html->link(__d('users', 'Edit Detail', true), array('action'=>'edit', $detail['Detail']['id'])); ?>
  • -
  • Html->link(__d('users', 'Delete Detail', true), array('action'=>'delete', $detail['Detail']['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $detail['Detail']['id'])); ?>
  • -
  • Html->link(__d('users', 'List Details', true), array('action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New Detail', true), array('action'=>'add')); ?>
  • -
  • Html->link(__d('users', 'List Users', true), array('controller'=> 'users', 'action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('controller'=> 'users', 'action'=>'add')); ?>
  • -
-
\ No newline at end of file diff --git a/views/elements/email/text/account_verification.ctp b/views/elements/email/text/account_verification.ctp deleted file mode 100644 index 0c372b030..000000000 --- a/views/elements/email/text/account_verification.ctp +++ /dev/null @@ -1,16 +0,0 @@ - false, 'plugin' => 'users', 'controller' => 'users', 'action' => 'verify', 'email', $user['User']['email_token']), true); diff --git a/views/elements/email/text/password_reset_request.ctp b/views/elements/email/text/password_reset_request.ctp deleted file mode 100644 index 111e7df99..000000000 --- a/views/elements/email/text/password_reset_request.ctp +++ /dev/null @@ -1,14 +0,0 @@ - false, 'plugin' => 'users', 'controller' => 'users', 'action' => 'reset_password', $token), true); diff --git a/views/elements/login.ctp b/views/elements/login.ctp deleted file mode 100644 index 7ee85a282..000000000 --- a/views/elements/login.ctp +++ /dev/null @@ -1,26 +0,0 @@ -Session->check('Auth.Users')) { - echo $this->Form->create('User', array( - 'url' => array( - 'admin' => false, - 'plugin' => 'users', - 'controller' => 'users', - 'action' => 'login'), - 'id' => 'LoginForm')); - echo $this->Form->input('email', array( - 'label' => __d('users', 'Email', true))); - echo $this->Form->input('passwd', array( - 'label' => __d('users', 'Password', true), - 'type' => 'password')); - echo $this->Form->end(__d('users', 'Login', true)); -} diff --git a/views/users/admin_add.ctp b/views/users/admin_add.ctp deleted file mode 100644 index 357995873..000000000 --- a/views/users/admin_add.ctp +++ /dev/null @@ -1,31 +0,0 @@ - -
-Form->create($model);?> -
- - Form->input('username'); - echo $this->Form->input('email'); - echo $this->Form->input('slug'); - echo $this->Form->input('role'); - echo $this->Form->input('active'); - echo $this->Form->input('is_admin'); - ?> -
-Form->end('Submit');?> -
-
-
    -
  • Html->link(__d('users', 'List Users', true), array('action'=>'index'));?>
  • -
-
diff --git a/views/users/admin_edit.ctp b/views/users/admin_edit.ctp deleted file mode 100644 index 42ca50391..000000000 --- a/views/users/admin_edit.ctp +++ /dev/null @@ -1,33 +0,0 @@ - -
-Form->create($model);?> -
- - Form->input('id'); - echo $this->Form->input('username'); - echo $this->Form->input('email'); - echo $this->Form->input('slug'); - echo $this->Form->input('role'); - echo $this->Form->input('active'); - echo $this->Form->input('is_admin'); - ?> -
-Form->end('Submit');?> -
-
-
    -
  • Html->link(__d('users', 'Delete', true), array('action'=>'delete', $this->Form->value('User.id')), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $this->Form->value('User.id'))); ?>
  • -
  • Html->link(__d('users', 'List Users', true), array('action'=>'index'));?>
  • -
-
diff --git a/views/users/admin_index.ctp b/views/users/admin_index.ctp deleted file mode 100644 index ec0e27349..000000000 --- a/views/users/admin_index.ctp +++ /dev/null @@ -1,67 +0,0 @@ - -
-

- -

- Form->create($model, array('action' => 'index')); - echo $this->Form->input('username', array( - 'label' => __d('users', 'Username', true))); - echo $this->Form->input('email', array( - 'label' => __d('users', 'Email', true))); - echo $this->Form->end(__d('users', 'Search', true)); - ?> - - element('paging'); ?> - - - - - - - - - - - > - - - - - - - - -
Paginator->sort('username');?>Paginator->sort('email');?>Paginator->sort('email_authenticated');?>Paginator->sort('active');?>Paginator->sort('created');?>
- - - - - - - - - - - Html->link(__d('users', 'View', true), array('action'=>'view', $user[$model]['id'])); ?> - Html->link(__d('users', 'Edit', true), array('action'=>'edit', $user[$model]['id'])); ?> - Html->link(__d('users', 'Delete', true), array('action'=>'delete', $user[$model]['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $user[$model]['id'])); ?> -
-
diff --git a/views/users/admin_view.ctp b/views/users/admin_view.ctp deleted file mode 100644 index e0ea3502e..000000000 --- a/views/users/admin_view.ctp +++ /dev/null @@ -1,39 +0,0 @@ - -
-

-
- > - > - -   - - > - > - -   - - > - > - -   - -
-
-
-
    -
  • Html->link(__d('users', 'Edit User', true), array('action'=>'edit', $user[$model]['id'])); ?>
  • -
  • Html->link(__d('users', 'Delete User', true), array('action'=>'delete', $user[$model]['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $user[$model]['id'])); ?>
  • -
  • Html->link(__d('users', 'List Users', true), array('action'=>'index')); ?>
  • -
  • Html->link(__d('users', 'New User', true), array('action'=>'add')); ?>
  • -
-
diff --git a/views/users/change_password.ctp b/views/users/change_password.ctp deleted file mode 100644 index 155e08e90..000000000 --- a/views/users/change_password.ctp +++ /dev/null @@ -1,28 +0,0 @@ - -

-

- -

-Form->create($model, array('action' => 'change_password')); - echo $this->Form->input('old_password', array( - 'label' => __d('users', 'Old Password', true), - 'type' => 'password')); - echo $this->Form->input('new_password', array( - 'label' => __d('users', 'New Password', true), - 'type' => 'password')); - echo $this->Form->input('confirm_password', array( - 'label' => __d('users', 'Confirm', true), - 'type' => 'password')); - echo $this->Form->end(__d('users', 'Submit', true)); -?> \ No newline at end of file diff --git a/views/users/dashboard.ctp b/views/users/dashboard.ctp deleted file mode 100644 index 838833a75..000000000 --- a/views/users/dashboard.ctp +++ /dev/null @@ -1,15 +0,0 @@ - -
-

-

-
\ No newline at end of file diff --git a/views/users/edit.ctp b/views/users/edit.ctp deleted file mode 100644 index 679da5ed8..000000000 --- a/views/users/edit.ctp +++ /dev/null @@ -1,22 +0,0 @@ - -Form->create($model);?> -
- - Form->input('id'); - echo $this->Form->input('account_type'); - echo $this->Form->input('url'); - echo $this->Form->input('username'); - ?> -
-Form->end('Submit');?> \ No newline at end of file diff --git a/views/users/index.ctp b/views/users/index.ctp deleted file mode 100644 index ea27ff4e6..000000000 --- a/views/users/index.ctp +++ /dev/null @@ -1,59 +0,0 @@ - -
-

-

-Paginator->counter(array( -'format' => __d('users', 'Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) -)); -?>

- - - - - - - - > - - - - - -
Paginator->sort('username');?>Paginator->sort('created');?>
- - - - - Html->link(__d('users', 'View', true), array('action'=>'view', $user[$model]['id'])); ?> - Html->link(__d('users', 'Edit', true), array('action'=>'edit', $user[$model]['id'])); ?> - Html->link(__d('users', 'Delete', true), array('action'=>'delete', $user[$model]['id']), null, sprintf(__d('users', 'Are you sure you want to delete # %s?', true), $user[$model]['id'])); ?> -
-
-
- Paginator->prev('<< '.__d('users', 'previous', true), array(), null, array('class'=>'disabled'));?> - | Paginator->numbers();?> - Paginator->next(__d('users', 'next', true).' >>', array(), null, array('class'=>'disabled'));?> -
-
-
    -
  • Html->link(__d('users', 'Register an account', true), array('action' => 'register')); ?>
  • -
-
diff --git a/views/users/login.ctp b/views/users/login.ctp deleted file mode 100644 index bbabce902..000000000 --- a/views/users/login.ctp +++ /dev/null @@ -1,26 +0,0 @@ - -

-
- - Form->create($model, array( - 'action' => 'login')); - echo $this->Form->input('email', array( - 'label' => __d('users', 'Email', true))); - echo $this->Form->input('passwd', array( - 'label' => __d('users', 'Password', true))); - echo __d('users', 'Remember Me') . $this->Form->checkbox('remember_me'); - echo $this->Form->hidden('User.return_to', array('value' => $return_to)); - echo $this->Form->end(__d('users', 'Submit', true)); - ?> -
\ No newline at end of file diff --git a/views/users/register.ctp b/views/users/register.ctp deleted file mode 100644 index 308b6c1a6..000000000 --- a/views/users/register.ctp +++ /dev/null @@ -1,103 +0,0 @@ - -Session->read('openIdAuthData');?> -

-
- - Form->create($model, array('url' => array('action'=>'register'))); - echo $this->Form->input('username', array( - 'error' => array( - 'unique_username' => __d('users', 'Please select a username that is not already in use', true), - 'username_min' => __d('users', 'Must be at least 3 characters', true), - 'alpha' => __d('users', 'Username must contain numbers and letters only', true), - 'required' => __d('users', 'Please choose username', true)))); - echo $this->Form->input('email', array( - 'label' => __d('users', 'E-mail (used as login)',true), - 'error' => array('isValid' => __d('users', 'Must be a valid email address', true), - 'isUnique' => __d('users', 'An account with that email already exists', true)))); - echo $this->Form->input('passwd', array( - 'label' => __d('users', 'Password',true), - 'type' => 'password', - 'error' => __d('users', 'Must be at least 5 characters long', true))); - echo $this->Form->input('temppassword', array( - 'label' => __d('users', 'Password (confirm)', true), - 'type' => 'password', - 'error' => __d('users', 'Passwords must match', true) - ) - ); - echo $this->Form->input('tos', array( - 'label' => __d('users', 'I have read and agreed to ', true) . $this->Html->link(__d('users', 'Terms of Service', true), array('controller' => 'pages', 'action' => 'tos')), - 'error' => __d('users', 'You must verify you have read the Terms of Service', true) - ) - ); - echo $this->Form->end(__d('users', 'Submit',true)); - } else { - if(isset($openIdAuthData['openid_claimed_id'])) { - $oid = $openIdAuthData['openid_claimed_id']; - } else { - $oid = $openIdAuthData['openid_identity']; - } - echo $this->Form->create('Openid.OpenidUser', array('url' => array('plugin' => 'openid', 'controller' => 'openid_users', 'action' => 'attach_identity'))); - echo $this->Form->input('openid_identifier', array( - 'name' => 'data[OpenidUser][openid_url]', - 'class' => 'openid', - 'value' => $oid, - 'type' => 'hidden', - 'label' => __d('users', 'Openid Identifier', true) - ) - ); - - if (isset($openIdAuthData['openid_sreg_nickname'])) { - $username = $openIdAuthData['openid_sreg_nickname']; - } else { - $username = ''; - } - echo $this->Form->input('username', array( - 'value' => $username, - 'label' => __d('users', 'Username', true), - )); - - if (isset($this->params['named']['username_taken'])) { - echo $this->Form->input('username', array( - 'value' => $openIdAuthData['openid_sreg_nickname'], - 'label' => __d('users', 'Username', true), - ) - ); - } - - if (isset($openIdAuthData['openid_sreg_email'])) { - echo $this->Form->input('email', array( - 'value' => $openIdAuthData['openid_sreg_email'], - 'label' => __d('users', 'Email', true), - 'type' => 'hidden', - ) - ); - } elseif (isset($openIdAuthData['openid_ext1_value_email'])) { - echo $this->Form->input('email', array( - 'value' => $openIdAuthData['openid_ext1_value_email'], - 'label' => __d('users', 'Email', true), - 'type' => 'hidden', - ) - ); - } - echo $this->Form->input('tos', array( - 'type' => 'checkbox', - 'label' => __d('users', 'I have read and agreed to ', true) . $this->Html->link(__d('users', 'Terms of Service', true), array('controller' => 'pages', 'action' => 'tos')), - 'error' => __d('users', 'You must verify you have read the Terms of Service', true) - ) - ); - echo $this->Form->end(__d('users', 'Submit',true)); - } -?> -
\ No newline at end of file diff --git a/views/users/request_password_change.ctp b/views/users/request_password_change.ctp deleted file mode 100644 index 5c866640a..000000000 --- a/views/users/request_password_change.ctp +++ /dev/null @@ -1,25 +0,0 @@ - -

-

- -

-Form->create($model, array( - 'url' => array( - 'admin' => false, - 'action' => 'reset_password'))); - echo $this->Form->input('email', array( - 'label' => __d('users', 'Your Email', true))); - echo $this->Form->submit(__d('users', 'Submit', true)); - echo $this->Form->end(); -?> \ No newline at end of file diff --git a/views/users/reset_password.ctp b/views/users/reset_password.ctp deleted file mode 100644 index 3e831e48e..000000000 --- a/views/users/reset_password.ctp +++ /dev/null @@ -1,16 +0,0 @@ -

- -Form->create($model, array( - 'url' => array( - 'action' => 'reset_password', - $token))); - echo $this->Form->input('new_password', array( - 'label' => __d('users', 'New Password', true), - 'type' => 'password')); - echo $this->Form->input('confirm_password', array( - 'label' => __d('users', 'Confirm', true), - 'type' => 'password')); - echo $this->Form->submit(__d('users', 'Submit', true)); - echo $this->Form->end(); -?> \ No newline at end of file diff --git a/views/users/search.ctp b/views/users/search.ctp deleted file mode 100644 index 4c8dfa7a8..000000000 --- a/views/users/search.ctp +++ /dev/null @@ -1,22 +0,0 @@ - -

-Form->create($model, array('action' => 'search')); - echo $this->Form->input('username', array( - 'label' => __d('users', 'Username', true))); - echo $this->Form->input('email', array( - 'label' => __d('users', 'Email', true))); - echo $this->Form->input('Profile.name', array( - 'label' => __d('users', 'Name', true))); - echo $this->Form->end(__d('users', 'Search', true)); -?> \ No newline at end of file diff --git a/views/users/view.ctp b/views/users/view.ctp deleted file mode 100644 index 3cfcb76fb..000000000 --- a/views/users/view.ctp +++ /dev/null @@ -1,34 +0,0 @@ - -
-

-
- > - > - -   - - > - > - -   - - $value) { - echo '
' . $field . '
'; - echo '
' . $value . '
'; - } - } - ?> -
-
diff --git a/webroot/empty b/webroot/empty new file mode 100644 index 000000000..e69de29bb diff --git a/webroot/img/avatar_placeholder.png b/webroot/img/avatar_placeholder.png new file mode 100644 index 000000000..5a29e1a2d Binary files /dev/null and b/webroot/img/avatar_placeholder.png differ 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); + } +}