From e8cdfc9d7a268c47e37aafc41f0122d99c9db18f Mon Sep 17 00:00:00 2001 From: nojimage Date: Mon, 9 Jun 2025 18:46:41 +0900 Subject: [PATCH 1/3] Support PHP 8.4, CakePHP 5.2 --- .github/workflows/ci.yml | 22 +++++++++---------- composer.json | 2 +- src/Authenticator/CookieAuthenticator.php | 4 ++-- src/Authenticator/EncryptCookieTrait.php | 4 ++-- src/Identifier/RememberMeTokenIdentifier.php | 2 +- .../Authenticator/CookieAuthenticatorTest.php | 22 +++++++++---------- 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b30c410..fe2a712 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,12 @@ on: jobs: testsuite: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - cakephp-version: ['5.0.*'] - php-version: ['8.1', '8.2', '8.3'] + cakephp-version: ['5.0.*', '5.1.*', '5.2.*'] + php-version: ['8.2', '8.3', '8.4'] db-type: ['mysql'] prefer-lowest: [''] coverage: ['no'] @@ -27,14 +27,14 @@ jobs: db-type: 'mysql:8.0' prefer-lowest: 'prefer-lowest' coverage: 'no' - - cakephp-version: '5.0.*' - php-version: '8.3' + - cakephp-version: '5.2.*' + php-version: '8.4' db-type: 'mysql' prefer-lowest: '' coverage: 'no' - - php-version: '8.3' - cakephp-version: '5.0.*' + - php-version: '8.4' + cakephp-version: '5.2.*' db-type: 'mysql' prefer-lowest: '' coverage: 'coverage' @@ -80,9 +80,7 @@ jobs: - name: Composer install run: | - if [[ ${{ matrix.php-version }} == '8.1' ]]; then - composer update --ignore-platform-reqs - elif ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then + if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then composer update --prefer-lowest --prefer-stable else composer update @@ -111,7 +109,7 @@ jobs: cs-stan: name: Coding Standard & Static Analysis - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -121,7 +119,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' extensions: mbstring, intl, apcu coverage: none diff --git a/composer.json b/composer.json index 3c36a6e..b9c9278 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "cakephp/cakephp": "^5.0", "cakephp/cakephp-codesniffer": "^5.1", "cakephp/migrations": "^4.0", - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.0.9" }, "autoload": { "psr-4": { diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index 9725b6f..1b795cd 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -148,7 +148,7 @@ public function persistIdentity(ServerRequestInterface $request, ResponseInterfa $encryptedToken = static::encryptToken( $identity[$this->getConfig('fields.' . AbstractIdentifier::CREDENTIAL_USERNAME)], $token['series'], - $token['token'] + $token['token'], ); $cookie = $this->_createCookie($encryptedToken, $token['expires']); @@ -261,7 +261,7 @@ protected function _createCookie(string $value, ?DateTime $expires = null): Cook $data['path'], $data['domain'], $data['secure'], - $data['httpOnly'] + $data['httpOnly'], ); } diff --git a/src/Authenticator/EncryptCookieTrait.php b/src/Authenticator/EncryptCookieTrait.php index 930b3ec..97cd5fb 100644 --- a/src/Authenticator/EncryptCookieTrait.php +++ b/src/Authenticator/EncryptCookieTrait.php @@ -44,8 +44,8 @@ public static function encryptToken(string $username, string $series, string $to return base64_encode( Security::encrypt( json_encode(compact('username', 'series', 'token'), JSON_THROW_ON_ERROR), - Security::getSalt() - ) + Security::getSalt(), + ), ); } diff --git a/src/Identifier/RememberMeTokenIdentifier.php b/src/Identifier/RememberMeTokenIdentifier.php index 8b58d6d..38b9282 100644 --- a/src/Identifier/RememberMeTokenIdentifier.php +++ b/src/Identifier/RememberMeTokenIdentifier.php @@ -71,7 +71,7 @@ public function identify(array $credentials): ArrayAccess|array|null !isset( $credentials[self::CREDENTIAL_USERNAME], $credentials[self::CREDENTIAL_SERIES], - $credentials[self::CREDENTIAL_TOKEN] + $credentials[self::CREDENTIAL_TOKEN], ) ) { return null; diff --git a/tests/TestCase/Authenticator/CookieAuthenticatorTest.php b/tests/TestCase/Authenticator/CookieAuthenticatorTest.php index a7504dd..c5e907a 100644 --- a/tests/TestCase/Authenticator/CookieAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/CookieAuthenticatorTest.php @@ -58,7 +58,7 @@ public function testAuthenticateCredentialsNotPresent(): void ]); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $authenticator = new CookieAuthenticator($identifiers); @@ -88,7 +88,7 @@ public function testAuthenticateEmptyCookie(): void null, [ 'rememberMe' => '', - ] + ], ); $authenticator = new CookieAuthenticator($identifiers); @@ -118,7 +118,7 @@ public function testAuthenticateUnencryptedCookie(): void null, [ 'rememberMe' => 'unencrypted', - ] + ], ); $authenticator = new CookieAuthenticator($identifiers); @@ -150,7 +150,7 @@ public function testAuthenticateInvalidCookie(): void null, [ 'rememberMe' => $encryptedToken, - ] + ], ); $authenticator = new CookieAuthenticator($identifiers); @@ -183,7 +183,7 @@ public function testAuthenticateExpired(): void null, [ 'rememberMe' => $encryptedToken, - ] + ], ); $authenticator = new CookieAuthenticator($identifiers); @@ -216,7 +216,7 @@ public function testAuthenticateValid(): void null, [ 'rememberMe' => $encryptedToken, - ] + ], ); $authenticator = new CookieAuthenticator($identifiers); @@ -238,7 +238,7 @@ public function testPersistIdentity(): void ]); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $request = $request->withParsedBody([ 'remember_me' => 1, @@ -297,7 +297,7 @@ public function testPersistIdentityCanTwice(): void ]); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $request = $request->withParsedBody([ 'remember_me' => 1, @@ -329,7 +329,7 @@ public function testDropExpiredTokenOnPersistIdentity(): void { $identifiers = new IdentifierCollection(['Authentication.Password']); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $request = $request->withParsedBody([ 'remember_me' => 1, @@ -367,7 +367,7 @@ public function testClearIdentity(): void ]); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $response = new Response(); @@ -395,7 +395,7 @@ public function testClearIdentityWithCookie(): void ]); $request = ServerRequestFactory::fromGlobals( - ['REQUEST_URI' => '/testpath'] + ['REQUEST_URI' => '/testpath'], ); $response = new Response(); From 00be2590b6115d249c578898a5ebe3f528ce8ff3 Mon Sep 17 00:00:00 2001 From: nojimage Date: Mon, 9 Jun 2025 18:58:53 +0900 Subject: [PATCH 2/3] Add phpstan --- .github/workflows/ci.yml | 6 +++--- composer.json | 8 ++++++++ phpstan.neon.dist | 11 +++++++++++ src/Authenticator/CookieAuthenticator.php | 11 ++++++++--- src/Identifier/RememberMeTokenIdentifier.php | 12 ++++++++---- src/Model/Entity/RememberMeToken.php | 12 ++---------- 6 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe2a712..01faa42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,6 +150,6 @@ jobs: # if: success() || failure() # run: vendor/bin/psalm.phar --output-format=github # -# - name: Run phpstan -# if: success() || failure() -# run: vendor/bin/phpstan.phar analyse --error-format=github + - name: Run phpstan + if: success() || failure() + run: vendor/bin/phpstan.phar analyse --error-format=github diff --git a/composer.json b/composer.json index b9c9278..d608021 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "cakephp/cakephp": "^5.0", "cakephp/cakephp-codesniffer": "^5.1", "cakephp/migrations": "^4.0", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.0.9" }, "autoload": { @@ -36,8 +37,15 @@ } }, "scripts": { + "check": [ + "@cs-check", + "@stan", + "@test" + ], "cs-check": "phpcs -p --extensions=php ./src ./tests", "cs-fix": "phpcbf -p --extensions=php ./src ./tests", + "stan": "phpstan analyse", + "test": "phpunit --colors=always", "update-lowest": "composer update --prefer-lowest --prefer-stable" }, "config": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..6c13016 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,11 @@ +parameters: + level: 8 + checkMissingIterableValueType: false + treatPhpDocTypesAsCertain: false + paths: + - src/ + excludePaths: + - test_app/ + ignoreErrors: + - + identifier: missingType.generics \ No newline at end of file diff --git a/src/Authenticator/CookieAuthenticator.php b/src/Authenticator/CookieAuthenticator.php index 1b795cd..251bf00 100644 --- a/src/Authenticator/CookieAuthenticator.php +++ b/src/Authenticator/CookieAuthenticator.php @@ -173,8 +173,13 @@ protected function _saveToken(ArrayAccess|array $identity, string $token): Entit throw new InvalidArgumentException('Can\'t detect user model'); } - $userTable = $this->fetchTable($userModel); - /** @var \RememberMe\Model\Table\RememberMeTokensTableInterface $tokenTable */ + $usersTable = $this->fetchTable($userModel); + $primaryKey = $usersTable->getPrimaryKey(); + if (!is_string($primaryKey)) { + throw new InvalidArgumentException('User model must have a single primary key.'); + } + + /** @var \RememberMe\Model\Table\RememberMeTokensTableInterface&\Cake\ORM\Table $tokenTable */ $tokenTable = $this->fetchTable($this->getConfig('tokenStorageModel')); if ($this->getConfig('dropExpiredToken')) { @@ -185,7 +190,7 @@ protected function _saveToken(ArrayAccess|array $identity, string $token): Entit // create token entity $entity = $tokenTable->newEntity([ 'model' => $userModel, - 'foreign_id' => $identity[$userTable->getPrimaryKey()], + 'foreign_id' => $identity[$primaryKey], 'series' => static::_generateToken($identity), 'token' => $token, 'expires' => new DateTime($this->getConfig('cookie.expire')), diff --git a/src/Identifier/RememberMeTokenIdentifier.php b/src/Identifier/RememberMeTokenIdentifier.php index 38b9282..e7717c3 100644 --- a/src/Identifier/RememberMeTokenIdentifier.php +++ b/src/Identifier/RememberMeTokenIdentifier.php @@ -50,7 +50,7 @@ class RememberMeTokenIdentifier extends AbstractIdentifier /** * @inheritDoc */ - protected function buildResolver($config): OrmResolver + protected function buildResolver(array|string $config): OrmResolver { $instance = $this->traitBuildResolver($config); @@ -118,10 +118,10 @@ protected function _findIdentity(string $identifier): ArrayAccess|array|EntityIn } /** - * find user's remember me token. + * find some user's remember me token. * * @param \Cake\Datasource\EntityInterface $identity the identity - * @param string $series the credentials series + * @param string $series the credential series * @return \Cake\Datasource\EntityInterface|null */ protected function _findToken(EntityInterface $identity, string $series): ?EntityInterface @@ -133,11 +133,15 @@ protected function _findToken(EntityInterface $identity, string $series): ?Entit $usersTable = $this->getResolver()->fetchTable($userModel); $tokenStorageTable = $this->getResolver()->fetchTable($this->getConfig('tokenStorageModel')); + $primaryKey = $usersTable->getPrimaryKey(); + if (!is_string($primaryKey)) { + throw new InvalidArgumentException('User model must have a single primary key.'); + } return $tokenStorageTable->find() ->where([ 'model' => $userModel, - 'foreign_id' => $identity->get($usersTable->getPrimaryKey()), + 'foreign_id' => $identity->get($primaryKey), 'series' => $series, ]) ->first(); diff --git a/src/Model/Entity/RememberMeToken.php b/src/Model/Entity/RememberMeToken.php index 4b2ca22..f80bd68 100644 --- a/src/Model/Entity/RememberMeToken.php +++ b/src/Model/Entity/RememberMeToken.php @@ -20,13 +20,7 @@ class RememberMeToken extends Entity { /** - * Fields that can be mass assigned using newEntity() or patchEntity(). - * - * Note that when '*' is set to true, this allows all unspecified fields to - * be mass assigned. For security purposes, it is advised to set '*' to false - * (or remove it), and explicitly make individual fields accessible as needed. - * - * @var array + * @inheritDoc */ protected array $_accessible = [ '*' => true, @@ -34,9 +28,7 @@ class RememberMeToken extends Entity ]; /** - * Fields that are excluded from JSON versions of the entity. - * - * @var array + * @inheritDoc */ protected array $_hidden = [ 'token', From 968a96a47e3573ad2d6c46d1fd0d5a35dd7707cf Mon Sep 17 00:00:00 2001 From: nojimage Date: Mon, 9 Jun 2025 19:06:10 +0900 Subject: [PATCH 3/3] Update README --- README.ja.md | 10 +++++----- README.md | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.ja.md b/README.ja.md index 045c644..28f6826 100644 --- a/README.ja.md +++ b/README.ja.md @@ -17,7 +17,7 @@ このプラグインは、Cookieによって永続的にログインする認証ハンドラを提供します。 暗号化されたユーザー名/パスワードをCookieに設定する代わりに、トークンを発行する方法を使用します。 -This library inspired by Barry Jaspan's article "[Improved Persistent Login Cookie Best Practice](http://jaspan.com/improved_persistent_login_cookie_best_practice)", and Gabriel Birke's libray "https://github.com/gbirke/rememberme". +This library is inspired by Barry Jaspan's article "[Improved Persistent Login Cookie Best Practice](http://jaspan.com/improved_persistent_login_cookie_best_practice)", and Gabriel Birke's library "https://github.com/gbirke/rememberme". ## インストール @@ -50,7 +50,7 @@ bin/cake migrations migrate -p RememberMe ## Authenticationプラグインでの使用方法 [cakephp/authentication](https://github.com/cakephp/authentication) を使用しているのであれば、 -`RememberMeTokenIdentifier` と `CookeAuthenticator` を使用してください。 +`RememberMeTokenIdentifier` と `CookieAuthenticator` を使用してください。 `Application` の `getAuthenticationService` フックで RememberMeプラグインの Identifier と Authenticator を呼び出す例です: @@ -135,7 +135,7 @@ default: `'remember_me_token'` ]); ``` -### RememberMe.CookeAuthenticator のオプション +### RememberMe.CookieAuthenticator のオプション #### `loginUrl` @@ -223,7 +223,7 @@ default: `'RememberMe.RememberMeTokens'` #### `always` -このオプションをtrueに設定すると、ログインCookieは認証が識別された後、常に発行されます。 +このオプションをtrueに設定すると、認証が成功した後、常にログインCookieが発行されます。 default: `false` @@ -235,7 +235,7 @@ default: `false` #### `dropExpiredToken` -このオプションをtrueに設定すると、認証が識別された後に有効期限が切れたトークンを削除します。 +このオプションをtrueに設定すると、認証が成功した後に有効期限が切れたトークンを削除します。 default: `true` diff --git a/README.md b/README.md index 35e68d4..aa0a63b 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@

-This plugin provides an authenticate handler that permanent login by cookie. This plugin use method of issuing a token, instead of set to cookie encrypted username/password. +This plugin provides an authentication handler that enables permanent login via cookie. This plugin uses a method of issuing a token instead of setting an encrypted username / password in a cookie. -This library inspired by Barry Jaspan's article "[Improved Persistent Login Cookie Best Practice](http://jaspan.com/improved_persistent_login_cookie_best_practice)", and Gabriel Birke's libray "https://github.com/gbirke/rememberme". +This library is inspired by Barry Jaspan's article "[Improved Persistent Login Cookie Best Practice](http://jaspan.com/improved_persistent_login_cookie_best_practice)", and Gabriel Birke's library "https://github.com/gbirke/rememberme". ## Installation @@ -50,9 +50,9 @@ bin/cake migrations migrate -p RememberMe ## Usage with Authentication plugin If you're using [cakephp/authentication](https://github.com/cakephp/authentication), -use `RememberMeTokenIdentifier` and `CookeAuthenticator`. +use `RememberMeTokenIdentifier` and `CookieAuthenticator`. -Example load RememberMe's Identifier and Authenticator into the `getAuthenticationService` hook within `Application`: +Example of loading RememberMe's Identifier and Authenticator into the `getAuthenticationService` hook within `Application`: ```php // in your src/Application.php @@ -97,8 +97,8 @@ default: `['username' => 'username']` #### `resolver` -The identity resolver. If change your Resolver, - must extend `Authentication\Identifier\Resolver\OrmResolver`. +The identity resolver. If you change your Resolver, +it must extend `Authentication\Identifier\Resolver\OrmResolver`. default: `'Authentication.Orm'` @@ -113,7 +113,7 @@ default: `'Authentication.Orm'` #### `tokenStorageModel` -A model used for find login cookie tokens. +A model used for finding login cookie tokens. default: `'RememberMe.RememberMeTokens'` @@ -223,7 +223,7 @@ default: `'RememberMe.RememberMeTokens'` #### `always` -When this option is set to true, a login cookie is always issued after authentication identified. +When this option is set to true, a login cookie is always issued after successful authentication. default: `false` @@ -235,7 +235,7 @@ default: `false` #### `dropExpiredToken` -When this option is set to true, drop expired tokens after authentication identified. +When this option is set to true, expired tokens are dropped after successful authentication. default: `true`