diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index a4cd1263..00000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: "🐛 Bug Report" -about: "If something isn't working as expected 🤔" - ---- - -Version: ?.?.? - -### Bug Description -... A clear and concise description of what the bug is. A good bug report shouldn't leave others needing to chase you up for more information. - -### Steps To Reproduce -... If possible a minimal demo of the problem ... - -### Expected Behavior -... A clear and concise description of what you expected to happen. - -### Possible Solution -... Only if you have suggestions on a fix for the bug diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index d2e21948..00000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: "🚀 Feature Request" -about: "I have a suggestion (and may want to implement it) 🙂" - ---- - -- Is your feature request related to a problem? Please describe. -- Explain your intentions. -- It's up to you to make a strong case to convince the project's developers of the merits of this feature. diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md deleted file mode 100644 index 75c48b6e..00000000 --- a/.github/ISSUE_TEMPLATE/Support_question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "🤗 Support Question" -about: "If you have a question 💬, please check out our forum!" - ---- - ---------------^ Click "Preview" for a nicer view! -We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁. - -* Nette Forum: https://forum.nette.org -* Nette Gitter: https://gitter.im/nette/nette -* Slack (czech): https://pehapkari.slack.com/messages/C2R30BLKA diff --git a/.github/ISSUE_TEMPLATE/Support_us.md b/.github/ISSUE_TEMPLATE/Support_us.md deleted file mode 100644 index 92d8a4c3..00000000 --- a/.github/ISSUE_TEMPLATE/Support_us.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "❤️ Support us" -about: "If you would like to support our efforts in maintaining this project 🙌" - ---- - ---------------^ Click "Preview" for a nicer view! - -> https://nette.org/donate - -Help support Nette! - -We develop Nette Framework for more than 14 years. In order to make your life more comfortable. Nette cares about the safety of your sites. Nette saves you time. And gives job opportunities. - -Nette earns you money. And is absolutely free. - -To ensure future development and improving the documentation, we need your donation. - -Whether you are chief of IT company which benefits from Nette, or developer who goes for advice on our forum, if you like Nette, [please make a donation now](https://nette.org/donate). - -Thank you! diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 25adc952..00000000 --- a/.github/funding.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: dg -custom: "https://nette.org/donate" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index f8aa3f40..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -- bug fix / new feature? -- BC break? yes/no -- doc PR: nette/docs#??? - - diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index a5618ecd..23019765 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,10 +7,10 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -21,10 +21,10 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0ae1a544..b23bf1a1 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,10 +10,10 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e87ca57d..239bdc4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,13 +7,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['8.0', '8.1', '8.2'] fail-fast: false name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -22,7 +22,7 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: output path: tests/**/output @@ -32,10 +32,10 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -46,10 +46,10 @@ jobs: name: Code Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 5d18b252..0f74ac92 100644 --- a/composer.json +++ b/composer.json @@ -15,14 +15,14 @@ } ], "require": { - "php": ">=7.2 <8.3", - "nette/utils": "^3.2.1" + "php": ">=8.0 <8.3", + "nette/utils": "^4.0" }, "require-dev": { - "nette/di": "^3.0.1", - "nette/http": "^3.0.0", - "nette/tester": "^2.0", - "tracy/tracy": "^2.4", + "nette/di": "^4.0", + "nette/http": "^4.0", + "nette/tester": "^2.4", + "tracy/tracy": "^2.8", "phpstan/phpstan-nette": "^1.0", "mockery/mockery": "^1.5" }, @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } } } diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 184152c0..00000000 --- a/contributing.md +++ /dev/null @@ -1,33 +0,0 @@ -How to contribute & use the issue tracker -========================================= - -Nette welcomes your contributions. There are several ways to help out: - -* Create an issue on GitHub, if you have found a bug -* Write test cases for open bug issues -* Write fixes for open bug/feature issues, preferably with test cases included -* Contribute to the [documentation](https://nette.org/en/writing) - -Issues ------- - -Please **do not use the issue tracker to ask questions**. We will be happy to help you -on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. - -**Feature requests** are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. - -Contributing ------------- - -If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). - -The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. - -Please do not fix whitespace, format code, or make a purely cosmetic patch. - -Thanks! :heart: diff --git a/readme.md b/readme.md index 8bfa4d96..6cf404f1 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ Authentication & Authorization library for Nette. Documentation can be found on the [website](https://doc.nette.org/access-control). -It requires PHP version 7.2 and supports PHP up to 8.2. +It requires PHP version 8.0 and supports PHP up to 8.2. [Support Me](https://github.com/sponsors/dg) @@ -72,7 +72,7 @@ $user->setExpiration(null); Expiration must be set to value equal or lower than the expiration of sessions. -The reason of the last logout can be obtained by method `$user->getLogoutReason()`, which returns either the constant `Nette\Security\IUserStorage::INACTIVITY` if the time expired or `IUserStorage::MANUAL` when the `logout()` method was called. +The reason of the last logout can be obtained by method `$user->getLogoutReason()`, which returns either the constant `Nette\Security\UserStorage::LOGOUT_INACTIVITY` if the time expired or `UserStorage::LOGOUT_MANUAL` when the `logout()` method was called. In presenters, you can verify login in the `startup()` method: @@ -102,12 +102,12 @@ $authenticator = new Nette\Security\SimpleAuthenticator([ This solution is more suitable for testing purposes. We will show you how to create an authenticator that will verify credentials against a database table. -An authenticator is an object that implements the [Nette\Security\IAuthenticator](https://api.nette.org/3.0/Nette/Security/IAuthenticator.html) interface with method `authenticate()`. Its task is either to return the so-called [identity](#Identity) or to throw an exception `Nette\Security\AuthenticationException`. It would also be possible to provide an fine-grain error code `IAuthenticator::IDENTITY_NOT_FOUND` or `IAuthenticator::INVALID_CREDENTIAL`. +An authenticator is an object that implements the [Nette\Security\Authenticator](https://api.nette.org/3.0/Nette/Security/Authenticator.html) interface with method `authenticate()`. Its task is either to return the so-called [identity](#Identity) or to throw an exception `Nette\Security\AuthenticationException`. It would also be possible to provide an fine-grain error code `Authenticator::IDENTITY_NOT_FOUND` or `Authenticator::INVALID_CREDENTIAL`. ```php use Nette; -class MyAuthenticator implements Nette\Security\IAuthenticator +class MyAuthenticator implements Nette\Security\Authenticator { private $database; private $passwords; @@ -118,10 +118,8 @@ class MyAuthenticator implements Nette\Security\IAuthenticator $this->passwords = $passwords; } - public function authenticate(array $credentials): Nette\Security\IIdentity + public function authenticate($username, $password): Nette\Security\IIdentity { - [$username, $password] = $credentials; - $row = $this->database->table('users') ->where('username', $username) ->fetch(); @@ -134,7 +132,7 @@ class MyAuthenticator implements Nette\Security\IAuthenticator throw new Nette\Security\AuthenticationException('Invalid password.'); } - return new Nette\Security\Identity( + return new Nette\Security\SimpleIdentity( $row->id, $row->role, // or array of roles ['name' => $row->username] @@ -180,7 +178,7 @@ Importantly, **when user logs out, identity is not deleted** and is still availa Thanks to this, you can still assume which user is at the computer and, for example, display personalized offers in the e-shop, however, you can only display his personal data after logging in. -Identity is an object that implements the [Nette\Security\IIdentity](https://api.nette.org/3.0/Nette/Security/IIdentity.html) interface, the default implementation is [Nette\Security\Identity](https://api.nette.org/3.0/Nette/Security/Identity.html). And as mentioned, identity is stored in the session, so if, for example, we change the role of some of the logged-in users, old data will be kept in the identity until he logs in again. +Identity is an object that implements the [Nette\Security\IIdentity](https://api.nette.org/3.0/Nette/Security/IIdentity.html) interface, the default implementation is [Nette\Security\SimpleIdentity](https://api.nette.org/3.0/Nette/Security/SimpleIdentity.html). And as mentioned, identity is stored in the session, so if, for example, we change the role of some of the logged-in users, old data will be kept in the identity until he logs in again. @@ -201,7 +199,7 @@ if ($user->isLoggedIn()) { // is user logged in? Roles ----- -The purpose of roles is to offer a more precise permission management and remain independent on the user name. As soon as user logs in, he is assigned one or more roles. Roles themselves may be simple strings, for example, `admin`, `member`, `guest`, etc. They are specified in the second argument of `Identity` constructor, either as a string or an array. +The purpose of roles is to offer a more precise permission management and remain independent on the user name. As soon as user logs in, he is assigned one or more roles. Roles themselves may be simple strings, for example, `admin`, `member`, `guest`, etc. They are specified in the second argument of `SimpleIdentity` constructor, either as a string or an array. As an authorization criterion, we will now use the method `isInRole()`, which checks whether the user is in the given role: @@ -211,7 +209,7 @@ if ($user->isInRole('admin')) { // is the admin role assigned to the user? } ``` -As you already know, logging the user out does not erase his identity. Thus, method `getIdentity()` still returns object `Identity`, including all granted roles. The Nette Framework adheres to the principle of "less code, more security", so when you are checking roles, you do not have to check whether the user is logged in too. Method `isInRole()` works with **effective roles**, ie if the user is logged in, roles assigned to identity are used, if he is not logged in, an automatic special role `guest` is used instead. +As you already know, logging the user out does not erase his identity. Thus, method `getIdentity()` still returns object `SimpleIdentity`, including all granted roles. The Nette Framework adheres to the principle of "less code, more security", so when you are checking roles, you do not have to check whether the user is logged in too. Method `isInRole()` works with **effective roles**, ie if the user is logged in, roles assigned to identity are used, if he is not logged in, an automatic special role `guest` is used instead. Authorizator @@ -223,10 +221,10 @@ In addition to roles, we will introduce the terms resource and operation: - **resource** is a logical unit of the application - article, page, user, menu item, poll, presenter, ... - **operation** is a specific activity, which user may or may not do with *resource* - view, edit, delete, vote, ... -An authorizer is an object that decides whether a given *role* has permission to perform a certain *operation* with specific *resource*. It is an object implementing the [Nette\Security\IAuthorizator](https://api.nette.org/3.0/Nette/Security/IAuthorizator.html) interface with only one method `isAllowed()`: +An authorizer is an object that decides whether a given *role* has permission to perform a certain *operation* with specific *resource*. It is an object implementing the [Nette\Security\Authorizator](https://api.nette.org/3.0/Nette/Security/Authorizator.html) interface with only one method `isAllowed()`: ```php -class MyAuthorizator implements Nette\Security\IAuthorizator +class MyAuthorizator implements Nette\Security\Authorizator { public function isAllowed($role, $resource, $operation): bool { @@ -434,3 +432,5 @@ It is possible to have several independent logged users within one site and one ```php $user->getStorage()->setNamespace('forum'); ``` + +[Continue...](https://doc.nette.org/en/3.0/access-control) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index b2e3b145..14796df5 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -19,8 +19,7 @@ */ class SecurityExtension extends Nette\DI\CompilerExtension { - /** @var bool */ - private $debugMode; + private bool $debugMode; public function __construct(bool $debugMode = false) @@ -40,8 +39,8 @@ public function getConfigSchema(): Nette\Schema\Schema 'password' => Expect::string(), 'roles' => Expect::anyOf(Expect::string(), Expect::listOf('string')), 'data' => Expect::array(), - ])->castTo('array') - ) + ])->castTo('array'), + ), ), 'roles' => Expect::arrayOf('string|array|null'), // role => parent(s) 'resources' => Expect::arrayOf('string|null'), // resource => parent @@ -81,10 +80,6 @@ public function loadConfiguration() $storage->addSetup('setCookieParameters', [$auth->cookieName, $auth->cookieDomain, $auth->cookieSamesite]); } - $builder->addDefinition($this->prefix('legacyUserStorage')) // deprecated - ->setType(Nette\Security\IUserStorage::class) - ->setFactory(Nette\Http\UserStorage::class); - $user = $builder->addDefinition($this->prefix('user')) ->setFactory(Nette\Security\User::class); @@ -102,7 +97,7 @@ public function loadConfiguration() } $builder->addDefinition($this->prefix('authenticator')) - ->setType(Nette\Security\IAuthenticator::class) + ->setType(Nette\Security\Authenticator::class) ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles, $usersData]); if ($this->name === 'security') { diff --git a/src/Bridges/SecurityHttp/CookieIdentity.php b/src/Bridges/SecurityHttp/CookieIdentity.php new file mode 100644 index 00000000..915a8a8f --- /dev/null +++ b/src/Bridges/SecurityHttp/CookieIdentity.php @@ -0,0 +1,53 @@ +uid = $uid; + } + + + public function getId(): string + { + return $this->uid; + } + + + public function getRoles(): array + { + throw new Nette\NotSupportedException; + } + + + public function getData(): array + { + throw new Nette\NotSupportedException; + } +} diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 0ee6e8a2..26462f22 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -23,26 +23,13 @@ final class CookieStorage implements Nette\Security\UserStorage private const MIN_LENGTH = 13; - /** @var Http\IRequest */ - private $request; - - /** @var Http\IResponse */ - private $response; - - /** @var ?string */ - private $uid; - - /** @var string */ - private $cookieName = 'userid'; - - /** @var ?string */ - private $cookieDomain; - - /** @var string */ - private $cookieSameSite = 'Lax'; - - /** @var ?string */ - private $cookieExpiration; + private Http\IRequest $request; + private Http\IResponse $response; + private ?string $uid = null; + private string $cookieName = 'userid'; + private ?string $cookieDomain = null; + private string $cookieSameSite = 'Lax'; + private string|int|null $cookieExpiration = null; public function __construct(Http\IRequest $request, Http\IResponse $response) @@ -64,11 +51,8 @@ public function saveAuthentication(IIdentity $identity): void $this->cookieName, $uid, $this->cookieExpiration, - null, - $this->cookieDomain, - null, - true, - $this->cookieSameSite + domain: $this->cookieDomain, + sameSite: $this->cookieSameSite, ); } @@ -78,8 +62,7 @@ public function clearAuthentication(bool $clearIdentity): void $this->uid = ''; $this->response->deleteCookie( $this->cookieName, - null, - $this->cookieDomain + domain: $this->cookieDomain, ); } @@ -97,7 +80,7 @@ public function getState(): array } - public function setExpiration(?string $expire, bool $clearIdentity): void + public function setExpiration(string|int|null $expire, bool $clearIdentity): void { $this->cookieExpiration = $expire; } @@ -106,7 +89,7 @@ public function setExpiration(?string $expire, bool $clearIdentity): void public function setCookieParameters( ?string $name = null, ?string $domain = null, - ?string $sameSite = null + ?string $sameSite = null, ) { $this->cookieName = $name ?? $this->cookieName; $this->cookieDomain = $domain ?? $this->cookieDomain; diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 87f77efa..1ebe6677 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -22,20 +22,11 @@ final class SessionStorage implements Nette\Security\UserStorage { use Nette\SmartObject; - /** @var string */ - private $namespace = ''; - - /** @var Session */ - private $sessionHandler; - - /** @var SessionSection */ - private $sessionSection; - - /** @var ?int */ - private $expireTime; - - /** @var bool */ - private $expireIdentity = false; + private string $namespace = ''; + private Session $sessionHandler; + private ?SessionSection $sessionSection = null; + private ?int $expireTime = null; + private bool $expireIdentity = false; public function __construct(Session $sessionHandler) @@ -82,7 +73,7 @@ public function getState(): array } - public function setExpiration(?string $time, bool $clearIdentity = false): void + public function setExpiration(string|int|null $time, bool $clearIdentity = false): void { $this->expireTime = $time ? (int) Nette\Utils\DateTime::from($time)->format('U') : null; $this->expireIdentity = $clearIdentity; @@ -110,9 +101,8 @@ private function setupExpiration(): void /** * Changes namespace; allows more users to share a session. - * @return static */ - public function setNamespace(string $namespace) + public function setNamespace(string $namespace): static { if ($this->namespace !== $namespace) { $this->namespace = $namespace; diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 488757c1..123d86c1 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -20,8 +20,7 @@ class UserPanel implements Tracy\IBarPanel { use Nette\SmartObject; - /** @var Nette\Security\User */ - private $user; + private Nette\Security\User $user; public function __construct(Nette\Security\User $user) diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php index fc40ea3c..a08ee055 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -13,8 +13,15 @@ /** * Performs authentication. */ -interface Authenticator extends IAuthenticator +interface Authenticator { + /** Exception error code */ + public const + IDENTITY_NOT_FOUND = 1, + INVALID_CREDENTIAL = 2, + FAILURE = 3, + NOT_APPROVED = 4; + /** * Performs an authentication. * @throws AuthenticationException diff --git a/src/Security/Authorizator.php b/src/Security/Authorizator.php index 085a8825..66586c1f 100644 --- a/src/Security/Authorizator.php +++ b/src/Security/Authorizator.php @@ -27,11 +27,8 @@ interface Authorizator /** * Performs a role-based authorization. - * @param string|null $role - * @param string|null $resource - * @param string|null $privilege */ - function isAllowed($role, $resource, $privilege): bool; + function isAllowed(?string $role, ?string $resource, ?string $privilege): bool; } diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php deleted file mode 100644 index 4d845ad6..00000000 --- a/src/Security/IAuthenticator.php +++ /dev/null @@ -1,37 +0,0 @@ -setId($id); - $this->setRoles((array) $roles); - $this->data = $data instanceof \Traversable - ? iterator_to_array($data) - : (array) $data; - } - - - /** - * Sets the ID of user. - * @param string|int $id - * @return static - */ - public function setId($id) - { - if (!is_string($id) && !is_int($id)) { - throw new Nette\InvalidArgumentException('Identity identifier must be string|int, but type "' . gettype($id) . '" given.'); - } - - $this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id; - return $this; - } - - - /** - * Returns the ID of user. - * @return mixed - */ - public function getId() - { - return $this->id; - } - - - /** - * Sets a list of roles that the user is a member of. - * @return static - */ - public function setRoles(array $roles) - { - $this->roles = $roles; - return $this; - } - - - /** - * Returns a list of roles that the user is a member of. - */ - public function getRoles(): array - { - return $this->roles; - } - - - /** - * Returns a user data. - */ - public function getData(): array - { - return $this->data; - } - - - /** - * Sets user data value. - */ - public function __set(string $key, $value): void - { - if ($this->parentIsSet($key)) { - $this->parentSet($key, $value); - - } else { - $this->data[$key] = $value; - } - } - - - /** - * Returns user data value. - * @return mixed - */ - public function &__get(string $key) - { - if ($this->parentIsSet($key)) { - return $this->parentGet($key); - - } else { - return $this->data[$key]; - } - } - - - public function __isset(string $key): bool - { - return isset($this->data[$key]) || $this->parentIsSet($key); - } -} diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 89f512d2..0117b4c0 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -19,21 +19,14 @@ class Passwords { use Nette\SmartObject; - /** @var int|string string since PHP 7.4 */ - private $algo; - - /** @var array */ - private $options; - - /** * Chooses which secure algorithm is used for hashing and how to configure it. * @see https://php.net/manual/en/password.constants.php */ - public function __construct($algo = PASSWORD_DEFAULT, array $options = []) - { - $this->algo = $algo; - $this->options = $options; + public function __construct( + private string $algo = PASSWORD_DEFAULT, + private array $options = [], + ) { } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index b825edd6..85b97cc8 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -21,14 +21,14 @@ class Permission implements Authorizator { use Nette\SmartObject; - /** @var array Role storage */ - private $roles = []; + /** Role storage */ + private array $roles = []; - /** @var array Resource storage */ - private $resources = []; + /** Resource storage */ + private array $resources = []; - /** @var array Access Control List rules; whitelist (deny everything to all) by default */ - private $rules = [ + /** Access Control List rules; whitelist (deny everything to all) by default */ + private array $rules = [ 'allResources' => [ 'allRoles' => [ 'allPrivileges' => [ @@ -42,9 +42,7 @@ class Permission implements Authorizator 'byResource' => [], ]; - /** @var mixed */ - private $queriedRole; - + private mixed $queriedRole; private $queriedResource; @@ -54,12 +52,10 @@ class Permission implements Authorizator /** * Adds a Role to the list. The most recently added parent * takes precedence over parents that were previously added. - * @param string|array $parents * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException - * @return static */ - public function addRole(string $role, $parents = null) + public function addRole(string $role, string|array|null $parents = null): static { $this->checkRole($role, false); if (isset($this->roles[$role])) { @@ -163,9 +159,8 @@ public function roleInheritsFrom(string $role, string $inherit, bool $onlyParent * Removes the Role from the list. * * @throws Nette\InvalidStateException - * @return static */ - public function removeRole(string $role) + public function removeRole(string $role): static { $this->checkRole($role); @@ -201,10 +196,8 @@ public function removeRole(string $role) /** * Removes all Roles from the list. - * - * @return static */ - public function removeAllRoles() + public function removeAllRoles(): static { $this->roles = []; @@ -230,9 +223,8 @@ public function removeAllRoles() * * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException - * @return static */ - public function addResource(string $resource, ?string $parent = null) + public function addResource(string $resource, ?string $parent = null): static { $this->checkResource($resource, false); @@ -326,9 +318,8 @@ public function resourceInheritsFrom(string $resource, string $inherit, bool $on * Removes a Resource and all of its children. * * @throws Nette\InvalidStateException - * @return static */ - public function removeResource(string $resource) + public function removeResource(string $resource): static { $this->checkResource($resource); @@ -358,9 +349,8 @@ public function removeResource(string $resource) /** * Removes all Resources. - * @return static */ - public function removeAllResources() + public function removeAllResources(): static { foreach ($this->resources as $resource => $foo) { foreach ($this->rules['byResource'] as $resourceCurrent => $rules) { @@ -385,14 +375,14 @@ public function removeAllResources() * @param string|string[]|null $roles * @param string|string[]|null $resources * @param string|string[]|null $privileges - * @return static */ public function allow( $roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, - ?callable $assertion = null - ) { + ?callable $assertion = null, + ): static + { $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion); return $this; } @@ -405,14 +395,14 @@ public function allow( * @param string|string[]|null $roles * @param string|string[]|null $resources * @param string|string[]|null $privileges - * @return static */ public function deny( $roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, - ?callable $assertion = null - ) { + ?callable $assertion = null, + ): static + { $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion); return $this; } @@ -424,9 +414,8 @@ public function deny( * @param string|string[]|null $roles * @param string|string[]|null $resources * @param string|string[]|null $privileges - * @return static */ - public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) + public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL): static { $this->setRule(false, self::ALLOW, $roles, $resources, $privileges); return $this; @@ -439,9 +428,8 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile * @param string|string[]|null $roles * @param string|string[]|null $resources * @param string|string[]|null $privileges - * @return static */ - public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) + public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL): static { $this->setRule(false, self::DENY, $roles, $resources, $privileges); return $this; @@ -454,9 +442,15 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * @param string|string[]|null $resources * @param string|string[]|null $privileges * @throws Nette\InvalidStateException - * @return static */ - protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privileges, ?callable $assertion = null) + protected function setRule( + bool $toAdd, + bool $type, + $roles, + $resources, + $privileges, + ?callable $assertion = null, + ): static { // ensure that all specified Roles exist; normalize input to array of Roles or null if ($roles === self::ALL) { @@ -566,12 +560,13 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile * and its respective parents are checked similarly before the lower-priority parents of * the Role are checked. * - * @param string|Role|null $role - * @param string|Nette\Security\Resource|null $resource - * @param string|null $privilege * @throws Nette\InvalidStateException */ - public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool + public function isAllowed( + string|Role|null $role = self::ALL, + string|Nette\Security\Resource|null $resource = self::ALL, + ?string $privilege = self::ALL, + ): bool { $this->queriedRole = $role; if ($role !== self::ALL) { @@ -628,9 +623,8 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = /** * Returns real currently queried Role. Use by assertion. - * @return mixed */ - public function getQueriedRole() + public function getQueriedRole(): mixed { return $this->queriedRole; } @@ -638,9 +632,8 @@ public function getQueriedRole() /** * Returns real currently queried Resource. Use by assertion. - * @return mixed */ - public function getQueriedResource() + public function getQueriedResource(): mixed { return $this->queriedResource; } @@ -655,7 +648,7 @@ public function getQueriedResource() * @param bool $all (true) or one? * @return mixed null if no applicable rule is found, otherwise returns ALLOW or DENY */ - private function searchRolePrivileges(bool $all, $role, $resource, $privilege) + private function searchRolePrivileges(bool $all, $role, $resource, $privilege): mixed { $dfs = [ 'visited' => [], @@ -700,12 +693,9 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) /** * Returns the rule type associated with the specified Resource, Role, and privilege. - * @param string|null $resource - * @param string|null $role - * @param string|null $privilege * @return bool|null null if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY */ - private function getRuleType($resource, $role, $privilege): ?bool + private function getRuleType(?string $resource, ?string $role, ?string $privilege): ?bool { if (!$rules = $this->getRules($resource, $role)) { return null; @@ -742,10 +732,8 @@ private function getRuleType($resource, $role, $privilege): ?bool /** * Returns the rules associated with a Resource and a Role, or null if no such rules exist. * If the $create parameter is true, then a rule set is first created and then returned to the caller. - * @param string|null $resource - * @param string|null $role */ - private function &getRules($resource, $role, bool $create = false): ?array + private function &getRules(?string $resource, ?string $role, bool $create = false): ?array { $null = null; if ($resource === self::ALL) { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index df8d0bad..2af590b0 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -19,26 +19,17 @@ class SimpleAuthenticator implements Authenticator { use Nette\SmartObject; - /** @var array */ - private $passwords; - - /** @var array */ - private $roles; - - /** @var array */ - private $data; - - /** * @param array $passwords list of pairs username => password * @param array $roles list of pairs username => role[] * @param array $data list of pairs username => mixed[] */ - public function __construct(array $passwords, array $roles = [], array $data = []) - { - $this->passwords = $passwords; - $this->roles = $roles; - $this->data = $data; + public function __construct( + private array $passwords, + private array $roles = [], + private array $data = [], + private ?Passwords $verifier = null, + ) { } @@ -65,6 +56,9 @@ public function authenticate(string $username, string $password): IIdentity protected function verifyPassword(string $password, string $passOrHash): bool { + if (preg_match('~\$.{50,}~A', $passOrHash)) { + return $this->verifier->verify($password, $passOrHash); + } return $password === $passOrHash; } } diff --git a/src/Security/SimpleIdentity.php b/src/Security/SimpleIdentity.php index 279aadd0..3c5ab1b1 100644 --- a/src/Security/SimpleIdentity.php +++ b/src/Security/SimpleIdentity.php @@ -9,10 +9,133 @@ namespace Nette\Security; +use Nette; + /** * Default implementation of IIdentity. + * @property string|int $id + * @property array $roles + * @property array $data */ -class SimpleIdentity extends Identity +class SimpleIdentity implements IIdentity { + use Nette\SmartObject { + __get as private parentGet; + __set as private parentSet; + __isset as private parentIsSet; + } + + private string|int $id; + private array $roles; + private array $data; + + + public function __construct($id, $roles = null, ?iterable $data = null) + { + $this->setId($id); + $this->setRoles((array) $roles); + $this->data = $data instanceof \Traversable + ? iterator_to_array($data) + : (array) $data; + } + + + /** + * Sets the ID of user. + */ + public function setId(string|int $id): static + { + $this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id; + return $this; + } + + + /** + * Returns the ID of user. + */ + public function getId(): string|int + { + return $this->id; + } + + + /** + * Sets a list of roles that the user is a member of. + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + return $this; + } + + + /** + * Returns a list of roles that the user is a member of. + */ + public function getRoles(): array + { + return $this->roles; + } + + + /** + * Returns a user data. + */ + public function getData(): array + { + return $this->data; + } + + + /** + * Sets user data value. + */ + public function __set(string $key, $value): void + { + if ($this->parentIsSet($key)) { + $this->parentSet($key, $value); + + } else { + $this->data[$key] = $value; + } + } + + + /** + * Returns user data value. + */ + public function &__get(string $key): mixed + { + if ($this->parentIsSet($key)) { + return $this->parentGet($key); + + } else { + return $this->data[$key]; + } + } + + + public function __isset(string $key): bool + { + return isset($this->data[$key]) || $this->parentIsSet($key); + } + + + public function __serialize(): array + { + return [ + 'id' => $this->id, + 'roles' => $this->roles, + 'data' => $this->data, + ]; + } + + + public function __unserialize(array $data): void + { + $this->id = $data['id'] ?? $data["\00Nette\\Security\\Identity\00id"] ?? 0; + $this->roles = $data['roles'] ?? $data["\00Nette\\Security\\Identity\00roles"] ?? []; + $this->data = $data['data'] ?? $data["\00Nette\\Security\\Identity\00data"] ?? []; + } } diff --git a/src/Security/User.php b/src/Security/User.php index 951e599f..e16d8b97 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -16,13 +16,13 @@ /** * User authentication and authorization. * - * @property-read bool $loggedIn - * @property-read IIdentity $identity - * @property-read mixed $id - * @property-read array $roles - * @property-read int $logoutReason - * @property IAuthenticator $authenticator - * @property Authorizator $authorizator + * @property-deprecated bool $loggedIn + * @property-deprecated IIdentity $identity + * @property-deprecated string|int $id + * @property-deprecated array $roles + * @property-deprecated int $logoutReason + * @property-deprecated Authenticator $authenticator + * @property-deprecated Authorizator $authorizator */ class User { @@ -30,65 +30,40 @@ class User /** @deprecated */ public const - MANUAL = IUserStorage::MANUAL, - INACTIVITY = IUserStorage::INACTIVITY; + MANUAL = UserStorage::LOGOUT_MANUAL, + INACTIVITY = UserStorage::LOGOUT_INACTIVITY; /** Log-out reason */ public const LOGOUT_MANUAL = UserStorage::LOGOUT_MANUAL, LOGOUT_INACTIVITY = UserStorage::LOGOUT_INACTIVITY; - /** @var string default role for unauthenticated user */ - public $guestRole = 'guest'; + /** default role for unauthenticated user */ + public string $guestRole = 'guest'; - /** @var string default role for authenticated user without own identity */ - public $authenticatedRole = 'authenticated'; + /** default role for authenticated user without own identity */ + public string $authenticatedRole = 'authenticated'; /** @var callable[] function (User $sender): void; Occurs when the user is successfully logged in */ - public $onLoggedIn = []; + public iterable $onLoggedIn = []; /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ - public $onLoggedOut = []; + public iterable $onLoggedOut = []; - /** @var UserStorage|IUserStorage Session storage for current user */ - private $storage; - - /** @var IAuthenticator|null */ - private $authenticator; - - /** @var Authorizator|null */ - private $authorizator; - - /** @var IIdentity|null */ - private $identity; - - /** @var bool|null */ - private $authenticated; - - /** @var int|null */ - private $logoutReason; + private ?IIdentity $identity = null; + private ?bool $authenticated = null; + private ?int $logoutReason = null; public function __construct( - ?IUserStorage $legacyStorage = null, - ?IAuthenticator $authenticator = null, - ?Authorizator $authorizator = null, - ?UserStorage $storage = null + private UserStorage $storage, + private ?Authenticator $authenticator = null, + private ?Authorizator $authorizator = null, ) { - $this->storage = $storage ?? $legacyStorage; // back compatibility - if (!$this->storage) { - throw new Nette\InvalidStateException('UserStorage has not been set.'); - } - - $this->authenticator = $authenticator; - $this->authorizator = $authorizator; } - /** - * @return UserStorage|IUserStorage - */ - final public function getStorage() + final public function getStorage(): UserStorage { return $this->storage; } @@ -102,28 +77,21 @@ final public function getStorage() * @param string|IIdentity $user name or Identity * @throws AuthenticationException if authentication was not successful */ - public function login($user, ?string $password = null): void + public function login(string|IIdentity $user, ?string $password = null): void { $this->logout(true); if ($user instanceof IIdentity) { $this->identity = $user; } else { $authenticator = $this->getAuthenticator(); - $this->identity = $authenticator instanceof Authenticator - ? $authenticator->authenticate(...func_get_args()) - : $authenticator->authenticate(func_get_args()); + $this->identity = $authenticator->authenticate(...func_get_args()); } $id = $this->authenticator instanceof IdentityHandler ? $this->authenticator->sleepIdentity($this->identity) : $this->identity; - if ($this->storage instanceof UserStorage) { - $this->storage->saveAuthentication($id); - } else { - $this->storage->setIdentity($id); - $this->storage->setAuthenticated(true); - } + $this->storage->saveAuthentication($id); $this->authenticated = true; $this->logoutReason = null; Arrays::invoke($this->onLoggedIn, $this); @@ -136,16 +104,7 @@ public function login($user, ?string $password = null): void final public function logout(bool $clearIdentity = false): void { $logged = $this->isLoggedIn(); - - if ($this->storage instanceof UserStorage) { - $this->storage->clearAuthentication($clearIdentity); - } else { - $this->storage->setAuthenticated(false); - if ($clearIdentity) { - $this->storage->setIdentity(null); - } - } - + $this->storage->clearAuthentication($clearIdentity); $this->authenticated = false; $this->logoutReason = self::MANUAL; if ($logged) { @@ -184,17 +143,11 @@ final public function getIdentity(): ?IIdentity private function getStoredData(): void { - if ($this->storage instanceof UserStorage) { - (function (bool $state, ?IIdentity $id, ?int $reason) use (&$identity) { - $identity = $id; - $this->authenticated = $state; - $this->logoutReason = $reason; - })(...$this->storage->getState()); - } else { - $identity = $this->storage->getIdentity(); - $this->authenticated = $this->storage->isAuthenticated(); - $this->logoutReason = $this->storage->getLogoutReason(); - } + (function (bool $state, ?IIdentity $id, ?int $reason) use (&$identity) { + $identity = $id; + $this->authenticated = $state; + $this->logoutReason = $reason; + })(...$this->storage->getState()); $this->identity = $identity && $this->authenticator instanceof IdentityHandler ? $this->authenticator->wakeupIdentity($identity) @@ -205,9 +158,8 @@ private function getStoredData(): void /** * Returns current user ID, if any. - * @return mixed */ - public function getId() + public function getId(): string|int|null { $identity = $this->getIdentity(); return $identity ? $identity->getId() : null; @@ -222,9 +174,8 @@ final public function refreshStorage(): void /** * Sets authentication handler. - * @return static */ - public function setAuthenticator(IAuthenticator $handler) + public function setAuthenticator(Authenticator $handler): static { $this->authenticator = $handler; return $this; @@ -234,14 +185,9 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. */ - final public function getAuthenticator(): ?IAuthenticator + final public function getAuthenticator(): Authenticator { - if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthenticatorIfExists()', E_USER_DEPRECATED); - $throw = func_get_arg(0); - } - - if (($throw ?? true) && !$this->authenticator) { + if (!$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); } @@ -252,7 +198,7 @@ final public function getAuthenticator(): ?IAuthenticator /** * Returns authentication handler. */ - final public function getAuthenticatorIfExists(): ?IAuthenticator + final public function getAuthenticatorIfExists(): ?Authenticator { return $this->authenticator; } @@ -267,25 +213,10 @@ final public function hasAuthenticator(): bool /** * Enables log out after inactivity (like '20 minutes'). - * @param string|null $expire - * @param int|bool $clearIdentity - * @return static */ - public function setExpiration($expire, $clearIdentity = null) + public function setExpiration(?string $expire, bool $clearIdentity = false) { - if ($expire !== null && !is_string($expire)) { - trigger_error("Expiration should be a string like '20 minutes' etc.", E_USER_DEPRECATED); - } - - if (func_num_args() > 2) { - $clearIdentity = $clearIdentity || func_get_arg(2); - trigger_error(__METHOD__ . '() third parameter is deprecated, use second one: setExpiration($time, true|false)', E_USER_DEPRECATED); - } - - $arg = $this->storage instanceof UserStorage - ? (bool) $clearIdentity - : ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); - $this->storage->setExpiration($expire, $arg); + $this->storage->setExpiration($expire, $clearIdentity); return $this; } @@ -349,9 +280,8 @@ public function isAllowed($resource = Authorizator::ALL, $privilege = Authorizat /** * Sets authorization handler. - * @return static */ - public function setAuthorizator(Authorizator $handler) + public function setAuthorizator(Authorizator $handler): static { $this->authorizator = $handler; return $this; @@ -361,14 +291,9 @@ public function setAuthorizator(Authorizator $handler) /** * Returns current authorization handler. */ - final public function getAuthorizator(): ?Authorizator + final public function getAuthorizator(): Authorizator { - if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthorizatorIfExists()', E_USER_DEPRECATED); - $throw = func_get_arg(0); - } - - if (($throw ?? true) && !$this->authorizator) { + if (!$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); } diff --git a/src/Security/UserStorage.php b/src/Security/UserStorage.php index 7abedac4..4e3d470d 100644 --- a/src/Security/UserStorage.php +++ b/src/Security/UserStorage.php @@ -39,5 +39,5 @@ function getState(): array; /** * Enables log out from the persistent storage after inactivity (like '20 minutes'). */ - function setExpiration(?string $expire, bool $clearIdentity): void; + function setExpiration(string|int|null $expire, bool $clearIdentity): void; } diff --git a/src/compatibility-intf.php b/src/compatibility-intf.php new file mode 100644 index 00000000..ebfd19d1 --- /dev/null +++ b/src/compatibility-intf.php @@ -0,0 +1,37 @@ +compile()); $container = new Container; Assert::type(Nette\Bridges\SecurityHttp\SessionStorage::class, $container->getService('security.userStorage')); -Assert::type(Nette\Http\UserStorage::class, $container->getService('security.legacyUserStorage')); Assert::type(Nette\Security\User::class, $container->getService('security.user')); // aliases diff --git a/tests/Security.Http/CookieStorage.authentication.phpt b/tests/Security.Http/CookieStorage.authentication.phpt index 8debb1ae..f88f01a6 100644 --- a/tests/Security.Http/CookieStorage.authentication.phpt +++ b/tests/Security.Http/CookieStorage.authentication.phpt @@ -12,9 +12,10 @@ require __DIR__ . '/../bootstrap.php'; $request = new Nette\Http\Request(new Nette\Http\UrlScript); $response = Mockery::mock(Nette\Http\IResponse::class); $storage = new CookieStorage($request, $response); -Assert::exception(function () use ($storage) { - $storage->saveAuthentication(new SimpleIdentity('short')); -}, LogicException::class); +Assert::exception( + fn() => $storage->saveAuthentication(new SimpleIdentity('short')), + LogicException::class, +); // correct id $id = '123456789123456'; diff --git a/tests/Security.Http/CookieStorage.getState.phpt b/tests/Security.Http/CookieStorage.getState.phpt index 148a0102..ba4d0961 100644 --- a/tests/Security.Http/CookieStorage.getState.phpt +++ b/tests/Security.Http/CookieStorage.getState.phpt @@ -15,18 +15,18 @@ $storage = new CookieStorage($request, $response); Assert::same([false, null, null], $storage->getState()); // short id -$request = new Nette\Http\Request(new Nette\Http\UrlScript, [], [], ['userid' => 'short']); +$request = new Nette\Http\Request(new Nette\Http\UrlScript, cookies: ['userid' => 'short']); $storage = new CookieStorage($request, $response); Assert::same([false, null, null], $storage->getState()); // correct id $id = '123456789123456'; -$request = new Nette\Http\Request(new Nette\Http\UrlScript, [], [], ['userid' => $id]); +$request = new Nette\Http\Request(new Nette\Http\UrlScript, cookies: ['userid' => $id]); $storage = new CookieStorage($request, $response); Assert::equal([true, new SimpleIdentity($id), null], $storage->getState()); // custom cookie -$request = new Nette\Http\Request(new Nette\Http\UrlScript, [], [], ['foo' => $id]); +$request = new Nette\Http\Request(new Nette\Http\UrlScript, cookies: ['foo' => $id]); $storage = new CookieStorage($request, $response); $storage->setCookieParameters('foo'); Assert::equal([true, new SimpleIdentity($id), null], $storage->getState()); diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index 30ea0d6b..8cc91415 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -1,12 +1,12 @@ 'John']); + $id = new SimpleIdentity(12, 'admin', ['name' => 'John']); Assert::same(12, $id->getId()); Assert::same(12, $id->id); @@ -27,10 +27,10 @@ test('', function () { test('', function () { - $id = new Identity('12'); + $id = new SimpleIdentity('12'); Assert::same(12, $id->getId()); - $id = new Identity('12345678901234567890'); + $id = new SimpleIdentity('12345678901234567890'); Assert::same('12345678901234567890', $id->getId()); }); diff --git a/tests/Security/MockUserStorage.legacy.php b/tests/Security/MockUserStorage.legacy.php deleted file mode 100644 index d4b134be..00000000 --- a/tests/Security/MockUserStorage.legacy.php +++ /dev/null @@ -1,45 +0,0 @@ -auth = $state; - } - - - public function isAuthenticated(): bool - { - return $this->auth; - } - - - public function setIdentity(?Nette\Security\IIdentity $identity = null) - { - $this->identity = $identity; - } - - - public function getIdentity(): ?Nette\Security\IIdentity - { - return $this->identity; - } - - - public function setExpiration(?string $time, int $flags = 0) - { - } - - - public function getLogoutReason(): ?int - { - return null; - } -} diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index a162ee01..8b59bcdf 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -29,7 +29,7 @@ public function getState(): array } - public function setExpiration(?string $expire, bool $clearIdentity): void + public function setExpiration(string|int|null $expire, bool $clearIdentity): void { } } diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index c8b7d376..a6acbcd1 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -14,20 +14,23 @@ require __DIR__ . '/../bootstrap.php'; Assert::truthy( - preg_match('#^\$.{50,}\z#', (new Passwords)->hash('my-password')) + preg_match('#^\$.{50,}\z#', (new Passwords)->hash('my-password')), ); Assert::truthy( - preg_match('#^\$2y\$05\$.{53}\z#', (new Passwords(PASSWORD_BCRYPT, ['cost' => 5]))->hash('dg')) + preg_match('#^\$2y\$05\$.{53}\z#', (new Passwords(PASSWORD_BCRYPT, ['cost' => 5]))->hash('dg')), ); $hash = (new Passwords(PASSWORD_BCRYPT))->hash('dg'); Assert::same($hash, crypt('dg', $hash)); -Assert::exception(function () { - (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'); -}, PHP_VERSION_ID < 80000 ? Nette\InvalidStateException::class : ValueError::class); +Assert::exception( + fn() => (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'), + PHP_VERSION_ID < 80000 ? Nette\InvalidStateException::class : ValueError::class, +); -Assert::exception(function () { - (new Passwords)->hash(''); -}, Nette\InvalidArgumentException::class, 'Password can not be empty.'); +Assert::exception( + fn() => (new Passwords)->hash(''), + Nette\InvalidArgumentException::class, + 'Password can not be empty.', +); diff --git a/tests/Security/Permission.IsAllowedNonExistent.phpt b/tests/Security/Permission.IsAllowedNonExistent.phpt index 260303c6..437e5085 100644 --- a/tests/Security/Permission.IsAllowedNonExistent.phpt +++ b/tests/Security/Permission.IsAllowedNonExistent.phpt @@ -13,12 +13,16 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - $acl = new Permission; - $acl->isAllowed('nonexistent'); -}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); - -Assert::exception(function () { - $acl = new Permission; - $acl->isAllowed(null, 'nonexistent'); -}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); +$acl = new Permission; +Assert::exception( + fn() => $acl->isAllowed('nonexistent'), + Nette\InvalidStateException::class, + "Role 'nonexistent' does not exist.", +); + +$acl = new Permission; +Assert::exception( + fn() => $acl->isAllowed(null, 'nonexistent'), + Nette\InvalidStateException::class, + "Resource 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt index 9bdf4678..0351edcf 100644 --- a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt @@ -14,6 +14,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function () use ($acl) { - $acl->addResource('area', 'nonexistent'); -}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->addResource('area', 'nonexistent'), + Nette\InvalidStateException::class, + "Resource 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.ResourceDuplicate.phpt b/tests/Security/Permission.ResourceDuplicate.phpt index 25788827..983e8308 100644 --- a/tests/Security/Permission.ResourceDuplicate.phpt +++ b/tests/Security/Permission.ResourceDuplicate.phpt @@ -13,8 +13,10 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function () { - $acl = new Permission; - $acl->addResource('area'); - $acl->addResource('area'); -}, Nette\InvalidStateException::class, "Resource 'area' already exists in the list."); +$acl = new Permission; +$acl->addResource('area'); +Assert::exception( + fn() => $acl->addResource('area'), + Nette\InvalidStateException::class, + "Resource 'area' already exists in the list.", +); diff --git a/tests/Security/Permission.ResourceInheritsNonExistent.phpt b/tests/Security/Permission.ResourceInheritsNonExistent.phpt index 5503d6e6..5f1c88cf 100644 --- a/tests/Security/Permission.ResourceInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceInheritsNonExistent.phpt @@ -15,10 +15,14 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); -Assert::exception(function () use ($acl) { - $acl->resourceInheritsFrom('nonexistent', 'area'); -}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); - -Assert::exception(function () use ($acl) { - $acl->resourceInheritsFrom('area', 'nonexistent'); -}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->resourceInheritsFrom('nonexistent', 'area'), + Nette\InvalidStateException::class, + "Resource 'nonexistent' does not exist.", +); + +Assert::exception( + fn() => $acl->resourceInheritsFrom('area', 'nonexistent'), + Nette\InvalidStateException::class, + "Resource 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt index 2b981b95..b3c79077 100644 --- a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt @@ -14,6 +14,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function () use ($acl) { - $acl->removeResource('nonexistent'); -}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->removeResource('nonexistent'), + Nette\InvalidStateException::class, + "Resource 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt index d2305cd5..4b91c0ca 100644 --- a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt @@ -14,6 +14,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function () use ($acl) { - $acl->addRole('guest', 'nonexistent'); -}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->addRole('guest', 'nonexistent'), + Nette\InvalidStateException::class, + "Role 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.RoleRegistryDuplicate.phpt b/tests/Security/Permission.RoleRegistryDuplicate.phpt index 9389568d..7eafc570 100644 --- a/tests/Security/Permission.RoleRegistryDuplicate.phpt +++ b/tests/Security/Permission.RoleRegistryDuplicate.phpt @@ -14,7 +14,9 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function () use ($acl) { - $acl->addRole('guest'); - $acl->addRole('guest'); -}, Nette\InvalidStateException::class, "Role 'guest' already exists in the list."); +$acl->addRole('guest'); +Assert::exception( + fn() => $acl->addRole('guest'), + Nette\InvalidStateException::class, + "Role 'guest' already exists in the list.", +); diff --git a/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt b/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt index 8001493d..3840c06e 100644 --- a/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt @@ -15,10 +15,14 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); -Assert::exception(function () use ($acl) { - $acl->roleInheritsFrom('nonexistent', 'guest'); -}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); - -Assert::exception(function () use ($acl) { - $acl->roleInheritsFrom('guest', 'nonexistent'); -}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->roleInheritsFrom('nonexistent', 'guest'), + Nette\InvalidStateException::class, + "Role 'nonexistent' does not exist.", +); + +Assert::exception( + fn() => $acl->roleInheritsFrom('guest', 'nonexistent'), + Nette\InvalidStateException::class, + "Role 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt index cbc0e6a5..e49801a1 100644 --- a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt @@ -14,6 +14,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function () use ($acl) { - $acl->removeRole('nonexistent'); -}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); +Assert::exception( + fn() => $acl->removeRole('nonexistent'), + Nette\InvalidStateException::class, + "Role 'nonexistent' does not exist.", +); diff --git a/tests/Security/Permission.RuleRoleRemove.phpt b/tests/Security/Permission.RuleRoleRemove.phpt index 39f76f0f..a4a2b13a 100644 --- a/tests/Security/Permission.RuleRoleRemove.phpt +++ b/tests/Security/Permission.RuleRoleRemove.phpt @@ -18,9 +18,11 @@ $acl->addRole('guest'); $acl->allow('guest'); Assert::true($acl->isAllowed('guest')); $acl->removeRole('guest'); -Assert::exception(function () use ($acl) { - $acl->isAllowed('guest'); -}, Nette\InvalidStateException::class, "Role 'guest' does not exist."); +Assert::exception( + fn() => $acl->isAllowed('guest'), + Nette\InvalidStateException::class, + "Role 'guest' does not exist.", +); $acl->addRole('guest'); Assert::false($acl->isAllowed('guest')); diff --git a/tests/Security/Permission.RuleRoleRemoveAll.phpt b/tests/Security/Permission.RuleRoleRemoveAll.phpt index 245caeca..d6b6a436 100644 --- a/tests/Security/Permission.RuleRoleRemoveAll.phpt +++ b/tests/Security/Permission.RuleRoleRemoveAll.phpt @@ -18,9 +18,11 @@ $acl->addRole('guest'); $acl->allow('guest'); Assert::true($acl->isAllowed('guest')); $acl->removeAllRoles(); -Assert::exception(function () use ($acl) { - $acl->isAllowed('guest'); -}, Nette\InvalidStateException::class, "Role 'guest' does not exist."); +Assert::exception( + fn() => $acl->isAllowed('guest'), + Nette\InvalidStateException::class, + "Role 'guest' does not exist.", +); $acl->addRole('guest'); Assert::false($acl->isAllowed('guest')); diff --git a/tests/Security/Permission.RulesResourceRemove.phpt b/tests/Security/Permission.RulesResourceRemove.phpt index ee1b902e..c51cc11b 100644 --- a/tests/Security/Permission.RulesResourceRemove.phpt +++ b/tests/Security/Permission.RulesResourceRemove.phpt @@ -18,9 +18,11 @@ $acl->addResource('area'); $acl->allow(null, 'area'); Assert::true($acl->isAllowed(null, 'area')); $acl->removeResource('area'); -Assert::exception(function () use ($acl) { - $acl->isAllowed(null, 'area'); -}, Nette\InvalidStateException::class, "Resource 'area' does not exist."); +Assert::exception( + fn() => $acl->isAllowed(null, 'area'), + Nette\InvalidStateException::class, + "Resource 'area' does not exist.", +); $acl->addResource('area'); Assert::false($acl->isAllowed(null, 'area')); diff --git a/tests/Security/Permission.RulesResourceRemoveAll.phpt b/tests/Security/Permission.RulesResourceRemoveAll.phpt index c346c096..4f14e870 100644 --- a/tests/Security/Permission.RulesResourceRemoveAll.phpt +++ b/tests/Security/Permission.RulesResourceRemoveAll.phpt @@ -18,9 +18,11 @@ $acl->addResource('area'); $acl->allow(null, 'area'); Assert::true($acl->isAllowed(null, 'area')); $acl->removeAllResources(); -Assert::exception(function () use ($acl) { - $acl->isAllowed(null, 'area'); -}, Nette\InvalidStateException::class, "Resource 'area' does not exist."); +Assert::exception( + fn() => $acl->isAllowed(null, 'area'), + Nette\InvalidStateException::class, + "Resource 'area' does not exist.", +); $acl->addResource('area'); Assert::false($acl->isAllowed(null, 'area')); diff --git a/tests/Security/SimpleAuthenticator.phpt b/tests/Security/SimpleAuthenticator.phpt index a2c2370f..a8ebded2 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -6,6 +6,7 @@ declare(strict_types=1); +use Nette\Security\Passwords; use Nette\Security\SimpleAuthenticator; use Tester\Assert; @@ -14,24 +15,37 @@ require __DIR__ . '/../bootstrap.php'; $users = [ - 'john' => 'password123!', + 'john' => '$2a$12$dliX6LynG/iChDUF7DhKzulN7d3nU.l3/RozE1MmEaxxBWdZXppm2', 'admin' => 'admin', ]; $authenticator = new SimpleAuthenticator($users); -$identity = $authenticator->authenticate('john', 'password123!'); -Assert::type(Nette\Security\IIdentity::class, $identity); -Assert::equal('john', $identity->getId()); - $identity = $authenticator->authenticate('admin', 'admin'); Assert::type(Nette\Security\IIdentity::class, $identity); Assert::equal('admin', $identity->getId()); -Assert::exception(function () use ($authenticator) { - $authenticator->authenticate('admin', 'wrong password'); -}, Nette\Security\AuthenticationException::class, 'Invalid password.'); +Assert::exception( + fn() => $authenticator->authenticate('admin', 'wrong password'), + Nette\Security\AuthenticationException::class, + 'Invalid password.', +); + +Assert::exception( + fn() => $authenticator->authenticate('nobody', 'password'), + Nette\Security\AuthenticationException::class, + "User 'nobody' not found.", +); + + +$authenticator = new SimpleAuthenticator($users, verifier: new Passwords); + +$identity = $authenticator->authenticate('john', 'password123!'); +Assert::type(Nette\Security\IIdentity::class, $identity); +Assert::equal('john', $identity->getId()); -Assert::exception(function () use ($authenticator) { - $authenticator->authenticate('nobody', 'password'); -}, Nette\Security\AuthenticationException::class, "User 'nobody' not found."); +Assert::exception( + fn() => $authenticator->authenticate('john', $users['john']), + Nette\Security\AuthenticationException::class, + 'Invalid password.', +); diff --git a/tests/Security/User.authentication.legacy.phpt b/tests/Security/User.authentication.legacy.phpt deleted file mode 100644 index 8c0e559d..00000000 --- a/tests/Security/User.authentication.legacy.phpt +++ /dev/null @@ -1,117 +0,0 @@ - 0, - 'logout' => 0, -]; - -$user->onLoggedIn[] = function () use ($counter) { - $counter->login++; -}; - -$user->onLoggedOut[] = function () use ($counter) { - $counter->logout++; -}; - - -Assert::false($user->isLoggedIn()); -Assert::null($user->getIdentity()); -Assert::null($user->getId()); - - -// authenticate -Assert::exception(function () use ($user) { - // login without handler - $user->login('jane', ''); -}, Nette\InvalidStateException::class, 'Authenticator has not been set.'); - -$handler = new Authenticator; -$user->setAuthenticator($handler); - -Assert::exception(function () use ($user) { - // login as jane - $user->login('jane', ''); -}, Nette\Security\AuthenticationException::class, 'Unknown user'); - -Assert::exception(function () use ($user) { - // login as john - $user->login('john', ''); -}, Nette\Security\AuthenticationException::class, 'Password not match'); - -// login as john#2 -$user->login('john', 'xxx'); -Assert::same(1, $counter->login); -Assert::true($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); -Assert::same('John Doe', $user->getId()); - -// login as john#3 -$user->logout(true); -Assert::same(1, $counter->logout); -$user->login(new Identity('John Doe', 'admin')); -Assert::same(2, $counter->login); -Assert::true($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); - - -// log out -// logging out... -$user->logout(false); -Assert::same(2, $counter->logout); - -Assert::false($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); - - -// logging out and clearing identity... -$user->logout(true); -Assert::same(2, $counter->logout); // not logged in -> logout event not triggered - -Assert::false($user->isLoggedIn()); -Assert::null($user->getIdentity()); - - -// namespace -// login as john#2? -$user->login('john', 'xxx'); -Assert::same(3, $counter->login); -Assert::true($user->isLoggedIn()); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index b66f7f1c..0314d2a5 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -36,7 +36,7 @@ class Authenticator implements Nette\Security\Authenticator } -$user = new Nette\Security\User(null, null, null, new MockUserStorage); +$user = new Nette\Security\User(new MockUserStorage); $counter = (object) [ 'login' => 0, @@ -58,23 +58,26 @@ Assert::null($user->getId()); // authenticate -Assert::exception(function () use ($user) { - // login without handler - $user->login('jane', ''); -}, Nette\InvalidStateException::class, 'Authenticator has not been set.'); +Assert::exception( + fn() => $user->login('jane', ''), + Nette\InvalidStateException::class, + 'Authenticator has not been set.', +); $handler = new Authenticator; $user->setAuthenticator($handler); -Assert::exception(function () use ($user) { - // login as jane - $user->login('jane', ''); -}, Nette\Security\AuthenticationException::class, 'Unknown user'); - -Assert::exception(function () use ($user) { - // login as john - $user->login('john', ''); -}, Nette\Security\AuthenticationException::class, 'Password not match'); +Assert::exception( + fn() => $user->login('jane', ''), + Nette\Security\AuthenticationException::class, + 'Unknown user', +); + +Assert::exception( + fn() => $user->login('john', ''), + Nette\Security\AuthenticationException::class, + 'Password not match', +); // login as john#2 $user->login('john', 'xxx'); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 9061096d..c6a18f87 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -42,7 +42,7 @@ class Authorizator implements Nette\Security\Authorizator { public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { - return $role === 'admin' && strpos($resource, 'jany') === false; + return $role === 'admin' && !str_contains($resource, 'jany'); } } @@ -54,7 +54,7 @@ class TesterRole implements Role } } -$user = new Nette\Security\User(null, null, null, new MockUserStorage); +$user = new Nette\Security\User(new MockUserStorage); // guest Assert::false($user->isLoggedIn()); @@ -81,9 +81,11 @@ Assert::false($user->isInRole('guest')); // authorization -Assert::exception(function () use ($user) { - $user->isAllowed('delete_file'); -}, Nette\InvalidStateException::class, 'Authorizator has not been set.'); +Assert::exception( + fn() => $user->isAllowed('delete_file'), + Nette\InvalidStateException::class, + 'Authorizator has not been set.', +); $handler = new Authorizator; $user->setAuthorizator($handler);