diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e6e623ce8..b596d4a9a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,11 +9,18 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Python + uses: actions/setup-python@v4 with: python-version: 3.x - - run: pip3 install mkdocs-material - - run: pip3 install mkdocs-git-revision-date-localized-plugin - - run: pip3 install mkdocs-redirects - - run: mkdocs gh-deploy --force + - name: Install Dependencies + run: | + pip3 install mkdocs-material + pip3 install mkdocs-git-revision-date-localized-plugin + pip3 install mkdocs-redirects + - name: Publish Shiled Documentation + run: mkdocs gh-deploy --force diff --git a/.github/workflows/no-merge-commits.yml b/.github/workflows/no-merge-commits.yml index 11173fbb4..6ce03b88c 100644 --- a/.github/workflows/no-merge-commits.yml +++ b/.github/workflows/no-merge-commits.yml @@ -2,6 +2,8 @@ name: Detect Merge Commits on: pull_request: + branches: + - develop permissions: contents: read diff --git a/.gitignore b/.gitignore index 2d47978cb..3e4d68617 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,11 @@ phpunit vendor/ composer.lock +#------------------------- +# MkDocs +#------------------------- +site/ + #------------------------- # IDE / Development Files #------------------------- diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5636649d5..0e986f3c2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + use CodeIgniter\CodingStandard\CodeIgniter4; use Nexus\CsConfig\Factory; use PhpCsFixer\Finder; @@ -28,4 +37,8 @@ 'cacheFile' => 'build/.php-cs-fixer.cache', ]; -return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); +return Factory::create(new CodeIgniter4(), $overrides, $options)->forLibrary( + 'CodeIgniter Shield', + 'CodeIgniter Foundation', + 'admin@codeigniter.com' +); diff --git a/README.md b/README.md index 8df491a39..b6f93e9a4 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ and authorization purposes in web applications. * Flexible Groups-based access control (think Roles, but more flexible) * Users can be granted additional Permissions -See the [An Official Auth Library](https://codeigniter.com/news/shield) for more Info. +See the [An Official Auth Library](https://forum.codeigniter.com/showthread.php?tid=82003) for more Info. ## Getting Started diff --git a/UPGRADING.md b/UPGRADING.md index 1a6bcd5cd..e004c9d2c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,72 @@ # Upgrade Guide +## Version 1.0.0-beta.7 to 1.0.0-beta.8 + +### Mandatory Config Changes + +#### Helper Autoloading + +Helper autoloading has been changed to be done by CodeIgniter's autoloader +instead of Composer. + +So you need to update the settings. Run `php spark shield:setup` again, and the +following steps will be done. + +1. Add `auth` and `setting` to the `$helpers` array in **app/Config/Autoload.php**: + + ```php + public $helpers = ['auth', 'setting']; + ``` + +2. Remove the following code in the `initController()` method in + `**app/Controllers/BaseController.php**: + + ```php + $this->helpers = array_merge($this->helpers, ['setting']); + ``` + +#### Config\Auth + +The following items have been added. Copy the properties in **src/Config/Auth.php**. + +- `permission_denied` and `group_denied` are added to `Config\Auth::$redirects`. +- `permissionDeniedRedirect()` and `groupDeniedRedirect()` are added. + +### Fix Custom Filter If extends `AbstractAuthFilter` + +If you have written a custom filter that extends `AbstractAuthFilter`, now you need to add and implement the `redirectToDeniedUrl()` method to your custom filter. +The following example is related to the above explanation for **group** filter. + +```php +/** + * If the user does not belong to the group, redirect to the configured URL with an error message. + */ +protected function redirectToDeniedUrl(): RedirectResponse +{ + return redirect()->to(config('Auth')->groupDeniedRedirect()) + ->with('error', lang('Auth.notEnoughPrivilege')); +} +``` + +### Fix to HMAC Secret Key Encryption + +#### Config\AuthToken + +If you are using the HMAC authentication you need to update the encryption settings in **app/Config/AuthToken.php**. +You will need to update and set the encryption key in `$hmacEncryptionKeys`. This should be set using **.env** and/or +system environment variables. Instructions on how to do that can be found in the +[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key) +section of the CodeIgniter 4 documentation and in [HMAC SHA256 Token Authenticator](./docs/references/authentication/hmac.md#hmac-secret-key-encryption). + +You also may wish to adjust the default Driver `$hmacEncryptionDefaultDriver` and the default Digest +`$hmacEncryptionDefaultDigest`, these currently default to `'OpenSSL'` and `'SHA512'` respectively. + +#### Encrypt Existing Keys + +After updating the key in `$hmacEncryptionKeys` value, you will need to run `php spark shield:hmac encrypt` in order +to encrypt any existing HMAC tokens. This only needs to be run if you have existing unencrypted HMAC secretKeys in +stored in the database. + ## Version 1.0.0-beta.6 to 1.0.0-beta.7 ### The minimum CodeIgniter version diff --git a/admin/RELEASE.md b/admin/RELEASE.md index 69cb216b2..5940307c3 100644 --- a/admin/RELEASE.md +++ b/admin/RELEASE.md @@ -36,11 +36,11 @@ the changelog. ## Preparation -* Clone **codeigniter4/shield** and resolve any necessary PRs +* [ ] Clone **codeigniter4/shield** and resolve any necessary PRs ```console git clone git@github.com:codeigniter4/shield.git ``` -* Merge any Security Advisory PRs in private forks +* [ ] Merge any Security Advisory PRs in private forks ## Process @@ -48,29 +48,31 @@ the changelog. > been included with their PR, so this process assumes you will not be > generating much new content. -* Create a new branch `release-1.x.x` -* Update **src/Auth.php** with the new version number: +* [ ] Create a new branch `release-1.x.x` +* [ ] Update **src/Auth.php** with the new version number: `const SHIELD_VERSION = '1.x.x';` -* Commit the changes with "Prep for 1.x.x release" and push to origin -* Create a new PR from `release-1.x.x` to `develop`: +* [ ] Commit the changes with "Prep for 1.x.x release" and push to origin +* [ ] Create a new PR from `release-1.x.x` to `develop`: * Title: "Prep for 1.x.x release" * Description: "Updates version references for `1.x.x`." (plus checklist) -* Let all tests run, then review and merge the PR -* Create a new PR from `develop` to `master`: +* [ ] Let all tests run, then review and merge the PR +* [ ] Create a new PR from `develop` to `master`: * Title: "1.x.x Ready code" * Description: blank -* Merge the PR -* Create a new Release: +* [ ] Merge the PR +* [ ] Create a new Release: * Version: "v1.x.x" * Target: master * Title: "v1.x.x" * Click the "Generate release notes" button * Remove "### Others (Only for checking. Remove this category)" section + * Add important notes if necessary + * Add link to Upgrade Guide if necessary * Check "Create a discussion for this release" * Click the "Publish release" button -* Watch for the "docs" action and verify that the user guide updated: +* [ ] Watch for the "docs" action and verify that the user guide updated: * [docs](https://github.com/codeigniter4/shield/actions/workflows/docs.yml) -* Fast-forward `develop` branch to catch the merge commit from `master` +* [ ] Fast-forward `develop` branch to catch the merge commit from `master` (note: pushing to `develop` is restricted to administrators): ```console git fetch origin @@ -82,9 +84,9 @@ the changelog. **At this point, `master` must be merged into `develop`.** Otherwise, the GitHub-generated release note from `develop` for the next release will not be generated correctly. -* Publish any Security Advisories that were resolved from private forks +* [ ] Publish any Security Advisories that were resolved from private forks (note: publishing is restricted to administrators) -* Announce the release on the forums and Slack channel +* [ ] Announce the release on the forums and Slack channel (note: this forum is restricted to administrators): * Make a new topic in the "News & Discussion" forums: https://forum.codeigniter.com/forum-2.html diff --git a/composer.json b/composer.json index d7a4327bd..f7eed49ca 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "mockery/mockery": "^1.0", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-strict-rules": "^1.5", - "rector/rector": "0.18.5" + "rector/rector": "0.18.10" }, "provide": { "codeigniter4/authentication-implementation": "1.0" @@ -52,9 +52,6 @@ "psr-4": { "CodeIgniter\\Shield\\": "src" }, - "files": [ - "src/Helpers/auth_helper.php" - ], "exclude-from-classmap": [ "**/Database/Migrations/**" ] diff --git a/docs/addons/jwt.md b/docs/addons/jwt.md index ad87cfb4e..f9ff4b83a 100644 --- a/docs/addons/jwt.md +++ b/docs/addons/jwt.md @@ -33,9 +33,10 @@ To use JWT Authentication, you need additional setup and configuration. 2. Copy the **AuthJWT.php** from **vendor/codeigniter4/shield/src/Config/** into your project's config folder and update the namespace to `Config`. You will also need to have these classes extend the original classes. See the example below. ```php - // new file - app/Config/AuthJWT.php post('auth/jwt', '\App\Controllers\Auth\LoginController::jwtLogin'); ``` ```php +group('api', ['filter' => 'jwt'], static function ($routes) { // ... }); + $routes->get('users', 'UserController::list', ['filter' => 'jwt']); ``` @@ -346,3 +364,14 @@ It uses the `secret` and `alg` in the `Config\AuthJWT::$keys['default']`. It sets the `Config\AuthJWT::$defaultClaims` to the token, and sets `"iat"` (Issued At) and `"exp"` (Expiration Time) claims automatically even if you don't pass them. + +## Logging + +Login attempts are recorded in the `auth_token_logins` table, according to the +configuration above. + +When a failed login attempt is logged, the raw token value sent is saved in +the `identifier` column. + +When a successful login attempt is logged, the SHA256 hash value of the token +sent is saved in the `identifier` column. diff --git a/docs/customization/adding_attributes_to_users.md b/docs/customization/adding_attributes_to_users.md new file mode 100644 index 000000000..05530c6f3 --- /dev/null +++ b/docs/customization/adding_attributes_to_users.md @@ -0,0 +1,174 @@ +# Adding Attributes to Users + +If you need to add new attributes like phone numbers, employee or school IDs, etc. +to users, one way is to add columns to `users` table. + +## Create Migration File + +Create a migration file to add new columns. + +You can easily create a file for it with the `spark` command: +```console +php spark make:migration AddMobileNumberToUsers +``` + +And write code to add/drop columns. + +```php +tables = $authConfig->tables; + } + + public function up() + { + $fields = [ + 'mobile_number' => ['type' => 'VARCHAR', 'constraint' => '20', 'null' => true], + ]; + $this->forge->addColumn($this->tables['users'], $fields); + } + + public function down() + { + $fields = [ + 'mobile_number', + ]; + $this->forge->dropColumn($this->tables['users'], $fields); + } +} +``` + +## Run Migrations + +Run the migration file: + +```console +php spark migrate +``` + +And check the `users` table: + +```console +php spark db:table users +``` + +## Create UserModel + +See [Customizing User Provider](./user_provider.md). + +## Update Validation Rules + +You need to update the [validation rules](./validation_rules.md) for registration. + +If you do not add the validation rules for the new fields, the new field data will +not be saved to the database. + +Add the `$registration` property with the all validation rules for registration +in **app/Config/Validation.php**: + +```php + [ + 'label' => 'Auth.username', + 'rules' => [ + 'required', + 'max_length[30]', + 'min_length[3]', + 'regex_match[/\A[a-zA-Z0-9\.]+\z/]', + 'is_unique[users.username]', + ], + ], + 'mobile_number' => [ + 'label' => 'Mobile Number', + 'rules' => [ + 'max_length[20]', + 'min_length[10]', + 'regex_match[/\A[0-9]+\z/]', + 'is_unique[users.mobile_number]', + ], + ], + 'email' => [ + 'label' => 'Auth.email', + 'rules' => [ + 'required', + 'max_length[254]', + 'valid_email', + 'is_unique[auth_identities.secret]', + ], + ], + 'password' => [ + 'label' => 'Auth.password', + 'rules' => [ + 'required', + 'max_byte[72]', + 'strong_password[]', + ], + 'errors' => [ + 'max_byte' => 'Auth.errorPasswordTooLongBytes', + ] + ], + 'password_confirm' => [ + 'label' => 'Auth.passwordConfirm', + 'rules' => 'required|matches[password]', + ], + ]; +} +``` + +## Customize Register View + +1. Change the `register` view file in the **app/Config/Auth.php** file. + + ```php + public array $views = [ + // ... + 'register' => '\App\Views\Shield\register', + // ... + ]; + ``` + +2. Copy file **vendor/codeigniter4/shield/src/Views/register.php** to **app/Views/Shield/register.php**. +3. Customize the registration form to add the new fields. + + ```php + +
+ + +
+ ``` diff --git a/docs/customization/integrating_custom_view_libs.md b/docs/customization/integrating_custom_view_libs.md index 4025de85c..93e51d3cc 100644 --- a/docs/customization/integrating_custom_view_libs.md +++ b/docs/customization/integrating_custom_view_libs.md @@ -5,6 +5,12 @@ If your application uses a different method to convert view files to HTML than C All controllers and actions use the `CodeIgniter\Shield\Traits\Viewable` trait which provides a simple `view()` method that takes the same arguments as the `view()` helper. This allows you to extend the Action or Controller and only change the single method of rendering the view, leaving all of the logic untouched so your app will not need to maintain Shield logic when it doesn't need to change it. ```php + '\App\Views\Shield\login', + // ... ]; ``` - !!! warning - - It is very important for security that if you add a new column for identifier, you must write a new **Validation Rules** and then set it using the [Customizing Validation Rules](./validation_rules.md) description. -3. Edit the login form to change the name of the default `email` input to the new field name. +2. Copy file **vendor/codeigniter4/shield/src/Views/login.php** to **app/Views/Shield/login.php**. +3. Customize the login form to change the name of the default `email` input to the new field name. ```php
- +
``` diff --git a/docs/customization/route_config.md b/docs/customization/route_config.md index 46d1c6597..09a15da09 100644 --- a/docs/customization/route_config.md +++ b/docs/customization/route_config.md @@ -57,7 +57,7 @@ update the paths for `except`: public $globals = [ 'before' => [ // ... - 'session' => ['except' => ['*/login*', '*/register', '*/auth/a/*']], + 'session' => ['except' => ['*/login*', '*/register', '*/auth/a/*', '*/logout']], ], // ... ]; diff --git a/docs/customization/views.md b/docs/customization/views.md new file mode 100644 index 000000000..e38961da6 --- /dev/null +++ b/docs/customization/views.md @@ -0,0 +1,39 @@ +# Customizing Views + +Shield provides the default view files, but they are sample files. +Customization is recommended. + +If your application uses a different method to convert view files to HTML than +CodeIgniter's built-in `view()` helper, see +[Integrating Custom View Libraries](./integrating_custom_view_libs.md). + +## Change $views + +Change values in `$views` in the **app/Config/Auth.php** file. + +For example, if you customize the login page, change the value for `'login'`: + +```php +public array $views = [ + 'login' => '\App\Views\Shield\login', // changed this line. + 'register' => '\CodeIgniter\Shield\Views\register', + 'layout' => '\CodeIgniter\Shield\Views\layout', + 'action_email_2fa' => '\CodeIgniter\Shield\Views\email_2fa_show', + 'action_email_2fa_verify' => '\CodeIgniter\Shield\Views\email_2fa_verify', + 'action_email_2fa_email' => '\CodeIgniter\Shield\Views\Email\email_2fa_email', + 'action_email_activate_show' => '\CodeIgniter\Shield\Views\email_activate_show', + 'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email', + 'magic-link-login' => '\CodeIgniter\Shield\Views\magic_link_form', + 'magic-link-message' => '\CodeIgniter\Shield\Views\magic_link_message', + 'magic-link-email' => '\CodeIgniter\Shield\Views\Email\magic_link_email', +]; +``` + +## Copy View File + +Copy the file you want to customize in **vendor/codeigniter4/shield/src/Views/** +to the **app/Views/Shield/** folder. + +## Customize Content + +Customize the content of the view file in **app/Views/Shield/** as you like. diff --git a/docs/getting_started/concepts.md b/docs/getting_started/concepts.md index 04ad6db9f..4b94fd6e8 100644 --- a/docs/getting_started/concepts.md +++ b/docs/getting_started/concepts.md @@ -70,7 +70,7 @@ systems that are appropriate for your application. The following Validators are You can choose which validators are used in `Config\Auth::$passwordValidators`: ```php -public $passwordValidators = [ +public array $passwordValidators = [ CompositionValidator::class, NothingPersonalValidator::class, DictionaryValidator::class, diff --git a/docs/getting_started/configuration.md b/docs/getting_started/configuration.md index 7d621fcb0..246cf99b9 100644 --- a/docs/getting_started/configuration.md +++ b/docs/getting_started/configuration.md @@ -8,20 +8,8 @@ If you have completed the setup according to this documentation, you will have the following configuration files: - **app/Config/Auth.php** -- **app/Config/AuthGroups.php** - For Authorization -- **app/Config/AuthToken.php** - For AccessTokens and HmacSha256 Authentication -- **app/Config/AuthJWT.php** - For JWT Authentication +- **app/Config/AuthGroups.php** - For [Authorization](../references/authorization.md) +- **app/Config/AuthToken.php** - For [AccessTokens](../references/authentication/tokens.md#configuration) and [HmacSha256](../references/authentication/hmac.md#configuration) Authentication +- **app/Config/AuthJWT.php** - For [JWT Authentication](../addons/jwt.md#configuration) Note that you do not need to have configuration files for features you do not use. - -This section describes the major Config items that are not described elsewhere. - -## AccessTokens Authenticator - -### Access Token Lifetime - -By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the **app/Config/AuthToken.php** config file. - -```php -public int $unusedTokenLifetime = YEAR; -``` diff --git a/docs/getting_started/install.md b/docs/getting_started/install.md index c608bd3f3..2f9534e3f 100644 --- a/docs/getting_started/install.md +++ b/docs/getting_started/install.md @@ -72,7 +72,8 @@ your project. ### Manual Setup -1. Copy the **Auth.php**, **AuthGroups.php**, and **AuthToken.php** from **vendor/codeigniter4/shield/src/Config/** into your project's config folder and update the namespace to `Config`. You will also need to have these classes extend the original classes. See the example below. These files contain all the settings, group, and permission information for your application and will need to be modified to meet the needs of your site. +1. **Config Setup:** + Copy the **Auth.php**, **AuthGroups.php**, and **AuthToken.php** from **vendor/codeigniter4/shield/src/Config/** into your project's config folder and update the namespace to `Config`. You will also need to have these classes extend the original classes. See the example below. These files contain all the settings, group, and permission information for your application and will need to be modified to meet the needs of your site. ```php // new file - app/Config/Auth.php @@ -91,29 +92,26 @@ your project. } ``` -2. **Helper Setup** The `setting` helper needs to be included in almost every page. The simplest way to do this is to add it to the `BaseController::initController()` method: +2. **Helper Setup:** + The `auth` and `setting` helpers need to be included in almost every page. + The simplest way to do this is to add it to the **app/Config/Autoload.php** file: ```php - public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) - { - $this->helpers = array_merge($this->helpers, ['setting']); - - // Do Not Edit This Line - parent::initController($request, $response, $logger); - } + public $helpers = ['auth', 'setting']; ``` - This requires that all of your controllers extend the `BaseController`, but that's a good practice anyway. - -3. **Routes Setup** The default auth routes can be setup with a single call in **app/Config/Routes.php**: +3. **Routes Setup:** + The default auth routes can be setup with a single call in **app/Config/Routes.php**: ```php service('auth')->routes($routes); ``` -4. **Security Setup** Set `Config\Security::$csrfProtection` to `'session'` for security reasons, if you use Session Authenticator. +4. **Security Setup:** + Set `Config\Security::$csrfProtection` to `'session'` for security reasons, if you use Session Authenticator. -5. Configure **app/Config/Email.php** to allow Shield to send emails with the [Email Class](https://codeigniter.com/user_guide/libraries/email.html). +5. **Email Setup:** + Configure **app/Config/Email.php** to allow Shield to send emails. ```php get('/hmac/token', static function () { +$routes->get('hmac/token', static function () { $token = auth()->user()->generateHmacToken(service('request')->getVar('token_name')); - return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); + return json_encode(['key' => $token->secret, 'secretKey' => $token->rawSecretKey]); }); ``` @@ -62,7 +63,7 @@ token is granted all access to all scopes. This might be enough for a smaller AP ```php $token = $user->generateHmacToken('token-name', ['users-read']); -return json_encode(['key' => $token->secret, 'secretKey' => $token->secret2]); +return json_encode(['key' => $token->secret, 'secretKey' => $token->rawSecretKey]); ``` !!! note @@ -87,6 +88,25 @@ $user->revokeHmacToken($key); $user->revokeAllHmacTokens(); ``` +## HMAC Secret Key Encryption + +The HMAC Secret Key is stored encrypted. Before you start using HMAC, you will need to set/override the encryption key +in `$hmacEncryptionKeys` in **app/Config/AuthToken.php**. This should be set using **.env** and/or system +environment variables. Instructions on how to do that can be found in the +[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key) +section of the CodeIgniter 4 documentation. + +You will also be able to adjust the default Driver `$hmacEncryptionDefaultDriver` and the default Digest +`$hmacEncryptionDefaultDigest`, these default to `'OpenSSL'` and `'SHA512'` respectively. + +See [HMAC SHA256 Token Authenticator](../references/authentication/hmac.md#hmac-secret-key-encryption) for additional +details on setting these values. + +### Encryption Key Rotation + +See [HMAC SHA256 Token Authenticator](../references/authentication/hmac.md#hmac-secret-key-encryption) for information on +how to set, rotate encryption keys and re-encrypt existing HMAC `'secretKey'` values. + ## Protecting Routes The first way to specify which routes are protected is to use the `hmac` controller filter. diff --git a/docs/guides/api_tokens.md b/docs/guides/api_tokens.md index 60f238b50..a2ce6c2d4 100644 --- a/docs/guides/api_tokens.md +++ b/docs/guides/api_tokens.md @@ -11,7 +11,7 @@ Tokens are issued with the `generateAccessToken()` method on the user. This retu The `generateAccessToken()` method requires a name for the token. These are free strings and are often used to identify the user/device the token was generated from, like 'Johns MacBook Air'. ```php -$routes->get('/access/token', static function() { +$routes->get('access/token', static function() { $token = auth()->user()->generateAccessToken(service('request')->getVar('token_name')); return json_encode(['token' => $token->raw_token]); diff --git a/docs/guides/mobile_apps.md b/docs/guides/mobile_apps.md index c974bf496..9f1af569f 100644 --- a/docs/guides/mobile_apps.md +++ b/docs/guides/mobile_apps.md @@ -9,11 +9,15 @@ Typically, a mobile application would issue a request from their login screen, p Start by creating a route that would handle the request from the login screen on the mobile device. The device name can be any arbitrary string, but is typically used to identify the device the request is being made from, like "Johns iPhone 13". ```php - // Routes.php $routes->post('auth/token', '\App\Controllers\Auth\LoginController::mobileLogin'); +``` + +```php + [ - 'label' => 'Auth.email', - 'rules' => config('Auth')->emailValidationRules, - ], + 'email' => config('Auth')->emailValidationRules, 'password' => [ 'label' => 'Auth.password', 'rules' => 'required', diff --git a/docs/quick_start_guide/using_session_auth.md b/docs/quick_start_guide/using_session_auth.md index 645d2a1b2..94af696b6 100644 --- a/docs/quick_start_guide/using_session_auth.md +++ b/docs/quick_start_guide/using_session_auth.md @@ -100,11 +100,18 @@ If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/pa public $globals = [ 'before' => [ // ... - 'session' => ['except' => ['login*', 'register', 'auth/a/*']], + 'session' => ['except' => ['login*', 'register', 'auth/a/*', 'logout']], ], // ... ]; ``` +!!! note + + The filter `$aliases` that Shield provides are automatically added for you by the + [Registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) + class located at **src/Config/Registrar.php**. So you don't need to add in + your **app/Config/Filters.php**. + Check your filters with the [spark routes](https://codeigniter.com/user_guide/incoming/routing.html#spark-routes) command. diff --git a/docs/references/authentication/authentication.md b/docs/references/authentication/authentication.md index 1ade0ffed..9cb057d3a 100644 --- a/docs/references/authentication/authentication.md +++ b/docs/references/authentication/authentication.md @@ -12,7 +12,7 @@ You can see the [Authenticator List](../../getting_started/authenticators.md). The available authenticators are defined in `Config\Auth`: ```php -public $authenticators = [ +public array $authenticators = [ // alias => classname 'session' => Session::class, 'tokens' => AccessTokens::class, @@ -24,7 +24,7 @@ public $authenticators = [ The default authenticator is also defined in the configuration file, and uses the alias given above: ```php -public $defaultAuthenticator = 'session'; +public string $defaultAuthenticator = 'session'; ``` ## Auth Helper @@ -48,8 +48,9 @@ auth()->getProvider(); !!! note - The `auth_helper` is autoloaded by Composer. If you want to *override* the functions, - you need to define them in **app/Common.php**. + The `auth_helper` is autoloaded by CodeIgniter's autoloader if you follow the + installation instruction. If you want to *override* the functions, create + **app/Helpers/auth_helper.php**. ## Authenticator Responses diff --git a/docs/references/authentication/hmac.md b/docs/references/authentication/hmac.md index c8c504453..fed1f0bb7 100644 --- a/docs/references/authentication/hmac.md +++ b/docs/references/authentication/hmac.md @@ -7,7 +7,7 @@ access to your API. These keys typically have a very long expiration time, often These are also suitable for use with mobile applications. In this case, the user would register/sign-in with their email/password. The application would create a new access token for them, with a recognizable -name, like John's iPhone 12, and return it to the mobile application, where it is stored and used +name, like "John's iPhone 12", and return it to the mobile application, where it is stored and used in all future requests. !!! note @@ -67,19 +67,19 @@ $token = $user->generateHmacToken('Work Laptop'); ``` This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys. -This means they are stored as-is in the database. The method returns an instance of -`CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the 'key' the field `secret2` is -the shared 'secretKey'. Both are required to when using this authentication method. +The '**key**' is stored as plain text in the database, the '**secretKey**' is stored encrypted. The method returns an +instance of `CodeIgniters\Shield\Authentication\Entities\AccessToken`. The field `secret` is the '**key**' the field +`rawSecretKey` is the shared '**secretKey**'. Both are required to when using this authentication method. **The plain text version of these keys should be displayed to the user immediately, so they can copy it for -their use.** It is recommended that after that only the 'key' field is displayed to a user. If a user loses the -'secretKey', they should be required to generate a new set of keys to use. +their use.** It is recommended that after that only the '**key**' field is displayed to a user. If a user loses the +'**secretKey**', they should be required to generate a new set of keys to use. ```php $token = $user->generateHmacToken('Work Laptop'); echo 'Key: ' . $token->secret; -echo 'SecretKey: ' . $token->secret2; +echo 'SecretKey: ' . $token->rawSecretKey; ``` ## Revoking HMAC Keys @@ -112,20 +112,6 @@ $token = $user->getHmacTokenById($id); $tokens = $user->hmacTokens(); ``` -## HMAC Keys Lifetime - -HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. -This uses the same configuration value as AccessTokens. - -By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the -[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -that CodeIgniter provides. - -```php -public $unusedTokenLifetime = YEAR; -``` - ## HMAC Keys Scopes Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as @@ -156,3 +142,113 @@ if ($user->hmacTokenCant('forums.manage')) { // do something.... } ``` + +## HMAC Secret Key Encryption + +The HMAC Secret Key is stored encrypted. Before you start using HMAC, you will need to set/override the encryption key +in `$hmacEncryptionKeys` in **app/Config/AuthToken.php**. This should be set using **.env** and/or system +environment variables. Instructions on how to do that can be found in the +[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key) +section of the CodeIgniter 4 documentation. + +You will also be able to adjust the default Driver `$hmacEncryptionDefaultDriver` and the default Digest +`$hmacEncryptionDefaultDigest`, these default to `'OpenSSL'` and `'SHA512'` respectively. These can also be +overridden for an individual key by including them in the keys array. + +```php +public $hmacEncryptionKeys = [ + 'k1' => [ + 'key' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7', + ], +]; + +public string $hmacEncryptionCurrentKey = 'k1'; +public string $hmacEncryptionDefaultDriver = 'OpenSSL'; +public string $hmacEncryptionDefaultDigest = 'SHA512'; +``` + +When it is time to update your encryption keys you will need to add an additional key to the above +`$hmacEncryptionKeys` array. Then adjust the `$hmacEncryptionCurrentKey` to point at the new key. After the new +encryption key is in place, run `php spark shield:hmac reencrypt` to re-encrypt all existing keys with the new +encryption key. You will need to leave the old key in the array as it will be used read the existing 'Secret Keys' +during re-encryption. + +```php +public $hmacEncryptionKeys = [ + 'k1' => [ + 'key' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7', + ], + 'k2' => [ + 'key' => 'hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0', + 'digest' => 'SHA256', + ], +]; + +public string $hmacEncryptionCurrentKey = 'k2'; +public string $hmacEncryptionDefaultDriver = 'OpenSSL'; +public string $hmacEncryptionDefaultDigest = 'SHA512'; + +``` + +```console +php spark shield:hmac reencrypt +``` + +You can (and should) set these values using environment variable and/or the **.env** file. To do this you will need to set +the values as JSON strings: + +```text +authtoken.hmacEncryptionKeys = '{"k1":{"key":"hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7"},"k2":{"key":"hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0"}}' +authtoken.hmacEncryptionCurrentKey = k2 +``` + +Depending on the set length of the Secret Key and the type of encryption used, it is possible for the encrypted value to +exceed the database column character limit of 255 characters. If this happens, creation of a new HMAC identity will +throw a `RuntimeException`. + +## Configuration + +Configure **app/Config/AuthToken.php** for your needs. + +!!! note + + Shield does not expect you use the Access Token Authenticator and HMAC Authenticator + at the same time. Therefore, some Config items are common. + +### HMAC Keys Lifetime + +HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used. + +By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` +value. This is in seconds so that you can use the +[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) +that CodeIgniter provides. + +```php +public $unusedTokenLifetime = YEAR; +``` + +### Login Attempt Logging + +By default, only failed login attempts are recorded in the `auth_token_logins` table. +This can be modified by changing the `$recordLoginAttempt` value. + +```php +public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE; +``` + +If you don't want any logs, set it to `Auth::RECORD_LOGIN_ATTEMPT_NONE`. + +If you want to log all login attempts, set it to `Auth::RECORD_LOGIN_ATTEMPT_ALL`. +It means you log all requests. + +## Logging + +Login attempts are recorded in the `auth_token_logins` table, according to the +configuration above. + +When a failed login attempt is logged, the raw token value sent is saved in +the `identifier` column. + +When a successful login attempt is logged, the token name is saved in the +`identifier` column. diff --git a/docs/references/authentication/tokens.md b/docs/references/authentication/tokens.md index b97cb6769..11b03afcd 100644 --- a/docs/references/authentication/tokens.md +++ b/docs/references/authentication/tokens.md @@ -83,18 +83,6 @@ $token = $user->getAccessTokenById($id); $tokens = $user->accessTokens(); ``` -## Access Token Lifetime - -Tokens will expire after a specified amount of time has passed since they have been used. -By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime` -value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the -[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -that CodeIgniter provides. - -```php -public $unusedTokenLifetime = YEAR; -``` - ## Access Token Scopes Each token can be given one or more scopes they can be used within. These can be thought of as @@ -125,3 +113,52 @@ if ($user->tokenCant('forums.manage')) { // do something.... } ``` + +## Configuration + +Configure **app/Config/AuthToken.php** for your needs. + +!!! note + + Shield does not expect you use the Access Token Authenticator and HMAC Authenticator + at the same time. Therefore, some Config items are common. + +### Access Token Lifetime + +Tokens will expire after a specified amount of time has passed since they have been used. + +By default, this is set to 1 year. +You can change this value by setting the `$unusedTokenLifetime` value. This is +in seconds so that you can use the +[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) +that CodeIgniter provides. + +```php +public $unusedTokenLifetime = YEAR; +``` + +### Login Attempt Logging + +By default, only failed login attempts are recorded in the `auth_token_logins` table. + +This can be modified by changing the `$recordLoginAttempt` value. + +```php +public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE; +``` + +If you don't want any logs, set it to `Auth::RECORD_LOGIN_ATTEMPT_NONE`. + +If you want to log all login attempts, set it to `Auth::RECORD_LOGIN_ATTEMPT_ALL`. +It means you log all requests. + +## Logging + +Login attempts are recorded in the `auth_token_logins` table, according to the +configuration above. + +When a failed login attempt is logged, the raw token value sent is saved in +the `identifier` column. + +When a successful login attempt is logged, the token name is saved in the +`identifier` column. diff --git a/docs/references/controller_filters.md b/docs/references/controller_filters.md index 88dfe4c1a..6afc8c4cd 100644 --- a/docs/references/controller_filters.md +++ b/docs/references/controller_filters.md @@ -4,13 +4,15 @@ !!! note - These filters are already loaded for you by the [registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) class located at **src/Config/Registrar.php**. + The filter `$aliases` that Shield provides are automatically added for you by the + [Registrar](https://codeigniter.com/user_guide/general/configuration.html#registrars) + class located at **src/Config/Registrar.php**. So you don't need to add in + your **app/Config/Filters.php**. The [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes Shield provides are: ```php -public $aliases = [ - // ... +$aliases = [ 'session' => \CodeIgniter\Shield\Filters\SessionAuth::class, 'tokens' => \CodeIgniter\Shield\Filters\TokenAuth::class, 'hmac' => \CodeIgniter\Shield\Filters\HmacAuth::class, @@ -47,7 +49,7 @@ If you want to limit all routes (e.g. `localhost:8080/admin`, `localhost:8080/pa public $globals = [ 'before' => [ // ... - 'session' => ['except' => ['login*', 'register', 'auth/a/*']], + 'session' => ['except' => ['login*', 'register', 'auth/a/*', 'logout']], ], // ... ]; @@ -102,7 +104,7 @@ Then the global `before` filter for `session` should look like so: public $globals = [ 'before' => [ // ... - 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*']] + 'session' => ['except' => ['accounts/login*', 'accounts/register', 'accounts/auth/a/*', 'accounts/logout']] ] ] ``` diff --git a/docs/references/events.md b/docs/references/events.md index 20344fd96..744362187 100644 --- a/docs/references/events.md +++ b/docs/references/events.md @@ -44,8 +44,7 @@ Events::on('failedLogin', function($credentials) { dd($credentials); }); -// Outputs: -['email' => 'foo@example.com']; +// Outputs: ['email' => 'foo@example.com']; ``` When the magic link login fails, the following array will be provided: diff --git a/mkdocs.yml b/mkdocs.yml index 64917aeef..24808cccf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,10 +106,12 @@ nav: - quick_start_guide/using_authorization.md - Customization: - customization/table_names.md + - customization/views.md - customization/route_config.md - customization/redirect_urls.md - customization/validation_rules.md - customization/user_provider.md + - customization/adding_attributes_to_users.md - customization/extending_controllers.md - customization/integrating_custom_view_libs.md - customization/login_identifier.md diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 2e880e9ff..a261d48ba 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1,21 +1,6 @@ '#^Call to function property_exists\\(\\) with CodeIgniter\\\\Shield\\\\Config\\\\Auth and \'userProvider\' will always evaluate to true\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Auth.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Auth.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Only booleans are allowed in a ternary operator condition, CodeIgniter\\\\Shield\\\\Entities\\\\User\\|null given\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Auth.php', -]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', @@ -27,12 +12,22 @@ 'count' => 2, 'path' => __DIR__ . '/src/Authentication/Actions/Email2FA.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authentication/Actions/Email2FA.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to deprecated function random_string\\(\\)\\: The type \'basic\', \'md5\', and \'sha1\' are deprecated\\. They are not cryptographically secure\\.$#', 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Actions/EmailActivator.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authentication/Actions/EmailActivator.php', +]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 1, @@ -48,6 +43,16 @@ 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Authenticators/AccessTokens.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\TokenLoginModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/AccessTokens.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/AccessTokens.php', +]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$credentials \\(array\\{token\\?\\: string\\}\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\Authenticators\\\\JWT\\:\\:attempt\\(\\) should be contravariant with parameter \\$credentials \\(array\\) of method CodeIgniter\\\\Shield\\\\Authentication\\\\AuthenticatorInterface\\:\\:attempt\\(\\)$#', 'count' => 1, @@ -58,6 +63,11 @@ 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Authenticators/JWT.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\TokenLoginModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/JWT.php', +]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 3, @@ -88,6 +98,21 @@ 'count' => 1, 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\LoginModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\RememberModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/Session.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to method CodeIgniter\\\\Shield\\\\Result\\:\\:isOK\\(\\) with incorrect case\\: isOk$#', 'count' => 1, @@ -159,6 +184,16 @@ 'count' => 1, 'path' => __DIR__ . '/src/Controllers/MagicLinkController.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Controllers/MagicLinkController.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\LoginModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Controllers/MagicLinkController.php', +]; $ignoreErrors[] = [ 'message' => '#^Call to function assert\\(\\) with false and \'Config Auth…\' will always evaluate to false\\.$#', 'count' => 1, @@ -224,6 +259,21 @@ 'count' => 1, 'path' => __DIR__ . '/src/Entities/User.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 19, + 'path' => __DIR__ . '/src/Entities/User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\LoginModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Entities/User.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserModel\\:\\:class is discouraged\\.$#', + 'count' => 9, + 'path' => __DIR__ . '/src/Commands/User.php', +]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', 'count' => 1, @@ -307,9 +357,9 @@ 'path' => __DIR__ . '/src/Models/UserModel.php', ]; $ignoreErrors[] = [ - 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', - 'count' => 2, - 'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php', + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Models/UserModel.php', ]; $ignoreErrors[] = [ 'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#', @@ -362,9 +412,23 @@ 'path' => __DIR__ . '/tests/Unit/UserTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\TokenLoginModel\\:\\:class is discouraged\\.$#', 'count' => 1, - 'path' => __DIR__ . '/tests/_support/Config/Registrar.php', + 'path' => __DIR__ . '/src/Authentication/Authenticators/HmacSha256.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\UserIdentityModel\\:\\:class is discouraged\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/src/Authentication/Authenticators/HmacSha256.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\GroupModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authorization/Traits/Authorizable.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Call to function model with CodeIgniter\\\\Shield\\\\Models\\\\PermissionModel\\:\\:class is discouraged\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/src/Authorization/Traits/Authorizable.php', ]; - return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index aa7040153..af444e0a1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -93,7 +93,10 @@ - + + + +
+ inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" required>
diff --git a/src/Views/email_activate_show.php b/src/Views/email_activate_show.php index 066bd0b1c..6b41d4626 100644 --- a/src/Views/email_activate_show.php +++ b/src/Views/email_activate_show.php @@ -21,7 +21,7 @@
+ pattern="[0-9]*" autocomplete="one-time-code" value="" required>
diff --git a/src/Views/login.php b/src/Views/login.php index 71b4501d0..838734b44 100644 --- a/src/Views/login.php +++ b/src/Views/login.php @@ -33,13 +33,13 @@
- +
- +
diff --git a/src/Views/magic_link_form.php b/src/Views/magic_link_form.php index ed820717e..6bef87a16 100644 --- a/src/Views/magic_link_form.php +++ b/src/Views/magic_link_form.php @@ -30,7 +30,7 @@
+ value="user()->email ?? null) ?>" required>
diff --git a/src/Views/register.php b/src/Views/register.php index 7adba1aa3..57726e8e8 100644 --- a/src/Views/register.php +++ b/src/Views/register.php @@ -29,25 +29,25 @@
- +
- +
- +
- +
diff --git a/tests/Authentication/AccessTokenTest.php b/tests/Authentication/AccessTokenTest.php index b5fd8b9d8..2103b0f9d 100644 --- a/tests/Authentication/AccessTokenTest.php +++ b/tests/Authentication/AccessTokenTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Entities\AccessToken; diff --git a/tests/Authentication/AuthHelperTest.php b/tests/Authentication/AuthHelperTest.php index da8dff537..a0cd35505 100644 --- a/tests/Authentication/AuthHelperTest.php +++ b/tests/Authentication/AuthHelperTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Authentication\AuthenticationException; diff --git a/tests/Authentication/AuthTest.php b/tests/Authentication/AuthTest.php index 100760c7a..429184b5d 100644 --- a/tests/Authentication/AuthTest.php +++ b/tests/Authentication/AuthTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Config\Services; diff --git a/tests/Authentication/AuthenticationTest.php b/tests/Authentication/AuthenticationTest.php index 58b92ce10..27d8029cc 100644 --- a/tests/Authentication/AuthenticationTest.php +++ b/tests/Authentication/AuthenticationTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Authentication\Authentication; diff --git a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php index 12ffadd54..43a6d7173 100644 --- a/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php @@ -2,17 +2,26 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Authenticators; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Authentication\Authentication; use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens; use CodeIgniter\Shield\Config\Auth; +use CodeIgniter\Shield\Config\AuthToken; use CodeIgniter\Shield\Entities\AccessToken; use CodeIgniter\Shield\Entities\User; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Models\UserModel; -use CodeIgniter\Shield\Result; use CodeIgniter\Test\Mock\MockEvents; use Config\Services; use Tests\Support\DatabaseTestCase; @@ -173,7 +182,6 @@ public function testAttemptCannotFindUser(): void 'token' => 'abc123', ]); - $this->assertInstanceOf(Result::class, $result); $this->assertFalse($result->isOK()); $this->assertSame(lang('Auth.badToken'), $result->reason()); @@ -196,7 +204,6 @@ public function testAttemptSuccess(): void 'token' => $token->raw_token, ]); - $this->assertInstanceOf(Result::class, $result); $this->assertTrue($result->isOK()); $foundUser = $result->extraInfo(); @@ -213,6 +220,37 @@ public function testAttemptSuccess(): void ]); } + public function testAttemptSuccessLog(): void + { + // Change $recordLoginAttempt in Config. + /** @var AuthToken $config */ + $config = config('AuthToken'); + $config->recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_ALL; + + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateAccessToken('foo'); + $this->setRequestHeader($token->raw_token); + + $result = $this->auth->attempt([ + 'token' => $token->raw_token, + ]); + + $this->assertTrue($result->isOK()); + + $foundUser = $result->extraInfo(); + $this->assertInstanceOf(User::class, $foundUser); + $this->assertSame($user->id, $foundUser->id); + $this->assertInstanceOf(AccessToken::class, $foundUser->currentAccessToken()); + $this->assertSame($token->token, $foundUser->currentAccessToken()->token); + + $this->seeInDatabase($this->tables['token_logins'], [ + 'id_type' => AccessTokens::ID_TYPE_ACCESS_TOKEN, + 'identifier' => 'foo', + 'success' => 1, + ]); + } + protected function setRequestHeader(string $token): void { $request = service('request'); diff --git a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php index 96db32cd0..da0ac2b94 100644 --- a/tests/Authentication/Authenticators/HmacAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/HmacAuthenticatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Authenticators; use CodeIgniter\I18n\Time; @@ -100,7 +109,7 @@ public function testLoginByIdWithToken(): void $user = fake(UserModel::class); $token = $user->generateHmacToken('foo'); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar'); $this->setRequestHeader($rawToken); $this->auth->loginById($user->id); @@ -117,7 +126,7 @@ public function testLoginByIdWithMultipleTokens(): void $token1 = $user->generateHmacToken('foo'); $user->generateHmacToken('bar'); - $this->setRequestHeader($this->generateRawHeaderToken($token1->secret, $token1->secret2, 'bar')); + $this->setRequestHeader($this->generateRawHeaderToken($token1->secret, $token1->rawSecretKey, 'bar')); $this->auth->loginById($user->id); @@ -161,7 +170,7 @@ public function testCheckOldToken(): void $identities->save($token); $result = $this->auth->check([ - 'token' => $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'), + 'token' => $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar'), 'body' => 'bar', ]); @@ -181,7 +190,7 @@ public function testCheckSuccess(): void 'last_used_at' => null, ]); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar'); $result = $this->auth->check([ 'token' => $rawToken, @@ -211,7 +220,7 @@ public function testCheckBadToken(): void 'last_used_at' => null, ]); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'foobar'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'foobar'); $result = $this->auth->check([ 'token' => $rawToken, @@ -245,7 +254,7 @@ public function testAttemptSuccess(): void /** @var User $user */ $user = fake(UserModel::class); $token = $user->generateHmacToken('foo'); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar'); $this->setRequestHeader($rawToken); $result = $this->auth->attempt([ @@ -264,7 +273,7 @@ public function testAttemptSuccess(): void // A login attempt should have been recorded $this->seeInDatabase($this->tables['token_logins'], [ 'id_type' => HmacSha256::ID_TYPE_HMAC_TOKEN, - 'identifier' => $rawToken, + 'identifier' => 'foo', 'success' => 1, ]); @@ -285,7 +294,7 @@ public function testAttemptBanned(): void $user->ban('Test ban.'); $token = $user->generateHmacToken('foo'); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar'); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar'); $this->setRequestHeader($rawToken); $result = $this->auth->attempt([ @@ -301,7 +310,7 @@ public function testAttemptBanned(): void // A login attempt should have been recorded $this->seeInDatabase($this->tables['token_logins'], [ 'id_type' => HmacSha256::ID_TYPE_HMAC_TOKEN, - 'identifier' => $rawToken, + 'identifier' => 'foo', 'success' => 0, ]); } diff --git a/tests/Authentication/Authenticators/JWTAuthenticatorTest.php b/tests/Authentication/Authenticators/JWTAuthenticatorTest.php index 3bd24070f..d4ee37f90 100644 --- a/tests/Authentication/Authenticators/JWTAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/JWTAuthenticatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Authenticators; use CodeIgniter\I18n\Time; @@ -217,7 +226,7 @@ public function testAttemptBannedUser(): void // The login attempt should have been recorded $this->seeInDatabase('auth_token_logins', [ 'id_type' => JWT::ID_TYPE_JWT, - 'identifier' => $token, + 'identifier' => 'sha256:' . hash('sha256', $token), 'success' => 0, 'user_id' => $this->user->id, ]); @@ -247,7 +256,7 @@ public function testAttemptSuccess(): void // A login attempt should have been recorded $this->seeInDatabase('auth_token_logins', [ 'id_type' => JWT::ID_TYPE_JWT, - 'identifier' => $token, + 'identifier' => 'sha256:' . hash('sha256', $token), 'success' => 1, ]); } diff --git a/tests/Authentication/Authenticators/SessionAuthenticatorTest.php b/tests/Authentication/Authenticators/SessionAuthenticatorTest.php index 662b32fd0..ca1a8b53e 100644 --- a/tests/Authentication/Authenticators/SessionAuthenticatorTest.php +++ b/tests/Authentication/Authenticators/SessionAuthenticatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Authenticators; use CodeIgniter\Config\Factories; diff --git a/tests/Authentication/Filters/AbstractFilterTestCase.php b/tests/Authentication/Filters/AbstractFilterTestCase.php index d44f9e931..246fc7b93 100644 --- a/tests/Authentication/Filters/AbstractFilterTestCase.php +++ b/tests/Authentication/Filters/AbstractFilterTestCase.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Config\Factories; diff --git a/tests/Authentication/Filters/ChainFilterTest.php b/tests/Authentication/Filters/ChainFilterTest.php index c71f92dff..b1c81aad4 100644 --- a/tests/Authentication/Filters/ChainFilterTest.php +++ b/tests/Authentication/Filters/ChainFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Shield\Entities\AccessToken; diff --git a/tests/Authentication/Filters/GroupFilterTest.php b/tests/Authentication/Filters/GroupFilterTest.php index 2690eeaff..3e0d2a905 100644 --- a/tests/Authentication/Filters/GroupFilterTest.php +++ b/tests/Authentication/Filters/GroupFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Shield\Entities\User; @@ -69,8 +78,8 @@ public function testFilterIncorrectGroupNoPrevious(): void ->get('protected-route'); // Should redirect to home page since previous_url is not set - $result->assertRedirectTo(site_url('/')); + $result->assertRedirectTo(config('Auth')->groupDeniedRedirect()); // Should have error message - $result->assertSessionHas('error'); + $result->assertSessionHas('error', lang('Auth.notEnoughPrivilege')); } } diff --git a/tests/Authentication/Filters/HmacFilterTest.php b/tests/Authentication/Filters/HmacFilterTest.php index 4a7acb9bc..ffb516313 100644 --- a/tests/Authentication/Filters/HmacFilterTest.php +++ b/tests/Authentication/Filters/HmacFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Shield\Entities\AccessToken; @@ -38,7 +47,7 @@ public function testFilterSuccess(): void $user = fake(UserModel::class); $token = $user->generateHmacToken('foo'); - $rawToken = $this->generateRawHeaderToken($token->secret, $token->secret2, ''); + $rawToken = $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, ''); $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $rawToken]) ->get('protected-route'); @@ -59,7 +68,7 @@ public function testFilterInvalidSignature(): void $user = fake(UserModel::class); $token = $user->generateHmacToken('foo'); - $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, 'bar')]) + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, 'bar')]) ->get('protected-route'); $result->assertStatus(401); @@ -71,7 +80,7 @@ public function testRecordActiveDate(): void $user = fake(UserModel::class); $token = $user->generateHmacToken('foo'); - $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, '')]) ->get('protected-route'); // Last Active should be greater than 'updated_at' column @@ -88,7 +97,7 @@ public function testFiltersProtectsWithScopes(): void $token2 = $user2->generateHmacToken('foo', ['users-write']); // User 1 should be able to access the route - $result1 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token1->secret, $token1->secret2, '')]) + $result1 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token1->secret, $token1->rawSecretKey, '')]) ->get('protected-user-route'); $result1->assertStatus(200); @@ -96,7 +105,7 @@ public function testFiltersProtectsWithScopes(): void $this->assertGreaterThan(auth('hmac')->user()->updated_at, auth('hmac')->user()->last_active); // User 2 should NOT be able to access the route - $result2 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token2->secret, $token2->secret2, '')]) + $result2 = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token2->secret, $token2->rawSecretKey, '')]) ->get('protected-user-route'); $result2->assertStatus(401); @@ -111,7 +120,7 @@ public function testBlocksInactiveUsers(): void // Activation only required with email activation setting('Auth.actions', ['register' => null]); - $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, '')]) ->get('protected-route'); $result->assertStatus(200); @@ -120,7 +129,7 @@ public function testBlocksInactiveUsers(): void // Now require user activation and try again setting('Auth.actions', ['register' => '\CodeIgniter\Shield\Authentication\Actions\EmailActivator']); - $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->secret2, '')]) + $result = $this->withHeaders(['Authorization' => 'HMAC-SHA256 ' . $this->generateRawHeaderToken($token->secret, $token->rawSecretKey, '')]) ->get('protected-route'); $result->assertStatus(403); diff --git a/tests/Authentication/Filters/JWTFilterTest.php b/tests/Authentication/Filters/JWTFilterTest.php index e8f4d3b39..f26ef13fa 100644 --- a/tests/Authentication/Filters/JWTFilterTest.php +++ b/tests/Authentication/Filters/JWTFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Config\Factories; diff --git a/tests/Authentication/Filters/PermissionFilterTest.php b/tests/Authentication/Filters/PermissionFilterTest.php index 140bde35b..67564fcdf 100644 --- a/tests/Authentication/Filters/PermissionFilterTest.php +++ b/tests/Authentication/Filters/PermissionFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Shield\Entities\User; @@ -69,8 +78,8 @@ public function testFilterIncorrectGroupNoPrevious(): void ->get('protected-route'); // Should redirect to home page since previous_url is not set - $result->assertRedirectTo(site_url('/')); + $result->assertRedirectTo(config('Auth')->permissionDeniedRedirect()); // Should have error message - $result->assertSessionHas('error'); + $result->assertSessionHas('error', lang('Auth.notEnoughPrivilege')); } } diff --git a/tests/Authentication/Filters/SessionFilterTest.php b/tests/Authentication/Filters/SessionFilterTest.php index a217d7a74..10c8c21b9 100644 --- a/tests/Authentication/Filters/SessionFilterTest.php +++ b/tests/Authentication/Filters/SessionFilterTest.php @@ -2,8 +2,18 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; +use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Filters\SessionAuth; use CodeIgniter\Shield\Models\UserModel; use CodeIgniter\Test\DatabaseTestTrait; @@ -43,7 +53,7 @@ public function testFilterSuccess(): void $this->assertSame($user->id, auth('session')->id()); $this->assertSame($user->id, auth('session')->user()->id); // Last Active should have been updated - $this->assertNotEmpty(auth('session')->user()->last_active); + $this->assertInstanceOf(Time::class, auth('session')->user()->last_active); } public function testRecordActiveDate(): void diff --git a/tests/Authentication/Filters/TokenFilterTest.php b/tests/Authentication/Filters/TokenFilterTest.php index fc5337761..c080be2df 100644 --- a/tests/Authentication/Filters/TokenFilterTest.php +++ b/tests/Authentication/Filters/TokenFilterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication\Filters; use CodeIgniter\Shield\Entities\AccessToken; diff --git a/tests/Authentication/ForcePasswordResetTest.php b/tests/Authentication/ForcePasswordResetTest.php index c677adf4b..582961f92 100644 --- a/tests/Authentication/ForcePasswordResetTest.php +++ b/tests/Authentication/ForcePasswordResetTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Entities\User; diff --git a/tests/Authentication/HasAccessTokensTest.php b/tests/Authentication/HasAccessTokensTest.php index 9612b8136..0005325d8 100644 --- a/tests/Authentication/HasAccessTokensTest.php +++ b/tests/Authentication/HasAccessTokensTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Entities\AccessToken; diff --git a/tests/Authentication/HasHmacTokensTest.php b/tests/Authentication/HasHmacTokensTest.php index 12ebcd284..b3b0fda17 100644 --- a/tests/Authentication/HasHmacTokensTest.php +++ b/tests/Authentication/HasHmacTokensTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\Shield\Entities\AccessToken; @@ -34,6 +43,7 @@ public function testGenerateHmacToken(): void $this->assertIsString($token->secret); $this->assertIsString($token->secret2); + $this->assertIsString($token->rawSecretKey); // All scopes are assigned by default via wildcard $this->assertSame(['*'], $token->scopes); @@ -47,12 +57,12 @@ public function testHmacTokens(): void // Give the user a couple of access tokens $token1 = fake( UserIdentityModel::class, - ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key1', 'secret2' => 'secretKey1'] + ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key1', 'secret2' => 'd862cd9ddc23e960ca6d45a3e0b64c7509f0c0ef0e5f7b64be8910a6a714c89b83fab95251bbf17f6c84b42c26cf460a28ea969591dc64b1f5c4b323f47615d2e8cbe4c62118001d3274e0f25850b0ac2617bc43119af22c99a1a83072002267177da01f9f37225435e1914be004f4d35a49869b737ed10ab232c1ed1048bb96ef6fb70979dc9c981e17134f4356a938'] ); $token2 = fake( UserIdentityModel::class, - ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key2', 'secret2' => 'secretKey2'] + ['user_id' => $this->user->id, 'type' => 'hmac_sha256', 'secret' => 'key2', 'secret2' => 'd862cd9ddc23e960ca6d45a3e0b64c7509f0c0ef0e5f7b64be8910a6a714c89b83fab95251bbf17f6c84b42c26cf460a28ea969591dc64b1f5c4b323f47615d2e8cbe4c62118001d3274e0f25850b0ac2617bc43119af22c99a1a83072002267177da01f9f37225435e1914be004f4d35a49869b737ed10ab232c1ed1048bb96ef6fb70979dc9c981e17134f4356a938'] ); $tokens = $this->user->hmacTokens(); diff --git a/tests/Authentication/MagicLinkTest.php b/tests/Authentication/MagicLinkTest.php index 63292fae6..f8426604a 100644 --- a/tests/Authentication/MagicLinkTest.php +++ b/tests/Authentication/MagicLinkTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authentication; use CodeIgniter\I18n\Time; diff --git a/tests/Authorization/AuthorizableTest.php b/tests/Authorization/AuthorizableTest.php index 1dc098abf..5f3d9ff9c 100644 --- a/tests/Authorization/AuthorizableTest.php +++ b/tests/Authorization/AuthorizableTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authorization; use CodeIgniter\I18n\Time; diff --git a/tests/Authorization/GroupTest.php b/tests/Authorization/GroupTest.php index 40cb5f039..68c190be8 100644 --- a/tests/Authorization/GroupTest.php +++ b/tests/Authorization/GroupTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authorization; use CodeIgniter\Shield\Authorization\Groups; diff --git a/tests/Authorization/GroupsTest.php b/tests/Authorization/GroupsTest.php index 219880742..b4b1ba32f 100644 --- a/tests/Authorization/GroupsTest.php +++ b/tests/Authorization/GroupsTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Authorization; use CodeIgniter\Shield\Authorization\Groups; diff --git a/tests/Collectors/AuthTest.php b/tests/Collectors/AuthTest.php index 45d7a5871..cba81aacc 100644 --- a/tests/Collectors/AuthTest.php +++ b/tests/Collectors/AuthTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Collectors; use CodeIgniter\Shield\Auth as ShieldAuth; diff --git a/tests/Commands/HmacTest.php b/tests/Commands/HmacTest.php new file mode 100644 index 000000000..9027599f3 --- /dev/null +++ b/tests/Commands/HmacTest.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Commands; + +use CodeIgniter\Shield\Authentication\HMAC\HmacEncrypter; +use CodeIgniter\Shield\Commands\Hmac; +use CodeIgniter\Shield\Config\AuthToken; +use CodeIgniter\Shield\Entities\User; +use CodeIgniter\Shield\Models\UserIdentityModel; +use CodeIgniter\Shield\Models\UserModel; +use CodeIgniter\Shield\Test\MockInputOutput; +use Tests\Support\DatabaseTestCase; + +/** + * @internal + */ +final class HmacTest extends DatabaseTestCase +{ + private ?MockInputOutput $io = null; + + public function testEncrypt(): void + { + $idModel = new UserIdentityModel(); + + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHmacToken('foo'); + + $rawSecretKey = $token->rawSecretKey; + $token->secret2 = $rawSecretKey; + + $idModel->save($token); + $tokenCheck = $idModel->find($token->id); + + $this->assertSame($rawSecretKey, $tokenCheck->secret2); + + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac encrypt')); + + $tokenCheck = $idModel->find($token->id); + + $encrypter = new HmacEncrypter(); + $decryptedKey = $encrypter->decrypt($tokenCheck->secret2); + + $this->assertSame($rawSecretKey, $decryptedKey); + + // verify that encryption can only happen once + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac encrypt')); + + $resultsString = trim($this->io->getOutputs()); + $this->assertSame('id: 1, already encrypted, skipped.', $resultsString); + } + + public function testDecrypt(): void + { + $idModel = new UserIdentityModel(); + + /** @var User $user */ + $user = fake(UserModel::class); + $token = $user->generateHmacToken('foo'); + + $rawSecretKey = $token->rawSecretKey; + + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac decrypt')); + + $token->secret2 = $rawSecretKey; + + $idModel->save($token); + $tokenCheck = $idModel->find($token->id); + + $this->assertSame($rawSecretKey, $tokenCheck->secret2); + + // verify that decryption does not run on fields already decrypted + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac decrypt')); + + $resultsString = trim($this->io->getOutputs()); + $this->assertSame('id: 1, not encrypted, skipped.', $resultsString); + } + + public function testReEncrypt(): void + { + $idModel = new UserIdentityModel(); + + // generate first token + /** @var User $user */ + $user = fake(UserModel::class); + $token1 = $user->generateHmacToken('foo'); + + // update config, rotate keys + /** @var AuthToken $config */ + $config = config('AuthToken'); + + $config->hmacEncryptionCurrentKey = 'k2'; + + // new key generated with updated encryption + $token2 = $user->generateHmacToken('bar'); + + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac reencrypt')); + + $resultsString = $this->io->getOutputs(); + $results = explode("\n", trim($resultsString)); + + // verify that only 1 key needed to be re-encrypted + $this->assertCount(2, $results); + $this->assertSame('id: 1, Re-encrypted.', trim($results[0])); + $this->assertSame('id: 2, already encrypted with current key, skipped.', trim($results[1])); + + $encrypter = new HmacEncrypter(); + + $tokenCheck1 = $idModel->find($token1->id); + $descryptSecretKey1 = $encrypter->decrypt($tokenCheck1->secret2); + $this->assertSame($token1->rawSecretKey, $descryptSecretKey1); + + $tokenCheck2 = $idModel->find($token2->id); + $descryptSecretKey2 = $encrypter->decrypt($tokenCheck2->secret2); + $this->assertSame($token2->rawSecretKey, $descryptSecretKey2); + } + + public function testBadCommand(): void + { + $this->setMockIo([]); + $this->assertNotFalse(command('shield:hmac badcommand')); + + $resultsString = $this->stripRedColorCode(trim($this->io->getOutputs())); + + $this->assertSame('Unrecognized Command', $resultsString); + } + + /** + * Set MockInputOutput and user inputs. + * + * @param list $inputs User inputs + */ + private function setMockIo(array $inputs): void + { + $this->io = new MockInputOutput(); + $this->io->setInputs($inputs); + Hmac::setInputOutput($this->io); + } + + /** + * Strip color from output code + */ + private function stripRedColorCode(string $output): string + { + $output = str_replace(["\033[0;31m", "\033[0m"], '', $output); + + if (is_windows()) { + $output = str_replace("\r\n", "\n", $output); + } + + return $output; + } +} diff --git a/tests/Commands/Setup/ContentReplacerTest.php b/tests/Commands/Setup/ContentReplacerTest.php index 9b4c8d8e1..2a6c029e3 100644 --- a/tests/Commands/Setup/ContentReplacerTest.php +++ b/tests/Commands/Setup/ContentReplacerTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands\Setup; use CodeIgniter\Shield\Commands\Setup\ContentReplacer; diff --git a/tests/Commands/SetupTest.php b/tests/Commands/SetupTest.php index 648f0f12e..1419b5962 100644 --- a/tests/Commands/SetupTest.php +++ b/tests/Commands/SetupTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Shield\Commands\Setup; @@ -65,6 +74,9 @@ public function testRun(): void $this->assertStringContainsString('namespace Config;', $authToken); $this->assertStringContainsString('use CodeIgniter\Shield\Config\AuthToken as ShieldAuthToken;', $authToken); + $autoload = file_get_contents($appFolder . 'Config/Autoload.php'); + $this->assertStringContainsString('$helpers = [\'auth\', \'setting\'];', $autoload); + $routes = file_get_contents($appFolder . 'Config/Routes.php'); $this->assertStringContainsString('service(\'auth\')->routes($routes);', $routes); @@ -77,7 +89,7 @@ public function testRun(): void ' Created: vfs://root/Config/Auth.php Created: vfs://root/Config/AuthGroups.php Created: vfs://root/Config/AuthToken.php - Updated: vfs://root/Controllers/BaseController.php + Updated: vfs://root/Config/Autoload.php Updated: vfs://root/Config/Routes.php Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons. Updated: vfs://root/Config/Email.php', @@ -112,7 +124,7 @@ public function testRunEmailConfigIsFine(): void ' Created: vfs://root/Config/Auth.php Created: vfs://root/Config/AuthGroups.php Created: vfs://root/Config/AuthToken.php - Updated: vfs://root/Controllers/BaseController.php + Updated: vfs://root/Config/Autoload.php Updated: vfs://root/Config/Routes.php Updated: We have updated file \'vfs://root/Config/Security.php\' for security reasons.', $result diff --git a/tests/Commands/UserModelGeneratorTest.php b/tests/Commands/UserModelGeneratorTest.php index 7b8ebcb3e..0e9124448 100644 --- a/tests/Commands/UserModelGeneratorTest.php +++ b/tests/Commands/UserModelGeneratorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Test\CIUnitTestCase; diff --git a/tests/Commands/UserTest.php b/tests/Commands/UserTest.php index 98a706563..76e1e3989 100644 --- a/tests/Commands/UserTest.php +++ b/tests/Commands/UserTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Shield\Commands\User; diff --git a/tests/Controllers/ActionsTest.php b/tests/Controllers/ActionsTest.php index cb815af40..a6d9394f6 100644 --- a/tests/Controllers/ActionsTest.php +++ b/tests/Controllers/ActionsTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Controllers; use CodeIgniter\Config\Factories; diff --git a/tests/Controllers/LoginTest.php b/tests/Controllers/LoginTest.php index f4c7bc5f7..c0b6ba8bb 100644 --- a/tests/Controllers/LoginTest.php +++ b/tests/Controllers/LoginTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Controllers; use CodeIgniter\Config\Factories; diff --git a/tests/Controllers/MagicLinkTest.php b/tests/Controllers/MagicLinkTest.php index b8b1f794e..477c98248 100644 --- a/tests/Controllers/MagicLinkTest.php +++ b/tests/Controllers/MagicLinkTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Controllers; use CodeIgniter\Config\Factories; diff --git a/tests/Controllers/RegisterTest.php b/tests/Controllers/RegisterTest.php index 630c13b38..03a6587f9 100644 --- a/tests/Controllers/RegisterTest.php +++ b/tests/Controllers/RegisterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Controllers; use CodeIgniter\Config\Factories; diff --git a/tests/Language/AbstractTranslationTestCase.php b/tests/Language/AbstractTranslationTestCase.php index b3b45a21f..641b65601 100644 --- a/tests/Language/AbstractTranslationTestCase.php +++ b/tests/Language/AbstractTranslationTestCase.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * @@ -68,7 +68,7 @@ abstract class AbstractTranslationTestCase extends TestCase // PolishTranslationTest::class => 'pl', PortugueseTranslationTest::class => 'pt', BrazilianTranslationTest::class => 'pt-BR', - // RussianTranslationTest::class => 'ru', + RussianTranslationTest::class => 'ru', // SinhalaTranslationTest::class => 'si', SlovakTranslationTest::class => 'sk', SerbianTranslationTest::class => 'sr', diff --git a/tests/Language/ArabicTranslationTest.php b/tests/Language/ArabicTranslationTest.php index 22eeaf14f..fbb449d82 100644 --- a/tests/Language/ArabicTranslationTest.php +++ b/tests/Language/ArabicTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/BrazilianTranslationTest.php b/tests/Language/BrazilianTranslationTest.php index 7cf1df04c..8745961df 100644 --- a/tests/Language/BrazilianTranslationTest.php +++ b/tests/Language/BrazilianTranslationTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Language; /** diff --git a/tests/Language/BulgarianTranslationTest.php b/tests/Language/BulgarianTranslationTest.php index 1125d144e..ab26a4d5f 100644 --- a/tests/Language/BulgarianTranslationTest.php +++ b/tests/Language/BulgarianTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/FarsiTranslationTest.php b/tests/Language/FarsiTranslationTest.php index ace86cbed..ffa8942fe 100644 --- a/tests/Language/FarsiTranslationTest.php +++ b/tests/Language/FarsiTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/FrenchTranslationTest.php b/tests/Language/FrenchTranslationTest.php index df6b64bbf..2ece0ee22 100644 --- a/tests/Language/FrenchTranslationTest.php +++ b/tests/Language/FrenchTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/GermanTranslationTest.php b/tests/Language/GermanTranslationTest.php index 8bccc62d6..6c32d25ee 100644 --- a/tests/Language/GermanTranslationTest.php +++ b/tests/Language/GermanTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/IndonesianTranslationTest.php b/tests/Language/IndonesianTranslationTest.php index 5e23c1792..457b0365a 100644 --- a/tests/Language/IndonesianTranslationTest.php +++ b/tests/Language/IndonesianTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * @@ -18,4 +18,7 @@ */ final class IndonesianTranslationTest extends AbstractTranslationTestCase { + protected array $excludedLocaleKeyTranslations = [ + 'Auth.token', + ]; } diff --git a/tests/Language/ItalianTranslationTest.php b/tests/Language/ItalianTranslationTest.php index c00ad673d..a22e05fcb 100644 --- a/tests/Language/ItalianTranslationTest.php +++ b/tests/Language/ItalianTranslationTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Language; /** diff --git a/tests/Language/JapaneseTranslationTest.php b/tests/Language/JapaneseTranslationTest.php index 71c1e834e..4d908479e 100644 --- a/tests/Language/JapaneseTranslationTest.php +++ b/tests/Language/JapaneseTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/LithuanianTranslationTest.php b/tests/Language/LithuanianTranslationTest.php index b9a5372f0..bbaebedd1 100644 --- a/tests/Language/LithuanianTranslationTest.php +++ b/tests/Language/LithuanianTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/PortugueseTranslationTest.php b/tests/Language/PortugueseTranslationTest.php index 1b74b90ad..7e976cead 100644 --- a/tests/Language/PortugueseTranslationTest.php +++ b/tests/Language/PortugueseTranslationTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Language; /** diff --git a/tests/Language/RussianTranslationTest.php b/tests/Language/RussianTranslationTest.php new file mode 100644 index 000000000..313668214 --- /dev/null +++ b/tests/Language/RussianTranslationTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Language; + +/** + * @internal + */ +final class RussianTranslationTest extends AbstractTranslationTestCase +{ +} diff --git a/tests/Language/SerbianTranslationTest.php b/tests/Language/SerbianTranslationTest.php index 5dede1cf5..df9978f82 100644 --- a/tests/Language/SerbianTranslationTest.php +++ b/tests/Language/SerbianTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/SlovakTranslationTest.php b/tests/Language/SlovakTranslationTest.php index b21914bb5..333cfaeac 100644 --- a/tests/Language/SlovakTranslationTest.php +++ b/tests/Language/SlovakTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/SpanishTranslationTest.php b/tests/Language/SpanishTranslationTest.php index 6b6365834..15bca9a43 100644 --- a/tests/Language/SpanishTranslationTest.php +++ b/tests/Language/SpanishTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/SwedishTranslationTest.php b/tests/Language/SwedishTranslationTest.php index 2c5ed0674..da1d818b9 100644 --- a/tests/Language/SwedishTranslationTest.php +++ b/tests/Language/SwedishTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/TurkishTranslationTest.php b/tests/Language/TurkishTranslationTest.php index df5afd3bc..a31afd2a3 100644 --- a/tests/Language/TurkishTranslationTest.php +++ b/tests/Language/TurkishTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Language/UkrainianTranslationTest.php b/tests/Language/UkrainianTranslationTest.php index 79bdfad01..f8bc3ff31 100644 --- a/tests/Language/UkrainianTranslationTest.php +++ b/tests/Language/UkrainianTranslationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * diff --git a/tests/Unit/AuthRoutesTest.php b/tests/Unit/AuthRoutesTest.php index e67bec994..9012b731e 100644 --- a/tests/Unit/AuthRoutesTest.php +++ b/tests/Unit/AuthRoutesTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use Tests\Support\TestCase; diff --git a/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php b/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php index 8385a3f00..ada3dfbbf 100644 --- a/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php +++ b/tests/Unit/Authentication/JWT/Adapters/FirebaseAdapaterTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit\Authentication\JWT\Adapters; use CodeIgniter\I18n\Time; diff --git a/tests/Unit/Authentication/JWT/JWTManagerTest.php b/tests/Unit/Authentication/JWT/JWTManagerTest.php index 045e70633..6acd82717 100644 --- a/tests/Unit/Authentication/JWT/JWTManagerTest.php +++ b/tests/Unit/Authentication/JWT/JWTManagerTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit\Authentication\JWT; use CodeIgniter\I18n\Time; diff --git a/tests/Unit/CompositionValidatorTest.php b/tests/Unit/CompositionValidatorTest.php index 151475f62..3b3bed60b 100644 --- a/tests/Unit/CompositionValidatorTest.php +++ b/tests/Unit/CompositionValidatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\AuthenticationException; diff --git a/tests/Unit/DictionaryValidatorTest.php b/tests/Unit/DictionaryValidatorTest.php index 97fbf4072..2c5819862 100644 --- a/tests/Unit/DictionaryValidatorTest.php +++ b/tests/Unit/DictionaryValidatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\Passwords\DictionaryValidator; diff --git a/tests/Unit/EmailActivatorTest.php b/tests/Unit/EmailActivatorTest.php index daa7f79ac..15d356cc0 100644 --- a/tests/Unit/EmailActivatorTest.php +++ b/tests/Unit/EmailActivatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\Actions\EmailActivator; diff --git a/tests/Unit/FilterInCliTest.php b/tests/Unit/FilterInCliTest.php index fa9a7c77d..ad642c657 100644 --- a/tests/Unit/FilterInCliTest.php +++ b/tests/Unit/FilterInCliTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Filters\FilterInterface; diff --git a/tests/Unit/LoginModelTest.php b/tests/Unit/LoginModelTest.php index 464a2b0f5..2d77f4cfd 100644 --- a/tests/Unit/LoginModelTest.php +++ b/tests/Unit/LoginModelTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Exceptions\ValidationException; diff --git a/tests/Unit/NothingPersonalValidatorTest.php b/tests/Unit/NothingPersonalValidatorTest.php index b3e7d8e77..d5d173d36 100644 --- a/tests/Unit/NothingPersonalValidatorTest.php +++ b/tests/Unit/NothingPersonalValidatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\Passwords\NothingPersonalValidator; @@ -281,7 +290,8 @@ public static function provideMaxSimilarityZeroTurnsOffSimilarityCalculation(): [ 66, false, - ], [ + ], + [ 0, true, ], @@ -314,7 +324,8 @@ public static function provideCheckPasswordWithBadEmail(): iterable [ 'test', true, - ], [ + ], + [ 'test@example', true, ], diff --git a/tests/Unit/PasswordsTest.php b/tests/Unit/PasswordsTest.php index 7398fc533..80bad8ac0 100644 --- a/tests/Unit/PasswordsTest.php +++ b/tests/Unit/PasswordsTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\Passwords; @@ -52,7 +61,7 @@ public function testHash(): string public function testNeedsRehashTakesCareOptions(string $hashedPassword): void { $config = new AuthConfig(); - $config->hashCost = 12; + $config->hashCost = 13; $passwords = new Passwords($config); $result = $passwords->needsRehash($hashedPassword); diff --git a/tests/Unit/PwnedValidatorTest.php b/tests/Unit/PwnedValidatorTest.php index 1d0c7ec90..52e5b6767 100644 --- a/tests/Unit/PwnedValidatorTest.php +++ b/tests/Unit/PwnedValidatorTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\HTTP\Exceptions\HTTPException; diff --git a/tests/Unit/UserIdentityModelTest.php b/tests/Unit/UserIdentityModelTest.php index 34e4e45f0..53cbb4259 100644 --- a/tests/Unit/UserIdentityModelTest.php +++ b/tests/Unit/UserIdentityModelTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Shield\Authentication\Authenticators\Session; diff --git a/tests/Unit/UserModelTest.php b/tests/Unit/UserModelTest.php index 6cb92a0d9..77c71ea59 100644 --- a/tests/Unit/UserModelTest.php +++ b/tests/Unit/UserModelTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\Database\Exceptions\DataException; diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index f2554fa62..9c4d3ccda 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Unit; use CodeIgniter\I18n\Time; diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index 5258da8e6..36c680924 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * This file is part of CodeIgniter 4 framework. + * This file is part of CodeIgniter Shield. * * (c) CodeIgniter Foundation * @@ -37,7 +37,7 @@ class Registrar 'DBDriver' => 'MySQLi', 'DBPrefix' => 'db_', 'pConnect' => false, - 'DBDebug' => (ENVIRONMENT !== 'production'), + 'DBDebug' => true, 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -56,7 +56,7 @@ class Registrar 'DBDriver' => 'Postgre', 'DBPrefix' => 'db_', 'pConnect' => false, - 'DBDebug' => (ENVIRONMENT !== 'production'), + 'DBDebug' => true, 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -75,7 +75,7 @@ class Registrar 'DBDriver' => 'SQLite3', 'DBPrefix' => 'db_', 'pConnect' => false, - 'DBDebug' => (ENVIRONMENT !== 'production'), + 'DBDebug' => true, 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -95,7 +95,7 @@ class Registrar 'DBDriver' => 'SQLSRV', 'DBPrefix' => 'db_', 'pConnect' => false, - 'DBDebug' => (ENVIRONMENT !== 'production'), + 'DBDebug' => true, 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -114,7 +114,7 @@ class Registrar 'DBDriver' => 'OCI8', 'DBPrefix' => 'db_', 'pConnect' => false, - 'DBDebug' => (ENVIRONMENT !== 'production'), + 'DBDebug' => true, 'charset' => 'utf8', 'DBCollat' => 'utf8_general_ci', 'swapPre' => '', @@ -136,7 +136,7 @@ public static function Database() // Under GitHub Actions, we can set an ENV var named 'DB' // so that we can test against multiple databases. - if (($group = getenv('DB')) && ! empty(self::$dbConfig[$group])) { + if (($group = getenv('DB')) && ! isset(self::$dbConfig[$group])) { $config['tests'] = self::$dbConfig[$group]; } diff --git a/tests/_support/DatabaseTestCase.php b/tests/_support/DatabaseTestCase.php index 8e72928af..61f5f8d25 100644 --- a/tests/_support/DatabaseTestCase.php +++ b/tests/_support/DatabaseTestCase.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support; use CodeIgniter\Shield\Config\Auth; diff --git a/tests/_support/FakeUser.php b/tests/_support/FakeUser.php index 6cc1fe09e..1dcb62fd8 100644 --- a/tests/_support/FakeUser.php +++ b/tests/_support/FakeUser.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support; use CodeIgniter\Shield\Entities\User; diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index 22ba985d6..a7af822ea 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Shield. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support; use CodeIgniter\Config\Factories; @@ -26,8 +35,10 @@ protected function setUp(): void $settings = new Settings($configSettings); Services::injectMock('settings', $settings); + // Load helpers that should be autoloaded + helper(['auth', 'setting']); + // Ensure from email is available anywhere during Tests - helper('setting'); setting('Email.fromEmail', 'foo@example.com'); setting('Email.fromName', 'John Smith');