From 00c29d93f9f7765df043c127f2b7b8586c503ffe Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Sep 2014 01:09:24 +0200 Subject: [PATCH 001/254] UserPanel: uses live dumps --- src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml index daf804f9..1e8244ac 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml @@ -3,11 +3,11 @@ namespace Nette\Bridges\SecurityTracy; use Nette, - Tracy; + Tracy\Dumper; ?>

isLoggedIn()): ?>Logged inUnlogged

- getIdentity()): echo Tracy\Dumper::toHtml($user->getIdentity()); else: ?>

no identity

+ getIdentity()): echo Dumper::toHtml($user->getIdentity(), array(Dumper::LIVE => TRUE)); else: ?>

no identity

From a42579367cb5be3c0832dbc58a01f340a6815334 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 27 Oct 2014 01:10:07 +0100 Subject: [PATCH 002/254] UserPanel: is rendered only when headers are not sent [Closes #7] --- src/Bridges/SecurityTracy/UserPanel.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 16512504..2396235c 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -34,6 +34,10 @@ public function __construct(Nette\Security\User $user) */ public function getTab() { + if (headers_sent()) { + return; + } + ob_start(); $user = $this->user; require __DIR__ . '/templates/UserPanel.tab.phtml'; From 0fb6ba2c42d17209e1e6bb1f2ee686fb7a3ecfdf Mon Sep 17 00:00:00 2001 From: Smuuf Date: Tue, 28 Oct 2014 14:11:27 +0100 Subject: [PATCH 003/254] Updated/fixed readme.md docs --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f444d1c7..29a17251 100644 --- a/readme.md +++ b/readme.md @@ -263,11 +263,11 @@ $acl->addRole('administrator', 'registered'); // and administrator inherits from Trivial, isn't it? This ensures all the properties of the parents will be inheritted by their children. -Do note the method `getRoleParents()`, which returns an array of all parent roles, and the method `roleIntheritsFrom()`, which checks whether a role extends another. Their usage: +Do note the method `getRoleParents()`, which returns an array of all direct parent roles, and the method `roleIntheritsFrom()`, which checks whether a role extends another. Their usage: ```php $acl->roleInheritsFrom('administrator', 'guest'); // TRUE -$acl->getRoleParents('administrator'); // array('guest', 'registered') +$acl->getRoleParents('administrator'); // array('registered') - only direct parents ``` Now is the right time to define the set of resources that the users may acccess: From 970b180f3979f6ee9291982f12006d1fdae84419 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Nov 2014 02:22:36 +0100 Subject: [PATCH 004/254] travis: code-checker checks whole repository --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d93f0eea..ec15da5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: script: - vendor/bin/tester tests -s - - php code-checker/src/code-checker.php -d src + - php code-checker/src/code-checker.php after_failure: # Print *.actual content From 893dfe752491931f7d5f783293eda46d4a4aaec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20K=C5=99=C3=AD=C5=BE?= Date: Tue, 25 Nov 2014 08:35:06 +0100 Subject: [PATCH 005/254] UserPanel: is rendered when headers are not sent or session is active --- src/Bridges/SecurityTracy/UserPanel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 2396235c..e25f40a5 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -34,7 +34,7 @@ public function __construct(Nette\Security\User $user) */ public function getTab() { - if (headers_sent()) { + if (headers_sent() && !session_id()) { return; } From e10cb7881ccdabfc3d519f58b697a64905ef662f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 12 Jan 2015 11:25:16 +0100 Subject: [PATCH 006/254] travis: php-cgi changed to php --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec15da5c..d67b6911 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: - php: hhvm script: - - vendor/bin/tester tests -s + - vendor/bin/tester tests -s -p php - php code-checker/src/code-checker.php after_failure: @@ -21,4 +21,4 @@ after_failure: before_script: # Install Nette Tester & Code Checker - composer install --no-interaction --dev --prefer-source - - composer create-project nette/code-checker code-checker ~2.2 --no-interaction --prefer-source + - composer create-project nette/code-checker code-checker ~2.3 --no-interaction --prefer-source From cc4f8875af889dce327545003940d1d0198d9660 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 18 Jan 2015 12:49:24 +0100 Subject: [PATCH 007/254] Identity: fixed conversion of big ints [Closes nette/nette#1520] --- src/Security/Identity.php | 2 +- tests/Security/Identity.phpt | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/Security/Identity.phpt diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 0e1705b3..4beaba03 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -51,7 +51,7 @@ public function __construct($id, $roles = NULL, $data = NULL) */ public function setId($id) { - $this->id = is_numeric($id) ? 1 * $id : $id; + $this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id; return $this; } diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt new file mode 100644 index 00000000..62ab3671 --- /dev/null +++ b/tests/Security/Identity.phpt @@ -0,0 +1,33 @@ + 'John')); + + Assert::same(12, $id->getId()); + Assert::same(12, $id->id); + Assert::same(array('admin'), $id->getRoles()); + Assert::same(array('admin'), $id->roles); + Assert::same(array('name' => 'John'), $id->getData()); + Assert::same('John', $id->name); +}); + + +test(function() { + $id = new Identity('12'); + Assert::same(12, $id->getId()); + + + $id = new Identity('12345678901234567890'); + Assert::same('12345678901234567890', $id->getId()); +}); From 4b2fae844cad66b80a73d24fb4d09706df221cb0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 19 Aug 2014 00:17:33 +0200 Subject: [PATCH 008/254] added SecurityExtension --- composer.json | 2 + src/Bridges/SecurityDI/SecurityExtension.php | 96 +++++++++++++++++++ .../SecurityExtension.authenticator.phpt | 56 +++++++++++ tests/Security.DI/SecurityExtension.user.phpt | 30 ++++++ 4 files changed, 184 insertions(+) create mode 100644 src/Bridges/SecurityDI/SecurityExtension.php create mode 100644 tests/Security.DI/SecurityExtension.authenticator.phpt create mode 100644 tests/Security.DI/SecurityExtension.user.phpt diff --git a/composer.json b/composer.json index 4547e66a..993cb4d3 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,8 @@ "nette/utils": "~2.2" }, "require-dev": { + "nette/di": "~2.3", + "nette/http": "~2.3", "nette/tester": "~1.0" }, "conflict": { diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php new file mode 100644 index 00000000..82c6713d --- /dev/null +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -0,0 +1,96 @@ + TRUE, + 'users' => array(), // of [user => password] or [user => ['password' => password, 'roles' => [role]]] + 'roles' => array(), // of [role => parents] + 'resources' => array(), // of [resource => parents] + ); + + /** @var bool */ + private $debugMode; + + + public function __construct($debugMode = FALSE) + { + $this->debugMode = $debugMode; + } + + + public function loadConfiguration() + { + $config = $this->validateConfig($this->defaults); + $container = $this->getContainerBuilder(); + + $container->addDefinition($this->prefix('userStorage')) + ->setClass('Nette\Security\IUserStorage') + ->setFactory('Nette\Http\UserStorage'); + + $user = $container->addDefinition($this->prefix('user')) + ->setClass('Nette\Security\User'); + + if ($this->debugMode && $config['debugger']) { + $user->addSetup('@Tracy\Bar::addPanel', array( + new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel') + )); + } + + if ($config['users']) { + $usersList = $usersRoles = array(); + foreach ($config['users'] as $username => $data) { + $data = is_array($data) ? $data : array('password' => $data); + $this->validateConfig(array('password' => NULL, 'roles' => NULL), $data, $this->prefix("security.users.$username")); + $usersList[$username] = $data['password']; + $usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL; + } + + $container->addDefinition($this->prefix('authenticator')) + ->setClass('Nette\Security\IAuthenticator') + ->setFactory('Nette\Security\SimpleAuthenticator', array($usersList, $usersRoles)); + + if ($this->name === 'security') { + $container->addAlias('nette.authenticator', $this->prefix('authenticator')); + } + } + + if ($config['roles'] || $config['resources']) { + $authorizator = $container->addDefinition($this->prefix('authorizator')) + ->setClass('Nette\Security\IAuthorizator') + ->setFactory('Nette\Security\Permission'); + + foreach ($config['roles'] as $role => $parents) { + $authorizator->addSetup('addRole', array($role, $parents)); + } + foreach ($config['resources'] as $resource => $parents) { + $authorizator->addSetup('addResource', array($resource, $parents)); + } + + if ($this->name === 'security') { + $container->addAlias('nette.authorizator', $this->prefix('authorizator')); + } + } + + if ($this->name === 'security') { + $container->addAlias('user', $this->prefix('user')); + $container->addAlias('nette.userStorage', $this->prefix('userStorage')); + } + } + +} diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt new file mode 100644 index 00000000..46938f13 --- /dev/null +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -0,0 +1,56 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('bar', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +$loader = new Nette\DI\Config\Loader; +$config = $loader->load(Tester\FileMock::create(' +security: + users: + john: john123 + admin: {password: admin123, roles: [admin, user]} + user: {password: user123} + moderator: {password: moderator123, roles: moderator} +', 'neon')); + +eval($compiler->compile($config, 'Container1')); +$container = new Container1; + +$authenticator = $container->getService('security.authenticator'); +Assert::type('Nette\Security\SimpleAuthenticator', $authenticator); +Assert::same($authenticator, $container->getService('nette.authenticator')); + +$userList = array( + 'john' => 'john123', + 'admin' => 'admin123', + 'user' => 'user123', + 'moderator' => 'moderator123', +); +$expectedRoles = array( + 'john' => array(), + 'admin' => array('admin', 'user'), + 'user' => array(), + 'moderator' => array('moderator'), +); + +foreach ($userList as $username => $password) { + $identity = $authenticator->authenticate(array($username, $password)); + Assert::equal($username, $identity->getId()); + Assert::equal($expectedRoles[$username], $identity->getRoles()); +} diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt new file mode 100644 index 00000000..4690919e --- /dev/null +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -0,0 +1,30 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('bar', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +eval($compiler->compile(array(), 'Container1')); +$container = new Container1; + +Assert::type('Nette\Http\UserStorage', $container->getService('security.userStorage')); +Assert::type('Nette\Security\User', $container->getService('security.user')); + +// aliases +Assert::same($container->getService('security.userStorage'), $container->getService('nette.userStorage')); +Assert::same($container->getService('security.user'), $container->getService('user')); From 93a9f7f9e8f4b1c92b816760dc590028c7e84024 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 25 Jan 2015 21:12:51 +0100 Subject: [PATCH 009/254] typos --- src/Security/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index b8d5faae..e96209de 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -37,10 +37,10 @@ class User extends Nette\Object /** @var string default role for authenticated user without own identity */ public $authenticatedRole = 'authenticated'; - /** @var array of function(User $sender); Occurs when the user is successfully logged in */ + /** @var callable[] function(User $sender); Occurs when the user is successfully logged in */ public $onLoggedIn; - /** @var array of function(User $sender); Occurs when the user is logged out */ + /** @var callable[] function(User $sender); Occurs when the user is logged out */ public $onLoggedOut; /** @var IUserStorage Session storage for current user */ From 2efc1aef06782584053cce0553b9767c4abbe5d4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 27 Jan 2015 14:35:56 +0100 Subject: [PATCH 010/254] added contributing.md inspiration https://github.com/necolas/issue-guidelines --- contributing.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 contributing.md diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000..a1cbbd53 --- /dev/null +++ b/contributing.md @@ -0,0 +1,27 @@ +How to contribute & use the issue tracker +========================================= + +The issue tracker is the preferred channel for bug reports, features requests +and submitting pull requests, but please respect the following restrictions: + +* Please **do not** use the issue tracker for personal support requests (use + [Nette forum](http://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)). + +* Please **do not** derail or troll issues. Keep the discussion on topic and + respect the opinions of others. + +* Use the GitHub **issue search** — check if the issue has already been + reported. + +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. + +Nette welcomes **pull requests**. If you'd like to contribute, please take a moment +to [read the guidelines](http://nette.org/en/contributing) in order to make +the contribution process easy and effective for everyone involved. + +Thanks! From c10664961229dcf12a95bd5e4b0f6e6d39495188 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 11 Jan 2012 08:12:19 +0100 Subject: [PATCH 011/254] User: bool parameters replaced with bit masks --- src/Security/User.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index e96209de..961c8439 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -29,7 +29,8 @@ class User extends Nette\Object /** @deprecated */ const MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::INACTIVITY, - BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED; + BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED, + CLEAR_IDENTITY = IUserStorage::CLEAR_IDENTITY; /** @var string default role for unauthenticated user */ public $guestRole = 'guest'; @@ -82,7 +83,7 @@ public function getStorage() */ public function login($id = NULL, $password = NULL) { - $this->logout(TRUE); + $this->logout(IUserStorage::CLEAR_IDENTITY); if (!$id instanceof IIdentity) { $id = $this->getAuthenticator()->authenticate(func_get_args()); } @@ -94,16 +95,16 @@ public function login($id = NULL, $password = NULL) /** * Logs out the user from the current session. - * @param bool clear the identity from persistent storage? + * @param int clear the identity from persistent storage? * @return void */ - public function logout($clearIdentity = FALSE) + public function logout($flags = NULL) { if ($this->isLoggedIn()) { $this->onLoggedOut($this); $this->storage->setAuthenticated(FALSE); } - if ($clearIdentity) { + if ($flags === TRUE || ($flags & IUserStorage::CLEAR_IDENTITY)) { $this->storage->setIdentity(NULL); } } @@ -167,13 +168,13 @@ public function getAuthenticator($need = TRUE) /** * Enables log out after inactivity. * @param string|int|DateTime number of seconds or timestamp - * @param bool log out when the browser is closed? - * @param bool clear the identity from persistent storage? + * @param int log out when the browser is closed? | clear the identity from persistent storage? * @return self */ - public function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE) + public function setExpiration($time, $flags = IUserStorage::BROWSER_CLOSED, $clearIdentity = FALSE) { - $flags = ($whenBrowserIsClosed ? IUserStorage::BROWSER_CLOSED : 0) | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); + $flags = ($flags === TRUE ? IUserStorage::BROWSER_CLOSED : $flags) // back compatibility + | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); $this->storage->setExpiration($time, $flags); return $this; } From 7c8500b9b92783685ef10e57ff3c49c076255e18 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 9 Feb 2015 15:44:21 +0100 Subject: [PATCH 012/254] UserPanel: used vector icon --- src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml index 38a461a1..cbf9afc3 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml @@ -5,8 +5,6 @@ namespace Nette\Bridges\SecurityTracy; use Nette; ?> -isLoggedIn()): ?> -  - -  - + + + From a4ce91375ccc9bbb3f27a384f9f747273011e443 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Feb 2015 21:37:24 +0100 Subject: [PATCH 013/254] opened 2.4-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 993cb4d3..8d0b4c94 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.4-dev" } } } From 5a1abfe2f4af470f4d7ff738caac41df0627c808 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 16 Mar 2015 14:30:51 +0100 Subject: [PATCH 014/254] typo --- src/Security/IUserStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index f1ed7cbc..62c9dec0 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -46,7 +46,7 @@ function setIdentity(IIdentity $identity = NULL); /** * Returns current user identity, if any. - * @return Nette\Security\IIdentity|NULL + * @return IIdentity|NULL */ function getIdentity(); From d42efcfa73dae0509f4b32e77ecbde298e141a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kar=C3=A1sek?= Date: Sat, 28 Feb 2015 20:33:31 +0100 Subject: [PATCH 015/254] readme.md: md5() replaced with Nette\Security\Passwords::verify() --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 29a17251..42db1523 100644 --- a/readme.md +++ b/readme.md @@ -120,7 +120,7 @@ class MyAuthenticator extends Nette\Object implements NS\IAuthenticator throw new NS\AuthenticationException('User not found.'); } - if ($row->password !== md5($password)) { + if (!NS\Passwords::verify($password, $row->password)) { throw new NS\AuthenticationException('Invalid password.'); } @@ -129,7 +129,7 @@ class MyAuthenticator extends Nette\Object implements NS\IAuthenticator } ``` -Class `MyAuthenticator` communicates with the database using [Nette\Database |database] layer and works with table `users`, where it grabs `username` and md5 hash of `password` in the appropriate columns. If the password check is successful, it returns new identity with user ID and role, which we will mention [later | #roles]; +Class `MyAuthenticator` communicates with the database using [Nette\Database |database] layer and works with table `users`, where it grabs `username` and hash of `password` in the appropriate columns. If the password check is successful, it returns new identity with user ID and role, which we will mention [later | #roles]; This authenticator would be configured in the `config.neon` file like this: From 2f5fe96f4dd8aef8397d0b61dd94ca40847c596f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2015 18:26:06 +0100 Subject: [PATCH 016/254] travis: added PHP 7.0 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d67b6911..42f64863 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,12 @@ php: - 5.4 - 5.5 - 5.6 + - 7.0 - hhvm matrix: allow_failures: + - php: 7.0 - php: hhvm script: From fad5403e828e38683cb1ab65c234b6b99a04d554 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 1 Apr 2015 19:22:24 +0200 Subject: [PATCH 017/254] composer: removed --dev --- .travis.yml | 2 +- tests/bootstrap.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 42f64863..e686d783 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,5 @@ after_failure: before_script: # Install Nette Tester & Code Checker - - composer install --no-interaction --dev --prefer-source + - composer install --no-interaction --prefer-source - composer create-project nette/code-checker code-checker ~2.3 --no-interaction --prefer-source diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 85f7726f..e333db3d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,7 +4,7 @@ // invoked through the command: ../vendor/bin/tester . if (@!include __DIR__ . '/../vendor/autoload.php') { - echo 'Install Nette Tester using `composer update --dev`'; + echo 'Install Nette Tester using `composer install`'; exit(1); } From e5651647bae9a1e87c71c561b040f0697da5b478 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 May 2015 16:59:27 +0200 Subject: [PATCH 018/254] requires PHP 5.4.4 --- .travis.yml | 1 - composer.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e686d783..1945e577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - 5.3.3 - 5.4 - 5.5 - 5.6 diff --git a/composer.json b/composer.json index 8d0b4c94..8590dfc5 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": ">=5.3.1", + "php": ">=5.4.4", "nette/utils": "~2.2" }, "require-dev": { From e4b93276a1e96adb774e62494dcddfd803b3549e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 May 2015 16:59:27 +0200 Subject: [PATCH 019/254] used PHP 5.4 syntax --- .travis.yml | 4 +- src/Bridges/SecurityDI/SecurityExtension.php | 26 +++--- .../templates/UserPanel.panel.phtml | 2 +- .../templates/UserPanel.tab.phtml | 4 +- src/Security/Permission.php | 86 +++++++++---------- src/Security/SimpleAuthenticator.php | 2 +- src/Security/User.php | 4 +- .../SecurityExtension.authenticator.phpt | 18 ++-- tests/Security.DI/SecurityExtension.user.phpt | 2 +- tests/Security/Identity.phpt | 8 +- tests/Security/Passwords.hash().phpt | 8 +- tests/Security/Passwords.needsRehash().phpt | 2 +- tests/Security/Permission.CMSExample.phpt | 10 +-- tests/Security/Permission.Privileges.phpt | 4 +- .../Security/Permission.ResourceInherits.phpt | 2 +- ...DefaultAllowRuleWithPrivilegeDenyRule.phpt | 2 +- ...eDefaultAllowRuleWithResourceDenyRule.phpt | 2 +- tests/Security/Permission.RolePrivileges.phpt | 4 +- .../Permission.RoleRegistryInherits.phpt | 10 +-- ...rmission.RoleRegistryInheritsMultiple.phpt | 8 +- tests/Security/Permission.RulesRemove.phpt | 2 +- tests/Security/SimpleAuthenticator.Roles.phpt | 22 ++--- tests/Security/SimpleAuthenticator.phpt | 12 +-- tests/Security/User.authentication.phpt | 6 +- tests/Security/User.authorization.phpt | 8 +- 25 files changed, 129 insertions(+), 129 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1945e577..2cc77a11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: script: - vendor/bin/tester tests -s -p php - - php code-checker/src/code-checker.php + - php temp/code-checker/src/code-checker.php --short-arrays after_failure: # Print *.actual content @@ -22,4 +22,4 @@ after_failure: before_script: # Install Nette Tester & Code Checker - composer install --no-interaction --prefer-source - - composer create-project nette/code-checker code-checker ~2.3 --no-interaction --prefer-source + - composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction --prefer-source diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 82c6713d..e70d4a36 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -17,12 +17,12 @@ */ class SecurityExtension extends Nette\DI\CompilerExtension { - public $defaults = array( + public $defaults = [ 'debugger' => TRUE, - 'users' => array(), // of [user => password] or [user => ['password' => password, 'roles' => [role]]] - 'roles' => array(), // of [role => parents] - 'resources' => array(), // of [resource => parents] - ); + 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] + 'roles' => [], // of [role => parents] + 'resources' => [], // of [resource => parents] + ]; /** @var bool */ private $debugMode; @@ -47,23 +47,23 @@ public function loadConfiguration() ->setClass('Nette\Security\User'); if ($this->debugMode && $config['debugger']) { - $user->addSetup('@Tracy\Bar::addPanel', array( + $user->addSetup('@Tracy\Bar::addPanel', [ new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel') - )); + ]); } if ($config['users']) { - $usersList = $usersRoles = array(); + $usersList = $usersRoles = []; foreach ($config['users'] as $username => $data) { - $data = is_array($data) ? $data : array('password' => $data); - $this->validateConfig(array('password' => NULL, 'roles' => NULL), $data, $this->prefix("security.users.$username")); + $data = is_array($data) ? $data : ['password' => $data]; + $this->validateConfig(['password' => NULL, 'roles' => NULL], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; $usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL; } $container->addDefinition($this->prefix('authenticator')) ->setClass('Nette\Security\IAuthenticator') - ->setFactory('Nette\Security\SimpleAuthenticator', array($usersList, $usersRoles)); + ->setFactory('Nette\Security\SimpleAuthenticator', [$usersList, $usersRoles]); if ($this->name === 'security') { $container->addAlias('nette.authenticator', $this->prefix('authenticator')); @@ -76,10 +76,10 @@ public function loadConfiguration() ->setFactory('Nette\Security\Permission'); foreach ($config['roles'] as $role => $parents) { - $authorizator->addSetup('addRole', array($role, $parents)); + $authorizator->addSetup('addRole', [$role, $parents]); } foreach ($config['resources'] as $resource => $parents) { - $authorizator->addSetup('addResource', array($resource, $parents)); + $authorizator->addSetup('addResource', [$resource, $parents]); } if ($this->name === 'security') { diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml index 1e8244ac..c18dfeaa 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml @@ -9,5 +9,5 @@ use Nette,

isLoggedIn()): ?>Logged inUnlogged

- getIdentity()): echo Dumper::toHtml($user->getIdentity(), array(Dumper::LIVE => TRUE)); else: ?>

no identity

+ getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => TRUE]); else: ?>

no identity

diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml index cbf9afc3..ec658b48 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml @@ -5,6 +5,6 @@ namespace Nette\Bridges\SecurityTracy; use Nette; ?> - - + + diff --git a/src/Security/Permission.php b/src/Security/Permission.php index ca1ace7d..c2c63213 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -26,25 +26,25 @@ class Permission extends Nette\Object implements IAuthorizator { /** @var array Role storage */ - private $roles = array(); + private $roles = []; /** @var array Resource storage */ - private $resources = array(); + private $resources = []; /** @var array Access Control List rules; whitelist (deny everything to all) by default */ - private $rules = array( - 'allResources' => array( - 'allRoles' => array( - 'allPrivileges' => array( + private $rules = [ + 'allResources' => [ + 'allRoles' => [ + 'allPrivileges' => [ 'type' => self::DENY, 'assert' => NULL, - ), - 'byPrivilege' => array(), - ), - 'byRole' => array(), - ), - 'byResource' => array(), - ); + ], + 'byPrivilege' => [], + ], + 'byRole' => [], + ], + 'byResource' => [], + ]; /** @var mixed */ private $queriedRole, $queriedResource; @@ -69,11 +69,11 @@ public function addRole($role, $parents = NULL) throw new Nette\InvalidStateException("Role '$role' already exists in the list."); } - $roleParents = array(); + $roleParents = []; if ($parents !== NULL) { if (!is_array($parents)) { - $parents = array($parents); + $parents = [$parents]; } foreach ($parents as $parent) { @@ -83,10 +83,10 @@ public function addRole($role, $parents = NULL) } } - $this->roles[$role] = array( + $this->roles[$role] = [ 'parents' => $roleParents, - 'children' => array(), - ); + 'children' => [], + ]; return $this; } @@ -222,7 +222,7 @@ public function removeRole($role) */ public function removeAllRoles() { - $this->roles = array(); + $this->roles = []; foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) { unset($this->rules['allResources']['byRole'][$roleCurrent]); @@ -263,10 +263,10 @@ public function addResource($resource, $parent = NULL) $this->resources[$parent]['children'][$resource] = TRUE; } - $this->resources[$resource] = array( + $this->resources[$resource] = [ 'parent' => $parent, - 'children' => array() - ); + 'children' => [] + ]; return $this; } @@ -366,7 +366,7 @@ public function removeResource($resource) unset($this->resources[$parent]['children'][$resource]); } - $removed = array($resource); + $removed = [$resource]; foreach ($this->resources[$resource]['children'] as $child => $foo) { $this->removeResource($child); $removed[] = $child; @@ -399,7 +399,7 @@ public function removeAllResources() } } - $this->resources = array(); + $this->resources = []; return $this; } @@ -486,11 +486,11 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse { // ensure that all specified Roles exist; normalize input to array of Roles or NULL if ($roles === self::ALL) { - $roles = array(self::ALL); + $roles = [self::ALL]; } else { if (!is_array($roles)) { - $roles = array($roles); + $roles = [$roles]; } foreach ($roles as $role) { @@ -500,11 +500,11 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse // ensure that all specified Resources exist; normalize input to array of Resources or NULL if ($resources === self::ALL) { - $resources = array(self::ALL); + $resources = [self::ALL]; } else { if (!is_array($resources)) { - $resources = array($resources); + $resources = [$resources]; } foreach ($resources as $resource) { @@ -514,10 +514,10 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse // normalize privileges to array if ($privileges === self::ALL) { - $privileges = array(); + $privileges = []; } elseif (!is_array($privileges)) { - $privileges = array($privileges); + $privileges = [$privileges]; } if ($toAdd) { // add to the rules @@ -528,7 +528,7 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse $rules['allPrivileges']['type'] = $type; $rules['allPrivileges']['assert'] = $assertion; if (!isset($rules['byPrivilege'])) { - $rules['byPrivilege'] = array(); + $rules['byPrivilege'] = []; } } else { foreach ($privileges as $privilege) { @@ -549,13 +549,13 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse if (count($privileges) === 0) { if ($resource === self::ALL && $role === self::ALL) { if ($type === $rules['allPrivileges']['type']) { - $rules = array( - 'allPrivileges' => array( + $rules = [ + 'allPrivileges' => [ 'type' => self::DENY, 'assert' => NULL - ), - 'byPrivilege' => array() - ); + ], + 'byPrivilege' => [] + ]; } continue; } @@ -681,10 +681,10 @@ public function getQueriedResource() */ private function searchRolePrivileges($all, $role, $resource, $privilege) { - $dfs = array( - 'visited' => array(), - 'stack' => array($role), - ); + $dfs = [ + 'visited' => [], + 'stack' => [$role], + ]; while (NULL !== ($role = array_pop($dfs['stack']))) { if (isset($dfs['visited'][$role])) { @@ -778,7 +778,7 @@ private function & getRules($resource, $role, $create = FALSE) if (!$create) { return $null; } - $this->rules['byResource'][$resource] = array(); + $this->rules['byResource'][$resource] = []; } $visitor = & $this->rules['byResource'][$resource]; } @@ -788,7 +788,7 @@ private function & getRules($resource, $role, $create = FALSE) if (!$create) { return $null; } - $visitor['allRoles']['byPrivilege'] = array(); + $visitor['allRoles']['byPrivilege'] = []; } return $visitor['allRoles']; } @@ -797,7 +797,7 @@ private function & getRules($resource, $role, $create = FALSE) if (!$create) { return $null; } - $visitor['byRole'][$role]['byPrivilege'] = array(); + $visitor['byRole'][$role]['byPrivilege'] = []; } return $visitor['byRole'][$role]; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 44492fd6..f3871eca 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -28,7 +28,7 @@ class SimpleAuthenticator extends Nette\Object implements IAuthenticator * @param array list of pairs username => password * @param array list of pairs username => role[] */ - public function __construct(array $userlist, array $usersRoles = array()) + public function __construct(array $userlist, array $usersRoles = []) { $this->userlist = $userlist; $this->usersRoles = $usersRoles; diff --git a/src/Security/User.php b/src/Security/User.php index 961c8439..941ea7b1 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -200,11 +200,11 @@ public function getLogoutReason() public function getRoles() { if (!$this->isLoggedIn()) { - return array($this->guestRole); + return [$this->guestRole]; } $identity = $this->getIdentity(); - return $identity && $identity->getRoles() ? $identity->getRoles() : array($this->authenticatedRole); + return $identity && $identity->getRoles() ? $identity->getRoles() : [$this->authenticatedRole]; } diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index 46938f13..5b506eef 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -36,21 +36,21 @@ $authenticator = $container->getService('security.authenticator'); Assert::type('Nette\Security\SimpleAuthenticator', $authenticator); Assert::same($authenticator, $container->getService('nette.authenticator')); -$userList = array( +$userList = [ 'john' => 'john123', 'admin' => 'admin123', 'user' => 'user123', 'moderator' => 'moderator123', -); -$expectedRoles = array( - 'john' => array(), - 'admin' => array('admin', 'user'), - 'user' => array(), - 'moderator' => array('moderator'), -); +]; +$expectedRoles = [ + 'john' => [], + 'admin' => ['admin', 'user'], + 'user' => [], + 'moderator' => ['moderator'], +]; foreach ($userList as $username => $password) { - $identity = $authenticator->authenticate(array($username, $password)); + $identity = $authenticator->authenticate([$username, $password]); Assert::equal($username, $identity->getId()); Assert::equal($expectedRoles[$username], $identity->getRoles()); } diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index 4690919e..81ea057d 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -19,7 +19,7 @@ $compiler->addExtension('foo', new HttpExtension); $compiler->addExtension('bar', new SessionExtension); $compiler->addExtension('security', new SecurityExtension); -eval($compiler->compile(array(), 'Container1')); +eval($compiler->compile([], 'Container1')); $container = new Container1; Assert::type('Nette\Http\UserStorage', $container->getService('security.userStorage')); diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index 62ab3671..af06419f 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -12,13 +12,13 @@ require __DIR__ . '/../bootstrap.php'; test(function() { - $id = new Identity(12, 'admin', array('name' => 'John')); + $id = new Identity(12, 'admin', ['name' => 'John']); Assert::same(12, $id->getId()); Assert::same(12, $id->id); - Assert::same(array('admin'), $id->getRoles()); - Assert::same(array('admin'), $id->roles); - Assert::same(array('name' => 'John'), $id->getData()); + Assert::same(['admin'], $id->getRoles()); + Assert::same(['admin'], $id->roles); + Assert::same(['name' => 'John'], $id->getData()); Assert::same('John', $id->name); }); diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index af1e511c..a953e6ee 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -19,7 +19,7 @@ Assert::truthy( Assert::truthy( preg_match('#^\$2y\$05\$123456789012345678901.{32}\z#', - $h = Passwords::hash('dg', array('cost' => 5, 'salt' => '1234567890123456789012'))) + $h = Passwords::hash('dg', ['cost' => 5, 'salt' => '1234567890123456789012'])) ); echo $h; @@ -28,13 +28,13 @@ Assert::same( $hash, crypt('dg', $hash) ); Assert::exception(function() { - Passwords::hash('dg', array('cost' => 3)); + Passwords::hash('dg', ['cost' => 3]); }, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 3 given.'); Assert::exception(function() { - Passwords::hash('dg', array('cost' => 32)); + Passwords::hash('dg', ['cost' => 32]); }, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 32 given.'); Assert::exception(function() { - Passwords::hash('dg', array('salt' => 'abc')); + Passwords::hash('dg', ['salt' => 'abc']); }, 'Nette\InvalidArgumentException', 'Salt must be 22 characters long, 3 given.'); diff --git a/tests/Security/Passwords.needsRehash().phpt b/tests/Security/Passwords.needsRehash().phpt index d86a4ce5..ebe26de7 100644 --- a/tests/Security/Passwords.needsRehash().phpt +++ b/tests/Security/Passwords.needsRehash().phpt @@ -13,4 +13,4 @@ require __DIR__ . '/../bootstrap.php'; Assert::true(Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::false(Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK', array('cost' => 5))); +Assert::false(Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK', ['cost' => 5])); diff --git a/tests/Security/Permission.CMSExample.phpt b/tests/Security/Permission.CMSExample.phpt index b99598f4..97193d9f 100644 --- a/tests/Security/Permission.CMSExample.phpt +++ b/tests/Security/Permission.CMSExample.phpt @@ -21,10 +21,10 @@ $acl->addRole('administrator'); $acl->allow('guest', NULL, 'view'); // Staff inherits view privilege from guest, but also needs additional privileges -$acl->allow('staff', NULL, array('edit', 'submit', 'revise')); +$acl->allow('staff', NULL, ['edit', 'submit', 'revise']); // Editor inherits view, edit, submit, and revise privileges, but also needs additional privileges -$acl->allow('editor', NULL, array('publish', 'archive', 'delete')); +$acl->allow('editor', NULL, ['publish', 'archive', 'delete']); // Administrator inherits nothing but is allowed all privileges $acl->allow('administrator'); @@ -93,12 +93,12 @@ $acl->addRole('marketing', 'staff'); // Refine the privilege sets for more specific needs // Allow marketing to publish and archive newsletters -$acl->allow('marketing', 'newsletter', array('publish', 'archive')); +$acl->allow('marketing', 'newsletter', ['publish', 'archive']); // Allow marketing to publish and archive latest news $acl->addResource('news'); $acl->addResource('latest', 'news'); -$acl->allow('marketing', 'latest', array('publish', 'archive')); +$acl->allow('marketing', 'latest', ['publish', 'archive']); // Deny staff (and marketing, by inheritance) rights to revise latest news $acl->deny('staff', 'latest', 'revise'); @@ -142,7 +142,7 @@ Assert::false( $acl->isAllowed('editor', 'announcement', 'archive') ); // Remove some previous permission specifications // Marketing can no longer publish and archive newsletters -$acl->removeAllow('marketing', 'newsletter', array('publish', 'archive')); +$acl->removeAllow('marketing', 'newsletter', ['publish', 'archive']); // Marketing can no longer archive the latest news $acl->removeAllow('marketing', 'latest', 'archive'); diff --git a/tests/Security/Permission.Privileges.phpt b/tests/Security/Permission.Privileges.phpt index 172990f7..9291f876 100644 --- a/tests/Security/Permission.Privileges.phpt +++ b/tests/Security/Permission.Privileges.phpt @@ -12,13 +12,13 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -$acl->allow(NULL, NULL, array('p1', 'p2', 'p3')); +$acl->allow(NULL, NULL, ['p1', 'p2', 'p3']); Assert::true( $acl->isAllowed(NULL, NULL, 'p1') ); Assert::true( $acl->isAllowed(NULL, NULL, 'p2') ); Assert::true( $acl->isAllowed(NULL, NULL, 'p3') ); Assert::false( $acl->isAllowed(NULL, NULL, 'p4') ); $acl->deny(NULL, NULL, 'p1'); Assert::false( $acl->isAllowed(NULL, NULL, 'p1') ); -$acl->deny(NULL, NULL, array('p2', 'p3')); +$acl->deny(NULL, NULL, ['p2', 'p3']); Assert::false( $acl->isAllowed(NULL, NULL, 'p2') ); Assert::false( $acl->isAllowed(NULL, NULL, 'p3') ); diff --git a/tests/Security/Permission.ResourceInherits.phpt b/tests/Security/Permission.ResourceInherits.phpt index 46f12a8c..c13abb05 100644 --- a/tests/Security/Permission.ResourceInherits.phpt +++ b/tests/Security/Permission.ResourceInherits.phpt @@ -16,7 +16,7 @@ $acl->addResource('city'); $acl->addResource('building', 'city'); $acl->addResource('room', 'building'); -Assert::same( array('city', 'building', 'room'), $acl->getResources() ); +Assert::same( ['city', 'building', 'room'], $acl->getResources() ); Assert::true( $acl->resourceInheritsFrom('building', 'city', TRUE) ); Assert::true( $acl->resourceInheritsFrom('room', 'building', TRUE) ); Assert::true( $acl->resourceInheritsFrom('room', 'city') ); diff --git a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt index c655c63d..56a60006 100644 --- a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt +++ b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt @@ -17,5 +17,5 @@ $acl->addRole('guest'); $acl->addRole('staff', 'guest'); $acl->deny(); $acl->allow('staff'); -$acl->deny('staff', NULL, array('privilege1', 'privilege2')); +$acl->deny('staff', NULL, ['privilege1', 'privilege2']); Assert::false( $acl->isAllowed('staff', NULL, 'privilege1') ); diff --git a/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt b/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt index ccfa447f..4a8a446e 100644 --- a/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt +++ b/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt @@ -19,5 +19,5 @@ $acl->addResource('area1'); $acl->addResource('area2'); $acl->deny(); $acl->allow('staff'); -$acl->deny('staff', array('area1', 'area2')); +$acl->deny('staff', ['area1', 'area2']); Assert::false( $acl->isAllowed('staff', 'area1') ); diff --git a/tests/Security/Permission.RolePrivileges.phpt b/tests/Security/Permission.RolePrivileges.phpt index 04538715..af45af8b 100644 --- a/tests/Security/Permission.RolePrivileges.phpt +++ b/tests/Security/Permission.RolePrivileges.phpt @@ -13,13 +13,13 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); -$acl->allow('guest', NULL, array('p1', 'p2', 'p3')); +$acl->allow('guest', NULL, ['p1', 'p2', 'p3']); Assert::true( $acl->isAllowed('guest', NULL, 'p1') ); Assert::true( $acl->isAllowed('guest', NULL, 'p2') ); Assert::true( $acl->isAllowed('guest', NULL, 'p3') ); Assert::false( $acl->isAllowed('guest', NULL, 'p4') ); $acl->deny('guest', NULL, 'p1'); Assert::false( $acl->isAllowed('guest', NULL, 'p1') ); -$acl->deny('guest', NULL, array('p2', 'p3')); +$acl->deny('guest', NULL, ['p2', 'p3']); Assert::false( $acl->isAllowed('guest', NULL, 'p2') ); Assert::false( $acl->isAllowed('guest', NULL, 'p3') ); diff --git a/tests/Security/Permission.RoleRegistryInherits.phpt b/tests/Security/Permission.RoleRegistryInherits.phpt index bd2866d3..01fbf927 100644 --- a/tests/Security/Permission.RoleRegistryInherits.phpt +++ b/tests/Security/Permission.RoleRegistryInherits.phpt @@ -15,10 +15,10 @@ $acl = new Permission; $acl->addRole('guest'); $acl->addRole('member', 'guest'); $acl->addRole('editor', 'member'); -Assert::same( array('guest', 'member', 'editor'), $acl->getRoles() ); -Assert::same( array(), $acl->getRoleParents('guest') ); -Assert::same( array('guest'), $acl->getRoleParents('member') ); -Assert::same( array('member'), $acl->getRoleParents('editor') ); +Assert::same( ['guest', 'member', 'editor'], $acl->getRoles() ); +Assert::same( [], $acl->getRoleParents('guest') ); +Assert::same( ['guest'], $acl->getRoleParents('member') ); +Assert::same( ['member'], $acl->getRoleParents('editor') ); Assert::true( $acl->roleInheritsFrom('member', 'guest', TRUE) ); @@ -30,5 +30,5 @@ Assert::false( $acl->roleInheritsFrom('member', 'editor') ); Assert::false( $acl->roleInheritsFrom('guest', 'editor') ); $acl->removeRole('member'); -Assert::same( array(), $acl->getRoleParents('editor') ); +Assert::same( [], $acl->getRoleParents('editor') ); Assert::false( $acl->roleInheritsFrom('editor', 'guest') ); diff --git a/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt b/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt index 2d64ffc1..7bd182ca 100644 --- a/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt +++ b/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt @@ -14,17 +14,17 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('parent1'); $acl->addRole('parent2'); -$acl->addRole('child', array('parent1', 'parent2')); +$acl->addRole('child', ['parent1', 'parent2']); -Assert::same( array( +Assert::same( [ 'parent1', 'parent2', -), $acl->getRoleParents('child') ); +], $acl->getRoleParents('child') ); Assert::true( $acl->roleInheritsFrom('child', 'parent1') ); Assert::true( $acl->roleInheritsFrom('child', 'parent2') ); $acl->removeRole('parent1'); -Assert::same( array('parent2'), $acl->getRoleParents('child') ); +Assert::same( ['parent2'], $acl->getRoleParents('child') ); Assert::true( $acl->roleInheritsFrom('child', 'parent2') ); diff --git a/tests/Security/Permission.RulesRemove.phpt b/tests/Security/Permission.RulesRemove.phpt index 91910a01..2d0e0f65 100644 --- a/tests/Security/Permission.RulesRemove.phpt +++ b/tests/Security/Permission.RulesRemove.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -$acl->allow(NULL, NULL, array('privilege1', 'privilege2')); +$acl->allow(NULL, NULL, ['privilege1', 'privilege2']); Assert::false( $acl->isAllowed() ); Assert::true( $acl->isAllowed(NULL, NULL, 'privilege1') ); Assert::true( $acl->isAllowed(NULL, NULL, 'privilege2') ); diff --git a/tests/Security/SimpleAuthenticator.Roles.phpt b/tests/Security/SimpleAuthenticator.Roles.phpt index 4ce3174a..db07facc 100644 --- a/tests/Security/SimpleAuthenticator.Roles.phpt +++ b/tests/Security/SimpleAuthenticator.Roles.phpt @@ -11,25 +11,25 @@ use Nette\Security\SimpleAuthenticator, require __DIR__ . '/../bootstrap.php'; -$users = array( +$users = [ 'john' => 'john123', 'admin' => 'admin123', 'user' => 'user123', -); -$usersRoles = array( - 'admin' => array('admin', 'user'), +]; +$usersRoles = [ + 'admin' => ['admin', 'user'], 'user' => 'user', -); -$expectedRoles = array( - 'admin' => array('admin', 'user'), - 'user' => array('user'), - 'john' => array(), -); +]; +$expectedRoles = [ + 'admin' => ['admin', 'user'], + 'user' => ['user'], + 'john' => [], +]; $authenticator = new SimpleAuthenticator($users, $usersRoles); foreach ($users as $username => $password) { - $identity = $authenticator->authenticate(array($username, $password)); + $identity = $authenticator->authenticate([$username, $password]); Assert::equal($username, $identity->getId()); Assert::equal($expectedRoles[$username], $identity->getRoles()); } diff --git a/tests/Security/SimpleAuthenticator.phpt b/tests/Security/SimpleAuthenticator.phpt index 94c7e86a..f00bdda3 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -11,25 +11,25 @@ use Nette\Security\SimpleAuthenticator, require __DIR__ . '/../bootstrap.php'; -$users = array( +$users = [ 'john' => 'password123!', 'admin' => 'admin', -); +]; $authenticator = new SimpleAuthenticator($users); -$identity = $authenticator->authenticate(array('john', 'password123!')); +$identity = $authenticator->authenticate(['john', 'password123!']); Assert::type( 'Nette\Security\IIdentity', $identity ); Assert::equal('john', $identity->getId()); -$identity = $authenticator->authenticate(array('admin', 'admin')); +$identity = $authenticator->authenticate(['admin', 'admin']); Assert::type( 'Nette\Security\IIdentity', $identity ); Assert::equal('admin', $identity->getId()); Assert::exception(function() use ($authenticator) { - $authenticator->authenticate(array('admin', 'wrong password')); + $authenticator->authenticate(['admin', 'wrong password']); }, 'Nette\Security\AuthenticationException', 'Invalid password.'); Assert::exception(function() use ($authenticator) { - $authenticator->authenticate(array('nobody', 'password')); + $authenticator->authenticate(['nobody', 'password']); }, 'Nette\Security\AuthenticationException', "User 'nobody' not found."); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index d945a0d9..3cf21c33 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -13,7 +13,7 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/MockUserStorage.php'; // Setup environment -$_COOKIE = array(); +$_COOKIE = []; ob_start(); @@ -43,10 +43,10 @@ class Authenticator implements IAuthenticator $user = new Nette\Security\User(new MockUserStorage); -$counter = (object) array( +$counter = (object) [ 'login' => 0, 'logout' => 0, -); +]; $user->onLoggedIn[] = function () use ($counter) { $counter->login++; diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index ecc91371..6630ac82 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -15,7 +15,7 @@ require __DIR__ . '/MockUserStorage.php'; // Setup environment -$_COOKIE = array(); +$_COOKIE = []; ob_start(); @@ -36,7 +36,7 @@ class Authenticator implements IAuthenticator throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); } else { - return new Identity('John Doe', array('admin')); + return new Identity('John Doe', ['admin']); } } @@ -65,7 +65,7 @@ $user = new Nette\Security\User(new MockUserStorage); Assert::false( $user->isLoggedIn() ); -Assert::same( array('guest'), $user->getRoles() ); +Assert::same( ['guest'], $user->getRoles() ); Assert::false( $user->isInRole('admin') ); Assert::true( $user->isInRole('guest') ); @@ -78,7 +78,7 @@ $user->setAuthenticator($handler); $user->login('john', 'xxx'); Assert::true( $user->isLoggedIn() ); -Assert::same( array('admin'), $user->getRoles() ); +Assert::same( ['admin'], $user->getRoles() ); Assert::true( $user->isInRole('admin') ); Assert::false( $user->isInRole('guest') ); From c3ce01e09cbd58e29249c3500de859613b505337 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 May 2015 18:15:06 +0200 Subject: [PATCH 020/254] removed support for PHP 5.3 --- src/Security/IUserStorage.php | 8 ++++---- src/Security/Passwords.php | 6 ++---- tests/Security/Passwords.hash().phpt | 1 - tests/Security/Passwords.needsRehash().phpt | 1 - tests/Security/Passwords.verify().phpt | 1 - 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 62c9dec0..8bc03ebd 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -18,12 +18,12 @@ interface IUserStorage { /** Log-out reason {@link IUserStorage::getLogoutReason()} */ - const MANUAL = 1, - INACTIVITY = 2, - BROWSER_CLOSED = 4; + const MANUAL = 0b0001, + INACTIVITY = 0b0010, + BROWSER_CLOSED = 0b0100; /** Log-out behavior */ - const CLEAR_IDENTITY = 8; + const CLEAR_IDENTITY = 0b1000; /** * Sets the authenticated status of this user. diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index cbb771e9..3918338e 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -11,7 +11,7 @@ /** - * Passwords tools. Requires PHP >= 5.3.7. + * Passwords tools. * * @author David Grudl */ @@ -31,9 +31,7 @@ public static function hash($password, array $options = NULL) $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST; $salt = isset($options['salt']) ? (string) $options['salt'] : Nette\Utils\Random::generate(22, '0-9A-Za-z./'); - if (PHP_VERSION_ID < 50307) { - throw new Nette\NotSupportedException(__METHOD__ . ' requires PHP >= 5.3.7.'); - } elseif (($len = strlen($salt)) < 22) { + if (($len = strlen($salt)) < 22) { throw new Nette\InvalidArgumentException("Salt must be 22 characters long, $len given."); } elseif ($cost < 4 || $cost > 31) { throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $cost given."); diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index a953e6ee..06613118 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -2,7 +2,6 @@ /** * Test: Nette\Security\Passwords::hash() - * @phpversion 5.3.7 */ use Nette\Security\Passwords, diff --git a/tests/Security/Passwords.needsRehash().phpt b/tests/Security/Passwords.needsRehash().phpt index ebe26de7..8fdc1e79 100644 --- a/tests/Security/Passwords.needsRehash().phpt +++ b/tests/Security/Passwords.needsRehash().phpt @@ -2,7 +2,6 @@ /** * Test: Nette\Security\Passwords::needsRehash() - * @phpversion 5.3.7 */ use Nette\Security\Passwords, diff --git a/tests/Security/Passwords.verify().phpt b/tests/Security/Passwords.verify().phpt index e2ad0ddf..8994f10b 100644 --- a/tests/Security/Passwords.verify().phpt +++ b/tests/Security/Passwords.verify().phpt @@ -2,7 +2,6 @@ /** * Test: Nette\Security\Passwords::verify() - * @phpversion 5.3.7 */ use Nette\Security\Passwords, From b7c73bae8a8d32bb9bd5547e1ed7163d905d734a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Jun 2015 16:45:57 +0200 Subject: [PATCH 021/254] improved coding style --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- src/Bridges/SecurityTracy/UserPanel.php | 4 +- src/Security/Permission.php | 12 +- src/Security/User.php | 4 +- .../SecurityExtension.authenticator.phpt | 10 +- tests/Security.DI/SecurityExtension.user.phpt | 10 +- tests/Security/Identity.phpt | 8 +- tests/Security/MockUserStorage.php | 7 +- tests/Security/Passwords.hash().phpt | 12 +- tests/Security/Passwords.needsRehash().phpt | 4 +- tests/Security/Passwords.verify().phpt | 4 +- tests/Security/Permission.CMSExample.phpt | 174 +++++++++--------- tests/Security/Permission.DefaultAssert.phpt | 6 +- tests/Security/Permission.DefaultDeny.phpt | 12 +- tests/Security/Permission.DefaultRuleSet.phpt | 12 +- .../Permission.IsAllowedNonExistent.phpt | 8 +- tests/Security/Permission.PrivilegeAllow.phpt | 6 +- .../Security/Permission.PrivilegeAssert.phpt | 8 +- tests/Security/Permission.PrivilegeDeny.phpt | 6 +- tests/Security/Permission.Privileges.phpt | 18 +- .../Permission.RemoveDefaultAllow.phpt | 8 +- ...mission.RemoveDefaultAllowNonExistent.phpt | 6 +- .../Permission.RemoveDefaultDeny.phpt | 8 +- .../Permission.RemoveDefaultDenyAssert.phpt | 8 +- ...rmission.RemoveDefaultDenyNonExistent.phpt | 6 +- ...AfterItWasAllowedAccessToAllResources.phpt | 6 +- .../Permission.ResourceAddAndGetOne.phpt | 10 +- ...ission.ResourceAddInheritsNonExistent.phpt | 6 +- .../Permission.ResourceDuplicate.phpt | 6 +- .../Security/Permission.ResourceInherits.phpt | 22 +-- ...ermission.ResourceInheritsNonExistent.phpt | 8 +- .../Permission.ResourceRemoveAll.phpt | 6 +- ...rmission.ResourceRemoveOneNonExistent.phpt | 6 +- ...DefaultAllowRuleWithPrivilegeDenyRule.phpt | 6 +- ...eDefaultAllowRuleWithResourceDenyRule.phpt | 8 +- .../Permission.RoleDefaultRuleSet.phpt | 8 +- ...ermission.RoleDefaultRuleSetPrivilege.phpt | 8 +- .../Permission.RolePrivilegeAllow.phpt | 6 +- .../Permission.RolePrivilegeAssert.phpt | 8 +- .../Permission.RolePrivilegeDeny.phpt | 6 +- tests/Security/Permission.RolePrivileges.phpt | 18 +- .../Permission.RoleRegistryAddAndGetOne.phpt | 10 +- ...on.RoleRegistryAddInheritsNonExistent.phpt | 6 +- .../Permission.RoleRegistryDuplicate.phpt | 6 +- .../Permission.RoleRegistryInherits.phpt | 30 +-- ...rmission.RoleRegistryInheritsMultiple.phpt | 16 +- ...ssion.RoleRegistryInheritsNonExistent.phpt | 8 +- .../Permission.RoleRegistryRemoveAll.phpt | 6 +- ...sion.RoleRegistryRemoveOneNonExistent.phpt | 6 +- tests/Security/Permission.RuleRoleRemove.phpt | 10 +- .../Permission.RuleRoleRemoveAll.phpt | 10 +- tests/Security/Permission.RulesRemove.phpt | 14 +- .../Permission.RulesResourceRemove.phpt | 10 +- .../Permission.RulesResourceRemoveAll.phpt | 10 +- tests/Security/SimpleAuthenticator.Roles.phpt | 14 +- tests/Security/SimpleAuthenticator.phpt | 12 +- tests/Security/User.authentication.phpt | 52 +++--- tests/Security/User.authorization.phpt | 32 ++-- 58 files changed, 377 insertions(+), 376 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index e70d4a36..ade238b8 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -48,7 +48,7 @@ public function loadConfiguration() if ($this->debugMode && $config['debugger']) { $user->addSetup('@Tracy\Bar::addPanel', [ - new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel') + new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel'), ]); } diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index e25f40a5..63fe63c8 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -7,8 +7,8 @@ namespace Nette\Bridges\SecurityTracy; -use Nette, - Tracy; +use Nette; +use Tracy; /** diff --git a/src/Security/Permission.php b/src/Security/Permission.php index c2c63213..d94d90eb 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -84,7 +84,7 @@ public function addRole($role, $parents = NULL) } $this->roles[$role] = [ - 'parents' => $roleParents, + 'parents' => $roleParents, 'children' => [], ]; @@ -264,8 +264,8 @@ public function addResource($resource, $parent = NULL) } $this->resources[$resource] = [ - 'parent' => $parent, - 'children' => [] + 'parent' => $parent, + 'children' => [], ]; return $this; @@ -552,10 +552,10 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse $rules = [ 'allPrivileges' => [ 'type' => self::DENY, - 'assert' => NULL + 'assert' => NULL, ], - 'byPrivilege' => [] - ]; + 'byPrivilege' => [], + ]; } continue; } diff --git a/src/Security/User.php b/src/Security/User.php index 941ea7b1..687c3ac5 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -38,10 +38,10 @@ class User extends Nette\Object /** @var string default role for authenticated user without own identity */ public $authenticatedRole = 'authenticated'; - /** @var callable[] function(User $sender); Occurs when the user is successfully logged in */ + /** @var callable[] function (User $sender); Occurs when the user is successfully logged in */ public $onLoggedIn; - /** @var callable[] function(User $sender); Occurs when the user is logged out */ + /** @var callable[] function (User $sender); Occurs when the user is logged out */ public $onLoggedOut; /** @var IUserStorage Session storage for current user */ diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index 5b506eef..8a26fb1b 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -4,11 +4,11 @@ * Test: SecurityExtension */ -use Nette\DI, - Nette\Bridges\HttpDI\HttpExtension, - Nette\Bridges\HttpDI\SessionExtension, - Nette\Bridges\SecurityDI\SecurityExtension, - Tester\Assert; +use Nette\DI; +use Nette\Bridges\HttpDI\HttpExtension; +use Nette\Bridges\HttpDI\SessionExtension; +use Nette\Bridges\SecurityDI\SecurityExtension; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index 81ea057d..b24df3f7 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -4,11 +4,11 @@ * Test: SecurityExtension */ -use Nette\DI, - Nette\Bridges\HttpDI\HttpExtension, - Nette\Bridges\HttpDI\SessionExtension, - Nette\Bridges\SecurityDI\SecurityExtension, - Tester\Assert; +use Nette\DI; +use Nette\Bridges\HttpDI\HttpExtension; +use Nette\Bridges\HttpDI\SessionExtension; +use Nette\Bridges\SecurityDI\SecurityExtension; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index af06419f..c93cca82 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Identity. */ -use Nette\Security\Identity, - Tester\Assert; +use Nette\Security\Identity; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -test(function() { +test(function () { $id = new Identity(12, 'admin', ['name' => 'John']); Assert::same(12, $id->getId()); @@ -23,7 +23,7 @@ test(function() { }); -test(function() { +test(function () { $id = new Identity('12'); Assert::same(12, $id->getId()); diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 4a9824c7..57dc6b97 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -26,9 +26,10 @@ function getIdentity() } function setExpiration($time, $flags = 0) - {} + { + } function getLogoutReason() - {} - + { + } } diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 06613118..39aa92cd 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Passwords::hash() */ -use Nette\Security\Passwords, - Tester\Assert; +use Nette\Security\Passwords; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -23,17 +23,17 @@ Assert::truthy( echo $h; $hash = Passwords::hash('dg'); -Assert::same( $hash, crypt('dg', $hash) ); +Assert::same($hash, crypt('dg', $hash)); -Assert::exception(function() { +Assert::exception(function () { Passwords::hash('dg', ['cost' => 3]); }, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 3 given.'); -Assert::exception(function() { +Assert::exception(function () { Passwords::hash('dg', ['cost' => 32]); }, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 32 given.'); -Assert::exception(function() { +Assert::exception(function () { Passwords::hash('dg', ['salt' => 'abc']); }, 'Nette\InvalidArgumentException', 'Salt must be 22 characters long, 3 given.'); diff --git a/tests/Security/Passwords.needsRehash().phpt b/tests/Security/Passwords.needsRehash().phpt index 8fdc1e79..4f3b9bc6 100644 --- a/tests/Security/Passwords.needsRehash().phpt +++ b/tests/Security/Passwords.needsRehash().phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Passwords::needsRehash() */ -use Nette\Security\Passwords, - Tester\Assert; +use Nette\Security\Passwords; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; diff --git a/tests/Security/Passwords.verify().phpt b/tests/Security/Passwords.verify().phpt index 8994f10b..6ac54692 100644 --- a/tests/Security/Passwords.verify().phpt +++ b/tests/Security/Passwords.verify().phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Passwords::verify() */ -use Nette\Security\Passwords, - Tester\Assert; +use Nette\Security\Passwords; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; diff --git a/tests/Security/Permission.CMSExample.phpt b/tests/Security/Permission.CMSExample.phpt index 97193d9f..19ac5ada 100644 --- a/tests/Security/Permission.CMSExample.phpt +++ b/tests/Security/Permission.CMSExample.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that an example for a content management system is operable. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -31,45 +31,45 @@ $acl->allow('administrator'); // Access control checks based on above permission sets -Assert::true( $acl->isAllowed('guest', NULL, 'view') ); -Assert::false( $acl->isAllowed('guest', NULL, 'edit') ); -Assert::false( $acl->isAllowed('guest', NULL, 'submit') ); -Assert::false( $acl->isAllowed('guest', NULL, 'revise') ); -Assert::false( $acl->isAllowed('guest', NULL, 'publish') ); -Assert::false( $acl->isAllowed('guest', NULL, 'archive') ); -Assert::false( $acl->isAllowed('guest', NULL, 'delete') ); -Assert::false( $acl->isAllowed('guest', NULL, 'unknown') ); -Assert::false( $acl->isAllowed('guest') ); - -Assert::true( $acl->isAllowed('staff', NULL, 'view') ); -Assert::true( $acl->isAllowed('staff', NULL, 'edit') ); -Assert::true( $acl->isAllowed('staff', NULL, 'submit') ); -Assert::true( $acl->isAllowed('staff', NULL, 'revise') ); -Assert::false( $acl->isAllowed('staff', NULL, 'publish') ); -Assert::false( $acl->isAllowed('staff', NULL, 'archive') ); -Assert::false( $acl->isAllowed('staff', NULL, 'delete') ); -Assert::false( $acl->isAllowed('staff', NULL, 'unknown') ); -Assert::false( $acl->isAllowed('staff') ); - -Assert::true( $acl->isAllowed('editor', NULL, 'view') ); -Assert::true( $acl->isAllowed('editor', NULL, 'edit') ); -Assert::true( $acl->isAllowed('editor', NULL, 'submit') ); -Assert::true( $acl->isAllowed('editor', NULL, 'revise') ); -Assert::true( $acl->isAllowed('editor', NULL, 'publish') ); -Assert::true( $acl->isAllowed('editor', NULL, 'archive') ); -Assert::true( $acl->isAllowed('editor', NULL, 'delete') ); -Assert::false( $acl->isAllowed('editor', NULL, 'unknown') ); -Assert::false( $acl->isAllowed('editor') ); - -Assert::true( $acl->isAllowed('administrator', NULL, 'view') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'edit') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'submit') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'revise') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'publish') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'archive') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'delete') ); -Assert::true( $acl->isAllowed('administrator', NULL, 'unknown') ); -Assert::true( $acl->isAllowed('administrator') ); +Assert::true($acl->isAllowed('guest', NULL, 'view')); +Assert::false($acl->isAllowed('guest', NULL, 'edit')); +Assert::false($acl->isAllowed('guest', NULL, 'submit')); +Assert::false($acl->isAllowed('guest', NULL, 'revise')); +Assert::false($acl->isAllowed('guest', NULL, 'publish')); +Assert::false($acl->isAllowed('guest', NULL, 'archive')); +Assert::false($acl->isAllowed('guest', NULL, 'delete')); +Assert::false($acl->isAllowed('guest', NULL, 'unknown')); +Assert::false($acl->isAllowed('guest')); + +Assert::true($acl->isAllowed('staff', NULL, 'view')); +Assert::true($acl->isAllowed('staff', NULL, 'edit')); +Assert::true($acl->isAllowed('staff', NULL, 'submit')); +Assert::true($acl->isAllowed('staff', NULL, 'revise')); +Assert::false($acl->isAllowed('staff', NULL, 'publish')); +Assert::false($acl->isAllowed('staff', NULL, 'archive')); +Assert::false($acl->isAllowed('staff', NULL, 'delete')); +Assert::false($acl->isAllowed('staff', NULL, 'unknown')); +Assert::false($acl->isAllowed('staff')); + +Assert::true($acl->isAllowed('editor', NULL, 'view')); +Assert::true($acl->isAllowed('editor', NULL, 'edit')); +Assert::true($acl->isAllowed('editor', NULL, 'submit')); +Assert::true($acl->isAllowed('editor', NULL, 'revise')); +Assert::true($acl->isAllowed('editor', NULL, 'publish')); +Assert::true($acl->isAllowed('editor', NULL, 'archive')); +Assert::true($acl->isAllowed('editor', NULL, 'delete')); +Assert::false($acl->isAllowed('editor', NULL, 'unknown')); +Assert::false($acl->isAllowed('editor')); + +Assert::true($acl->isAllowed('administrator', NULL, 'view')); +Assert::true($acl->isAllowed('administrator', NULL, 'edit')); +Assert::true($acl->isAllowed('administrator', NULL, 'submit')); +Assert::true($acl->isAllowed('administrator', NULL, 'revise')); +Assert::true($acl->isAllowed('administrator', NULL, 'publish')); +Assert::true($acl->isAllowed('administrator', NULL, 'archive')); +Assert::true($acl->isAllowed('administrator', NULL, 'delete')); +Assert::true($acl->isAllowed('administrator', NULL, 'unknown')); +Assert::true($acl->isAllowed('administrator')); // Some checks on specific areas, which inherit access controls from the root ACL node $acl->addResource('newsletter'); @@ -78,14 +78,14 @@ $acl->addResource('gallery'); $acl->addResource('profiles', 'gallery'); $acl->addResource('config'); $acl->addResource('hosts', 'config'); -Assert::true( $acl->isAllowed('guest', 'pending', 'view') ); -Assert::true( $acl->isAllowed('staff', 'profiles', 'revise') ); -Assert::true( $acl->isAllowed('staff', 'pending', 'view') ); -Assert::true( $acl->isAllowed('staff', 'pending', 'edit') ); -Assert::false( $acl->isAllowed('staff', 'pending', 'publish') ); -Assert::false( $acl->isAllowed('staff', 'pending') ); -Assert::false( $acl->isAllowed('editor', 'hosts', 'unknown') ); -Assert::true( $acl->isAllowed('administrator', 'pending') ); +Assert::true($acl->isAllowed('guest', 'pending', 'view')); +Assert::true($acl->isAllowed('staff', 'profiles', 'revise')); +Assert::true($acl->isAllowed('staff', 'pending', 'view')); +Assert::true($acl->isAllowed('staff', 'pending', 'edit')); +Assert::false($acl->isAllowed('staff', 'pending', 'publish')); +Assert::false($acl->isAllowed('staff', 'pending')); +Assert::false($acl->isAllowed('editor', 'hosts', 'unknown')); +Assert::true($acl->isAllowed('administrator', 'pending')); // Add a new group, marketing, which bases its permissions on staff $acl->addRole('marketing', 'staff'); @@ -109,35 +109,35 @@ $acl->deny(NULL, 'announcement', 'archive'); // Access control checks for the above refined permission sets -Assert::true( $acl->isAllowed('marketing', NULL, 'view') ); -Assert::true( $acl->isAllowed('marketing', NULL, 'edit') ); -Assert::true( $acl->isAllowed('marketing', NULL, 'submit') ); -Assert::true( $acl->isAllowed('marketing', NULL, 'revise') ); -Assert::false( $acl->isAllowed('marketing', NULL, 'publish') ); -Assert::false( $acl->isAllowed('marketing', NULL, 'archive') ); -Assert::false( $acl->isAllowed('marketing', NULL, 'delete') ); -Assert::false( $acl->isAllowed('marketing', NULL, 'unknown') ); -Assert::false( $acl->isAllowed('marketing') ); - -Assert::true( $acl->isAllowed('marketing', 'newsletter', 'publish') ); -Assert::false( $acl->isAllowed('staff', 'pending', 'publish') ); -Assert::true( $acl->isAllowed('marketing', 'pending', 'publish') ); -Assert::true( $acl->isAllowed('marketing', 'newsletter', 'archive') ); -Assert::false( $acl->isAllowed('marketing', 'newsletter', 'delete') ); -Assert::false( $acl->isAllowed('marketing', 'newsletter') ); - -Assert::true( $acl->isAllowed('marketing', 'latest', 'publish') ); -Assert::true( $acl->isAllowed('marketing', 'latest', 'archive') ); -Assert::false( $acl->isAllowed('marketing', 'latest', 'delete') ); -Assert::false( $acl->isAllowed('marketing', 'latest', 'revise') ); -Assert::false( $acl->isAllowed('marketing', 'latest') ); - -Assert::false( $acl->isAllowed('marketing', 'announcement', 'archive') ); -Assert::false( $acl->isAllowed('staff', 'announcement', 'archive') ); -Assert::false( $acl->isAllowed('administrator', 'announcement', 'archive') ); - -Assert::false( $acl->isAllowed('staff', 'latest', 'publish') ); -Assert::false( $acl->isAllowed('editor', 'announcement', 'archive') ); +Assert::true($acl->isAllowed('marketing', NULL, 'view')); +Assert::true($acl->isAllowed('marketing', NULL, 'edit')); +Assert::true($acl->isAllowed('marketing', NULL, 'submit')); +Assert::true($acl->isAllowed('marketing', NULL, 'revise')); +Assert::false($acl->isAllowed('marketing', NULL, 'publish')); +Assert::false($acl->isAllowed('marketing', NULL, 'archive')); +Assert::false($acl->isAllowed('marketing', NULL, 'delete')); +Assert::false($acl->isAllowed('marketing', NULL, 'unknown')); +Assert::false($acl->isAllowed('marketing')); + +Assert::true($acl->isAllowed('marketing', 'newsletter', 'publish')); +Assert::false($acl->isAllowed('staff', 'pending', 'publish')); +Assert::true($acl->isAllowed('marketing', 'pending', 'publish')); +Assert::true($acl->isAllowed('marketing', 'newsletter', 'archive')); +Assert::false($acl->isAllowed('marketing', 'newsletter', 'delete')); +Assert::false($acl->isAllowed('marketing', 'newsletter')); + +Assert::true($acl->isAllowed('marketing', 'latest', 'publish')); +Assert::true($acl->isAllowed('marketing', 'latest', 'archive')); +Assert::false($acl->isAllowed('marketing', 'latest', 'delete')); +Assert::false($acl->isAllowed('marketing', 'latest', 'revise')); +Assert::false($acl->isAllowed('marketing', 'latest')); + +Assert::false($acl->isAllowed('marketing', 'announcement', 'archive')); +Assert::false($acl->isAllowed('staff', 'announcement', 'archive')); +Assert::false($acl->isAllowed('administrator', 'announcement', 'archive')); + +Assert::false($acl->isAllowed('staff', 'latest', 'publish')); +Assert::false($acl->isAllowed('editor', 'announcement', 'archive')); // Remove some previous permission specifications @@ -152,19 +152,19 @@ $acl->removeDeny('staff', 'latest', 'revise'); // Access control checks for the above refinements -Assert::false( $acl->isAllowed('marketing', 'newsletter', 'publish') ); -Assert::false( $acl->isAllowed('marketing', 'newsletter', 'archive') ); +Assert::false($acl->isAllowed('marketing', 'newsletter', 'publish')); +Assert::false($acl->isAllowed('marketing', 'newsletter', 'archive')); -Assert::false( $acl->isAllowed('marketing', 'latest', 'archive') ); +Assert::false($acl->isAllowed('marketing', 'latest', 'archive')); -Assert::true( $acl->isAllowed('staff', 'latest', 'revise') ); -Assert::true( $acl->isAllowed('marketing', 'latest', 'revise') ); +Assert::true($acl->isAllowed('staff', 'latest', 'revise')); +Assert::true($acl->isAllowed('marketing', 'latest', 'revise')); // Grant marketing all permissions on the latest news $acl->allow('marketing', 'latest'); // Access control checks for the above refinement -Assert::true( $acl->isAllowed('marketing', 'latest', 'archive') ); -Assert::true( $acl->isAllowed('marketing', 'latest', 'publish') ); -Assert::true( $acl->isAllowed('marketing', 'latest', 'edit') ); -Assert::true( $acl->isAllowed('marketing', 'latest') ); +Assert::true($acl->isAllowed('marketing', 'latest', 'archive')); +Assert::true($acl->isAllowed('marketing', 'latest', 'publish')); +Assert::true($acl->isAllowed('marketing', 'latest', 'edit')); +Assert::true($acl->isAllowed('marketing', 'latest')); diff --git a/tests/Security/Permission.DefaultAssert.phpt b/tests/Security/Permission.DefaultAssert.phpt index 97646b69..b0d3315e 100644 --- a/tests/Security/Permission.DefaultAssert.phpt +++ b/tests/Security/Permission.DefaultAssert.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that the default rule obeys its assertion. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -19,4 +19,4 @@ function falseAssertion() $acl = new Permission; $acl->deny(NULL, NULL, NULL, 'falseAssertion'); -Assert::true( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.DefaultDeny.phpt b/tests/Security/Permission.DefaultDeny.phpt index 31003737..2ac4a182 100644 --- a/tests/Security/Permission.DefaultDeny.phpt +++ b/tests/Security/Permission.DefaultDeny.phpt @@ -4,17 +4,17 @@ * Test: Nette\Security\Permission Ensures that by default denies access to everything by all. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::false( $acl->isAllowed() ); -Assert::false( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed()); +Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); $acl->addRole('guest'); -Assert::false( $acl->isAllowed('guest') ); -Assert::false( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed('guest')); +Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.DefaultRuleSet.phpt b/tests/Security/Permission.DefaultRuleSet.phpt index 6a2e612b..063218b1 100644 --- a/tests/Security/Permission.DefaultRuleSet.phpt +++ b/tests/Security/Permission.DefaultRuleSet.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that ACL-wide rules (all Roles, Resources, and privileges) work properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,9 +13,9 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); -Assert::true( $acl->isAllowed() ); -Assert::true( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed()); +Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); $acl->deny(); -Assert::false( $acl->isAllowed() ); -Assert::false( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed()); +Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.IsAllowedNonExistent.phpt b/tests/Security/Permission.IsAllowedNonExistent.phpt index fd2b129f..e7f3b9f7 100644 --- a/tests/Security/Permission.IsAllowedNonExistent.phpt +++ b/tests/Security/Permission.IsAllowedNonExistent.phpt @@ -4,19 +4,19 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Role and Resource parameters are specified to isAllowed(). */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function() { +Assert::exception(function () { $acl = new Permission; $acl->isAllowed('nonexistent'); }, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); -Assert::exception(function() { +Assert::exception(function () { $acl = new Permission; $acl->isAllowed(NULL, 'nonexistent'); }, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.PrivilegeAllow.phpt b/tests/Security/Permission.PrivilegeAllow.phpt index f93bd1ca..a5dd2141 100644 --- a/tests/Security/Permission.PrivilegeAllow.phpt +++ b/tests/Security/Permission.PrivilegeAllow.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that a privilege allowed for all Roles upon all Resources works properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,4 +13,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(NULL, NULL, 'somePrivilege'); -Assert::true( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.PrivilegeAssert.phpt b/tests/Security/Permission.PrivilegeAssert.phpt index 5a157bd6..557db609 100644 --- a/tests/Security/Permission.PrivilegeAssert.phpt +++ b/tests/Security/Permission.PrivilegeAssert.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that assertions on privileges work properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -24,7 +24,7 @@ function trueAssertion() $acl = new Permission; $acl->allow(NULL, NULL, 'somePrivilege', 'trueAssertion'); -Assert::true( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); $acl->allow(NULL, NULL, 'somePrivilege', 'falseAssertion'); -Assert::false( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.PrivilegeDeny.phpt b/tests/Security/Permission.PrivilegeDeny.phpt index 9d82d8cc..01d59d92 100644 --- a/tests/Security/Permission.PrivilegeDeny.phpt +++ b/tests/Security/Permission.PrivilegeDeny.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that a privilege denied for all Roles upon all Resources works properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); $acl->deny(NULL, NULL, 'somePrivilege'); -Assert::false( $acl->isAllowed(NULL, NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.Privileges.phpt b/tests/Security/Permission.Privileges.phpt index 9291f876..0c95cbf4 100644 --- a/tests/Security/Permission.Privileges.phpt +++ b/tests/Security/Permission.Privileges.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that multiple privileges work properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,12 +13,12 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(NULL, NULL, ['p1', 'p2', 'p3']); -Assert::true( $acl->isAllowed(NULL, NULL, 'p1') ); -Assert::true( $acl->isAllowed(NULL, NULL, 'p2') ); -Assert::true( $acl->isAllowed(NULL, NULL, 'p3') ); -Assert::false( $acl->isAllowed(NULL, NULL, 'p4') ); +Assert::true($acl->isAllowed(NULL, NULL, 'p1')); +Assert::true($acl->isAllowed(NULL, NULL, 'p2')); +Assert::true($acl->isAllowed(NULL, NULL, 'p3')); +Assert::false($acl->isAllowed(NULL, NULL, 'p4')); $acl->deny(NULL, NULL, 'p1'); -Assert::false( $acl->isAllowed(NULL, NULL, 'p1') ); +Assert::false($acl->isAllowed(NULL, NULL, 'p1')); $acl->deny(NULL, NULL, ['p2', 'p3']); -Assert::false( $acl->isAllowed(NULL, NULL, 'p2') ); -Assert::false( $acl->isAllowed(NULL, NULL, 'p3') ); +Assert::false($acl->isAllowed(NULL, NULL, 'p2')); +Assert::false($acl->isAllowed(NULL, NULL, 'p3')); diff --git a/tests/Security/Permission.RemoveDefaultAllow.phpt b/tests/Security/Permission.RemoveDefaultAllow.phpt index 5745c4dc..975f04ab 100644 --- a/tests/Security/Permission.RemoveDefaultAllow.phpt +++ b/tests/Security/Permission.RemoveDefaultAllow.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removing the default allow rule results in default deny rule being assigned. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,6 +13,6 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); -Assert::true( $acl->isAllowed() ); +Assert::true($acl->isAllowed()); $acl->removeAllow(); -Assert::false( $acl->isAllowed() ); +Assert::false($acl->isAllowed()); diff --git a/tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt b/tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt index 21d0617d..72f58318 100644 --- a/tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt +++ b/tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removing non-existent default allow rule does nothing. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,4 +13,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->removeAllow(); -Assert::false( $acl->isAllowed() ); +Assert::false($acl->isAllowed()); diff --git a/tests/Security/Permission.RemoveDefaultDeny.phpt b/tests/Security/Permission.RemoveDefaultDeny.phpt index 4a031214..291150b0 100644 --- a/tests/Security/Permission.RemoveDefaultDeny.phpt +++ b/tests/Security/Permission.RemoveDefaultDeny.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that removing the default deny rule results in default deny rule. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::false( $acl->isAllowed() ); +Assert::false($acl->isAllowed()); $acl->removeDeny(); -Assert::false( $acl->isAllowed() ); +Assert::false($acl->isAllowed()); diff --git a/tests/Security/Permission.RemoveDefaultDenyAssert.phpt b/tests/Security/Permission.RemoveDefaultDenyAssert.phpt index b8ecd180..4fd7fe45 100644 --- a/tests/Security/Permission.RemoveDefaultDenyAssert.phpt +++ b/tests/Security/Permission.RemoveDefaultDenyAssert.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removing the default deny rule results in assertion method being removed. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -19,6 +19,6 @@ function falseAssertion() $acl = new Permission; $acl->deny(NULL, NULL, NULL, 'falseAssertion'); -Assert::true( $acl->isAllowed() ); +Assert::true($acl->isAllowed()); $acl->removeDeny(); -Assert::false( $acl->isAllowed() ); +Assert::false($acl->isAllowed()); diff --git a/tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt b/tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt index 713d464a..76e88fcd 100644 --- a/tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt +++ b/tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removing non-existent default deny rule does nothing. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); $acl->removeDeny(); -Assert::true( $acl->isAllowed() ); +Assert::true($acl->isAllowed()); diff --git a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt index d3ce326a..e5b5c1d4 100644 --- a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt +++ b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt @@ -5,8 +5,8 @@ * raise undefined index error. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -24,4 +24,4 @@ $acl->allow(NULL,'Test','xxx'); $acl->removeRole('test0'); // Check after fix -Assert::false( $acl->hasRole('test0') ); +Assert::false($acl->hasRole('test0')); diff --git a/tests/Security/Permission.ResourceAddAndGetOne.phpt b/tests/Security/Permission.ResourceAddAndGetOne.phpt index c34b6d93..92106ca6 100644 --- a/tests/Security/Permission.ResourceAddAndGetOne.phpt +++ b/tests/Security/Permission.ResourceAddAndGetOne.phpt @@ -4,18 +4,18 @@ * Test: Nette\Security\Permission Ensures that basic addition and retrieval of a single Resource works. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::false( $acl->hasResource('area') ); +Assert::false($acl->hasResource('area')); $acl->addResource('area'); -Assert::true( $acl->hasResource('area') ); +Assert::true($acl->hasResource('area')); $acl->removeResource('area'); -Assert::false( $acl->hasResource('area') ); +Assert::false($acl->hasResource('area')); diff --git a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt index 1d599129..fc6836de 100644 --- a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Resource is specified as a parent upon Resource addition. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->addResource('area', 'nonexistent'); }, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.ResourceDuplicate.phpt b/tests/Security/Permission.ResourceDuplicate.phpt index ea92f2e1..7e071875 100644 --- a/tests/Security/Permission.ResourceDuplicate.phpt +++ b/tests/Security/Permission.ResourceDuplicate.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that the same Resource cannot be added more than once. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::exception(function() { +Assert::exception(function () { $acl = new Permission; $acl->addResource('area'); $acl->addResource('area'); diff --git a/tests/Security/Permission.ResourceInherits.phpt b/tests/Security/Permission.ResourceInherits.phpt index c13abb05..ff3c431d 100644 --- a/tests/Security/Permission.ResourceInherits.phpt +++ b/tests/Security/Permission.ResourceInherits.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Tests basic Resource inheritance. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -16,14 +16,14 @@ $acl->addResource('city'); $acl->addResource('building', 'city'); $acl->addResource('room', 'building'); -Assert::same( ['city', 'building', 'room'], $acl->getResources() ); -Assert::true( $acl->resourceInheritsFrom('building', 'city', TRUE) ); -Assert::true( $acl->resourceInheritsFrom('room', 'building', TRUE) ); -Assert::true( $acl->resourceInheritsFrom('room', 'city') ); -Assert::false( $acl->resourceInheritsFrom('room', 'city', TRUE) ); -Assert::false( $acl->resourceInheritsFrom('city', 'building') ); -Assert::false( $acl->resourceInheritsFrom('building', 'room') ); -Assert::false( $acl->resourceInheritsFrom('city', 'room') ); +Assert::same(['city', 'building', 'room'], $acl->getResources()); +Assert::true($acl->resourceInheritsFrom('building', 'city', TRUE)); +Assert::true($acl->resourceInheritsFrom('room', 'building', TRUE)); +Assert::true($acl->resourceInheritsFrom('room', 'city')); +Assert::false($acl->resourceInheritsFrom('room', 'city', TRUE)); +Assert::false($acl->resourceInheritsFrom('city', 'building')); +Assert::false($acl->resourceInheritsFrom('building', 'room')); +Assert::false($acl->resourceInheritsFrom('city', 'room')); $acl->removeResource('building'); -Assert::false( $acl->hasResource('room') ); +Assert::false($acl->hasResource('room')); diff --git a/tests/Security/Permission.ResourceInheritsNonExistent.phpt b/tests/Security/Permission.ResourceInheritsNonExistent.phpt index 1ae3545b..8e8569c6 100644 --- a/tests/Security/Permission.ResourceInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceInheritsNonExistent.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Resource is specified to each parameter of inherits(). */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,10 +13,10 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->resourceInheritsFrom('nonexistent', 'area'); }, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->resourceInheritsFrom('area', 'nonexistent'); }, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.ResourceRemoveAll.phpt b/tests/Security/Permission.ResourceRemoveAll.phpt index 16401b61..15578931 100644 --- a/tests/Security/Permission.ResourceRemoveAll.phpt +++ b/tests/Security/Permission.ResourceRemoveAll.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of all Resources works. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); $acl->removeAllResources(); -Assert::false( $acl->hasResource('area') ); +Assert::false($acl->hasResource('area')); diff --git a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt index 2819116a..ae996b31 100644 --- a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Resource is specified for removal. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->removeResource('nonexistent'); }, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt index 56a60006..7658cdb4 100644 --- a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt +++ b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt @@ -5,8 +5,8 @@ * rule on the entire ACL. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -18,4 +18,4 @@ $acl->addRole('staff', 'guest'); $acl->deny(); $acl->allow('staff'); $acl->deny('staff', NULL, ['privilege1', 'privilege2']); -Assert::false( $acl->isAllowed('staff', NULL, 'privilege1') ); +Assert::false($acl->isAllowed('staff', NULL, 'privilege1')); diff --git a/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt b/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt index 4a8a446e..f28e715b 100644 --- a/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt +++ b/tests/Security/Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt @@ -2,11 +2,11 @@ /** * Test: Nette\Security\Permission Ensures that for a particular Role, a deny rule on a specific Resource is honored before an allow rule -* on the entire ACL. + * on the entire ACL. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -20,4 +20,4 @@ $acl->addResource('area2'); $acl->deny(); $acl->allow('staff'); $acl->deny('staff', ['area1', 'area2']); -Assert::false( $acl->isAllowed('staff', 'area1') ); +Assert::false($acl->isAllowed('staff', 'area1')); diff --git a/tests/Security/Permission.RoleDefaultRuleSet.phpt b/tests/Security/Permission.RoleDefaultRuleSet.phpt index bc84c751..05fe4234 100644 --- a/tests/Security/Permission.RoleDefaultRuleSet.phpt +++ b/tests/Security/Permission.RoleDefaultRuleSet.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that ACL-wide rules (all Resources and privileges) work properly for a particular Role. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,6 +14,6 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -Assert::true( $acl->isAllowed('guest') ); +Assert::true($acl->isAllowed('guest')); $acl->deny('guest'); -Assert::false( $acl->isAllowed('guest') ); +Assert::false($acl->isAllowed('guest')); diff --git a/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt b/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt index fc1c39e5..2e49c9e1 100644 --- a/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt +++ b/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that ACL-wide rules apply to privileges for a particular Role. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,6 +14,6 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -Assert::true( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); $acl->deny('guest'); -Assert::false( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeAllow.phpt b/tests/Security/Permission.RolePrivilegeAllow.phpt index a1602693..61bcc179 100644 --- a/tests/Security/Permission.RolePrivilegeAllow.phpt +++ b/tests/Security/Permission.RolePrivilegeAllow.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that a privilege allowed for a particular Role upon all Resources works properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest', NULL, 'somePrivilege'); -Assert::true( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeAssert.phpt b/tests/Security/Permission.RolePrivilegeAssert.phpt index 0751f135..0b31e0f2 100644 --- a/tests/Security/Permission.RolePrivilegeAssert.phpt +++ b/tests/Security/Permission.RolePrivilegeAssert.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that assertions on privileges work properly for a particular Role. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -25,6 +25,6 @@ function trueAssertion() $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest', NULL, 'somePrivilege', 'trueAssertion'); -Assert::true( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); $acl->allow('guest', NULL, 'somePrivilege', 'falseAssertion'); -Assert::false( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeDeny.phpt b/tests/Security/Permission.RolePrivilegeDeny.phpt index 06a91395..03988147 100644 --- a/tests/Security/Permission.RolePrivilegeDeny.phpt +++ b/tests/Security/Permission.RolePrivilegeDeny.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that a privilege denied for a particular Role upon all Resources works properly. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -15,4 +15,4 @@ $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); $acl->deny('guest', NULL, 'somePrivilege'); -Assert::false( $acl->isAllowed('guest', NULL, 'somePrivilege') ); +Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivileges.phpt b/tests/Security/Permission.RolePrivileges.phpt index af45af8b..0dca08a1 100644 --- a/tests/Security/Permission.RolePrivileges.phpt +++ b/tests/Security/Permission.RolePrivileges.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that multiple privileges work properly for a particular Role. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,12 +14,12 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest', NULL, ['p1', 'p2', 'p3']); -Assert::true( $acl->isAllowed('guest', NULL, 'p1') ); -Assert::true( $acl->isAllowed('guest', NULL, 'p2') ); -Assert::true( $acl->isAllowed('guest', NULL, 'p3') ); -Assert::false( $acl->isAllowed('guest', NULL, 'p4') ); +Assert::true($acl->isAllowed('guest', NULL, 'p1')); +Assert::true($acl->isAllowed('guest', NULL, 'p2')); +Assert::true($acl->isAllowed('guest', NULL, 'p3')); +Assert::false($acl->isAllowed('guest', NULL, 'p4')); $acl->deny('guest', NULL, 'p1'); -Assert::false( $acl->isAllowed('guest', NULL, 'p1') ); +Assert::false($acl->isAllowed('guest', NULL, 'p1')); $acl->deny('guest', NULL, ['p2', 'p3']); -Assert::false( $acl->isAllowed('guest', NULL, 'p2') ); -Assert::false( $acl->isAllowed('guest', NULL, 'p3') ); +Assert::false($acl->isAllowed('guest', NULL, 'p2')); +Assert::false($acl->isAllowed('guest', NULL, 'p3')); diff --git a/tests/Security/Permission.RoleRegistryAddAndGetOne.phpt b/tests/Security/Permission.RoleRegistryAddAndGetOne.phpt index c7617fc5..82f5f03a 100644 --- a/tests/Security/Permission.RoleRegistryAddAndGetOne.phpt +++ b/tests/Security/Permission.RoleRegistryAddAndGetOne.phpt @@ -4,18 +4,18 @@ * Test: Nette\Security\Permission Ensures that basic addition and retrieval of a single Role works. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::false( $acl->hasRole('guest') ); +Assert::false($acl->hasRole('guest')); $acl->addRole('guest'); -Assert::true( $acl->hasRole('guest') ); +Assert::true($acl->hasRole('guest')); $acl->removeRole('guest'); -Assert::false( $acl->hasRole('guest') ); +Assert::false($acl->hasRole('guest')); diff --git a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt index 928a54e0..974ca66b 100644 --- a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Role is specified as a parent upon Role addition. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->addRole('guest', 'nonexistent'); }, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleRegistryDuplicate.phpt b/tests/Security/Permission.RoleRegistryDuplicate.phpt index 97adde8c..621f66f0 100644 --- a/tests/Security/Permission.RoleRegistryDuplicate.phpt +++ b/tests/Security/Permission.RoleRegistryDuplicate.phpt @@ -4,15 +4,15 @@ * Test: Nette\Security\Permission Ensures that the same Role cannot be registered more than once to the registry. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->addRole('guest'); $acl->addRole('guest'); }, 'Nette\InvalidStateException', "Role 'guest' already exists in the list."); diff --git a/tests/Security/Permission.RoleRegistryInherits.phpt b/tests/Security/Permission.RoleRegistryInherits.phpt index 01fbf927..1e2ae6c7 100644 --- a/tests/Security/Permission.RoleRegistryInherits.phpt +++ b/tests/Security/Permission.RoleRegistryInherits.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Tests basic Role inheritance. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -15,20 +15,20 @@ $acl = new Permission; $acl->addRole('guest'); $acl->addRole('member', 'guest'); $acl->addRole('editor', 'member'); -Assert::same( ['guest', 'member', 'editor'], $acl->getRoles() ); -Assert::same( [], $acl->getRoleParents('guest') ); -Assert::same( ['guest'], $acl->getRoleParents('member') ); -Assert::same( ['member'], $acl->getRoleParents('editor') ); +Assert::same(['guest', 'member', 'editor'], $acl->getRoles()); +Assert::same([], $acl->getRoleParents('guest')); +Assert::same(['guest'], $acl->getRoleParents('member')); +Assert::same(['member'], $acl->getRoleParents('editor')); -Assert::true( $acl->roleInheritsFrom('member', 'guest', TRUE) ); -Assert::true( $acl->roleInheritsFrom('editor', 'member', TRUE) ); -Assert::true( $acl->roleInheritsFrom('editor', 'guest') ); -Assert::false( $acl->roleInheritsFrom('editor', 'guest', TRUE) ); -Assert::false( $acl->roleInheritsFrom('guest', 'member') ); -Assert::false( $acl->roleInheritsFrom('member', 'editor') ); -Assert::false( $acl->roleInheritsFrom('guest', 'editor') ); +Assert::true($acl->roleInheritsFrom('member', 'guest', TRUE)); +Assert::true($acl->roleInheritsFrom('editor', 'member', TRUE)); +Assert::true($acl->roleInheritsFrom('editor', 'guest')); +Assert::false($acl->roleInheritsFrom('editor', 'guest', TRUE)); +Assert::false($acl->roleInheritsFrom('guest', 'member')); +Assert::false($acl->roleInheritsFrom('member', 'editor')); +Assert::false($acl->roleInheritsFrom('guest', 'editor')); $acl->removeRole('member'); -Assert::same( [], $acl->getRoleParents('editor') ); -Assert::false( $acl->roleInheritsFrom('editor', 'guest') ); +Assert::same([], $acl->getRoleParents('editor')); +Assert::false($acl->roleInheritsFrom('editor', 'guest')); diff --git a/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt b/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt index 7bd182ca..c4e8a407 100644 --- a/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt +++ b/tests/Security/Permission.RoleRegistryInheritsMultiple.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Tests basic Role multiple inheritance. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -16,15 +16,15 @@ $acl->addRole('parent1'); $acl->addRole('parent2'); $acl->addRole('child', ['parent1', 'parent2']); -Assert::same( [ +Assert::same([ 'parent1', 'parent2', -], $acl->getRoleParents('child') ); +], $acl->getRoleParents('child')); -Assert::true( $acl->roleInheritsFrom('child', 'parent1') ); -Assert::true( $acl->roleInheritsFrom('child', 'parent2') ); +Assert::true($acl->roleInheritsFrom('child', 'parent1')); +Assert::true($acl->roleInheritsFrom('child', 'parent2')); $acl->removeRole('parent1'); -Assert::same( ['parent2'], $acl->getRoleParents('child') ); -Assert::true( $acl->roleInheritsFrom('child', 'parent2') ); +Assert::same(['parent2'], $acl->getRoleParents('child')); +Assert::true($acl->roleInheritsFrom('child', 'parent2')); diff --git a/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt b/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt index 462f980c..765196cb 100644 --- a/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Role is specified to each parameter of inherits(). */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,10 +13,10 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->roleInheritsFrom('nonexistent', 'guest'); }, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->roleInheritsFrom('guest', 'nonexistent'); }, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleRegistryRemoveAll.phpt b/tests/Security/Permission.RoleRegistryRemoveAll.phpt index 9fbc25af..d7334f94 100644 --- a/tests/Security/Permission.RoleRegistryRemoveAll.phpt +++ b/tests/Security/Permission.RoleRegistryRemoveAll.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of all Roles works. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->removeAllRoles(); -Assert::false( $acl->hasRole('guest') ); +Assert::false($acl->hasRole('guest')); diff --git a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt index a232ba27..0ecc4207 100644 --- a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt @@ -4,14 +4,14 @@ * Test: Nette\Security\Permission Ensures that an exception is thrown when a non-existent Role is specified for removal. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->removeRole('nonexistent'); }, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RuleRoleRemove.phpt b/tests/Security/Permission.RuleRoleRemove.phpt index 26ecb1cd..466a1312 100644 --- a/tests/Security/Permission.RuleRoleRemove.phpt +++ b/tests/Security/Permission.RuleRoleRemove.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of a Role results in its rules being removed. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,11 +14,11 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -Assert::true( $acl->isAllowed('guest') ); +Assert::true($acl->isAllowed('guest')); $acl->removeRole('guest'); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->isAllowed('guest'); }, 'Nette\InvalidStateException', "Role 'guest' does not exist."); $acl->addRole('guest'); -Assert::false( $acl->isAllowed('guest') ); +Assert::false($acl->isAllowed('guest')); diff --git a/tests/Security/Permission.RuleRoleRemoveAll.phpt b/tests/Security/Permission.RuleRoleRemoveAll.phpt index 00719d58..9b428477 100644 --- a/tests/Security/Permission.RuleRoleRemoveAll.phpt +++ b/tests/Security/Permission.RuleRoleRemoveAll.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of all Roles results in Role-specific rules being removed. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,11 +14,11 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -Assert::true( $acl->isAllowed('guest') ); +Assert::true($acl->isAllowed('guest')); $acl->removeAllRoles(); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->isAllowed('guest'); }, 'Nette\InvalidStateException', "Role 'guest' does not exist."); $acl->addRole('guest'); -Assert::false( $acl->isAllowed('guest') ); +Assert::false($acl->isAllowed('guest')); diff --git a/tests/Security/Permission.RulesRemove.phpt b/tests/Security/Permission.RulesRemove.phpt index 2d0e0f65..d5119200 100644 --- a/tests/Security/Permission.RulesRemove.phpt +++ b/tests/Security/Permission.RulesRemove.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensure that basic rule removal works. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -13,9 +13,9 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(NULL, NULL, ['privilege1', 'privilege2']); -Assert::false( $acl->isAllowed() ); -Assert::true( $acl->isAllowed(NULL, NULL, 'privilege1') ); -Assert::true( $acl->isAllowed(NULL, NULL, 'privilege2') ); +Assert::false($acl->isAllowed()); +Assert::true($acl->isAllowed(NULL, NULL, 'privilege1')); +Assert::true($acl->isAllowed(NULL, NULL, 'privilege2')); $acl->removeAllow(NULL, NULL, 'privilege1'); -Assert::false( $acl->isAllowed(NULL, NULL, 'privilege1') ); -Assert::true( $acl->isAllowed(NULL, NULL, 'privilege2') ); +Assert::false($acl->isAllowed(NULL, NULL, 'privilege1')); +Assert::true($acl->isAllowed(NULL, NULL, 'privilege2')); diff --git a/tests/Security/Permission.RulesResourceRemove.phpt b/tests/Security/Permission.RulesResourceRemove.phpt index 078ded22..560cd9db 100644 --- a/tests/Security/Permission.RulesResourceRemove.phpt +++ b/tests/Security/Permission.RulesResourceRemove.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of a Resource results in its rules being removed. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,11 +14,11 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); $acl->allow(NULL, 'area'); -Assert::true( $acl->isAllowed(NULL, 'area') ); +Assert::true($acl->isAllowed(NULL, 'area')); $acl->removeResource('area'); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->isAllowed(NULL, 'area'); }, 'Nette\InvalidStateException', "Resource 'area' does not exist."); $acl->addResource('area'); -Assert::false( $acl->isAllowed(NULL, 'area') ); +Assert::false($acl->isAllowed(NULL, 'area')); diff --git a/tests/Security/Permission.RulesResourceRemoveAll.phpt b/tests/Security/Permission.RulesResourceRemoveAll.phpt index c11b4c6b..a1b7bc6b 100644 --- a/tests/Security/Permission.RulesResourceRemoveAll.phpt +++ b/tests/Security/Permission.RulesResourceRemoveAll.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\Permission Ensures that removal of all Resources results in Resource-specific rules being removed. */ -use Nette\Security\Permission, - Tester\Assert; +use Nette\Security\Permission; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,11 +14,11 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); $acl->allow(NULL, 'area'); -Assert::true( $acl->isAllowed(NULL, 'area') ); +Assert::true($acl->isAllowed(NULL, 'area')); $acl->removeAllResources(); -Assert::exception(function() use ($acl) { +Assert::exception(function () use ($acl) { $acl->isAllowed(NULL, 'area'); }, 'Nette\InvalidStateException', "Resource 'area' does not exist."); $acl->addResource('area'); -Assert::false( $acl->isAllowed(NULL, 'area') ); +Assert::false($acl->isAllowed(NULL, 'area')); diff --git a/tests/Security/SimpleAuthenticator.Roles.phpt b/tests/Security/SimpleAuthenticator.Roles.phpt index db07facc..19108993 100644 --- a/tests/Security/SimpleAuthenticator.Roles.phpt +++ b/tests/Security/SimpleAuthenticator.Roles.phpt @@ -4,26 +4,26 @@ * Test: Nette\Security\SimpleAuthenticator and roles */ -use Nette\Security\SimpleAuthenticator, - Tester\Assert; +use Nette\Security\SimpleAuthenticator; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; $users = [ - 'john' => 'john123', + 'john' => 'john123', 'admin' => 'admin123', - 'user' => 'user123', + 'user' => 'user123', ]; $usersRoles = [ 'admin' => ['admin', 'user'], - 'user' => 'user', + 'user' => 'user', ]; $expectedRoles = [ 'admin' => ['admin', 'user'], - 'user' => ['user'], - 'john' => [], + 'user' => ['user'], + 'john' => [], ]; $authenticator = new SimpleAuthenticator($users, $usersRoles); diff --git a/tests/Security/SimpleAuthenticator.phpt b/tests/Security/SimpleAuthenticator.phpt index f00bdda3..993c6bb3 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -4,8 +4,8 @@ * Test: Nette\Security\SimpleAuthenticator */ -use Nette\Security\SimpleAuthenticator, - Tester\Assert; +use Nette\Security\SimpleAuthenticator; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -19,17 +19,17 @@ $users = [ $authenticator = new SimpleAuthenticator($users); $identity = $authenticator->authenticate(['john', 'password123!']); -Assert::type( 'Nette\Security\IIdentity', $identity ); +Assert::type('Nette\Security\IIdentity', $identity); Assert::equal('john', $identity->getId()); $identity = $authenticator->authenticate(['admin', 'admin']); -Assert::type( 'Nette\Security\IIdentity', $identity ); +Assert::type('Nette\Security\IIdentity', $identity); Assert::equal('admin', $identity->getId()); -Assert::exception(function() use ($authenticator) { +Assert::exception(function () use ($authenticator) { $authenticator->authenticate(['admin', 'wrong password']); }, 'Nette\Security\AuthenticationException', 'Invalid password.'); -Assert::exception(function() use ($authenticator) { +Assert::exception(function () use ($authenticator) { $authenticator->authenticate(['nobody', 'password']); }, 'Nette\Security\AuthenticationException', "User 'nobody' not found."); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 3cf21c33..ca534907 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -4,9 +4,9 @@ * Test: Nette\Security\User authentication. */ -use Nette\Security\IAuthenticator, - Nette\Security\Identity, - Tester\Assert; +use Nette\Security\IAuthenticator; +use Nette\Security\Identity; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -57,13 +57,13 @@ $user->onLoggedOut[] = function () use ($counter) { }; -Assert::false( $user->isLoggedIn() ); -Assert::null( $user->getIdentity() ); -Assert::null( $user->getId() ); +Assert::false($user->isLoggedIn()); +Assert::null($user->getIdentity()); +Assert::null($user->getId()); // authenticate -Assert::exception(function() use ($user) { +Assert::exception(function () use ($user) { // login without handler $user->login('jane', ''); }, 'Nette\InvalidStateException', 'Authenticator has not been set.'); @@ -71,51 +71,51 @@ Assert::exception(function() use ($user) { $handler = new Authenticator; $user->setAuthenticator($handler); -Assert::exception(function() use ($user) { +Assert::exception(function () use ($user) { // login as jane $user->login('jane', ''); }, 'Nette\Security\AuthenticationException', 'Unknown user'); -Assert::exception(function() use ($user) { +Assert::exception(function () use ($user) { // login as john $user->login('john', ''); }, 'Nette\Security\AuthenticationException', '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() ); +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() ); +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::same(2, $counter->logout); -Assert::false( $user->isLoggedIn() ); -Assert::equal( new Identity('John Doe', 'admin'), $user->getIdentity() ); +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::same(2, $counter->logout); // not logged in -> logout event not triggered -Assert::false( $user->isLoggedIn() ); -Assert::null( $user->getIdentity() ); +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() ); +Assert::same(3, $counter->login); +Assert::true($user->isLoggedIn()); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 6630ac82..806f7945 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -4,10 +4,10 @@ * Test: Nette\Security\User authorization. */ -use Nette\Security\IAuthenticator, - Nette\Security\Identity, - Nette\Security\IAuthorizator, - Tester\Assert; +use Nette\Security\IAuthenticator; +use Nette\Security\Identity; +use Nette\Security\IAuthorizator; +use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -62,12 +62,12 @@ class Authorizator implements IAuthorizator $user = new Nette\Security\User(new MockUserStorage); // guest -Assert::false( $user->isLoggedIn() ); +Assert::false($user->isLoggedIn()); -Assert::same( ['guest'], $user->getRoles() ); -Assert::false( $user->isInRole('admin') ); -Assert::true( $user->isInRole('guest') ); +Assert::same(['guest'], $user->getRoles()); +Assert::false($user->isInRole('admin')); +Assert::true($user->isInRole('guest')); // authenticated @@ -77,26 +77,26 @@ $user->setAuthenticator($handler); // login as john $user->login('john', 'xxx'); -Assert::true( $user->isLoggedIn() ); -Assert::same( ['admin'], $user->getRoles() ); -Assert::true( $user->isInRole('admin') ); -Assert::false( $user->isInRole('guest') ); +Assert::true($user->isLoggedIn()); +Assert::same(['admin'], $user->getRoles()); +Assert::true($user->isInRole('admin')); +Assert::false($user->isInRole('guest')); // authorization -Assert::exception(function() use ($user) { +Assert::exception(function () use ($user) { $user->isAllowed('delete_file'); }, 'Nette\InvalidStateException', 'Authorizator has not been set.'); $handler = new Authorizator; $user->setAuthorizator($handler); -Assert::true( $user->isAllowed('delete_file') ); -Assert::false( $user->isAllowed('sleep_with_jany') ); +Assert::true($user->isAllowed('delete_file')); +Assert::false($user->isAllowed('sleep_with_jany')); // log out // logging out... $user->logout(FALSE); -Assert::false( $user->isAllowed('delete_file') ); +Assert::false($user->isAllowed('delete_file')); From 9bab420b89c341c5787e29664cf153e28d88a9b4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 20 Jun 2015 16:14:44 +0200 Subject: [PATCH 022/254] readme.md: added badges --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 42db1523..48e53c5f 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,8 @@ Nette Security: Access Control [![Downloads this Month](https://img.shields.io/packagist/dm/nette/security.svg)](https://packagist.org/packages/nette/security) [![Build Status](https://travis-ci.org/nette/security.svg?branch=master)](https://travis-ci.org/nette/security) +[![Latest Stable Version](https://poser.pugx.org/nette/security/v/stable)](https://github.com/nette/security/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/security/blob/master/license.md) - user login and logout - verifying user privileges From 0c6f8d2f0f1b13c492b4668dc270400709440cbf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 21 Jun 2015 17:25:56 +0200 Subject: [PATCH 023/254] travis: testing with lowest dependencies --- .travis.yml | 6 +++++- composer.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2cc77a11..87920592 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ matrix: - php: 7.0 - php: hhvm + include: + - php: 5.6 + env: dependencies="--prefer-lowest --prefer-stable" + script: - vendor/bin/tester tests -s -p php - php temp/code-checker/src/code-checker.php --short-arrays @@ -21,5 +25,5 @@ after_failure: before_script: # Install Nette Tester & Code Checker - - composer install --no-interaction --prefer-source + - composer update --no-interaction --prefer-source $dependencies - composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction --prefer-source diff --git a/composer.json b/composer.json index 8590dfc5..a7e7b7de 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require-dev": { "nette/di": "~2.3", "nette/http": "~2.3", - "nette/tester": "~1.0" + "nette/tester": "~1.4" }, "conflict": { "nette/nette": "<2.2" From 5bf8b495950c9a788a42ddbde20b6be9371e064f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Jun 2015 14:47:20 +0200 Subject: [PATCH 024/254] removed @author --- src/Bridges/SecurityDI/SecurityExtension.php | 2 -- src/Bridges/SecurityTracy/UserPanel.php | 2 -- src/Security/AuthenticationException.php | 2 -- src/Security/IAuthenticator.php | 2 -- src/Security/IAuthorizator.php | 2 -- src/Security/IIdentity.php | 2 -- src/Security/IResource.php | 2 -- src/Security/IRole.php | 2 -- src/Security/IUserStorage.php | 2 -- src/Security/Identity.php | 2 -- src/Security/Passwords.php | 2 -- src/Security/Permission.php | 1 - src/Security/SimpleAuthenticator.php | 2 -- src/Security/User.php | 2 -- 14 files changed, 27 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index ade238b8..074ba4f6 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -12,8 +12,6 @@ /** * Security extension for Nette DI. - * - * @author David Grudl */ class SecurityExtension extends Nette\DI\CompilerExtension { diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 63fe63c8..931156d3 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -13,8 +13,6 @@ /** * User panel for Debugger Bar. - * - * @author David Grudl */ class UserPanel extends Nette\Object implements Tracy\IBarPanel { diff --git a/src/Security/AuthenticationException.php b/src/Security/AuthenticationException.php index 7dee689c..b6333145 100644 --- a/src/Security/AuthenticationException.php +++ b/src/Security/AuthenticationException.php @@ -12,8 +12,6 @@ /** * Authentication exception. - * - * @author David Grudl */ class AuthenticationException extends \Exception { diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 2727605c..4ce9c60d 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -12,8 +12,6 @@ /** * Performs authentication. - * - * @author David Grudl */ interface IAuthenticator { diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index c3517678..e87981bc 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -13,8 +13,6 @@ /** * Authorizator checks if a given role has authorization * to access a given resource. - * - * @author David Grudl */ interface IAuthorizator { diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index 1385643d..a44f4b94 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -12,8 +12,6 @@ /** * Represents the user of application. - * - * @author David Grudl */ interface IIdentity { diff --git a/src/Security/IResource.php b/src/Security/IResource.php index 7be0bac9..4a546ec7 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -12,8 +12,6 @@ /** * Represents resource, an object to which access is controlled. - * - * @author David Grudl */ interface IResource { diff --git a/src/Security/IRole.php b/src/Security/IRole.php index 91257d29..eab29bfa 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -12,8 +12,6 @@ /** * Represents role, an object that may request access to an IResource. - * - * @author David Grudl */ interface IRole { diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 8bc03ebd..e27271c0 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -12,8 +12,6 @@ /** * Interface for persistent storage for user object data. - * - * @author David Grudl, Jan Tichý */ interface IUserStorage { diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 4beaba03..0a358af5 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -13,8 +13,6 @@ /** * Default implementation of IIdentity. * - * @author David Grudl - * * @property mixed $id * @property array $roles * @property-read array $data diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 3918338e..bea55e4b 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -12,8 +12,6 @@ /** * Passwords tools. - * - * @author David Grudl */ class Passwords { diff --git a/src/Security/Permission.php b/src/Security/Permission.php index d94d90eb..52ee5a49 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -16,7 +16,6 @@ * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license * * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. - * @author David Grudl * * @property-read array $roles * @property-read array $resources diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index f3871eca..db0e7f71 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -12,8 +12,6 @@ /** * Trivial implementation of IAuthenticator. - * - * @author David Grudl */ class SimpleAuthenticator extends Nette\Object implements IAuthenticator { diff --git a/src/Security/User.php b/src/Security/User.php index 687c3ac5..18ea800b 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -13,8 +13,6 @@ /** * User authentication and authorization. * - * @author David Grudl - * * @property-read bool $loggedIn * @property-read IIdentity $identity * @property-read mixed $id From 087c05534f45f9a7a3778b083d1033207eeb2bc6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Jun 2015 16:00:35 +0200 Subject: [PATCH 025/254] typo: removed unused use --- src/Security/AuthenticationException.php | 2 -- src/Security/IAuthenticator.php | 2 -- src/Security/IAuthorizator.php | 2 -- src/Security/IIdentity.php | 2 -- src/Security/IResource.php | 2 -- src/Security/IRole.php | 2 -- src/Security/IUserStorage.php | 2 -- 7 files changed, 14 deletions(-) diff --git a/src/Security/AuthenticationException.php b/src/Security/AuthenticationException.php index b6333145..3cb18db1 100644 --- a/src/Security/AuthenticationException.php +++ b/src/Security/AuthenticationException.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Authentication exception. diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 4ce9c60d..810d0d8a 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Performs authentication. diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index e87981bc..5f37050f 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Authorizator checks if a given role has authorization diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index a44f4b94..cc316509 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Represents the user of application. diff --git a/src/Security/IResource.php b/src/Security/IResource.php index 4a546ec7..d2d87ae5 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Represents resource, an object to which access is controlled. diff --git a/src/Security/IRole.php b/src/Security/IRole.php index eab29bfa..aed8e0b8 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Represents role, an object that may request access to an IResource. diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index e27271c0..e009c37b 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -7,8 +7,6 @@ namespace Nette\Security; -use Nette; - /** * Interface for persistent storage for user object data. From e1b0f97234bc271b0bab518eb18cfeacc0c36d77 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 11 Jul 2015 22:59:23 +0200 Subject: [PATCH 026/254] travis: migrating to container-based infrastructure --- .travis.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87920592..83564d57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,11 @@ after_failure: before_script: # Install Nette Tester & Code Checker - - composer update --no-interaction --prefer-source $dependencies - - composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction --prefer-source + - travis_retry composer update --no-interaction --prefer-dist $dependencies + - travis_retry composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction + +sudo: false + +cache: + directories: + - $HOME/.composer/cache From 2609fe4956c31d177b7ccf29ccbb0f5319c89372 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 15 Aug 2015 11:21:14 +0200 Subject: [PATCH 027/254] Passwords::hash() removed option 'salt' (BC break) --- src/Security/Passwords.php | 11 ++++------- tests/Security/Passwords.hash().phpt | 8 ++------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index bea55e4b..38cf8339 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -21,20 +21,17 @@ class Passwords /** * Computes salted password hash. * @param string - * @param array with cost (4-31), salt (22 chars) + * @param array with cost (4-31) * @return string 60 chars long */ public static function hash($password, array $options = NULL) { $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST; - $salt = isset($options['salt']) ? (string) $options['salt'] : Nette\Utils\Random::generate(22, '0-9A-Za-z./'); - - if (($len = strlen($salt)) < 22) { - throw new Nette\InvalidArgumentException("Salt must be 22 characters long, $len given."); - } elseif ($cost < 4 || $cost > 31) { + if ($cost < 4 || $cost > 31) { throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $cost given."); } + $salt = Nette\Utils\Random::generate(22, '0-9A-Za-z./'); $hash = crypt($password, '$2y$' . ($cost < 10 ? 0 : '') . $cost . '$' . $salt); if (strlen($hash) < 60) { throw new Nette\InvalidStateException('Hash returned by crypt is invalid.'); @@ -51,7 +48,7 @@ public static function verify($password, $hash) { return preg_match('#^\$2y\$(?P\d\d)\$(?P.{22})#', $hash, $m) && $m['cost'] >= 4 && $m['cost'] <= 31 - && self::hash($password, $m) === $hash; + && crypt($password, $hash) === $hash; } diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 39aa92cd..474560a6 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -17,8 +17,8 @@ Assert::truthy( ); Assert::truthy( - preg_match('#^\$2y\$05\$123456789012345678901.{32}\z#', - $h = Passwords::hash('dg', ['cost' => 5, 'salt' => '1234567890123456789012'])) + preg_match('#^\$2y\$05\$.{53}\z#', + $h = Passwords::hash('dg', ['cost' => 5])) ); echo $h; @@ -33,7 +33,3 @@ Assert::exception(function () { Assert::exception(function () { Passwords::hash('dg', ['cost' => 32]); }, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 32 given.'); - -Assert::exception(function () { - Passwords::hash('dg', ['salt' => 'abc']); -}, 'Nette\InvalidArgumentException', 'Salt must be 22 characters long, 3 given.'); From 0db1d3cfedb3f869d6b0c85a4c3e0779fb242670 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sun, 23 Aug 2015 21:14:00 +0200 Subject: [PATCH 028/254] fixed using undefined classes in typehints --- src/Security/IUserStorage.php | 2 +- src/Security/User.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index e009c37b..61fb4945 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -48,7 +48,7 @@ function getIdentity(); /** * Enables log out from the persistent storage after inactivity. - * @param string|int|DateTime number of seconds or timestamp + * @param string|int|\DateTime number of seconds or timestamp * @param int Log out when the browser is closed | Clear the identity from persistent storage? * @return void */ diff --git a/src/Security/User.php b/src/Security/User.php index 18ea800b..84dd5efb 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -165,7 +165,7 @@ public function getAuthenticator($need = TRUE) /** * Enables log out after inactivity. - * @param string|int|DateTime number of seconds or timestamp + * @param string|int|\DateTime number of seconds or timestamp * @param int log out when the browser is closed? | clear the identity from persistent storage? * @return self */ From 68853f9f827630bf8d7536b9e327c2c253190bce Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sun, 23 Aug 2015 23:57:55 +0200 Subject: [PATCH 029/254] requires PHP 5.6.0 --- .travis.yml | 2 -- composer.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83564d57..4d2d58e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: php php: - - 5.4 - - 5.5 - 5.6 - 7.0 - hhvm diff --git a/composer.json b/composer.json index a7e7b7de..f5e10187 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": ">=5.4.4", + "php": ">=5.6.0", "nette/utils": "~2.2" }, "require-dev": { From f7e0ebeeee2a53bfaa16dfe437c21e46b09556af Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sun, 23 Aug 2015 23:59:18 +0200 Subject: [PATCH 030/254] use ClassName::class syntax --- src/Bridges/SecurityDI/SecurityExtension.php | 16 ++++++++-------- .../SecurityExtension.authenticator.phpt | 2 +- tests/Security.DI/SecurityExtension.user.phpt | 4 ++-- tests/Security/Passwords.hash().phpt | 4 ++-- .../Permission.IsAllowedNonExistent.phpt | 4 ++-- ...ermission.ResourceAddInheritsNonExistent.phpt | 2 +- tests/Security/Permission.ResourceDuplicate.phpt | 2 +- .../Permission.ResourceInheritsNonExistent.phpt | 4 ++-- .../Permission.ResourceRemoveOneNonExistent.phpt | 2 +- ...ssion.RoleRegistryAddInheritsNonExistent.phpt | 2 +- .../Permission.RoleRegistryDuplicate.phpt | 2 +- ...rmission.RoleRegistryInheritsNonExistent.phpt | 4 ++-- ...mission.RoleRegistryRemoveOneNonExistent.phpt | 2 +- tests/Security/Permission.RuleRoleRemove.phpt | 2 +- tests/Security/Permission.RuleRoleRemoveAll.phpt | 2 +- .../Security/Permission.RulesResourceRemove.phpt | 2 +- .../Permission.RulesResourceRemoveAll.phpt | 2 +- tests/Security/SimpleAuthenticator.phpt | 8 ++++---- tests/Security/User.authentication.phpt | 6 +++--- tests/Security/User.authorization.phpt | 2 +- 20 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 074ba4f6..908a3649 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -38,15 +38,15 @@ public function loadConfiguration() $container = $this->getContainerBuilder(); $container->addDefinition($this->prefix('userStorage')) - ->setClass('Nette\Security\IUserStorage') - ->setFactory('Nette\Http\UserStorage'); + ->setClass(Nette\Security\IUserStorage::class) + ->setFactory(Nette\Http\UserStorage::class); $user = $container->addDefinition($this->prefix('user')) - ->setClass('Nette\Security\User'); + ->setClass(Nette\Security\User::class); if ($this->debugMode && $config['debugger']) { $user->addSetup('@Tracy\Bar::addPanel', [ - new Nette\DI\Statement('Nette\Bridges\SecurityTracy\UserPanel'), + new Nette\DI\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), ]); } @@ -60,8 +60,8 @@ public function loadConfiguration() } $container->addDefinition($this->prefix('authenticator')) - ->setClass('Nette\Security\IAuthenticator') - ->setFactory('Nette\Security\SimpleAuthenticator', [$usersList, $usersRoles]); + ->setClass(Nette\Security\IAuthenticator::class) + ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]); if ($this->name === 'security') { $container->addAlias('nette.authenticator', $this->prefix('authenticator')); @@ -70,8 +70,8 @@ public function loadConfiguration() if ($config['roles'] || $config['resources']) { $authorizator = $container->addDefinition($this->prefix('authorizator')) - ->setClass('Nette\Security\IAuthorizator') - ->setFactory('Nette\Security\Permission'); + ->setClass(Nette\Security\IAuthorizator::class) + ->setFactory(Nette\Security\Permission::class); foreach ($config['roles'] as $role => $parents) { $authorizator->addSetup('addRole', [$role, $parents]); diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index 8a26fb1b..c69fad9c 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -33,7 +33,7 @@ eval($compiler->compile($config, 'Container1')); $container = new Container1; $authenticator = $container->getService('security.authenticator'); -Assert::type('Nette\Security\SimpleAuthenticator', $authenticator); +Assert::type(Nette\Security\SimpleAuthenticator::class, $authenticator); Assert::same($authenticator, $container->getService('nette.authenticator')); $userList = [ diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index b24df3f7..472c3bba 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -22,8 +22,8 @@ $compiler->addExtension('security', new SecurityExtension); eval($compiler->compile([], 'Container1')); $container = new Container1; -Assert::type('Nette\Http\UserStorage', $container->getService('security.userStorage')); -Assert::type('Nette\Security\User', $container->getService('security.user')); +Assert::type(Nette\Http\UserStorage::class, $container->getService('security.userStorage')); +Assert::type(Nette\Security\User::class, $container->getService('security.user')); // aliases Assert::same($container->getService('security.userStorage'), $container->getService('nette.userStorage')); diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 474560a6..dd1080a0 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -28,8 +28,8 @@ Assert::same($hash, crypt('dg', $hash)); Assert::exception(function () { Passwords::hash('dg', ['cost' => 3]); -}, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 3 given.'); +}, Nette\InvalidArgumentException::class, 'Cost must be in range 4-31, 3 given.'); Assert::exception(function () { Passwords::hash('dg', ['cost' => 32]); -}, 'Nette\InvalidArgumentException', 'Cost must be in range 4-31, 32 given.'); +}, Nette\InvalidArgumentException::class, 'Cost must be in range 4-31, 32 given.'); diff --git a/tests/Security/Permission.IsAllowedNonExistent.phpt b/tests/Security/Permission.IsAllowedNonExistent.phpt index e7f3b9f7..433aea73 100644 --- a/tests/Security/Permission.IsAllowedNonExistent.phpt +++ b/tests/Security/Permission.IsAllowedNonExistent.phpt @@ -14,9 +14,9 @@ require __DIR__ . '/../bootstrap.php'; Assert::exception(function () { $acl = new Permission; $acl->isAllowed('nonexistent'); -}, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); Assert::exception(function () { $acl = new Permission; $acl->isAllowed(NULL, 'nonexistent'); -}, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt index fc6836de..07db81eb 100644 --- a/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceAddInheritsNonExistent.phpt @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; Assert::exception(function () use ($acl) { $acl->addResource('area', 'nonexistent'); -}, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.ResourceDuplicate.phpt b/tests/Security/Permission.ResourceDuplicate.phpt index 7e071875..72359557 100644 --- a/tests/Security/Permission.ResourceDuplicate.phpt +++ b/tests/Security/Permission.ResourceDuplicate.phpt @@ -15,4 +15,4 @@ Assert::exception(function () { $acl = new Permission; $acl->addResource('area'); $acl->addResource('area'); -}, 'Nette\InvalidStateException', "Resource 'area' already exists in the list."); +}, 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 8e8569c6..b37cc5a1 100644 --- a/tests/Security/Permission.ResourceInheritsNonExistent.phpt +++ b/tests/Security/Permission.ResourceInheritsNonExistent.phpt @@ -15,8 +15,8 @@ $acl = new Permission; $acl->addResource('area'); Assert::exception(function () use ($acl) { $acl->resourceInheritsFrom('nonexistent', 'area'); -}, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); Assert::exception(function () use ($acl) { $acl->resourceInheritsFrom('area', 'nonexistent'); -}, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt index ae996b31..2bdeb8aa 100644 --- a/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.ResourceRemoveOneNonExistent.phpt @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; Assert::exception(function () use ($acl) { $acl->removeResource('nonexistent'); -}, 'Nette\InvalidStateException', "Resource 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt index 974ca66b..b49637ce 100644 --- a/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryAddInheritsNonExistent.phpt @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; Assert::exception(function () use ($acl) { $acl->addRole('guest', 'nonexistent'); -}, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleRegistryDuplicate.phpt b/tests/Security/Permission.RoleRegistryDuplicate.phpt index 621f66f0..f569239c 100644 --- a/tests/Security/Permission.RoleRegistryDuplicate.phpt +++ b/tests/Security/Permission.RoleRegistryDuplicate.phpt @@ -15,4 +15,4 @@ $acl = new Permission; Assert::exception(function () use ($acl) { $acl->addRole('guest'); $acl->addRole('guest'); -}, 'Nette\InvalidStateException', "Role 'guest' already exists in the list."); +}, 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 765196cb..3365ce1f 100644 --- a/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt @@ -15,8 +15,8 @@ $acl = new Permission; $acl->addRole('guest'); Assert::exception(function () use ($acl) { $acl->roleInheritsFrom('nonexistent', 'guest'); -}, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); Assert::exception(function () use ($acl) { $acl->roleInheritsFrom('guest', 'nonexistent'); -}, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt index 0ecc4207..db037840 100644 --- a/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt +++ b/tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; Assert::exception(function () use ($acl) { $acl->removeRole('nonexistent'); -}, 'Nette\InvalidStateException', "Role 'nonexistent' does not exist."); +}, Nette\InvalidStateException::class, "Role 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.RuleRoleRemove.phpt b/tests/Security/Permission.RuleRoleRemove.phpt index 466a1312..c170ed2a 100644 --- a/tests/Security/Permission.RuleRoleRemove.phpt +++ b/tests/Security/Permission.RuleRoleRemove.phpt @@ -18,7 +18,7 @@ Assert::true($acl->isAllowed('guest')); $acl->removeRole('guest'); Assert::exception(function () use ($acl) { $acl->isAllowed('guest'); -}, 'Nette\InvalidStateException', "Role 'guest' does not exist."); +}, 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 9b428477..b30a8bf9 100644 --- a/tests/Security/Permission.RuleRoleRemoveAll.phpt +++ b/tests/Security/Permission.RuleRoleRemoveAll.phpt @@ -18,7 +18,7 @@ Assert::true($acl->isAllowed('guest')); $acl->removeAllRoles(); Assert::exception(function () use ($acl) { $acl->isAllowed('guest'); -}, 'Nette\InvalidStateException', "Role 'guest' does not exist."); +}, 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 560cd9db..1e9b7c28 100644 --- a/tests/Security/Permission.RulesResourceRemove.phpt +++ b/tests/Security/Permission.RulesResourceRemove.phpt @@ -18,7 +18,7 @@ Assert::true($acl->isAllowed(NULL, 'area')); $acl->removeResource('area'); Assert::exception(function () use ($acl) { $acl->isAllowed(NULL, 'area'); -}, 'Nette\InvalidStateException', "Resource 'area' does not exist."); +}, 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 a1b7bc6b..7afc8741 100644 --- a/tests/Security/Permission.RulesResourceRemoveAll.phpt +++ b/tests/Security/Permission.RulesResourceRemoveAll.phpt @@ -18,7 +18,7 @@ Assert::true($acl->isAllowed(NULL, 'area')); $acl->removeAllResources(); Assert::exception(function () use ($acl) { $acl->isAllowed(NULL, 'area'); -}, 'Nette\InvalidStateException', "Resource 'area' does not exist."); +}, 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 993c6bb3..1dd22669 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -19,17 +19,17 @@ $users = [ $authenticator = new SimpleAuthenticator($users); $identity = $authenticator->authenticate(['john', 'password123!']); -Assert::type('Nette\Security\IIdentity', $identity); +Assert::type(Nette\Security\IIdentity::class, $identity); Assert::equal('john', $identity->getId()); $identity = $authenticator->authenticate(['admin', 'admin']); -Assert::type('Nette\Security\IIdentity', $identity); +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', 'Invalid password.'); +}, Nette\Security\AuthenticationException::class, 'Invalid password.'); Assert::exception(function () use ($authenticator) { $authenticator->authenticate(['nobody', 'password']); -}, 'Nette\Security\AuthenticationException', "User 'nobody' not found."); +}, Nette\Security\AuthenticationException::class, "User 'nobody' not found."); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index ca534907..5f10132a 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -66,7 +66,7 @@ Assert::null($user->getId()); Assert::exception(function () use ($user) { // login without handler $user->login('jane', ''); -}, 'Nette\InvalidStateException', 'Authenticator has not been set.'); +}, Nette\InvalidStateException::class, 'Authenticator has not been set.'); $handler = new Authenticator; $user->setAuthenticator($handler); @@ -74,12 +74,12 @@ $user->setAuthenticator($handler); Assert::exception(function () use ($user) { // login as jane $user->login('jane', ''); -}, 'Nette\Security\AuthenticationException', 'Unknown user'); +}, Nette\Security\AuthenticationException::class, 'Unknown user'); Assert::exception(function () use ($user) { // login as john $user->login('john', ''); -}, 'Nette\Security\AuthenticationException', 'Password not match'); +}, 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 806f7945..3a6920bb 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -86,7 +86,7 @@ Assert::false($user->isInRole('guest')); // authorization Assert::exception(function () use ($user) { $user->isAllowed('delete_file'); -}, 'Nette\InvalidStateException', 'Authorizator has not been set.'); +}, Nette\InvalidStateException::class, 'Authorizator has not been set.'); $handler = new Authorizator; $user->setAuthorizator($handler); From f3b0471a317a27f34041116cdb8e300b9127e920 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Mon, 24 Aug 2015 22:36:49 +0200 Subject: [PATCH 031/254] use DateTimeInterface --- src/Security/IUserStorage.php | 2 +- src/Security/User.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 61fb4945..a23c1caa 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -48,7 +48,7 @@ function getIdentity(); /** * Enables log out from the persistent storage after inactivity. - * @param string|int|\DateTime number of seconds or timestamp + * @param string|int|\DateTimeInterface number of seconds or timestamp * @param int Log out when the browser is closed | Clear the identity from persistent storage? * @return void */ diff --git a/src/Security/User.php b/src/Security/User.php index 84dd5efb..1a71f107 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -165,7 +165,7 @@ public function getAuthenticator($need = TRUE) /** * Enables log out after inactivity. - * @param string|int|\DateTime number of seconds or timestamp + * @param string|int|\DateTimeInterface number of seconds or timestamp * @param int log out when the browser is closed? | clear the identity from persistent storage? * @return self */ From b1da1624ab13a3ce8486c4352f4a40a15933a4d2 Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Wed, 26 Aug 2015 08:20:51 +0200 Subject: [PATCH 032/254] Passwords: simplified with password_* API --- src/Security/Passwords.php | 25 ++++++++++--------------- tests/Security/Passwords.verify().phpt | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 38cf8339..0acc91c5 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -15,6 +15,7 @@ */ class Passwords { + /** @deprecated */ const BCRYPT_COST = 10; @@ -24,17 +25,15 @@ class Passwords * @param array with cost (4-31) * @return string 60 chars long */ - public static function hash($password, array $options = NULL) + public static function hash($password, array $options = []) { - $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST; - if ($cost < 4 || $cost > 31) { - throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $cost given."); + if (isset($options['cost']) && ($options['cost'] < 4 || $options['cost'] > 31)) { + throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $options[cost] given."); } - $salt = Nette\Utils\Random::generate(22, '0-9A-Za-z./'); - $hash = crypt($password, '$2y$' . ($cost < 10 ? 0 : '') . $cost . '$' . $salt); - if (strlen($hash) < 60) { - throw new Nette\InvalidStateException('Hash returned by crypt is invalid.'); + $hash = password_hash($password, PASSWORD_BCRYPT, $options); + if ($hash === FALSE || strlen($hash) < 60) { + throw new Nette\InvalidStateException('Hash computed by password_hash is invalid.'); } return $hash; } @@ -46,9 +45,7 @@ public static function hash($password, array $options = NULL) */ public static function verify($password, $hash) { - return preg_match('#^\$2y\$(?P\d\d)\$(?P.{22})#', $hash, $m) - && $m['cost'] >= 4 && $m['cost'] <= 31 - && crypt($password, $hash) === $hash; + return password_verify($password, $hash); } @@ -58,11 +55,9 @@ public static function verify($password, $hash) * @param array with cost (4-31) * @return bool */ - public static function needsRehash($hash, array $options = NULL) + public static function needsRehash($hash, array $options = []) { - $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST; - return !preg_match('#^\$2y\$(?P\d\d)\$(?P.{22})#', $hash, $m) - || $m['cost'] < $cost; + return password_needs_rehash($hash, PASSWORD_BCRYPT, $options); } } diff --git a/tests/Security/Passwords.verify().phpt b/tests/Security/Passwords.verify().phpt index 6ac54692..b1d25dd7 100644 --- a/tests/Security/Passwords.verify().phpt +++ b/tests/Security/Passwords.verify().phpt @@ -12,5 +12,5 @@ require __DIR__ . '/../bootstrap.php'; Assert::true(Passwords::verify('dg', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::false(Passwords::verify('dg', '$2x$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +Assert::true(Passwords::verify('dg', '$2x$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); Assert::false(Passwords::verify('dgx', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); From 9583bc3cf618577de630a9616ef136cc02b4eda6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 23 Sep 2015 18:28:49 +0200 Subject: [PATCH 033/254] removed rarely used @property phpDoc --- src/Security/Identity.php | 1 - src/Security/Permission.php | 5 ----- src/Security/User.php | 1 - 3 files changed, 7 deletions(-) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 0a358af5..85f744ee 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -15,7 +15,6 @@ * * @property mixed $id * @property array $roles - * @property-read array $data */ class Identity extends Nette\Object implements IIdentity { diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 52ee5a49..8e98524e 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -16,11 +16,6 @@ * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license * * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. - * - * @property-read array $roles - * @property-read array $resources - * @property-read mixed $queriedRole - * @property-read mixed $queriedResource */ class Permission extends Nette\Object implements IAuthorizator { diff --git a/src/Security/User.php b/src/Security/User.php index 1a71f107..73b60c6b 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -18,7 +18,6 @@ * @property-read mixed $id * @property-read array $roles * @property-read int $logoutReason - * @property-read IUserStorage $storage * @property IAuthenticator $authenticator * @property IAuthorizator $authorizator */ From d65e02643109b3a101a7568cc2f4f81eed2f050c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 5 Oct 2015 14:35:47 +0200 Subject: [PATCH 034/254] used https --- composer.json | 6 +++--- contributing.md | 4 ++-- license.md | 2 +- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- src/Bridges/SecurityTracy/UserPanel.php | 4 ++-- src/Security/AuthenticationException.php | 4 ++-- src/Security/IAuthenticator.php | 4 ++-- src/Security/IAuthorizator.php | 4 ++-- src/Security/IIdentity.php | 4 ++-- src/Security/IResource.php | 4 ++-- src/Security/IRole.php | 4 ++-- src/Security/IUserStorage.php | 4 ++-- src/Security/Identity.php | 4 ++-- src/Security/Passwords.php | 4 ++-- src/Security/Permission.php | 4 ++-- src/Security/SimpleAuthenticator.php | 4 ++-- src/Security/User.php | 4 ++-- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index f5e10187..9e0b71c6 100644 --- a/composer.json +++ b/composer.json @@ -1,16 +1,16 @@ { "name": "nette/security", "description": "Nette Security: Access Control Component", - "homepage": "http://nette.org", + "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"], "authors": [ { "name": "David Grudl", - "homepage": "http://davidgrudl.com" + "homepage": "https://davidgrudl.com" }, { "name": "Nette Community", - "homepage": "http://nette.org/contributors" + "homepage": "https://nette.org/contributors" } ], "require": { diff --git a/contributing.md b/contributing.md index a1cbbd53..860882bf 100644 --- a/contributing.md +++ b/contributing.md @@ -5,7 +5,7 @@ The issue tracker is the preferred channel for bug reports, features requests and submitting pull requests, but please respect the following restrictions: * Please **do not** use the issue tracker for personal support requests (use - [Nette forum](http://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)). + [Nette forum](https://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)). * Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. @@ -21,7 +21,7 @@ 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. Nette welcomes **pull requests**. If you'd like to contribute, please take a moment -to [read the guidelines](http://nette.org/en/contributing) in order to make +to [read the guidelines](https://nette.org/en/contributing) in order to make the contribution process easy and effective for everyone involved. Thanks! diff --git a/license.md b/license.md index af571d59..cf741bd0 100644 --- a/license.md +++ b/license.md @@ -21,7 +21,7 @@ If your stuff is good, it will not take long to establish a reputation for yours New BSD License --------------- -Copyright (c) 2004, 2014 David Grudl (http://davidgrudl.com) +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 908a3649..e202f45d 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -1,8 +1,8 @@ Date: Mon, 5 Oct 2015 14:36:12 +0200 Subject: [PATCH 035/254] travis: removed PHP 7 from allowed failures --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d2d58e4..e447d651 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ php: matrix: allow_failures: - - php: 7.0 - php: hhvm include: From a738fcb0e46b82bffd6bff94fa148fbd1e001016 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 6 Feb 2016 23:05:37 +0100 Subject: [PATCH 036/254] typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e447d651..700267bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ before_script: # Install Nette Tester & Code Checker - travis_retry composer update --no-interaction --prefer-dist $dependencies - travis_retry composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction - + sudo: false cache: From ccbcb6cea7411218c90a739255d68de3a97c2931 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Feb 2016 14:12:39 +0100 Subject: [PATCH 037/254] UserPanel: ob_start() is protected against flush when error occurs --- src/Bridges/SecurityTracy/UserPanel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index ea0e76c1..46ca603a 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -36,7 +36,7 @@ public function getTab() return; } - ob_start(); + ob_start(function () {}); $user = $this->user; require __DIR__ . '/templates/UserPanel.tab.phtml'; return ob_get_clean(); @@ -49,7 +49,7 @@ public function getTab() */ public function getPanel() { - ob_start(); + ob_start(function () {}); $user = $this->user; require __DIR__ . '/templates/UserPanel.panel.phtml'; return ob_get_clean(); From e86e8e4564c235ac27a7ac5c0e913e3a9b662949 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 15 Mar 2016 19:56:27 +0000 Subject: [PATCH 038/254] composer: added Tracy --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9e0b71c6..4a3a8947 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "require-dev": { "nette/di": "~2.3", "nette/http": "~2.3", - "nette/tester": "~1.4" + "nette/tester": "~1.4", + "tracy/tracy": "^2.3" }, "conflict": { "nette/nette": "<2.2" From c150cb9ccff04d6068b3288bfc2ebfd4d21da287 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Apr 2016 00:18:58 +0200 Subject: [PATCH 039/254] uses Nette\SmartObject & StaticClass --- composer.json | 2 +- src/Bridges/SecurityTracy/UserPanel.php | 4 +++- src/Security/Identity.php | 30 ++++++++++--------------- src/Security/Passwords.php | 2 ++ src/Security/Permission.php | 4 +++- src/Security/SimpleAuthenticator.php | 4 +++- src/Security/User.php | 4 +++- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 4a3a8947..17f156f2 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ ], "require": { "php": ">=5.6.0", - "nette/utils": "~2.2" + "nette/utils": "~2.4" }, "require-dev": { "nette/di": "~2.3", diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 46ca603a..5bfd0a92 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -14,8 +14,10 @@ /** * User panel for Debugger Bar. */ -class UserPanel extends Nette\Object implements Tracy\IBarPanel +class UserPanel implements Tracy\IBarPanel { + use Nette\SmartObject; + /** @var Nette\Security\User */ private $user; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index de975659..04ccf287 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -16,8 +16,14 @@ * @property mixed $id * @property array $roles */ -class Identity extends Nette\Object implements IIdentity +class Identity implements IIdentity { + use Nette\SmartObject { + __get as private parentGet; + __set as private parentSet; + __isset as private parentIsSet; + } + /** @var mixed */ private $id; @@ -103,8 +109,8 @@ public function getData() */ public function __set($key, $value) { - if (parent::__isset($key)) { - parent::__set($key, $value); + if ($this->parentIsSet($key)) { + $this->parentSet($key, $value); } else { $this->data[$key] = $value; @@ -119,8 +125,8 @@ public function __set($key, $value) */ public function &__get($key) { - if (parent::__isset($key)) { - return parent::__get($key); + if ($this->parentIsSet($key)) { + return $this->parentGet($key); } else { return $this->data[$key]; @@ -135,19 +141,7 @@ public function &__get($key) */ public function __isset($key) { - return isset($this->data[$key]) || parent::__isset($key); - } - - - /** - * Removes property. - * @param string property name - * @return void - * @throws Nette\MemberAccessException - */ - public function __unset($name) - { - Nette\Utils\ObjectMixin::remove($this, $name); + return isset($this->data[$key]) || $this->parentIsSet($key); } } diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 43f5779d..26ad2fb3 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -15,6 +15,8 @@ */ class Passwords { + use Nette\StaticClass; + /** @deprecated */ const BCRYPT_COST = 10; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 92929e17..6e243ccb 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -17,8 +17,10 @@ * * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. */ -class Permission extends Nette\Object implements IAuthorizator +class Permission implements IAuthorizator { + use Nette\SmartObject; + /** @var array Role storage */ private $roles = []; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 94949337..ac3dfeed 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -13,8 +13,10 @@ /** * Trivial implementation of IAuthenticator. */ -class SimpleAuthenticator extends Nette\Object implements IAuthenticator +class SimpleAuthenticator implements IAuthenticator { + use Nette\SmartObject; + /** @var array */ private $userlist; diff --git a/src/Security/User.php b/src/Security/User.php index 4ac799ec..c61545dd 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -21,8 +21,10 @@ * @property IAuthenticator $authenticator * @property IAuthorizator $authorizator */ -class User extends Nette\Object +class User { + use Nette\SmartObject; + /** @deprecated */ const MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::INACTIVITY, From 79f650dcc9517219d661fcaae3a9cf53251bdbcc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 Apr 2016 17:34:47 +0200 Subject: [PATCH 040/254] tests/travis: reports code coverage to Coveralls --- .travis.yml | 11 ++++++++++- readme.md | 1 + tests/.coveralls.yml | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/.coveralls.yml diff --git a/.travis.yml b/.travis.yml index 700267bc..7d58d6dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: env: dependencies="--prefer-lowest --prefer-stable" script: - - vendor/bin/tester tests -s -p php + - vendor/bin/tester tests -s -p php $coverage - php temp/code-checker/src/code-checker.php --short-arrays after_failure: @@ -24,6 +24,15 @@ before_script: # Install Nette Tester & Code Checker - travis_retry composer update --no-interaction --prefer-dist $dependencies - travis_retry composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction + - if [ $TRAVIS_PHP_VERSION == "7.0" ]; then coverage="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi + +after_script: + # Report Code Coverage + - > + if [ "$coverage" != "" ]; then + wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar + && php coveralls.phar --verbose --config tests/.coveralls.yml + || true; fi sudo: false diff --git a/readme.md b/readme.md index 48e53c5f..17e2c204 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,7 @@ Nette Security: Access Control [![Downloads this Month](https://img.shields.io/packagist/dm/nette/security.svg)](https://packagist.org/packages/nette/security) [![Build Status](https://travis-ci.org/nette/security.svg?branch=master)](https://travis-ci.org/nette/security) +[![Coverage Status](https://coveralls.io/repos/github/nette/security/badge.svg?branch=master)](https://coveralls.io/github/nette/security?branch=master) [![Latest Stable Version](https://poser.pugx.org/nette/security/v/stable)](https://github.com/nette/security/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/security/blob/master/license.md) diff --git a/tests/.coveralls.yml b/tests/.coveralls.yml new file mode 100644 index 00000000..82764a3f --- /dev/null +++ b/tests/.coveralls.yml @@ -0,0 +1,4 @@ +# for php-coveralls +service_name: travis-ci +coverage_clover: coverage.xml +json_path: coverage.json From b8894b54e10d0d4a1fc40e956d80e00f61d13a8f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 3 May 2016 16:55:41 +0200 Subject: [PATCH 041/254] Revert "User: bool parameters replaced with bit masks" This reverts commit c10664961229dcf12a95bd5e4b0f6e6d39495188. --- src/Security/User.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index c61545dd..5872a0bb 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -28,8 +28,7 @@ class User /** @deprecated */ const MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::INACTIVITY, - BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED, - CLEAR_IDENTITY = IUserStorage::CLEAR_IDENTITY; + BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED; /** @var string default role for unauthenticated user */ public $guestRole = 'guest'; @@ -82,7 +81,7 @@ public function getStorage() */ public function login($id = NULL, $password = NULL) { - $this->logout(IUserStorage::CLEAR_IDENTITY); + $this->logout(TRUE); if (!$id instanceof IIdentity) { $id = $this->getAuthenticator()->authenticate(func_get_args()); } @@ -94,16 +93,16 @@ public function login($id = NULL, $password = NULL) /** * Logs out the user from the current session. - * @param int clear the identity from persistent storage? + * @param bool clear the identity from persistent storage? * @return void */ - public function logout($flags = NULL) + public function logout($clearIdentity = FALSE) { if ($this->isLoggedIn()) { $this->onLoggedOut($this); $this->storage->setAuthenticated(FALSE); } - if ($flags === TRUE || ($flags & IUserStorage::CLEAR_IDENTITY)) { + if ($clearIdentity) { $this->storage->setIdentity(NULL); } } @@ -167,13 +166,13 @@ public function getAuthenticator($need = TRUE) /** * Enables log out after inactivity. * @param string|int|\DateTimeInterface number of seconds or timestamp - * @param int log out when the browser is closed? | clear the identity from persistent storage? + * @param bool log out when the browser is closed? + * @param bool clear the identity from persistent storage? * @return self */ - public function setExpiration($time, $flags = IUserStorage::BROWSER_CLOSED, $clearIdentity = FALSE) + public function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE) { - $flags = ($flags === TRUE ? IUserStorage::BROWSER_CLOSED : $flags) // back compatibility - | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); + $flags = ($whenBrowserIsClosed ? IUserStorage::BROWSER_CLOSED : 0) | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); $this->storage->setExpiration($time, $flags); return $this; } From 5e44eb8fd693d5475f242ec53ffcbb23a5bf2e98 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 7 May 2016 13:11:19 +0200 Subject: [PATCH 042/254] renamed some local vars --- src/Bridges/SecurityDI/SecurityExtension.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index e202f45d..0a86194b 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -35,13 +35,13 @@ public function __construct($debugMode = FALSE) public function loadConfiguration() { $config = $this->validateConfig($this->defaults); - $container = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder(); - $container->addDefinition($this->prefix('userStorage')) + $builder->addDefinition($this->prefix('userStorage')) ->setClass(Nette\Security\IUserStorage::class) ->setFactory(Nette\Http\UserStorage::class); - $user = $container->addDefinition($this->prefix('user')) + $user = $builder->addDefinition($this->prefix('user')) ->setClass(Nette\Security\User::class); if ($this->debugMode && $config['debugger']) { @@ -59,17 +59,17 @@ public function loadConfiguration() $usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL; } - $container->addDefinition($this->prefix('authenticator')) + $builder->addDefinition($this->prefix('authenticator')) ->setClass(Nette\Security\IAuthenticator::class) ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]); if ($this->name === 'security') { - $container->addAlias('nette.authenticator', $this->prefix('authenticator')); + $builder->addAlias('nette.authenticator', $this->prefix('authenticator')); } } if ($config['roles'] || $config['resources']) { - $authorizator = $container->addDefinition($this->prefix('authorizator')) + $authorizator = $builder->addDefinition($this->prefix('authorizator')) ->setClass(Nette\Security\IAuthorizator::class) ->setFactory(Nette\Security\Permission::class); @@ -81,13 +81,13 @@ public function loadConfiguration() } if ($this->name === 'security') { - $container->addAlias('nette.authorizator', $this->prefix('authorizator')); + $builder->addAlias('nette.authorizator', $this->prefix('authorizator')); } } if ($this->name === 'security') { - $container->addAlias('user', $this->prefix('user')); - $container->addAlias('nette.userStorage', $this->prefix('userStorage')); + $builder->addAlias('user', $this->prefix('user')); + $builder->addAlias('nette.userStorage', $this->prefix('userStorage')); } } From f0b9151344d32882628e8774c51c69a3632ad135 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 12 May 2016 21:48:58 +0200 Subject: [PATCH 043/254] tests: compatible with master DI --- tests/Security.DI/SecurityExtension.authenticator.phpt | 4 ++-- tests/Security.DI/SecurityExtension.user.phpt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index c69fad9c..e783f007 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -29,8 +29,8 @@ security: moderator: {password: moderator123, roles: moderator} ', 'neon')); -eval($compiler->compile($config, 'Container1')); -$container = new Container1; +eval($compiler->addConfig($config)->compile()); +$container = new Container; $authenticator = $container->getService('security.authenticator'); Assert::type(Nette\Security\SimpleAuthenticator::class, $authenticator); diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index 472c3bba..80c42e43 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -19,8 +19,8 @@ $compiler->addExtension('foo', new HttpExtension); $compiler->addExtension('bar', new SessionExtension); $compiler->addExtension('security', new SecurityExtension); -eval($compiler->compile([], 'Container1')); -$container = new Container1; +eval($compiler->compile()); +$container = new Container; Assert::type(Nette\Http\UserStorage::class, $container->getService('security.userStorage')); Assert::type(Nette\Security\User::class, $container->getService('security.user')); From 6d8508b7d2cec24f1b3c718941c6edcaf41b00d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Zl=C3=A1mal?= Date: Tue, 17 May 2016 14:11:41 +0200 Subject: [PATCH 044/254] Identity: add property annotation (#18) --- src/Security/Identity.php | 1 + tests/Security/Identity.phpt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 04ccf287..2a896c74 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -15,6 +15,7 @@ * * @property mixed $id * @property array $roles + * @property array $data */ class Identity implements IIdentity { diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index c93cca82..1936f9f7 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -19,6 +19,7 @@ test(function () { Assert::same(['admin'], $id->getRoles()); Assert::same(['admin'], $id->roles); Assert::same(['name' => 'John'], $id->getData()); + Assert::same(['name' => 'John'], $id->data); Assert::same('John', $id->name); }); From 3fd0ee289b71e187480a58fc4842be44395bf4c8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 17 May 2016 17:37:18 +0200 Subject: [PATCH 045/254] travis: removed HHVM --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d58d6dd..72ae79cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,8 @@ language: php php: - 5.6 - 7.0 - - hhvm matrix: - allow_failures: - - php: hhvm - include: - php: 5.6 env: dependencies="--prefer-lowest --prefer-stable" From 5aeaa40d478d60216cfba8ae94187f2b3cfbdfcb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 17 May 2016 17:49:45 +0200 Subject: [PATCH 046/254] composer: used Tester 2 --- .travis.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 72ae79cb..e5bdfacd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: env: dependencies="--prefer-lowest --prefer-stable" script: - - vendor/bin/tester tests -s -p php $coverage + - vendor/bin/tester tests -s $coverage - php temp/code-checker/src/code-checker.php --short-arrays after_failure: diff --git a/composer.json b/composer.json index 17f156f2..cf2a1b65 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require-dev": { "nette/di": "~2.3", "nette/http": "~2.3", - "nette/tester": "~1.4", + "nette/tester": "~2.0", "tracy/tracy": "^2.3" }, "conflict": { From cc14195f23f08a15dbbcb686ce478291e1915e3c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 27 Jun 2016 21:13:01 +0200 Subject: [PATCH 047/254] composer.json: minimal dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index cf2a1b65..20cc5c02 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ "nette/utils": "~2.4" }, "require-dev": { - "nette/di": "~2.3", - "nette/http": "~2.3", + "nette/di": "~2.4", + "nette/http": "~2.4", "nette/tester": "~2.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.4" }, "conflict": { "nette/nette": "<2.2" From d506dc273daae4fcc91017ac08980b0c7c55b9cd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 21 Jul 2016 14:16:49 +0200 Subject: [PATCH 048/254] travis: added PHP 7.1 --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index e5bdfacd..fe3e4729 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,16 @@ language: php php: - 5.6 - 7.0 + - 7.1 matrix: include: - php: 5.6 env: dependencies="--prefer-lowest --prefer-stable" + allow_failures: + - php: 7.1 + script: - vendor/bin/tester tests -s $coverage - php temp/code-checker/src/code-checker.php --short-arrays From d9b52287b743a219a997699c4b67af41bda3d6f6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 17 Oct 2016 14:08:18 +0200 Subject: [PATCH 049/254] typos --- src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml | 3 +-- src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml index c18dfeaa..fdc801d0 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml @@ -2,8 +2,7 @@ namespace Nette\Bridges\SecurityTracy; -use Nette, - Tracy\Dumper; +use Tracy\Dumper; ?>
diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml index ec658b48..2b41f20c 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml @@ -2,8 +2,6 @@ namespace Nette\Bridges\SecurityTracy; -use Nette; - ?> From e8d4b1579cdbd7413e0ef2a40c2d43e5dbd6fe28 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 21 Nov 2016 19:07:15 +0100 Subject: [PATCH 050/254] travis: removed PHP 7.1 from allowed failures --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe3e4729..29a67b31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,6 @@ matrix: - php: 5.6 env: dependencies="--prefer-lowest --prefer-stable" - allow_failures: - - php: 7.1 - script: - vendor/bin/tester tests -s $coverage - php temp/code-checker/src/code-checker.php --short-arrays From e890d48d9eb5622e50de7d184ca88b3a6227662e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 12 Dec 2016 12:17:43 +0100 Subject: [PATCH 051/254] @return self -> static --- src/Security/Identity.php | 4 ++-- src/Security/Permission.php | 22 +++++++++++----------- src/Security/User.php | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 2a896c74..039563eb 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -51,7 +51,7 @@ public function __construct($id, $roles = NULL, $data = NULL) /** * Sets the ID of user. * @param mixed - * @return self + * @return static */ public function setId($id) { @@ -73,7 +73,7 @@ public function getId() /** * Sets a list of roles that the user is a member of. * @param array - * @return self + * @return static */ public function setRoles(array $roles) { diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 6e243ccb..964be77e 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -56,7 +56,7 @@ class Permission implements IAuthorizator * @param string|array * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException - * @return self + * @return static */ public function addRole($role, $parents = NULL) { @@ -175,7 +175,7 @@ public function roleInheritsFrom($role, $inherit, $onlyParents = FALSE) * * @param string * @throws Nette\InvalidStateException - * @return self + * @return static */ public function removeRole($role) { @@ -214,7 +214,7 @@ public function removeRole($role) /** * Removes all Roles from the list. * - * @return self + * @return static */ public function removeAllRoles() { @@ -244,7 +244,7 @@ public function removeAllRoles() * @param string * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException - * @return self + * @return static */ public function addResource($resource, $parent = NULL) { @@ -351,7 +351,7 @@ public function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE) * * @param string * @throws Nette\InvalidStateException - * @return self + * @return static */ public function removeResource($resource) { @@ -383,7 +383,7 @@ public function removeResource($resource) /** * Removes all Resources. - * @return self + * @return static */ public function removeAllResources() { @@ -411,7 +411,7 @@ public function removeAllResources() * @param string|array|Permission::ALL resources * @param string|array|Permission::ALL privileges * @param callable assertion - * @return self + * @return static */ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL) { @@ -428,7 +428,7 @@ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = * @param string|array|Permission::ALL resources * @param string|array|Permission::ALL privileges * @param callable assertion - * @return self + * @return static */ public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL) { @@ -443,7 +443,7 @@ public function deny($roles = self::ALL, $resources = self::ALL, $privileges = s * @param string|array|Permission::ALL roles * @param string|array|Permission::ALL resources * @param string|array|Permission::ALL privileges - * @return self + * @return static */ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) { @@ -458,7 +458,7 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile * @param string|array|Permission::ALL roles * @param string|array|Permission::ALL resources * @param string|array|Permission::ALL privileges - * @return self + * @return static */ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) { @@ -476,7 +476,7 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * @param string|array|Permission::ALL privileges * @param callable assertion * @throws Nette\InvalidStateException - * @return self + * @return static */ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = NULL) { diff --git a/src/Security/User.php b/src/Security/User.php index 5872a0bb..1255178b 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -141,7 +141,7 @@ public function getId() /** * Sets authentication handler. - * @return self + * @return static */ public function setAuthenticator(IAuthenticator $handler) { @@ -168,7 +168,7 @@ public function getAuthenticator($need = TRUE) * @param string|int|\DateTimeInterface number of seconds or timestamp * @param bool log out when the browser is closed? * @param bool clear the identity from persistent storage? - * @return self + * @return static */ public function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE) { @@ -238,7 +238,7 @@ public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthoriz /** * Sets authorization handler. - * @return self + * @return static */ public function setAuthorizator(IAuthorizator $handler) { From e547c81b36bae6cb5a6ba0c498314940f5d428ed Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 14 Dec 2016 00:51:53 +0100 Subject: [PATCH 052/254] UserStorage: removed BROWSER_CLOSED expiration reason [Closes nette/http#112] --- readme.md | 13 +++++-------- src/Security/IUserStorage.php | 11 +++++++---- src/Security/User.php | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/readme.md b/readme.md index 17e2c204..a7fb6944 100644 --- a/readme.md +++ b/readme.md @@ -42,23 +42,20 @@ Simple, right? .[note] Logging in requires users to have cookies enabled - other methods are not safe! -Besides logging the user out with the `logout()` method, it can be done automatically based on specified time interval or closing the browser window. For this configuration we have to call `setExpiration()` during the login process. As an argument, it takes a relative time in seconds, UNIX timestamp, or textual representation of time. The second argument specifies whether the user should be logged out when the browser is closed. +Besides logging the user out with the `logout()` method, it can be done automatically based on specified time interval or closing the browser window. For this configuration we have to call `setExpiration()` during the login process. As an argument, it takes a relative time in seconds, UNIX timestamp, or textual representation of time. ```php -// login expires after 30 minutes of inactivity or after closing browser -$user->setExpiration('30 minutes', TRUE); +// login expires after 30 minutes of inactivity +$user->setExpiration('30 minutes'); // login expires after two days of inactivity -$user->setExpiration('2 days', FALSE); - -// login expires when a browser is closed, but not sooner (ie. without a time limit) -$user->setExpiration(0, TRUE); +$user->setExpiration('2 days'); ``` .[note] Expiration must be set to value equal or lower than the expiration of [sessions]. -The reason of last logout can be obtained by method `$user->getLogoutReason()`, which returns one of these constants: `IUserStorage::INACTIVITY` if time expired, `IUserStorage::BROWSER_CLOSED` when user has closed the browser or `IUserStorage::MANUAL` when the `logout()` method was called. +The reason of last logout can be obtained by method `$user->getLogoutReason()`, which returns one of these constants: `IUserStorage::INACTIVITY` if time expired or `IUserStorage::MANUAL` when the `logout()` method was called. To make the example above work, we in fact have to create an object that verifies user's name and password. It's called **authenticator**. Its trivial implementation is the class [api:Nette\Security\SimpleAuthenticator], which in its constructor accepts an associative array: diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 27f5ab5d..938c6047 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -14,13 +14,16 @@ interface IUserStorage { /** Log-out reason {@link IUserStorage::getLogoutReason()} */ - const MANUAL = 0b0001, - INACTIVITY = 0b0010, - BROWSER_CLOSED = 0b0100; + const + MANUAL = 0b0001, + INACTIVITY = 0b0010; /** Log-out behavior */ const CLEAR_IDENTITY = 0b1000; + /** @deprecated */ + const BROWSER_CLOSED = 0b0100; + /** * Sets the authenticated status of this user. * @param bool @@ -49,7 +52,7 @@ function getIdentity(); /** * Enables log out from the persistent storage after inactivity. * @param string|int|\DateTimeInterface number of seconds or timestamp - * @param int Log out when the browser is closed | Clear the identity from persistent storage? + * @param int Clear the identity from persistent storage? * @return void */ function setExpiration($time, $flags = 0); diff --git a/src/Security/User.php b/src/Security/User.php index 1255178b..79136d64 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -26,9 +26,12 @@ class User use Nette\SmartObject; /** @deprecated */ - const MANUAL = IUserStorage::MANUAL, - INACTIVITY = IUserStorage::INACTIVITY, - BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED; + const + MANUAL = IUserStorage::MANUAL, + INACTIVITY = IUserStorage::INACTIVITY; + + /** @deprecated */ + const BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED; /** @var string default role for unauthenticated user */ public $guestRole = 'guest'; @@ -166,14 +169,14 @@ public function getAuthenticator($need = TRUE) /** * Enables log out after inactivity. * @param string|int|\DateTimeInterface number of seconds or timestamp - * @param bool log out when the browser is closed? - * @param bool clear the identity from persistent storage? + * @param int|bool flag IUserStorage::CLEAR_IDENTITY + * @param bool clear the identity from persistent storage? (deprecated) * @return static */ - public function setExpiration($time, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE) + public function setExpiration($time, $flags = NULL, $clearIdentity = FALSE) { - $flags = ($whenBrowserIsClosed ? IUserStorage::BROWSER_CLOSED : 0) | ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); - $this->storage->setExpiration($time, $flags); + $clearIdentity = $clearIdentity || $flags === IUserStorage::CLEAR_IDENTITY; + $this->storage->setExpiration($time, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); return $this; } From c21062f996bae6d8332bc232edba86a45d9edd83 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 22 Dec 2016 04:56:43 +0100 Subject: [PATCH 053/254] updated contributing.md, added GitHub templates --- .gitattributes | 1 + .github/issue_template.md | 16 ++++++++++++++++ .github/pull_request_template.md | 12 ++++++++++++ contributing.md | 32 +++++++++++++++++++------------- 4 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md diff --git a/.gitattributes b/.gitattributes index f6cd3871..ab732073 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ .gitattributes export-ignore .gitignore export-ignore +.github export-ignore .travis.yml export-ignore tests/ export-ignore *.sh eol=lf diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000..a6a22a9b --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,16 @@ +- bug report? yes/no +- feature request? yes/no +- version: ?.?.? + +### Description +... + +### Steps To Reproduce +... If possible a minimal demo of the problem ... + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..8bcf026e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +- bug fix? yes/no +- new feature? yes/no +- BC break? yes/no +- doc PR: nette/docs#??? + + diff --git a/contributing.md b/contributing.md index 860882bf..184152c0 100644 --- a/contributing.md +++ b/contributing.md @@ -1,27 +1,33 @@ How to contribute & use the issue tracker ========================================= -The issue tracker is the preferred channel for bug reports, features requests -and submitting pull requests, but please respect the following restrictions: +Nette welcomes your contributions. There are several ways to help out: -* Please **do not** use the issue tracker for personal support requests (use - [Nette forum](https://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)). +* 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) -* Please **do not** derail or troll issues. Keep the discussion on topic and - respect the opinions of others. +Issues +------ -* Use the GitHub **issue search** — check if the issue has already been - reported. +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 +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. -Nette welcomes **pull requests**. If you'd like to contribute, please take a moment -to [read the guidelines](https://nette.org/en/contributing) in order to make -the contribution process easy and effective for everyone involved. +Contributing +------------ -Thanks! +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: From 7a75310b5315af8a7eec2b73aba9b21ac8db4d60 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 9 Jan 2017 19:52:44 +0100 Subject: [PATCH 054/254] coding style: removed space after reference & --- src/Security/Permission.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 964be77e..88a0265b 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -519,7 +519,7 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse if ($toAdd) { // add to the rules foreach ($resources as $resource) { foreach ($roles as $role) { - $rules = & $this->getRules($resource, $role, TRUE); + $rules = &$this->getRules($resource, $role, TRUE); if (count($privileges) === 0) { $rules['allPrivileges']['type'] = $type; $rules['allPrivileges']['assert'] = $assertion; @@ -538,7 +538,7 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse } else { // remove from the rules foreach ($resources as $resource) { foreach ($roles as $role) { - $rules = & $this->getRules($resource, $role); + $rules = &$this->getRules($resource, $role); if ($rules === NULL) { continue; } @@ -764,11 +764,11 @@ private function getRuleType($resource, $role, $privilege) * @param bool * @return array|NULL */ - private function & getRules($resource, $role, $create = FALSE) + private function &getRules($resource, $role, $create = FALSE) { $null = NULL; if ($resource === self::ALL) { - $visitor = & $this->rules['allResources']; + $visitor = &$this->rules['allResources']; } else { if (!isset($this->rules['byResource'][$resource])) { if (!$create) { @@ -776,7 +776,7 @@ private function & getRules($resource, $role, $create = FALSE) } $this->rules['byResource'][$resource] = []; } - $visitor = & $this->rules['byResource'][$resource]; + $visitor = &$this->rules['byResource'][$resource]; } if ($role === self::ALL) { From 58b7ef0a31ea72ee1efd0f0eda3cd6d204393ad8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 13 Jan 2017 09:06:08 +0100 Subject: [PATCH 055/254] travis: phpdbg added to allow_failures --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29a67b31..a9801d11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,20 @@ php: - 7.0 - 7.1 +env: matrix: include: - php: 5.6 env: dependencies="--prefer-lowest --prefer-stable" + - php: 7.0 + env: coverage=on + + allow_failures: + - php: 7.0 + env: coverage=on script: - - vendor/bin/tester tests -s $coverage + - vendor/bin/tester tests -s $coverageArgs - php temp/code-checker/src/code-checker.php --short-arrays after_failure: @@ -21,12 +28,12 @@ before_script: # Install Nette Tester & Code Checker - travis_retry composer update --no-interaction --prefer-dist $dependencies - travis_retry composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction - - if [ $TRAVIS_PHP_VERSION == "7.0" ]; then coverage="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi + - if [ "$coverage" == "on" ]; then coverageArgs="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi after_script: # Report Code Coverage - > - if [ "$coverage" != "" ]; then + if [ "$coverage" == "on" ]; then wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar && php coveralls.phar --verbose --config tests/.coveralls.yml || true; fi From e6e675a7ad20dd4b3611b432e506c4a3dd50548b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 21 Jan 2017 01:31:24 +0100 Subject: [PATCH 056/254] fixed phpDoc --- src/Security/IAuthenticator.php | 6 ++-- src/Security/IAuthorizator.php | 6 ++-- src/Security/IUserStorage.php | 10 +++---- src/Security/Identity.php | 16 +++++------ src/Security/Permission.php | 38 ++++++++++++------------- src/Security/User.php | 6 ++-- tests/Security/User.authentication.phpt | 6 +--- tests/Security/User.authorization.phpt | 13 ++------- 8 files changed, 44 insertions(+), 57 deletions(-) diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index f8cd4527..1cac4e0f 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -14,11 +14,13 @@ interface IAuthenticator { /** Credential key */ - const USERNAME = 0, + const + USERNAME = 0, PASSWORD = 1; /** Exception error code */ - const IDENTITY_NOT_FOUND = 1, + const + IDENTITY_NOT_FOUND = 1, INVALID_CREDENTIAL = 2, FAILURE = 3, NOT_APPROVED = 4; diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index cbd59d30..f5211e6a 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -26,9 +26,9 @@ interface IAuthorizator /** * Performs a role-based authorization. - * @param string role - * @param string resource - * @param string privilege + * @param string|NULL + * @param string|NULL + * @param string|NULL * @return bool */ function isAllowed($role, $resource, $privilege); diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 938c6047..36bae4a2 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -27,7 +27,7 @@ interface IUserStorage /** * Sets the authenticated status of this user. * @param bool - * @return void + * @return static */ function setAuthenticated($state); @@ -39,7 +39,7 @@ function isAuthenticated(); /** * Sets the user identity. - * @return void + * @return static */ function setIdentity(IIdentity $identity = NULL); @@ -52,14 +52,14 @@ function getIdentity(); /** * Enables log out from the persistent storage after inactivity. * @param string|int|\DateTimeInterface number of seconds or timestamp - * @param int Clear the identity from persistent storage? - * @return void + * @param int flag IUserStorage::CLEAR_IDENTITY + * @return static */ function setExpiration($time, $flags = 0); /** * Why was user logged out? - * @return int + * @return int|NULL */ function getLogoutReason(); diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 039563eb..6be5960b 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -36,9 +36,9 @@ class Identity implements IIdentity /** - * @param mixed identity ID - * @param mixed roles - * @param array user data + * @param mixed + * @param mixed + * @param iterable */ public function __construct($id, $roles = NULL, $data = NULL) { @@ -72,7 +72,6 @@ public function getId() /** * Sets a list of roles that the user is a member of. - * @param array * @return static */ public function setRoles(array $roles) @@ -104,8 +103,8 @@ public function getData() /** * Sets user data value. - * @param string property name - * @param mixed property value + * @param string + * @param mixed * @return void */ public function __set($key, $value) @@ -121,7 +120,7 @@ public function __set($key, $value) /** * Returns user data value. - * @param string property name + * @param string * @return mixed */ public function &__get($key) @@ -136,8 +135,7 @@ public function &__get($key) /** - * Is property defined? - * @param string property name + * @param string * @return bool */ public function __isset($key) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 88a0265b..a9cada40 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -407,9 +407,9 @@ public function removeAllResources() * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return TRUE in order for rule to apply. * - * @param string|array|Permission::ALL roles - * @param string|array|Permission::ALL resources - * @param string|array|Permission::ALL privileges + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL * @param callable assertion * @return static */ @@ -424,9 +424,9 @@ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return TRUE in order for rule to apply. * - * @param string|array|Permission::ALL roles - * @param string|array|Permission::ALL resources - * @param string|array|Permission::ALL privileges + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL * @param callable assertion * @return static */ @@ -440,9 +440,9 @@ public function deny($roles = self::ALL, $resources = self::ALL, $privileges = s /** * Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges. * - * @param string|array|Permission::ALL roles - * @param string|array|Permission::ALL resources - * @param string|array|Permission::ALL privileges + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL * @return static */ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) @@ -455,9 +455,9 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile /** * Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges. * - * @param string|array|Permission::ALL roles - * @param string|array|Permission::ALL resources - * @param string|array|Permission::ALL privileges + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL * @return static */ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) @@ -471,9 +471,9 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * Performs operations on Access Control List rules. * @param bool operation add? * @param bool type - * @param string|array|Permission::ALL roles - * @param string|array|Permission::ALL resources - * @param string|array|Permission::ALL privileges + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL + * @param string|string[]|Permission::ALL * @param callable assertion * @throws Nette\InvalidStateException * @return static @@ -585,9 +585,9 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse * and its respective parents are checked similarly before the lower-priority parents of * the Role are checked. * - * @param string|Permission::ALL|IRole role - * @param string|Permission::ALL|IResource resource - * @param string|Permission::ALL privilege + * @param string|Permission::ALL|IRole + * @param string|Permission::ALL|IResource + * @param string|Permission::ALL * @throws Nette\InvalidStateException * @return bool */ @@ -720,7 +720,7 @@ private function searchRolePrivileges($all, $role, $resource, $privilege) * @param string|Permission::ALL * @param string|Permission::ALL * @param string|Permission::ALL - * @return mixed NULL if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY + * @return bool|NULL NULL if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY */ private function getRuleType($resource, $role, $privilege) { diff --git a/src/Security/User.php b/src/Security/User.php index 79136d64..c1c27437 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -155,7 +155,7 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. - * @return IAuthenticator + * @return IAuthenticator|NULL */ public function getAuthenticator($need = TRUE) { @@ -183,7 +183,7 @@ public function setExpiration($time, $flags = NULL, $clearIdentity = FALSE) /** * Why was user logged out? - * @return int + * @return int|NULL */ public function getLogoutReason() { @@ -252,7 +252,7 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. - * @return IAuthorizator + * @return IAuthorizator|NULL */ public function getAuthorizator($need = TRUE) { diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 5f10132a..2dadc1d9 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -19,11 +19,7 @@ ob_start(); class Authenticator implements IAuthenticator { - /** - * @param array - * @return IIdentity - * @throws Nette\Security\AuthenticationException - */ + function authenticate(array $credentials) { list($username, $password) = $credentials; diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 3a6920bb..03efdc05 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -21,11 +21,7 @@ ob_start(); class Authenticator implements IAuthenticator { - /** - * @param array - * @return IIdentity - * @throws Nette\Security\AuthenticationException - */ + function authenticate(array $credentials) { list($username, $password) = $credentials; @@ -45,12 +41,7 @@ class Authenticator implements IAuthenticator class Authorizator implements IAuthorizator { - /** - * @param string role - * @param string resource - * @param string privilege - * @return bool - */ + function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL) { return $role === 'admin' && strpos($resource, 'jany') === FALSE; From 22df104c03f3c0da12aaf447534656219cdbf988 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 23 Jan 2017 18:37:16 +0100 Subject: [PATCH 057/254] renamed parameter $need -> $throw --- src/Security/Permission.php | 8 ++++---- src/Security/User.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index a9cada40..1f28b903 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -107,12 +107,12 @@ public function hasRole($role) * @throws Nette\InvalidStateException * @return void */ - private function checkRole($role, $need = TRUE) + private function checkRole($role, $throw = TRUE) { if (!is_string($role) || $role === '') { throw new Nette\InvalidArgumentException('Role must be a non-empty string.'); - } elseif ($need && !isset($this->roles[$role])) { + } elseif ($throw && !isset($this->roles[$role])) { throw new Nette\InvalidStateException("Role '$role' does not exist."); } } @@ -287,12 +287,12 @@ public function hasResource($resource) * @throws Nette\InvalidStateException * @return void */ - private function checkResource($resource, $need = TRUE) + private function checkResource($resource, $throw = TRUE) { if (!is_string($resource) || $resource === '') { throw new Nette\InvalidArgumentException('Resource must be a non-empty string.'); - } elseif ($need && !isset($this->resources[$resource])) { + } elseif ($throw && !isset($this->resources[$resource])) { throw new Nette\InvalidStateException("Resource '$resource' does not exist."); } } diff --git a/src/Security/User.php b/src/Security/User.php index c1c27437..73fb7170 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -157,9 +157,9 @@ public function setAuthenticator(IAuthenticator $handler) * Returns authentication handler. * @return IAuthenticator|NULL */ - public function getAuthenticator($need = TRUE) + public function getAuthenticator($throw = TRUE) { - if ($need && !$this->authenticator) { + if ($throw && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); } return $this->authenticator; @@ -254,9 +254,9 @@ public function setAuthorizator(IAuthorizator $handler) * Returns current authorization handler. * @return IAuthorizator|NULL */ - public function getAuthorizator($need = TRUE) + public function getAuthorizator($throw = TRUE) { - if ($need && !$this->authorizator) { + if ($throw && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); } return $this->authorizator; From 7e5642bfaa90c4771fb1fc373f0bf7de86a37eb5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Jan 2017 02:15:48 +0100 Subject: [PATCH 058/254] composer: accepts nette 3.0 --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 20cc5c02..2338481e 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,12 @@ ], "require": { "php": ">=5.6.0", - "nette/utils": "~2.4" + "nette/utils": "^2.4 || ~3.0.0" }, "require-dev": { - "nette/di": "~2.4", - "nette/http": "~2.4", - "nette/tester": "~2.0", + "nette/di": "^2.4 || ~3.0.0", + "nette/http": "^2.4", + "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, "conflict": { From 5b37230c19056a1c569d48fb285acd6d5a66f44e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2017 15:07:06 +0100 Subject: [PATCH 059/254] opened 3.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2338481e..9c5c3aed 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "3.0-dev" } } } From 952c5e9263d5b0864d91cfc2175eafb6ae554dce Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2017 15:26:03 +0100 Subject: [PATCH 060/254] requires PHP 7.1 --- .travis.yml | 8 +++----- composer.json | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9801d11..f3cc1707 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,17 @@ language: php php: - - 5.6 - - 7.0 - 7.1 env: matrix: include: - - php: 5.6 + - php: 7.1 env: dependencies="--prefer-lowest --prefer-stable" - - php: 7.0 + - php: 7.1 env: coverage=on allow_failures: - - php: 7.0 + - php: 7.1 env: coverage=on script: diff --git a/composer.json b/composer.json index 9c5c3aed..91e7ff79 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": ">=5.6.0", + "php": ">=7.1", "nette/utils": "^2.4 || ~3.0.0" }, "require-dev": { From 0daab9a6b0dc92ae45d38a56d66cfd0efdfdd05e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2017 15:24:46 +0100 Subject: [PATCH 061/254] added declare(strict_types=1); --- src/Bridges/SecurityDI/SecurityExtension.php | 2 ++ src/Bridges/SecurityTracy/UserPanel.php | 2 ++ src/Security/AuthenticationException.php | 2 ++ src/Security/IAuthenticator.php | 2 ++ src/Security/IAuthorizator.php | 2 ++ src/Security/IIdentity.php | 2 ++ src/Security/IResource.php | 2 ++ src/Security/IRole.php | 2 ++ src/Security/IUserStorage.php | 2 ++ src/Security/Identity.php | 2 ++ src/Security/Passwords.php | 2 ++ src/Security/Permission.php | 2 ++ src/Security/SimpleAuthenticator.php | 2 ++ src/Security/User.php | 2 ++ tests/Security.DI/SecurityExtension.authenticator.phpt | 2 ++ tests/Security.DI/SecurityExtension.user.phpt | 2 ++ tests/Security/Identity.phpt | 2 ++ tests/Security/MockUserStorage.php | 2 ++ tests/Security/Passwords.hash().phpt | 2 ++ tests/Security/Passwords.needsRehash().phpt | 2 ++ tests/Security/Passwords.verify().phpt | 2 ++ tests/Security/Permission.CMSExample.phpt | 2 ++ tests/Security/Permission.DefaultAssert.phpt | 2 ++ tests/Security/Permission.DefaultDeny.phpt | 2 ++ tests/Security/Permission.DefaultRuleSet.phpt | 2 ++ tests/Security/Permission.IsAllowedNonExistent.phpt | 2 ++ tests/Security/Permission.PrivilegeAllow.phpt | 2 ++ tests/Security/Permission.PrivilegeAssert.phpt | 2 ++ tests/Security/Permission.PrivilegeDeny.phpt | 2 ++ tests/Security/Permission.Privileges.phpt | 2 ++ tests/Security/Permission.RemoveDefaultAllow.phpt | 2 ++ tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt | 2 ++ tests/Security/Permission.RemoveDefaultDeny.phpt | 2 ++ tests/Security/Permission.RemoveDefaultDenyAssert.phpt | 2 ++ tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt | 2 ++ ...ssion.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt | 2 ++ tests/Security/Permission.ResourceAddAndGetOne.phpt | 2 ++ tests/Security/Permission.ResourceAddInheritsNonExistent.phpt | 2 ++ tests/Security/Permission.ResourceDuplicate.phpt | 2 ++ tests/Security/Permission.ResourceInherits.phpt | 2 ++ tests/Security/Permission.ResourceInheritsNonExistent.phpt | 2 ++ tests/Security/Permission.ResourceRemoveAll.phpt | 2 ++ tests/Security/Permission.ResourceRemoveOneNonExistent.phpt | 2 ++ .../Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt | 2 ++ .../Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt | 2 ++ tests/Security/Permission.RoleDefaultRuleSet.phpt | 2 ++ tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt | 2 ++ tests/Security/Permission.RolePrivilegeAllow.phpt | 2 ++ tests/Security/Permission.RolePrivilegeAssert.phpt | 2 ++ tests/Security/Permission.RolePrivilegeDeny.phpt | 2 ++ tests/Security/Permission.RolePrivileges.phpt | 2 ++ tests/Security/Permission.RoleRegistryAddAndGetOne.phpt | 2 ++ .../Security/Permission.RoleRegistryAddInheritsNonExistent.phpt | 2 ++ tests/Security/Permission.RoleRegistryDuplicate.phpt | 2 ++ tests/Security/Permission.RoleRegistryInherits.phpt | 2 ++ tests/Security/Permission.RoleRegistryInheritsMultiple.phpt | 2 ++ tests/Security/Permission.RoleRegistryInheritsNonExistent.phpt | 2 ++ tests/Security/Permission.RoleRegistryRemoveAll.phpt | 2 ++ tests/Security/Permission.RoleRegistryRemoveOneNonExistent.phpt | 2 ++ tests/Security/Permission.RuleRoleRemove.phpt | 2 ++ tests/Security/Permission.RuleRoleRemoveAll.phpt | 2 ++ tests/Security/Permission.RulesRemove.phpt | 2 ++ tests/Security/Permission.RulesResourceRemove.phpt | 2 ++ tests/Security/Permission.RulesResourceRemoveAll.phpt | 2 ++ tests/Security/SimpleAuthenticator.Roles.phpt | 2 ++ tests/Security/SimpleAuthenticator.phpt | 2 ++ tests/Security/User.authentication.phpt | 2 ++ tests/Security/User.authorization.phpt | 2 ++ tests/bootstrap.php | 2 ++ 69 files changed, 138 insertions(+) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 0a86194b..7e2104ff 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Bridges\SecurityDI; use Nette; diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 5bfd0a92..d3e3137c 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Bridges\SecurityTracy; use Nette; diff --git a/src/Security/AuthenticationException.php b/src/Security/AuthenticationException.php index c03803d9..79c90b44 100644 --- a/src/Security/AuthenticationException.php +++ b/src/Security/AuthenticationException.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 1cac4e0f..2fb5ebdf 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index f5211e6a..6eb95013 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index bc9be15a..6e7165ae 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IResource.php b/src/Security/IResource.php index e449ce94..30b0842e 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IRole.php b/src/Security/IRole.php index 6d6bcfb7..329d3f2f 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 36bae4a2..80a11859 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 6be5960b..60c07030 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; use Nette; diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 26ad2fb3..585f699b 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; use Nette; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 1f28b903..31a1fc97 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; use Nette; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index ac3dfeed..16441de9 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; use Nette; diff --git a/src/Security/User.php b/src/Security/User.php index 73fb7170..8aeea3c0 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -5,6 +5,8 @@ * Copyright (c) 2004 David Grudl (https://davidgrudl.com) */ +declare(strict_types=1); + namespace Nette\Security; use Nette; diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index e783f007..6e570b77 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -4,6 +4,8 @@ * Test: SecurityExtension */ +declare(strict_types=1); + use Nette\DI; use Nette\Bridges\HttpDI\HttpExtension; use Nette\Bridges\HttpDI\SessionExtension; diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index 80c42e43..c454ef33 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -4,6 +4,8 @@ * Test: SecurityExtension */ +declare(strict_types=1); + use Nette\DI; use Nette\Bridges\HttpDI\HttpExtension; use Nette\Bridges\HttpDI\SessionExtension; diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index 1936f9f7..96544994 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -4,6 +4,8 @@ * Test: Nette\Security\Identity. */ +declare(strict_types=1); + use Nette\Security\Identity; use Tester\Assert; diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 57dc6b97..306a9579 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -1,5 +1,7 @@ Date: Sun, 15 Jan 2017 06:51:48 +0100 Subject: [PATCH 062/254] removed deprecated stuff --- src/Security/IUserStorage.php | 3 --- src/Security/Passwords.php | 3 --- src/Security/User.php | 3 --- 3 files changed, 9 deletions(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 80a11859..2f75c89f 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -23,9 +23,6 @@ interface IUserStorage /** Log-out behavior */ const CLEAR_IDENTITY = 0b1000; - /** @deprecated */ - const BROWSER_CLOSED = 0b0100; - /** * Sets the authenticated status of this user. * @param bool diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 585f699b..9e620995 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -19,9 +19,6 @@ class Passwords { use Nette\StaticClass; - /** @deprecated */ - const BCRYPT_COST = 10; - /** * Computes salted password hash. diff --git a/src/Security/User.php b/src/Security/User.php index 8aeea3c0..d095f625 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -32,9 +32,6 @@ class User MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::INACTIVITY; - /** @deprecated */ - const BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED; - /** @var string default role for unauthenticated user */ public $guestRole = 'guest'; From 4e5c799932eb54252225a0c15d2b2664d86ef76c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Jan 2017 07:58:36 +0100 Subject: [PATCH 063/254] used PHP 7.1 features --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- src/Security/SimpleAuthenticator.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 7e2104ff..4a1a7c74 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -58,7 +58,7 @@ public function loadConfiguration() $data = is_array($data) ? $data : ['password' => $data]; $this->validateConfig(['password' => NULL, 'roles' => NULL], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; - $usersRoles[$username] = isset($data['roles']) ? $data['roles'] : NULL; + $usersRoles[$username] = $data['roles'] ?? NULL; } $builder->addDefinition($this->prefix('authenticator')) diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 16441de9..9eed46d1 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -45,11 +45,11 @@ public function __construct(array $userlist, array $usersRoles = []) */ public function authenticate(array $credentials) { - list($username, $password) = $credentials; + [$username, $password] = $credentials; foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { if ((string) $pass === (string) $password) { - return new Identity($name, isset($this->usersRoles[$name]) ? $this->usersRoles[$name] : NULL); + return new Identity($name, $this->usersRoles[$name] ?? NULL); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); } From e042f88050c15545f6387bcdab4f2f049bf9ebd7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jan 2017 17:45:50 +0100 Subject: [PATCH 064/254] travis: Code Checker checks for strict_types --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3cc1707..b196df6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: script: - vendor/bin/tester tests -s $coverageArgs - - php temp/code-checker/src/code-checker.php --short-arrays + - php temp/code-checker/src/code-checker.php --short-arrays --strict-types after_failure: # Print *.actual content @@ -25,7 +25,7 @@ after_failure: before_script: # Install Nette Tester & Code Checker - travis_retry composer update --no-interaction --prefer-dist $dependencies - - travis_retry composer create-project nette/code-checker temp/code-checker ~2.5 --no-interaction + - travis_retry composer create-project nette/code-checker temp/code-checker ~2.8 --no-interaction - if [ "$coverage" == "on" ]; then coverageArgs="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi after_script: From 84ada184b35ac7cc19a382dc9ebb8a0e0fcc076d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 21 Dec 2016 01:22:30 +0100 Subject: [PATCH 065/254] User::setExpiration() changed arguments (BC break) --- src/Security/User.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index d095f625..f6051cef 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -168,13 +168,19 @@ public function getAuthenticator($throw = TRUE) /** * Enables log out after inactivity. * @param string|int|\DateTimeInterface number of seconds or timestamp - * @param int|bool flag IUserStorage::CLEAR_IDENTITY - * @param bool clear the identity from persistent storage? (deprecated) + * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ - public function setExpiration($time, $flags = NULL, $clearIdentity = FALSE) + public function setExpiration($time, $flags = 0) { - $clearIdentity = $clearIdentity || $flags === IUserStorage::CLEAR_IDENTITY; + $clearIdentity = $flags === IUserStorage::CLEAR_IDENTITY; + if (is_bool($flags)) { + trigger_error(__METHOD__ . '() second parameter $whenBrowserIsClosed was removed.', E_USER_DEPRECATED); + } + if (func_num_args() > 2) { + $clearIdentity = $clearIdentity || func_get_arg(2); + trigger_error(__METHOD__ . '() third parameter is deprecated, use flag setExpiration($time, IUserStorage::CLEAR_IDENTITY)', E_USER_DEPRECATED); + } $this->storage->setExpiration($time, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); return $this; } From 7640bcbcd0c9bdbc4e18273022a681cfbe1e4a48 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2017 17:11:59 +0100 Subject: [PATCH 066/254] composer: uses nette/http 3.0 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 91e7ff79..23dc90e8 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,11 @@ ], "require": { "php": ">=7.1", - "nette/utils": "^2.4 || ~3.0.0" + "nette/utils": "^2.4 || ^3.0" }, "require-dev": { - "nette/di": "^2.4 || ~3.0.0", - "nette/http": "^2.4", + "nette/di": "^2.4 || ^3.0", + "nette/http": "^3.0", "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, From 84024f612fb3f55f5d6e3e3e28eef1ad0388fa56 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 20 Jan 2017 22:54:55 +0100 Subject: [PATCH 067/254] added PHP 7.1 scalar and return type hints IAuthorizator::isAllowed() must not use typehints until this will be merged https://wiki.php.net/rfc/parameter-no-type-variance User::setExpiration() missing typehint due to compatibility with v2.x --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- src/Bridges/SecurityTracy/UserPanel.php | 8 +- src/Security/IAuthenticator.php | 3 +- src/Security/IAuthorizator.php | 3 +- src/Security/IIdentity.php | 3 +- src/Security/IResource.php | 3 +- src/Security/IRole.php | 3 +- src/Security/IUserStorage.php | 16 ++-- src/Security/Identity.php | 28 ++----- src/Security/Passwords.php | 17 ++--- src/Security/Permission.php | 78 ++++++-------------- src/Security/SimpleAuthenticator.php | 3 +- src/Security/User.php | 38 +++------- tests/Security/MockUserStorage.php | 10 +-- tests/Security/User.authentication.phpt | 2 +- tests/Security/User.authorization.phpt | 4 +- 16 files changed, 67 insertions(+), 154 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 4a1a7c74..e46c63a3 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -28,7 +28,7 @@ class SecurityExtension extends Nette\DI\CompilerExtension private $debugMode; - public function __construct($debugMode = FALSE) + public function __construct(bool $debugMode = FALSE) { $this->debugMode = $debugMode; } diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index d3e3137c..b057fc11 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -32,12 +32,11 @@ public function __construct(Nette\Security\User $user) /** * Renders tab. - * @return string */ - public function getTab() + public function getTab(): ?string { if (headers_sent() && !session_id()) { - return; + return NULL; } ob_start(function () {}); @@ -49,9 +48,8 @@ public function getTab() /** * Renders panel. - * @return string */ - public function getPanel() + public function getPanel(): string { ob_start(function () {}); $user = $this->user; diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 2fb5ebdf..6d8d4309 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -30,9 +30,8 @@ interface IAuthenticator /** * Performs an authentication against e.g. database. * and returns IIdentity on success or throws AuthenticationException - * @return IIdentity * @throws AuthenticationException */ - function authenticate(array $credentials); + function authenticate(array $credentials): IIdentity; } diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index 6eb95013..1369744e 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -31,8 +31,7 @@ interface IAuthorizator * @param string|NULL * @param string|NULL * @param string|NULL - * @return bool */ - function isAllowed($role, $resource, $privilege); + function isAllowed($role, $resource, $privilege): bool; } diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index 6e7165ae..d3bd22af 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -24,8 +24,7 @@ function getId(); /** * Returns a list of roles that the user is a member of. - * @return array */ - function getRoles(); + function getRoles(): array; } diff --git a/src/Security/IResource.php b/src/Security/IResource.php index 30b0842e..d5615f6c 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -18,8 +18,7 @@ interface IResource /** * Returns a string identifier of the Resource. - * @return string */ - function getResourceId(); + function getResourceId(): string; } diff --git a/src/Security/IRole.php b/src/Security/IRole.php index 329d3f2f..b136687b 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -18,8 +18,7 @@ interface IRole /** * Returns a string identifier of the Role. - * @return string */ - function getRoleId(); + function getRoleId(): string; } diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 2f75c89f..411a1f8c 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -25,28 +25,25 @@ interface IUserStorage /** * Sets the authenticated status of this user. - * @param bool * @return static */ - function setAuthenticated($state); + function setAuthenticated(bool $state); /** * Is this user authenticated? - * @return bool */ - function isAuthenticated(); + function isAuthenticated(): bool; /** * Sets the user identity. * @return static */ - function setIdentity(IIdentity $identity = NULL); + function setIdentity(?IIdentity $identity); /** * Returns current user identity, if any. - * @return IIdentity|NULL */ - function getIdentity(); + function getIdentity(): ?IIdentity; /** * Enables log out from the persistent storage after inactivity. @@ -54,12 +51,11 @@ function getIdentity(); * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ - function setExpiration($time, $flags = 0); + function setExpiration($time, int $flags = 0); /** * Why was user logged out? - * @return int|NULL */ - function getLogoutReason(); + function getLogoutReason(): ?int; } diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 60c07030..6c7e5730 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -37,12 +37,7 @@ class Identity implements IIdentity private $data; - /** - * @param mixed - * @param mixed - * @param iterable - */ - public function __construct($id, $roles = NULL, $data = NULL) + public function __construct($id, $roles = NULL, iterable $data = NULL) { $this->setId($id); $this->setRoles((array) $roles); @@ -52,7 +47,6 @@ public function __construct($id, $roles = NULL, $data = NULL) /** * Sets the ID of user. - * @param mixed * @return static */ public function setId($id) @@ -85,9 +79,8 @@ public function setRoles(array $roles) /** * Returns a list of roles that the user is a member of. - * @return array */ - public function getRoles() + public function getRoles(): array { return $this->roles; } @@ -95,9 +88,8 @@ public function getRoles() /** * Returns a user data. - * @return array */ - public function getData() + public function getData(): array { return $this->data; } @@ -105,11 +97,8 @@ public function getData() /** * Sets user data value. - * @param string - * @param mixed - * @return void */ - public function __set($key, $value) + public function __set(string $key, $value): void { if ($this->parentIsSet($key)) { $this->parentSet($key, $value); @@ -122,10 +111,9 @@ public function __set($key, $value) /** * Returns user data value. - * @param string * @return mixed */ - public function &__get($key) + public function &__get(string $key) { if ($this->parentIsSet($key)) { return $this->parentGet($key); @@ -136,11 +124,7 @@ public function &__get($key) } - /** - * @param string - * @return bool - */ - public function __isset($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 9e620995..038b5602 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -21,12 +21,9 @@ class Passwords /** - * Computes salted password hash. - * @param string - * @param array with cost (4-31) - * @return string 60 chars long + * Computes salted password hash. Accepts option 'cost' (4-31) */ - public static function hash($password, array $options = []) + public static function hash(string $password, array $options = []): string { if (isset($options['cost']) && ($options['cost'] < 4 || $options['cost'] > 31)) { throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $options[cost] given."); @@ -42,21 +39,17 @@ public static function hash($password, array $options = []) /** * Verifies that a password matches a hash. - * @return bool */ - public static function verify($password, $hash) + public static function verify(string $password, string $hash): bool { return password_verify($password, $hash); } /** - * Checks if the given hash matches the options. - * @param string - * @param array with cost (4-31) - * @return bool + * Checks if the given hash matches the options. Accepts option 'cost' (4-31) */ - public static function needsRehash($hash, array $options = []) + public static function needsRehash(string $hash, array $options = []): bool { return password_needs_rehash($hash, PASSWORD_BCRYPT, $options); } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 31a1fc97..b432e5b7 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -54,13 +54,12 @@ class Permission implements IAuthorizator /** * Adds a Role to the list. The most recently added parent * takes precedence over parents that were previously added. - * @param string - * @param string|array + * @param string|array $parents * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException * @return static */ - public function addRole($role, $parents = NULL) + public function addRole(string $role, $parents = NULL) { $this->checkRole($role, FALSE); if (isset($this->roles[$role])) { @@ -92,10 +91,8 @@ public function addRole($role, $parents = NULL) /** * Returns TRUE if the Role exists in the list. - * @param string - * @return bool */ - public function hasRole($role) + public function hasRole(string $role): bool { $this->checkRole($role, FALSE); return isset($this->roles[$role]); @@ -104,12 +101,9 @@ public function hasRole($role) /** * Checks whether Role is valid and exists in the list. - * @param string - * @param bool * @throws Nette\InvalidStateException - * @return void */ - private function checkRole($role, $throw = TRUE) + private function checkRole(string $role, bool $throw = TRUE): void { if (!is_string($role) || $role === '') { throw new Nette\InvalidArgumentException('Role must be a non-empty string.'); @@ -122,9 +116,8 @@ private function checkRole($role, $throw = TRUE) /** * Returns all Roles. - * @return array */ - public function getRoles() + public function getRoles(): array { return array_keys($this->roles); } @@ -132,10 +125,8 @@ public function getRoles() /** * Returns existing Role's parents ordered by ascending priority. - * @param string - * @return array */ - public function getRoleParents($role) + public function getRoleParents(string $role): array { $this->checkRole($role); return array_keys($this->roles[$role]['parents']); @@ -145,13 +136,9 @@ public function getRoleParents($role) /** * Returns TRUE if $role inherits from $inherit. If $onlyParents is TRUE, * then $role must inherit directly from $inherit. - * @param string - * @param string - * @param bool * @throws Nette\InvalidStateException - * @return bool */ - public function roleInheritsFrom($role, $inherit, $onlyParents = FALSE) + public function roleInheritsFrom(string $role, string $inherit, bool $onlyParents = FALSE): bool { $this->checkRole($role); $this->checkRole($inherit); @@ -175,11 +162,10 @@ public function roleInheritsFrom($role, $inherit, $onlyParents = FALSE) /** * Removes the Role from the list. * - * @param string * @throws Nette\InvalidStateException * @return static */ - public function removeRole($role) + public function removeRole(string $role) { $this->checkRole($role); @@ -242,13 +228,11 @@ public function removeAllRoles() /** * Adds a Resource having an identifier unique to the list. * - * @param string - * @param string * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException * @return static */ - public function addResource($resource, $parent = NULL) + public function addResource(string $resource, string $parent = NULL) { $this->checkResource($resource, FALSE); @@ -272,10 +256,8 @@ public function addResource($resource, $parent = NULL) /** * Returns TRUE if the Resource exists in the list. - * @param string - * @return bool */ - public function hasResource($resource) + public function hasResource(string $resource): bool { $this->checkResource($resource, FALSE); return isset($this->resources[$resource]); @@ -284,12 +266,9 @@ public function hasResource($resource) /** * Checks whether Resource is valid and exists in the list. - * @param string - * @param bool * @throws Nette\InvalidStateException - * @return void */ - private function checkResource($resource, $throw = TRUE) + private function checkResource(string $resource, bool $throw = TRUE): void { if (!is_string($resource) || $resource === '') { throw new Nette\InvalidArgumentException('Resource must be a non-empty string.'); @@ -302,9 +281,8 @@ private function checkResource($resource, $throw = TRUE) /** * Returns all Resources. - * @return array */ - public function getResources() + public function getResources(): array { return array_keys($this->resources); } @@ -314,13 +292,9 @@ public function getResources() * Returns TRUE if $resource inherits from $inherit. If $onlyParents is TRUE, * then $resource must inherit directly from $inherit. * - * @param string - * @param string - * @param bool * @throws Nette\InvalidStateException - * @return bool */ - public function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE) + public function resourceInheritsFrom(string $resource, string $inherit, bool $onlyParent = FALSE): bool { $this->checkResource($resource); $this->checkResource($inherit); @@ -351,11 +325,10 @@ public function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE) /** * Removes a Resource and all of its children. * - * @param string * @throws Nette\InvalidStateException * @return static */ - public function removeResource($resource) + public function removeResource(string $resource) { $this->checkResource($resource); @@ -412,10 +385,9 @@ public function removeAllResources() * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL - * @param callable assertion * @return static */ - public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL) + public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = NULL) { $this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion); return $this; @@ -429,10 +401,9 @@ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL - * @param callable assertion * @return static */ - public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL) + public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = NULL) { $this->setRule(TRUE, self::DENY, $roles, $resources, $privileges, $assertion); return $this; @@ -476,11 +447,10 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL - * @param callable assertion * @throws Nette\InvalidStateException * @return static */ - protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = NULL) + protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privileges, callable $assertion = NULL) { // ensure that all specified Roles exist; normalize input to array of Roles or NULL if ($roles === self::ALL) { @@ -591,9 +561,8 @@ protected function setRule($toAdd, $type, $roles, $resources, $privileges, $asse * @param string|Permission::ALL|IResource * @param string|Permission::ALL * @throws Nette\InvalidStateException - * @return bool */ - public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL) + public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { $this->queriedRole = $role; if ($role !== self::ALL) { @@ -672,12 +641,9 @@ public function getQueriedResource() * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule * allowing/denying $role access to a/all $privilege upon $resource. * @param bool all (true) or one? - * @param string - * @param string - * @param string only for one * @return mixed NULL if no applicable rule is found, otherwise returns ALLOW or DENY */ - private function searchRolePrivileges($all, $role, $resource, $privilege) + private function searchRolePrivileges(bool $all, $role, $resource, $privilege) { $dfs = [ 'visited' => [], @@ -724,7 +690,7 @@ private function searchRolePrivileges($all, $role, $resource, $privilege) * @param string|Permission::ALL * @return bool|NULL NULL if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY */ - private function getRuleType($resource, $role, $privilege) + private function getRuleType($resource, $role, $privilege): ?bool { if (!$rules = $this->getRules($resource, $role)) { return NULL; @@ -763,10 +729,8 @@ private function getRuleType($resource, $role, $privilege) * If the $create parameter is TRUE, then a rule set is first created and then returned to the caller. * @param string|Permission::ALL * @param string|Permission::ALL - * @param bool - * @return array|NULL */ - private function &getRules($resource, $role, $create = FALSE) + private function &getRules($resource, $role, bool $create = FALSE): ?array { $null = NULL; if ($resource === self::ALL) { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 9eed46d1..bff78391 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -40,10 +40,9 @@ public function __construct(array $userlist, array $usersRoles = []) /** * Performs an authentication against e.g. database. * and returns IIdentity on success or throws AuthenticationException - * @return IIdentity * @throws AuthenticationException */ - public function authenticate(array $credentials) + public function authenticate(array $credentials): IIdentity { [$username, $password] = $credentials; foreach ($this->userlist as $name => $pass) { diff --git a/src/Security/User.php b/src/Security/User.php index f6051cef..d64a7d1c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -62,10 +62,7 @@ public function __construct(IUserStorage $storage, IAuthenticator $authenticator } - /** - * @return IUserStorage - */ - public function getStorage() + public function getStorage(): IUserStorage { return $this->storage; } @@ -78,10 +75,9 @@ public function getStorage() * Conducts the authentication process. Parameters are optional. * @param mixed optional parameter (e.g. username or IIdentity) * @param mixed optional parameter (e.g. password) - * @return void * @throws AuthenticationException if authentication was not successful */ - public function login($id = NULL, $password = NULL) + public function login($id = NULL, $password = NULL): void { $this->logout(TRUE); if (!$id instanceof IIdentity) { @@ -96,9 +92,8 @@ public function login($id = NULL, $password = NULL) /** * Logs out the user from the current session. * @param bool clear the identity from persistent storage? - * @return void */ - public function logout($clearIdentity = FALSE) + public function logout(bool $clearIdentity = FALSE): void { if ($this->isLoggedIn()) { $this->onLoggedOut($this); @@ -112,9 +107,8 @@ public function logout($clearIdentity = FALSE) /** * Is this user authenticated? - * @return bool */ - public function isLoggedIn() + public function isLoggedIn(): bool { return $this->storage->isAuthenticated(); } @@ -122,9 +116,8 @@ public function isLoggedIn() /** * Returns current user identity, if any. - * @return IIdentity|NULL */ - public function getIdentity() + public function getIdentity(): ?IIdentity { return $this->storage->getIdentity(); } @@ -154,9 +147,8 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. - * @return IAuthenticator|NULL */ - public function getAuthenticator($throw = TRUE) + public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator { if ($throw && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); @@ -188,9 +180,8 @@ public function setExpiration($time, $flags = 0) /** * Why was user logged out? - * @return int|NULL */ - public function getLogoutReason() + public function getLogoutReason(): ?int { return $this->storage->getLogoutReason(); } @@ -201,9 +192,8 @@ public function getLogoutReason() /** * Returns a list of effective roles that a user has been granted. - * @return array */ - public function getRoles() + public function getRoles(): array { if (!$this->isLoggedIn()) { return [$this->guestRole]; @@ -216,10 +206,8 @@ public function getRoles() /** * Is a user in the specified effective role? - * @param string - * @return bool */ - public function isInRole($role) + public function isInRole(string $role): bool { return in_array($role, $this->getRoles(), TRUE); } @@ -228,11 +216,8 @@ public function isInRole($role) /** * Has a user effective access to the Resource? * If $resource is NULL, then the query applies to all resources. - * @param string resource - * @param string privilege - * @return bool */ - public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL) + public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL): bool { foreach ($this->getRoles() as $role) { if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) { @@ -257,9 +242,8 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. - * @return IAuthorizator|NULL */ - public function getAuthorizator($throw = TRUE) + public function getAuthorizator(bool $throw = TRUE): ?IAuthorizator { if ($throw && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 306a9579..7c483fd3 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -7,12 +7,12 @@ class MockUserStorage implements Nette\Security\IUserStorage private $auth = FALSE; private $identity; - function setAuthenticated($state) + function setAuthenticated(bool $state) { $this->auth = $state; } - function isAuthenticated() + function isAuthenticated(): bool { return $this->auth; } @@ -22,16 +22,16 @@ function setIdentity(Nette\Security\IIdentity $identity = NULL) $this->identity = $identity; } - function getIdentity() + function getIdentity(): ?Nette\Security\IIdentity { return $this->identity; } - function setExpiration($time, $flags = 0) + function setExpiration($time, int $flags = 0) { } - function getLogoutReason() + function getLogoutReason(): ?int { } } diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 1859daab..5e7ffc8f 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -22,7 +22,7 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials) + function authenticate(array $credentials): Nette\Security\IIdentity { list($username, $password) = $credentials; if ($username !== 'john') { diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 186b6015..808dbe9c 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -24,7 +24,7 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials) + function authenticate(array $credentials): Nette\Security\IIdentity { list($username, $password) = $credentials; if ($username !== 'john') { @@ -44,7 +44,7 @@ class Authenticator implements IAuthenticator class Authorizator implements IAuthorizator { - function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL) + function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { return $role === 'admin' && strpos($resource, 'jany') === FALSE; } From c7cc80bbe5f8db6346b7008f88eb3b478380d6cd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 23 Jan 2017 10:10:58 +0100 Subject: [PATCH 068/254] some classes & members marked as final (BC break) This reverts commit "Christmas Gift 2013" 34d788ff876f8f92d132a48efeb5b93d37b10da6. --- src/Security/User.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index d64a7d1c..5ff355d0 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -62,7 +62,7 @@ public function __construct(IUserStorage $storage, IAuthenticator $authenticator } - public function getStorage(): IUserStorage + final public function getStorage(): IUserStorage { return $this->storage; } @@ -93,7 +93,7 @@ public function login($id = NULL, $password = NULL): void * Logs out the user from the current session. * @param bool clear the identity from persistent storage? */ - public function logout(bool $clearIdentity = FALSE): void + final public function logout(bool $clearIdentity = FALSE): void { if ($this->isLoggedIn()) { $this->onLoggedOut($this); @@ -108,7 +108,7 @@ public function logout(bool $clearIdentity = FALSE): void /** * Is this user authenticated? */ - public function isLoggedIn(): bool + final public function isLoggedIn(): bool { return $this->storage->isAuthenticated(); } @@ -117,7 +117,7 @@ public function isLoggedIn(): bool /** * Returns current user identity, if any. */ - public function getIdentity(): ?IIdentity + final public function getIdentity(): ?IIdentity { return $this->storage->getIdentity(); } @@ -148,7 +148,7 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. */ - public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator + final public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator { if ($throw && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); @@ -181,7 +181,7 @@ public function setExpiration($time, $flags = 0) /** * Why was user logged out? */ - public function getLogoutReason(): ?int + final public function getLogoutReason(): ?int { return $this->storage->getLogoutReason(); } @@ -207,7 +207,7 @@ public function getRoles(): array /** * Is a user in the specified effective role? */ - public function isInRole(string $role): bool + final public function isInRole(string $role): bool { return in_array($role, $this->getRoles(), TRUE); } @@ -243,7 +243,7 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. */ - public function getAuthorizator(bool $throw = TRUE): ?IAuthorizator + final public function getAuthorizator(bool $throw = TRUE): ?IAuthorizator { if ($throw && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); From d1941a78962df0671a2e725d43ae588bf18623ed Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 Feb 2017 01:32:28 +0100 Subject: [PATCH 069/254] IUserStorage::setExpiration() time must be string like '20 minutes' or NULL --- src/Security/IUserStorage.php | 2 +- src/Security/User.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 411a1f8c..c318e493 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -47,7 +47,7 @@ function getIdentity(): ?IIdentity; /** * Enables log out from the persistent storage after inactivity. - * @param string|int|\DateTimeInterface number of seconds or timestamp + * @param string|NULL like '20 minutes' * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ diff --git a/src/Security/User.php b/src/Security/User.php index 5ff355d0..dc19fa96 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -159,7 +159,7 @@ final public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator /** * Enables log out after inactivity. - * @param string|int|\DateTimeInterface number of seconds or timestamp + * @param string|NULL like '20 minutes' * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ From f0e09562385d4199f33054da78cb37d2732526d7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 9 Feb 2017 20:24:52 +0100 Subject: [PATCH 070/254] composer.json: improved description --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 23dc90e8..eded5ef4 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "nette/security", - "description": "Nette Security: Access Control Component", + "description": "🔑 Provides authentication, authorization and a role-based access control management via ACL (Access Control List)", + "keywords": ["nette", "authentication", "authorization", "ACL"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"], "authors": [ From 08035d4023719c7c875c8579e301903b8d99f588 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 14 Mar 2017 18:44:13 +0100 Subject: [PATCH 071/254] public constants --- src/Security/IAuthenticator.php | 4 ++-- src/Security/IAuthorizator.php | 6 +++--- src/Security/IUserStorage.php | 4 ++-- src/Security/User.php | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 6d8d4309..750811f7 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -16,12 +16,12 @@ interface IAuthenticator { /** Credential key */ - const + public const USERNAME = 0, PASSWORD = 1; /** Exception error code */ - const + public const IDENTITY_NOT_FOUND = 1, INVALID_CREDENTIAL = 2, FAILURE = 3, diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index 1369744e..fdd71fee 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -17,13 +17,13 @@ interface IAuthorizator { /** Set type: all */ - const ALL = NULL; + public const ALL = NULL; /** Permission type: allow */ - const ALLOW = TRUE; + public const ALLOW = TRUE; /** Permission type: deny */ - const DENY = FALSE; + public const DENY = FALSE; /** diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index c318e493..9b90f501 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -16,12 +16,12 @@ interface IUserStorage { /** Log-out reason {@link IUserStorage::getLogoutReason()} */ - const + public const MANUAL = 0b0001, INACTIVITY = 0b0010; /** Log-out behavior */ - const CLEAR_IDENTITY = 0b1000; + public const CLEAR_IDENTITY = 0b1000; /** * Sets the authenticated status of this user. diff --git a/src/Security/User.php b/src/Security/User.php index dc19fa96..47f99838 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -28,7 +28,7 @@ class User use Nette\SmartObject; /** @deprecated */ - const + public const MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::INACTIVITY; From c049ea690fab6ba8677bc8cb85b2594b79fc1be4 Mon Sep 17 00:00:00 2001 From: Andrej Rypo Date: Thu, 8 Jun 2017 04:11:34 +0200 Subject: [PATCH 072/254] comment fix & clarification (#21) --- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index e46c63a3..2e21d451 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -20,8 +20,8 @@ class SecurityExtension extends Nette\DI\CompilerExtension public $defaults = [ 'debugger' => TRUE, 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] - 'roles' => [], // of [role => parents] - 'resources' => [], // of [resource => parents] + 'roles' => [], // of [role => parent(s)] + 'resources' => [], // of [resource => parent] ]; /** @var bool */ From a9215dff7790cf4fc36f56ab522894dddda0dfef Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 10 Jul 2017 20:27:28 +0200 Subject: [PATCH 073/254] composer: improved description --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eded5ef4..254335ac 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "nette/security", - "description": "🔑 Provides authentication, authorization and a role-based access control management via ACL (Access Control List)", + "description": "🔑 Nette Security: provides authentication, authorization and a role-based access control management via ACL (Access Control List)", "keywords": ["nette", "authentication", "authorization", "ACL"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"], From ad60573045927ba49c38c6932b11c50ceeb8e0ae Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 10 Jul 2017 20:53:01 +0200 Subject: [PATCH 074/254] readme: added installation notice --- readme.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/readme.md b/readme.md index a7fb6944..5b8a4dad 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,13 @@ Nette Security: Access Control - how to create custom authenticators and authorizators - Access Control List +Install it using Composer: + +``` +composer require nette/security +``` + +The last stable release requires PHP version 5.6 or newer (is compatible with PHP 7.0 and 7.1). The dev-master version requires PHP 7.1. Authentication ============== From 0bb38ff0ee51055000473bcc295e298d2ff9a96a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 11 Jul 2017 11:36:00 +0200 Subject: [PATCH 075/254] coding style: fixed spaces & use statements order --- src/Bridges/SecurityDI/SecurityExtension.php | 1 - src/Bridges/SecurityTracy/UserPanel.php | 1 - src/Security/IAuthenticator.php | 1 - src/Security/IAuthorizator.php | 2 -- src/Security/IIdentity.php | 1 - src/Security/IResource.php | 1 - src/Security/IRole.php | 1 - src/Security/IUserStorage.php | 1 - src/Security/Identity.php | 1 - src/Security/Passwords.php | 2 -- src/Security/Permission.php | 1 - src/Security/SimpleAuthenticator.php | 1 - src/Security/User.php | 1 - tests/Security.DI/SecurityExtension.authenticator.phpt | 2 +- tests/Security.DI/SecurityExtension.user.phpt | 2 +- tests/Security/MockUserStorage.php | 7 ++++++- tests/Security/Permission.PrivilegeAssert.phpt | 1 + tests/Security/Permission.RolePrivilegeAssert.phpt | 1 + tests/Security/User.authentication.phpt | 2 -- tests/Security/User.authorization.phpt | 6 +----- tests/bootstrap.php | 1 - 21 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 2e21d451..14a2e443 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -92,5 +92,4 @@ public function loadConfiguration() $builder->addAlias('nette.userStorage', $this->prefix('userStorage')); } } - } diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index b057fc11..ef1eea9b 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -56,5 +56,4 @@ public function getPanel(): string require __DIR__ . '/templates/UserPanel.panel.phtml'; return ob_get_clean(); } - } diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 750811f7..6f0e54ee 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -33,5 +33,4 @@ interface IAuthenticator * @throws AuthenticationException */ function authenticate(array $credentials): IIdentity; - } diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index fdd71fee..5d8966a0 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -25,7 +25,6 @@ interface IAuthorizator /** Permission type: deny */ public const DENY = FALSE; - /** * Performs a role-based authorization. * @param string|NULL @@ -33,5 +32,4 @@ interface IAuthorizator * @param string|NULL */ function isAllowed($role, $resource, $privilege): bool; - } diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index d3bd22af..d898e3cb 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -26,5 +26,4 @@ function getId(); * Returns a list of roles that the user is a member of. */ function getRoles(): array; - } diff --git a/src/Security/IResource.php b/src/Security/IResource.php index d5615f6c..67765d97 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -20,5 +20,4 @@ interface IResource * Returns a string identifier of the Resource. */ function getResourceId(): string; - } diff --git a/src/Security/IRole.php b/src/Security/IRole.php index b136687b..6f5c1332 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -20,5 +20,4 @@ interface IRole * Returns a string identifier of the Role. */ function getRoleId(): string; - } diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 9b90f501..65a68849 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -57,5 +57,4 @@ function setExpiration($time, int $flags = 0); * Why was user logged out? */ function getLogoutReason(): ?int; - } diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 6c7e5730..8aba1db8 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -128,5 +128,4 @@ 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 038b5602..c8848f5e 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -19,7 +19,6 @@ class Passwords { use Nette\StaticClass; - /** * Computes salted password hash. Accepts option 'cost' (4-31) */ @@ -53,5 +52,4 @@ public static function needsRehash(string $hash, array $options = []): bool { return password_needs_rehash($hash, PASSWORD_BCRYPT, $options); } - } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index b432e5b7..607a619e 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -764,5 +764,4 @@ private function &getRules($resource, $role, bool $create = FALSE): ?array return $visitor['byRole'][$role]; } - } diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index bff78391..fd73f73b 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -56,5 +56,4 @@ public function authenticate(array $credentials): IIdentity } throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND); } - } diff --git a/src/Security/User.php b/src/Security/User.php index 47f99838..33218d44 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -250,5 +250,4 @@ final public function getAuthorizator(bool $throw = TRUE): ?IAuthorizator } return $this->authorizator; } - } diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index 6e570b77..2ba1e258 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -6,10 +6,10 @@ declare(strict_types=1); -use Nette\DI; use Nette\Bridges\HttpDI\HttpExtension; use Nette\Bridges\HttpDI\SessionExtension; use Nette\Bridges\SecurityDI\SecurityExtension; +use Nette\DI; use Tester\Assert; diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index c454ef33..242a285d 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -6,10 +6,10 @@ declare(strict_types=1); -use Nette\DI; use Nette\Bridges\HttpDI\HttpExtension; use Nette\Bridges\HttpDI\SessionExtension; use Nette\Bridges\SecurityDI\SecurityExtension; +use Nette\DI; use Tester\Assert; diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 7c483fd3..256318b3 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -1,5 +1,4 @@ auth = $state; } + function isAuthenticated(): bool { return $this->auth; } + function setIdentity(Nette\Security\IIdentity $identity = NULL) { $this->identity = $identity; } + function getIdentity(): ?Nette\Security\IIdentity { return $this->identity; } + function setExpiration($time, int $flags = 0) { } + function getLogoutReason(): ?int { } diff --git a/tests/Security/Permission.PrivilegeAssert.phpt b/tests/Security/Permission.PrivilegeAssert.phpt index 5536b4ef..fe2c49f6 100644 --- a/tests/Security/Permission.PrivilegeAssert.phpt +++ b/tests/Security/Permission.PrivilegeAssert.phpt @@ -18,6 +18,7 @@ function falseAssertion() return FALSE; } + function trueAssertion() { return TRUE; diff --git a/tests/Security/Permission.RolePrivilegeAssert.phpt b/tests/Security/Permission.RolePrivilegeAssert.phpt index cceb1412..58289694 100644 --- a/tests/Security/Permission.RolePrivilegeAssert.phpt +++ b/tests/Security/Permission.RolePrivilegeAssert.phpt @@ -18,6 +18,7 @@ function falseAssertion() return FALSE; } + function trueAssertion() { return TRUE; diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 5e7ffc8f..bf3cb3a5 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -21,7 +21,6 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials): Nette\Security\IIdentity { list($username, $password) = $credentials; @@ -35,7 +34,6 @@ class Authenticator implements IAuthenticator return new Identity('John Doe', 'admin'); } } - } diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 808dbe9c..3f0dffa2 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -7,8 +7,8 @@ declare(strict_types=1); use Nette\Security\IAuthenticator; -use Nette\Security\Identity; use Nette\Security\IAuthorizator; +use Nette\Security\Identity; use Tester\Assert; @@ -23,7 +23,6 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials): Nette\Security\IIdentity { list($username, $password) = $credentials; @@ -37,18 +36,15 @@ class Authenticator implements IAuthenticator return new Identity('John Doe', ['admin']); } } - } class Authorizator implements IAuthorizator { - function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { return $role === 'admin' && strpos($resource, 'jany') === FALSE; } - } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 19fc4b32..cf4b918d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,4 @@ Date: Tue, 11 Jul 2017 11:41:11 +0200 Subject: [PATCH 076/254] coding style: TRUE/FALSE/NULL -> true/false/null --- readme.md | 28 ++-- src/Bridges/SecurityDI/SecurityExtension.php | 8 +- src/Bridges/SecurityTracy/UserPanel.php | 2 +- .../templates/UserPanel.panel.phtml | 2 +- src/Security/IAuthorizator.php | 12 +- src/Security/IUserStorage.php | 2 +- src/Security/Identity.php | 2 +- src/Security/Passwords.php | 2 +- src/Security/Permission.php | 138 +++++++++--------- src/Security/SimpleAuthenticator.php | 2 +- src/Security/User.php | 30 ++-- tests/Security/MockUserStorage.php | 4 +- tests/Security/Permission.CMSExample.phpt | 88 +++++------ tests/Security/Permission.DefaultAssert.phpt | 6 +- tests/Security/Permission.DefaultDeny.phpt | 4 +- tests/Security/Permission.DefaultRuleSet.phpt | 4 +- .../Permission.IsAllowedNonExistent.phpt | 2 +- tests/Security/Permission.PrivilegeAllow.phpt | 4 +- .../Security/Permission.PrivilegeAssert.phpt | 12 +- tests/Security/Permission.PrivilegeDeny.phpt | 4 +- tests/Security/Permission.Privileges.phpt | 20 +-- .../Permission.RemoveDefaultDenyAssert.phpt | 4 +- ...AfterItWasAllowedAccessToAllResources.phpt | 2 +- .../Security/Permission.ResourceInherits.phpt | 6 +- ...DefaultAllowRuleWithPrivilegeDenyRule.phpt | 4 +- ...ermission.RoleDefaultRuleSetPrivilege.phpt | 4 +- .../Permission.RolePrivilegeAllow.phpt | 4 +- .../Permission.RolePrivilegeAssert.phpt | 12 +- .../Permission.RolePrivilegeDeny.phpt | 4 +- tests/Security/Permission.RolePrivileges.phpt | 20 +-- .../Permission.RoleRegistryInherits.phpt | 6 +- tests/Security/Permission.RulesRemove.phpt | 12 +- .../Permission.RulesResourceRemove.phpt | 8 +- .../Permission.RulesResourceRemoveAll.phpt | 8 +- tests/Security/User.authentication.phpt | 6 +- tests/Security/User.authorization.phpt | 4 +- 36 files changed, 240 insertions(+), 240 deletions(-) diff --git a/readme.md b/readme.md index 5b8a4dad..e369b8e0 100644 --- a/readme.md +++ b/readme.md @@ -153,7 +153,7 @@ Identity Identity presents a set of user information, as returned by autheticator. It's an object implementing [api:Nette\Security\IIdentity] interface, with default implementation [api:Nette\Security\Identity]. Class has methods `getId()`, that returns users ID (for example primary key for the respective database row), and `getRoles()`, which returns an array of all roles user is in. User data can be access as if they were identity properties. -Identity is not erased when the user is logged out. So, if identity exists, it by itself does not grant that the user is also logged in. If we would like to explicitly delete the identity for some reason, we logout the user by calling `$user->logout(TRUE)`. +Identity is not erased when the user is logged out. So, if identity exists, it by itself does not grant that the user is also logged in. If we would like to explicitly delete the identity for some reason, we logout the user by calling `$user->logout(true)`. Service `user` of class [api:Nette\Security\User] keeps the identity in session and uses it to all authorizations. Identity can be access with `getIdentity` upon `$user`: @@ -218,7 +218,7 @@ class MyAuthorizator extends Nette\Object function isAllowed($role, $resource, $privilege) { - return ...; // returns either TRUE or FALSE + return ...; // returns either true or false } } @@ -273,7 +273,7 @@ Trivial, isn't it? This ensures all the properties of the parents will be inheri Do note the method `getRoleParents()`, which returns an array of all direct parent roles, and the method `roleIntheritsFrom()`, which checks whether a role extends another. Their usage: ```php -$acl->roleInheritsFrom('administrator', 'guest'); // TRUE +$acl->roleInheritsFrom('administrator', 'guest'); // true $acl->getRoleParents('administrator'); // array('registered') - only direct parents ``` @@ -308,27 +308,27 @@ Now when we have created the set of rules, we may simply ask the authorization q ```php // can guest view articles? -echo $acl->isAllowed('guest', 'article', 'view'); // TRUE +echo $acl->isAllowed('guest', 'article', 'view'); // true // can guest edit an article? -echo $acl->isAllowed('guest', 'article', 'edit'); // FALSE +echo $acl->isAllowed('guest', 'article', 'edit'); // false // may guest add comments? -echo $acl->isAllowed('guest', 'comments', 'add'); // FALSE +echo $acl->isAllowed('guest', 'comments', 'add'); // false ``` The same is true for the registered user, though he is allowed to add a comment: ```php -echo $acl->isAllowed('registered', 'article', 'view'); // TRUE -echo $acl->isAllowed('registered', 'comments', 'add'); // TRUE -echo $acl->isAllowed('registered', 'backend', 'view'); // FALSE +echo $acl->isAllowed('registered', 'article', 'view'); // true +echo $acl->isAllowed('registered', 'comments', 'add'); // true +echo $acl->isAllowed('registered', 'backend', 'view'); // false ``` Administrator is allowed to do everything: ```php -echo $acl->isAllowed('administrator', 'article', 'view'); // TRUE -echo $acl->isAllowed('administrator', 'commend', 'add'); // TRUE -echo $acl->isAllowed('administrator', 'poll', 'edit'); // TRUE +echo $acl->isAllowed('administrator', 'article', 'view'); // true +echo $acl->isAllowed('administrator', 'commend', 'add'); // true +echo $acl->isAllowed('administrator', 'poll', 'edit'); // true ``` Admin rules may possibly be defined without any restrictions (without inheriting from any other roles): @@ -354,11 +354,11 @@ $acl->deny('guest', 'backend'); // example A: role admin has lower weight than role guest $acl->addRole('john', array('admin', 'guest')); -$acl->isAllowed('john', 'backend'); // FALSE +$acl->isAllowed('john', 'backend'); // false // example B: role admin has greater weight than role guest $acl->addRole('mary', array('guest', 'admin')); -$acl->isAllowed('mary', 'backend'); // TRUE +$acl->isAllowed('mary', 'backend'); // true ``` diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 14a2e443..b401c373 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -18,7 +18,7 @@ class SecurityExtension extends Nette\DI\CompilerExtension { public $defaults = [ - 'debugger' => TRUE, + 'debugger' => true, 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] 'roles' => [], // of [role => parent(s)] 'resources' => [], // of [resource => parent] @@ -28,7 +28,7 @@ class SecurityExtension extends Nette\DI\CompilerExtension private $debugMode; - public function __construct(bool $debugMode = FALSE) + public function __construct(bool $debugMode = false) { $this->debugMode = $debugMode; } @@ -56,9 +56,9 @@ public function loadConfiguration() $usersList = $usersRoles = []; foreach ($config['users'] as $username => $data) { $data = is_array($data) ? $data : ['password' => $data]; - $this->validateConfig(['password' => NULL, 'roles' => NULL], $data, $this->prefix("security.users.$username")); + $this->validateConfig(['password' => null, 'roles' => null], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; - $usersRoles[$username] = $data['roles'] ?? NULL; + $usersRoles[$username] = $data['roles'] ?? null; } $builder->addDefinition($this->prefix('authenticator')) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index ef1eea9b..0ccbd18b 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -36,7 +36,7 @@ public function __construct(Nette\Security\User $user) public function getTab(): ?string { if (headers_sent() && !session_id()) { - return NULL; + return null; } ob_start(function () {}); diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml index fdc801d0..e410c959 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml @@ -8,5 +8,5 @@ use Tracy\Dumper;

isLoggedIn()): ?>Logged inUnlogged

- getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => TRUE]); else: ?>

no identity

+ getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => true]); else: ?>

no identity

diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index 5d8966a0..003ab155 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -17,19 +17,19 @@ interface IAuthorizator { /** Set type: all */ - public const ALL = NULL; + public const ALL = null; /** Permission type: allow */ - public const ALLOW = TRUE; + public const ALLOW = true; /** Permission type: deny */ - public const DENY = FALSE; + public const DENY = false; /** * Performs a role-based authorization. - * @param string|NULL - * @param string|NULL - * @param string|NULL + * @param string|null + * @param string|null + * @param string|null */ function isAllowed($role, $resource, $privilege): bool; } diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 65a68849..6afe5e68 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -47,7 +47,7 @@ function getIdentity(): ?IIdentity; /** * Enables log out from the persistent storage after inactivity. - * @param string|NULL like '20 minutes' + * @param string|null like '20 minutes' * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 8aba1db8..63d00506 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -37,7 +37,7 @@ class Identity implements IIdentity private $data; - public function __construct($id, $roles = NULL, iterable $data = NULL) + public function __construct($id, $roles = null, iterable $data = null) { $this->setId($id); $this->setRoles((array) $roles); diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index c8848f5e..ac11c43b 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -29,7 +29,7 @@ public static function hash(string $password, array $options = []): string } $hash = password_hash($password, PASSWORD_BCRYPT, $options); - if ($hash === FALSE || strlen($hash) < 60) { + if ($hash === false || strlen($hash) < 60) { throw new Nette\InvalidStateException('Hash computed by password_hash is invalid.'); } return $hash; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 607a619e..db99ae27 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -35,7 +35,7 @@ class Permission implements IAuthorizator 'allRoles' => [ 'allPrivileges' => [ 'type' => self::DENY, - 'assert' => NULL, + 'assert' => null, ], 'byPrivilege' => [], ], @@ -59,24 +59,24 @@ class Permission implements IAuthorizator * @throws Nette\InvalidStateException * @return static */ - public function addRole(string $role, $parents = NULL) + public function addRole(string $role, $parents = null) { - $this->checkRole($role, FALSE); + $this->checkRole($role, false); if (isset($this->roles[$role])) { throw new Nette\InvalidStateException("Role '$role' already exists in the list."); } $roleParents = []; - if ($parents !== NULL) { + if ($parents !== null) { if (!is_array($parents)) { $parents = [$parents]; } foreach ($parents as $parent) { $this->checkRole($parent); - $roleParents[$parent] = TRUE; - $this->roles[$parent]['children'][$role] = TRUE; + $roleParents[$parent] = true; + $this->roles[$parent]['children'][$role] = true; } } @@ -90,11 +90,11 @@ public function addRole(string $role, $parents = NULL) /** - * Returns TRUE if the Role exists in the list. + * Returns true if the Role exists in the list. */ public function hasRole(string $role): bool { - $this->checkRole($role, FALSE); + $this->checkRole($role, false); return isset($this->roles[$role]); } @@ -103,7 +103,7 @@ public function hasRole(string $role): bool * Checks whether Role is valid and exists in the list. * @throws Nette\InvalidStateException */ - private function checkRole(string $role, bool $throw = TRUE): void + private function checkRole(string $role, bool $throw = true): void { if (!is_string($role) || $role === '') { throw new Nette\InvalidArgumentException('Role must be a non-empty string.'); @@ -134,11 +134,11 @@ public function getRoleParents(string $role): array /** - * Returns TRUE if $role inherits from $inherit. If $onlyParents is TRUE, + * Returns true if $role inherits from $inherit. If $onlyParents is true, * then $role must inherit directly from $inherit. * @throws Nette\InvalidStateException */ - public function roleInheritsFrom(string $role, string $inherit, bool $onlyParents = FALSE): bool + public function roleInheritsFrom(string $role, string $inherit, bool $onlyParents = false): bool { $this->checkRole($role); $this->checkRole($inherit); @@ -151,11 +151,11 @@ public function roleInheritsFrom(string $role, string $inherit, bool $onlyParent foreach ($this->roles[$role]['parents'] as $parent => $foo) { if ($this->roleInheritsFrom($parent, $inherit)) { - return TRUE; + return true; } } - return FALSE; + return false; } @@ -232,17 +232,17 @@ public function removeAllRoles() * @throws Nette\InvalidStateException * @return static */ - public function addResource(string $resource, string $parent = NULL) + public function addResource(string $resource, string $parent = null) { - $this->checkResource($resource, FALSE); + $this->checkResource($resource, false); if (isset($this->resources[$resource])) { throw new Nette\InvalidStateException("Resource '$resource' already exists in the list."); } - if ($parent !== NULL) { + if ($parent !== null) { $this->checkResource($parent); - $this->resources[$parent]['children'][$resource] = TRUE; + $this->resources[$parent]['children'][$resource] = true; } $this->resources[$resource] = [ @@ -255,11 +255,11 @@ public function addResource(string $resource, string $parent = NULL) /** - * Returns TRUE if the Resource exists in the list. + * Returns true if the Resource exists in the list. */ public function hasResource(string $resource): bool { - $this->checkResource($resource, FALSE); + $this->checkResource($resource, false); return isset($this->resources[$resource]); } @@ -268,7 +268,7 @@ public function hasResource(string $resource): bool * Checks whether Resource is valid and exists in the list. * @throws Nette\InvalidStateException */ - private function checkResource(string $resource, bool $throw = TRUE): void + private function checkResource(string $resource, bool $throw = true): void { if (!is_string($resource) || $resource === '') { throw new Nette\InvalidArgumentException('Resource must be a non-empty string.'); @@ -289,36 +289,36 @@ public function getResources(): array /** - * Returns TRUE if $resource inherits from $inherit. If $onlyParents is TRUE, + * Returns true if $resource inherits from $inherit. If $onlyParents is true, * then $resource must inherit directly from $inherit. * * @throws Nette\InvalidStateException */ - public function resourceInheritsFrom(string $resource, string $inherit, bool $onlyParent = FALSE): bool + public function resourceInheritsFrom(string $resource, string $inherit, bool $onlyParent = false): bool { $this->checkResource($resource); $this->checkResource($inherit); - if ($this->resources[$resource]['parent'] === NULL) { - return FALSE; + if ($this->resources[$resource]['parent'] === null) { + return false; } $parent = $this->resources[$resource]['parent']; if ($inherit === $parent) { - return TRUE; + return true; } elseif ($onlyParent) { - return FALSE; + return false; } - while ($this->resources[$parent]['parent'] !== NULL) { + while ($this->resources[$parent]['parent'] !== null) { $parent = $this->resources[$parent]['parent']; if ($inherit === $parent) { - return TRUE; + return true; } } - return FALSE; + return false; } @@ -333,7 +333,7 @@ public function removeResource(string $resource) $this->checkResource($resource); $parent = $this->resources[$resource]['parent']; - if ($parent !== NULL) { + if ($parent !== null) { unset($this->resources[$parent]['children'][$resource]); } @@ -380,32 +380,32 @@ public function removeAllResources() /** * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s). - * If $assertion is provided, then it must return TRUE in order for rule to apply. + * If $assertion is provided, then it must return true in order for rule to apply. * * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @return static */ - public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = NULL) + public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = null) { - $this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion); + $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion); return $this; } /** * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s). - * If $assertion is provided, then it must return TRUE in order for rule to apply. + * If $assertion is provided, then it must return true in order for rule to apply. * * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @param string|string[]|Permission::ALL * @return static */ - public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = NULL) + public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = null) { - $this->setRule(TRUE, self::DENY, $roles, $resources, $privileges, $assertion); + $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion); return $this; } @@ -420,7 +420,7 @@ public function deny($roles = self::ALL, $resources = self::ALL, $privileges = s */ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) { - $this->setRule(FALSE, self::ALLOW, $roles, $resources, $privileges); + $this->setRule(false, self::ALLOW, $roles, $resources, $privileges); return $this; } @@ -435,7 +435,7 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile */ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL) { - $this->setRule(FALSE, self::DENY, $roles, $resources, $privileges); + $this->setRule(false, self::DENY, $roles, $resources, $privileges); return $this; } @@ -450,9 +450,9 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * @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) { - // ensure that all specified Roles exist; normalize input to array of Roles or NULL + // ensure that all specified Roles exist; normalize input to array of Roles or null if ($roles === self::ALL) { $roles = [self::ALL]; @@ -466,7 +466,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } } - // ensure that all specified Resources exist; normalize input to array of Resources or NULL + // ensure that all specified Resources exist; normalize input to array of Resources or null if ($resources === self::ALL) { $resources = [self::ALL]; @@ -491,7 +491,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile if ($toAdd) { // add to the rules foreach ($resources as $resource) { foreach ($roles as $role) { - $rules = &$this->getRules($resource, $role, TRUE); + $rules = &$this->getRules($resource, $role, true); if (count($privileges) === 0) { $rules['allPrivileges']['type'] = $type; $rules['allPrivileges']['assert'] = $assertion; @@ -511,7 +511,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile foreach ($resources as $resource) { foreach ($roles as $role) { $rules = &$this->getRules($resource, $role); - if ($rules === NULL) { + if ($rules === null) { continue; } if (count($privileges) === 0) { @@ -520,7 +520,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile $rules = [ 'allPrivileges' => [ 'type' => self::DENY, - 'assert' => NULL, + 'assert' => null, ], 'byPrivilege' => [], ]; @@ -550,7 +550,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile /** - * Returns TRUE if and only if the Role has access to [certain $privileges upon] the Resource. + * Returns true if and only if the Role has access to [certain $privileges upon] the Resource. * * This method checks Role inheritance using a depth-first traversal of the Role list. * The highest priority parent (i.e., the parent most recently added) is checked first, @@ -582,34 +582,34 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = do { // depth-first search on $role if it is not 'allRoles' pseudo-parent - if ($role !== NULL && NULL !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) { + if ($role !== null && null !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) { break; } if ($privilege === self::ALL) { if ($rules = $this->getRules($resource, self::ALL)) { // look for rule on 'allRoles' psuedo-parent foreach ($rules['byPrivilege'] as $privilege => $rule) { - if (self::DENY === ($result = $this->getRuleType($resource, NULL, $privilege))) { + if (self::DENY === ($result = $this->getRuleType($resource, null, $privilege))) { break 2; } } - if (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) { + if (null !== ($result = $this->getRuleType($resource, null, null))) { break; } } } else { - if (NULL !== ($result = $this->getRuleType($resource, NULL, $privilege))) { // look for rule on 'allRoles' pseudo-parent + if (null !== ($result = $this->getRuleType($resource, null, $privilege))) { // look for rule on 'allRoles' pseudo-parent break; - } elseif (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) { + } elseif (null !== ($result = $this->getRuleType($resource, null, null))) { break; } } $resource = $this->resources[$resource]['parent']; // try next Resource - } while (TRUE); + } while (true); - $this->queriedRole = $this->queriedResource = NULL; + $this->queriedRole = $this->queriedResource = null; return $result; } @@ -641,7 +641,7 @@ public function getQueriedResource() * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule * allowing/denying $role access to a/all $privilege upon $resource. * @param bool all (true) or one? - * @return mixed NULL if no applicable rule is found, otherwise returns ALLOW or DENY + * @return mixed null if no applicable rule is found, otherwise returns ALLOW or DENY */ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) { @@ -650,7 +650,7 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) 'stack' => [$role], ]; - while (NULL !== ($role = array_pop($dfs['stack']))) { + while (null !== ($role = array_pop($dfs['stack']))) { if (isset($dfs['visited'][$role])) { continue; } @@ -661,25 +661,25 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) return self::DENY; } } - if (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) { + if (null !== ($type = $this->getRuleType($resource, $role, null))) { return $type; } } } else { - if (NULL !== ($type = $this->getRuleType($resource, $role, $privilege))) { + if (null !== ($type = $this->getRuleType($resource, $role, $privilege))) { return $type; - } elseif (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) { + } elseif (null !== ($type = $this->getRuleType($resource, $role, null))) { return $type; } } - $dfs['visited'][$role] = TRUE; + $dfs['visited'][$role] = true; foreach ($this->roles[$role]['parents'] as $roleParent => $foo) { $dfs['stack'][] = $roleParent; } } - return NULL; + return null; } @@ -688,32 +688,32 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) * @param string|Permission::ALL * @param string|Permission::ALL * @param string|Permission::ALL - * @return bool|NULL NULL if a rule does not exist or assertion fails, otherwise returns ALLOW or DENY + * @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 { if (!$rules = $this->getRules($resource, $role)) { - return NULL; + return null; } if ($privilege === self::ALL) { if (isset($rules['allPrivileges'])) { $rule = $rules['allPrivileges']; } else { - return NULL; + return null; } } elseif (!isset($rules['byPrivilege'][$privilege])) { - return NULL; + return null; } else { $rule = $rules['byPrivilege'][$privilege]; } - if ($rule['assert'] === NULL || Nette\Utils\Callback::invoke($rule['assert'], $this, $role, $resource, $privilege)) { + if ($rule['assert'] === null || Nette\Utils\Callback::invoke($rule['assert'], $this, $role, $resource, $privilege)) { return $rule['type']; } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) { - return NULL; + return null; } elseif (self::ALLOW === $rule['type']) { return self::DENY; @@ -725,14 +725,14 @@ 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. + * 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|Permission::ALL * @param string|Permission::ALL */ - private function &getRules($resource, $role, bool $create = FALSE): ?array + private function &getRules($resource, $role, bool $create = false): ?array { - $null = NULL; + $null = null; if ($resource === self::ALL) { $visitor = &$this->rules['allResources']; } else { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index fd73f73b..c2915494 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -48,7 +48,7 @@ public function authenticate(array $credentials): IIdentity foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { if ((string) $pass === (string) $password) { - return new Identity($name, $this->usersRoles[$name] ?? NULL); + return new Identity($name, $this->usersRoles[$name] ?? null); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); } diff --git a/src/Security/User.php b/src/Security/User.php index 33218d44..09ed9fbc 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -54,7 +54,7 @@ class User private $authorizator; - public function __construct(IUserStorage $storage, IAuthenticator $authenticator = NULL, IAuthorizator $authorizator = NULL) + public function __construct(IUserStorage $storage, IAuthenticator $authenticator = null, IAuthorizator $authorizator = null) { $this->storage = $storage; $this->authenticator = $authenticator; @@ -77,14 +77,14 @@ final public function getStorage(): IUserStorage * @param mixed optional parameter (e.g. password) * @throws AuthenticationException if authentication was not successful */ - public function login($id = NULL, $password = NULL): void + public function login($id = null, $password = null): void { - $this->logout(TRUE); + $this->logout(true); if (!$id instanceof IIdentity) { $id = $this->getAuthenticator()->authenticate(func_get_args()); } $this->storage->setIdentity($id); - $this->storage->setAuthenticated(TRUE); + $this->storage->setAuthenticated(true); $this->onLoggedIn($this); } @@ -93,14 +93,14 @@ public function login($id = NULL, $password = NULL): void * Logs out the user from the current session. * @param bool clear the identity from persistent storage? */ - final public function logout(bool $clearIdentity = FALSE): void + final public function logout(bool $clearIdentity = false): void { if ($this->isLoggedIn()) { $this->onLoggedOut($this); - $this->storage->setAuthenticated(FALSE); + $this->storage->setAuthenticated(false); } if ($clearIdentity) { - $this->storage->setIdentity(NULL); + $this->storage->setIdentity(null); } } @@ -130,7 +130,7 @@ final public function getIdentity(): ?IIdentity public function getId() { $identity = $this->getIdentity(); - return $identity ? $identity->getId() : NULL; + return $identity ? $identity->getId() : null; } @@ -148,7 +148,7 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. */ - final public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator + final public function getAuthenticator(bool $throw = true): ?IAuthenticator { if ($throw && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); @@ -159,7 +159,7 @@ final public function getAuthenticator(bool $throw = TRUE): ?IAuthenticator /** * Enables log out after inactivity. - * @param string|NULL like '20 minutes' + * @param string|null like '20 minutes' * @param int flag IUserStorage::CLEAR_IDENTITY * @return static */ @@ -209,23 +209,23 @@ public function getRoles(): array */ final public function isInRole(string $role): bool { - return in_array($role, $this->getRoles(), TRUE); + return in_array($role, $this->getRoles(), true); } /** * Has a user effective access to the Resource? - * If $resource is NULL, then the query applies to all resources. + * If $resource is null, then the query applies to all resources. */ public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL): bool { foreach ($this->getRoles() as $role) { if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) { - return TRUE; + return true; } } - return FALSE; + return false; } @@ -243,7 +243,7 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. */ - final public function getAuthorizator(bool $throw = TRUE): ?IAuthorizator + final public function getAuthorizator(bool $throw = true): ?IAuthorizator { if ($throw && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 256318b3..4152cde6 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -3,7 +3,7 @@ class MockUserStorage implements Nette\Security\IUserStorage { - private $auth = FALSE; + private $auth = false; private $identity; @@ -19,7 +19,7 @@ function isAuthenticated(): bool } - function setIdentity(Nette\Security\IIdentity $identity = NULL) + function setIdentity(Nette\Security\IIdentity $identity = null) { $this->identity = $identity; } diff --git a/tests/Security/Permission.CMSExample.phpt b/tests/Security/Permission.CMSExample.phpt index ea080ef9..6f889f63 100644 --- a/tests/Security/Permission.CMSExample.phpt +++ b/tests/Security/Permission.CMSExample.phpt @@ -20,57 +20,57 @@ $acl->addRole('editor', 'staff'); // editor inherits permissions from staff $acl->addRole('administrator'); // Guest may only view content -$acl->allow('guest', NULL, 'view'); +$acl->allow('guest', null, 'view'); // Staff inherits view privilege from guest, but also needs additional privileges -$acl->allow('staff', NULL, ['edit', 'submit', 'revise']); +$acl->allow('staff', null, ['edit', 'submit', 'revise']); // Editor inherits view, edit, submit, and revise privileges, but also needs additional privileges -$acl->allow('editor', NULL, ['publish', 'archive', 'delete']); +$acl->allow('editor', null, ['publish', 'archive', 'delete']); // Administrator inherits nothing but is allowed all privileges $acl->allow('administrator'); // Access control checks based on above permission sets -Assert::true($acl->isAllowed('guest', NULL, 'view')); -Assert::false($acl->isAllowed('guest', NULL, 'edit')); -Assert::false($acl->isAllowed('guest', NULL, 'submit')); -Assert::false($acl->isAllowed('guest', NULL, 'revise')); -Assert::false($acl->isAllowed('guest', NULL, 'publish')); -Assert::false($acl->isAllowed('guest', NULL, 'archive')); -Assert::false($acl->isAllowed('guest', NULL, 'delete')); -Assert::false($acl->isAllowed('guest', NULL, 'unknown')); +Assert::true($acl->isAllowed('guest', null, 'view')); +Assert::false($acl->isAllowed('guest', null, 'edit')); +Assert::false($acl->isAllowed('guest', null, 'submit')); +Assert::false($acl->isAllowed('guest', null, 'revise')); +Assert::false($acl->isAllowed('guest', null, 'publish')); +Assert::false($acl->isAllowed('guest', null, 'archive')); +Assert::false($acl->isAllowed('guest', null, 'delete')); +Assert::false($acl->isAllowed('guest', null, 'unknown')); Assert::false($acl->isAllowed('guest')); -Assert::true($acl->isAllowed('staff', NULL, 'view')); -Assert::true($acl->isAllowed('staff', NULL, 'edit')); -Assert::true($acl->isAllowed('staff', NULL, 'submit')); -Assert::true($acl->isAllowed('staff', NULL, 'revise')); -Assert::false($acl->isAllowed('staff', NULL, 'publish')); -Assert::false($acl->isAllowed('staff', NULL, 'archive')); -Assert::false($acl->isAllowed('staff', NULL, 'delete')); -Assert::false($acl->isAllowed('staff', NULL, 'unknown')); +Assert::true($acl->isAllowed('staff', null, 'view')); +Assert::true($acl->isAllowed('staff', null, 'edit')); +Assert::true($acl->isAllowed('staff', null, 'submit')); +Assert::true($acl->isAllowed('staff', null, 'revise')); +Assert::false($acl->isAllowed('staff', null, 'publish')); +Assert::false($acl->isAllowed('staff', null, 'archive')); +Assert::false($acl->isAllowed('staff', null, 'delete')); +Assert::false($acl->isAllowed('staff', null, 'unknown')); Assert::false($acl->isAllowed('staff')); -Assert::true($acl->isAllowed('editor', NULL, 'view')); -Assert::true($acl->isAllowed('editor', NULL, 'edit')); -Assert::true($acl->isAllowed('editor', NULL, 'submit')); -Assert::true($acl->isAllowed('editor', NULL, 'revise')); -Assert::true($acl->isAllowed('editor', NULL, 'publish')); -Assert::true($acl->isAllowed('editor', NULL, 'archive')); -Assert::true($acl->isAllowed('editor', NULL, 'delete')); -Assert::false($acl->isAllowed('editor', NULL, 'unknown')); +Assert::true($acl->isAllowed('editor', null, 'view')); +Assert::true($acl->isAllowed('editor', null, 'edit')); +Assert::true($acl->isAllowed('editor', null, 'submit')); +Assert::true($acl->isAllowed('editor', null, 'revise')); +Assert::true($acl->isAllowed('editor', null, 'publish')); +Assert::true($acl->isAllowed('editor', null, 'archive')); +Assert::true($acl->isAllowed('editor', null, 'delete')); +Assert::false($acl->isAllowed('editor', null, 'unknown')); Assert::false($acl->isAllowed('editor')); -Assert::true($acl->isAllowed('administrator', NULL, 'view')); -Assert::true($acl->isAllowed('administrator', NULL, 'edit')); -Assert::true($acl->isAllowed('administrator', NULL, 'submit')); -Assert::true($acl->isAllowed('administrator', NULL, 'revise')); -Assert::true($acl->isAllowed('administrator', NULL, 'publish')); -Assert::true($acl->isAllowed('administrator', NULL, 'archive')); -Assert::true($acl->isAllowed('administrator', NULL, 'delete')); -Assert::true($acl->isAllowed('administrator', NULL, 'unknown')); +Assert::true($acl->isAllowed('administrator', null, 'view')); +Assert::true($acl->isAllowed('administrator', null, 'edit')); +Assert::true($acl->isAllowed('administrator', null, 'submit')); +Assert::true($acl->isAllowed('administrator', null, 'revise')); +Assert::true($acl->isAllowed('administrator', null, 'publish')); +Assert::true($acl->isAllowed('administrator', null, 'archive')); +Assert::true($acl->isAllowed('administrator', null, 'delete')); +Assert::true($acl->isAllowed('administrator', null, 'unknown')); Assert::true($acl->isAllowed('administrator')); // Some checks on specific areas, which inherit access controls from the root ACL node @@ -107,18 +107,18 @@ $acl->deny('staff', 'latest', 'revise'); // Deny everyone access to archive news announcements $acl->addResource('announcement', 'news'); -$acl->deny(NULL, 'announcement', 'archive'); +$acl->deny(null, 'announcement', 'archive'); // Access control checks for the above refined permission sets -Assert::true($acl->isAllowed('marketing', NULL, 'view')); -Assert::true($acl->isAllowed('marketing', NULL, 'edit')); -Assert::true($acl->isAllowed('marketing', NULL, 'submit')); -Assert::true($acl->isAllowed('marketing', NULL, 'revise')); -Assert::false($acl->isAllowed('marketing', NULL, 'publish')); -Assert::false($acl->isAllowed('marketing', NULL, 'archive')); -Assert::false($acl->isAllowed('marketing', NULL, 'delete')); -Assert::false($acl->isAllowed('marketing', NULL, 'unknown')); +Assert::true($acl->isAllowed('marketing', null, 'view')); +Assert::true($acl->isAllowed('marketing', null, 'edit')); +Assert::true($acl->isAllowed('marketing', null, 'submit')); +Assert::true($acl->isAllowed('marketing', null, 'revise')); +Assert::false($acl->isAllowed('marketing', null, 'publish')); +Assert::false($acl->isAllowed('marketing', null, 'archive')); +Assert::false($acl->isAllowed('marketing', null, 'delete')); +Assert::false($acl->isAllowed('marketing', null, 'unknown')); Assert::false($acl->isAllowed('marketing')); Assert::true($acl->isAllowed('marketing', 'newsletter', 'publish')); diff --git a/tests/Security/Permission.DefaultAssert.phpt b/tests/Security/Permission.DefaultAssert.phpt index 56ab756b..3b930c21 100644 --- a/tests/Security/Permission.DefaultAssert.phpt +++ b/tests/Security/Permission.DefaultAssert.phpt @@ -15,10 +15,10 @@ require __DIR__ . '/../bootstrap.php'; function falseAssertion() { - return FALSE; + return false; } $acl = new Permission; -$acl->deny(NULL, NULL, NULL, 'falseAssertion'); -Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); +$acl->deny(null, null, null, 'falseAssertion'); +Assert::true($acl->isAllowed(null, null, 'somePrivilege')); diff --git a/tests/Security/Permission.DefaultDeny.phpt b/tests/Security/Permission.DefaultDeny.phpt index 21fd6819..8be91d02 100644 --- a/tests/Security/Permission.DefaultDeny.phpt +++ b/tests/Security/Permission.DefaultDeny.phpt @@ -15,8 +15,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; Assert::false($acl->isAllowed()); -Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); +Assert::false($acl->isAllowed(null, null, 'somePrivilege')); $acl->addRole('guest'); Assert::false($acl->isAllowed('guest')); -Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); +Assert::false($acl->isAllowed('guest', null, 'somePrivilege')); diff --git a/tests/Security/Permission.DefaultRuleSet.phpt b/tests/Security/Permission.DefaultRuleSet.phpt index 42a70432..06fadcc1 100644 --- a/tests/Security/Permission.DefaultRuleSet.phpt +++ b/tests/Security/Permission.DefaultRuleSet.phpt @@ -16,8 +16,8 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); Assert::true($acl->isAllowed()); -Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); +Assert::true($acl->isAllowed(null, null, 'somePrivilege')); $acl->deny(); Assert::false($acl->isAllowed()); -Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); +Assert::false($acl->isAllowed(null, null, 'somePrivilege')); diff --git a/tests/Security/Permission.IsAllowedNonExistent.phpt b/tests/Security/Permission.IsAllowedNonExistent.phpt index 1843af43..260303c6 100644 --- a/tests/Security/Permission.IsAllowedNonExistent.phpt +++ b/tests/Security/Permission.IsAllowedNonExistent.phpt @@ -20,5 +20,5 @@ Assert::exception(function () { Assert::exception(function () { $acl = new Permission; - $acl->isAllowed(NULL, 'nonexistent'); + $acl->isAllowed(null, 'nonexistent'); }, Nette\InvalidStateException::class, "Resource 'nonexistent' does not exist."); diff --git a/tests/Security/Permission.PrivilegeAllow.phpt b/tests/Security/Permission.PrivilegeAllow.phpt index 9514c336..6cbfe581 100644 --- a/tests/Security/Permission.PrivilegeAllow.phpt +++ b/tests/Security/Permission.PrivilegeAllow.phpt @@ -14,5 +14,5 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -$acl->allow(NULL, NULL, 'somePrivilege'); -Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); +$acl->allow(null, null, 'somePrivilege'); +Assert::true($acl->isAllowed(null, null, 'somePrivilege')); diff --git a/tests/Security/Permission.PrivilegeAssert.phpt b/tests/Security/Permission.PrivilegeAssert.phpt index fe2c49f6..165659ab 100644 --- a/tests/Security/Permission.PrivilegeAssert.phpt +++ b/tests/Security/Permission.PrivilegeAssert.phpt @@ -15,19 +15,19 @@ require __DIR__ . '/../bootstrap.php'; function falseAssertion() { - return FALSE; + return false; } function trueAssertion() { - return TRUE; + return true; } $acl = new Permission; -$acl->allow(NULL, NULL, 'somePrivilege', 'trueAssertion'); -Assert::true($acl->isAllowed(NULL, NULL, 'somePrivilege')); +$acl->allow(null, null, 'somePrivilege', 'trueAssertion'); +Assert::true($acl->isAllowed(null, null, 'somePrivilege')); -$acl->allow(NULL, NULL, 'somePrivilege', 'falseAssertion'); -Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); +$acl->allow(null, null, 'somePrivilege', 'falseAssertion'); +Assert::false($acl->isAllowed(null, null, 'somePrivilege')); diff --git a/tests/Security/Permission.PrivilegeDeny.phpt b/tests/Security/Permission.PrivilegeDeny.phpt index eb94c843..e7628711 100644 --- a/tests/Security/Permission.PrivilegeDeny.phpt +++ b/tests/Security/Permission.PrivilegeDeny.phpt @@ -15,5 +15,5 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->allow(); -$acl->deny(NULL, NULL, 'somePrivilege'); -Assert::false($acl->isAllowed(NULL, NULL, 'somePrivilege')); +$acl->deny(null, null, 'somePrivilege'); +Assert::false($acl->isAllowed(null, null, 'somePrivilege')); diff --git a/tests/Security/Permission.Privileges.phpt b/tests/Security/Permission.Privileges.phpt index a32fb09c..7121dda4 100644 --- a/tests/Security/Permission.Privileges.phpt +++ b/tests/Security/Permission.Privileges.phpt @@ -14,13 +14,13 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -$acl->allow(NULL, NULL, ['p1', 'p2', 'p3']); -Assert::true($acl->isAllowed(NULL, NULL, 'p1')); -Assert::true($acl->isAllowed(NULL, NULL, 'p2')); -Assert::true($acl->isAllowed(NULL, NULL, 'p3')); -Assert::false($acl->isAllowed(NULL, NULL, 'p4')); -$acl->deny(NULL, NULL, 'p1'); -Assert::false($acl->isAllowed(NULL, NULL, 'p1')); -$acl->deny(NULL, NULL, ['p2', 'p3']); -Assert::false($acl->isAllowed(NULL, NULL, 'p2')); -Assert::false($acl->isAllowed(NULL, NULL, 'p3')); +$acl->allow(null, null, ['p1', 'p2', 'p3']); +Assert::true($acl->isAllowed(null, null, 'p1')); +Assert::true($acl->isAllowed(null, null, 'p2')); +Assert::true($acl->isAllowed(null, null, 'p3')); +Assert::false($acl->isAllowed(null, null, 'p4')); +$acl->deny(null, null, 'p1'); +Assert::false($acl->isAllowed(null, null, 'p1')); +$acl->deny(null, null, ['p2', 'p3']); +Assert::false($acl->isAllowed(null, null, 'p2')); +Assert::false($acl->isAllowed(null, null, 'p3')); diff --git a/tests/Security/Permission.RemoveDefaultDenyAssert.phpt b/tests/Security/Permission.RemoveDefaultDenyAssert.phpt index cd6568f0..496c37e1 100644 --- a/tests/Security/Permission.RemoveDefaultDenyAssert.phpt +++ b/tests/Security/Permission.RemoveDefaultDenyAssert.phpt @@ -15,12 +15,12 @@ require __DIR__ . '/../bootstrap.php'; function falseAssertion() { - return FALSE; + return false; } $acl = new Permission; -$acl->deny(NULL, NULL, NULL, 'falseAssertion'); +$acl->deny(null, null, null, 'falseAssertion'); Assert::true($acl->isAllowed()); $acl->removeDeny(); Assert::false($acl->isAllowed()); diff --git a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt index 9feccdc0..a4cda115 100644 --- a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt +++ b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt @@ -20,7 +20,7 @@ $acl->addRole('test1'); $acl->addRole('test2'); $acl->addResource('Test'); -$acl->allow(NULL,'Test','xxx'); +$acl->allow(null,'Test','xxx'); // error test $acl->removeRole('test0'); diff --git a/tests/Security/Permission.ResourceInherits.phpt b/tests/Security/Permission.ResourceInherits.phpt index 1163c94d..2512733c 100644 --- a/tests/Security/Permission.ResourceInherits.phpt +++ b/tests/Security/Permission.ResourceInherits.phpt @@ -19,10 +19,10 @@ $acl->addResource('building', 'city'); $acl->addResource('room', 'building'); Assert::same(['city', 'building', 'room'], $acl->getResources()); -Assert::true($acl->resourceInheritsFrom('building', 'city', TRUE)); -Assert::true($acl->resourceInheritsFrom('room', 'building', TRUE)); +Assert::true($acl->resourceInheritsFrom('building', 'city', true)); +Assert::true($acl->resourceInheritsFrom('room', 'building', true)); Assert::true($acl->resourceInheritsFrom('room', 'city')); -Assert::false($acl->resourceInheritsFrom('room', 'city', TRUE)); +Assert::false($acl->resourceInheritsFrom('room', 'city', true)); Assert::false($acl->resourceInheritsFrom('city', 'building')); Assert::false($acl->resourceInheritsFrom('building', 'room')); Assert::false($acl->resourceInheritsFrom('city', 'room')); diff --git a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt index 9fb9c538..06feca15 100644 --- a/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt +++ b/tests/Security/Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt @@ -19,5 +19,5 @@ $acl->addRole('guest'); $acl->addRole('staff', 'guest'); $acl->deny(); $acl->allow('staff'); -$acl->deny('staff', NULL, ['privilege1', 'privilege2']); -Assert::false($acl->isAllowed('staff', NULL, 'privilege1')); +$acl->deny('staff', null, ['privilege1', 'privilege2']); +Assert::false($acl->isAllowed('staff', null, 'privilege1')); diff --git a/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt b/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt index 9c38580a..c472fac3 100644 --- a/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt +++ b/tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt @@ -16,6 +16,6 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); +Assert::true($acl->isAllowed('guest', null, 'somePrivilege')); $acl->deny('guest'); -Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); +Assert::false($acl->isAllowed('guest', null, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeAllow.phpt b/tests/Security/Permission.RolePrivilegeAllow.phpt index 54fc8eac..38935f7c 100644 --- a/tests/Security/Permission.RolePrivilegeAllow.phpt +++ b/tests/Security/Permission.RolePrivilegeAllow.phpt @@ -15,5 +15,5 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); -$acl->allow('guest', NULL, 'somePrivilege'); -Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); +$acl->allow('guest', null, 'somePrivilege'); +Assert::true($acl->isAllowed('guest', null, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeAssert.phpt b/tests/Security/Permission.RolePrivilegeAssert.phpt index 58289694..6855771f 100644 --- a/tests/Security/Permission.RolePrivilegeAssert.phpt +++ b/tests/Security/Permission.RolePrivilegeAssert.phpt @@ -15,19 +15,19 @@ require __DIR__ . '/../bootstrap.php'; function falseAssertion() { - return FALSE; + return false; } function trueAssertion() { - return TRUE; + return true; } $acl = new Permission; $acl->addRole('guest'); -$acl->allow('guest', NULL, 'somePrivilege', 'trueAssertion'); -Assert::true($acl->isAllowed('guest', NULL, 'somePrivilege')); -$acl->allow('guest', NULL, 'somePrivilege', 'falseAssertion'); -Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); +$acl->allow('guest', null, 'somePrivilege', 'trueAssertion'); +Assert::true($acl->isAllowed('guest', null, 'somePrivilege')); +$acl->allow('guest', null, 'somePrivilege', 'falseAssertion'); +Assert::false($acl->isAllowed('guest', null, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivilegeDeny.phpt b/tests/Security/Permission.RolePrivilegeDeny.phpt index 4d617121..860c8b3b 100644 --- a/tests/Security/Permission.RolePrivilegeDeny.phpt +++ b/tests/Security/Permission.RolePrivilegeDeny.phpt @@ -16,5 +16,5 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); $acl->allow('guest'); -$acl->deny('guest', NULL, 'somePrivilege'); -Assert::false($acl->isAllowed('guest', NULL, 'somePrivilege')); +$acl->deny('guest', null, 'somePrivilege'); +Assert::false($acl->isAllowed('guest', null, 'somePrivilege')); diff --git a/tests/Security/Permission.RolePrivileges.phpt b/tests/Security/Permission.RolePrivileges.phpt index e5b663d1..7084e728 100644 --- a/tests/Security/Permission.RolePrivileges.phpt +++ b/tests/Security/Permission.RolePrivileges.phpt @@ -15,13 +15,13 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addRole('guest'); -$acl->allow('guest', NULL, ['p1', 'p2', 'p3']); -Assert::true($acl->isAllowed('guest', NULL, 'p1')); -Assert::true($acl->isAllowed('guest', NULL, 'p2')); -Assert::true($acl->isAllowed('guest', NULL, 'p3')); -Assert::false($acl->isAllowed('guest', NULL, 'p4')); -$acl->deny('guest', NULL, 'p1'); -Assert::false($acl->isAllowed('guest', NULL, 'p1')); -$acl->deny('guest', NULL, ['p2', 'p3']); -Assert::false($acl->isAllowed('guest', NULL, 'p2')); -Assert::false($acl->isAllowed('guest', NULL, 'p3')); +$acl->allow('guest', null, ['p1', 'p2', 'p3']); +Assert::true($acl->isAllowed('guest', null, 'p1')); +Assert::true($acl->isAllowed('guest', null, 'p2')); +Assert::true($acl->isAllowed('guest', null, 'p3')); +Assert::false($acl->isAllowed('guest', null, 'p4')); +$acl->deny('guest', null, 'p1'); +Assert::false($acl->isAllowed('guest', null, 'p1')); +$acl->deny('guest', null, ['p2', 'p3']); +Assert::false($acl->isAllowed('guest', null, 'p2')); +Assert::false($acl->isAllowed('guest', null, 'p3')); diff --git a/tests/Security/Permission.RoleRegistryInherits.phpt b/tests/Security/Permission.RoleRegistryInherits.phpt index 234b035a..aaa19373 100644 --- a/tests/Security/Permission.RoleRegistryInherits.phpt +++ b/tests/Security/Permission.RoleRegistryInherits.phpt @@ -23,10 +23,10 @@ Assert::same(['guest'], $acl->getRoleParents('member')); Assert::same(['member'], $acl->getRoleParents('editor')); -Assert::true($acl->roleInheritsFrom('member', 'guest', TRUE)); -Assert::true($acl->roleInheritsFrom('editor', 'member', TRUE)); +Assert::true($acl->roleInheritsFrom('member', 'guest', true)); +Assert::true($acl->roleInheritsFrom('editor', 'member', true)); Assert::true($acl->roleInheritsFrom('editor', 'guest')); -Assert::false($acl->roleInheritsFrom('editor', 'guest', TRUE)); +Assert::false($acl->roleInheritsFrom('editor', 'guest', true)); Assert::false($acl->roleInheritsFrom('guest', 'member')); Assert::false($acl->roleInheritsFrom('member', 'editor')); Assert::false($acl->roleInheritsFrom('guest', 'editor')); diff --git a/tests/Security/Permission.RulesRemove.phpt b/tests/Security/Permission.RulesRemove.phpt index 011243ab..bdbb0347 100644 --- a/tests/Security/Permission.RulesRemove.phpt +++ b/tests/Security/Permission.RulesRemove.phpt @@ -14,10 +14,10 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; -$acl->allow(NULL, NULL, ['privilege1', 'privilege2']); +$acl->allow(null, null, ['privilege1', 'privilege2']); Assert::false($acl->isAllowed()); -Assert::true($acl->isAllowed(NULL, NULL, 'privilege1')); -Assert::true($acl->isAllowed(NULL, NULL, 'privilege2')); -$acl->removeAllow(NULL, NULL, 'privilege1'); -Assert::false($acl->isAllowed(NULL, NULL, 'privilege1')); -Assert::true($acl->isAllowed(NULL, NULL, 'privilege2')); +Assert::true($acl->isAllowed(null, null, 'privilege1')); +Assert::true($acl->isAllowed(null, null, 'privilege2')); +$acl->removeAllow(null, null, 'privilege1'); +Assert::false($acl->isAllowed(null, null, 'privilege1')); +Assert::true($acl->isAllowed(null, null, 'privilege2')); diff --git a/tests/Security/Permission.RulesResourceRemove.phpt b/tests/Security/Permission.RulesResourceRemove.phpt index 57048ddb..ee1b902e 100644 --- a/tests/Security/Permission.RulesResourceRemove.phpt +++ b/tests/Security/Permission.RulesResourceRemove.phpt @@ -15,12 +15,12 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); -$acl->allow(NULL, 'area'); -Assert::true($acl->isAllowed(NULL, 'area')); +$acl->allow(null, 'area'); +Assert::true($acl->isAllowed(null, 'area')); $acl->removeResource('area'); Assert::exception(function () use ($acl) { - $acl->isAllowed(NULL, 'area'); + $acl->isAllowed(null, 'area'); }, Nette\InvalidStateException::class, "Resource 'area' does not exist."); $acl->addResource('area'); -Assert::false($acl->isAllowed(NULL, 'area')); +Assert::false($acl->isAllowed(null, 'area')); diff --git a/tests/Security/Permission.RulesResourceRemoveAll.phpt b/tests/Security/Permission.RulesResourceRemoveAll.phpt index f25a3c4e..c346c096 100644 --- a/tests/Security/Permission.RulesResourceRemoveAll.phpt +++ b/tests/Security/Permission.RulesResourceRemoveAll.phpt @@ -15,12 +15,12 @@ require __DIR__ . '/../bootstrap.php'; $acl = new Permission; $acl->addResource('area'); -$acl->allow(NULL, 'area'); -Assert::true($acl->isAllowed(NULL, 'area')); +$acl->allow(null, 'area'); +Assert::true($acl->isAllowed(null, 'area')); $acl->removeAllResources(); Assert::exception(function () use ($acl) { - $acl->isAllowed(NULL, 'area'); + $acl->isAllowed(null, 'area'); }, Nette\InvalidStateException::class, "Resource 'area' does not exist."); $acl->addResource('area'); -Assert::false($acl->isAllowed(NULL, 'area')); +Assert::false($acl->isAllowed(null, 'area')); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index bf3cb3a5..913601e5 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -85,7 +85,7 @@ Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); Assert::same('John Doe', $user->getId()); // login as john#3 -$user->logout(TRUE); +$user->logout(true); Assert::same(1, $counter->logout); $user->login(new Identity('John Doe', 'admin')); Assert::same(2, $counter->login); @@ -95,7 +95,7 @@ Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); // log out // logging out... -$user->logout(FALSE); +$user->logout(false); Assert::same(2, $counter->logout); Assert::false($user->isLoggedIn()); @@ -103,7 +103,7 @@ Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); // logging out and clearing identity... -$user->logout(TRUE); +$user->logout(true); Assert::same(2, $counter->logout); // not logged in -> logout event not triggered Assert::false($user->isLoggedIn()); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 3f0dffa2..c3095d00 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -43,7 +43,7 @@ class Authorizator implements IAuthorizator { function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { - return $role === 'admin' && strpos($resource, 'jany') === FALSE; + return $role === 'admin' && strpos($resource, 'jany') === false; } } @@ -86,6 +86,6 @@ Assert::false($user->isAllowed('sleep_with_jany')); // log out // logging out... -$user->logout(FALSE); +$user->logout(false); Assert::false($user->isAllowed('delete_file')); From b2d1f447611ff9516076ec83864a758d8214aa01 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 11 Jul 2017 11:41:17 +0200 Subject: [PATCH 077/254] coding style: fixes in code --- src/Security/Permission.php | 25 ++++++++++--------- tests/Security/MockUserStorage.php | 12 ++++----- ...AfterItWasAllowedAccessToAllResources.phpt | 2 +- tests/Security/User.authentication.phpt | 4 +-- tests/Security/User.authorization.phpt | 6 ++--- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index db99ae27..e6d4c493 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -45,7 +45,8 @@ class Permission implements IAuthorizator ]; /** @var mixed */ - private $queriedRole, $queriedResource; + private $queriedRole; + private $queriedResource; /********************* roles ****************d*g**/ @@ -582,26 +583,26 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = do { // depth-first search on $role if it is not 'allRoles' pseudo-parent - if ($role !== null && null !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) { + if ($role !== null && ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege)) !== null) { break; } if ($privilege === self::ALL) { if ($rules = $this->getRules($resource, self::ALL)) { // look for rule on 'allRoles' psuedo-parent foreach ($rules['byPrivilege'] as $privilege => $rule) { - if (self::DENY === ($result = $this->getRuleType($resource, null, $privilege))) { + if (($result = $this->getRuleType($resource, null, $privilege)) === self::DENY) { break 2; } } - if (null !== ($result = $this->getRuleType($resource, null, null))) { + if (($result = $this->getRuleType($resource, null, null)) !== null) { break; } } } else { - if (null !== ($result = $this->getRuleType($resource, null, $privilege))) { // look for rule on 'allRoles' pseudo-parent + if (($result = $this->getRuleType($resource, null, $privilege)) !== null) { // look for rule on 'allRoles' pseudo-parent break; - } elseif (null !== ($result = $this->getRuleType($resource, null, null))) { + } elseif (($result = $this->getRuleType($resource, null, null)) !== null) { break; } } @@ -650,26 +651,26 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) 'stack' => [$role], ]; - while (null !== ($role = array_pop($dfs['stack']))) { + while (($role = array_pop($dfs['stack'])) !== null) { if (isset($dfs['visited'][$role])) { continue; } if ($all) { if ($rules = $this->getRules($resource, $role)) { foreach ($rules['byPrivilege'] as $privilege2 => $rule) { - if (self::DENY === $this->getRuleType($resource, $role, $privilege2)) { + if ($this->getRuleType($resource, $role, $privilege2) === self::DENY) { return self::DENY; } } - if (null !== ($type = $this->getRuleType($resource, $role, null))) { + if (($type = $this->getRuleType($resource, $role, null)) !== null) { return $type; } } } else { - if (null !== ($type = $this->getRuleType($resource, $role, $privilege))) { + if (($type = $this->getRuleType($resource, $role, $privilege)) !== null) { return $type; - } elseif (null !== ($type = $this->getRuleType($resource, $role, null))) { + } elseif (($type = $this->getRuleType($resource, $role, null)) !== null) { return $type; } } @@ -715,7 +716,7 @@ private function getRuleType($resource, $role, $privilege): ?bool } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) { return null; - } elseif (self::ALLOW === $rule['type']) { + } elseif ($rule['type'] === self::ALLOW) { return self::DENY; } else { diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 4152cde6..244669c5 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -7,36 +7,36 @@ class MockUserStorage implements Nette\Security\IUserStorage private $identity; - function setAuthenticated(bool $state) + public function setAuthenticated(bool $state) { $this->auth = $state; } - function isAuthenticated(): bool + public function isAuthenticated(): bool { return $this->auth; } - function setIdentity(Nette\Security\IIdentity $identity = null) + public function setIdentity(Nette\Security\IIdentity $identity = null) { $this->identity = $identity; } - function getIdentity(): ?Nette\Security\IIdentity + public function getIdentity(): ?Nette\Security\IIdentity { return $this->identity; } - function setExpiration($time, int $flags = 0) + public function setExpiration($time, int $flags = 0) { } - function getLogoutReason(): ?int + public function getLogoutReason(): ?int { } } diff --git a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt index a4cda115..3f337d96 100644 --- a/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt +++ b/tests/Security/Permission.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt @@ -20,7 +20,7 @@ $acl->addRole('test1'); $acl->addRole('test2'); $acl->addResource('Test'); -$acl->allow(null,'Test','xxx'); +$acl->allow(null, 'Test', 'xxx'); // error test $acl->removeRole('test0'); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 913601e5..7bbb31b7 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -21,9 +21,9 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials): Nette\Security\IIdentity + public function authenticate(array $credentials): Nette\Security\IIdentity { - list($username, $password) = $credentials; + [$username, $password] = $credentials; if ($username !== 'john') { throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index c3095d00..ec819d7b 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -23,9 +23,9 @@ ob_start(); class Authenticator implements IAuthenticator { - function authenticate(array $credentials): Nette\Security\IIdentity + public function authenticate(array $credentials): Nette\Security\IIdentity { - list($username, $password) = $credentials; + [$username, $password] = $credentials; if ($username !== 'john') { throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); @@ -41,7 +41,7 @@ class Authenticator implements IAuthenticator class Authorizator implements IAuthorizator { - function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool + public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool { return $role === 'admin' && strpos($resource, 'jany') === false; } From 3500e5b65d98ff9751bd7db71656b6607286fabb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 12 Jul 2017 16:14:14 +0200 Subject: [PATCH 078/254] UserPanel: added declare(strict_types=1) --- src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml | 1 + src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml index e410c959..18e3c954 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml @@ -1,4 +1,5 @@ Date: Thu, 13 Jul 2017 18:55:26 +0200 Subject: [PATCH 079/254] travis: tested using Nette Coding Standard --- .travis.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b196df6b..ea9decd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ matrix: env: dependencies="--prefer-lowest --prefer-stable" - php: 7.1 env: coverage=on + - php: 7.1 + env: codingStandard=on allow_failures: - php: 7.1 @@ -16,25 +18,34 @@ matrix: script: - vendor/bin/tester tests -s $coverageArgs - - php temp/code-checker/src/code-checker.php --short-arrays --strict-types + - > + if [ "$codingStandard" ]; then + php temp/code-checker/src/code-checker.php --short-arrays --strict-types + && php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.neon; + fi after_failure: # Print *.actual content - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done before_script: - # Install Nette Tester & Code Checker + # Install Nette Tester - travis_retry composer update --no-interaction --prefer-dist $dependencies - - travis_retry composer create-project nette/code-checker temp/code-checker ~2.8 --no-interaction - - if [ "$coverage" == "on" ]; then coverageArgs="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi + # Install Code Checkers + - > + if [ "$codingStandard" ]; then + travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-interaction; + travis_retry composer create-project nette/coding-standard temp/coding-standard --no-interaction; + fi + - if [ "$coverage" ]; then coverageArgs="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi after_script: # Report Code Coverage - > - if [ "$coverage" == "on" ]; then + if [ "$coverage" ]; then wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - && php coveralls.phar --verbose --config tests/.coveralls.yml - || true; fi + && php coveralls.phar --verbose --config tests/.coveralls.yml; + fi sudo: false From fdb8f2a8f837f4d3636306160a979f746800b540 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 23 Jul 2017 19:35:19 +0200 Subject: [PATCH 080/254] travis: added PHP 7.2 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ea9decd9..a58cbf75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php php: - 7.1 + - 7.2 env: matrix: From 6e61eaaaa03e9058459b9f47a392cdee0681374d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Jul 2017 13:37:45 +0200 Subject: [PATCH 081/254] travis: use stages --- .travis.yml | 76 ++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index a58cbf75..074f4534 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,53 +3,57 @@ php: - 7.1 - 7.2 -env: -matrix: - include: - - php: 7.1 - env: dependencies="--prefer-lowest --prefer-stable" - - php: 7.1 - env: coverage=on - - php: 7.1 - env: codingStandard=on +before_install: + # turn off XDebug + - phpenv config-rm xdebug.ini || return 0 - allow_failures: - - php: 7.1 - env: coverage=on +install: + - travis_retry composer install --no-progress --prefer-dist script: - - vendor/bin/tester tests -s $coverageArgs - - > - if [ "$codingStandard" ]; then - php temp/code-checker/src/code-checker.php --short-arrays --strict-types - && php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.neon; - fi + - vendor/bin/tester tests -s after_failure: # Print *.actual content - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done -before_script: - # Install Nette Tester - - travis_retry composer update --no-interaction --prefer-dist $dependencies - # Install Code Checkers - - > - if [ "$codingStandard" ]; then - travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-interaction; - travis_retry composer create-project nette/coding-standard temp/coding-standard --no-interaction; - fi - - if [ "$coverage" ]; then coverageArgs="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi - -after_script: - # Report Code Coverage - - > - if [ "$coverage" ]; then - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - && php coveralls.phar --verbose --config tests/.coveralls.yml; - fi +jobs: + include: + - env: title="Lowest Dependencies" + install: + - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable + + + - stage: Code Standard Checker + php: 7.1 + install: + # Install Nette Code Checker + - travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-progress + # Install Nette Coding Standard + - travis_retry composer create-project nette/coding-standard temp/coding-standard --no-progress + + script: + - php temp/code-checker/src/code-checker.php --short-arrays --strict-types + - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.neon + + + - stage: Code Coverage + script: + - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src + after_script: + - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar + - php coveralls.phar --verbose --config tests/.coveralls.yml + + + allow_failures: + - stage: Code Coverage + sudo: false cache: directories: - $HOME/.composer/cache + +notifications: + email: false From f3824d56a72ce07d8e227c0c6039b0134c946aa8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 24 Aug 2017 23:05:37 +0200 Subject: [PATCH 082/254] SecurityExtension: used setFactory() instead of misused setClass() --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index b401c373..7fcf594e 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -44,7 +44,7 @@ public function loadConfiguration() ->setFactory(Nette\Http\UserStorage::class); $user = $builder->addDefinition($this->prefix('user')) - ->setClass(Nette\Security\User::class); + ->setFactory(Nette\Security\User::class); if ($this->debugMode && $config['debugger']) { $user->addSetup('@Tracy\Bar::addPanel', [ From dd2c14440e2b7c387120c2aa4d2c2ddc8ce6177c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 8 Sep 2017 02:18:32 +0200 Subject: [PATCH 083/254] Permission: used native callback invocation --- src/Security/Permission.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index e6d4c493..1c121415 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -710,7 +710,7 @@ private function getRuleType($resource, $role, $privilege): ?bool $rule = $rules['byPrivilege'][$privilege]; } - if ($rule['assert'] === null || Nette\Utils\Callback::invoke($rule['assert'], $this, $role, $resource, $privilege)) { + if ($rule['assert'] === null || $rule['assert']($this, $role, $resource, $privilege)) { return $rule['type']; } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) { From b7c6f385ddce2bfe4e21f48a5a9ea950a4b574e1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 26 Sep 2017 13:20:37 +0200 Subject: [PATCH 084/254] coding style --- src/Security/Permission.php | 1 + tests/Security/MockUserStorage.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 1c121415..e25a50a4 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -46,6 +46,7 @@ class Permission implements IAuthorizator /** @var mixed */ private $queriedRole; + private $queriedResource; diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 244669c5..dcd9039a 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -4,6 +4,7 @@ class MockUserStorage implements Nette\Security\IUserStorage { private $auth = false; + private $identity; From 18d47c6c6fd30a3b6702bf0fa4e16a399a4b7f92 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 26 Sep 2017 15:44:43 +0200 Subject: [PATCH 085/254] readme: updated --- readme.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e369b8e0..08166357 100644 --- a/readme.md +++ b/readme.md @@ -7,19 +7,32 @@ Nette Security: Access Control [![Latest Stable Version](https://poser.pugx.org/nette/security/v/stable)](https://github.com/nette/security/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/security/blob/master/license.md) + +Introduction +============ + +Authentication & Authorization library for Nette. + - user login and logout - verifying user privileges - securing against vulnerabilities - how to create custom authenticators and authorizators - Access Control List -Install it using Composer: +Documentation can be found on the [website](https://doc.nette.org/access-control). + + +Installation +============ + +The recommended way to install is via Composer: ``` composer require nette/security ``` -The last stable release requires PHP version 5.6 or newer (is compatible with PHP 7.0 and 7.1). The dev-master version requires PHP 7.1. +It requires PHP version 5.6 and supports PHP up to 7.2. The dev-master version requires PHP 7.1. + Authentication ============== From 1d69f8245003fcfec2061d235c5a48b827ba0802 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Mar 2018 23:03:00 +0100 Subject: [PATCH 086/254] compatibility with latest nette/http --- composer.json | 2 +- src/Security/IUserStorage.php | 6 ++---- tests/Security/MockUserStorage.php | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 254335ac..6efb5f48 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "nette/di": "^2.4 || ^3.0", - "nette/http": "^3.0", + "nette/http": "^3.0.0-beta", "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 6afe5e68..4923abbc 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -46,12 +46,10 @@ function setIdentity(?IIdentity $identity); function getIdentity(): ?IIdentity; /** - * Enables log out from the persistent storage after inactivity. - * @param string|null like '20 minutes' - * @param int flag IUserStorage::CLEAR_IDENTITY + * Enables log out from the persistent storage after inactivity (like '20 minutes'). Accepts flag IUserStorage::CLEAR_IDENTITY. * @return static */ - function setExpiration($time, int $flags = 0); + function setExpiration(?string $expire, int $flags = 0); /** * Why was user logged out? diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index dcd9039a..b81088ee 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -32,7 +32,7 @@ public function getIdentity(): ?Nette\Security\IIdentity } - public function setExpiration($time, int $flags = 0) + public function setExpiration(?string $time, int $flags = 0) { } From 5c4065cbdc48f23c7494894c7bac02313ae59981 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Mar 2018 23:04:33 +0100 Subject: [PATCH 087/254] User: type fixes in API --- src/Security/User.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 09ed9fbc..5f593f8c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -47,10 +47,10 @@ class User /** @var IUserStorage Session storage for current user */ private $storage; - /** @var IAuthenticator */ + /** @var IAuthenticator|null */ private $authenticator; - /** @var IAuthorizator */ + /** @var IAuthorizator|null */ private $authorizator; @@ -73,17 +73,16 @@ final public function getStorage(): IUserStorage /** * Conducts the authentication process. Parameters are optional. - * @param mixed optional parameter (e.g. username or IIdentity) - * @param mixed optional parameter (e.g. password) + * @param string|IIdentity username or Identity * @throws AuthenticationException if authentication was not successful */ - public function login($id = null, $password = null): void + public function login($user, string $password = null): void { $this->logout(true); - if (!$id instanceof IIdentity) { - $id = $this->getAuthenticator()->authenticate(func_get_args()); + if (!$user instanceof IIdentity) { + $user = $this->getAuthenticator()->authenticate(func_get_args()); } - $this->storage->setIdentity($id); + $this->storage->setIdentity($user); $this->storage->setAuthenticated(true); $this->onLoggedIn($this); } @@ -91,7 +90,6 @@ public function login($id = null, $password = null): void /** * Logs out the user from the current session. - * @param bool clear the identity from persistent storage? */ final public function logout(bool $clearIdentity = false): void { @@ -158,14 +156,17 @@ final public function getAuthenticator(bool $throw = true): ?IAuthenticator /** - * Enables log out after inactivity. - * @param string|null like '20 minutes' - * @param int flag IUserStorage::CLEAR_IDENTITY + * Enables log out after inactivity (like '20 minutes'). Accepts flag IUserStorage::CLEAR_IDENTITY. + * @param string|null + * @param int * @return static */ - public function setExpiration($time, $flags = 0) + public function setExpiration($expire, /*int*/$flags = 0) { $clearIdentity = $flags === IUserStorage::CLEAR_IDENTITY; + if ($expire !== null && !is_string($expire)) { + trigger_error("Expiration should be a string like '20 minutes' etc.", E_USER_DEPRECATED); + } if (is_bool($flags)) { trigger_error(__METHOD__ . '() second parameter $whenBrowserIsClosed was removed.', E_USER_DEPRECATED); } @@ -173,7 +174,7 @@ public function setExpiration($time, $flags = 0) $clearIdentity = $clearIdentity || func_get_arg(2); trigger_error(__METHOD__ . '() third parameter is deprecated, use flag setExpiration($time, IUserStorage::CLEAR_IDENTITY)', E_USER_DEPRECATED); } - $this->storage->setExpiration($time, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); + $this->storage->setExpiration($expire, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); return $this; } From edf349e3f7752b17fafc47dd91e9cfd08adb53b5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Mar 2018 20:20:27 +0100 Subject: [PATCH 088/254] phpDoc: added $var name to @param --- src/Security/IAuthorizator.php | 6 ++-- src/Security/Permission.php | 50 +++++++++++++--------------- src/Security/SimpleAuthenticator.php | 4 +-- src/Security/User.php | 6 ++-- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/Security/IAuthorizator.php b/src/Security/IAuthorizator.php index 003ab155..2d12705c 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/IAuthorizator.php @@ -27,9 +27,9 @@ interface IAuthorizator /** * Performs a role-based authorization. - * @param string|null - * @param string|null - * @param string|null + * @param string|null $role + * @param string|null $resource + * @param string|null $privilege */ function isAllowed($role, $resource, $privilege): bool; } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index e25a50a4..8388e726 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -384,9 +384,9 @@ public function removeAllResources() * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. * - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL + * @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) @@ -400,9 +400,9 @@ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. * - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL + * @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) @@ -415,9 +415,9 @@ public function deny($roles = self::ALL, $resources = self::ALL, $privileges = s /** * Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges. * - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL + * @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) @@ -430,9 +430,9 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile /** * Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges. * - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL + * @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) @@ -444,11 +444,9 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg /** * Performs operations on Access Control List rules. - * @param bool operation add? - * @param bool type - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL - * @param string|string[]|Permission::ALL + * @param string|string[]|null $roles + * @param string|string[]|null $resources + * @param string|string[]|null $privileges * @throws Nette\InvalidStateException * @return static */ @@ -559,9 +557,9 @@ 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|Permission::ALL|IRole - * @param string|Permission::ALL|IResource - * @param string|Permission::ALL + * @param string|null|IRole $role + * @param string|null|IResource $resource + * @param string|null $privilege * @throws Nette\InvalidStateException */ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool @@ -642,7 +640,7 @@ public function getQueriedResource() /** * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule * allowing/denying $role access to a/all $privilege upon $resource. - * @param bool all (true) or one? + * @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) @@ -687,9 +685,9 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) /** * Returns the rule type associated with the specified Resource, Role, and privilege. - * @param string|Permission::ALL - * @param string|Permission::ALL - * @param string|Permission::ALL + * @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 @@ -729,8 +727,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|Permission::ALL - * @param string|Permission::ALL + * @param string|null $resource + * @param string|null $role */ private function &getRules($resource, $role, bool $create = false): ?array { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index c2915494..b2f97325 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -27,8 +27,8 @@ class SimpleAuthenticator implements IAuthenticator /** - * @param array list of pairs username => password - * @param array list of pairs username => role[] + * @param array $userlist list of pairs username => password + * @param array $usersRoles list of pairs username => role[] */ public function __construct(array $userlist, array $usersRoles = []) { diff --git a/src/Security/User.php b/src/Security/User.php index 5f593f8c..769678e7 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -73,7 +73,7 @@ final public function getStorage(): IUserStorage /** * Conducts the authentication process. Parameters are optional. - * @param string|IIdentity username or Identity + * @param string|IIdentity $user name or Identity * @throws AuthenticationException if authentication was not successful */ public function login($user, string $password = null): void @@ -157,8 +157,8 @@ final public function getAuthenticator(bool $throw = true): ?IAuthenticator /** * Enables log out after inactivity (like '20 minutes'). Accepts flag IUserStorage::CLEAR_IDENTITY. - * @param string|null - * @param int + * @param string|null $expire + * @param int $flags * @return static */ public function setExpiration($expire, /*int*/$flags = 0) From 45fe6cecc781db439119d0d632f5da194139658b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 10 May 2018 00:54:25 +0200 Subject: [PATCH 089/254] Update Github templates Thanks to Babel for inspiration <3 --- .github/ISSUE_TEMPLATE/Bug_report.md | 19 +++++++++++++++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 9 +++++++++ .github/ISSUE_TEMPLATE/Support_question.md | 12 ++++++++++++ .github/ISSUE_TEMPLATE/Support_us.md | 21 +++++++++++++++++++++ .github/issue_template.md | 16 ---------------- .github/pull_request_template.md | 3 +-- 6 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/Support_question.md create mode 100644 .github/ISSUE_TEMPLATE/Support_us.md delete mode 100644 .github/issue_template.md diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 00000000..a4cd1263 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,19 @@ +--- +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 new file mode 100644 index 00000000..d2e21948 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100644 index 00000000..75c48b6e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Support_question.md @@ -0,0 +1,12 @@ +--- +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 new file mode 100644 index 00000000..92d8a4c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Support_us.md @@ -0,0 +1,21 @@ +--- +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/issue_template.md b/.github/issue_template.md deleted file mode 100644 index a6a22a9b..00000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,16 +0,0 @@ -- bug report? yes/no -- feature request? yes/no -- version: ?.?.? - -### Description -... - -### Steps To Reproduce -... If possible a minimal demo of the problem ... - - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8bcf026e..f8aa3f40 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,4 @@ -- bug fix? yes/no -- new feature? yes/no +- bug fix / new feature? - BC break? yes/no - doc PR: nette/docs#??? From 5c237bfef4a47e44102e8cf94520ca7887bea225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Kapi=C4=8Dka?= Date: Sun, 13 May 2018 18:22:28 +0200 Subject: [PATCH 090/254] Remove Nette\Object from Readme (#30) --- readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 08166357..4a792d1f 100644 --- a/readme.md +++ b/readme.md @@ -121,7 +121,7 @@ We will create a custom authenticator that will check validity of login credenti ```php use Nette\Security as NS; -class MyAuthenticator extends Nette\Object implements NS\IAuthenticator +class MyAuthenticator implements NS\IAuthenticator { public $database; @@ -225,8 +225,7 @@ Authorizator decides, whether the user has permission to take some action. It's An implementation skeleton looks like this: ```php -class MyAuthorizator extends Nette\Object - implements Nette\Security\IAuthorizator +class MyAuthorizator implements Nette\Security\IAuthorizator { function isAllowed($role, $resource, $privilege) From 8647df3e15c3948ae3a9463c6b7fb972466cd1c0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 22 Jun 2018 11:34:07 +0200 Subject: [PATCH 091/254] readme.md: added donation request --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 4a792d1f..91005406 100644 --- a/readme.md +++ b/readme.md @@ -21,6 +21,8 @@ Authentication & Authorization library for Nette. Documentation can be found on the [website](https://doc.nette.org/access-control). +If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you! + Installation ============ From e0da01080872b8493045e78535ff55546e4f02db Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Jul 2018 17:52:29 +0200 Subject: [PATCH 092/254] Passwords: changed from static to object class (BC break) it triggers E_DEPRECATED automatically (method should not be called statically) --- src/Security/Passwords.php | 46 ++++++++++++++------- tests/Security/Passwords.hash().phpt | 18 +++----- tests/Security/Passwords.needsRehash().phpt | 4 +- tests/Security/Passwords.static.phpt | 30 ++++++++++++++ tests/Security/Passwords.verify().phpt | 6 +-- 5 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 tests/Security/Passwords.static.phpt diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index ac11c43b..19e6702f 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -13,24 +13,40 @@ /** - * Passwords tools. + * Password Hashing. */ class Passwords { - use Nette\StaticClass; + use Nette\SmartObject; + + /** @var int */ + private $algo; + + /** @var array */ + private $options; + /** - * Computes salted password hash. Accepts option 'cost' (4-31) + * See http://php.net/manual/en/password.constants.php */ - public static function hash(string $password, array $options = []): string + public function __construct(int $algo = PASSWORD_BCRYPT, array $options = []) { - if (isset($options['cost']) && ($options['cost'] < 4 || $options['cost'] > 31)) { - throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $options[cost] given."); - } + $this->algo = $algo; + $this->options = $options; + } + + + /** + * Computes salted password hash. + */ + public function hash(string $password): string + { + $hash = isset($this) + ? @password_hash($password, $this->algo, $this->options) // @ is escalated to exception + : @password_hash($password, PASSWORD_BCRYPT, func_get_args()[1] ?? []); // back compatibility with v2.x - $hash = password_hash($password, PASSWORD_BCRYPT, $options); - if ($hash === false || strlen($hash) < 60) { - throw new Nette\InvalidStateException('Hash computed by password_hash is invalid.'); + if (!$hash) { + throw new Nette\InvalidStateException('Computed hash is invalid. ' . error_get_last()['message']); } return $hash; } @@ -39,17 +55,19 @@ public static function hash(string $password, array $options = []): string /** * Verifies that a password matches a hash. */ - public static function verify(string $password, string $hash): bool + public function verify(string $password, string $hash): bool { return password_verify($password, $hash); } /** - * Checks if the given hash matches the options. Accepts option 'cost' (4-31) + * Checks if the given hash matches the options. */ - public static function needsRehash(string $hash, array $options = []): bool + public function needsRehash(string $hash): bool { - return password_needs_rehash($hash, PASSWORD_BCRYPT, $options); + return isset($this) + ? password_needs_rehash($hash, $this->algo, $this->options) + : password_needs_rehash($hash, PASSWORD_BCRYPT, func_get_args()[1] ?? []); // back compatibility with v2.x } } diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index d4fdc11f..6d6f7324 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -14,24 +14,16 @@ require __DIR__ . '/../bootstrap.php'; Assert::truthy( - preg_match('#^\$2.\$\d\d\$.{53}\z#', - Passwords::hash('')) + preg_match('#^\$2.\$\d\d\$.{53}\z#', (new Passwords)->hash('')) ); Assert::truthy( - preg_match('#^\$2y\$05\$.{53}\z#', - $h = Passwords::hash('dg', ['cost' => 5])) + preg_match('#^\$2y\$05\$.{53}\z#', (new Passwords(PASSWORD_BCRYPT, ['cost' => 5]))->hash('dg')) ); -echo $h; -$hash = Passwords::hash('dg'); +$hash = (new Passwords(PASSWORD_BCRYPT))->hash('dg'); Assert::same($hash, crypt('dg', $hash)); - -Assert::exception(function () { - Passwords::hash('dg', ['cost' => 3]); -}, Nette\InvalidArgumentException::class, 'Cost must be in range 4-31, 3 given.'); - Assert::exception(function () { - Passwords::hash('dg', ['cost' => 32]); -}, Nette\InvalidArgumentException::class, 'Cost must be in range 4-31, 32 given.'); + (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'); +}, Nette\InvalidStateException::class, 'Computed hash is invalid. password_hash(): Invalid bcrypt cost parameter specified: 3'); diff --git a/tests/Security/Passwords.needsRehash().phpt b/tests/Security/Passwords.needsRehash().phpt index 973f1dd6..a912352c 100644 --- a/tests/Security/Passwords.needsRehash().phpt +++ b/tests/Security/Passwords.needsRehash().phpt @@ -13,5 +13,5 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::true(Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::false(Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK', ['cost' => 5])); +Assert::true((new Passwords(PASSWORD_BCRYPT))->needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +Assert::false((new Passwords(PASSWORD_BCRYPT, ['cost' => 5]))->needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); diff --git a/tests/Security/Passwords.static.phpt b/tests/Security/Passwords.static.phpt new file mode 100644 index 00000000..4183e0c0 --- /dev/null +++ b/tests/Security/Passwords.static.phpt @@ -0,0 +1,30 @@ + 5])) // @ is not static +); + + +Assert::true(@Passwords::verify('dg', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static +Assert::false(@Passwords::verify('dgx', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static + + +Assert::true(@Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static diff --git a/tests/Security/Passwords.verify().phpt b/tests/Security/Passwords.verify().phpt index 72eccfcf..8b6d0d8c 100644 --- a/tests/Security/Passwords.verify().phpt +++ b/tests/Security/Passwords.verify().phpt @@ -13,6 +13,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::true(Passwords::verify('dg', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::true(Passwords::verify('dg', '$2x$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::false(Passwords::verify('dgx', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +Assert::true((new Passwords)->verify('dg', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +Assert::true((new Passwords)->verify('dg', '$2x$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +Assert::false((new Passwords)->verify('dgx', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); From cc1972e54c96720e25e3a5dd7178298ec91d1c54 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 16 Jul 2018 11:43:33 +0200 Subject: [PATCH 093/254] SecurityExtension: added service 'passwords' --- src/Bridges/SecurityDI/SecurityExtension.php | 3 +++ .../SecurityExtension.passwords.phpt | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/Security.DI/SecurityExtension.passwords.phpt diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 7fcf594e..2ec9b534 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -39,6 +39,9 @@ public function loadConfiguration() $config = $this->validateConfig($this->defaults); $builder = $this->getContainerBuilder(); + $builder->addDefinition($this->prefix('passwords')) + ->setFactory(Nette\Security\Passwords::class); + $builder->addDefinition($this->prefix('userStorage')) ->setClass(Nette\Security\IUserStorage::class) ->setFactory(Nette\Http\UserStorage::class); diff --git a/tests/Security.DI/SecurityExtension.passwords.phpt b/tests/Security.DI/SecurityExtension.passwords.phpt new file mode 100644 index 00000000..a7e6efd1 --- /dev/null +++ b/tests/Security.DI/SecurityExtension.passwords.phpt @@ -0,0 +1,27 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('bar', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +eval($compiler->compile()); +$container = new Container; + +Assert::type(Nette\Security\Passwords::class, $container->getService('security.passwords')); From 1a281ecba926d11463a915986b166e306addabb6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 28 Jul 2018 13:44:17 +0200 Subject: [PATCH 094/254] cs --- src/Security/User.php | 4 ++-- tests/Security/MockUserStorage.php | 1 + tests/bootstrap.php | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 769678e7..0013a67e 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -38,10 +38,10 @@ class User /** @var string default role for authenticated user without own identity */ public $authenticatedRole = 'authenticated'; - /** @var callable[] function (User $sender); Occurs when the user is successfully logged in */ + /** @var callable[] function (User $sender): void; Occurs when the user is successfully logged in */ public $onLoggedIn; - /** @var callable[] function (User $sender); Occurs when the user is logged out */ + /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ public $onLoggedOut; /** @var IUserStorage Session storage for current user */ diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index b81088ee..07f0a1e6 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -1,4 +1,5 @@ Date: Tue, 4 Sep 2018 17:35:31 +0200 Subject: [PATCH 095/254] Passwords: BCRYPT changed to default algorithm Warning: the result in a database column can expand beyond 60 characters! --- src/Security/Passwords.php | 2 +- tests/Security/Passwords.hash().phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 19e6702f..da71f4bc 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -29,7 +29,7 @@ class Passwords /** * See http://php.net/manual/en/password.constants.php */ - public function __construct(int $algo = PASSWORD_BCRYPT, array $options = []) + public function __construct(int $algo = PASSWORD_DEFAULT, array $options = []) { $this->algo = $algo; $this->options = $options; diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 6d6f7324..7bd529b0 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; Assert::truthy( - preg_match('#^\$2.\$\d\d\$.{53}\z#', (new Passwords)->hash('')) + preg_match('#^\$.{50,}\z#', (new Passwords)->hash('')) ); Assert::truthy( From 760864e10869173d6ec69e89d9a298b1655ca542 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 13 Sep 2018 01:40:32 +0200 Subject: [PATCH 096/254] travis: uses NCS 2 & CS 3 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 074f4534..9bf2bfb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,13 +28,13 @@ jobs: php: 7.1 install: # Install Nette Code Checker - - travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-progress + - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress # Install Nette Coding Standard - - travis_retry composer create-project nette/coding-standard temp/coding-standard --no-progress + - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress script: - - php temp/code-checker/src/code-checker.php --short-arrays --strict-types - - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.neon + - php temp/code-checker/code-checker --strict-types + - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml - stage: Code Coverage From e041dca865965fd3556ee58f40a39d03104b123f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 17 Sep 2018 17:57:51 +0200 Subject: [PATCH 097/254] User: added hasAuthenticator() & hasAuthorizator(), deprecated $throw in getAuthenticator() & getAuthorizator() --- src/Security/User.php | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 0013a67e..780a1cb8 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -146,15 +146,28 @@ public function setAuthenticator(IAuthenticator $handler) /** * Returns authentication handler. */ - final public function getAuthenticator(bool $throw = true): ?IAuthenticator + final public function getAuthenticator(): ?IAuthenticator { - if ($throw && !$this->authenticator) { + if (func_num_args()) { + trigger_error(__METHOD__ . '() parameter $throw is deprecated, use hasAuthenticator()', E_USER_DEPRECATED); + $throw = func_get_arg(0); + } + if (($throw ?? true) && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); } return $this->authenticator; } + /** + * Does the authentication exist? + */ + final public function hasAuthenticator(): bool + { + return (bool) $this->authenticator; + } + + /** * Enables log out after inactivity (like '20 minutes'). Accepts flag IUserStorage::CLEAR_IDENTITY. * @param string|null $expire @@ -244,11 +257,24 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. */ - final public function getAuthorizator(bool $throw = true): ?IAuthorizator + final public function getAuthorizator(): ?IAuthorizator { - if ($throw && !$this->authorizator) { + if (func_num_args()) { + trigger_error(__METHOD__ . '() parameter $throw is deprecated, use hasAuthorizator()', E_USER_DEPRECATED); + $throw = func_get_arg(0); + } + if (($throw ?? true) && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); } return $this->authorizator; } + + + /** + * Does the authorization exist? + */ + final public function hasAuthorizator(): bool + { + return (bool) $this->authorizator; + } } From c314da7ce826ef84bb3770113c13f26a44fa4e88 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 17 Oct 2018 17:50:02 +0200 Subject: [PATCH 098/254] travis: added PHP 7.3 --- .travis.yml | 1 + readme.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9bf2bfb2..c201a8ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: php php: - 7.1 - 7.2 + - 7.3 before_install: # turn off XDebug diff --git a/readme.md b/readme.md index 91005406..9324e49a 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ The recommended way to install is via Composer: composer require nette/security ``` -It requires PHP version 5.6 and supports PHP up to 7.2. The dev-master version requires PHP 7.1. +It requires PHP version 5.6 and supports PHP up to 7.3. The dev-master version requires PHP 7.1. Authentication From 2a954732cbeb8278698832c1da000c723f6b6949 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 24 Oct 2018 18:28:52 +0200 Subject: [PATCH 099/254] updated for nette/di 3 --- composer.json | 4 ++-- src/Bridges/SecurityDI/SecurityExtension.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 6efb5f48..2f39e1de 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ "nette/utils": "^2.4 || ^3.0" }, "require-dev": { - "nette/di": "^2.4 || ^3.0", - "nette/http": "^3.0.0-beta", + "nette/di": "^3.0.0", + "nette/http": "^3.0.0", "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 2ec9b534..a265f94e 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -43,7 +43,7 @@ public function loadConfiguration() ->setFactory(Nette\Security\Passwords::class); $builder->addDefinition($this->prefix('userStorage')) - ->setClass(Nette\Security\IUserStorage::class) + ->setType(Nette\Security\IUserStorage::class) ->setFactory(Nette\Http\UserStorage::class); $user = $builder->addDefinition($this->prefix('user')) @@ -51,7 +51,7 @@ public function loadConfiguration() if ($this->debugMode && $config['debugger']) { $user->addSetup('@Tracy\Bar::addPanel', [ - new Nette\DI\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), + new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), ]); } @@ -65,7 +65,7 @@ public function loadConfiguration() } $builder->addDefinition($this->prefix('authenticator')) - ->setClass(Nette\Security\IAuthenticator::class) + ->setType(Nette\Security\IAuthenticator::class) ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]); if ($this->name === 'security') { @@ -75,7 +75,7 @@ public function loadConfiguration() if ($config['roles'] || $config['resources']) { $authorizator = $builder->addDefinition($this->prefix('authorizator')) - ->setClass(Nette\Security\IAuthorizator::class) + ->setType(Nette\Security\IAuthorizator::class) ->setFactory(Nette\Security\Permission::class); foreach ($config['roles'] as $role => $parents) { From 8e547165612345e3b9fc2ffdc6f04d642212b8de Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Feb 2019 18:36:55 +0100 Subject: [PATCH 100/254] travis: finely segmented matrix, added PhpStan --- .travis.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c201a8ca..d2ec5115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,24 +20,34 @@ after_failure: jobs: include: - - env: title="Lowest Dependencies" + - name: Lowest Dependencies install: - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - stage: Code Standard Checker - php: 7.1 + - name: Nette Code Checker install: - # Install Nette Code Checker - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - # Install Nette Coding Standard - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress - script: - php temp/code-checker/code-checker --strict-types + + + - name: Nette Coding Standard + install: + - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + script: - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml + - stage: Static Analysis (informative) + install: + # Install PHPStan + - travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress + - travis_retry composer install --no-progress --prefer-dist + script: + - php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src + + - stage: Code Coverage script: - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src @@ -47,6 +57,7 @@ jobs: allow_failures: + - stage: Static Analysis (informative) - stage: Code Coverage From a07f0133a5ada1170fa08e1e8d09ff31abdf9a14 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Feb 2019 15:12:52 +0100 Subject: [PATCH 101/254] SecurityExtension: removed default dependency on Tracy --- src/Bridges/SecurityDI/SecurityExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index a265f94e..0612dab2 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -18,7 +18,7 @@ class SecurityExtension extends Nette\DI\CompilerExtension { public $defaults = [ - 'debugger' => true, + 'debugger' => null, 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] 'roles' => [], // of [role => parent(s)] 'resources' => [], // of [resource => parent] @@ -30,6 +30,7 @@ class SecurityExtension extends Nette\DI\CompilerExtension public function __construct(bool $debugMode = false) { + $this->defaults['debugger'] = interface_exists(\Tracy\IBarPanel::class); $this->debugMode = $debugMode; } From 55b6f3e7e3defc8e4f1f78c947f53185c51d6147 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 17 Feb 2019 22:13:42 +0100 Subject: [PATCH 102/254] readme: typos --- readme.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/readme.md b/readme.md index 9324e49a..0009cb5e 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,6 @@ $user->logout(); Simple, right? -.[note] Logging in requires users to have cookies enabled - other methods are not safe! Besides logging the user out with the `logout()` method, it can be done automatically based on specified time interval or closing the browser window. For this configuration we have to call `setExpiration()` during the login process. As an argument, it takes a relative time in seconds, UNIX timestamp, or textual representation of time. @@ -74,12 +73,11 @@ $user->setExpiration('30 minutes'); $user->setExpiration('2 days'); ``` -.[note] -Expiration must be set to value equal or lower than the expiration of [sessions]. +Expiration must be set to value equal or lower than the expiration of sessions. The reason of last logout can be obtained by method `$user->getLogoutReason()`, which returns one of these constants: `IUserStorage::INACTIVITY` if time expired or `IUserStorage::MANUAL` when the `logout()` method was called. -To make the example above work, we in fact have to create an object that verifies user's name and password. It's called **authenticator**. Its trivial implementation is the class [api:Nette\Security\SimpleAuthenticator], which in its constructor accepts an associative array: +To make the example above work, we in fact have to create an object that verifies user's name and password. It's called **authenticator**. Its trivial implementation is the class Nette\Security\SimpleAuthenticator, which in its constructor accepts an associative array: ```php $authenticator = new Nette\Security\SimpleAuthenticator(array( @@ -89,7 +87,7 @@ $authenticator = new Nette\Security\SimpleAuthenticator(array( $user->setAuthenticator($authenticator); ``` -If the login credentials are not valid, authenticator throws an [api:Nette\Security\AuthenticationException]: +If the login credentials are not valid, authenticator throws an Nette\Security\AuthenticationException: ```php try { @@ -103,22 +101,21 @@ try { } ``` -We usually configure authenticator inside a [config file |configuring], which only creates the object if it's requested by the application. The example above would be set in `config.neon` as follows: +We usually configure authenticator inside a config file, which only creates the object if it's requested by the application. The example above would be set in `config.neon` as follows: ``` -common: - services: - authenticator: Nette\Security\SimpleAuthenticator([ - john: IJ^%4dfh54* - kathy: 12345 - ]) +services: + authenticator: Nette\Security\SimpleAuthenticator([ + john: IJ^%4dfh54* + kathy: 12345 + ]) ``` Custom authenticator -------------------- -We will create a custom authenticator that will check validity of login credentials against a database table. Every authenticator must be an implementation of [api:Nette\Security\IAuthenticator], with its only method `authenticate()`. Its only purpose is to return an [identity | #identity] or to throw an `Nette\Security\AuthenticationException`. Framework defines few error codes, that can be used to determine the reason login was not successful, such as self-explaining `IAuthenticator::IDENTITY_NOT_FOUND` or `IAuthenticator::INVALID_CREDENTIAL`. +We will create a custom authenticator that will check validity of login credentials against a database table. Every authenticator must be an implementation of Nette\Security\IAuthenticator, with its only method `authenticate()`. Its only purpose is to return an identity or to throw an `Nette\Security\AuthenticationException`. Framework defines few error codes, that can be used to determine the reason login was not successful, such as self-explaining `IAuthenticator::IDENTITY_NOT_FOUND` or `IAuthenticator::INVALID_CREDENTIAL`. ```php use Nette\Security as NS; @@ -151,7 +148,7 @@ class MyAuthenticator implements NS\IAuthenticator } ``` -Class `MyAuthenticator` communicates with the database using [Nette\Database |database] layer and works with table `users`, where it grabs `username` and hash of `password` in the appropriate columns. If the password check is successful, it returns new identity with user ID and role, which we will mention [later | #roles]; +Class `MyAuthenticator` communicates with the database using Nette\Database layer and works with table `users`, where it grabs `username` and hash of `password` in the appropriate columns. If the password check is successful, it returns new identity with user ID and role, which we will mention later; This authenticator would be configured in the `config.neon` file like this: @@ -165,12 +162,12 @@ common: Identity -------- -Identity presents a set of user information, as returned by autheticator. It's an object implementing [api:Nette\Security\IIdentity] interface, with default implementation [api:Nette\Security\Identity]. +Identity presents a set of user information, as returned by autheticator. It's an object implementing Nette\Security\IIdentity interface, with default implementation Nette\Security\Identity. Class has methods `getId()`, that returns users ID (for example primary key for the respective database row), and `getRoles()`, which returns an array of all roles user is in. User data can be access as if they were identity properties. Identity is not erased when the user is logged out. So, if identity exists, it by itself does not grant that the user is also logged in. If we would like to explicitly delete the identity for some reason, we logout the user by calling `$user->logout(true)`. -Service `user` of class [api:Nette\Security\User] keeps the identity in session and uses it to all authorizations. +Service `user` of class Nette\Security\User keeps the identity in session and uses it to all authorizations. Identity can be access with `getIdentity` upon `$user`: ```php @@ -217,7 +214,7 @@ As you already know, logging user out does not erase his identity. Therefore the Authorizator ------------ -Authorizator decides, whether the user has permission to take some action. It's an implementation of [api:Nette\Security\IAuthorizator] interface with only one method `isAllowed()`. Purpose of this method is to determine, whether given role has the permission to perform certain *operation* with specific *resource*. +Authorizator decides, whether the user has permission to take some action. It's an implementation of Nette\Security\IAuthorizator interface with only one method `isAllowed()`. Purpose of this method is to determine, whether given role has the permission to perform certain *operation* with specific *resource*. - **role** is a user attribute - for example moderator, editor, visitor, registered user, administrator, ... - **resource** is a logical unit of the application - article, page, user, menu item, poll, presenter, ... @@ -253,7 +250,6 @@ if ($user->isAllowed('file', 'delete')) { // is user allowed to delete a resourc } ``` -.[note] Do not confuse two different methods `isAllowed`: one belongs to the authorizator and the other one to the `User` class, where first argument is not `$role`. Because user may have many roles, he is granted the permission only if at least one of roles has the permission. Both arguments are optional and their default value is *everything*. @@ -261,7 +257,7 @@ Because user may have many roles, he is granted the permission only if at least Permission ACL -------------- -Nette Framework has a complete authorizator, class [api:Nette\Security\Permission] which offers a light weight and flexible ACL((Access Control List)) layer for permission and access control. When we work with this class, we define roles, resources and individual privileges. Roles and resources may form hierarchies, as shown in the following example: +Nette Framework has a complete authorizator, class Nette\Security\Permission which offers a light weight and flexible ACL((Access Control List)) layer for permission and access control. When we work with this class, we define roles, resources and individual privileges. Roles and resources may form hierarchies, as shown in the following example: - `guest`: visitor that is not logged in, allowed to read and browse public part of the web, ie. articles, comments, and to vote in a poll From 20827937de5d858f734ad86b5f7916e567be83b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=BDurek?= Date: Sat, 2 Mar 2019 07:37:45 +0100 Subject: [PATCH 103/254] Permission: removed useless condition (#33) --- src/Security/Permission.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 8388e726..826db639 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -107,7 +107,7 @@ public function hasRole(string $role): bool */ private function checkRole(string $role, bool $throw = true): void { - if (!is_string($role) || $role === '') { + if ($role === '') { throw new Nette\InvalidArgumentException('Role must be a non-empty string.'); } elseif ($throw && !isset($this->roles[$role])) { @@ -272,7 +272,7 @@ public function hasResource(string $resource): bool */ private function checkResource(string $resource, bool $throw = true): void { - if (!is_string($resource) || $resource === '') { + if ($resource === '') { throw new Nette\InvalidArgumentException('Resource must be a non-empty string.'); } elseif ($throw && !isset($this->resources[$resource])) { From daa6083fb5ae7961983bad96a3792a7b4f5fef11 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 Mar 2019 00:24:14 +0100 Subject: [PATCH 104/254] composer: removed conflict with nette/nette < 2.2 --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index 2f39e1de..31f83fc3 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,6 @@ "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, - "conflict": { - "nette/nette": "<2.2" - }, "autoload": { "classmap": ["src/"] }, From 4c2149e54a273e804de6f905fdbd807258438e00 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 22 Mar 2019 02:00:12 +0100 Subject: [PATCH 105/254] readme: update --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0009cb5e..3d5d8180 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ The recommended way to install is via Composer: composer require nette/security ``` -It requires PHP version 5.6 and supports PHP up to 7.3. The dev-master version requires PHP 7.1. +It requires PHP version 7.1 and supports PHP up to 7.3. Authentication From c128c4d7ed5bd0ebeffc423b107350cb3f95afab Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 12 Apr 2019 15:20:21 +0200 Subject: [PATCH 106/254] tests: added test --- .../SecurityExtension.authorizator.phpt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/Security.DI/SecurityExtension.authorizator.phpt diff --git a/tests/Security.DI/SecurityExtension.authorizator.phpt b/tests/Security.DI/SecurityExtension.authorizator.phpt new file mode 100644 index 00000000..0f8b3b7c --- /dev/null +++ b/tests/Security.DI/SecurityExtension.authorizator.phpt @@ -0,0 +1,48 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('bar', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +$loader = new Nette\DI\Config\Loader; +$config = $loader->load(Tester\FileMock::create(' +security: + roles: + guest: + member: [guest] + resources: + item: + article: item +', 'neon')); + +eval($compiler->addConfig($config)->compile()); +$container = new Container; + +$authorizator = $container->getService('security.authorizator'); +Assert::type(Nette\Security\Permission::class, $authorizator); +Assert::same($authorizator, $container->getService('nette.authorizator')); + +Assert::same(['guest', 'member'], $authorizator->getRoles()); +Assert::same([], $authorizator->getRoleParents('guest')); +Assert::same(['guest'], $authorizator->getRoleParents('member')); + +Assert::same(['item', 'article'], $authorizator->getResources()); +Assert::false($authorizator->resourceInheritsFrom('item', 'article')); +Assert::true($authorizator->resourceInheritsFrom('article', 'item')); From bb751fe694183463ab23f7753f9094f8c565c1d2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Mar 2019 23:57:38 +0100 Subject: [PATCH 107/254] SecurityExtension: uses configuration Schema --- composer.json | 3 ++ src/Bridges/SecurityDI/SecurityExtension.php | 42 +++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 31f83fc3..b929630b 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,9 @@ "nette/tester": "^2.0", "tracy/tracy": "^2.4" }, + "conflict": { + "nette/di": "<3.0" + }, "autoload": { "classmap": ["src/"] }, diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 0612dab2..bb485da6 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -10,6 +10,7 @@ namespace Nette\Bridges\SecurityDI; use Nette; +use Nette\Schema\Expect; /** @@ -17,27 +18,38 @@ */ class SecurityExtension extends Nette\DI\CompilerExtension { - public $defaults = [ - 'debugger' => null, - 'users' => [], // of [user => password] or [user => ['password' => password, 'roles' => [role]]] - 'roles' => [], // of [role => parent(s)] - 'resources' => [], // of [resource => parent] - ]; - /** @var bool */ private $debugMode; public function __construct(bool $debugMode = false) { - $this->defaults['debugger'] = interface_exists(\Tracy\IBarPanel::class); $this->debugMode = $debugMode; } + public function getConfigSchema(): Nette\Schema\Schema + { + return Expect::structure([ + 'debugger' => Expect::bool(interface_exists(\Tracy\IBarPanel::class)), + 'users' => Expect::arrayOf( + Expect::anyOf( + Expect::string(), // user => password + Expect::structure([ // user => password + roles + 'password' => Expect::string(), + 'roles' => Expect::anyOf(Expect::string(), Expect::listOf('string')), + ])->castTo('array') + ) + ), + 'roles' => Expect::arrayOf('string|array|null'), // role => parent(s) + 'resources' => Expect::arrayOf('string|null'), // resource => parent + ]); + } + + public function loadConfiguration() { - $config = $this->validateConfig($this->defaults); + $config = $this->config; $builder = $this->getContainerBuilder(); $builder->addDefinition($this->prefix('passwords')) @@ -50,15 +62,15 @@ public function loadConfiguration() $user = $builder->addDefinition($this->prefix('user')) ->setFactory(Nette\Security\User::class); - if ($this->debugMode && $config['debugger']) { + if ($this->debugMode && $config->debugger) { $user->addSetup('@Tracy\Bar::addPanel', [ new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), ]); } - if ($config['users']) { + if ($config->users) { $usersList = $usersRoles = []; - foreach ($config['users'] as $username => $data) { + foreach ($config->users as $username => $data) { $data = is_array($data) ? $data : ['password' => $data]; $this->validateConfig(['password' => null, 'roles' => null], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; @@ -74,15 +86,15 @@ public function loadConfiguration() } } - if ($config['roles'] || $config['resources']) { + if ($config->roles || $config->resources) { $authorizator = $builder->addDefinition($this->prefix('authorizator')) ->setType(Nette\Security\IAuthorizator::class) ->setFactory(Nette\Security\Permission::class); - foreach ($config['roles'] as $role => $parents) { + foreach ($config->roles as $role => $parents) { $authorizator->addSetup('addRole', [$role, $parents]); } - foreach ($config['resources'] as $resource => $parents) { + foreach ($config->resources as $resource => $parents) { $authorizator->addSetup('addResource', [$resource, $parents]); } From d4525fe0bc50f8edd96ffad0c958fb7f657aa1d9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 4 Apr 2019 05:49:14 +0200 Subject: [PATCH 108/254] composer: update lower dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b929630b..b7f4a27f 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "tracy/tracy": "^2.4" }, "conflict": { - "nette/di": "<3.0" + "nette/di": "<3.0-stable" }, "autoload": { "classmap": ["src/"] From 10230ba18c017c34e2fa7191e4ecce9c8ff75f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Fri, 13 Sep 2019 11:16:48 +0200 Subject: [PATCH 109/254] typo (#38) --- license.md | 4 ++-- src/Security/Passwords.php | 2 +- src/Security/Permission.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/license.md b/license.md index cf741bd0..a955f5d3 100644 --- a/license.md +++ b/license.md @@ -56,5 +56,5 @@ GNU General Public License GPL licenses are very very long, so instead of including them here we offer you URLs with full text: -- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) -- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) +- [GPL version 2](https://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index da71f4bc..f671deea 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -27,7 +27,7 @@ class Passwords /** - * See http://php.net/manual/en/password.constants.php + * See https://php.net/manual/en/password.constants.php */ public function __construct(int $algo = PASSWORD_DEFAULT, array $options = []) { diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 826db639..448d57f2 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -15,7 +15,7 @@ /** * Access control list (ACL) functionality and privileges management. * - * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license + * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (https://www.zend.com), new BSD license * * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. */ From 6fd9f86f5342071045bba97dfc5ecaeb68df98f6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 24 Sep 2019 12:55:16 +0200 Subject: [PATCH 110/254] Passwords: constants PASSWORD_* are strings since PHP 7.4 [Closes #35] --- src/Security/Passwords.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index f671deea..a776a1d5 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -19,7 +19,7 @@ class Passwords { use Nette\SmartObject; - /** @var int */ + /** @var int|string string since PHP 7.4 */ private $algo; /** @var array */ @@ -29,7 +29,7 @@ class Passwords /** * See https://php.net/manual/en/password.constants.php */ - public function __construct(int $algo = PASSWORD_DEFAULT, array $options = []) + public function __construct($algo = PASSWORD_DEFAULT, array $options = []) { $this->algo = $algo; $this->options = $options; From 9aea7a8f9962357a96e36c0f5b9bb947ba595f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Tue, 24 Sep 2019 12:57:27 +0200 Subject: [PATCH 111/254] travis: run tests also on PHP 7.4 (#36) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d2ec5115..94dd96f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot before_install: # turn off XDebug From ac9742ba5c55dbe3d648d27993b2cd1371023525 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 16 Oct 2019 13:15:37 +0200 Subject: [PATCH 112/254] User: added getAuthenticatorIfExists & getAuthorizatorIfExists() --- src/Security/User.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 780a1cb8..d6458d16 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -149,7 +149,7 @@ public function setAuthenticator(IAuthenticator $handler) final public function getAuthenticator(): ?IAuthenticator { if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use hasAuthenticator()', E_USER_DEPRECATED); + trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthenticatorIfExists()', E_USER_DEPRECATED); $throw = func_get_arg(0); } if (($throw ?? true) && !$this->authenticator) { @@ -159,6 +159,15 @@ final public function getAuthenticator(): ?IAuthenticator } + /** + * Returns authentication handler. + */ + final public function getAuthenticatorIfExists(): ?IAuthenticator + { + return $this->authenticator; + } + + /** * Does the authentication exist? */ @@ -260,7 +269,7 @@ public function setAuthorizator(IAuthorizator $handler) final public function getAuthorizator(): ?IAuthorizator { if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use hasAuthorizator()', E_USER_DEPRECATED); + trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthorizatorIfExists()', E_USER_DEPRECATED); $throw = func_get_arg(0); } if (($throw ?? true) && !$this->authorizator) { @@ -270,6 +279,15 @@ final public function getAuthorizator(): ?IAuthorizator } + /** + * Returns current authorization handler. + */ + final public function getAuthorizatorIfExists(): ?IAuthorizator + { + return $this->authorizator; + } + + /** * Does the authorization exist? */ From f18cf84a87c241d74d87b484a694cefe18c17061 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 16 Oct 2019 13:23:11 +0200 Subject: [PATCH 113/254] User: deprecated hasAuthenticator() & hasAuthorizator() --- src/Security/User.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index d6458d16..cd3f9e36 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -168,9 +168,7 @@ final public function getAuthenticatorIfExists(): ?IAuthenticator } - /** - * Does the authentication exist? - */ + /** @deprecated */ final public function hasAuthenticator(): bool { return (bool) $this->authenticator; @@ -288,9 +286,7 @@ final public function getAuthorizatorIfExists(): ?IAuthorizator } - /** - * Does the authorization exist? - */ + /** @deprecated */ final public function hasAuthorizator(): bool { return (bool) $this->authorizator; From 209f7356144323bca705ba17885bf6d0f8cdaee6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 19 Nov 2019 15:54:03 +0100 Subject: [PATCH 114/254] travis: added PHP 7.4 --- .travis.yml | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 94dd96f7..abff51c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 before_install: # turn off XDebug diff --git a/readme.md b/readme.md index 3d5d8180..a6c43538 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ The recommended way to install is via Composer: composer require nette/security ``` -It requires PHP version 7.1 and supports PHP up to 7.3. +It requires PHP version 7.1 and supports PHP up to 7.4. Authentication From d1647f6c543794477dc5751482d9edc9cd7c59e6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Nov 2019 14:22:53 +0100 Subject: [PATCH 115/254] typo --- src/Security/IIdentity.php | 1 - src/Security/IResource.php | 1 - src/Security/IRole.php | 1 - src/Security/Permission.php | 8 ++++---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index d898e3cb..b11ffceb 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -15,7 +15,6 @@ */ interface IIdentity { - /** * Returns the ID of user. * @return mixed diff --git a/src/Security/IResource.php b/src/Security/IResource.php index 67765d97..14f61de8 100644 --- a/src/Security/IResource.php +++ b/src/Security/IResource.php @@ -15,7 +15,6 @@ */ interface IResource { - /** * Returns a string identifier of the Resource. */ diff --git a/src/Security/IRole.php b/src/Security/IRole.php index 6f5c1332..69f52057 100644 --- a/src/Security/IRole.php +++ b/src/Security/IRole.php @@ -15,7 +15,6 @@ */ interface IRole { - /** * Returns a string identifier of the Role. */ diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 448d57f2..a646b81e 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -105,12 +105,12 @@ public function hasRole(string $role): bool * Checks whether Role is valid and exists in the list. * @throws Nette\InvalidStateException */ - private function checkRole(string $role, bool $throw = true): void + private function checkRole(string $role, bool $exists = true): void { if ($role === '') { throw new Nette\InvalidArgumentException('Role must be a non-empty string.'); - } elseif ($throw && !isset($this->roles[$role])) { + } elseif ($exists && !isset($this->roles[$role])) { throw new Nette\InvalidStateException("Role '$role' does not exist."); } } @@ -270,12 +270,12 @@ public function hasResource(string $resource): bool * Checks whether Resource is valid and exists in the list. * @throws Nette\InvalidStateException */ - private function checkResource(string $resource, bool $throw = true): void + private function checkResource(string $resource, bool $exists = true): void { if ($resource === '') { throw new Nette\InvalidArgumentException('Resource must be a non-empty string.'); - } elseif ($throw && !isset($this->resources[$resource])) { + } elseif ($exists && !isset($this->resources[$resource])) { throw new Nette\InvalidStateException("Resource '$resource' does not exist."); } } From 43290c18f3702840154b6b394be3e3f3443398d9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Dec 2019 17:07:13 +0100 Subject: [PATCH 116/254] composer: added PHPStan --- .travis.yml | 6 +----- composer.json | 7 ++++++- tests/phpstan.neon | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 tests/phpstan.neon diff --git a/.travis.yml b/.travis.yml index abff51c1..b1ecaee6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,12 +41,8 @@ jobs: - stage: Static Analysis (informative) - install: - # Install PHPStan - - travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress - - travis_retry composer install --no-progress --prefer-dist script: - - php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src + - composer run-script phpstan - stage: Code Coverage diff --git a/composer.json b/composer.json index b7f4a27f..9cb8f816 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "nette/di": "^3.0.0", "nette/http": "^3.0.0", "nette/tester": "^2.0", - "tracy/tracy": "^2.4" + "tracy/tracy": "^2.4", + "phpstan/phpstan-nette": "^0.12" }, "conflict": { "nette/di": "<3.0-stable" @@ -31,6 +32,10 @@ "classmap": ["src/"] }, "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src", + "tester": "tester tests -s" + }, "extra": { "branch-alias": { "dev-master": "3.0-dev" diff --git a/tests/phpstan.neon b/tests/phpstan.neon new file mode 100644 index 00000000..60616a10 --- /dev/null +++ b/tests/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - ../vendor/phpstan/phpstan-nette/extension.neon From 9d8c96033f5553123b7a0f3d410b4115094430d3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 4 Jan 2020 15:46:02 +0100 Subject: [PATCH 117/254] uses nette/utils 3.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9cb8f816..8f85876a 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=7.1", - "nette/utils": "^2.4 || ^3.0" + "nette/utils": "^3.1" }, "require-dev": { "nette/di": "^3.0.0", From cae31582a2a8f9125e5e0df2e511684a1e30808d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 16 Dec 2019 00:07:19 +0100 Subject: [PATCH 118/254] UserPanel: uses capturing --- src/Bridges/SecurityTracy/UserPanel.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 0ccbd18b..42b6a043 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -39,10 +39,10 @@ public function getTab(): ?string return null; } - ob_start(function () {}); - $user = $this->user; - require __DIR__ . '/templates/UserPanel.tab.phtml'; - return ob_get_clean(); + return Nette\Utils\Helpers::capture(function () { + $user = $this->user; + require __DIR__ . '/templates/UserPanel.tab.phtml'; + }); } @@ -51,9 +51,9 @@ public function getTab(): ?string */ public function getPanel(): string { - ob_start(function () {}); - $user = $this->user; - require __DIR__ . '/templates/UserPanel.panel.phtml'; - return ob_get_clean(); + return Nette\Utils\Helpers::capture(function () { + $user = $this->user; + require __DIR__ . '/templates/UserPanel.panel.phtml'; + }); } } From bb3e14d8c4fb414110a1c6824f14abbee26853e8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 7 Jan 2020 11:49:34 +0100 Subject: [PATCH 119/254] composer: license clarification --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8f85876a..b06d24b4 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "🔑 Nette Security: provides authentication, authorization and a role-based access control management via ACL (Access Control List)", "keywords": ["nette", "authentication", "authorization", "ACL"], "homepage": "https://nette.org", - "license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"], + "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], "authors": [ { "name": "David Grudl", From db73664b99e609f586b4d18b94a4414c01c2b149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Krav=C4=8D=C3=ADk?= Date: Mon, 13 Jan 2020 12:27:12 +0100 Subject: [PATCH 120/254] readme: Updated (#39) Fix doc to get via DIC, prevent: Deprecated Non-static method Nette\Security\Passwords::verify() should not be called statically --- readme.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index a6c43538..4820c955 100644 --- a/readme.md +++ b/readme.md @@ -123,10 +123,12 @@ use Nette\Security as NS; class MyAuthenticator implements NS\IAuthenticator { public $database; + public $passwords; - function __construct(Nette\Database\Connection $database) + function __construct(Nette\Database\Connection $database, NS\Passwords $passwords) { $this->database = $database; + $this->passwords = $password; } function authenticate(array $credentials) @@ -139,7 +141,7 @@ class MyAuthenticator implements NS\IAuthenticator throw new NS\AuthenticationException('User not found.'); } - if (!NS\Passwords::verify($password, $row->password)) { + if (!$passwords->verify($password, $row->password)) { throw new NS\AuthenticationException('Invalid password.'); } @@ -153,9 +155,8 @@ Class `MyAuthenticator` communicates with the database using Nette\Database laye This authenticator would be configured in the `config.neon` file like this: ``` -common: - services: - authenticator: MyAuthenticator +services: + authenticator: MyAuthenticator ``` From f53dcb32d1103669e9a639ebb1500ce8fc642c53 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Jan 2020 15:48:01 +0100 Subject: [PATCH 121/254] User: identity and authenticated state are cached, UserStorage is not called repeatedly --- src/Security/User.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index cd3f9e36..26dc440c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -53,6 +53,12 @@ class User /** @var IAuthorizator|null */ private $authorizator; + /** @var IIdentity|null|false false means undefined */ + private $identity = false; + + /** @var bool|null */ + private $authenticated; + public function __construct(IUserStorage $storage, IAuthenticator $authenticator = null, IAuthorizator $authorizator = null) { @@ -84,6 +90,8 @@ public function login($user, string $password = null): void } $this->storage->setIdentity($user); $this->storage->setAuthenticated(true); + $this->identity = $user; + $this->authenticated = true; $this->onLoggedIn($this); } @@ -96,9 +104,11 @@ final public function logout(bool $clearIdentity = false): void if ($this->isLoggedIn()) { $this->onLoggedOut($this); $this->storage->setAuthenticated(false); + $this->authenticated = false; } if ($clearIdentity) { $this->storage->setIdentity(null); + $this->identity = null; } } @@ -108,7 +118,10 @@ final public function logout(bool $clearIdentity = false): void */ final public function isLoggedIn(): bool { - return $this->storage->isAuthenticated(); + if ($this->authenticated === null) { + $this->authenticated = $this->storage->isAuthenticated(); + } + return $this->authenticated; } @@ -117,7 +130,10 @@ final public function isLoggedIn(): bool */ final public function getIdentity(): ?IIdentity { - return $this->storage->getIdentity(); + if ($this->identity === false) { + $this->identity = $this->storage->getIdentity(); + } + return $this->identity; } From 93bf4acfea506ad9d50a9d9362359045b0ee02c3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Jan 2020 20:47:39 +0100 Subject: [PATCH 122/254] PHPStan: treatPhpDocTypesAsCertain: false --- tests/phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 60616a10..1b188579 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -1,2 +1,5 @@ includes: - ../vendor/phpstan/phpstan-nette/extension.neon + +parameters: + treatPhpDocTypesAsCertain: false From 3ca7ffc014ed54ba5323399f4712344f2776f90b Mon Sep 17 00:00:00 2001 From: MartkCz Date: Mon, 27 Jan 2020 11:07:33 +0100 Subject: [PATCH 123/254] SecurityExtensions, SimpleAuthenticator: add option for users data (#40) --- src/Bridges/SecurityDI/SecurityExtension.php | 10 +++--- src/Security/SimpleAuthenticator.php | 9 +++-- tests/Security/SimpleAuthenticator.Data.phpt | 37 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 tests/Security/SimpleAuthenticator.Data.phpt diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index bb485da6..1630fab9 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -35,9 +35,10 @@ public function getConfigSchema(): Nette\Schema\Schema 'users' => Expect::arrayOf( Expect::anyOf( Expect::string(), // user => password - Expect::structure([ // user => password + roles + Expect::structure([ // user => password + roles + data 'password' => Expect::string(), 'roles' => Expect::anyOf(Expect::string(), Expect::listOf('string')), + 'data' => Expect::array(), ])->castTo('array') ) ), @@ -69,17 +70,18 @@ public function loadConfiguration() } if ($config->users) { - $usersList = $usersRoles = []; + $usersList = $usersRoles = $usersData = []; foreach ($config->users as $username => $data) { $data = is_array($data) ? $data : ['password' => $data]; - $this->validateConfig(['password' => null, 'roles' => null], $data, $this->prefix("security.users.$username")); + $this->validateConfig(['password' => null, 'roles' => null, 'data' => []], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; $usersRoles[$username] = $data['roles'] ?? null; + $usersData[$username] = $data['data'] ?? []; } $builder->addDefinition($this->prefix('authenticator')) ->setType(Nette\Security\IAuthenticator::class) - ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]); + ->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles, $usersData]); if ($this->name === 'security') { $builder->addAlias('nette.authenticator', $this->prefix('authenticator')); diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index b2f97325..9f0a1996 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -25,15 +25,20 @@ class SimpleAuthenticator implements IAuthenticator /** @var array */ private $usersRoles; + /** @var array */ + private $usersData; + /** * @param array $userlist list of pairs username => password * @param array $usersRoles list of pairs username => role[] + * @param array $usersData list of pairs username => mixed[] */ - public function __construct(array $userlist, array $usersRoles = []) + public function __construct(array $userlist, array $usersRoles = [], array $usersData = []) { $this->userlist = $userlist; $this->usersRoles = $usersRoles; + $this->usersData = $usersData; } @@ -48,7 +53,7 @@ public function authenticate(array $credentials): IIdentity foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { if ((string) $pass === (string) $password) { - return new Identity($name, $this->usersRoles[$name] ?? null); + return new Identity($name, $this->usersRoles[$name] ?? null, $this->usersData[$name] ?? []); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); } diff --git a/tests/Security/SimpleAuthenticator.Data.phpt b/tests/Security/SimpleAuthenticator.Data.phpt new file mode 100644 index 00000000..4cf1a6e5 --- /dev/null +++ b/tests/Security/SimpleAuthenticator.Data.phpt @@ -0,0 +1,37 @@ + 'john123', + 'admin' => 'admin123', + 'user' => 'user123', +]; +$usersData = [ + 'admin' => ['nick' => 'admin', 'email' => 'foo@bar.com'], + 'user' => ['nick' => 'user', 'email' => 'foo@bar.com'], +]; +$expectedData = [ + 'admin' => ['nick' => 'admin', 'email' => 'foo@bar.com'], + 'user' => ['nick' => 'user', 'email' => 'foo@bar.com'], + 'john' => [], +]; + +$authenticator = new SimpleAuthenticator($users, [], $usersData); + +foreach ($users as $username => $password) { + $identity = $authenticator->authenticate([$username, $password]); + Assert::equal($username, $identity->getId()); + Assert::equal($expectedData[$username], $identity->getData()); +} From a0949f9e5e2270a3492d01b056649c209a66334f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 23 Feb 2020 19:19:08 +0100 Subject: [PATCH 124/254] travis: uses PHP 7.4 for coding checks --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b1ecaee6..3a1d7bc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ jobs: - name: Nette Code Checker + php: 7.4 install: - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress script: @@ -34,6 +35,7 @@ jobs: - name: Nette Coding Standard + php: 7.4 install: - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress script: @@ -41,11 +43,13 @@ jobs: - stage: Static Analysis (informative) + php: 7.4 script: - composer run-script phpstan - stage: Code Coverage + php: 7.4 script: - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src after_script: From d0d253b028cd417cb24dc0a327bcc67d93311f0b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 May 2020 00:57:15 +0200 Subject: [PATCH 125/254] added funding.yml --- .github/funding.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 00000000..25adc952 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,2 @@ +github: dg +custom: "https://nette.org/donate" From 2eacedebfbeb9d9587ef1e59c18d170879474c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Fri, 3 Apr 2020 20:30:25 +0200 Subject: [PATCH 126/254] phpstan fix (#43) (#44) --- src/Bridges/SecurityDI/SecurityExtension.php | 1 + src/Security/Permission.php | 13 +++++-------- tests/phpstan.neon | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 1630fab9..d87f21a7 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -50,6 +50,7 @@ public function getConfigSchema(): Nette\Schema\Schema public function loadConfiguration() { + /** @var object{debugger: bool, users: array, roles: array, resources: array} $config */ $config = $this->config; $builder = $this->getContainerBuilder(); diff --git a/src/Security/Permission.php b/src/Security/Permission.php index a646b81e..7e0e74aa 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -597,20 +597,17 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = break; } } - } else { - if (($result = $this->getRuleType($resource, null, $privilege)) !== null) { // look for rule on 'allRoles' pseudo-parent - break; - - } elseif (($result = $this->getRuleType($resource, null, null)) !== null) { - break; - } + } elseif (($result = $this->getRuleType($resource, null, $privilege)) !== null) { // look for rule on 'allRoles' pseudo-parent + break; + } elseif (($result = $this->getRuleType($resource, null, null)) !== null) { + break; } $resource = $this->resources[$resource]['parent']; // try next Resource } while (true); $this->queriedRole = $this->queriedResource = null; - return $result; + return $result ?? false; } diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 1b188579..5faa990b 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -3,3 +3,5 @@ includes: parameters: treatPhpDocTypesAsCertain: false + ignoreErrors: + - '#Variable \$this in isset\(\) always exists and is not nullable\.#' From 1450b1de10a61de2354752c9adef247d62c05ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Sun, 7 Jun 2020 12:49:22 +0200 Subject: [PATCH 127/254] Passwords: hash(): Password can not be empty. (#47) --- src/Security/Passwords.php | 4 ++++ tests/Security/Passwords.hash().phpt | 6 +++++- tests/Security/Passwords.static.phpt | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index a776a1d5..f0c22e7e 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -41,6 +41,10 @@ public function __construct($algo = PASSWORD_DEFAULT, array $options = []) */ public function hash(string $password): string { + if ($password === '') { + throw new Nette\InvalidArgumentException('Password can not be empty.'); + } + $hash = isset($this) ? @password_hash($password, $this->algo, $this->options) // @ is escalated to exception : @password_hash($password, PASSWORD_BCRYPT, func_get_args()[1] ?? []); // back compatibility with v2.x diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 7bd529b0..1752fc82 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; Assert::truthy( - preg_match('#^\$.{50,}\z#', (new Passwords)->hash('')) + preg_match('#^\$.{50,}\z#', (new Passwords)->hash('my-password')) ); Assert::truthy( @@ -27,3 +27,7 @@ Assert::same($hash, crypt('dg', $hash)); Assert::exception(function () { (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'); }, Nette\InvalidStateException::class, 'Computed hash is invalid. password_hash(): Invalid bcrypt cost parameter specified: 3'); + +Assert::exception(function () { + (new Passwords)->hash(''); +}, Nette\InvalidArgumentException::class, 'Password can not be empty.'); diff --git a/tests/Security/Passwords.static.phpt b/tests/Security/Passwords.static.phpt index 4183e0c0..cb4fd959 100644 --- a/tests/Security/Passwords.static.phpt +++ b/tests/Security/Passwords.static.phpt @@ -15,7 +15,7 @@ require __DIR__ . '/../bootstrap.php'; // deprecated static usage Assert::error(function () { - Passwords::hash(''); + Passwords::hash('my-password'); }, E_DEPRECATED, 'Non-static method Nette\Security\Passwords::hash() should not be called statically'); Assert::truthy( From 17d388ae4c63488b1ebf0017115012abdaa00e6f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 17 Jun 2020 12:13:39 +0200 Subject: [PATCH 128/254] removed old code --- src/Bridges/SecurityDI/SecurityExtension.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index d87f21a7..17190d21 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -74,7 +74,6 @@ public function loadConfiguration() $usersList = $usersRoles = $usersData = []; foreach ($config->users as $username => $data) { $data = is_array($data) ? $data : ['password' => $data]; - $this->validateConfig(['password' => null, 'roles' => null, 'data' => []], $data, $this->prefix("security.users.$username")); $usersList[$username] = $data['password']; $usersRoles[$username] = $data['roles'] ?? null; $usersData[$username] = $data['data'] ?? []; From dee3d249a0836cbc6e75acefc10e1167977d11f7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 31 Jul 2020 16:43:03 +0200 Subject: [PATCH 129/254] readme.md: updated --- readme.md | 367 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 205 insertions(+), 162 deletions(-) diff --git a/readme.md b/readme.md index 4820c955..c1cdb88a 100644 --- a/readme.md +++ b/readme.md @@ -19,176 +19,167 @@ Authentication & Authorization library for Nette. - how to create custom authenticators and authorizators - Access Control List -Documentation can be found on the [website](https://doc.nette.org/access-control). - -If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you! - - -Installation -============ - -The recommended way to install is via Composer: - -``` -composer require nette/security -``` - -It requires PHP version 7.1 and supports PHP up to 7.4. +Documentation can be found on the [website](https://doc.nette.org/access-control). If you like it, **[please make a donation now](https://github.com/sponsors/dg)**. Thank you! +In the examples, we will use an object of class [Nette\Security\User](https://api.nette.org/3.0/Nette/Security/User.html), which represents the current user. Authentication ============== -Authentication means **user login**, ie. the process during which user's identity is verified. User usually identifies himself using username and password. - -Logging user in with username and password: +Authentication means **user login**, ie. the process during which a user's identity is verified. The user usually identifies himself using username and password. Verification is performed by the so-called [authenticator](#Authenticator). If the login fails, it throws `Nette\Security\AuthenticationException`. ```php -$user->login($username, $password); +try { + $user->login($username, $password); +} catch (Nette\Security\AuthenticationException $e) { + $this->flashMessage('The username or password you entered is incorrect.'); +} ``` -Checking if user is logged in: +Logging him out: ```php -echo $user->isLoggedIn() ? 'yes' : 'no'; +$user->logout(); ``` -And logging him out: +And checking if user is logged in: ```php -$user->logout(); +echo $user->isLoggedIn() ? 'yes' : 'no'; ``` -Simple, right? +Simple, right? And all security aspects are handled by Nette for you. -Logging in requires users to have cookies enabled - other methods are not safe! - -Besides logging the user out with the `logout()` method, it can be done automatically based on specified time interval or closing the browser window. For this configuration we have to call `setExpiration()` during the login process. As an argument, it takes a relative time in seconds, UNIX timestamp, or textual representation of time. +You can also set the time interval after which the user logs off (otherwise he logs off with session expiration). This is done by the method `setExpiration()`, which is called before `login()`. Specify a string with relative time as a parameter: ```php // login expires after 30 minutes of inactivity $user->setExpiration('30 minutes'); -// login expires after two days of inactivity -$user->setExpiration('2 days'); +// cancel expiration +$user->setExpiration(null); ``` Expiration must be set to value equal or lower than the expiration of sessions. -The reason of last logout can be obtained by method `$user->getLogoutReason()`, which returns one of these constants: `IUserStorage::INACTIVITY` if 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\IUserStorage::INACTIVITY` if the time expired or `IUserStorage::MANUAL` when the `logout()` method was called. -To make the example above work, we in fact have to create an object that verifies user's name and password. It's called **authenticator**. Its trivial implementation is the class Nette\Security\SimpleAuthenticator, which in its constructor accepts an associative array: +In presenters, you can verify login in the `startup()` method: ```php -$authenticator = new Nette\Security\SimpleAuthenticator(array( - 'john' => 'IJ^%4dfh54*', - 'kathy' => '12345', // Kathy, this is a very weak password! -)); -$user->setAuthenticator($authenticator); +protected function startup() +{ + parent::startup(); + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } +} ``` -If the login credentials are not valid, authenticator throws an Nette\Security\AuthenticationException: - -```php -try { - // we try to log the user in - $user->login($username, $password); - // ... and redirect upon success - $this->redirect(...); -} catch (Nette\Security\AuthenticationException $e) { - echo 'Login error: ', $e->getMessage(); -} -``` +Authenticator +------------- -We usually configure authenticator inside a config file, which only creates the object if it's requested by the application. The example above would be set in `config.neon` as follows: +It is an object that verifies the login data, ie usually the name and password. The trivial implementation is the class [Nette\Security\SimpleAuthenticator](https://api.nette.org/3.0/Nette/Security/SimpleAuthenticator.html), which can be defined this way +```php +$authenticator = new Nette\Security\SimpleAuthenticator([ + # name => password + 'johndoe' => 'secret123', + 'kathy' => 'evenmoresecretpassword', +]); ``` -services: - authenticator: Nette\Security\SimpleAuthenticator([ - john: IJ^%4dfh54* - kathy: 12345 - ]) -``` - -Custom authenticator --------------------- +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. -We will create a custom authenticator that will check validity of login credentials against a database table. Every authenticator must be an implementation of Nette\Security\IAuthenticator, with its only method `authenticate()`. Its only purpose is to return an identity or to throw an `Nette\Security\AuthenticationException`. Framework defines few error codes, that can be used to determine the reason login was not successful, such as self-explaining `IAuthenticator::IDENTITY_NOT_FOUND` or `IAuthenticator::INVALID_CREDENTIAL`. +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`. ```php -use Nette\Security as NS; +use Nette; -class MyAuthenticator implements NS\IAuthenticator +class MyAuthenticator implements Nette\Security\IAuthenticator { - public $database; - public $passwords; + private $database; + private $passwords; - function __construct(Nette\Database\Connection $database, NS\Passwords $passwords) + public function __construct(Nette\Database\Context $database, Nette\Security\Passwords $passwords) { $this->database = $database; - $this->passwords = $password; + $this->passwords = $passwords; } - function authenticate(array $credentials) + public function authenticate(array $credentials): Nette\Security\IIdentity { - list($username, $password) = $credentials; + [$username, $password] = $credentials; + $row = $this->database->table('users') - ->where('username', $username)->fetch(); + ->where('username', $username) + ->fetch(); if (!$row) { - throw new NS\AuthenticationException('User not found.'); + throw new Nette\Security\AuthenticationException('User not found.'); } - if (!$passwords->verify($password, $row->password)) { - throw new NS\AuthenticationException('Invalid password.'); + if (!$this->passwords->verify($password, $row->password)) { + throw new Nette\Security\AuthenticationException('Invalid password.'); } - return new NS\Identity($row->id, $row->role); + return new Nette\Security\Identity( + $row->id, + $row->role, // or array of roles + ['name' => $row->username] + ); } } ``` -Class `MyAuthenticator` communicates with the database using Nette\Database layer and works with table `users`, where it grabs `username` and hash of `password` in the appropriate columns. If the password check is successful, it returns new identity with user ID and role, which we will mention later; +The MyAuthenticator class communicates with the database through [Nette Database Explorer](https://doc.nette.org/database) and works with table `users`, where column `username` contains the user's login name and column `password` contains [hash](https://doc.nette.org/passwords). After verifying the name and password, it returns the identity with user's ID, role (column `role` in the table), which we will mention [later ](#roles), and an array with additional data (in our case, the username). -This authenticator would be configured in the `config.neon` file like this: +$onLoggedIn, $onLoggedOut events +-------------------------------- + +Object `Nette\Security\User` has [events](https://doc.nette.org/smartobject#toc-events) `$onLoggedIn` and `$onLoggedOut`, so you can add callbacks that are triggered after a successful login or after the user logs out. + + +```php +$user->onLoggedIn[] = function () { + // user has just logged in +}; ``` -services: - authenticator: MyAuthenticator -``` + Identity --------- +======== -Identity presents a set of user information, as returned by autheticator. It's an object implementing Nette\Security\IIdentity interface, with default implementation Nette\Security\Identity. -Class has methods `getId()`, that returns users ID (for example primary key for the respective database row), and `getRoles()`, which returns an array of all roles user is in. User data can be access as if they were identity properties. +An identity is a set of information about a user that is returned by the authenticator and which is then stored in a session and retrieved using `$user->getIdentity()`. So we can get the id, roles and other user data as we passed them in the authenticator: -Identity is not erased when the user is logged out. So, if identity exists, it by itself does not grant that the user is also logged in. If we would like to explicitly delete the identity for some reason, we logout the user by calling `$user->logout(true)`. +```php +$user->getIdentity()->getId(); +// also works shortcut $user->getId(); -Service `user` of class Nette\Security\User keeps the identity in session and uses it to all authorizations. -Identity can be access with `getIdentity` upon `$user`: +$user->getIdentity()->getRoles(); -```php -if ($user->isLoggedIn()) { - echo 'User logged in: ', $user->getIdentity()->getId(); -} else { - echo 'User is not logged in'; -} +// user data can be access as properties +// the name we passed on in MyAuthenticator +$user->getIdentity()->name; ``` +Importantly, **when user logs out, identity is not deleted** and is still available. So, if identity exists, it by itself does not grant that the user is also logged in. If we want to explicitly delete the identity, we logout the user by `$user->logout(true)`. + +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. + Authorization ============= -Authorization detects whether the user has enough privilege to do some action, for example opening a file or deleting an article. Authorization assumes that the user has been successfully authenticated (logged in). +Authorization determines whether a user has sufficient privileges, for example, to access a specific resource or to perform an action. Authorization assumes previous successful authentication, ie that the user is logged in. -Nette Framework authorization may be based on what groups the user belongs to or on which roles were assigned to the user. We will start from the very beginning. - -For simple web sites with administration, where all users share same privileges, it is sufficient to use already mentioned `isLoggedIn()` method. Simply put, if the user is logged in, he has permissions to all actions, and vice versa. +For very simple websites with administration, where user rights are not distinguished, it is possible to use the already known method as an authorization criterion `isLoggedIn()`. In other words: once a user is logged in, he has permissions to all actions and vice versa. ```php if ($user->isLoggedIn()) { // is user logged in? @@ -200,9 +191,9 @@ if ($user->isLoggedIn()) { // is user logged in? Roles ----- -The purpose of roles is to offer a more precise privilege control while remaining independent on the user name. As soon as user logs in, he is assigned one or more roles. Roles themselves may be simple strings, such as `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 `Identity` constructor, either as a string or an array. -This time we will use the `isInRole()` method to check if the user is allowed to perform some action: +As an authorization criterion, we will now use the method `isInRole()`, which checks whether the user is in the given role: ```php if ($user->isInRole('admin')) { // is the admin role assigned to the user? @@ -210,38 +201,42 @@ if ($user->isInRole('admin')) { // is the admin role assigned to the user? } ``` -As you already know, logging user out does not erase his identity. Therefore the `getIdentity()` method still returns an `Identity` object, with all the assigned roles regardless on logout. Nette Framework adheres to the "less code, more security" principle, which is why it doesn't want to force coders to write `if ($user->isLoggedIn() && $user->isInRole('admin'))` everywhere and therefore the `isInRole()` method works with **efective roles**. If the user is logged in, roles assigned to identity are used, if he is logged out, 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 `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. + Authorizator ------------ -Authorizator decides, whether the user has permission to take some action. It's an implementation of Nette\Security\IAuthorizator interface with only one method `isAllowed()`. Purpose of this method is to determine, whether given role has the permission to perform certain *operation* with specific *resource*. +In addition to roles, we will introduce the terms resource and operation: - **role** is a user attribute - for example moderator, editor, visitor, registered user, administrator, ... - **resource** is a logical unit of the application - article, page, user, menu item, poll, presenter, ... -- **privilege** is a specific activity, which user may or may not do with *resource* - view, edit, delete, vote, ... - +- **operation** is a specific activity, which user may or may not do with *resource* - view, edit, delete, vote, ... -An implementation skeleton looks like this: +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()`: ```php class MyAuthorizator implements Nette\Security\IAuthorizator { - - function isAllowed($role, $resource, $privilege) + public function isAllowed($role, $resource, $operation): bool { - return ...; // returns either true or false - } + if ($role === 'admin') { + return true; + } + if ($role === 'user' && $resource === 'article') { + return true; + } + + ... + return false; + } } ``` -And an example of use: +And the following is an example of use. Note that this time we call the method `Nette\Security\User::isAllowed()`, not the authorizator's one, so there is not first parameter `$role`. This method calls `MyAuthorizator::isAllowed()` sequentially for all user roles and returns true if at least one of them has permission. ```php -// registers the authorizator -$user->setAuthorizator(new MyAuthorizator); - if ($user->isAllowed('file')) { // is user allowed to do everything with resource 'file'? useFile(); } @@ -251,110 +246,156 @@ if ($user->isAllowed('file', 'delete')) { // is user allowed to delete a resourc } ``` -Do not confuse two different methods `isAllowed`: one belongs to the authorizator and the other one to the `User` class, where first argument is not `$role`. +Both arguments are optional and their default value means *everything*. -Because user may have many roles, he is granted the permission only if at least one of roles has the permission. Both arguments are optional and their default value is *everything*. Permission ACL -------------- -Nette Framework has a complete authorizator, class Nette\Security\Permission which offers a light weight and flexible ACL((Access Control List)) layer for permission and access control. When we work with this class, we define roles, resources and individual privileges. Roles and resources may form hierarchies, as shown in the following example: - -- `guest`: visitor that is not logged in, allowed to read and browse public part of the web, ie. articles, comments, and to vote in a poll -- `registered`: logged in user, which may on top of that post comments +Nette comes with a built-in implementation of the authorizer, the [Nette\Security\Permission](https://api.nette.org/3.0/Nette/Security/Permission.html) class, which offers a lightweight and flexible ACL (Access Control List) layer for permission and access control. When we work with this class, we define roles, resources, and individual permissions. And roles and resources may form hierarchies. To explain, we will show an example of a web application: -- `administrator`: may write and administer articles, comments and polls +- `guest`: visitor that is not logged in, allowed to read and browse public part of the web, ie. read articles, comment and vote in polls +- `registered`: logged-in user, which may on top of that post comments +- `administrator`: can manage articles, comments and polls -So we have defined certain roles (`guest`, `registered` and `administrator`) and metioned resources (`article`, `comments`, `poll`), which the users may access or take actions on (`view`, `vote`, `add`, `edit`). +So we have defined certain roles (`guest`, `registered` and `administrator`) and mentioned resources (`article`, `comments`, `poll`), which the users may access or take actions on (`view`, `vote`, `add`, `edit`). -We create an instance of Presmission and define the user roles. As roles may inherit each other, we may for example specify that `administrator` may do the same as an ordinary visitor (and of course more). +We create an instance of the Permission class and define **roles**. It is possible to use the inheritance of roles, which ensures that, for example, a user with a role `administrator` can do what an ordinary website visitor can do (and of course more). ```php $acl = new Nette\Security\Permission; -// roles definition $acl->addRole('guest'); $acl->addRole('registered', 'guest'); // registered inherits from guest $acl->addRole('administrator', 'registered'); // and administrator inherits from registered ``` -Trivial, isn't it? This ensures all the properties of the parents will be inheritted by their children. - -Do note the method `getRoleParents()`, which returns an array of all direct parent roles, and the method `roleIntheritsFrom()`, which checks whether a role extends another. Their usage: - -```php -$acl->roleInheritsFrom('administrator', 'guest'); // true -$acl->getRoleParents('administrator'); // array('registered') - only direct parents -``` - -Now is the right time to define the set of resources that the users may acccess: +We will now define a list of **resources** that users can access: ```php $acl->addResource('article'); -$acl->addResource('comments'); +$acl->addResource('comment'); $acl->addResource('poll'); ``` -Also resources may use inheritance. The API offers similar methods, only the names are slightly different: `resourceInheritsFrom()`, `removeResource()`. - -And now the most important part. Roles and resources alone would do us no good, we have to create rules defining who can do what with whatever: +Resources can also use inheritance, for example, we can add `$acl->addResource('perex', 'article')`. -TODO: missing example for deny() +And now the most important thing. We will define between them **rules** determining who can do what: ```php // everything is denied now -// guest may view articles, comments and polls -$acl->allow('guest', array('article', 'comments', 'poll'), 'view'); +// let the guest view articles, comments and polls +$acl->allow('guest', ['article', 'comment', 'poll'], 'view'); +// and also vote in polls +$acl->allow('guest', 'poll', 'vote'); + +// the registered inherits the permissions from guesta, we will also let him to comment +$acl->allow('registered', 'comment', 'add'); -// registered user has also right to add comments -$acl->allow('registered', 'comments', 'add'); +// the administrator can view and edit anything +$acl->allow('administrator', $acl::ALL, ['view', 'edit', 'add']); +``` + +What if we want to **prevent** someone from accessing a resource? -// administrator may also edit and add everything -$acl->allow('administrator', Permission::ALL, array('view', 'edit', 'add')); +```php +// administrator cannot edit polls, that would be undemocractic. +$acl->deny('administrator', 'poll', 'edit'); ``` Now when we have created the set of rules, we may simply ask the authorization queries: ```php // can guest view articles? -echo $acl->isAllowed('guest', 'article', 'view'); // true +$acl->isAllowed('guest', 'article', 'view'); // true + // can guest edit an article? -echo $acl->isAllowed('guest', 'article', 'edit'); // false +$acl->isAllowed('guest', 'article', 'edit'); // false + +// can guest vote in polls? +$acl->isAllowed('guest', 'poll', 'vote'); // true + // may guest add comments? -echo $acl->isAllowed('guest', 'comments', 'add'); // false +$acl->isAllowed('guest', 'comment', 'add'); // false +``` + +The same applies to a registered user, but he can also comment: + +```php +$acl->isAllowed('registered', 'article', 'view'); // true +$acl->isAllowed('registered', 'comment', 'add'); // true +$acl->isAllowed('registered', 'comment', 'edit'); // false +``` + +The administrator can edit everything except polls: + +```php +$acl->isAllowed('administrator', 'poll', 'vote'); // true +$acl->isAllowed('administrator', 'poll', 'edit'); // false +$acl->isAllowed('administrator', 'comment', 'edit'); // true ``` -The same is true for the registered user, though he is allowed to add a comment: +Permissions can also be evaluated dynamically and we can leave the decision to our own callback, to which all parameters are passed: ```php -echo $acl->isAllowed('registered', 'article', 'view'); // true -echo $acl->isAllowed('registered', 'comments', 'add'); // true -echo $acl->isAllowed('registered', 'backend', 'view'); // false +$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { + return ...; +}; + +$acl->allow('registered', 'comment', null, $assertion); ``` -Administrator is allowed to do everything: +But how to solve a situation where the names of roles and resources are not enough, ie we would like to define that, for example, a role `registered` can edit a resource `article` only if it is its author? We will use objects instead of strings, the role will be the object [Nette\Security\IRole](https://api.nette.org/3.0/Nette/Security/IRole.html) and the source [Nette\Security\IResource](https://api.nette.org/3.0/Nette/Security/IResource.html). Their methods `getRoleId()` resp. `getResourceId()` will return the original strings: ```php -echo $acl->isAllowed('administrator', 'article', 'view'); // true -echo $acl->isAllowed('administrator', 'commend', 'add'); // true -echo $acl->isAllowed('administrator', 'poll', 'edit'); // true +class Registered implements Nette\Security\IRole +{ + public $id; + + public function getRoleId(): string + { + return 'registered'; + } +} + + +class Article implements Nette\Security\IResource +{ + public $authorId; + + public function getResourceId(): string + { + return 'article'; + } +} ``` -Admin rules may possibly be defined without any restrictions (without inheriting from any other roles): +And now let's create a rule: ```php -$acl->addRole('supervisor'); -$acl->allow('supervisor'); // all privileges for all resources for supervisor +$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { + $role = $acl->getQueriedRole(); // object Registered + $resource = $acl->getQueriedResource(); // object Article + return $role->id === $resource->authorId; +}; + +$acl->allow('registered', 'article', 'edit', $assertion); ``` -Whenever during the application runtime we may remove roles with `removeRolle()`, resources with `removeResource()` or rules with `removeAllow()` or `removeDeny()`. +The ACL is queried by passing objects: + +```php +$user = new Registered(...); +$article = new Article(...); +$acl->isAllowed($user, $article, 'edit'); +``` -Roles may inherit form one or more other roles. But what happens, if one ancestor has certain action allowed and the other one has it denied? Then the *role weight* comes into play - the last role in the array of roles to inherit has the greatest weight, first one the lowest: +A role may inherit form one or more other roles. But what happens, if one ancestor has certain action allowed and the other one has it denied? Then the *role weight* comes into play - the last role in the array of roles to inherit has the greatest weight, first one the lowest: ```php -$acl = new Permission(); +$acl = new Nette\Security\Permission; $acl->addRole('admin'); $acl->addRole('guest'); @@ -364,20 +405,22 @@ $acl->allow('admin', 'backend'); $acl->deny('guest', 'backend'); // example A: role admin has lower weight than role guest -$acl->addRole('john', array('admin', 'guest')); +$acl->addRole('john', ['admin', 'guest']); $acl->isAllowed('john', 'backend'); // false // example B: role admin has greater weight than role guest -$acl->addRole('mary', array('guest', 'admin')); +$acl->addRole('mary', ['guest', 'admin']); $acl->isAllowed('mary', 'backend'); // true ``` +Roles and resources can also be removed (`removeRole()`, `removeResource()`), rules can also be reverted (`removeAllow()`, `removeDeny()`). The array of all direct parent roles returns `getRoleParents()`. Whether two entities inherit from each other returns `roleInheritsFrom()` and `resourceInheritsFrom()`. + -Multiple applications in one scope -================================== +Multiple Independent Authentications +==================================== -Multiple applications may work on the same server, session, etc., each with separated authentication logic. We just have to set a unique namespace for each: +It is possible to have several independent logged users within one site and one session at a time. For example, if we want to have separate authentication for frontend and backend, we will just set a unique session namespace for each of them: ```php -$user->setNamespace('forum'); +$user->getStorage()->setNamespace('forum'); ``` From 7cf701952c82f7f1d1165c24b3773f5a554f3e80 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 16 Aug 2020 16:08:32 +0200 Subject: [PATCH 130/254] tests: test() with description --- tests/Security/Identity.phpt | 4 ++-- tests/bootstrap.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Security/Identity.phpt b/tests/Security/Identity.phpt index 96544994..30ea0d6b 100644 --- a/tests/Security/Identity.phpt +++ b/tests/Security/Identity.phpt @@ -13,7 +13,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -test(function () { +test('', function () { $id = new Identity(12, 'admin', ['name' => 'John']); Assert::same(12, $id->getId()); @@ -26,7 +26,7 @@ test(function () { }); -test(function () { +test('', function () { $id = new Identity('12'); Assert::same(12, $id->getId()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3fdbcd98..00852399 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -15,7 +15,7 @@ date_default_timezone_set('Europe/Prague'); -function test(\Closure $function): void +function test(string $title, Closure $function): void { $function(); } From 20dfaf48c2cf8c5de4926b23ba321494be280737 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 4 Aug 2020 01:09:03 +0200 Subject: [PATCH 131/254] improved PHP doc --- src/Security/Passwords.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index f0c22e7e..cb695e85 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -27,7 +27,8 @@ class Passwords /** - * See https://php.net/manual/en/password.constants.php + * 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 = []) { @@ -37,7 +38,7 @@ public function __construct($algo = PASSWORD_DEFAULT, array $options = []) /** - * Computes salted password hash. + * Computes password´s hash. The result contains the algorithm ID and its settings, cryptographical salt and the hash itself. */ public function hash(string $password): string { @@ -57,7 +58,7 @@ public function hash(string $password): string /** - * Verifies that a password matches a hash. + * Finds out, whether the given password matches the given hash. */ public function verify(string $password, string $hash): bool { @@ -66,7 +67,7 @@ public function verify(string $password, string $hash): bool /** - * Checks if the given hash matches the options. + * Finds out if the hash matches the options given in constructor. */ public function needsRehash(string $hash): bool { From 86d98577656eb215373dbe57b5a0c1155107ffda Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Oct 2020 18:59:23 +0200 Subject: [PATCH 132/254] added GitHub Actions workflow --- .github/workflows/coding-style.yml | 31 ++++++++++++++ .github/workflows/static-analysis.yml | 21 ++++++++++ .github/workflows/tests.yml | 58 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 .github/workflows/coding-style.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml new file mode 100644 index 00000000..4b3d7952 --- /dev/null +++ b/.github/workflows/coding-style.yml @@ -0,0 +1,31 @@ +name: Coding Style + +on: [push, pull_request] + +jobs: + nette_cc: + name: Nette Code Checker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: 7.1 + coverage: none + + - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress + - run: php temp/code-checker/code-checker --strict-types + + + nette_cs: + name: Nette Coding Standard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: 7.4 + coverage: none + + - run: composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + - run: php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000..e129028c --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,21 @@ +name: Static Analysis (only informative) + +on: + push: + branches: + - master + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: 7.4 + coverage: none + + - run: composer install --no-progress --prefer-dist + - run: composer phpstan + continue-on-error: true # is only informative diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..bb1a89ff --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,58 @@ +name: Tests + +on: [push, pull_request] + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.1', '7.2', '7.3', '7.4'] + + fail-fast: false + + name: PHP ${{ matrix.php }} tests + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: ${{ matrix.php }} + coverage: none + + - run: composer install --no-progress --prefer-dist + - run: vendor/bin/tester tests -s -C + - if: failure() + uses: actions/upload-artifact@v2 + with: + name: output + path: tests/**/output + + + lowest_dependencies: + name: Lowest Dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: 7.1 + coverage: none + + - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable + - run: vendor/bin/tester tests -s -C + + + code_coverage: + name: Code Coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v1 + with: + php-version: 7.4 + coverage: none + + - run: composer install --no-progress --prefer-dist + - run: wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar + - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src + - run: php coveralls.phar --verbose --config tests/.coveralls.yml From 248ac3a6c9d9fec52e169024a375b40a7e6fcbae Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Oct 2020 18:00:13 +0200 Subject: [PATCH 133/254] SecurityExtension: detects Tracy by presence of service @Tracy\Bar [Closes nette/di#245] --- src/Bridges/SecurityDI/SecurityExtension.php | 23 ++++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 17190d21..b3b7ae56 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -31,7 +31,7 @@ public function __construct(bool $debugMode = false) public function getConfigSchema(): Nette\Schema\Schema { return Expect::structure([ - 'debugger' => Expect::bool(interface_exists(\Tracy\IBarPanel::class)), + 'debugger' => Expect::bool(), 'users' => Expect::arrayOf( Expect::anyOf( Expect::string(), // user => password @@ -64,12 +64,6 @@ public function loadConfiguration() $user = $builder->addDefinition($this->prefix('user')) ->setFactory(Nette\Security\User::class); - if ($this->debugMode && $config->debugger) { - $user->addSetup('@Tracy\Bar::addPanel', [ - new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), - ]); - } - if ($config->users) { $usersList = $usersRoles = $usersData = []; foreach ($config->users as $username => $data) { @@ -110,4 +104,19 @@ public function loadConfiguration() $builder->addAlias('nette.userStorage', $this->prefix('userStorage')); } } + + + public function beforeCompile() + { + $builder = $this->getContainerBuilder(); + + if ( + $this->debugMode && + ($this->config->debugger ?? $builder->getByType(\Tracy\Bar::class)) + ) { + $builder->getDefinition($this->prefix('user'))->addSetup('@Tracy\Bar::addPanel', [ + new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), + ]); + } + } } From 45b289e0d799791505ae27fa29a903ec78c043d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Oct 2020 19:06:38 +0200 Subject: [PATCH 134/254] tests: fixed compatibility with PHP 8.0 --- tests/Security/Passwords.hash().phpt | 2 +- tests/Security/Passwords.static.phpt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 1752fc82..c8b7d376 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -26,7 +26,7 @@ Assert::same($hash, crypt('dg', $hash)); Assert::exception(function () { (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'); -}, Nette\InvalidStateException::class, 'Computed hash is invalid. password_hash(): Invalid bcrypt cost parameter specified: 3'); +}, PHP_VERSION_ID < 80000 ? Nette\InvalidStateException::class : ValueError::class); Assert::exception(function () { (new Passwords)->hash(''); diff --git a/tests/Security/Passwords.static.phpt b/tests/Security/Passwords.static.phpt index cb4fd959..c50c9b57 100644 --- a/tests/Security/Passwords.static.phpt +++ b/tests/Security/Passwords.static.phpt @@ -2,6 +2,7 @@ /** * Test: Nette\Security\Passwords deprecated static usage + * @phpVersion < 8 */ declare(strict_types=1); From d854229ca0b33dd25e29eb8e7a6dbc3aa82bb491 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 15 Oct 2020 17:25:05 +0200 Subject: [PATCH 135/254] tested on PHP 8.0 --- .github/workflows/tests.yml | 6 +++--- .travis.yml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb1a89ff..983b2a27 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,14 +7,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4'] + php: ['7.1', '7.2', '7.3', '7.4', '8.0'] fail-fast: false name: PHP ${{ matrix.php }} tests steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: none @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none diff --git a/.travis.yml b/.travis.yml index 3a1d7bc3..6d393394 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 7.2 - 7.3 - 7.4 + - 8.0snapshot before_install: # turn off XDebug @@ -62,7 +63,7 @@ jobs: - stage: Code Coverage -sudo: false +dist: xenial cache: directories: From c50ca29ae63ab547aecbf8eb8e7f457f0e9aa587 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 29 Oct 2020 01:13:03 +0100 Subject: [PATCH 136/254] updated nette/coding-standard --- .github/workflows/coding-style.yml | 4 ++-- .travis.yml | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 4b3d7952..38e63c0c 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -27,5 +27,5 @@ jobs: php-version: 7.4 coverage: none - - run: composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress - - run: php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml + - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress + - run: php temp/coding-standard/ecs check diff --git a/.travis.yml b/.travis.yml index 6d393394..e6239063 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,6 @@ jobs: - name: Nette Code Checker - php: 7.4 install: - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress script: @@ -38,9 +37,9 @@ jobs: - name: Nette Coding Standard php: 7.4 install: - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + - travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress script: - - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml + - php temp/coding-standard/ecs check - stage: Static Analysis (informative) From 5ea47b99cee60169c2cf8a42b243d2c4d51370d5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 29 Oct 2020 01:44:58 +0100 Subject: [PATCH 137/254] updated phpstan --- composer.json | 2 +- tests/phpstan.neon => phpstan.neon | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) rename tests/phpstan.neon => phpstan.neon (65%) diff --git a/composer.json b/composer.json index b06d24b4..54282bde 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "minimum-stability": "dev", "scripts": { - "phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src", + "phpstan": "phpstan analyse", "tester": "tester tests -s" }, "extra": { diff --git a/tests/phpstan.neon b/phpstan.neon similarity index 65% rename from tests/phpstan.neon rename to phpstan.neon index 5faa990b..f8dd9b68 100644 --- a/tests/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,14 @@ -includes: - - ../vendor/phpstan/phpstan-nette/extension.neon - parameters: + level: 5 + + paths: + - src + treatPhpDocTypesAsCertain: false + ignoreErrors: - '#Variable \$this in isset\(\) always exists and is not nullable\.#' + + +includes: + - vendor/phpstan/phpstan-nette/extension.neon From 7cf035a9d0511af14a11ef51d5780a7c6f8bfb43 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 29 Oct 2020 01:56:34 +0100 Subject: [PATCH 138/254] updated gitattributes --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index ab732073..3aa6270a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,5 +2,9 @@ .gitignore export-ignore .github export-ignore .travis.yml export-ignore +ecs.php export-ignore +phpstan.neon export-ignore tests/ export-ignore + *.sh eol=lf +*.php* diff=php linguist-language=PHP From 2f581180807e05a3d73569cc6015a05f762a2bd4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 29 Oct 2020 15:51:52 +0100 Subject: [PATCH 139/254] coding style --- src/Security/Identity.php | 4 +++- src/Security/Permission.php | 27 +++++++++++++++++++-------- src/Security/User.php | 9 ++++++--- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 63d00506..388182cc 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -41,7 +41,9 @@ 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; + $this->data = $data instanceof \Traversable + ? iterator_to_array($data) + : (array) $data; } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 7e0e74aa..adae3b1a 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -389,8 +389,12 @@ public function removeAllResources() * @param string|string[]|null $privileges * @return static */ - public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = null) - { + public function allow( + $roles = self::ALL, + $resources = self::ALL, + $privileges = self::ALL, + callable $assertion = null + ) { $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion); return $this; } @@ -405,8 +409,12 @@ public function allow($roles = self::ALL, $resources = self::ALL, $privileges = * @param string|string[]|null $privileges * @return static */ - public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, callable $assertion = null) - { + public function deny( + $roles = self::ALL, + $resources = self::ALL, + $privileges = self::ALL, + callable $assertion = null + ) { $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion); return $this; } @@ -521,7 +529,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile 'allPrivileges' => [ 'type' => self::DENY, 'assert' => null, - ], + ], 'byPrivilege' => [], ]; } @@ -557,8 +565,8 @@ 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|null|IRole $role - * @param string|null|IResource $resource + * @param string|IRole|null $role + * @param string|IResource|null $resource * @param string|null $privilege * @throws Nette\InvalidStateException */ @@ -582,7 +590,10 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = do { // depth-first search on $role if it is not 'allRoles' pseudo-parent - if ($role !== null && ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege)) !== null) { + if ( + $role !== null + && ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege)) !== null + ) { break; } diff --git a/src/Security/User.php b/src/Security/User.php index 26dc440c..f65b5c32 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -53,15 +53,18 @@ class User /** @var IAuthorizator|null */ private $authorizator; - /** @var IIdentity|null|false false means undefined */ + /** @var IIdentity|false|null false means undefined */ private $identity = false; /** @var bool|null */ private $authenticated; - public function __construct(IUserStorage $storage, IAuthenticator $authenticator = null, IAuthorizator $authorizator = null) - { + public function __construct( + IUserStorage $storage, + IAuthenticator $authenticator = null, + IAuthorizator $authorizator = null + ) { $this->storage = $storage; $this->authenticator = $authenticator; $this->authorizator = $authorizator; From 8f8965aa409ee79e7508b4af66d87803f0bd4d59 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Nov 2020 00:57:46 +0100 Subject: [PATCH 140/254] readme: php version info --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c1cdb88a..2f98fde7 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,6 @@ Nette Security: Access Control [![Latest Stable Version](https://poser.pugx.org/nette/security/v/stable)](https://github.com/nette/security/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/security/blob/master/license.md) - Introduction ============ @@ -23,6 +22,8 @@ Documentation can be found on the [website](https://doc.nette.org/access-control In the examples, we will use an object of class [Nette\Security\User](https://api.nette.org/3.0/Nette/Security/User.html), which represents the current user. +It requires PHP version 7.1 and supports PHP up to 8.0. + Authentication ============== From 98259d6951b7fb8325cb05c3cbfbddc649cab8b0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 31 Dec 2020 03:41:29 +0100 Subject: [PATCH 141/254] UserPanel: displays 'Session is closed' on closed session [Closes #52] --- src/Bridges/SecurityTracy/UserPanel.php | 11 ++++++++--- .../SecurityTracy/templates/UserPanel.tab.phtml | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 42b6a043..161dee75 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -35,12 +35,14 @@ public function __construct(Nette\Security\User $user) */ public function getTab(): ?string { - if (headers_sent() && !session_id()) { + if (!session_id()) { return null; } return Nette\Utils\Helpers::capture(function () { - $user = $this->user; + $status = session_status() === PHP_SESSION_ACTIVE + ? $this->user->isLoggedIn() + : '?'; require __DIR__ . '/templates/UserPanel.tab.phtml'; }); } @@ -49,8 +51,11 @@ public function getTab(): ?string /** * Renders panel. */ - public function getPanel(): string + public function getPanel(): ?string { + if (session_status() !== PHP_SESSION_ACTIVE) { + return null; + } return Nette\Utils\Helpers::capture(function () { $user = $this->user; require __DIR__ . '/templates/UserPanel.panel.phtml'; diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml index 4fa7e173..4571fbda 100644 --- a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml +++ b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml @@ -3,7 +3,9 @@ declare(strict_types=1); namespace Nette\Bridges\SecurityTracy; +$message = [true => 'Logged in', false => 'Unlogged', '?' => 'Session is closed']; +$color = [true => '#61A519', false => '#ababab', '?' => '#bb0000']; ?> - - + + From 9220a98036e12b7c4bbb50c370cc017d26c16b11 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Jan 2021 20:42:07 +0100 Subject: [PATCH 142/254] User: refactoring --- src/Security/User.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index f65b5c32..9a017635 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -53,8 +53,8 @@ class User /** @var IAuthorizator|null */ private $authorizator; - /** @var IIdentity|false|null false means undefined */ - private $identity = false; + /** @var IIdentity|null */ + private $identity; /** @var bool|null */ private $authenticated; @@ -122,7 +122,7 @@ final public function logout(bool $clearIdentity = false): void final public function isLoggedIn(): bool { if ($this->authenticated === null) { - $this->authenticated = $this->storage->isAuthenticated(); + $this->getStoredData(); } return $this->authenticated; } @@ -133,13 +133,20 @@ final public function isLoggedIn(): bool */ final public function getIdentity(): ?IIdentity { - if ($this->identity === false) { - $this->identity = $this->storage->getIdentity(); + if ($this->authenticated === null) { + $this->getStoredData(); } return $this->identity; } + private function getStoredData(): void + { + $this->identity = $this->storage->getIdentity(); + $this->authenticated = $this->identity && $this->storage->isAuthenticated(); + } + + /** * Returns current user ID, if any. * @return mixed From 3baad4b2ae6977c7c0c2a429298addd2069c8cf3 Mon Sep 17 00:00:00 2001 From: Pavel Weirich Date: Thu, 31 Dec 2020 03:44:06 +0100 Subject: [PATCH 143/254] User: added method refreshStorage() to clear identity cache. (#46) (#50) --- src/Security/User.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Security/User.php b/src/Security/User.php index 9a017635..c1869a89 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -158,6 +158,12 @@ public function getId() } + final public function refreshStorage(): void + { + $this->identity = $this->authenticated = null; + } + + /** * Sets authentication handler. * @return static From e1ac2bc563c06b5ce12d4a7b53d97c8d06436f18 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 3 Jan 2021 22:20:45 +0100 Subject: [PATCH 144/254] updated workflows --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 38e63c0c..9530e95c 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: 7.1 coverage: none @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index e129028c..0ae1a544 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 983b2a27..aa8766a2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: shivammathur/setup-php@v2 with: php-version: 7.1 coverage: none From 57fe52bc0e96d5bac0a9a6cf75d9ee622f57c197 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Jul 2020 14:36:56 +0200 Subject: [PATCH 145/254] opened 3.1-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 54282bde..e154e1eb 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } } } From 8fe2c755d0ca5c4fa8c531edbcb47d3cc0c9d526 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 2 Aug 2020 18:56:28 +0200 Subject: [PATCH 146/254] requires PHP 7.2 --- .github/workflows/coding-style.yml | 2 +- .github/workflows/tests.yml | 4 ++-- .travis.yml | 1 - composer.json | 2 +- readme.md | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 9530e95c..a5618ecd 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.1 + php-version: 7.2 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aa8766a2..04f8b5a8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['7.2', '7.3', '7.4', '8.0'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.1 + php-version: 7.2 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable diff --git a/.travis.yml b/.travis.yml index e6239063..07099980 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - 7.1 - 7.2 - 7.3 - 7.4 diff --git a/composer.json b/composer.json index e154e1eb..50bbc1af 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2 <8.1", "nette/utils": "^3.1" }, "require-dev": { diff --git a/readme.md b/readme.md index 2f98fde7..3fe01860 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ Documentation can be found on the [website](https://doc.nette.org/access-control In the examples, we will use an object of class [Nette\Security\User](https://api.nette.org/3.0/Nette/Security/User.html), which represents the current user. -It requires PHP version 7.1 and supports PHP up to 8.0. +It requires PHP version 7.2 and supports PHP up to 8.0. Authentication ============== From a602c8878edcd6647cfddcde06ba4af5c9960f58 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Jul 2020 14:39:04 +0200 Subject: [PATCH 147/254] removed deprecated stuff --- src/Security/Passwords.php | 9 ++------ tests/Security/Passwords.static.phpt | 31 ---------------------------- 2 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 tests/Security/Passwords.static.phpt diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index cb695e85..b9917dba 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -46,10 +46,7 @@ public function hash(string $password): string throw new Nette\InvalidArgumentException('Password can not be empty.'); } - $hash = isset($this) - ? @password_hash($password, $this->algo, $this->options) // @ is escalated to exception - : @password_hash($password, PASSWORD_BCRYPT, func_get_args()[1] ?? []); // back compatibility with v2.x - + $hash = @password_hash($password, $this->algo, $this->options); // @ is escalated to exception if (!$hash) { throw new Nette\InvalidStateException('Computed hash is invalid. ' . error_get_last()['message']); } @@ -71,8 +68,6 @@ public function verify(string $password, string $hash): bool */ public function needsRehash(string $hash): bool { - return isset($this) - ? password_needs_rehash($hash, $this->algo, $this->options) - : password_needs_rehash($hash, PASSWORD_BCRYPT, func_get_args()[1] ?? []); // back compatibility with v2.x + return password_needs_rehash($hash, $this->algo, $this->options); } } diff --git a/tests/Security/Passwords.static.phpt b/tests/Security/Passwords.static.phpt deleted file mode 100644 index c50c9b57..00000000 --- a/tests/Security/Passwords.static.phpt +++ /dev/null @@ -1,31 +0,0 @@ - 5])) // @ is not static -); - - -Assert::true(@Passwords::verify('dg', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static -Assert::false(@Passwords::verify('dgx', '$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static - - -Assert::true(@Passwords::needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); // @ is not static From 7fc85a03b61f4600e57828259a6344e5b6d7e293 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 8 Dec 2020 21:23:45 +0100 Subject: [PATCH 148/254] removed travis --- .travis.yml | 71 ----------------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 07099980..00000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -language: php -php: - - 7.2 - - 7.3 - - 7.4 - - 8.0snapshot - -before_install: - # turn off XDebug - - phpenv config-rm xdebug.ini || return 0 - -install: - - travis_retry composer install --no-progress --prefer-dist - -script: - - vendor/bin/tester tests -s - -after_failure: - # Print *.actual content - - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done - -jobs: - include: - - name: Lowest Dependencies - install: - - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - - - name: Nette Code Checker - install: - - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - script: - - php temp/code-checker/code-checker --strict-types - - - - name: Nette Coding Standard - php: 7.4 - install: - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress - script: - - php temp/coding-standard/ecs check - - - - stage: Static Analysis (informative) - php: 7.4 - script: - - composer run-script phpstan - - - - stage: Code Coverage - php: 7.4 - script: - - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src - after_script: - - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - - php coveralls.phar --verbose --config tests/.coveralls.yml - - - allow_failures: - - stage: Static Analysis (informative) - - stage: Code Coverage - - -dist: xenial - -cache: - directories: - - $HOME/.composer/cache - -notifications: - email: false From 3f209f1043f4535c1d3dccc1dbede56d0b8caf5a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Nov 2020 17:21:49 +0100 Subject: [PATCH 149/254] removed I prefixes from IAuthorizator, IResource, IRole --- ecs.php | 22 +++++++++++++ src/Bridges/SecurityDI/SecurityExtension.php | 2 +- .../{IAuthorizator.php => Authorizator.php} | 5 ++- src/Security/Permission.php | 10 +++--- src/Security/{IResource.php => Resource.php} | 5 ++- src/Security/{IRole.php => Role.php} | 5 ++- src/Security/User.php | 14 ++++----- src/compatibility.php | 31 +++++++++++++++++++ tests/Security/User.authorization.phpt | 3 +- 9 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 ecs.php rename src/Security/{IAuthorizator.php => Authorizator.php} (91%) rename src/Security/{IResource.php => Resource.php} (86%) rename src/Security/{IRole.php => Role.php} (88%) create mode 100644 src/compatibility.php diff --git a/ecs.php b/ecs.php new file mode 100644 index 00000000..e241616b --- /dev/null +++ b/ecs.php @@ -0,0 +1,22 @@ +import(PRESET_DIR . '/php71.php'); + + $parameters = $containerConfigurator->parameters(); + + $parameters->set('skip', [ + // Resource typehint + PhpCsFixer\Fixer\Phpdoc\PhpdocTypesFixer::class => [ + 'src/Security/Permission.php', + ], + ]); +}; diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index b3b7ae56..ad18a39c 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -84,7 +84,7 @@ public function loadConfiguration() if ($config->roles || $config->resources) { $authorizator = $builder->addDefinition($this->prefix('authorizator')) - ->setType(Nette\Security\IAuthorizator::class) + ->setType(Nette\Security\Authorizator::class) ->setFactory(Nette\Security\Permission::class); foreach ($config->roles as $role => $parents) { diff --git a/src/Security/IAuthorizator.php b/src/Security/Authorizator.php similarity index 91% rename from src/Security/IAuthorizator.php rename to src/Security/Authorizator.php index 2d12705c..085a8825 100644 --- a/src/Security/IAuthorizator.php +++ b/src/Security/Authorizator.php @@ -14,7 +14,7 @@ * Authorizator checks if a given role has authorization * to access a given resource. */ -interface IAuthorizator +interface Authorizator { /** Set type: all */ public const ALL = null; @@ -33,3 +33,6 @@ interface IAuthorizator */ function isAllowed($role, $resource, $privilege): bool; } + + +interface_exists(IAuthorizator::class); diff --git a/src/Security/Permission.php b/src/Security/Permission.php index adae3b1a..f755ff7d 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -19,7 +19,7 @@ * * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. */ -class Permission implements IAuthorizator +class Permission implements Authorizator { use Nette\SmartObject; @@ -565,8 +565,8 @@ 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|IRole|null $role - * @param string|IResource|null $resource + * @param string|Role|null $role + * @param string|Resource|null $resource * @param string|null $privilege * @throws Nette\InvalidStateException */ @@ -574,7 +574,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = { $this->queriedRole = $role; if ($role !== self::ALL) { - if ($role instanceof IRole) { + if ($role instanceof Role) { $role = $role->getRoleId(); } $this->checkRole($role); @@ -582,7 +582,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = $this->queriedResource = $resource; if ($resource !== self::ALL) { - if ($resource instanceof IResource) { + if ($resource instanceof Resource) { $resource = $resource->getResourceId(); } $this->checkResource($resource); diff --git a/src/Security/IResource.php b/src/Security/Resource.php similarity index 86% rename from src/Security/IResource.php rename to src/Security/Resource.php index 14f61de8..0e316182 100644 --- a/src/Security/IResource.php +++ b/src/Security/Resource.php @@ -13,10 +13,13 @@ /** * Represents resource, an object to which access is controlled. */ -interface IResource +interface Resource { /** * Returns a string identifier of the Resource. */ function getResourceId(): string; } + + +interface_exists(IResource::class); diff --git a/src/Security/IRole.php b/src/Security/Role.php similarity index 88% rename from src/Security/IRole.php rename to src/Security/Role.php index 69f52057..a869e32f 100644 --- a/src/Security/IRole.php +++ b/src/Security/Role.php @@ -13,10 +13,13 @@ /** * Represents role, an object that may request access to an IResource. */ -interface IRole +interface Role { /** * Returns a string identifier of the Role. */ function getRoleId(): string; } + + +interface_exists(IRole::class); diff --git a/src/Security/User.php b/src/Security/User.php index c1869a89..fe717ba4 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -21,7 +21,7 @@ * @property-read array $roles * @property-read int $logoutReason * @property IAuthenticator $authenticator - * @property IAuthorizator $authorizator + * @property Authorizator $authorizator */ class User { @@ -50,7 +50,7 @@ class User /** @var IAuthenticator|null */ private $authenticator; - /** @var IAuthorizator|null */ + /** @var Authorizator|null */ private $authorizator; /** @var IIdentity|null */ @@ -63,7 +63,7 @@ class User public function __construct( IUserStorage $storage, IAuthenticator $authenticator = null, - IAuthorizator $authorizator = null + Authorizator $authorizator = null ) { $this->storage = $storage; $this->authenticator = $authenticator; @@ -270,7 +270,7 @@ final public function isInRole(string $role): bool * Has a user effective access to the Resource? * If $resource is null, then the query applies to all resources. */ - public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL): bool + public function isAllowed($resource = Authorizator::ALL, $privilege = Authorizator::ALL): bool { foreach ($this->getRoles() as $role) { if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) { @@ -286,7 +286,7 @@ public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthoriz * Sets authorization handler. * @return static */ - public function setAuthorizator(IAuthorizator $handler) + public function setAuthorizator(Authorizator $handler) { $this->authorizator = $handler; return $this; @@ -296,7 +296,7 @@ public function setAuthorizator(IAuthorizator $handler) /** * Returns current authorization handler. */ - final public function getAuthorizator(): ?IAuthorizator + final public function getAuthorizator(): ?Authorizator { if (func_num_args()) { trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthorizatorIfExists()', E_USER_DEPRECATED); @@ -312,7 +312,7 @@ final public function getAuthorizator(): ?IAuthorizator /** * Returns current authorization handler. */ - final public function getAuthorizatorIfExists(): ?IAuthorizator + final public function getAuthorizatorIfExists(): ?Authorizator { return $this->authorizator; } diff --git a/src/compatibility.php b/src/compatibility.php new file mode 100644 index 00000000..f3827198 --- /dev/null +++ b/src/compatibility.php @@ -0,0 +1,31 @@ + Date: Wed, 9 Dec 2020 12:54:26 +0100 Subject: [PATCH 150/254] SimpleIdentity: Identifier must be int|string. (#53) --- src/Security/Identity.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 388182cc..91d99ebb 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -49,10 +49,14 @@ public function __construct($id, $roles = null, iterable $data = null) /** * 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; } From cbd4e8c3aad9c8f112a98ef005465d8e6b048310 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 31 Dec 2020 00:47:33 +0100 Subject: [PATCH 151/254] SecurityExtension: 'roles' & 'resources' are deprecated --- composer.json | 3 ++- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- tests/Security.DI/SecurityExtension.authorizator.phpt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 50bbc1af..16e8a560 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "phpstan/phpstan-nette": "^0.12" }, "conflict": { - "nette/di": "<3.0-stable" + "nette/di": "<3.0-stable", + "nette/schema": "<1.1" }, "autoload": { "classmap": ["src/"] diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index ad18a39c..f695ac1d 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -42,8 +42,8 @@ public function getConfigSchema(): Nette\Schema\Schema ])->castTo('array') ) ), - 'roles' => Expect::arrayOf('string|array|null'), // role => parent(s) - 'resources' => Expect::arrayOf('string|null'), // resource => parent + 'roles' => Expect::arrayOf('string|array|null')->deprecated(), // role => parent(s) + 'resources' => Expect::arrayOf('string|null')->deprecated(), // resource => parent ]); } diff --git a/tests/Security.DI/SecurityExtension.authorizator.phpt b/tests/Security.DI/SecurityExtension.authorizator.phpt index 0f8b3b7c..d898082c 100644 --- a/tests/Security.DI/SecurityExtension.authorizator.phpt +++ b/tests/Security.DI/SecurityExtension.authorizator.phpt @@ -32,7 +32,7 @@ security: article: item ', 'neon')); -eval($compiler->addConfig($config)->compile()); +@eval($compiler->addConfig($config)->compile()); // @ is deprecated $container = new Container; $authorizator = $container->getService('security.authorizator'); From e13731cc91f15333d51ecf98639436dfffda208e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Nov 2020 17:39:40 +0100 Subject: [PATCH 152/254] added SimpleIdentity, successor to Identity --- src/Security/Identity.php | 3 +- src/Security/SimpleAuthenticator.php | 2 +- src/Security/SimpleIdentity.php | 18 +++ tests/Security/SimpleIdentity.phpt | 36 ++++++ .../Security/User.authentication.legacy.phpt | 117 ++++++++++++++++++ tests/Security/User.authentication.phpt | 12 +- 6 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 src/Security/SimpleIdentity.php create mode 100644 tests/Security/SimpleIdentity.phpt create mode 100644 tests/Security/User.authentication.legacy.phpt diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 91d99ebb..e50116d7 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -13,8 +13,7 @@ /** - * Default implementation of IIdentity. - * + * @deprecated use Nette\Security\SimpleIdentity * @property mixed $id * @property array $roles * @property array $data diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 9f0a1996..85c359eb 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -53,7 +53,7 @@ public function authenticate(array $credentials): IIdentity foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { if ((string) $pass === (string) $password) { - return new Identity($name, $this->usersRoles[$name] ?? null, $this->usersData[$name] ?? []); + return new SimpleIdentity($name, $this->usersRoles[$name] ?? null, $this->usersData[$name] ?? []); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); } diff --git a/src/Security/SimpleIdentity.php b/src/Security/SimpleIdentity.php new file mode 100644 index 00000000..279aadd0 --- /dev/null +++ b/src/Security/SimpleIdentity.php @@ -0,0 +1,18 @@ + 'John']); + + Assert::same(12, $id->getId()); + Assert::same(12, $id->id); + Assert::same(['admin'], $id->getRoles()); + Assert::same(['admin'], $id->roles); + Assert::same(['name' => 'John'], $id->getData()); + Assert::same(['name' => 'John'], $id->data); + Assert::same('John', $id->name); +}); + + +test('', function () { + $id = new SimpleIdentity('12'); + Assert::same(12, $id->getId()); + + + $id = new SimpleIdentity('12345678901234567890'); + Assert::same('12345678901234567890', $id->getId()); +}); diff --git a/tests/Security/User.authentication.legacy.phpt b/tests/Security/User.authentication.legacy.phpt new file mode 100644 index 00000000..7bbb31b7 --- /dev/null +++ b/tests/Security/User.authentication.legacy.phpt @@ -0,0 +1,117 @@ + 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 7bbb31b7..17de106a 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -7,7 +7,7 @@ declare(strict_types=1); use Nette\Security\IAuthenticator; -use Nette\Security\Identity; +use Nette\Security\SimpleIdentity; use Tester\Assert; @@ -31,7 +31,7 @@ class Authenticator implements IAuthenticator throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); } else { - return new Identity('John Doe', 'admin'); + return new SimpleIdentity('John Doe', 'admin'); } } } @@ -81,16 +81,16 @@ Assert::exception(function () use ($user) { $user->login('john', 'xxx'); Assert::same(1, $counter->login); Assert::true($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); +Assert::equal(new SimpleIdentity('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')); +$user->login(new SimpleIdentity('John Doe', 'admin')); Assert::same(2, $counter->login); Assert::true($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); +Assert::equal(new SimpleIdentity('John Doe', 'admin'), $user->getIdentity()); // log out @@ -99,7 +99,7 @@ $user->logout(false); Assert::same(2, $counter->logout); Assert::false($user->isLoggedIn()); -Assert::equal(new Identity('John Doe', 'admin'), $user->getIdentity()); +Assert::equal(new SimpleIdentity('John Doe', 'admin'), $user->getIdentity()); // logging out and clearing identity... From 5db5f80490730a2ca858d0165fe1a5beed5cc857 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 4 Jan 2021 00:12:11 +0100 Subject: [PATCH 153/254] added Authenticator, successor to IAuthenticator --- src/Security/Authenticator.php | 23 +++++++++++++++++++++++ src/Security/IAuthenticator.php | 5 +++-- src/Security/User.php | 12 ++++++++---- tests/Security/User.authentication.phpt | 7 +++---- tests/Security/User.authorization.phpt | 11 +++++------ 5 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 src/Security/Authenticator.php diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php new file mode 100644 index 00000000..fc40ea3c --- /dev/null +++ b/src/Security/Authenticator.php @@ -0,0 +1,23 @@ +logout(true); - if (!$user instanceof IIdentity) { - $user = $this->getAuthenticator()->authenticate(func_get_args()); + if ($user instanceof IIdentity) { + $this->identity = $user; + } else { + $authenticator = $this->getAuthenticator(); + $this->identity = $authenticator instanceof Authenticator + ? $authenticator->authenticate($user, $password) + : $authenticator->authenticate(func_get_args()); } - $this->storage->setIdentity($user); + $this->storage->setIdentity($this->identity); $this->storage->setAuthenticated(true); - $this->identity = $user; $this->authenticated = true; $this->onLoggedIn($this); } diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 17de106a..bb560b1f 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -6,7 +6,7 @@ declare(strict_types=1); -use Nette\Security\IAuthenticator; +use Nette\Security\IIdentity; use Nette\Security\SimpleIdentity; use Tester\Assert; @@ -19,11 +19,10 @@ $_COOKIE = []; ob_start(); -class Authenticator implements IAuthenticator +class Authenticator implements Nette\Security\Authenticator { - public function authenticate(array $credentials): Nette\Security\IIdentity + public function authenticate(string $username, string $password): IIdentity { - [$username, $password] = $credentials; if ($username !== 'john') { throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 5172d1f8..dc178db1 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -6,8 +6,8 @@ declare(strict_types=1); -use Nette\Security\IAuthenticator; -use Nette\Security\Identity; +use Nette\Security\IIdentity; +use Nette\Security\SimpleIdentity; use Tester\Assert; @@ -20,11 +20,10 @@ $_COOKIE = []; ob_start(); -class Authenticator implements IAuthenticator +class Authenticator implements Nette\Security\Authenticator { - public function authenticate(array $credentials): Nette\Security\IIdentity + public function authenticate(string $username, string $password): IIdentity { - [$username, $password] = $credentials; if ($username !== 'john') { throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); @@ -32,7 +31,7 @@ class Authenticator implements IAuthenticator throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); } else { - return new Identity('John Doe', ['admin']); + return new SimpleIdentity('John Doe', 'admin'); } } } From 7491dd1e235e3b54a6a03c471d4e4411ec476288 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Nov 2020 17:56:33 +0100 Subject: [PATCH 154/254] SimpleAuthenticator implements Autheticator --- src/Security/SimpleAuthenticator.php | 12 ++++++++---- .../Security.DI/SecurityExtension.authenticator.phpt | 2 +- tests/Security/SimpleAuthenticator.Data.phpt | 2 +- tests/Security/SimpleAuthenticator.Roles.phpt | 2 +- tests/Security/SimpleAuthenticator.phpt | 8 ++++---- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 85c359eb..f7468778 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -13,9 +13,9 @@ /** - * Trivial implementation of IAuthenticator. + * Trivial implementation of Authenticator. */ -class SimpleAuthenticator implements IAuthenticator +class SimpleAuthenticator implements Authenticator { use Nette\SmartObject; @@ -47,9 +47,13 @@ public function __construct(array $userlist, array $usersRoles = [], array $user * and returns IIdentity on success or throws AuthenticationException * @throws AuthenticationException */ - public function authenticate(array $credentials): IIdentity + public function authenticate(/*string*/ $username, string $password = null): IIdentity { - [$username, $password] = $credentials; + if (is_array($username)) { + [$username, $password] = $username; // back compatibility + trigger_error(__METHOD__ . '() now accepts arguments (string $username, string $password).', E_USER_DEPRECATED); + } + foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { if ((string) $pass === (string) $password) { diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index 2ba1e258..ececfc23 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -52,7 +52,7 @@ $expectedRoles = [ ]; foreach ($userList as $username => $password) { - $identity = $authenticator->authenticate([$username, $password]); + $identity = $authenticator->authenticate($username, $password); Assert::equal($username, $identity->getId()); Assert::equal($expectedRoles[$username], $identity->getRoles()); } diff --git a/tests/Security/SimpleAuthenticator.Data.phpt b/tests/Security/SimpleAuthenticator.Data.phpt index 4cf1a6e5..3c9ebe2e 100644 --- a/tests/Security/SimpleAuthenticator.Data.phpt +++ b/tests/Security/SimpleAuthenticator.Data.phpt @@ -31,7 +31,7 @@ $expectedData = [ $authenticator = new SimpleAuthenticator($users, [], $usersData); foreach ($users as $username => $password) { - $identity = $authenticator->authenticate([$username, $password]); + $identity = $authenticator->authenticate($username, $password); Assert::equal($username, $identity->getId()); Assert::equal($expectedData[$username], $identity->getData()); } diff --git a/tests/Security/SimpleAuthenticator.Roles.phpt b/tests/Security/SimpleAuthenticator.Roles.phpt index 4cc7ffd5..d2e7a382 100644 --- a/tests/Security/SimpleAuthenticator.Roles.phpt +++ b/tests/Security/SimpleAuthenticator.Roles.phpt @@ -31,7 +31,7 @@ $expectedRoles = [ $authenticator = new SimpleAuthenticator($users, $usersRoles); foreach ($users as $username => $password) { - $identity = $authenticator->authenticate([$username, $password]); + $identity = $authenticator->authenticate($username, $password); Assert::equal($username, $identity->getId()); Assert::equal($expectedRoles[$username], $identity->getRoles()); } diff --git a/tests/Security/SimpleAuthenticator.phpt b/tests/Security/SimpleAuthenticator.phpt index 9a3c3764..a2c2370f 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -20,18 +20,18 @@ $users = [ $authenticator = new SimpleAuthenticator($users); -$identity = $authenticator->authenticate(['john', 'password123!']); +$identity = $authenticator->authenticate('john', 'password123!'); Assert::type(Nette\Security\IIdentity::class, $identity); Assert::equal('john', $identity->getId()); -$identity = $authenticator->authenticate(['admin', 'admin']); +$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']); + $authenticator->authenticate('admin', 'wrong password'); }, Nette\Security\AuthenticationException::class, 'Invalid password.'); Assert::exception(function () use ($authenticator) { - $authenticator->authenticate(['nobody', 'password']); + $authenticator->authenticate('nobody', 'password'); }, Nette\Security\AuthenticationException::class, "User 'nobody' not found."); From c6123873bab05f895a6d61c57fc1901da3eb18b6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 26 Nov 2020 21:53:47 +0100 Subject: [PATCH 155/254] User: logout reason is cached --- src/Security/User.php | 10 ++++++++-- tests/Security/MockUserStorage.php | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 62319dda..b176d847 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -59,6 +59,9 @@ class User /** @var bool|null */ private $authenticated; + /** @var int|null */ + private $logoutReason; + public function __construct( IUserStorage $storage, @@ -99,6 +102,7 @@ public function login($user, string $password = null): void $this->storage->setIdentity($this->identity); $this->storage->setAuthenticated(true); $this->authenticated = true; + $this->logoutReason = null; $this->onLoggedIn($this); } @@ -112,6 +116,7 @@ final public function logout(bool $clearIdentity = false): void $this->onLoggedOut($this); $this->storage->setAuthenticated(false); $this->authenticated = false; + $this->logoutReason = self::MANUAL; } if ($clearIdentity) { $this->storage->setIdentity(null); @@ -148,6 +153,7 @@ private function getStoredData(): void { $this->identity = $this->storage->getIdentity(); $this->authenticated = $this->identity && $this->storage->isAuthenticated(); + $this->logoutReason = $this->storage->getLogoutReason(); } @@ -164,7 +170,7 @@ public function getId() final public function refreshStorage(): void { - $this->identity = $this->authenticated = null; + $this->identity = $this->authenticated = $this->logoutReason = null; } @@ -240,7 +246,7 @@ public function setExpiration($expire, /*int*/$flags = 0) */ final public function getLogoutReason(): ?int { - return $this->storage->getLogoutReason(); + return $this->logoutReason; } diff --git a/tests/Security/MockUserStorage.php b/tests/Security/MockUserStorage.php index 07f0a1e6..19848057 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -40,5 +40,6 @@ public function setExpiration(?string $time, int $flags = 0) public function getLogoutReason(): ?int { + return null; } } From 9986f15d34888ea1648fe3cb7d49d18e4578c98a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 05:41:37 +0100 Subject: [PATCH 156/254] added UserStorage, successor to IUserStorage --- src/Security/IUserStorage.php | 2 +- src/Security/User.php | 63 ++++++++++++++----- src/Security/UserStorage.php | 43 +++++++++++++ tests/Security/MockUserStorage.legacy.php | 45 +++++++++++++ tests/Security/MockUserStorage.php | 28 +++------ .../Security/User.authentication.legacy.phpt | 2 +- tests/Security/User.authentication.phpt | 2 +- tests/Security/User.authorization.phpt | 2 +- 8 files changed, 148 insertions(+), 39 deletions(-) create mode 100644 src/Security/UserStorage.php create mode 100644 tests/Security/MockUserStorage.legacy.php diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php index 4923abbc..061e52c6 100644 --- a/src/Security/IUserStorage.php +++ b/src/Security/IUserStorage.php @@ -11,7 +11,7 @@ /** - * Interface for persistent storage for user object data. + * @deprecated use Nette\Security\UserStorage */ interface IUserStorage { diff --git a/src/Security/User.php b/src/Security/User.php index b176d847..c46fd296 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -44,7 +44,7 @@ class User /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ public $onLoggedOut; - /** @var IUserStorage Session storage for current user */ + /** @var UserStorage|IUserStorage Session storage for current user */ private $storage; /** @var IAuthenticator|null */ @@ -64,17 +64,24 @@ class User public function __construct( - IUserStorage $storage, + IUserStorage $legacyStorage = null, IAuthenticator $authenticator = null, - Authorizator $authorizator = null + Authorizator $authorizator = null, + UserStorage $storage = null ) { - $this->storage = $storage; + $this->storage = $storage ?? $legacyStorage; // back compatibility + if (!$this->storage) { + throw new Nette\InvalidStateException('UserStorage has not been set.'); + } $this->authenticator = $authenticator; $this->authorizator = $authorizator; } - final public function getStorage(): IUserStorage + /** + * @return UserStorage|IUserStorage + */ + final public function getStorage() { return $this->storage; } @@ -99,8 +106,14 @@ public function login($user, string $password = null): void ? $authenticator->authenticate($user, $password) : $authenticator->authenticate(func_get_args()); } - $this->storage->setIdentity($this->identity); - $this->storage->setAuthenticated(true); + + if ($this->storage instanceof UserStorage) { + $this->storage->saveAuthentication($this->identity); + } else { + $this->storage->setIdentity($this->identity); + $this->storage->setAuthenticated(true); + } + $this->authenticated = true; $this->logoutReason = null; $this->onLoggedIn($this); @@ -114,14 +127,20 @@ final public function logout(bool $clearIdentity = false): void { if ($this->isLoggedIn()) { $this->onLoggedOut($this); + } + + $this->authenticated = false; + $this->identity = $clearIdentity ? null : $this->identity; + + if ($this->storage instanceof UserStorage) { + $this->storage->clearAuthentication($clearIdentity); + } else { $this->storage->setAuthenticated(false); - $this->authenticated = false; + if ($clearIdentity) { + $this->storage->setIdentity(null); + } $this->logoutReason = self::MANUAL; } - if ($clearIdentity) { - $this->storage->setIdentity(null); - $this->identity = null; - } } @@ -151,9 +170,17 @@ final public function getIdentity(): ?IIdentity private function getStoredData(): void { - $this->identity = $this->storage->getIdentity(); - $this->authenticated = $this->identity && $this->storage->isAuthenticated(); - $this->logoutReason = $this->storage->getLogoutReason(); + if ($this->storage instanceof UserStorage) { + (function (bool $state, ?IIdentity $identity, ?int $reason) { + $this->identity = $identity; + $this->authenticated = $this->identity && $state; + $this->logoutReason = $reason; + })(...$this->storage->getState()); + } else { + $this->identity = $this->storage->getIdentity(); + $this->authenticated = $this->identity && $this->storage->isAuthenticated(); + $this->logoutReason = $this->storage->getLogoutReason(); + } } @@ -236,7 +263,11 @@ public function setExpiration($expire, /*int*/$flags = 0) $clearIdentity = $clearIdentity || func_get_arg(2); trigger_error(__METHOD__ . '() third parameter is deprecated, use flag setExpiration($time, IUserStorage::CLEAR_IDENTITY)', E_USER_DEPRECATED); } - $this->storage->setExpiration($expire, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); + + $arg = $this->storage instanceof UserStorage + ? $clearIdentity + : ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); + $this->storage->setExpiration($expire, $arg); return $this; } diff --git a/src/Security/UserStorage.php b/src/Security/UserStorage.php new file mode 100644 index 00000000..7abedac4 --- /dev/null +++ b/src/Security/UserStorage.php @@ -0,0 +1,43 @@ +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 19848057..a162ee01 100644 --- a/tests/Security/MockUserStorage.php +++ b/tests/Security/MockUserStorage.php @@ -2,44 +2,34 @@ declare(strict_types=1); -class MockUserStorage implements Nette\Security\IUserStorage +class MockUserStorage implements Nette\Security\UserStorage { private $auth = false; private $identity; - public function setAuthenticated(bool $state) - { - $this->auth = $state; - } - - - public function isAuthenticated(): bool - { - return $this->auth; - } - - - public function setIdentity(Nette\Security\IIdentity $identity = null) + public function saveAuthentication(Nette\Security\IIdentity $identity): void { + $this->auth = true; $this->identity = $identity; } - public function getIdentity(): ?Nette\Security\IIdentity + public function clearAuthentication(bool $clearIdentity): void { - return $this->identity; + $this->auth = false; + $this->identity = $clearIdentity ? null : $this->identity; } - public function setExpiration(?string $time, int $flags = 0) + public function getState(): array { + return [$this->auth, $this->identity, null]; } - public function getLogoutReason(): ?int + public function setExpiration(?string $expire, bool $clearIdentity): void { - return null; } } diff --git a/tests/Security/User.authentication.legacy.phpt b/tests/Security/User.authentication.legacy.phpt index 7bbb31b7..8c0e559d 100644 --- a/tests/Security/User.authentication.legacy.phpt +++ b/tests/Security/User.authentication.legacy.phpt @@ -12,7 +12,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -require __DIR__ . '/MockUserStorage.php'; +require __DIR__ . '/MockUserStorage.legacy.php'; // Setup environment $_COOKIE = []; diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index bb560b1f..b66f7f1c 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(new MockUserStorage); +$user = new Nette\Security\User(null, null, null, new MockUserStorage); $counter = (object) [ 'login' => 0, diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index dc178db1..6c9a17f8 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -46,7 +46,7 @@ class Authorizator implements Nette\Security\Authorizator } -$user = new Nette\Security\User(new MockUserStorage); +$user = new Nette\Security\User(null, null, null, new MockUserStorage); // guest Assert::false($user->isLoggedIn()); From ab73c13c6e89a9870d68d4114a53885c87e84699 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 05:43:08 +0100 Subject: [PATCH 157/254] added IdentityHandler --- src/Security/IdentityHandler.php | 21 +++++++++++++++++++++ src/Security/User.php | 22 +++++++++++++++------- 2 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 src/Security/IdentityHandler.php diff --git a/src/Security/IdentityHandler.php b/src/Security/IdentityHandler.php new file mode 100644 index 00000000..486538d6 --- /dev/null +++ b/src/Security/IdentityHandler.php @@ -0,0 +1,21 @@ +authenticate(func_get_args()); } + $id = $this->authenticator instanceof IdentityHandler + ? $this->authenticator->sleepIdentity($this->identity) + : $this->identity; if ($this->storage instanceof UserStorage) { - $this->storage->saveAuthentication($this->identity); + $this->storage->saveAuthentication($id); } else { - $this->storage->setIdentity($this->identity); + $this->storage->setIdentity($id); $this->storage->setAuthenticated(true); } @@ -171,16 +174,21 @@ final public function getIdentity(): ?IIdentity private function getStoredData(): void { if ($this->storage instanceof UserStorage) { - (function (bool $state, ?IIdentity $identity, ?int $reason) { - $this->identity = $identity; - $this->authenticated = $this->identity && $state; + (function (bool $state, ?IIdentity $id, ?int $reason) use (&$identity) { + $identity = $id; + $this->authenticated = $state; $this->logoutReason = $reason; })(...$this->storage->getState()); } else { - $this->identity = $this->storage->getIdentity(); - $this->authenticated = $this->identity && $this->storage->isAuthenticated(); + $identity = $this->storage->getIdentity(); + $this->authenticated = $this->storage->isAuthenticated(); $this->logoutReason = $this->storage->getLogoutReason(); } + + $this->identity = $identity && $this->authenticator instanceof IdentityHandler + ? $this->authenticator->wakeupIdentity($identity) + : $identity; + $this->authenticated = $this->authenticated && $this->identity; } From 3ae0bac4226b39da018df70aa7defdcf7952d627 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 03:26:45 +0100 Subject: [PATCH 158/254] IIdentity: silently added method getData() --- src/Security/IIdentity.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index b11ffceb..862db78b 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -12,6 +12,7 @@ /** * Represents the user of application. + * @method array getData() */ interface IIdentity { @@ -25,4 +26,9 @@ function getId(); * Returns a list of roles that the user is a member of. */ function getRoles(): array; + + /** + * Returns user data. + */ + //function getData(): array; } From 0b1ae96cd1b56ad9b69261d025e0c9fb0335488a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 03:27:12 +0100 Subject: [PATCH 159/254] added SessionStorage & CookieStorage (replaced Nette\Http\UserStorage) --- src/Bridges/SecurityHttp/CookieStorage.php | 97 +++++++++++ src/Bridges/SecurityHttp/SessionStorage.php | 151 ++++++++++++++++++ .../SecurityExtension.cookieStorage.phpt | 43 +++++ .../SecurityExtension.sessionStorage.phpt | 34 ++++ 4 files changed, 325 insertions(+) create mode 100644 src/Bridges/SecurityHttp/CookieStorage.php create mode 100644 src/Bridges/SecurityHttp/SessionStorage.php create mode 100644 tests/Security.DI/SecurityExtension.cookieStorage.phpt create mode 100644 tests/Security.DI/SecurityExtension.sessionStorage.phpt diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php new file mode 100644 index 00000000..4bdc034a --- /dev/null +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -0,0 +1,97 @@ +response = $response; + $this->request = $request; + } + + + public function saveAuthentication(IIdentity $identity): void + { + $this->response->setCookie( + $this->cookieName, + $identity->getId(), + $this->cookieExpiration, + null, + $this->cookieDomain + ); + } + + + public function clearAuthentication(bool $clearIdentity): void + { + $this->response->deleteCookie( + $this->cookieName, + null, + $this->cookieDomain + ); + } + + + public function getState(): array + { + $uid = $this->request->getCookie($this->cookieName); + $identity = is_string($uid) + ? new Nette\Security\SimpleIdentity($uid) + : null; + return [(bool) $identity, $identity, null]; + } + + + public function setExpiration(?string $expire, bool $clearIdentity): void + { + $this->cookieExpiration = $expire; + } + + + public function setCookieParameters( + string $name = null, + string $domain = null, + string $sameSite = null + ) { + $this->cookieName = $name ?? $this->cookieName; + $this->cookieDomain = $domain ?? $this->cookieDomain; + $this->cookieSameSite = $sameSite ?? $this->cookieSameSite; + } +} diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php new file mode 100644 index 00000000..0f904d26 --- /dev/null +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -0,0 +1,151 @@ +sessionHandler = $sessionHandler; + } + + + public function saveAuthentication(IIdentity $identity): void + { + $section = $this->getSessionSection(true); + $section->authenticated = true; + $section->reason = null; + $section->authTime = time(); // informative value + $section->identity = $identity; + + // Session Fixation defence + $this->sessionHandler->regenerateId(); + } + + + public function clearAuthentication(bool $clearIdentity): void + { + $section = $this->getSessionSection(true); + $section->authenticated = false; + $section->reason = self::LOGOUT_MANUAL; + $section->authTime = null; + + // Session Fixation defence + $this->sessionHandler->regenerateId(); + } + + + public function getState(): array + { + $session = $this->getSessionSection(false); + return $session + ? [(bool) $session->authenticated, $session->identity, $session->reason] + : [false, null, null]; + } + + + public function setExpiration(?string $time, bool $clearIdentity = false): void + { + $section = $this->getSessionSection(true); + if ($time) { + $time = Nette\Utils\DateTime::from($time)->format('U'); + $section->expireTime = $time; + $section->expireDelta = $time - time(); + + } else { + unset($section->expireTime, $section->expireDelta); + } + + $section->expireIdentity = (bool) $clearIdentity; + $section->setExpiration($time, 'foo'); // time check + } + + + /** + * Changes namespace; allows more users to share a session. + * @return static + */ + public function setNamespace(string $namespace) + { + if ($this->namespace !== $namespace) { + $this->namespace = $namespace; + $this->sessionSection = null; + } + return $this; + } + + + /** + * Returns current namespace. + */ + public function getNamespace(): string + { + return $this->namespace; + } + + + /** + * Returns and initializes $this->sessionSection. + */ + protected function getSessionSection(bool $need): ?SessionSection + { + if ($this->sessionSection !== null) { + return $this->sessionSection; + } + + if (!$need && !$this->sessionHandler->exists()) { + return null; + } + + $this->sessionSection = $section = $this->sessionHandler->getSection('Nette.Http.UserStorage/' . $this->namespace); + + if (!$section->identity instanceof IIdentity || !is_bool($section->authenticated)) { + $section->remove(); + } + + if ($section->authenticated && $section->expireDelta > 0) { // check time expiration + if ($section->expireTime < time()) { + $section->reason = self::LOGOUT_INACTIVITY; + $section->authenticated = false; + if ($section->expireIdentity) { + unset($section->identity); + } + } + $section->expireTime = time() + $section->expireDelta; // sliding expiration + } + + if (!$section->authenticated) { + unset($section->expireTime, $section->expireDelta, $section->expireIdentity, $section->authTime); + } + + return $this->sessionSection; + } +} diff --git a/tests/Security.DI/SecurityExtension.cookieStorage.phpt b/tests/Security.DI/SecurityExtension.cookieStorage.phpt new file mode 100644 index 00000000..81de7d10 --- /dev/null +++ b/tests/Security.DI/SecurityExtension.cookieStorage.phpt @@ -0,0 +1,43 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('session', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +$loader = new Nette\DI\Config\Loader; +$config = $loader->load(Tester\FileMock::create(' +security: + authentication: + storage: cookie + expiration: 1 week + cookieName: abc + cookieDomain: xyz + cookieSamesite: Strict +', 'neon')); + +eval($compiler->addConfig($config)->compile()); +$container = new Container; + +$storage = $container->getService('security.userStorage'); +Assert::type(Nette\Bridges\SecurityHttp\CookieStorage::class, $storage); + +Assert::with($storage, function () { + Assert::same('1 week', $this->cookieExpiration); +}); diff --git a/tests/Security.DI/SecurityExtension.sessionStorage.phpt b/tests/Security.DI/SecurityExtension.sessionStorage.phpt new file mode 100644 index 00000000..56719df6 --- /dev/null +++ b/tests/Security.DI/SecurityExtension.sessionStorage.phpt @@ -0,0 +1,34 @@ +addExtension('foo', new HttpExtension); +$compiler->addExtension('session', new SessionExtension); +$compiler->addExtension('security', new SecurityExtension); + +$loader = new Nette\DI\Config\Loader; +$config = $loader->load(Tester\FileMock::create(' +session: + expiration: 1 year + +security: + authentication: + storage: session + expiration: 1 week +', 'neon')); + +eval($compiler->addConfig($config)->compile()); +$container = new Container; + +Assert::type(Nette\Bridges\SecurityHttp\SessionStorage::class, $container->getService('security.userStorage')); From 3537b70eafb1d1ea91beb09c8c41350bd67a72bb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 03:27:30 +0100 Subject: [PATCH 160/254] SecurityExtension: changed service security.userStorage (BC break) --- src/Bridges/SecurityDI/SecurityExtension.php | 6 +++++- tests/Security.DI/SecurityExtension.user.phpt | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index f695ac1d..d3104558 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -57,7 +57,11 @@ public function loadConfiguration() $builder->addDefinition($this->prefix('passwords')) ->setFactory(Nette\Security\Passwords::class); - $builder->addDefinition($this->prefix('userStorage')) + $storage = $builder->addDefinition($this->prefix('userStorage')) + ->setType(Nette\Security\UserStorage::class) + ->setFactory(Nette\Bridges\SecurityHttp\SessionStorage::class); + + $builder->addDefinition($this->prefix('legacyUserStorage')) // deprecated ->setType(Nette\Security\IUserStorage::class) ->setFactory(Nette\Http\UserStorage::class); diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index 242a285d..f4f3e9ff 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -24,7 +24,8 @@ $compiler->addExtension('security', new SecurityExtension); eval($compiler->compile()); $container = new Container; -Assert::type(Nette\Http\UserStorage::class, $container->getService('security.userStorage')); +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 From bce278f5e981ad8f421de0c04e5424c5d23731ac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Jan 2021 03:39:36 +0100 Subject: [PATCH 161/254] SecurityExtension: added section 'authentication' --- composer.json | 2 +- src/Bridges/SecurityDI/SecurityExtension.php | 24 ++++++++++++++++++- .../SecurityExtension.cookieStorage.phpt | 11 +++++++-- .../SecurityExtension.sessionStorage.phpt | 4 +++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 16e8a560..9099b614 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "nette/utils": "^3.1" }, "require-dev": { - "nette/di": "^3.0.0", + "nette/di": "^3.0.1", "nette/http": "^3.0.0", "nette/tester": "^2.0", "tracy/tracy": "^2.4", diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index d3104558..27cba4b2 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -44,6 +44,13 @@ public function getConfigSchema(): Nette\Schema\Schema ), 'roles' => Expect::arrayOf('string|array|null')->deprecated(), // role => parent(s) 'resources' => Expect::arrayOf('string|null')->deprecated(), // resource => parent + 'authentication' => Expect::structure([ + 'storage' => Expect::anyOf('session', 'cookie')->default('session'), + 'expiration' => Expect::string()->dynamic(), + 'cookieName' => Expect::string(), + 'cookieDomain' => Expect::string(), + 'cookieSamesite' => Expect::anyOf('Lax', 'Strict', 'None'), + ]), ]); } @@ -57,9 +64,20 @@ public function loadConfiguration() $builder->addDefinition($this->prefix('passwords')) ->setFactory(Nette\Security\Passwords::class); + $auth = $config->authentication; $storage = $builder->addDefinition($this->prefix('userStorage')) ->setType(Nette\Security\UserStorage::class) - ->setFactory(Nette\Bridges\SecurityHttp\SessionStorage::class); + ->setFactory([ + 'session' => Nette\Bridges\SecurityHttp\SessionStorage::class, + 'cookie' => Nette\Bridges\SecurityHttp\CookieStorage::class, + ][$auth->storage]); + + if ($auth->storage === 'cookie') { + if ($auth->cookieDomain === 'domain') { + $auth->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)'); + } + $storage->addSetup('setCookieParameters', [$auth->cookieName, $auth->cookieDomain, $auth->cookieSamesite]); + } $builder->addDefinition($this->prefix('legacyUserStorage')) // deprecated ->setType(Nette\Security\IUserStorage::class) @@ -68,6 +86,10 @@ public function loadConfiguration() $user = $builder->addDefinition($this->prefix('user')) ->setFactory(Nette\Security\User::class); + if ($auth->expiration) { + $user->addSetup('setExpiration', [$auth->expiration]); + } + if ($config->users) { $usersList = $usersRoles = $usersData = []; foreach ($config->users as $username => $data) { diff --git a/tests/Security.DI/SecurityExtension.cookieStorage.phpt b/tests/Security.DI/SecurityExtension.cookieStorage.phpt index 81de7d10..a6296853 100644 --- a/tests/Security.DI/SecurityExtension.cookieStorage.phpt +++ b/tests/Security.DI/SecurityExtension.cookieStorage.phpt @@ -17,7 +17,7 @@ require __DIR__ . '/../bootstrap.php'; $compiler = new DI\Compiler; -$compiler->addExtension('foo', new HttpExtension); +$compiler->addExtension('http', new HttpExtension); $compiler->addExtension('session', new SessionExtension); $compiler->addExtension('security', new SecurityExtension); @@ -28,16 +28,23 @@ security: storage: cookie expiration: 1 week cookieName: abc - cookieDomain: xyz + cookieDomain: domain cookieSamesite: Strict + +services: + http.request: Nette\Http\Request(Nette\Http\UrlScript("http://www.nette.org")) ', 'neon')); eval($compiler->addConfig($config)->compile()); $container = new Container; $storage = $container->getService('security.userStorage'); +$user = $container->getService('security.user'); Assert::type(Nette\Bridges\SecurityHttp\CookieStorage::class, $storage); Assert::with($storage, function () { Assert::same('1 week', $this->cookieExpiration); + Assert::same('abc', $this->cookieName); + Assert::same('nette.org', $this->cookieDomain); + Assert::same('Strict', $this->cookieSameSite); }); diff --git a/tests/Security.DI/SecurityExtension.sessionStorage.phpt b/tests/Security.DI/SecurityExtension.sessionStorage.phpt index 56719df6..38f9f42c 100644 --- a/tests/Security.DI/SecurityExtension.sessionStorage.phpt +++ b/tests/Security.DI/SecurityExtension.sessionStorage.phpt @@ -31,4 +31,6 @@ security: eval($compiler->addConfig($config)->compile()); $container = new Container; -Assert::type(Nette\Bridges\SecurityHttp\SessionStorage::class, $container->getService('security.userStorage')); +$storage = $container->getService('security.userStorage'); +$user = $container->getService('security.user'); +Assert::type(Nette\Bridges\SecurityHttp\SessionStorage::class, $storage); From 520b68fd7d4c48caa1740e3c5fbb5fb8cc025f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Tue, 5 Jan 2021 21:33:42 +0100 Subject: [PATCH 162/254] SessionStorage: Fix clearing identity in case of clearAuthentication(true). (#55) --- src/Bridges/SecurityHttp/SessionStorage.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 0f904d26..2c27fc67 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -57,6 +57,9 @@ public function clearAuthentication(bool $clearIdentity): void $section->authenticated = false; $section->reason = self::LOGOUT_MANUAL; $section->authTime = null; + if ($clearIdentity === true) { + $section->identity = null; + } // Session Fixation defence $this->sessionHandler->regenerateId(); From b0db78084a3fa95ccb626685d34dca109c922717 Mon Sep 17 00:00:00 2001 From: kori Date: Sun, 8 Nov 2020 08:10:51 +0100 Subject: [PATCH 163/254] User::inInRole() works with object Role --- src/Security/User.php | 7 ++++++- tests/Security/User.authorization.phpt | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 174fd378..c20f6db5 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -311,7 +311,12 @@ public function getRoles(): array */ final public function isInRole(string $role): bool { - return in_array($role, $this->getRoles(), true); + foreach ($this->getRoles() as $r) { + if ($role === ($r instanceof Role ? $r->getRoleId() : $r)) { + return true; + } + } + return false; } diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 6c9a17f8..9061096d 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\Security\IIdentity; +use Nette\Security\Role; use Nette\Security\SimpleIdentity; use Tester\Assert; @@ -31,7 +32,7 @@ class Authenticator implements Nette\Security\Authenticator throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); } else { - return new SimpleIdentity('John Doe', 'admin'); + return new SimpleIdentity('John Doe', ['admin', new TesterRole]); } } } @@ -45,6 +46,13 @@ class Authorizator implements Nette\Security\Authorizator } } +class TesterRole implements Role +{ + public function getRoleId(): string + { + return 'tester'; + } +} $user = new Nette\Security\User(null, null, null, new MockUserStorage); @@ -54,6 +62,7 @@ Assert::false($user->isLoggedIn()); Assert::same(['guest'], $user->getRoles()); Assert::false($user->isInRole('admin')); +Assert::false($user->isInRole('tester')); Assert::true($user->isInRole('guest')); @@ -65,8 +74,9 @@ $user->setAuthenticator($handler); $user->login('john', 'xxx'); Assert::true($user->isLoggedIn()); -Assert::same(['admin'], $user->getRoles()); +Assert::equal(['admin', new TesterRole], $user->getRoles()); Assert::true($user->isInRole('admin')); +Assert::true($user->isInRole('tester')); Assert::false($user->isInRole('guest')); From d7d9beec518cbcc3cfe3f123e162ef6fafb68013 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 6 Jan 2021 04:14:12 +0100 Subject: [PATCH 164/254] CookieStorage: added MIN_LENGTH for UID --- src/Bridges/SecurityHttp/CookieStorage.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 4bdc034a..e613718b 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -21,6 +21,8 @@ final class CookieStorage implements Nette\Security\UserStorage { use Nette\SmartObject; + private const MIN_LENGTH = 13; + /** @var Http\IRequest */ private $request; @@ -49,9 +51,13 @@ public function __construct(Http\IRequest $request, Http\IResponse $response) public function saveAuthentication(IIdentity $identity): void { + $uid = (string) $identity->getId(); + if (strlen($uid) < self::MIN_LENGTH) { + throw new \LogicException('UID is too short.'); + } $this->response->setCookie( $this->cookieName, - $identity->getId(), + $uid, $this->cookieExpiration, null, $this->cookieDomain @@ -72,7 +78,7 @@ public function clearAuthentication(bool $clearIdentity): void public function getState(): array { $uid = $this->request->getCookie($this->cookieName); - $identity = is_string($uid) + $identity = is_string($uid) && strlen($uid) >= self::MIN_LENGTH ? new Nette\Security\SimpleIdentity($uid) : null; return [(bool) $identity, $identity, null]; From 5d68727d27e616e9cc7d1e8aa27d64334bd09eab Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 10 Jan 2021 03:09:19 +0100 Subject: [PATCH 165/254] User: passes to authenticator all arguments --- src/Security/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/User.php b/src/Security/User.php index c20f6db5..e4d05960 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -103,7 +103,7 @@ public function login($user, string $password = null): void } else { $authenticator = $this->getAuthenticator(); $this->identity = $authenticator instanceof Authenticator - ? $authenticator->authenticate($user, $password) + ? $authenticator->authenticate(...func_get_args()) : $authenticator->authenticate(func_get_args()); } From 12df4f177e0ab11f8757b95a61745450b1754947 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Jan 2021 02:19:36 +0100 Subject: [PATCH 166/254] events: removed magic --- composer.json | 2 +- src/Security/User.php | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 9099b614..66b7a400 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=7.2 <8.1", - "nette/utils": "^3.1" + "nette/utils": "^3.2.1" }, "require-dev": { "nette/di": "^3.0.1", diff --git a/src/Security/User.php b/src/Security/User.php index e4d05960..6f7ea9cb 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -10,6 +10,7 @@ namespace Nette\Security; use Nette; +use Nette\Utils\Arrays; /** @@ -39,10 +40,10 @@ class User public $authenticatedRole = 'authenticated'; /** @var callable[] function (User $sender): void; Occurs when the user is successfully logged in */ - public $onLoggedIn; + public $onLoggedIn = []; /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ - public $onLoggedOut; + public $onLoggedOut = []; /** @var UserStorage|IUserStorage Session storage for current user */ private $storage; @@ -119,7 +120,7 @@ public function login($user, string $password = null): void $this->authenticated = true; $this->logoutReason = null; - $this->onLoggedIn($this); + Arrays::invoke($this->onLoggedIn, $this); } @@ -129,7 +130,7 @@ public function login($user, string $password = null): void final public function logout(bool $clearIdentity = false): void { if ($this->isLoggedIn()) { - $this->onLoggedOut($this); + Arrays::invoke($this->onLoggedOut, $this); } $this->authenticated = false; From 31bbfdb2d4ac7696dac2e17dcf176577d6278490 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Jan 2021 14:58:36 +0100 Subject: [PATCH 167/254] Revert "SecurityExtension: 'roles' & 'resources' are deprecated" This reverts commit cbd4e8c3aad9c8f112a98ef005465d8e6b048310. --- composer.json | 3 +-- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- tests/Security.DI/SecurityExtension.authorizator.phpt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 66b7a400..a706ea16 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,7 @@ "phpstan/phpstan-nette": "^0.12" }, "conflict": { - "nette/di": "<3.0-stable", - "nette/schema": "<1.1" + "nette/di": "<3.0-stable" }, "autoload": { "classmap": ["src/"] diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 27cba4b2..e12fe8ad 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -42,8 +42,8 @@ public function getConfigSchema(): Nette\Schema\Schema ])->castTo('array') ) ), - 'roles' => Expect::arrayOf('string|array|null')->deprecated(), // role => parent(s) - 'resources' => Expect::arrayOf('string|null')->deprecated(), // resource => parent + 'roles' => Expect::arrayOf('string|array|null'), // role => parent(s) + 'resources' => Expect::arrayOf('string|null'), // resource => parent 'authentication' => Expect::structure([ 'storage' => Expect::anyOf('session', 'cookie')->default('session'), 'expiration' => Expect::string()->dynamic(), diff --git a/tests/Security.DI/SecurityExtension.authorizator.phpt b/tests/Security.DI/SecurityExtension.authorizator.phpt index d898082c..0f8b3b7c 100644 --- a/tests/Security.DI/SecurityExtension.authorizator.phpt +++ b/tests/Security.DI/SecurityExtension.authorizator.phpt @@ -32,7 +32,7 @@ security: article: item ', 'neon')); -@eval($compiler->addConfig($config)->compile()); // @ is deprecated +eval($compiler->addConfig($config)->compile()); $container = new Container; $authorizator = $container->getService('security.authorizator'); From 6ce3aab5853f2db00cacf618ca8595750e9bf3bb Mon Sep 17 00:00:00 2001 From: dakur Date: Tue, 12 Jan 2021 11:43:41 +0100 Subject: [PATCH 168/254] typo (#57) --- src/Security/Permission.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index f755ff7d..65670cfb 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -566,7 +566,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile * the Role are checked. * * @param string|Role|null $role - * @param string|Resource|null $resource + * @param string|Nette\Security\Resource|null $resource * @param string|null $privilege * @throws Nette\InvalidStateException */ From 817ee98aad1f122f8f40b728c35e634086e1093d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 Jan 2021 18:44:40 +0100 Subject: [PATCH 169/254] User::logout() steps reorder --- src/Security/User.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 6f7ea9cb..77b68663 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -129,12 +129,7 @@ public function login($user, string $password = null): void */ final public function logout(bool $clearIdentity = false): void { - if ($this->isLoggedIn()) { - Arrays::invoke($this->onLoggedOut, $this); - } - - $this->authenticated = false; - $this->identity = $clearIdentity ? null : $this->identity; + $logged = $this->isLoggedIn(); if ($this->storage instanceof UserStorage) { $this->storage->clearAuthentication($clearIdentity); @@ -143,8 +138,14 @@ final public function logout(bool $clearIdentity = false): void if ($clearIdentity) { $this->storage->setIdentity(null); } - $this->logoutReason = self::MANUAL; } + + $this->authenticated = false; + $this->logoutReason = self::MANUAL; + if ($logged) { + Arrays::invoke($this->onLoggedOut, $this); + } + $this->identity = $clearIdentity ? null : $this->identity; } From 71cb30b2d55d682a52e747954bc4ce68b704a4df Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 22 Jan 2021 23:57:46 +0100 Subject: [PATCH 170/254] compatibility: improved code hinting --- src/compatibility.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compatibility.php b/src/compatibility.php index f3827198..bd085db5 100644 --- a/src/compatibility.php +++ b/src/compatibility.php @@ -16,12 +16,12 @@ interface IAuthorizator extends Authorizator } /** @deprecated use Nette\Security\Resource */ - interface IResource + interface IResource extends Resource { } /** @deprecated use Nette\Security\Role */ - interface IRole + interface IRole extends Role { } } elseif (!interface_exists(IAuthorizator::class)) { From 0447a2486e78e01fa8db569e74ef09a09665dae6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Jan 2021 00:56:33 +0100 Subject: [PATCH 171/254] updated workflows --- .github/workflows/tests.yml | 6 ++++-- tests/.coveralls.yml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04f8b5a8..01c7894f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,6 +53,8 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src - - run: php coveralls.phar --verbose --config tests/.coveralls.yml + - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + - env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: php php-coveralls.phar --verbose --config tests/.coveralls.yml diff --git a/tests/.coveralls.yml b/tests/.coveralls.yml index 82764a3f..84503825 100644 --- a/tests/.coveralls.yml +++ b/tests/.coveralls.yml @@ -1,4 +1,4 @@ # for php-coveralls -service_name: travis-ci +service_name: github-actions coverage_clover: coverage.xml json_path: coverage.json From 5a3360749f13f0e620b34dc7ef1718eb581ff5b2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Jan 2021 21:15:15 +0100 Subject: [PATCH 172/254] readme: updated badge --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3fe01860..1b676569 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ Nette Security: Access Control ============================== [![Downloads this Month](https://img.shields.io/packagist/dm/nette/security.svg)](https://packagist.org/packages/nette/security) -[![Build Status](https://travis-ci.org/nette/security.svg?branch=master)](https://travis-ci.org/nette/security) +[![Tests](https://github.com/nette/security/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/security/actions) [![Coverage Status](https://coveralls.io/repos/github/nette/security/badge.svg?branch=master)](https://coveralls.io/github/nette/security?branch=master) [![Latest Stable Version](https://poser.pugx.org/nette/security/v/stable)](https://github.com/nette/security/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/security/blob/master/license.md) From 4eaa3e4c04a642cf849f2ce68244fd20d7e2dd0d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 5 Feb 2021 21:49:20 +0100 Subject: [PATCH 173/254] readme: added support me --- readme.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 1b676569..4fafc1fc 100644 --- a/readme.md +++ b/readme.md @@ -18,12 +18,21 @@ Authentication & Authorization library for Nette. - how to create custom authenticators and authorizators - Access Control List -Documentation can be found on the [website](https://doc.nette.org/access-control). If you like it, **[please make a donation now](https://github.com/sponsors/dg)**. Thank you! - -In the examples, we will use an object of class [Nette\Security\User](https://api.nette.org/3.0/Nette/Security/User.html), which represents the current user. +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.0. + +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette Security? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! + + Authentication ============== From 9127bfefc386a6b448e17537267c5e7d36d8863f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 15:41:45 +0100 Subject: [PATCH 174/254] User: added consts LOGOUT_MANUAL & LOGOUT_INACTIVITY --- src/Security/User.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Security/User.php b/src/Security/User.php index 77b68663..d5ba1a0f 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -33,6 +33,11 @@ class User MANUAL = IUserStorage::MANUAL, INACTIVITY = IUserStorage::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'; @@ -283,7 +288,7 @@ public function setExpiration($expire, /*int*/$flags = 0) /** - * Why was user logged out? + * Why was user logged out? Returns LOGOUT_MANUAL or LOGOUT_INACTIVITY. */ final public function getLogoutReason(): ?int { From 6e23b318566358b381813ae7249458457cc4dd5f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 15:25:53 +0100 Subject: [PATCH 175/254] User::setExpiration() second argument accept also bool $clearIdentity Thanks to named parameters, it is better to use a parameter than flags. --- src/Security/User.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index d5ba1a0f..1af3843f 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -260,27 +260,23 @@ final public function hasAuthenticator(): bool /** - * Enables log out after inactivity (like '20 minutes'). Accepts flag IUserStorage::CLEAR_IDENTITY. + * Enables log out after inactivity (like '20 minutes'). * @param string|null $expire - * @param int $flags + * @param int|bool $clearIdentity * @return static */ - public function setExpiration($expire, /*int*/$flags = 0) + public function setExpiration($expire, $clearIdentity = null) { - $clearIdentity = $flags === IUserStorage::CLEAR_IDENTITY; if ($expire !== null && !is_string($expire)) { trigger_error("Expiration should be a string like '20 minutes' etc.", E_USER_DEPRECATED); } - if (is_bool($flags)) { - trigger_error(__METHOD__ . '() second parameter $whenBrowserIsClosed was removed.', E_USER_DEPRECATED); - } if (func_num_args() > 2) { $clearIdentity = $clearIdentity || func_get_arg(2); - trigger_error(__METHOD__ . '() third parameter is deprecated, use flag setExpiration($time, IUserStorage::CLEAR_IDENTITY)', E_USER_DEPRECATED); + trigger_error(__METHOD__ . '() third parameter is deprecated, use second one: setExpiration($time, true|false)', E_USER_DEPRECATED); } $arg = $this->storage instanceof UserStorage - ? $clearIdentity + ? (bool) $clearIdentity : ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); $this->storage->setExpiration($expire, $arg); return $this; From 7c9edc35f63eee70c4695b6757edc4b61d7cc67c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 3 Mar 2021 22:16:32 +0100 Subject: [PATCH 176/254] fixed compatibility with Symfony DebugClassLoader --- src/compatibility.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compatibility.php b/src/compatibility.php index bd085db5..ebfd19d1 100644 --- a/src/compatibility.php +++ b/src/compatibility.php @@ -14,18 +14,24 @@ interface IAuthorizator extends Authorizator { } +} elseif (!interface_exists(IAuthorizator::class)) { + class_alias(Authorizator::class, IAuthorizator::class); +} +if (false) { /** @deprecated use Nette\Security\Resource */ interface IResource extends Resource { } +} elseif (!interface_exists(IResource::class)) { + class_alias(Resource::class, IResource::class); +} +if (false) { /** @deprecated use Nette\Security\Role */ interface IRole extends Role { } -} elseif (!interface_exists(IAuthorizator::class)) { - class_alias(Authorizator::class, IAuthorizator::class); - class_alias(Resource::class, IResource::class); +} elseif (!interface_exists(IRole::class)) { class_alias(Role::class, IRole::class); } From a7796fe14e58f023a549bb39908df21b3e49faec Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 22 Apr 2021 18:31:08 +0200 Subject: [PATCH 177/254] removed ecs.php --- ecs.php | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 ecs.php diff --git a/ecs.php b/ecs.php deleted file mode 100644 index e241616b..00000000 --- a/ecs.php +++ /dev/null @@ -1,22 +0,0 @@ -import(PRESET_DIR . '/php71.php'); - - $parameters = $containerConfigurator->parameters(); - - $parameters->set('skip', [ - // Resource typehint - PhpCsFixer\Fixer\Phpdoc\PhpdocTypesFixer::class => [ - 'src/Security/Permission.php', - ], - ]); -}; From a78bbb9d2260fffd898701b1ebdafcc797acb663 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 25 Aug 2021 02:11:48 +0200 Subject: [PATCH 178/254] support for PHP 8.1 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 01c7894f..266902db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1'] fail-fast: false diff --git a/composer.json b/composer.json index a706ea16..8ff8e8ef 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.1", + "php": ">=7.2 <8.2", "nette/utils": "^3.2.1" }, "require-dev": { diff --git a/readme.md b/readme.md index 4fafc1fc..7d51c292 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.0. +It requires PHP version 7.2 and supports PHP up to 8.1. [Support Me](https://github.com/sponsors/dg) From c120893f561b09494486c66594720b2abcb099b2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 6 Sep 2021 00:14:47 +0200 Subject: [PATCH 179/254] SessionStorage: used new sessionSection API to not start the session unless needed --- composer.json | 3 +- src/Bridges/SecurityHttp/SessionStorage.php | 63 ++++++++++----------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/composer.json b/composer.json index 8ff8e8ef..1360cda7 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "phpstan/phpstan-nette": "^0.12" }, "conflict": { - "nette/di": "<3.0-stable" + "nette/di": "<3.0-stable", + "nette/http": "<3.1.3" }, "autoload": { "classmap": ["src/"] diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 2c27fc67..dae25b0d 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -40,11 +40,11 @@ public function __construct(Session $sessionHandler) public function saveAuthentication(IIdentity $identity): void { - $section = $this->getSessionSection(true); - $section->authenticated = true; - $section->reason = null; - $section->authTime = time(); // informative value - $section->identity = $identity; + $section = $this->getSessionSection(); + $section->set('authenticated', true); + $section->set('reason', null); + $section->set('authTime', time()); // informative value + $section->set('identity', $identity); // Session Fixation defence $this->sessionHandler->regenerateId(); @@ -53,12 +53,12 @@ public function saveAuthentication(IIdentity $identity): void public function clearAuthentication(bool $clearIdentity): void { - $section = $this->getSessionSection(true); - $section->authenticated = false; - $section->reason = self::LOGOUT_MANUAL; - $section->authTime = null; + $section = $this->getSessionSection(); + $section->set('authenticated', false); + $section->set('reason', self::LOGOUT_MANUAL); + $section->set('authTime', null); if ($clearIdentity === true) { - $section->identity = null; + $section->set('identity', null); } // Session Fixation defence @@ -68,26 +68,25 @@ public function clearAuthentication(bool $clearIdentity): void public function getState(): array { - $session = $this->getSessionSection(false); - return $session - ? [(bool) $session->authenticated, $session->identity, $session->reason] + $section = $this->getSessionSection(); + return $section + ? [(bool) $section->get('authenticated'), $section->get('identity'), $section->get('reason')] : [false, null, null]; } public function setExpiration(?string $time, bool $clearIdentity = false): void { - $section = $this->getSessionSection(true); + $section = $this->getSessionSection(); if ($time) { $time = Nette\Utils\DateTime::from($time)->format('U'); - $section->expireTime = $time; - $section->expireDelta = $time - time(); - + $section->set('expireTime', $time); + $section->set('expireDelta', $time - time()); } else { - unset($section->expireTime, $section->expireDelta); + $section->remove(['expireTime', 'expireDelta']); } - $section->expireIdentity = (bool) $clearIdentity; + $section->set('expireIdentity', (bool) $clearIdentity); $section->setExpiration($time, 'foo'); // time check } @@ -118,35 +117,31 @@ public function getNamespace(): string /** * Returns and initializes $this->sessionSection. */ - protected function getSessionSection(bool $need): ?SessionSection + protected function getSessionSection(): ?SessionSection { if ($this->sessionSection !== null) { return $this->sessionSection; } - if (!$need && !$this->sessionHandler->exists()) { - return null; - } - $this->sessionSection = $section = $this->sessionHandler->getSection('Nette.Http.UserStorage/' . $this->namespace); - if (!$section->identity instanceof IIdentity || !is_bool($section->authenticated)) { + if (!$section->get('identity') instanceof IIdentity || !is_bool($section->get('authenticated'))) { $section->remove(); } - if ($section->authenticated && $section->expireDelta > 0) { // check time expiration - if ($section->expireTime < time()) { - $section->reason = self::LOGOUT_INACTIVITY; - $section->authenticated = false; - if ($section->expireIdentity) { - unset($section->identity); + if ($section->get('authenticated') && $section->get('expireDelta') > 0) { // check time expiration + if ($section->get('expireTime') < time()) { + $section->set('reason', self::LOGOUT_INACTIVITY); + $section->set('authenticated', false); + if ($section->get('expireIdentity')) { + $section->remove('identity'); } } - $section->expireTime = time() + $section->expireDelta; // sliding expiration + $section->set('expireTime', time() + $section->expireDelta); // sliding expiration } - if (!$section->authenticated) { - unset($section->expireTime, $section->expireDelta, $section->expireIdentity, $section->authTime); + if (!$section->get('authenticated')) { + $section->remove(['expireTime', 'expireDelta', 'expireIdentity', 'authTime']); } return $this->sessionSection; From ec010e7d0b8ad1eec8dad1af8bbf24bf1113f354 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Jun 2022 16:10:08 +0200 Subject: [PATCH 180/254] SimpleAuthenticator: removed dead code --- src/Security/SimpleAuthenticator.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index f7468778..7153cd91 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -47,16 +47,11 @@ public function __construct(array $userlist, array $usersRoles = [], array $user * and returns IIdentity on success or throws AuthenticationException * @throws AuthenticationException */ - public function authenticate(/*string*/ $username, string $password = null): IIdentity + public function authenticate(string $username, string $password): IIdentity { - if (is_array($username)) { - [$username, $password] = $username; // back compatibility - trigger_error(__METHOD__ . '() now accepts arguments (string $username, string $password).', E_USER_DEPRECATED); - } - foreach ($this->userlist as $name => $pass) { if (strcasecmp($name, $username) === 0) { - if ((string) $pass === (string) $password) { + if ((string) $pass === $password) { return new SimpleIdentity($name, $this->usersRoles[$name] ?? null, $this->usersData[$name] ?? []); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); From b21df5735bb157da229564bebba1d0f7c892a7f4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 02:45:42 +0100 Subject: [PATCH 181/254] cs --- src/Bridges/SecurityDI/SecurityExtension.php | 3 ++- src/Security/Permission.php | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index e12fe8ad..7e65f2f2 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -11,6 +11,7 @@ use Nette; use Nette\Schema\Expect; +use Tracy; /** @@ -138,7 +139,7 @@ public function beforeCompile() if ( $this->debugMode && - ($this->config->debugger ?? $builder->getByType(\Tracy\Bar::class)) + ($this->config->debugger ?? $builder->getByType(Tracy\Bar::class)) ) { $builder->getDefinition($this->prefix('user'))->addSetup('@Tracy\Bar::addPanel', [ new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 65670cfb..5c80e82b 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -16,8 +16,6 @@ * Access control list (ACL) functionality and privileges management. * * This solution is mostly based on Zend_Acl (c) Zend Technologies USA Inc. (https://www.zend.com), new BSD license - * - * @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc. */ class Permission implements Authorizator { From 2615d667c5b56c3169661c341f425c2525cf43ce Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 03:39:45 +0100 Subject: [PATCH 182/254] cs whitespace --- src/Bridges/SecurityDI/SecurityExtension.php | 2 ++ src/Bridges/SecurityHttp/CookieStorage.php | 1 + src/Bridges/SecurityHttp/SessionStorage.php | 2 ++ src/Bridges/SecurityTracy/UserPanel.php | 1 + src/Security/Identity.php | 1 + src/Security/Passwords.php | 1 + src/Security/Permission.php | 16 +++++++++++++++- src/Security/SimpleAuthenticator.php | 1 + src/Security/User.php | 10 ++++++++++ 9 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 7e65f2f2..b2e3b145 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -77,6 +77,7 @@ public function loadConfiguration() if ($auth->cookieDomain === 'domain') { $auth->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)'); } + $storage->addSetup('setCookieParameters', [$auth->cookieName, $auth->cookieDomain, $auth->cookieSamesite]); } @@ -117,6 +118,7 @@ public function loadConfiguration() foreach ($config->roles as $role => $parents) { $authorizator->addSetup('addRole', [$role, $parents]); } + foreach ($config->resources as $resource => $parents) { $authorizator->addSetup('addResource', [$resource, $parents]); } diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index e613718b..8acd1682 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -55,6 +55,7 @@ public function saveAuthentication(IIdentity $identity): void if (strlen($uid) < self::MIN_LENGTH) { throw new \LogicException('UID is too short.'); } + $this->response->setCookie( $this->cookieName, $uid, diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index dae25b0d..a9b6c129 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -101,6 +101,7 @@ public function setNamespace(string $namespace) $this->namespace = $namespace; $this->sessionSection = null; } + return $this; } @@ -137,6 +138,7 @@ protected function getSessionSection(): ?SessionSection $section->remove('identity'); } } + $section->set('expireTime', time() + $section->expireDelta); // sliding expiration } diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 161dee75..488757c1 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -56,6 +56,7 @@ public function getPanel(): ?string if (session_status() !== PHP_SESSION_ACTIVE) { return null; } + return Nette\Utils\Helpers::capture(function () { $user = $this->user; require __DIR__ . '/templates/UserPanel.panel.phtml'; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index e50116d7..5be78833 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -56,6 +56,7 @@ 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; } diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index b9917dba..89f512d2 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -50,6 +50,7 @@ public function hash(string $password): string if (!$hash) { throw new Nette\InvalidStateException('Computed hash is invalid. ' . error_get_last()['message']); } + return $hash; } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 5c80e82b..35bb1809 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -512,7 +512,6 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } } } - } else { // remove from the rules foreach ($resources as $resource) { foreach ($roles as $role) { @@ -520,6 +519,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile if ($rules === null) { continue; } + if (count($privileges) === 0) { if ($resource === self::ALL && $role === self::ALL) { if ($type === $rules['allPrivileges']['type']) { @@ -531,8 +531,10 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile 'byPrivilege' => [], ]; } + continue; } + if ($type === $rules['allPrivileges']['type']) { unset($rules['allPrivileges']); } @@ -548,6 +550,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } } } + return $this; } @@ -575,6 +578,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = if ($role instanceof Role) { $role = $role->getRoleId(); } + $this->checkRole($role); } @@ -583,6 +587,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = if ($resource instanceof Resource) { $resource = $resource->getResourceId(); } + $this->checkResource($resource); } @@ -602,6 +607,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = break 2; } } + if (($result = $this->getRuleType($resource, null, null)) !== null) { break; } @@ -660,6 +666,7 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) if (isset($dfs['visited'][$role])) { continue; } + if ($all) { if ($rules = $this->getRules($resource, $role)) { foreach ($rules['byPrivilege'] as $privilege2 => $rule) { @@ -667,6 +674,7 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) return self::DENY; } } + if (($type = $this->getRuleType($resource, $role, null)) !== null) { return $type; } @@ -685,6 +693,7 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) $dfs['stack'][] = $roleParent; } } + return null; } @@ -746,8 +755,10 @@ private function &getRules($resource, $role, bool $create = false): ?array if (!$create) { return $null; } + $this->rules['byResource'][$resource] = []; } + $visitor = &$this->rules['byResource'][$resource]; } @@ -756,8 +767,10 @@ private function &getRules($resource, $role, bool $create = false): ?array if (!$create) { return $null; } + $visitor['allRoles']['byPrivilege'] = []; } + return $visitor['allRoles']; } @@ -765,6 +778,7 @@ private function &getRules($resource, $role, bool $create = false): ?array if (!$create) { return $null; } + $visitor['byRole'][$role]['byPrivilege'] = []; } diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 7153cd91..154c0a18 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -58,6 +58,7 @@ public function authenticate(string $username, string $password): IIdentity } } } + throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND); } } diff --git a/src/Security/User.php b/src/Security/User.php index 1af3843f..ea86aa72 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -79,6 +79,7 @@ public function __construct( if (!$this->storage) { throw new Nette\InvalidStateException('UserStorage has not been set.'); } + $this->authenticator = $authenticator; $this->authorizator = $authorizator; } @@ -150,6 +151,7 @@ final public function logout(bool $clearIdentity = false): void if ($logged) { Arrays::invoke($this->onLoggedOut, $this); } + $this->identity = $clearIdentity ? null : $this->identity; } @@ -162,6 +164,7 @@ final public function isLoggedIn(): bool if ($this->authenticated === null) { $this->getStoredData(); } + return $this->authenticated; } @@ -174,6 +177,7 @@ final public function getIdentity(): ?IIdentity if ($this->authenticated === null) { $this->getStoredData(); } + return $this->identity; } @@ -236,9 +240,11 @@ final public function getAuthenticator(): ?IAuthenticator trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthenticatorIfExists()', E_USER_DEPRECATED); $throw = func_get_arg(0); } + if (($throw ?? true) && !$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); } + return $this->authenticator; } @@ -270,6 +276,7 @@ public function setExpiration($expire, $clearIdentity = null) 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); @@ -319,6 +326,7 @@ final public function isInRole(string $role): bool return true; } } + return false; } @@ -359,9 +367,11 @@ final public function getAuthorizator(): ?Authorizator trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getAuthorizatorIfExists()', E_USER_DEPRECATED); $throw = func_get_arg(0); } + if (($throw ?? true) && !$this->authorizator) { throw new Nette\InvalidStateException('Authorizator has not been set.'); } + return $this->authorizator; } From f2d06fe44e1bf0cbb0ea6678cba068293f9efcff Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:11:49 +0100 Subject: [PATCH 183/254] cs nullable typehints --- src/Bridges/SecurityHttp/CookieStorage.php | 6 +++--- src/Security/Identity.php | 2 +- src/Security/Permission.php | 8 ++++---- src/Security/User.php | 10 +++++----- tests/Security/MockUserStorage.legacy.php | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 8acd1682..8cb03bf6 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -93,9 +93,9 @@ public function setExpiration(?string $expire, bool $clearIdentity): void public function setCookieParameters( - string $name = null, - string $domain = null, - string $sameSite = null + ?string $name = null, + ?string $domain = null, + ?string $sameSite = null ) { $this->cookieName = $name ?? $this->cookieName; $this->cookieDomain = $domain ?? $this->cookieDomain; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 5be78833..3a78c2e5 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -36,7 +36,7 @@ class Identity implements IIdentity private $data; - public function __construct($id, $roles = null, iterable $data = null) + public function __construct($id, $roles = null, ?iterable $data = null) { $this->setId($id); $this->setRoles((array) $roles); diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 35bb1809..b825edd6 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -232,7 +232,7 @@ public function removeAllRoles() * @throws Nette\InvalidStateException * @return static */ - public function addResource(string $resource, string $parent = null) + public function addResource(string $resource, ?string $parent = null) { $this->checkResource($resource, false); @@ -391,7 +391,7 @@ public function allow( $roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, - callable $assertion = null + ?callable $assertion = null ) { $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion); return $this; @@ -411,7 +411,7 @@ public function deny( $roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, - callable $assertion = null + ?callable $assertion = null ) { $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion); return $this; @@ -456,7 +456,7 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg * @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) { // ensure that all specified Roles exist; normalize input to array of Roles or null if ($roles === self::ALL) { diff --git a/src/Security/User.php b/src/Security/User.php index ea86aa72..951e599f 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -70,10 +70,10 @@ class User public function __construct( - IUserStorage $legacyStorage = null, - IAuthenticator $authenticator = null, - Authorizator $authorizator = null, - UserStorage $storage = null + ?IUserStorage $legacyStorage = null, + ?IAuthenticator $authenticator = null, + ?Authorizator $authorizator = null, + ?UserStorage $storage = null ) { $this->storage = $storage ?? $legacyStorage; // back compatibility if (!$this->storage) { @@ -102,7 +102,7 @@ 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($user, ?string $password = null): void { $this->logout(true); if ($user instanceof IIdentity) { diff --git a/tests/Security/MockUserStorage.legacy.php b/tests/Security/MockUserStorage.legacy.php index 19848057..d4b134be 100644 --- a/tests/Security/MockUserStorage.legacy.php +++ b/tests/Security/MockUserStorage.legacy.php @@ -21,7 +21,7 @@ public function isAuthenticated(): bool } - public function setIdentity(Nette\Security\IIdentity $identity = null) + public function setIdentity(?Nette\Security\IIdentity $identity = null) { $this->identity = $identity; } From 3562c80318113fbca9b8612928aeecbcf6365335 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Jun 2022 16:06:09 +0200 Subject: [PATCH 184/254] SimpleAuthenticator: refactoring, added verifyPassword() --- src/Security/SimpleAuthenticator.php | 32 +++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 154c0a18..df8d0bad 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -20,25 +20,25 @@ class SimpleAuthenticator implements Authenticator use Nette\SmartObject; /** @var array */ - private $userlist; + private $passwords; /** @var array */ - private $usersRoles; + private $roles; /** @var array */ - private $usersData; + private $data; /** - * @param array $userlist list of pairs username => password - * @param array $usersRoles list of pairs username => role[] - * @param array $usersData list of pairs username => mixed[] + * @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 $userlist, array $usersRoles = [], array $usersData = []) + public function __construct(array $passwords, array $roles = [], array $data = []) { - $this->userlist = $userlist; - $this->usersRoles = $usersRoles; - $this->usersData = $usersData; + $this->passwords = $passwords; + $this->roles = $roles; + $this->data = $data; } @@ -49,10 +49,10 @@ public function __construct(array $userlist, array $usersRoles = [], array $user */ public function authenticate(string $username, string $password): IIdentity { - foreach ($this->userlist as $name => $pass) { + foreach ($this->passwords as $name => $pass) { if (strcasecmp($name, $username) === 0) { - if ((string) $pass === $password) { - return new SimpleIdentity($name, $this->usersRoles[$name] ?? null, $this->usersData[$name] ?? []); + if ($this->verifyPassword($password, $pass)) { + return new SimpleIdentity($name, $this->roles[$name] ?? null, $this->data[$name] ?? []); } else { throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); } @@ -61,4 +61,10 @@ public function authenticate(string $username, string $password): IIdentity throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND); } + + + protected function verifyPassword(string $password, string $passOrHash): bool + { + return $password === $passOrHash; + } } From 7c5f08a3ab8f4b6014268b1645778c62a731d3dc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 24 Jun 2022 12:32:46 +0200 Subject: [PATCH 185/254] SessionStorage::setExpiration() does not overwrite data in the session --- src/Bridges/SecurityHttp/SessionStorage.php | 31 ++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index a9b6c129..d52343bc 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -31,6 +31,12 @@ final class SessionStorage implements Nette\Security\UserStorage /** @var SessionSection */ private $sessionSection; + /** @var ?int */ + private $expireTime; + + /** @var bool */ + private $expireIdentity = false; + public function __construct(Session $sessionHandler) { @@ -45,6 +51,7 @@ public function saveAuthentication(IIdentity $identity): void $section->set('reason', null); $section->set('authTime', time()); // informative value $section->set('identity', $identity); + $this->setupExpiration(); // Session Fixation defence $this->sessionHandler->regenerateId(); @@ -77,17 +84,27 @@ public function getState(): array public function setExpiration(?string $time, bool $clearIdentity = false): void { - $section = $this->getSessionSection(); - if ($time) { - $time = Nette\Utils\DateTime::from($time)->format('U'); - $section->set('expireTime', $time); - $section->set('expireDelta', $time - time()); + $this->expireTime = $time ? Nette\Utils\DateTime::from($time)->format('U') : null; + $this->expireIdentity = $clearIdentity; + + if ($this->sessionSection && $this->sessionSection->get('authenticated')) { + $this->setupExpiration(); + } + } + + + private function setupExpiration(): void + { + $section = $this->sessionSection; + if ($this->expireTime) { + $section->set('expireTime', $this->expireTime); + $section->set('expireDelta', $this->expireTime - time()); } else { $section->remove(['expireTime', 'expireDelta']); } - $section->set('expireIdentity', (bool) $clearIdentity); - $section->setExpiration($time, 'foo'); // time check + $section->set('expireIdentity', $this->expireIdentity); + $section->setExpiration($this->expireTime, 'foo'); // time check } From 8e3c587b0343329e1fd41b900c9038b431ab4ab6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Aug 2022 22:48:14 +0200 Subject: [PATCH 186/254] CookieStorage: fixed SameSite attribute --- src/Bridges/SecurityHttp/CookieStorage.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 8cb03bf6..3a224ee0 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -61,7 +61,10 @@ public function saveAuthentication(IIdentity $identity): void $uid, $this->cookieExpiration, null, - $this->cookieDomain + $this->cookieDomain, + null, + true, + $this->cookieSameSite ); } From f22d677f330c0bf6a4878bfadee8390f13611ddb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Sep 2022 03:02:37 +0200 Subject: [PATCH 187/254] support for PHP 8.2 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 266902db..e87ca57d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] fail-fast: false diff --git a/composer.json b/composer.json index 1360cda7..283f85a7 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.2", + "php": ">=7.2 <8.3", "nette/utils": "^3.2.1" }, "require-dev": { diff --git a/readme.md b/readme.md index 7d51c292..8bfa4d96 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.1. +It requires PHP version 7.2 and supports PHP up to 8.2. [Support Me](https://github.com/sponsors/dg) From be191769301511e6d8d77e174dca4a9521b98941 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 6 Sep 2022 03:05:57 +0200 Subject: [PATCH 188/254] strict type fix --- src/Bridges/SecurityHttp/SessionStorage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index d52343bc..87f77efa 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -84,7 +84,7 @@ public function getState(): array public function setExpiration(?string $time, bool $clearIdentity = false): void { - $this->expireTime = $time ? Nette\Utils\DateTime::from($time)->format('U') : null; + $this->expireTime = $time ? (int) Nette\Utils\DateTime::from($time)->format('U') : null; $this->expireIdentity = $clearIdentity; if ($this->sessionSection && $this->sessionSection->get('authenticated')) { @@ -104,7 +104,7 @@ private function setupExpiration(): void } $section->set('expireIdentity', $this->expireIdentity); - $section->setExpiration($this->expireTime, 'foo'); // time check + $section->setExpiration((string) $this->expireTime, 'foo'); // time check } From edd6e31a63ac06a5d6e2a8e6088e6e8cf8d216bc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 6 Oct 2022 18:37:03 +0200 Subject: [PATCH 189/254] tests: added CookieStorage tests --- composer.json | 3 +- .../CookieStorage.authentication.phpt | 28 ++++++++++++++++ .../Security.Http/CookieStorage.getState.phpt | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/Security.Http/CookieStorage.authentication.phpt create mode 100644 tests/Security.Http/CookieStorage.getState.phpt diff --git a/composer.json b/composer.json index 283f85a7..5d18b252 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "nette/http": "^3.0.0", "nette/tester": "^2.0", "tracy/tracy": "^2.4", - "phpstan/phpstan-nette": "^0.12" + "phpstan/phpstan-nette": "^1.0", + "mockery/mockery": "^1.5" }, "conflict": { "nette/di": "<3.0-stable", diff --git a/tests/Security.Http/CookieStorage.authentication.phpt b/tests/Security.Http/CookieStorage.authentication.phpt new file mode 100644 index 00000000..669cc6c2 --- /dev/null +++ b/tests/Security.Http/CookieStorage.authentication.phpt @@ -0,0 +1,28 @@ +saveAuthentication(new SimpleIdentity('short')); +}, LogicException::class); + +// correct id +$id = '123456789123456'; +$response->expects()->setCookie('userid', $id, null, null, null, false, true, 'Lax'); +$storage->saveAuthentication(new SimpleIdentity($id)); +Mockery::close(); + +// clear id +$response->expects()->deleteCookie('userid', null, null); +$storage->clearAuthentication(true); +Mockery::close(); diff --git a/tests/Security.Http/CookieStorage.getState.phpt b/tests/Security.Http/CookieStorage.getState.phpt new file mode 100644 index 00000000..148a0102 --- /dev/null +++ b/tests/Security.Http/CookieStorage.getState.phpt @@ -0,0 +1,32 @@ +getState()); + +// short id +$request = new Nette\Http\Request(new Nette\Http\UrlScript, [], [], ['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]); +$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]); +$storage = new CookieStorage($request, $response); +$storage->setCookieParameters('foo'); +Assert::equal([true, new SimpleIdentity($id), null], $storage->getState()); From 587c777d36dca70dbeb1cc7e89d36a4e81ef3078 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 6 Oct 2022 18:14:20 +0200 Subject: [PATCH 190/254] CookieStorage: getState returns the previously set ID [Closes #67] --- src/Bridges/SecurityHttp/CookieStorage.php | 18 +++++++++++++----- .../CookieStorage.authentication.phpt | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 3a224ee0..0ee6e8a2 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -29,6 +29,9 @@ final class CookieStorage implements Nette\Security\UserStorage /** @var Http\IResponse */ private $response; + /** @var ?string */ + private $uid; + /** @var string */ private $cookieName = 'userid'; @@ -56,6 +59,7 @@ public function saveAuthentication(IIdentity $identity): void throw new \LogicException('UID is too short.'); } + $this->uid = $uid; $this->response->setCookie( $this->cookieName, $uid, @@ -71,6 +75,7 @@ public function saveAuthentication(IIdentity $identity): void public function clearAuthentication(bool $clearIdentity): void { + $this->uid = ''; $this->response->deleteCookie( $this->cookieName, null, @@ -81,11 +86,14 @@ public function clearAuthentication(bool $clearIdentity): void public function getState(): array { - $uid = $this->request->getCookie($this->cookieName); - $identity = is_string($uid) && strlen($uid) >= self::MIN_LENGTH - ? new Nette\Security\SimpleIdentity($uid) - : null; - return [(bool) $identity, $identity, null]; + if ($this->uid === null) { + $uid = $this->request->getCookie($this->cookieName); + $this->uid = is_string($uid) && strlen($uid) >= self::MIN_LENGTH ? $uid : ''; + } + + return $this->uid + ? [true, new Nette\Security\SimpleIdentity($this->uid), null] + : [false, null, null]; } diff --git a/tests/Security.Http/CookieStorage.authentication.phpt b/tests/Security.Http/CookieStorage.authentication.phpt index 669cc6c2..8debb1ae 100644 --- a/tests/Security.Http/CookieStorage.authentication.phpt +++ b/tests/Security.Http/CookieStorage.authentication.phpt @@ -20,9 +20,11 @@ Assert::exception(function () use ($storage) { $id = '123456789123456'; $response->expects()->setCookie('userid', $id, null, null, null, false, true, 'Lax'); $storage->saveAuthentication(new SimpleIdentity($id)); +Assert::equal([true, new SimpleIdentity($id), null], $storage->getState()); Mockery::close(); // clear id $response->expects()->deleteCookie('userid', null, null); $storage->clearAuthentication(true); +Assert::same([false, null, null], $storage->getState()); Mockery::close(); From 77a20cac539d62f5bcde15cd29244ae13890b483 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 13 Oct 2022 00:34:14 +0200 Subject: [PATCH 191/254] updated github workflow --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index a5618ecd..d8020f6f 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,7 +7,7 @@ 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 @@ -21,7 +21,7 @@ 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 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0ae1a544..08f1414a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ 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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e87ca57d..6890f4fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -32,7 +32,7 @@ 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 @@ -46,7 +46,7 @@ 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 From 95d1a868bab6e9403eaac404283dc0abf7dc4443 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 13 Oct 2022 00:34:14 +0200 Subject: [PATCH 192/254] updated github workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6890f4fe..23cfd728 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 From c3e6193c027bf6dc2bf7d7cb4bc07ba4926aadd1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 21 Dec 2022 01:45:17 +0100 Subject: [PATCH 193/254] updated .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3aa6270a..9670e954 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,7 @@ .gitattributes export-ignore .gitignore export-ignore .github export-ignore -.travis.yml export-ignore -ecs.php export-ignore +ncs.* export-ignore phpstan.neon export-ignore tests/ export-ignore From 4c5f0435fe8534ba5a7e2eac595b8a777cc7fff1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 9 Jan 2023 21:22:12 +0100 Subject: [PATCH 194/254] composer: allows nette/utils 4.0 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5d18b252..11779aa3 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=7.2 <8.3", - "nette/utils": "^3.2.1" + "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { "nette/di": "^3.0.1", @@ -24,7 +24,7 @@ "nette/tester": "^2.0", "tracy/tracy": "^2.4", "phpstan/phpstan-nette": "^1.0", - "mockery/mockery": "^1.5" + "mockery/mockery": "^1.3.6" }, "conflict": { "nette/di": "<3.0-stable", From cabcbad3b0e98d50ffeb161d2b85290b5f7d1a66 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 27 Feb 2023 22:31:26 +0100 Subject: [PATCH 195/254] used #[\SensitiveParameter] to mark sensitive parameters --- src/Security/Passwords.php | 11 +++++++++-- src/Security/SimpleAuthenticator.php | 14 +++++++++++--- src/Security/User.php | 6 +++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 89f512d2..476514b3 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -40,7 +40,10 @@ public function __construct($algo = PASSWORD_DEFAULT, array $options = []) /** * Computes password´s hash. The result contains the algorithm ID and its settings, cryptographical salt and the hash itself. */ - public function hash(string $password): string + public function hash( + #[\SensitiveParameter] + string $password + ): string { if ($password === '') { throw new Nette\InvalidArgumentException('Password can not be empty.'); @@ -58,7 +61,11 @@ public function hash(string $password): string /** * Finds out, whether the given password matches the given hash. */ - public function verify(string $password, string $hash): bool + public function verify( + #[\SensitiveParameter] + string $password, + string $hash + ): bool { return password_verify($password, $hash); } diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index df8d0bad..7100c647 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -34,8 +34,12 @@ class SimpleAuthenticator implements Authenticator * @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 = []) - { + public function __construct( + #[\SensitiveParameter] + array $passwords, + array $roles = [], + array $data = [] + ) { $this->passwords = $passwords; $this->roles = $roles; $this->data = $data; @@ -47,7 +51,11 @@ public function __construct(array $passwords, array $roles = [], array $data = [ * and returns IIdentity on success or throws AuthenticationException * @throws AuthenticationException */ - public function authenticate(string $username, string $password): IIdentity + public function authenticate( + string $username, + #[\SensitiveParameter] + string $password + ): IIdentity { foreach ($this->passwords as $name => $pass) { if (strcasecmp($name, $username) === 0) { diff --git a/src/Security/User.php b/src/Security/User.php index 951e599f..df2b7b3f 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -102,7 +102,11 @@ 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( + $user, + #[\SensitiveParameter] + ?string $password = null + ): void { $this->logout(true); if ($user instanceof IIdentity) { From caadca87660a2272a178f255271d8a5a2579045a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Nov 2022 22:32:30 +0100 Subject: [PATCH 196/254] constants are PascalCase --- readme.md | 2 +- src/Bridges/SecurityHttp/CookieStorage.php | 6 +- src/Bridges/SecurityHttp/SessionStorage.php | 4 +- src/Security/Authenticator.php | 7 ++ src/Security/Authorizator.php | 10 ++- src/Security/Permission.php | 72 ++++++++++----------- src/Security/SimpleAuthenticator.php | 4 +- src/Security/User.php | 9 ++- src/Security/UserStorage.php | 7 +- tests/Security/User.authentication.phpt | 4 +- tests/Security/User.authorization.phpt | 6 +- 11 files changed, 74 insertions(+), 57 deletions(-) diff --git a/readme.md b/readme.md index 8bfa4d96..ece7167c 100644 --- a/readme.md +++ b/readme.md @@ -305,7 +305,7 @@ $acl->allow('guest', 'poll', 'vote'); $acl->allow('registered', 'comment', 'add'); // the administrator can view and edit anything -$acl->allow('administrator', $acl::ALL, ['view', 'edit', 'add']); +$acl->allow('administrator', $acl::All, ['view', 'edit', 'add']); ``` What if we want to **prevent** someone from accessing a resource? diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 0ee6e8a2..12764efd 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -21,7 +21,7 @@ final class CookieStorage implements Nette\Security\UserStorage { use Nette\SmartObject; - private const MIN_LENGTH = 13; + private const MinLength = 13; /** @var Http\IRequest */ private $request; @@ -55,7 +55,7 @@ public function __construct(Http\IRequest $request, Http\IResponse $response) public function saveAuthentication(IIdentity $identity): void { $uid = (string) $identity->getId(); - if (strlen($uid) < self::MIN_LENGTH) { + if (strlen($uid) < self::MinLength) { throw new \LogicException('UID is too short.'); } @@ -88,7 +88,7 @@ public function getState(): array { if ($this->uid === null) { $uid = $this->request->getCookie($this->cookieName); - $this->uid = is_string($uid) && strlen($uid) >= self::MIN_LENGTH ? $uid : ''; + $this->uid = is_string($uid) && strlen($uid) >= self::MinLength ? $uid : ''; } return $this->uid diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 87f77efa..c1d2d491 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -62,7 +62,7 @@ public function clearAuthentication(bool $clearIdentity): void { $section = $this->getSessionSection(); $section->set('authenticated', false); - $section->set('reason', self::LOGOUT_MANUAL); + $section->set('reason', self::LogoutManual); $section->set('authTime', null); if ($clearIdentity === true) { $section->set('identity', null); @@ -149,7 +149,7 @@ protected function getSessionSection(): ?SessionSection if ($section->get('authenticated') && $section->get('expireDelta') > 0) { // check time expiration if ($section->get('expireTime') < time()) { - $section->set('reason', self::LOGOUT_INACTIVITY); + $section->set('reason', self::LogoutInactivity); $section->set('authenticated', false); if ($section->get('expireIdentity')) { $section->remove('identity'); diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php index fc40ea3c..d79e6364 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -15,6 +15,13 @@ */ interface Authenticator extends IAuthenticator { + /** Exception error code */ + public const + IdentityNotFound = 1, + InvalidCredential = 2, + Failure = 3, + NotApproved = 4; + /** * Performs an authentication. * @throws AuthenticationException diff --git a/src/Security/Authorizator.php b/src/Security/Authorizator.php index 085a8825..267fcccd 100644 --- a/src/Security/Authorizator.php +++ b/src/Security/Authorizator.php @@ -17,13 +17,17 @@ interface Authorizator { /** Set type: all */ - public const ALL = null; + public const All = null; /** Permission type: allow */ - public const ALLOW = true; + public const Allow = true; /** Permission type: deny */ - public const DENY = false; + public const Deny = false; + + public const ALL = self::All; + public const ALLOW = self::Allow; + public const DENY = self::Deny; /** * Performs a role-based authorization. diff --git a/src/Security/Permission.php b/src/Security/Permission.php index b825edd6..d1a801bc 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -32,7 +32,7 @@ class Permission implements Authorizator 'allResources' => [ 'allRoles' => [ 'allPrivileges' => [ - 'type' => self::DENY, + 'type' => self::Deny, 'assert' => null, ], 'byPrivilege' => [], @@ -388,12 +388,12 @@ public function removeAllResources() * @return static */ public function allow( - $roles = self::ALL, - $resources = self::ALL, - $privileges = self::ALL, + $roles = self::All, + $resources = self::All, + $privileges = self::All, ?callable $assertion = null ) { - $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion); + $this->setRule(true, self::Allow, $roles, $resources, $privileges, $assertion); return $this; } @@ -408,12 +408,12 @@ public function allow( * @return static */ public function deny( - $roles = self::ALL, - $resources = self::ALL, - $privileges = self::ALL, + $roles = self::All, + $resources = self::All, + $privileges = self::All, ?callable $assertion = null ) { - $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion); + $this->setRule(true, self::Deny, $roles, $resources, $privileges, $assertion); return $this; } @@ -426,9 +426,9 @@ public function deny( * @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) { - $this->setRule(false, self::ALLOW, $roles, $resources, $privileges); + $this->setRule(false, self::Allow, $roles, $resources, $privileges); return $this; } @@ -441,9 +441,9 @@ public function removeAllow($roles = self::ALL, $resources = self::ALL, $privile * @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) { - $this->setRule(false, self::DENY, $roles, $resources, $privileges); + $this->setRule(false, self::Deny, $roles, $resources, $privileges); return $this; } @@ -459,8 +459,8 @@ public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileg protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privileges, ?callable $assertion = null) { // ensure that all specified Roles exist; normalize input to array of Roles or null - if ($roles === self::ALL) { - $roles = [self::ALL]; + if ($roles === self::All) { + $roles = [self::All]; } else { if (!is_array($roles)) { @@ -473,8 +473,8 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } // ensure that all specified Resources exist; normalize input to array of Resources or null - if ($resources === self::ALL) { - $resources = [self::ALL]; + if ($resources === self::All) { + $resources = [self::All]; } else { if (!is_array($resources)) { @@ -487,7 +487,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } // normalize privileges to array - if ($privileges === self::ALL) { + if ($privileges === self::All) { $privileges = []; } elseif (!is_array($privileges)) { @@ -521,11 +521,11 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } if (count($privileges) === 0) { - if ($resource === self::ALL && $role === self::ALL) { + if ($resource === self::All && $role === self::All) { if ($type === $rules['allPrivileges']['type']) { $rules = [ 'allPrivileges' => [ - 'type' => self::DENY, + 'type' => self::Deny, 'assert' => null, ], 'byPrivilege' => [], @@ -571,10 +571,10 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile * @param string|null $privilege * @throws Nette\InvalidStateException */ - public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool + public function isAllowed($role = self::All, $resource = self::All, $privilege = self::All): bool { $this->queriedRole = $role; - if ($role !== self::ALL) { + if ($role !== self::All) { if ($role instanceof Role) { $role = $role->getRoleId(); } @@ -583,7 +583,7 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = } $this->queriedResource = $resource; - if ($resource !== self::ALL) { + if ($resource !== self::All) { if ($resource instanceof Resource) { $resource = $resource->getResourceId(); } @@ -595,15 +595,15 @@ public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = // depth-first search on $role if it is not 'allRoles' pseudo-parent if ( $role !== null - && ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege)) !== null + && ($result = $this->searchRolePrivileges($privilege === self::All, $role, $resource, $privilege)) !== null ) { break; } - if ($privilege === self::ALL) { - if ($rules = $this->getRules($resource, self::ALL)) { // look for rule on 'allRoles' psuedo-parent + if ($privilege === self::All) { + if ($rules = $this->getRules($resource, self::All)) { // look for rule on 'allRoles' psuedo-parent foreach ($rules['byPrivilege'] as $privilege => $rule) { - if (($result = $this->getRuleType($resource, null, $privilege)) === self::DENY) { + if (($result = $this->getRuleType($resource, null, $privilege)) === self::Deny) { break 2; } } @@ -670,8 +670,8 @@ private function searchRolePrivileges(bool $all, $role, $resource, $privilege) if ($all) { if ($rules = $this->getRules($resource, $role)) { foreach ($rules['byPrivilege'] as $privilege2 => $rule) { - if ($this->getRuleType($resource, $role, $privilege2) === self::DENY) { - return self::DENY; + if ($this->getRuleType($resource, $role, $privilege2) === self::Deny) { + return self::Deny; } } @@ -711,7 +711,7 @@ private function getRuleType($resource, $role, $privilege): ?bool return null; } - if ($privilege === self::ALL) { + if ($privilege === self::All) { if (isset($rules['allPrivileges'])) { $rule = $rules['allPrivileges']; } else { @@ -727,14 +727,14 @@ private function getRuleType($resource, $role, $privilege): ?bool if ($rule['assert'] === null || $rule['assert']($this, $role, $resource, $privilege)) { return $rule['type']; - } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) { + } elseif ($resource !== self::All || $role !== self::All || $privilege !== self::All) { return null; - } elseif ($rule['type'] === self::ALLOW) { - return self::DENY; + } elseif ($rule['type'] === self::Allow) { + return self::Deny; } else { - return self::ALLOW; + return self::Allow; } } @@ -748,7 +748,7 @@ private function getRuleType($resource, $role, $privilege): ?bool private function &getRules($resource, $role, bool $create = false): ?array { $null = null; - if ($resource === self::ALL) { + if ($resource === self::All) { $visitor = &$this->rules['allResources']; } else { if (!isset($this->rules['byResource'][$resource])) { @@ -762,7 +762,7 @@ private function &getRules($resource, $role, bool $create = false): ?array $visitor = &$this->rules['byResource'][$resource]; } - if ($role === self::ALL) { + if ($role === self::All) { if (!isset($visitor['allRoles'])) { if (!$create) { return $null; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 7100c647..cbf2aae8 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -62,12 +62,12 @@ public function authenticate( if ($this->verifyPassword($password, $pass)) { return new SimpleIdentity($name, $this->roles[$name] ?? null, $this->data[$name] ?? []); } else { - throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL); + throw new AuthenticationException('Invalid password.', self::InvalidCredential); } } } - throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND); + throw new AuthenticationException("User '$username' not found.", self::IdentityNotFound); } diff --git a/src/Security/User.php b/src/Security/User.php index df2b7b3f..10dafe72 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -35,8 +35,11 @@ class User /** Log-out reason */ public const - LOGOUT_MANUAL = UserStorage::LOGOUT_MANUAL, - LOGOUT_INACTIVITY = UserStorage::LOGOUT_INACTIVITY; + LogoutManual = UserStorage::LogoutManual, + LogoutInactivity = UserStorage::LogoutInactivity; + + public const LOGOUT_MANUAL = self::LogoutManual; + public const LOGOUT_INACTIVITY = self::LogoutInactivity; /** @var string default role for unauthenticated user */ public $guestRole = 'guest'; @@ -339,7 +342,7 @@ final public function isInRole(string $role): bool * Has a user effective access to the Resource? * If $resource is null, then the query applies to all resources. */ - public function isAllowed($resource = Authorizator::ALL, $privilege = Authorizator::ALL): bool + public function isAllowed($resource = Authorizator::All, $privilege = Authorizator::All): bool { foreach ($this->getRoles() as $role) { if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) { diff --git a/src/Security/UserStorage.php b/src/Security/UserStorage.php index 7abedac4..1b5e97e3 100644 --- a/src/Security/UserStorage.php +++ b/src/Security/UserStorage.php @@ -17,8 +17,11 @@ interface UserStorage { /** Log-out reason */ public const - LOGOUT_MANUAL = 1, - LOGOUT_INACTIVITY = 2; + LogoutManual = 1, + LogoutInactivity = 2; + + public const LOGOUT_MANUAL = self::LogoutManual; + public const LOGOUT_INACTIVITY = self::LogoutInactivity; /** * Sets the authenticated state of user. diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index b66f7f1c..9a94a8db 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -24,10 +24,10 @@ class Authenticator implements Nette\Security\Authenticator public function authenticate(string $username, string $password): IIdentity { if ($username !== 'john') { - throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); + throw new Nette\Security\AuthenticationException('Unknown user', self::IdentityNotFound); } elseif ($password !== 'xxx') { - throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); + throw new Nette\Security\AuthenticationException('Password not match', self::InvalidCredential); } else { return new SimpleIdentity('John Doe', 'admin'); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 9061096d..a0c48ab2 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -26,10 +26,10 @@ class Authenticator implements Nette\Security\Authenticator public function authenticate(string $username, string $password): IIdentity { if ($username !== 'john') { - throw new Nette\Security\AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND); + throw new Nette\Security\AuthenticationException('Unknown user', self::IdentityNotFound); } elseif ($password !== 'xxx') { - throw new Nette\Security\AuthenticationException('Password not match', self::INVALID_CREDENTIAL); + throw new Nette\Security\AuthenticationException('Password not match', self::InvalidCredential); } else { return new SimpleIdentity('John Doe', ['admin', new TesterRole]); @@ -40,7 +40,7 @@ class Authenticator implements Nette\Security\Authenticator class Authorizator implements Nette\Security\Authorizator { - public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL): bool + public function isAllowed($role = self::All, $resource = self::All, $privilege = self::All): bool { return $role === 'admin' && strpos($resource, 'jany') === false; } From 4fe02122ec0e86f1cae333dadc6dcd09c7999c85 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Jul 2023 16:13:56 +0200 Subject: [PATCH 197/254] support for PHP 8.3 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 23cfd728..cf1784de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] fail-fast: false diff --git a/composer.json b/composer.json index 11779aa3..16f8e372 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.3", + "php": "7.2 - 8.3", "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { diff --git a/readme.md b/readme.md index ece7167c..74854f9c 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 7.2 and supports PHP up to 8.3. [Support Me](https://github.com/sponsors/dg) From 9b8e5c76b2e738350498470c35a36a6f0d0e38d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 11 Mar 2021 21:55:20 +0100 Subject: [PATCH 198/254] removed community health files --- .github/ISSUE_TEMPLATE/Bug_report.md | 19 ------------- .github/ISSUE_TEMPLATE/Feature_request.md | 9 ------ .github/ISSUE_TEMPLATE/Support_question.md | 12 -------- .github/ISSUE_TEMPLATE/Support_us.md | 21 -------------- .github/funding.yml | 2 -- .github/pull_request_template.md | 11 -------- contributing.md | 33 ---------------------- 7 files changed, 107 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_question.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_us.md delete mode 100644 .github/funding.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 contributing.md 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/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: From 151890b8943b8c16598a1c4e08891ab7a1d32e13 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Dec 2023 13:39:55 +0100 Subject: [PATCH 199/254] User: source of constants for logout reasons --- readme.md | 2 +- src/Bridges/SecurityHttp/SessionStorage.php | 5 +++-- src/Security/User.php | 14 +++++++------- src/Security/UserStorage.php | 10 ++++------ 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/readme.md b/readme.md index 74854f9c..5324ad92 100644 --- a/readme.md +++ b/readme.md @@ -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\User::LogoutInactivity` if the time expired or `User::LogoutManual` when the `logout()` method was called. In presenters, you can verify login in the `startup()` method: diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index c1d2d491..4da9f63c 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -13,6 +13,7 @@ use Nette\Http\Session; use Nette\Http\SessionSection; use Nette\Security\IIdentity; +use Nette\Security\User; /** @@ -62,7 +63,7 @@ public function clearAuthentication(bool $clearIdentity): void { $section = $this->getSessionSection(); $section->set('authenticated', false); - $section->set('reason', self::LogoutManual); + $section->set('reason', User::LogoutManual); $section->set('authTime', null); if ($clearIdentity === true) { $section->set('identity', null); @@ -149,7 +150,7 @@ protected function getSessionSection(): ?SessionSection if ($section->get('authenticated') && $section->get('expireDelta') > 0) { // check time expiration if ($section->get('expireTime') < time()) { - $section->set('reason', self::LogoutInactivity); + $section->set('reason', User::LogoutInactivity); $section->set('authenticated', false); if ($section->get('expireIdentity')) { $section->remove('identity'); diff --git a/src/Security/User.php b/src/Security/User.php index 10dafe72..77ad9f9d 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -28,15 +28,15 @@ class User { use Nette\SmartObject; - /** @deprecated */ + /** Log-out reason */ public const - MANUAL = IUserStorage::MANUAL, - INACTIVITY = IUserStorage::INACTIVITY; + LogoutManual = 1, + LogoutInactivity = 2; - /** Log-out reason */ + /** @deprecated */ public const - LogoutManual = UserStorage::LogoutManual, - LogoutInactivity = UserStorage::LogoutInactivity; + MANUAL = self::LogoutManual, + INACTIVITY = self::LogoutInactivity; public const LOGOUT_MANUAL = self::LogoutManual; public const LOGOUT_INACTIVITY = self::LogoutInactivity; @@ -154,7 +154,7 @@ final public function logout(bool $clearIdentity = false): void } $this->authenticated = false; - $this->logoutReason = self::MANUAL; + $this->logoutReason = self::LogoutManual; if ($logged) { Arrays::invoke($this->onLoggedOut, $this); } diff --git a/src/Security/UserStorage.php b/src/Security/UserStorage.php index 1b5e97e3..8ed6a922 100644 --- a/src/Security/UserStorage.php +++ b/src/Security/UserStorage.php @@ -15,13 +15,11 @@ */ interface UserStorage { - /** Log-out reason */ - public const - LogoutManual = 1, - LogoutInactivity = 2; + /** @deprecated use User::LogoutManual */ + public const LOGOUT_MANUAL = 1; - public const LOGOUT_MANUAL = self::LogoutManual; - public const LOGOUT_INACTIVITY = self::LogoutInactivity; + /** @deprecated use User::LogoutInactivity */ + public const LOGOUT_INACTIVITY = 2; /** * Sets the authenticated state of user. From 6afc6c3f0443999718ffdc9871399dc7f823fc86 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 18:36:34 +0200 Subject: [PATCH 200/254] readme: updated --- readme.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 5324ad92..c414ed90 100644 --- a/readme.md +++ b/readme.md @@ -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/security/master/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/master/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/master/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/security/authentication) From 72a64c89775ed472d88947e5c8cc2c2d8a80e5ef Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 21 Jan 2024 22:31:25 +0100 Subject: [PATCH 201/254] typo --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index b2e3b145..0013c279 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -58,7 +58,7 @@ public function getConfigSchema(): Nette\Schema\Schema public function loadConfiguration() { - /** @var object{debugger: bool, users: array, roles: array, resources: array} $config */ + /** @var object{debugger: bool, users: array, roles: array, resources: array, authentication: \stdClass} $config */ $config = $this->config; $builder = $this->getContainerBuilder(); From 1acee37ba00032c6fb04f94a65cdfd74bcd7a6f3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:30:34 +0100 Subject: [PATCH 202/254] opened 3.2-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 16f8e372..8a0e94ca 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } } } From e9d3e31a3eedd18729d08eb95a6f1ec3d4975828 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 17:38:22 +0100 Subject: [PATCH 203/254] requires PHP 8.1 --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- composer.json | 2 +- readme.md | 2 +- tests/Security/Passwords.hash().phpt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index d8020f6f..97895fbe 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.1 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 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 08f1414a..d385b9c2 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf1784de..144b3690 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php: ['8.1', '8.2', '8.3'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.1 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 8a0e94ca..36846405 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "7.2 - 8.3", + "php": "8.1 - 8.3", "nette/utils": "^3.2.1 || ~4.0.0" }, "require-dev": { diff --git a/readme.md b/readme.md index c414ed90..47b2f6d3 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.3. +It requires PHP version 8.1 and supports PHP up to 8.3. [Support Me](https://github.com/sponsors/dg) diff --git a/tests/Security/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index c8b7d376..9aa99303 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -26,7 +26,7 @@ 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); +}, ValueError::class); Assert::exception(function () { (new Passwords)->hash(''); From b9d064017cc9ab985ca619a8f51a7104eeb54439 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 16:43:32 +0100 Subject: [PATCH 204/254] composer: updated dependencies --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 36846405..2f352bdc 100644 --- a/composer.json +++ b/composer.json @@ -16,15 +16,15 @@ ], "require": { "php": "8.1 - 8.3", - "nette/utils": "^3.2.1 || ~4.0.0" + "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": "^3.1", + "nette/http": "^3.2", + "nette/tester": "^2.5", + "tracy/tracy": "^2.9", "phpstan/phpstan-nette": "^1.0", - "mockery/mockery": "^1.3.6" + "mockery/mockery": "^1.5" }, "conflict": { "nette/di": "<3.0-stable", From 69df236333dcaf85fbb470613cca03156dd11779 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:12:40 +0100 Subject: [PATCH 205/254] coding style --- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- src/Bridges/SecurityHttp/CookieStorage.php | 6 +++--- src/Security/Passwords.php | 4 ++-- src/Security/Permission.php | 14 +++++++------- src/Security/SimpleAuthenticator.php | 4 ++-- src/Security/User.php | 4 ++-- tests/Security.Http/CookieStorage.getState.phpt | 6 +++--- tests/Security/Passwords.hash().phpt | 4 ++-- tests/Security/Permission.ResourceInherits.phpt | 6 +++--- .../Security/Permission.RoleRegistryInherits.phpt | 6 +++--- tests/Security/User.authorization.phpt | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 0013c279..cf762ef3 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -40,8 +40,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 diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 12764efd..48ba18a2 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -68,7 +68,7 @@ public function saveAuthentication(IIdentity $identity): void $this->cookieDomain, null, true, - $this->cookieSameSite + $this->cookieSameSite, ); } @@ -79,7 +79,7 @@ public function clearAuthentication(bool $clearIdentity): void $this->response->deleteCookie( $this->cookieName, null, - $this->cookieDomain + $this->cookieDomain, ); } @@ -106,7 +106,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/Security/Passwords.php b/src/Security/Passwords.php index 476514b3..4c87a06a 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -42,7 +42,7 @@ public function __construct($algo = PASSWORD_DEFAULT, array $options = []) */ public function hash( #[\SensitiveParameter] - string $password + string $password, ): string { if ($password === '') { @@ -64,7 +64,7 @@ public function hash( public function verify( #[\SensitiveParameter] string $password, - string $hash + string $hash, ): bool { return password_verify($password, $hash); diff --git a/src/Security/Permission.php b/src/Security/Permission.php index d1a801bc..0a6b9e42 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -61,7 +61,7 @@ class Permission implements Authorizator */ public function addRole(string $role, $parents = null) { - $this->checkRole($role, false); + $this->checkRole($role, exists: false); if (isset($this->roles[$role])) { throw new Nette\InvalidStateException("Role '$role' already exists in the list."); } @@ -94,7 +94,7 @@ public function addRole(string $role, $parents = null) */ public function hasRole(string $role): bool { - $this->checkRole($role, false); + $this->checkRole($role, exists: false); return isset($this->roles[$role]); } @@ -234,7 +234,7 @@ public function removeAllRoles() */ public function addResource(string $resource, ?string $parent = null) { - $this->checkResource($resource, false); + $this->checkResource($resource, exists: false); if (isset($this->resources[$resource])) { throw new Nette\InvalidStateException("Resource '$resource' already exists in the list."); @@ -259,7 +259,7 @@ public function addResource(string $resource, ?string $parent = null) */ public function hasResource(string $resource): bool { - $this->checkResource($resource, false); + $this->checkResource($resource, exists: false); return isset($this->resources[$resource]); } @@ -391,7 +391,7 @@ public function allow( $roles = self::All, $resources = self::All, $privileges = self::All, - ?callable $assertion = null + ?callable $assertion = null, ) { $this->setRule(true, self::Allow, $roles, $resources, $privileges, $assertion); return $this; @@ -411,7 +411,7 @@ public function deny( $roles = self::All, $resources = self::All, $privileges = self::All, - ?callable $assertion = null + ?callable $assertion = null, ) { $this->setRule(true, self::Deny, $roles, $resources, $privileges, $assertion); return $this; @@ -497,7 +497,7 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile if ($toAdd) { // add to the rules foreach ($resources as $resource) { foreach ($roles as $role) { - $rules = &$this->getRules($resource, $role, true); + $rules = &$this->getRules($resource, $role, create: true); if (count($privileges) === 0) { $rules['allPrivileges']['type'] = $type; $rules['allPrivileges']['assert'] = $assertion; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index cbf2aae8..b1e05bba 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -38,7 +38,7 @@ public function __construct( #[\SensitiveParameter] array $passwords, array $roles = [], - array $data = [] + array $data = [], ) { $this->passwords = $passwords; $this->roles = $roles; @@ -54,7 +54,7 @@ public function __construct( public function authenticate( string $username, #[\SensitiveParameter] - string $password + string $password, ): IIdentity { foreach ($this->passwords as $name => $pass) { diff --git a/src/Security/User.php b/src/Security/User.php index 77ad9f9d..64fae555 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -76,7 +76,7 @@ public function __construct( ?IUserStorage $legacyStorage = null, ?IAuthenticator $authenticator = null, ?Authorizator $authorizator = null, - ?UserStorage $storage = null + ?UserStorage $storage = null, ) { $this->storage = $storage ?? $legacyStorage; // back compatibility if (!$this->storage) { @@ -108,7 +108,7 @@ final public function getStorage() public function login( $user, #[\SensitiveParameter] - ?string $password = null + ?string $password = null, ): void { $this->logout(true); 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/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index 9aa99303..f091a67c 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -14,11 +14,11 @@ 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'); diff --git a/tests/Security/Permission.ResourceInherits.phpt b/tests/Security/Permission.ResourceInherits.phpt index 2512733c..38c348d1 100644 --- a/tests/Security/Permission.ResourceInherits.phpt +++ b/tests/Security/Permission.ResourceInherits.phpt @@ -19,10 +19,10 @@ $acl->addResource('building', 'city'); $acl->addResource('room', 'building'); Assert::same(['city', 'building', 'room'], $acl->getResources()); -Assert::true($acl->resourceInheritsFrom('building', 'city', true)); -Assert::true($acl->resourceInheritsFrom('room', 'building', true)); +Assert::true($acl->resourceInheritsFrom('building', 'city', onlyParent: true)); +Assert::true($acl->resourceInheritsFrom('room', 'building', onlyParent: true)); Assert::true($acl->resourceInheritsFrom('room', 'city')); -Assert::false($acl->resourceInheritsFrom('room', 'city', true)); +Assert::false($acl->resourceInheritsFrom('room', 'city', onlyParent: true)); Assert::false($acl->resourceInheritsFrom('city', 'building')); Assert::false($acl->resourceInheritsFrom('building', 'room')); Assert::false($acl->resourceInheritsFrom('city', 'room')); diff --git a/tests/Security/Permission.RoleRegistryInherits.phpt b/tests/Security/Permission.RoleRegistryInherits.phpt index aaa19373..ddb25de1 100644 --- a/tests/Security/Permission.RoleRegistryInherits.phpt +++ b/tests/Security/Permission.RoleRegistryInherits.phpt @@ -23,10 +23,10 @@ Assert::same(['guest'], $acl->getRoleParents('member')); Assert::same(['member'], $acl->getRoleParents('editor')); -Assert::true($acl->roleInheritsFrom('member', 'guest', true)); -Assert::true($acl->roleInheritsFrom('editor', 'member', true)); +Assert::true($acl->roleInheritsFrom('member', 'guest', onlyParents: true)); +Assert::true($acl->roleInheritsFrom('editor', 'member', onlyParents: true)); Assert::true($acl->roleInheritsFrom('editor', 'guest')); -Assert::false($acl->roleInheritsFrom('editor', 'guest', true)); +Assert::false($acl->roleInheritsFrom('editor', 'guest', onlyParents: true)); Assert::false($acl->roleInheritsFrom('guest', 'member')); Assert::false($acl->roleInheritsFrom('member', 'editor')); Assert::false($acl->roleInheritsFrom('guest', 'editor')); diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index a0c48ab2..e093ee56 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'); } } From 29b92a60a29bcfa9b7bea871b4a745ce39e0ec2e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Apr 2022 18:04:32 +0200 Subject: [PATCH 206/254] coding style: reformatted Assert::exception --- .../CookieStorage.authentication.phpt | 7 +++-- tests/Security/Passwords.hash().phpt | 15 ++++++---- .../Permission.IsAllowedNonExistent.phpt | 22 ++++++++------ ...ission.ResourceAddInheritsNonExistent.phpt | 8 +++-- .../Permission.ResourceDuplicate.phpt | 12 ++++---- ...ermission.ResourceInheritsNonExistent.phpt | 18 +++++++----- ...rmission.ResourceRemoveOneNonExistent.phpt | 8 +++-- ...on.RoleRegistryAddInheritsNonExistent.phpt | 8 +++-- .../Permission.RoleRegistryDuplicate.phpt | 10 ++++--- ...ssion.RoleRegistryInheritsNonExistent.phpt | 18 +++++++----- ...sion.RoleRegistryRemoveOneNonExistent.phpt | 8 +++-- tests/Security/Permission.RuleRoleRemove.phpt | 8 +++-- .../Permission.RuleRoleRemoveAll.phpt | 8 +++-- .../Permission.RulesResourceRemove.phpt | 8 +++-- .../Permission.RulesResourceRemoveAll.phpt | 8 +++-- tests/Security/SimpleAuthenticator.phpt | 18 +++++++----- tests/Security/User.authentication.phpt | 29 ++++++++++--------- tests/Security/User.authorization.phpt | 8 +++-- 18 files changed, 133 insertions(+), 88 deletions(-) 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/Passwords.hash().phpt b/tests/Security/Passwords.hash().phpt index f091a67c..7baf9afe 100644 --- a/tests/Security/Passwords.hash().phpt +++ b/tests/Security/Passwords.hash().phpt @@ -24,10 +24,13 @@ Assert::truthy( $hash = (new Passwords(PASSWORD_BCRYPT))->hash('dg'); Assert::same($hash, crypt('dg', $hash)); -Assert::exception(function () { - (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'); -}, ValueError::class); +Assert::exception( + fn() => (new Passwords(PASSWORD_BCRYPT, ['cost' => 3]))->hash('dg'), + 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..ed0ecfc4 100644 --- a/tests/Security/SimpleAuthenticator.phpt +++ b/tests/Security/SimpleAuthenticator.phpt @@ -28,10 +28,14 @@ $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(function () use ($authenticator) { - $authenticator->authenticate('nobody', 'password'); -}, Nette\Security\AuthenticationException::class, "User 'nobody' not found."); +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.", +); diff --git a/tests/Security/User.authentication.phpt b/tests/Security/User.authentication.phpt index 9a94a8db..558aa40e 100644 --- a/tests/Security/User.authentication.phpt +++ b/tests/Security/User.authentication.phpt @@ -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 e093ee56..118890be 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -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); From dc329d35aac503b9a9a8dab0d03eda3a4275c89b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 19:15:45 +0100 Subject: [PATCH 207/254] added property typehints --- src/Bridges/SecurityDI/SecurityExtension.php | 3 +- src/Bridges/SecurityHttp/CookieStorage.php | 27 ++++---------- src/Bridges/SecurityHttp/SessionStorage.php | 19 +++------- src/Bridges/SecurityTracy/UserPanel.php | 3 +- src/Security/Identity.php | 13 +++---- src/Security/Passwords.php | 7 ++-- src/Security/Permission.php | 17 ++++----- src/Security/SimpleAuthenticator.php | 11 ++---- src/Security/User.php | 38 ++++++++------------ 9 files changed, 43 insertions(+), 95 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index cf762ef3..016e4113 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) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 48ba18a2..7279a797 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 MinLength = 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 $cookieExpiration = null; public function __construct(Http\IRequest $request, Http\IResponse $response) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 4da9f63c..fb4fdc7b 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -23,20 +23,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) 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/Identity.php b/src/Security/Identity.php index 3a78c2e5..ab1f2ce1 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -14,7 +14,7 @@ /** * @deprecated use Nette\Security\SimpleIdentity - * @property mixed $id + * @property string|int $id * @property array $roles * @property array $data */ @@ -26,14 +26,9 @@ class Identity implements IIdentity __isset as private parentIsSet; } - /** @var mixed */ - private $id; - - /** @var array */ - private $roles; - - /** @var array */ - private $data; + private string|int $id; + private array $roles; + private array $data; public function __construct($id, $roles = null, ?iterable $data = null) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 4c87a06a..0c690dca 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -19,11 +19,8 @@ class Passwords { use Nette\SmartObject; - /** @var int|string string since PHP 7.4 */ - private $algo; - - /** @var array */ - private $options; + private string $algo; + private array $options; /** diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 0a6b9e42..758aa943 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -21,14 +21,11 @@ class Permission implements Authorizator { use Nette\SmartObject; - /** @var array Role storage */ - private $roles = []; + private array $roles = []; + private array $resources = []; - /** @var array Resource storage */ - private $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,10 +39,8 @@ class Permission implements Authorizator 'byResource' => [], ]; - /** @var mixed */ - private $queriedRole; - - private $queriedResource; + private string|Role|null $queriedRole; + private string|Resource|null $queriedResource; /********************* roles ****************d*g**/ diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index b1e05bba..8cc589f9 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -19,14 +19,9 @@ class SimpleAuthenticator implements Authenticator { use Nette\SmartObject; - /** @var array */ - private $passwords; - - /** @var array */ - private $roles; - - /** @var array */ - private $data; + private array $passwords; + private array $roles; + private array $data; /** diff --git a/src/Security/User.php b/src/Security/User.php index 64fae555..7d1209c1 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -41,35 +41,25 @@ class User public const LOGOUT_MANUAL = self::LogoutManual; public const LOGOUT_INACTIVITY = self::LogoutInactivity; - /** @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 array $onLoggedIn = []; /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ - public $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; + public array $onLoggedOut = []; + + /** Session storage for current user */ + private UserStorage|IUserStorage $storage; + private ?IAuthenticator $authenticator; + private ?Authorizator $authorizator; + private ?IIdentity $identity = null; + private ?bool $authenticated = null; + private ?int $logoutReason = null; public function __construct( From e843379f38bd9f1561917824f7b4c8d11bddcd71 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:12:59 +0100 Subject: [PATCH 208/254] added PHP 8 typehints --- src/Bridges/SecurityDI/SecurityExtension.php | 4 +- src/Bridges/SecurityHttp/SessionStorage.php | 3 +- src/Security/Authorizator.php | 5 +- src/Security/Identity.php | 17 +-- src/Security/Passwords.php | 2 +- src/Security/Permission.php | 113 ++++++++----------- src/Security/User.php | 27 ++--- tests/Security/MockUserStorage.legacy.php | 3 +- 8 files changed, 62 insertions(+), 112 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 016e4113..1b183cca 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -55,7 +55,7 @@ public function getConfigSchema(): Nette\Schema\Schema } - public function loadConfiguration() + public function loadConfiguration(): void { /** @var object{debugger: bool, users: array, roles: array, resources: array, authentication: \stdClass} $config */ $config = $this->config; @@ -134,7 +134,7 @@ public function loadConfiguration() } - public function beforeCompile() + public function beforeCompile(): void { $builder = $this->getContainerBuilder(); diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index fb4fdc7b..efe6b654 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -102,9 +102,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/Security/Authorizator.php b/src/Security/Authorizator.php index 267fcccd..106f6cb8 100644 --- a/src/Security/Authorizator.php +++ b/src/Security/Authorizator.php @@ -31,11 +31,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/Identity.php b/src/Security/Identity.php index ab1f2ce1..8bc45790 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -43,15 +43,9 @@ public function __construct($id, $roles = null, ?iterable $data = null) /** * Sets the ID of user. - * @param string|int $id - * @return static */ - public function setId($id) + public function setId(string|int $id): static { - 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; } @@ -59,9 +53,8 @@ public function setId($id) /** * Returns the ID of user. - * @return mixed */ - public function getId() + public function getId(): string|int { return $this->id; } @@ -69,9 +62,8 @@ public function getId() /** * Sets a list of roles that the user is a member of. - * @return static */ - public function setRoles(array $roles) + public function setRoles(array $roles): static { $this->roles = $roles; return $this; @@ -112,9 +104,8 @@ public function __set(string $key, $value): void /** * Returns user data value. - * @return mixed */ - public function &__get(string $key) + public function &__get(string $key): mixed { if ($this->parentIsSet($key)) { return $this->parentGet($key); diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 0c690dca..60b70090 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -27,7 +27,7 @@ class Passwords * 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 = []) + public function __construct(string $algo = PASSWORD_DEFAULT, array $options = []) { $this->algo = $algo; $this->options = $options; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 758aa943..5ea5f944 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -49,12 +49,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 $parents = null): static { $this->checkRole($role, exists: false); if (isset($this->roles[$role])) { @@ -158,9 +156,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); @@ -196,10 +193,8 @@ public function removeRole(string $role) /** * Removes all Roles from the list. - * - * @return static */ - public function removeAllRoles() + public function removeAllRoles(): static { $this->roles = []; @@ -225,9 +220,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, exists: false); @@ -321,9 +315,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); @@ -353,9 +346,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) { @@ -376,18 +368,14 @@ public function removeAllResources() /** * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. - * - * @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, + string|array|null $roles = self::All, + string|array|null $resources = self::All, + string|array|null $privileges = self::All, ?callable $assertion = null, - ) { + ): static + { $this->setRule(true, self::Allow, $roles, $resources, $privileges, $assertion); return $this; } @@ -396,18 +384,14 @@ public function allow( /** * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. - * - * @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, + string|array|null $roles = self::All, + string|array|null $resources = self::All, + string|array|null $privileges = self::All, ?callable $assertion = null, - ) { + ): static + { $this->setRule(true, self::Deny, $roles, $resources, $privileges, $assertion); return $this; } @@ -415,13 +399,12 @@ public function deny( /** * Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges. - * - * @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( + string|array|null $roles = self::All, + string|array|null $resources = self::All, + string|array|null $privileges = self::All, + ): static { $this->setRule(false, self::Allow, $roles, $resources, $privileges); return $this; @@ -430,13 +413,12 @@ public function removeAllow($roles = self::All, $resources = self::All, $privile /** * Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges. - * - * @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( + string|array|null $roles = self::All, + string|array|null $resources = self::All, + string|array|null $privileges = self::All, + ): static { $this->setRule(false, self::Deny, $roles, $resources, $privileges); return $this; @@ -445,13 +427,16 @@ public function removeDeny($roles = self::All, $resources = self::All, $privileg /** * Performs operations on Access Control List rules. - * @param string|string[]|null $roles - * @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, + string|array|null $roles, + string|array|null $resources, + string|array|null $privileges, + ?callable $assertion = null, + ): void { // ensure that all specified Roles exist; normalize input to array of Roles or null if ($roles === self::All) { @@ -545,8 +530,6 @@ protected function setRule(bool $toAdd, bool $type, $roles, $resources, $privile } } } - - return $this; } @@ -561,12 +544,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|Resource|null $resource = self::All, + string|null $privilege = self::All, + ): bool { $this->queriedRole = $role; if ($role !== self::All) { @@ -623,9 +607,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(): string|Role|null { return $this->queriedRole; } @@ -633,9 +616,8 @@ public function getQueriedRole() /** * Returns real currently queried Resource. Use by assertion. - * @return mixed */ - public function getQueriedResource() + public function getQueriedResource(): string|Resource|null { return $this->queriedResource; } @@ -648,9 +630,8 @@ public function getQueriedResource() * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule * allowing/denying $role access to a/all $privilege upon $resource. * @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, ?string $role, ?string $resource, ?string $privilege): ?bool { $dfs = [ 'visited' => [], @@ -695,12 +676,8 @@ 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; @@ -737,10 +714,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/User.php b/src/Security/User.php index 7d1209c1..a7ea4d8b 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -18,7 +18,7 @@ * * @property-read bool $loggedIn * @property-read IIdentity $identity - * @property-read mixed $id + * @property-read string|int $id * @property-read array $roles * @property-read int $logoutReason * @property IAuthenticator $authenticator @@ -78,10 +78,7 @@ public function __construct( } - /** - * @return UserStorage|IUserStorage - */ - final public function getStorage() + final public function getStorage(): UserStorage|IUserStorage { return $this->storage; } @@ -96,7 +93,7 @@ final public function getStorage() * @throws AuthenticationException if authentication was not successful */ public function login( - $user, + string|IIdentity $user, #[\SensitiveParameter] ?string $password = null, ): void @@ -202,9 +199,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; @@ -219,9 +215,8 @@ final public function refreshStorage(): void /** * Sets authentication handler. - * @return static */ - public function setAuthenticator(IAuthenticator $handler) + public function setAuthenticator(IAuthenticator $handler): static { $this->authenticator = $handler; return $this; @@ -264,16 +259,9 @@ 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|int|null $clearIdentity = null) { - 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); @@ -346,9 +334,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; diff --git a/tests/Security/MockUserStorage.legacy.php b/tests/Security/MockUserStorage.legacy.php index d4b134be..679e2b36 100644 --- a/tests/Security/MockUserStorage.legacy.php +++ b/tests/Security/MockUserStorage.legacy.php @@ -9,9 +9,10 @@ class MockUserStorage implements Nette\Security\IUserStorage private $identity; - public function setAuthenticated(bool $state) + public function setAuthenticated(bool $state): self { $this->auth = $state; + return $this; } From b87b3e7c83f109e6e96f3168348734a865e218cd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 15:03:47 +0100 Subject: [PATCH 209/254] removed deprecated stuff --- src/Security/User.php | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index a7ea4d8b..91b8fb7c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -226,14 +226,9 @@ public function setAuthenticator(IAuthenticator $handler): static /** * Returns authentication handler. */ - final public function getAuthenticator(): ?IAuthenticator + final public function getAuthenticator(): IAuthenticator { - 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.'); } @@ -262,11 +257,6 @@ final public function hasAuthenticator(): bool */ public function setExpiration(?string $expire, bool|int|null $clearIdentity = null) { - 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); @@ -345,14 +335,9 @@ public function setAuthorizator(Authorizator $handler): static /** * 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.'); } From 590b965cdc8379a0762f18ef3f785801156a19dc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:13:42 +0100 Subject: [PATCH 210/254] removed deprecated IUserStorage (BC break) --- src/Bridges/SecurityDI/SecurityExtension.php | 4 - src/Security/IUserStorage.php | 58 --------- src/Security/User.php | 54 ++------ tests/Security.DI/SecurityExtension.user.phpt | 1 - tests/Security/MockUserStorage.legacy.php | 46 ------- .../Security/User.authentication.legacy.phpt | 117 ------------------ tests/Security/User.authentication.phpt | 2 +- tests/Security/User.authorization.phpt | 2 +- 8 files changed, 15 insertions(+), 269 deletions(-) delete mode 100644 src/Security/IUserStorage.php delete mode 100644 tests/Security/MockUserStorage.legacy.php delete mode 100644 tests/Security/User.authentication.legacy.phpt diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 1b183cca..339ec43d 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -80,10 +80,6 @@ public function loadConfiguration(): void $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); diff --git a/src/Security/IUserStorage.php b/src/Security/IUserStorage.php deleted file mode 100644 index 061e52c6..00000000 --- a/src/Security/IUserStorage.php +++ /dev/null @@ -1,58 +0,0 @@ -storage = $storage ?? $legacyStorage; // back compatibility - if (!$this->storage) { - throw new Nette\InvalidStateException('UserStorage has not been set.'); - } - + $this->storage = $storage; $this->authenticator = $authenticator; $this->authorizator = $authorizator; } - final public function getStorage(): UserStorage|IUserStorage + final public function getStorage(): UserStorage { return $this->storage; } @@ -111,13 +106,8 @@ public function login( $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); @@ -130,16 +120,7 @@ public function login( 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::LogoutManual; if ($logged) { @@ -178,17 +159,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) @@ -255,12 +230,9 @@ final public function hasAuthenticator(): bool /** * Enables log out after inactivity (like '20 minutes'). */ - public function setExpiration(?string $expire, bool|int|null $clearIdentity = null) + public function setExpiration(?string $expire, bool $clearIdentity = false) { - $arg = $this->storage instanceof UserStorage - ? (bool) $clearIdentity - : ($clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0); - $this->storage->setExpiration($expire, $arg); + $this->storage->setExpiration($expire, $clearIdentity); return $this; } diff --git a/tests/Security.DI/SecurityExtension.user.phpt b/tests/Security.DI/SecurityExtension.user.phpt index f4f3e9ff..fa0706e5 100644 --- a/tests/Security.DI/SecurityExtension.user.phpt +++ b/tests/Security.DI/SecurityExtension.user.phpt @@ -25,7 +25,6 @@ eval($compiler->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/MockUserStorage.legacy.php b/tests/Security/MockUserStorage.legacy.php deleted file mode 100644 index 679e2b36..00000000 --- a/tests/Security/MockUserStorage.legacy.php +++ /dev/null @@ -1,46 +0,0 @@ -auth = $state; - return $this; - } - - - 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/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 558aa40e..5d6413e6 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, diff --git a/tests/Security/User.authorization.phpt b/tests/Security/User.authorization.phpt index 118890be..ee26305e 100644 --- a/tests/Security/User.authorization.phpt +++ b/tests/Security/User.authorization.phpt @@ -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()); From 0e93577e05a0994be14a0dd3f1ce37419f2f5795 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 13 Nov 2023 12:59:56 +0100 Subject: [PATCH 211/254] removed Nette\SmartObject --- src/Bridges/SecurityHttp/CookieStorage.php | 2 -- src/Bridges/SecurityHttp/SessionStorage.php | 2 -- src/Bridges/SecurityTracy/UserPanel.php | 2 -- src/Security/Identity.php | 19 ++++++------------- src/Security/Passwords.php | 2 -- src/Security/Permission.php | 2 -- src/Security/SimpleAuthenticator.php | 4 ---- 7 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 7279a797..70c15a47 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -19,8 +19,6 @@ */ final class CookieStorage implements Nette\Security\UserStorage { - use Nette\SmartObject; - private const MinLength = 13; private Http\IRequest $request; diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index efe6b654..e0ab56ec 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -21,8 +21,6 @@ */ final class SessionStorage implements Nette\Security\UserStorage { - use Nette\SmartObject; - private string $namespace = ''; private Session $sessionHandler; private ?SessionSection $sessionSection = null; diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 123d86c1..4cf753a1 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -18,8 +18,6 @@ */ class UserPanel implements Tracy\IBarPanel { - use Nette\SmartObject; - private Nette\Security\User $user; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 8bc45790..ee00111c 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -9,8 +9,6 @@ namespace Nette\Security; -use Nette; - /** * @deprecated use Nette\Security\SimpleIdentity @@ -20,12 +18,6 @@ */ class Identity 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; @@ -93,8 +85,8 @@ public function getData(): array */ public function __set(string $key, $value): void { - if ($this->parentIsSet($key)) { - $this->parentSet($key, $value); + if (in_array($key, ['id', 'roles', 'data'], strict: true)) { + $this->{"set$key"}($value); } else { $this->data[$key] = $value; @@ -107,8 +99,9 @@ public function __set(string $key, $value): void */ public function &__get(string $key): mixed { - if ($this->parentIsSet($key)) { - return $this->parentGet($key); + if (in_array($key, ['id', 'roles', 'data'], strict: true)) { + $res = $this->{"get$key"}(); + return $res; } else { return $this->data[$key]; @@ -118,6 +111,6 @@ public function &__get(string $key): mixed public function __isset(string $key): bool { - return isset($this->data[$key]) || $this->parentIsSet($key); + return isset($this->data[$key]) || in_array($key, ['id', 'roles', 'data'], strict: true); } } diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 60b70090..10329d65 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -17,8 +17,6 @@ */ class Passwords { - use Nette\SmartObject; - private string $algo; private array $options; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 5ea5f944..888e85a2 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -19,8 +19,6 @@ */ class Permission implements Authorizator { - use Nette\SmartObject; - private array $roles = []; private array $resources = []; diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 8cc589f9..bb6ab13f 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -9,16 +9,12 @@ namespace Nette\Security; -use Nette; - /** * Trivial implementation of Authenticator. */ class SimpleAuthenticator implements Authenticator { - use Nette\SmartObject; - private array $passwords; private array $roles; private array $data; From b5f6be63d6d562815538e60e7b0720075c635fab Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Dec 2023 13:26:37 +0100 Subject: [PATCH 212/254] used promoted properties --- src/Security/Passwords.php | 12 ++++-------- src/Security/SimpleAuthenticator.php | 14 +++----------- src/Security/User.php | 13 +++---------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 10329d65..ffc4927f 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -17,18 +17,14 @@ */ class Passwords { - private string $algo; - private array $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(string $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/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index bb6ab13f..eb5b2b27 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -15,11 +15,6 @@ */ class SimpleAuthenticator implements Authenticator { - private array $passwords; - private array $roles; - private array $data; - - /** * @param array $passwords list of pairs username => password * @param array $roles list of pairs username => role[] @@ -27,13 +22,10 @@ class SimpleAuthenticator implements Authenticator */ public function __construct( #[\SensitiveParameter] - array $passwords, - array $roles = [], - array $data = [], + private array $passwords, + private array $roles = [], + private array $data = [], ) { - $this->passwords = $passwords; - $this->roles = $roles; - $this->data = $data; } diff --git a/src/Security/User.php b/src/Security/User.php index 3748db48..b2acb47e 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -53,23 +53,16 @@ class User /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ public array $onLoggedOut = []; - /** Session storage for current user */ - private UserStorage $storage; - private ?IAuthenticator $authenticator; - private ?Authorizator $authorizator; private ?IIdentity $identity = null; private ?bool $authenticated = null; private ?int $logoutReason = null; public function __construct( - UserStorage $storage, - ?IAuthenticator $authenticator = null, - ?Authorizator $authorizator = null, + private UserStorage $storage, + private ?IAuthenticator $authenticator = null, + private ?Authorizator $authorizator = null, ) { - $this->storage = $storage; - $this->authenticator = $authenticator; - $this->authorizator = $authorizator; } From fe89d52697036fb2e14835dfb46b696d28a9ebf6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Nov 2022 22:32:30 +0100 Subject: [PATCH 213/254] uppercase constants marked as deprecated --- src/Security/Authenticator.php | 12 ++++++++++++ src/Security/Authorizator.php | 5 +++++ src/Security/User.php | 14 +++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php index d79e6364..96829284 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -22,6 +22,18 @@ interface Authenticator extends IAuthenticator Failure = 3, NotApproved = 4; + /** @deprecated use Authenticator::IdentityNotFound */ + public const IDENTITY_NOT_FOUND = self::IdentityNotFound; + + /** @deprecated use Authenticator::InvalidCredential */ + public const INVALID_CREDENTIAL = self::InvalidCredential; + + /** @deprecated use Authenticator::Failure */ + public const FAILURE = self::Failure; + + /** @deprecated use Authenticator::NotApproved */ + public const NOT_APPROVED = self::NotApproved; + /** * Performs an authentication. * @throws AuthenticationException diff --git a/src/Security/Authorizator.php b/src/Security/Authorizator.php index 106f6cb8..a8fa356e 100644 --- a/src/Security/Authorizator.php +++ b/src/Security/Authorizator.php @@ -25,8 +25,13 @@ interface Authorizator /** Permission type: deny */ public const Deny = false; + /** @deprecated use Authorizator::All */ public const ALL = self::All; + + /** @deprecated use Authorizator::Allow */ public const ALLOW = self::Allow; + + /** @deprecated use Authorizator::Deny */ public const DENY = self::Deny; /** diff --git a/src/Security/User.php b/src/Security/User.php index b2acb47e..afeb67bb 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -33,14 +33,18 @@ class User LogoutManual = 1, LogoutInactivity = 2; - /** @deprecated */ - public const - MANUAL = self::LogoutManual, - INACTIVITY = self::LogoutInactivity; - + /** @deprecated use User::LogoutManual */ public const LOGOUT_MANUAL = self::LogoutManual; + + /** @deprecated use User::LogoutManual */ + public const MANUAL = self::LogoutManual; + + /** @deprecated use User::LogoutInactivity */ public const LOGOUT_INACTIVITY = self::LogoutInactivity; + /** @deprecated use User::LogoutInactivity */ + public const INACTIVITY = self::LogoutInactivity; + /** default role for unauthenticated user */ public string $guestRole = 'guest'; From dd295ca5872e90b70e1e98cada621865ebe98148 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 26 Apr 2024 16:01:01 +0200 Subject: [PATCH 214/254] cs --- src/Security/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index afeb67bb..9ac3fcdb 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -51,10 +51,10 @@ class User /** 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 */ + /** @var array Occurs when the user is successfully logged in */ public array $onLoggedIn = []; - /** @var callable[] function (User $sender): void; Occurs when the user is logged out */ + /** @var array Occurs when the user is logged out */ public array $onLoggedOut = []; private ?IIdentity $identity = null; From eee50d913061e0fc435c2571bd601d9a448a042b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 12:59:56 +0200 Subject: [PATCH 215/254] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 97895fbe..8c60f763 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,7 +7,7 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 @@ -21,7 +21,7 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d385b9c2..c23cc2f3 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 144b3690..fd95d74b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -32,7 +32,7 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 @@ -46,7 +46,7 @@ jobs: name: Code Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 From 6cc63f2060d061e3c534f1b5afae2fb90c252c24 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Jun 2024 23:27:19 +0200 Subject: [PATCH 216/254] support for PHP 8.4 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- src/Security/Permission.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd95d74b..9637f74c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3'] + php: ['8.1', '8.2', '8.3', '8.4'] fail-fast: false diff --git a/composer.json b/composer.json index 2f352bdc..151d792c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.1 - 8.3", + "php": "8.1 - 8.4", "nette/utils": "^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index 47b2f6d3..d88b02a5 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 8.1 and supports PHP up to 8.3. +It requires PHP version 8.1 and supports PHP up to 8.4. [Support Me](https://github.com/sponsors/dg) diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 888e85a2..5dab4238 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -50,7 +50,7 @@ class Permission implements Authorizator * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException */ - public function addRole(string $role, string|array $parents = null): static + public function addRole(string $role, string|array|null $parents = null): static { $this->checkRole($role, exists: false); if (isset($this->roles[$role])) { From 4391db383d789a2ba028ab9b36eb1d49ee1672da Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 4 Nov 2024 13:10:17 +0100 Subject: [PATCH 217/254] typo [Closes #75] --- src/Bridges/SecurityHttp/SessionStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index e0ab56ec..b1ae9e25 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -145,7 +145,7 @@ protected function getSessionSection(): ?SessionSection } } - $section->set('expireTime', time() + $section->expireDelta); // sliding expiration + $section->set('expireTime', time() + $section->get('expireDelta')); // sliding expiration } if (!$section->get('authenticated')) { From d698efe196f8a863a8f480e39f3eeffa0ee53da0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 4 Nov 2024 13:16:42 +0100 Subject: [PATCH 218/254] SecurityExtension: password can be dynamic [Closes #74] --- src/Bridges/SecurityDI/SecurityExtension.php | 4 ++-- tests/Security.DI/SecurityExtension.authenticator.phpt | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 339ec43d..750bcb57 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -34,9 +34,9 @@ public function getConfigSchema(): Nette\Schema\Schema 'debugger' => Expect::bool(), 'users' => Expect::arrayOf( Expect::anyOf( - Expect::string(), // user => password + Expect::string()->dynamic(), // user => password Expect::structure([ // user => password + roles + data - 'password' => Expect::string(), + 'password' => Expect::string()->dynamic(), 'roles' => Expect::anyOf(Expect::string(), Expect::listOf('string')), 'data' => Expect::array(), ])->castTo('array'), diff --git a/tests/Security.DI/SecurityExtension.authenticator.phpt b/tests/Security.DI/SecurityExtension.authenticator.phpt index ececfc23..fd159549 100644 --- a/tests/Security.DI/SecurityExtension.authenticator.phpt +++ b/tests/Security.DI/SecurityExtension.authenticator.phpt @@ -29,6 +29,8 @@ security: admin: {password: admin123, roles: [admin, user]} user: {password: user123} moderator: {password: moderator123, roles: moderator} + dynamic1: ::trim(xxx) + dynamic2: {password: ::trim(xxx)} ', 'neon')); eval($compiler->addConfig($config)->compile()); @@ -43,12 +45,16 @@ $userList = [ 'admin' => 'admin123', 'user' => 'user123', 'moderator' => 'moderator123', + 'dynamic1' => 'xxx', + 'dynamic2' => 'xxx', ]; $expectedRoles = [ 'john' => [], 'admin' => ['admin', 'user'], 'user' => [], 'moderator' => ['moderator'], + 'dynamic1' => [], + 'dynamic2' => [], ]; foreach ($userList as $username => $password) { From 6e19bf604934aec0cd3343a307e28fd997e40e96 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 4 Nov 2024 13:21:21 +0100 Subject: [PATCH 219/254] $user => $username [Closes #73] --- src/Security/Authenticator.php | 2 +- src/Security/User.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php index 96829284..500331de 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -38,5 +38,5 @@ interface Authenticator extends IAuthenticator * Performs an authentication. * @throws AuthenticationException */ - function authenticate(string $user, string $password): IIdentity; + function authenticate(string $username, string $password): IIdentity; } diff --git a/src/Security/User.php b/src/Security/User.php index 9ac3fcdb..3fd6ad73 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -81,18 +81,18 @@ final public function getStorage(): UserStorage /** * Conducts the authentication process. Parameters are optional. - * @param string|IIdentity $user name or Identity + * @param string|IIdentity $username name or Identity * @throws AuthenticationException if authentication was not successful */ public function login( - string|IIdentity $user, + string|IIdentity $username, #[\SensitiveParameter] ?string $password = null, ): void { $this->logout(true); - if ($user instanceof IIdentity) { - $this->identity = $user; + if ($username instanceof IIdentity) { + $this->identity = $username; } else { $authenticator = $this->getAuthenticator(); $this->identity = $authenticator instanceof Authenticator From 270823b48574ed77c63c162e5d3e2d0dc90e8697 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 3 Feb 2025 03:31:59 +0100 Subject: [PATCH 220/254] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 8c60f763..a8f19b11 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9637f74c..9b2ee263 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,9 +22,9 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: output + name: output-${{ matrix.php }} path: tests/**/output From 286554b7e313e4a7e3e9bffc792da35e1b2440fb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:05:43 +0200 Subject: [PATCH 221/254] composer: require stable packages outside of nette --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 151d792c..4301ed9e 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "nette/http": "^3.2", "nette/tester": "^2.5", "tracy/tracy": "^2.9", - "phpstan/phpstan-nette": "^1.0", - "mockery/mockery": "^1.5" + "phpstan/phpstan-nette": "^2.0@stable", + "mockery/mockery": "^1.6@stable" }, "conflict": { "nette/di": "<3.0-stable", From b6b0ddd455176daf213f662122eb034dad352d63 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:20:40 +0200 Subject: [PATCH 222/254] composer: added psr-4 loader --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4301ed9e..d3062dc7 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,10 @@ "nette/http": "<3.1.3" }, "autoload": { - "classmap": ["src/"] + "classmap": ["src/"], + "psr-4": { + "Nette\\": "src" + } }, "minimum-stability": "dev", "scripts": { From 4950b6f42bf632aeeb0254ed9a99fe039321977e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Jun 2025 19:47:28 +0200 Subject: [PATCH 223/254] optimized global function calls --- src/Bridges/SecurityDI/SecurityExtension.php | 1 + src/Bridges/SecurityHttp/CookieStorage.php | 1 + src/Bridges/SecurityHttp/SessionStorage.php | 1 + src/Bridges/SecurityTracy/UserPanel.php | 1 + src/Security/Identity.php | 2 ++ src/Security/Passwords.php | 1 + src/Security/Permission.php | 1 + src/Security/User.php | 1 + 8 files changed, 9 insertions(+) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 750bcb57..9d689fc4 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -12,6 +12,7 @@ use Nette; use Nette\Schema\Expect; use Tracy; +use function is_array; /** diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 70c15a47..8882b19d 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -12,6 +12,7 @@ use Nette; use Nette\Http; use Nette\Security\IIdentity; +use function is_string, strlen; /** diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index b1ae9e25..4023269d 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -14,6 +14,7 @@ use Nette\Http\SessionSection; use Nette\Security\IIdentity; use Nette\Security\User; +use function is_bool, time; /** diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index 4cf753a1..ac86fbf5 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -11,6 +11,7 @@ use Nette; use Tracy; +use const PHP_SESSION_ACTIVE; /** diff --git a/src/Security/Identity.php b/src/Security/Identity.php index ee00111c..73d6b263 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -9,6 +9,8 @@ namespace Nette\Security; +use function in_array, is_float, is_numeric, iterator_to_array; + /** * @deprecated use Nette\Security\SimpleIdentity diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index ffc4927f..570f75ff 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -10,6 +10,7 @@ namespace Nette\Security; use Nette; +use const PASSWORD_DEFAULT; /** diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 5dab4238..6d53af38 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -10,6 +10,7 @@ namespace Nette\Security; use Nette; +use function array_keys, array_pop, count, is_array; /** diff --git a/src/Security/User.php b/src/Security/User.php index 3fd6ad73..83d460e2 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -11,6 +11,7 @@ use Nette; use Nette\Utils\Arrays; +use function func_get_args; /** From 3bc104f3377433af86ce519002a19f6378bdc9f3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 1 Aug 2025 04:14:57 +0200 Subject: [PATCH 224/254] support for PHP 8.5 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b2ee263..8ed81e7e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4'] + php: ['8.1', '8.2', '8.3', '8.4', '8.5'] fail-fast: false diff --git a/composer.json b/composer.json index d3062dc7..6e5e7ece 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.1 - 8.4", + "php": "8.1 - 8.5", "nette/utils": "^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index d88b02a5..45be02fa 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 8.1 and supports PHP up to 8.4. +It requires PHP version 8.1 and supports PHP up to 8.5. [Support Me](https://github.com/sponsors/dg) From beca6757457281ebc9428743bec7960809f40d49 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Jan 2025 07:16:41 +0100 Subject: [PATCH 225/254] UserPanel: convert templates to Latte-like syntax --- src/Bridges/SecurityTracy/UserPanel.php | 4 ++-- src/Bridges/SecurityTracy/dist/panel.phtml | 13 +++++++++++++ src/Bridges/SecurityTracy/dist/tab.phtml | 10 ++++++++++ src/Bridges/SecurityTracy/panel.latte | 9 +++++++++ src/Bridges/SecurityTracy/tab.latte | 7 +++++++ .../SecurityTracy/templates/UserPanel.panel.phtml | 13 ------------- .../SecurityTracy/templates/UserPanel.tab.phtml | 11 ----------- 7 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/Bridges/SecurityTracy/dist/panel.phtml create mode 100644 src/Bridges/SecurityTracy/dist/tab.phtml create mode 100644 src/Bridges/SecurityTracy/panel.latte create mode 100644 src/Bridges/SecurityTracy/tab.latte delete mode 100644 src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml delete mode 100644 src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index ac86fbf5..39fbef4f 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -41,7 +41,7 @@ public function getTab(): ?string $status = session_status() === PHP_SESSION_ACTIVE ? $this->user->isLoggedIn() : '?'; - require __DIR__ . '/templates/UserPanel.tab.phtml'; + require __DIR__ . '/dist/tab.phtml'; }); } @@ -57,7 +57,7 @@ public function getPanel(): ?string return Nette\Utils\Helpers::capture(function () { $user = $this->user; - require __DIR__ . '/templates/UserPanel.panel.phtml'; + require __DIR__ . '/dist/panel.phtml'; }); } } diff --git a/src/Bridges/SecurityTracy/dist/panel.phtml b/src/Bridges/SecurityTracy/dist/panel.phtml new file mode 100644 index 00000000..5173af7c --- /dev/null +++ b/src/Bridges/SecurityTracy/dist/panel.phtml @@ -0,0 +1,13 @@ + +

isLoggedIn()): ?> +Logged in +Unlogged +

+ +
+getIdentity()): ?> getIdentity(), [Tracy\Dumper::LIVE => true]) ?> + +

no identity

+
diff --git a/src/Bridges/SecurityTracy/dist/tab.phtml b/src/Bridges/SecurityTracy/dist/tab.phtml new file mode 100644 index 00000000..0b3a2f70 --- /dev/null +++ b/src/Bridges/SecurityTracy/dist/tab.phtml @@ -0,0 +1,10 @@ + + 'Logged in', false => 'Unlogged', '?' => 'Session is closed'] ?> '#61A519', false => '#ababab', '?' => '#bb0000'] ?> + + + + diff --git a/src/Bridges/SecurityTracy/panel.latte b/src/Bridges/SecurityTracy/panel.latte new file mode 100644 index 00000000..7ba7c784 --- /dev/null +++ b/src/Bridges/SecurityTracy/panel.latte @@ -0,0 +1,9 @@ +

{if $user->isLoggedIn()}Logged in{else}Unlogged{/if}

+ +
+ {if $user->getIdentity()} + {Tracy\Dumper::toHtml($user->getIdentity(), [Tracy\Dumper::LIVE => true])} + {else} +

no identity

+ {/if} +
diff --git a/src/Bridges/SecurityTracy/tab.latte b/src/Bridges/SecurityTracy/tab.latte new file mode 100644 index 00000000..f745d1ae --- /dev/null +++ b/src/Bridges/SecurityTracy/tab.latte @@ -0,0 +1,7 @@ +{do $messages = [true => 'Logged in', false => Unlogged, '?' => 'Session is closed']} +{do $colors = [true => '#61A519', false => '#ababab', '?' => '#bb0000']} + + + + + diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml deleted file mode 100644 index 18e3c954..00000000 --- a/src/Bridges/SecurityTracy/templates/UserPanel.panel.phtml +++ /dev/null @@ -1,13 +0,0 @@ - -
-

isLoggedIn()): ?>Logged inUnlogged

- - getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => true]); else: ?>

no identity

-
diff --git a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml b/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml deleted file mode 100644 index 4571fbda..00000000 --- a/src/Bridges/SecurityTracy/templates/UserPanel.tab.phtml +++ /dev/null @@ -1,11 +0,0 @@ - 'Logged in', false => 'Unlogged', '?' => 'Session is closed']; -$color = [true => '#61A519', false => '#ababab', '?' => '#bb0000']; -?> - - - From 87b155f2899a4bca4aeccbfe3a6c5bdad28247f4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 23 Feb 2026 04:50:25 +0100 Subject: [PATCH 226/254] singleline declare statements --- src/Bridges/SecurityDI/SecurityExtension.php | 4 +--- src/Bridges/SecurityHttp/CookieStorage.php | 4 +--- src/Bridges/SecurityHttp/SessionStorage.php | 4 +--- src/Bridges/SecurityTracy/UserPanel.php | 4 +--- src/Bridges/SecurityTracy/dist/panel.phtml | 4 +--- src/Bridges/SecurityTracy/dist/tab.phtml | 4 +--- src/Security/AuthenticationException.php | 4 +--- src/Security/Authenticator.php | 4 +--- src/Security/Authorizator.php | 4 +--- src/Security/IAuthenticator.php | 4 +--- src/Security/IIdentity.php | 4 +--- src/Security/Identity.php | 4 +--- src/Security/IdentityHandler.php | 4 +--- src/Security/Passwords.php | 4 +--- src/Security/Permission.php | 4 +--- src/Security/Resource.php | 4 +--- src/Security/Role.php | 4 +--- src/Security/SimpleAuthenticator.php | 4 +--- src/Security/SimpleIdentity.php | 4 +--- src/Security/User.php | 4 +--- src/Security/UserStorage.php | 4 +--- src/compatibility.php | 4 +--- tests/Security.DI/SecurityExtension.authenticator.phpt | 4 +--- tests/Security.DI/SecurityExtension.authorizator.phpt | 4 +--- tests/Security.DI/SecurityExtension.cookieStorage.phpt | 4 +--- tests/Security.DI/SecurityExtension.passwords.phpt | 4 +--- tests/Security.DI/SecurityExtension.sessionStorage.phpt | 4 +--- tests/Security.DI/SecurityExtension.user.phpt | 4 +--- tests/Security.Http/CookieStorage.authentication.phpt | 4 +--- tests/Security.Http/CookieStorage.getState.phpt | 4 +--- tests/Security/Identity.phpt | 4 +--- tests/Security/MockUserStorage.php | 4 +--- tests/Security/Passwords.hash().phpt | 4 +--- tests/Security/Passwords.needsRehash().phpt | 4 +--- tests/Security/Passwords.verify().phpt | 4 +--- tests/Security/Permission.CMSExample.phpt | 4 +--- tests/Security/Permission.DefaultAssert.phpt | 4 +--- tests/Security/Permission.DefaultDeny.phpt | 4 +--- tests/Security/Permission.DefaultRuleSet.phpt | 4 +--- tests/Security/Permission.IsAllowedNonExistent.phpt | 4 +--- tests/Security/Permission.PrivilegeAllow.phpt | 4 +--- tests/Security/Permission.PrivilegeAssert.phpt | 4 +--- tests/Security/Permission.PrivilegeDeny.phpt | 4 +--- tests/Security/Permission.Privileges.phpt | 4 +--- tests/Security/Permission.RemoveDefaultAllow.phpt | 4 +--- tests/Security/Permission.RemoveDefaultAllowNonExistent.phpt | 4 +--- tests/Security/Permission.RemoveDefaultDeny.phpt | 4 +--- tests/Security/Permission.RemoveDefaultDenyAssert.phpt | 4 +--- tests/Security/Permission.RemoveDefaultDenyNonExistent.phpt | 4 +--- ...ion.RemovingRoleAfterItWasAllowedAccessToAllResources.phpt | 4 +--- tests/Security/Permission.ResourceAddAndGetOne.phpt | 4 +--- tests/Security/Permission.ResourceAddInheritsNonExistent.phpt | 4 +--- tests/Security/Permission.ResourceDuplicate.phpt | 4 +--- tests/Security/Permission.ResourceInherits.phpt | 4 +--- tests/Security/Permission.ResourceInheritsNonExistent.phpt | 4 +--- tests/Security/Permission.ResourceRemoveAll.phpt | 4 +--- tests/Security/Permission.ResourceRemoveOneNonExistent.phpt | 4 +--- .../Permission.RoleDefaultAllowRuleWithPrivilegeDenyRule.phpt | 4 +--- .../Permission.RoleDefaultAllowRuleWithResourceDenyRule.phpt | 4 +--- tests/Security/Permission.RoleDefaultRuleSet.phpt | 4 +--- tests/Security/Permission.RoleDefaultRuleSetPrivilege.phpt | 4 +--- tests/Security/Permission.RolePrivilegeAllow.phpt | 4 +--- tests/Security/Permission.RolePrivilegeAssert.phpt | 4 +--- tests/Security/Permission.RolePrivilegeDeny.phpt | 4 +--- tests/Security/Permission.RolePrivileges.phpt | 4 +--- tests/Security/Permission.RoleRegistryAddAndGetOne.phpt | 4 +--- .../Permission.RoleRegistryAddInheritsNonExistent.phpt | 4 +--- tests/Security/Permission.RoleRegistryDuplicate.phpt | 4 +--- tests/Security/Permission.RoleRegistryInherits.phpt | 4 +--- tests/Security/Permission.RoleRegistryInheritsMultiple.phpt | 4 +--- .../Security/Permission.RoleRegistryInheritsNonExistent.phpt | 4 +--- tests/Security/Permission.RoleRegistryRemoveAll.phpt | 4 +--- .../Security/Permission.RoleRegistryRemoveOneNonExistent.phpt | 4 +--- tests/Security/Permission.RuleRoleRemove.phpt | 4 +--- tests/Security/Permission.RuleRoleRemoveAll.phpt | 4 +--- tests/Security/Permission.RulesRemove.phpt | 4 +--- tests/Security/Permission.RulesResourceRemove.phpt | 4 +--- tests/Security/Permission.RulesResourceRemoveAll.phpt | 4 +--- tests/Security/SimpleAuthenticator.Data.phpt | 4 +--- tests/Security/SimpleAuthenticator.Roles.phpt | 4 +--- tests/Security/SimpleAuthenticator.phpt | 4 +--- tests/Security/SimpleIdentity.phpt | 4 +--- tests/Security/User.authentication.phpt | 4 +--- tests/Security/User.authorization.phpt | 4 +--- tests/bootstrap.php | 4 +--- 85 files changed, 85 insertions(+), 255 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 9d689fc4..b4535453 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -1,12 +1,10 @@ - +

isLoggedIn()): ?> Logged in Unlogged diff --git a/src/Bridges/SecurityTracy/dist/tab.phtml b/src/Bridges/SecurityTracy/dist/tab.phtml index 0b3a2f70..84b088c2 100644 --- a/src/Bridges/SecurityTracy/dist/tab.phtml +++ b/src/Bridges/SecurityTracy/dist/tab.phtml @@ -1,6 +1,4 @@ - + 'Logged in', false => 'Unlogged', '?' => 'Session is closed'] ?> '#61A519', false => '#ababab', '?' => '#bb0000'] ?> diff --git a/src/Security/AuthenticationException.php b/src/Security/AuthenticationException.php index 79c90b44..2a03b5bf 100644 --- a/src/Security/AuthenticationException.php +++ b/src/Security/AuthenticationException.php @@ -1,12 +1,10 @@ - Date: Sat, 27 Dec 2025 02:24:15 +0100 Subject: [PATCH 227/254] cs --- src/Bridges/SecurityDI/SecurityExtension.php | 9 +++------ src/Bridges/SecurityHttp/CookieStorage.php | 14 ++++++-------- src/Bridges/SecurityHttp/SessionStorage.php | 9 ++++----- src/Bridges/SecurityTracy/UserPanel.php | 9 +++------ src/Security/Identity.php | 4 ++-- src/Security/Passwords.php | 5 ++--- src/Security/User.php | 8 ++++---- 7 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index b4535453..c6831230 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -18,12 +18,9 @@ */ class SecurityExtension extends Nette\DI\CompilerExtension { - private bool $debugMode; - - - public function __construct(bool $debugMode = false) - { - $this->debugMode = $debugMode; + public function __construct( + private readonly bool $debugMode = false, + ) { } diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 73475beb..3a814e13 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -19,9 +19,6 @@ final class CookieStorage implements Nette\Security\UserStorage { private const MinLength = 13; - - private Http\IRequest $request; - private Http\IResponse $response; private ?string $uid = null; private string $cookieName = 'userid'; private ?string $cookieDomain = null; @@ -29,10 +26,10 @@ final class CookieStorage implements Nette\Security\UserStorage private ?string $cookieExpiration = null; - public function __construct(Http\IRequest $request, Http\IResponse $response) - { - $this->response = $response; - $this->request = $request; + public function __construct( + private readonly Http\IRequest $request, + private readonly Http\IResponse $response, + ) { } @@ -91,7 +88,8 @@ public function setCookieParameters( ?string $name = null, ?string $domain = null, ?string $sameSite = null, - ) { + ): void + { $this->cookieName = $name ?? $this->cookieName; $this->cookieDomain = $domain ?? $this->cookieDomain; $this->cookieSameSite = $sameSite ?? $this->cookieSameSite; diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 5d0e7670..f9167652 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -21,15 +21,14 @@ final class SessionStorage implements Nette\Security\UserStorage { private string $namespace = ''; - private Session $sessionHandler; private ?SessionSection $sessionSection = null; private ?int $expireTime = null; private bool $expireIdentity = false; - public function __construct(Session $sessionHandler) - { - $this->sessionHandler = $sessionHandler; + public function __construct( + private readonly Session $sessionHandler, + ) { } @@ -123,7 +122,7 @@ public function getNamespace(): string /** * Returns and initializes $this->sessionSection. */ - protected function getSessionSection(): ?SessionSection + private function getSessionSection(): ?SessionSection { if ($this->sessionSection !== null) { return $this->sessionSection; diff --git a/src/Bridges/SecurityTracy/UserPanel.php b/src/Bridges/SecurityTracy/UserPanel.php index cfc9a34e..75a523d8 100644 --- a/src/Bridges/SecurityTracy/UserPanel.php +++ b/src/Bridges/SecurityTracy/UserPanel.php @@ -17,12 +17,9 @@ */ class UserPanel implements Tracy\IBarPanel { - private Nette\Security\User $user; - - - public function __construct(Nette\Security\User $user) - { - $this->user = $user; + public function __construct( + private readonly Nette\Security\User $user, + ) { } diff --git a/src/Security/Identity.php b/src/Security/Identity.php index eb3368b0..3d3339c1 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -23,7 +23,7 @@ class Identity implements IIdentity private array $data; - public function __construct($id, $roles = null, ?iterable $data = null) + public function __construct(string|int $id, $roles = null, ?iterable $data = null) { $this->setId($id); $this->setRoles((array) $roles); @@ -83,7 +83,7 @@ public function getData(): array /** * Sets user data value. */ - public function __set(string $key, $value): void + public function __set(string $key, mixed $value): void { if (in_array($key, ['id', 'roles', 'data'], strict: true)) { $this->{"set$key"}($value); diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 940904ef..5c6c9d32 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -8,7 +8,6 @@ namespace Nette\Security; use Nette; -use const PASSWORD_DEFAULT; /** @@ -21,8 +20,8 @@ class Passwords * @see https://php.net/manual/en/password.constants.php */ public function __construct( - private string $algo = PASSWORD_DEFAULT, - private array $options = [], + private readonly string $algo = PASSWORD_DEFAULT, + private readonly array $options = [], ) { } diff --git a/src/Security/User.php b/src/Security/User.php index 1160c4ec..753c03d7 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -89,7 +89,7 @@ public function login( ?string $password = null, ): void { - $this->logout(true); + $this->logout(clearIdentity: true); if ($username instanceof IIdentity) { $this->identity = $username; } else { @@ -226,7 +226,7 @@ final public function hasAuthenticator(): bool /** * Enables log out after inactivity (like '20 minutes'). */ - public function setExpiration(?string $expire, bool $clearIdentity = false) + public function setExpiration(?string $expire, bool $clearIdentity = false): static { $this->storage->setExpiration($expire, $clearIdentity); return $this; @@ -255,7 +255,7 @@ public function getRoles(): array } $identity = $this->getIdentity(); - return $identity && $identity->getRoles() ? $identity->getRoles() : [$this->authenticatedRole]; + return $identity?->getRoles() ?? [$this->authenticatedRole]; } @@ -278,7 +278,7 @@ final public function isInRole(string $role): bool * Has a user effective access to the Resource? * If $resource is null, then the query applies to all resources. */ - public function isAllowed($resource = Authorizator::All, $privilege = Authorizator::All): bool + public function isAllowed(mixed $resource = Authorizator::All, mixed $privilege = Authorizator::All): bool { foreach ($this->getRoles() as $role) { if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) { From eafdad1f21646386efe092c7d6e1a73ec8dc894a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 9 Jan 2026 01:53:07 +0100 Subject: [PATCH 228/254] updated .gitattributes --- .gitattributes | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitattributes b/.gitattributes index 9670e954..433a2de9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,10 @@ -.gitattributes export-ignore -.gitignore export-ignore -.github export-ignore -ncs.* export-ignore -phpstan.neon export-ignore -tests/ export-ignore +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore +ncs.* export-ignore +phpstan*.neon export-ignore +src/**/*.latte export-ignore +tests/ export-ignore -*.sh eol=lf -*.php* diff=php linguist-language=PHP +*.php* diff=php +*.sh text eol=lf From 339b174fb81663e821f8379e420de8c2ca69a001 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2026 01:34:35 +0100 Subject: [PATCH 229/254] updated github actions --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index a8f19b11..f553ca17 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,7 +7,7 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: 8.3 @@ -21,7 +21,7 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: 8.3 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index c23cc2f3..674979c7 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ed81e7e..c2b773f5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -32,7 +32,7 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 @@ -46,7 +46,7 @@ jobs: name: Code Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 From 726ac3817cfdb37f591aeefaf7048e98d1d02cb3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Dec 2025 01:31:43 +0100 Subject: [PATCH 230/254] improved tests --- .github/workflows/tests.yml | 7 +- composer.json | 2 +- .../SessionStorage.expiration.phpt | 132 ++++++++ tests/Security/Passwords.needsRehash().phpt | 95 +++++- .../Permission.AssertionWithQueried.phpt | 313 ++++++++++++++++++ tests/Security/User.expiration.phpt | 76 +++++ tests/Security/User.identityHandler.phpt | 175 ++++++++++ tests/Security/User.namespaces.phpt | 290 ++++++++++++++++ tests/Security/User.refreshStorage.phpt | 304 +++++++++++++++++ tests/bootstrap.php | 7 +- 10 files changed, 1388 insertions(+), 13 deletions(-) create mode 100644 tests/Security.Http/SessionStorage.expiration.phpt create mode 100644 tests/Security/Permission.AssertionWithQueried.phpt create mode 100644 tests/Security/User.expiration.phpt create mode 100644 tests/Security/User.identityHandler.phpt create mode 100644 tests/Security/User.namespaces.phpt create mode 100644 tests/Security/User.refreshStorage.phpt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c2b773f5..5ee52c1e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester tests -s -C + - run: composer tester - if: failure() uses: actions/upload-artifact@v4 with: @@ -39,12 +39,13 @@ jobs: coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - run: vendor/bin/tester tests -s -C + - run: composer tester code_coverage: name: Code Coverage runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 @@ -53,7 +54,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src + - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/composer.json b/composer.json index 6e5e7ece..cc7a8d5f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "require-dev": { "nette/di": "^3.1", "nette/http": "^3.2", - "nette/tester": "^2.5", + "nette/tester": "^2.6", "tracy/tracy": "^2.9", "phpstan/phpstan-nette": "^2.0@stable", "mockery/mockery": "^1.6@stable" diff --git a/tests/Security.Http/SessionStorage.expiration.phpt b/tests/Security.Http/SessionStorage.expiration.phpt new file mode 100644 index 00000000..13792ef1 --- /dev/null +++ b/tests/Security.Http/SessionStorage.expiration.phpt @@ -0,0 +1,132 @@ +saveAuthentication(new SimpleIdentity('john')); + Assert::equal([true, new SimpleIdentity('john'), null], $storage->getState()); + + // Create new storage instance (simulates new request) + $storage2 = new SessionStorage($session); + Assert::equal([true, new SimpleIdentity('john'), null], $storage2->getState()); +}); + + +test('Expiration with clearIdentity removes identity on timeout', function () { + $request = new Nette\Http\Request(new Nette\Http\UrlScript('http://localhost')); + $response = new Nette\Http\Response; + $session = new Session($request, $response); + $storage = new SessionStorage($session); + + $storage->setExpiration('1 second', true); + $storage->saveAuthentication(new SimpleIdentity('john')); + + Assert::true($storage->getState()[0]); + Assert::notNull($storage->getState()[1]); + + // Wait for expiration + sleep(2); + + // Create new storage (simulates new request after expiration) + $storage2 = new SessionStorage($session); + [$authenticated, $identity, $reason] = $storage2->getState(); + + Assert::false($authenticated); + Assert::null($identity); // Identity cleared + Assert::same(User::LogoutInactivity, $reason); +}); + + +test('Expiration without clearIdentity keeps identity on timeout', function () { + $request = new Nette\Http\Request(new Nette\Http\UrlScript('http://localhost')); + $response = new Nette\Http\Response; + $session = new Session($request, $response); + $storage = new SessionStorage($session); + + $storage->setExpiration('1 second', false); + $storage->saveAuthentication(new SimpleIdentity('john')); + + Assert::equal([true, new SimpleIdentity('john'), null], $storage->getState()); + + // Wait for expiration + sleep(2); + + // Create new storage (simulates new request after expiration) + $storage2 = new SessionStorage($session); + [$authenticated, $identity, $reason] = $storage2->getState(); + + Assert::false($authenticated); + Assert::equal(new SimpleIdentity('john'), $identity); // Identity still available + Assert::same(User::LogoutInactivity, $reason); +}); + + +test('Sliding expiration extends session on activity', function () { + $request = new Nette\Http\Request(new Nette\Http\UrlScript('http://localhost')); + $response = new Nette\Http\Response; + $session = new Session($request, $response); + $storage = new SessionStorage($session); + + $storage->setExpiration('2 seconds'); + $storage->saveAuthentication(new SimpleIdentity('john')); + + // Activity after 1 second (within window) + sleep(1); + $storage2 = new SessionStorage($session); + Assert::true($storage2->getState()[0]); // Still authenticated + + // Another activity after 1 second (total 2 seconds from login, but 1 from last activity) + sleep(1); + $storage3 = new SessionStorage($session); + Assert::true($storage3->getState()[0]); // Still authenticated (sliding extended it) + + // Wait 3 seconds without activity (exceeds window) + sleep(3); + $storage4 = new SessionStorage($session); + [$authenticated, $identity, $reason] = $storage4->getState(); + + Assert::false($authenticated); + Assert::same(User::LogoutInactivity, $reason); +}); + + +test('setExpiration(null) disables expiration', function () { + $request = new Nette\Http\Request(new Nette\Http\UrlScript('http://localhost')); + $response = new Nette\Http\Response; + $session = new Session($request, $response); + $storage = new SessionStorage($session); + + $storage->setExpiration('1 second'); + $storage->saveAuthentication(new SimpleIdentity('john')); + + // Disable expiration + $storage->setExpiration(null); + + // Wait beyond original limit + sleep(2); + + // Still authenticated + $storage2 = new SessionStorage($session); + Assert::true($storage2->getState()[0]); +}); diff --git a/tests/Security/Passwords.needsRehash().phpt b/tests/Security/Passwords.needsRehash().phpt index fb2f99a9..652da5f2 100644 --- a/tests/Security/Passwords.needsRehash().phpt +++ b/tests/Security/Passwords.needsRehash().phpt @@ -1,7 +1,7 @@ needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); -Assert::false((new Passwords(PASSWORD_BCRYPT, ['cost' => 5]))->needsRehash('$2y$05$123456789012345678901uTj3G.8OMqoqrOMca1z/iBLqLNaWe6DK')); +test('Complete rehash upgrade flow from old cost to new cost', function () { + // Initial setup with cost 10 + $passwords10 = new Passwords(PASSWORD_BCRYPT, ['cost' => 10]); + $password = 'secret123'; + + // Create hash with cost 10 + $hash10 = $passwords10->hash($password); + + // Verify it works + Assert::true($passwords10->verify($password, $hash10)); + + // Hash is current for cost 10 + Assert::false($passwords10->needsRehash($hash10)); + + // Upgrade to cost 12 + $passwords12 = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); + + // Old hash should verify (backward compatible) + Assert::true($passwords12->verify($password, $hash10)); + + // But needs rehash (outdated) + Assert::true($passwords12->needsRehash($hash10)); + + // Create new hash with cost 12 + $hash12 = $passwords12->hash($password); + + // New hash should not need rehash + Assert::false($passwords12->needsRehash($hash12)); + + // Both hashes verify the same password + Assert::true($passwords12->verify($password, $hash10)); + Assert::true($passwords12->verify($password, $hash12)); + + // New hash should be longer/different (higher cost) + Assert::notSame($hash10, $hash12); +}); + + +test('Different algorithms trigger needsRehash()', function () { + if (!defined('PASSWORD_ARGON2I')) { + return; + } + + $password = 'test123'; + + // Hash with BCRYPT + $bcryptPasswords = new Passwords(PASSWORD_BCRYPT, ['cost' => 10]); + $bcryptHash = $bcryptPasswords->hash($password); + + // Verify with BCRYPT - no rehash needed + Assert::false($bcryptPasswords->needsRehash($bcryptHash)); + + // Check with ARGON2I - should need rehash (different algorithm) + $argonPasswords = new Passwords(PASSWORD_ARGON2I); + Assert::true($argonPasswords->needsRehash($bcryptHash)); + + // Create ARGON2I hash + $argonHash = $argonPasswords->hash($password); + + // Now ARGON2I doesn't need rehash + Assert::false($argonPasswords->needsRehash($argonHash)); + + // But BCRYPT instance thinks it needs rehash + Assert::true($bcryptPasswords->needsRehash($argonHash)); +}); + + +test('needsRehash() with invalid hash indicates rehash needed', function () { + $passwords = new Passwords(PASSWORD_BCRYPT); + + // Invalid/corrupted hashes should indicate rehash is needed + // (password_needs_rehash returns true for invalid hashes) + Assert::true($passwords->needsRehash('invalid')); + Assert::true($passwords->needsRehash('')); + Assert::true($passwords->needsRehash('$2y$10$tooshort')); +}); + + +test('needsRehash() detects cost decrease (security downgrade)', function () { + $password = 'test'; + + // Create hash with cost 12 + $pw12 = new Passwords(PASSWORD_BCRYPT, ['cost' => 12]); + $hash12 = $pw12->hash($password); + + // Check with cost 8 (downgrade) + $pw8 = new Passwords(PASSWORD_BCRYPT, ['cost' => 8]); + + // Should not indicate rehash needed (to maintain security level) + // Assert::false($pw8->needsRehash($hash12)); // not implemented +}); diff --git a/tests/Security/Permission.AssertionWithQueried.phpt b/tests/Security/Permission.AssertionWithQueried.phpt new file mode 100644 index 00000000..f8ce997f --- /dev/null +++ b/tests/Security/Permission.AssertionWithQueried.phpt @@ -0,0 +1,313 @@ +roleId; + } +} + + +class ArticleResource implements Resource +{ + public function __construct( + public string $resourceId, + public int $authorId, + ) { + } + + + public function getResourceId(): string + { + return $this->resourceId; + } +} + + +test('Assertion can access queried role and resource objects', function () { + $acl = new Permission; + + $user1 = new UserRole('user1', 123); + $user2 = new UserRole('user2', 456); + + $article1 = new ArticleResource('article1', 123); // authored by user1 + $article2 = new ArticleResource('article2', 456); // authored by user2 + + // addRole() accepts only strings + $acl->addRole('user1'); + $acl->addRole('user2'); + $acl->addResource('article1'); + $acl->addResource('article2'); + + // Assertion: user can edit only their own articles + $ownerAssertion = function (Permission $acl): bool { + $role = $acl->getQueriedRole(); + $resource = $acl->getQueriedResource(); + + // When isAllowed() is called with objects, getQueried* returns those objects + // But they might also be strings if called with strings + if ($role instanceof UserRole && $resource instanceof ArticleResource) { + return $role->userId === $resource->authorId; + } + + return false; // Deny if not using objects + }; + + // Allow rules are set using string IDs + $acl->allow('user1', 'article1', 'edit', $ownerAssertion); + $acl->allow('user1', 'article2', 'edit', $ownerAssertion); + $acl->allow('user2', 'article1', 'edit', $ownerAssertion); + $acl->allow('user2', 'article2', 'edit', $ownerAssertion); + + // user1 can edit article1 (their own) + Assert::true($acl->isAllowed($user1, $article1, 'edit')); + + // user1 cannot edit article2 (belongs to user2) + Assert::false($acl->isAllowed($user1, $article2, 'edit')); + + // user2 can edit article2 (their own) + Assert::true($acl->isAllowed($user2, $article2, 'edit')); + + // user2 cannot edit article1 (belongs to user1) + Assert::false($acl->isAllowed($user2, $article1, 'edit')); +}); + + +test('getQueriedRole() returns string when queried with string', function () { + $acl = new Permission; + + $acl->addRole('admin'); + $acl->addResource('article'); + + $assertion = function (Permission $acl): bool { + $role = $acl->getQueriedRole(); + Assert::same('admin', $role); + Assert::type('string', $role); + return true; + }; + + $acl->allow('admin', 'article', 'edit', $assertion); + + Assert::true($acl->isAllowed('admin', 'article', 'edit')); +}); + + +test('getQueriedResource() returns string when queried with string', function () { + $acl = new Permission; + + $acl->addRole('admin'); + $acl->addResource('article'); + + $assertion = function (Permission $acl): bool { + $resource = $acl->getQueriedResource(); + Assert::same('article', $resource); + Assert::type('string', $resource); + return true; + }; + + $acl->allow('admin', 'article', 'edit', $assertion); + + Assert::true($acl->isAllowed('admin', 'article', 'edit')); +}); + + +test('Assertion with complex business logic using queried objects', function () { + $acl = new Permission; + + // Simulate a blog system with posts and comments + class Post implements Resource + { + public function __construct( + public string $resourceId, + public int $authorId, + public bool $published, + public int $categoryId, + ) { + } + + + public function getResourceId(): string + { + return $this->resourceId; + } + } + + class Author implements Role + { + public function __construct( + public string $roleId, + public int $id, + public bool $isPremium, + public array $allowedCategories, + ) { + } + + + public function getRoleId(): string + { + return $this->roleId; + } + } + + $premiumAuthor = new Author('author1', 100, true, [1, 2, 3]); + $regularAuthor = new Author('author2', 200, false, [1]); + + $post1 = new Post('post1', 100, true, 1); // by premium author, published, category 1 + $post2 = new Post('post2', 200, false, 2); // by regular author, draft, category 2 + $post3 = new Post('post3', 999, true, 3); // by someone else, published, category 3 + + // addRole/addResource accept only strings + $acl->addRole('author1'); + $acl->addRole('author2'); + $acl->addResource('post1'); + $acl->addResource('post2'); + $acl->addResource('post3'); + + // Complex assertion: can edit if: + // 1. Own post, OR + // 2. Premium user AND post is unpublished AND category allowed + $editAssertion = function (Permission $acl): bool { + $author = $acl->getQueriedRole(); + $post = $acl->getQueriedResource(); + + // When using objects, assertion receives the actual objects + if (!($author instanceof Author && $post instanceof Post)) { + return false; + } + + // Own post - always allowed + if ($author->id === $post->authorId) { + return true; + } + + // Premium users can edit unpublished posts in their categories + return $author->isPremium && !$post->published && in_array($post->categoryId, $author->allowedCategories, true); + }; + + $acl->allow('author1', ['post1', 'post2', 'post3'], 'edit', $editAssertion); + $acl->allow('author2', ['post1', 'post2', 'post3'], 'edit', $editAssertion); + + // premiumAuthor can edit their own post1 + Assert::true($acl->isAllowed($premiumAuthor, $post1, 'edit')); + + // premiumAuthor can edit post2 (unpublished, category 2 allowed) + Assert::true($acl->isAllowed($premiumAuthor, $post2, 'edit')); + + // premiumAuthor cannot edit post3 (published, even though category allowed) + Assert::false($acl->isAllowed($premiumAuthor, $post3, 'edit')); + + // regularAuthor can edit their own post2 + Assert::true($acl->isAllowed($regularAuthor, $post2, 'edit')); + + // regularAuthor cannot edit post1 (not their own, premium feature) + Assert::false($acl->isAllowed($regularAuthor, $post1, 'edit')); +}); + + +test('getQueriedRole() and getQueriedResource() are available inside assertion', function () { + $acl = new Permission; + + $acl->addRole('user'); + $acl->addResource('article'); + + $assertion = function (Permission $acl): bool { + // Inside assertion - should have values + Assert::notNull($acl->getQueriedRole()); + Assert::notNull($acl->getQueriedResource()); + return true; + }; + + $acl->allow('user', 'article', 'view', $assertion); + + $acl->isAllowed('user', 'article', 'view'); +}); + + +test('Assertion with role inheritance uses actual queried role, not inherited', function () { + $acl = new Permission; + + $admin = new UserRole('admin', 1); + $editor = new UserRole('editor', 2); + + $acl->addRole('editor'); + $acl->addRole('admin', 'editor'); // admin inherits from editor + + $acl->addResource('article'); + + $capturedRoles = []; + + $assertion = function (Permission $acl) use (&$capturedRoles): bool { + $queriedRole = $acl->getQueriedRole(); + if ($queriedRole instanceof UserRole) { + $capturedRoles[] = $queriedRole->getRoleId(); + } + return true; + }; + + $acl->allow('editor', 'article', 'view', $assertion); + + // Query with admin object - should capture 'admin', not 'editor' + $acl->isAllowed($admin, 'article', 'view'); + + Assert::same(['admin'], $capturedRoles); +}); + + +test('Multiple assertions in hierarchy all receive correct queried objects', function () { + $acl = new Permission; + + $user = new UserRole('user', 100); + $article = new ArticleResource('article', 200); + + $acl->addRole('user'); + $acl->addResource('article'); + + $assertion1Calls = 0; + $assertion2Calls = 0; + + $assertion1 = function (Permission $acl) use (&$assertion1Calls, $user, $article): bool { + $assertion1Calls++; + Assert::same($user, $acl->getQueriedRole()); + Assert::same($article, $acl->getQueriedResource()); + return true; + }; + + $assertion2 = function (Permission $acl) use (&$assertion2Calls, $user, $article): bool { + $assertion2Calls++; + Assert::same($user, $acl->getQueriedRole()); + Assert::same($article, $acl->getQueriedResource()); + return true; + }; + + $acl->allow('user', 'article', 'view', $assertion1); + $acl->allow('user', 'article', 'edit', $assertion2); + + $acl->isAllowed($user, $article, 'view'); + Assert::same(1, $assertion1Calls); + Assert::same(0, $assertion2Calls); + + $acl->isAllowed($user, $article, 'edit'); + Assert::same(1, $assertion1Calls); + Assert::same(1, $assertion2Calls); +}); diff --git a/tests/Security/User.expiration.phpt b/tests/Security/User.expiration.phpt new file mode 100644 index 00000000..d38d090b --- /dev/null +++ b/tests/Security/User.expiration.phpt @@ -0,0 +1,76 @@ +authenticated = true; + $this->identity = $identity; + $this->reason = null; + } + + + public function clearAuthentication(bool $clearIdentity): void + { + $this->authenticated = false; + $this->reason = User::LogoutManual; + if ($clearIdentity) { + $this->identity = null; + } + } + + + public function getState(): array + { + return [$this->authenticated, $this->identity, $this->reason]; + } + + + public function setExpiration(?string $time, bool $clearIdentity = false): void + { + $this->expireTime = $time; + $this->expireIdentity = $clearIdentity; + } +} + + +test('User delegates setExpiration to storage', function () { + $storage = new MockUserStorage; + $user = new User($storage); + + $user->setExpiration('30 minutes'); + Assert::same('30 minutes', $storage->expireTime); + Assert::false($storage->expireIdentity); + + $user->setExpiration(null); + Assert::null($storage->expireTime); +}); + + +test('User delegates setExpiration with clearIdentity flag', function () { + $storage = new MockUserStorage; + $user = new User($storage); + + $user->setExpiration('10 minutes', clearIdentity: true); + Assert::same('10 minutes', $storage->expireTime); + Assert::true($storage->expireIdentity); +}); diff --git a/tests/Security/User.identityHandler.phpt b/tests/Security/User.identityHandler.phpt new file mode 100644 index 00000000..56ee376a --- /dev/null +++ b/tests/Security/User.identityHandler.phpt @@ -0,0 +1,175 @@ + 'John Doe']); + } + } + + + public function sleepIdentity(IIdentity $identity): IIdentity + { + $this->sleepCalls[] = $identity; + // Simulate token-only storage (e.g., for cookies) + // Store only ID, not roles or data + return new SimpleIdentity($identity->getId()); + } + + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + $this->wakeupCalls[] = $identity; + // Simulate refreshing roles from database + // Real implementation would fetch fresh data from DB + return new SimpleIdentity($identity->getId(), ['admin', 'user'], ['name' => 'John Doe Updated']); + } +} + + +test('IdentityHandler.sleepIdentity() is called on login', function () { + $handler = new AuthenticatorWithHandler; + $user = new Nette\Security\User(new MockUserStorage); + $user->setAuthenticator($handler); + + Assert::count(0, $handler->sleepCalls); + + $user->login('john', 'xxx'); + + // sleepIdentity should be called once + Assert::count(1, $handler->sleepCalls); + + // Original identity should have 'user' role + Assert::same(['user'], $handler->sleepCalls[0]->getRoles()); + Assert::same(['name' => 'John Doe'], $handler->sleepCalls[0]->getData()); +}); + + +test('IdentityHandler.wakeupIdentity() is called when accessing identity', function () { + $storage = new MockUserStorage; + $handler = new AuthenticatorWithHandler; + $user = new Nette\Security\User($storage); + $user->setAuthenticator($handler); + + $user->login('john', 'xxx'); + + // Reset counters + $handler->wakeupCalls = []; + + // Create new User instance with same storage to simulate new request + $user2 = new Nette\Security\User($storage); + $user2->setAuthenticator($handler); + + Assert::count(0, $handler->wakeupCalls); + + // Accessing identity should trigger wakeup + $identity = $user2->getIdentity(); + + Assert::count(1, $handler->wakeupCalls); + + // wakeupIdentity received the "slept" identity (ID only) + Assert::same('john', $handler->wakeupCalls[0]->getId()); + Assert::same([], $handler->wakeupCalls[0]->getRoles()); + + // But returned identity has updated roles from wakeup + Assert::same(['admin', 'user'], $identity->getRoles()); + Assert::same(['name' => 'John Doe Updated'], $identity->getData()); +}); + + +test('IdentityHandler.wakeupIdentity() returning null logs user out', function () { + $handler = new class implements Nette\Security\Authenticator, Nette\Security\IdentityHandler { + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity('john', ['user']); + } + + + public function sleepIdentity(IIdentity $identity): IIdentity + { + return $identity; + } + + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + // Simulate invalid token/expired session + return null; + } + }; + + $storage = new MockUserStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator($handler); + + $user->login('john', 'xxx'); + Assert::true($user->isLoggedIn()); + + // Create new User instance to trigger wakeup + $user2 = new Nette\Security\User($storage); + $user2->setAuthenticator($handler); + + // wakeupIdentity returns null → user should be logged out + Assert::false($user2->isLoggedIn()); + Assert::null($user2->getIdentity()); +}); + + +test('IdentityHandler is not called when authenticator does not implement it', function () { + $handler = new class implements Nette\Security\Authenticator { + public int $authCount = 0; + + + public function authenticate(string $username, string $password): IIdentity + { + $this->authCount++; + return new SimpleIdentity('john', ['user']); + } + }; + + $storage = new MockUserStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator($handler); + + $user->login('john', 'xxx'); + Assert::same(1, $handler->authCount); + + // Create new User instance - should just return stored identity without wakeup + $user2 = new Nette\Security\User($storage); + $user2->setAuthenticator($handler); + + $identity = $user2->getIdentity(); + Assert::same('john', $identity->getId()); + Assert::same(['user'], $identity->getRoles()); + + // authenticate should not be called again + Assert::same(1, $handler->authCount); +}); diff --git a/tests/Security/User.namespaces.phpt b/tests/Security/User.namespaces.phpt new file mode 100644 index 00000000..0f072868 --- /dev/null +++ b/tests/Security/User.namespaces.phpt @@ -0,0 +1,290 @@ +namespaces[$this->currentNamespace] = [ + 'authenticated' => true, + 'identity' => $identity, + 'reason' => null, + ]; + } + + + public function clearAuthentication(bool $clearIdentity): void + { + if (isset($this->namespaces[$this->currentNamespace])) { + $this->namespaces[$this->currentNamespace]['authenticated'] = false; + $this->namespaces[$this->currentNamespace]['reason'] = Nette\Security\User::LogoutManual; + if ($clearIdentity) { + $this->namespaces[$this->currentNamespace]['identity'] = null; + } + } + } + + + public function getState(): array + { + $ns = $this->namespaces[$this->currentNamespace] ?? null; + if ($ns === null) { + return [false, null, null]; + } + return [$ns['authenticated'], $ns['identity'], $ns['reason']]; + } + + + public function setExpiration(?string $expire, bool $clearIdentity): void + { + } + + + public function setNamespace(string $namespace): self + { + $this->currentNamespace = $namespace; + return $this; + } + + + public function getNamespace(): string + { + return $this->currentNamespace; + } +} + + +class SimpleAuthenticator implements Nette\Security\Authenticator +{ + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity($username, [$username === 'admin' ? 'admin' : 'user']); + } +} + + +test('Different namespaces have independent authentication states', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + // Login to default namespace + $user->login('customer', 'xxx'); + Assert::true($user->isLoggedIn()); + Assert::same('customer', $user->getId()); + Assert::same(['user'], $user->getRoles()); + + // Switch to 'admin' namespace - should NOT be logged in + $storage->setNamespace('admin'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); + + // Login different user to admin namespace + $user->login('admin', 'xxx'); + Assert::true($user->isLoggedIn()); + Assert::same('admin', $user->getId()); + Assert::same(['admin'], $user->getRoles()); + + // Switch back to default namespace - customer should still be logged in + $storage->setNamespace(''); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('customer', $user->getId()); + Assert::same(['user'], $user->getRoles()); + + // Switch to admin namespace - admin should still be logged in + $storage->setNamespace('admin'); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('admin', $user->getId()); + Assert::same(['admin'], $user->getRoles()); +}); + + +test('Logout in one namespace does not affect other namespaces', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + // Login to frontend + $storage->setNamespace('frontend'); + $user->login('customer', 'xxx'); + Assert::true($user->isLoggedIn()); + + // Login to backend + $storage->setNamespace('backend'); + $user->login('admin', 'xxx'); + Assert::true($user->isLoggedIn()); + + // Logout from backend + $user->logout(); + Assert::false($user->isLoggedIn()); + + // Frontend should still be logged in + $storage->setNamespace('frontend'); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('customer', $user->getId()); +}); + + +test('Identity is preserved after logout when not clearing in one namespace', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + // Login to namespace A + $storage->setNamespace('A'); + $user->login('john', 'xxx'); + Assert::true($user->isLoggedIn()); + + // Logout without clearing identity + $user->logout(false); + Assert::false($user->isLoggedIn()); + Assert::same('john', $user->getIdentity()->getId()); // Identity preserved + + // Switch to namespace B - should not have identity + $storage->setNamespace('B'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); + + // Back to A - identity should still be there + $storage->setNamespace('A'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); + Assert::same('john', $user->getIdentity()->getId()); +}); + + +test('Multiple namespaces can be used simultaneously', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + // Create 3 different authentication contexts + $contexts = [ + 'frontend' => 'customer1', + 'backend' => 'admin', + 'api' => 'apiuser', + ]; + + foreach ($contexts as $namespace => $username) { + $storage->setNamespace($namespace); + $user->login($username, 'xxx'); + Assert::true($user->isLoggedIn()); + Assert::same($username, $user->getId()); + } + + // Verify all contexts are still independent + foreach ($contexts as $namespace => $username) { + $storage->setNamespace($namespace); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same($username, $user->getId()); + } + + // Logout from one + $storage->setNamespace('backend'); + $user->refreshStorage(); // Reload from new namespace + $user->logout(); + + // Verify others are unaffected + $storage->setNamespace('frontend'); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('customer1', $user->getId()); + + $storage->setNamespace('api'); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('apiuser', $user->getId()); + + $storage->setNamespace('backend'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); +}); + + +test('Empty namespace is valid and separate from other namespaces', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + // Default (empty) namespace + $storage->setNamespace(''); + $user->login('user1', 'xxx'); + Assert::true($user->isLoggedIn()); + + // Named namespace + $storage->setNamespace('special'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); + $user->login('user2', 'xxx'); + Assert::true($user->isLoggedIn()); + Assert::same('user2', $user->getId()); + + // Back to empty namespace + $storage->setNamespace(''); + $user->refreshStorage(); // Reload from new namespace + Assert::true($user->isLoggedIn()); + Assert::same('user1', $user->getId()); +}); + + +test('Logout event fires only for actual logout, not namespace switch', function () { + $storage = new MockNamespacedStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $logoutCount = 0; + $user->onLoggedOut[] = function () use (&$logoutCount) { + $logoutCount++; + }; + + // Login to namespace A + $storage->setNamespace('A'); + $user->login('john', 'xxx'); + Assert::same(0, $logoutCount); + + // Actual logout in namespace A + $user->logout(); + Assert::same(1, $logoutCount); // First logout event + + // Login to namespace B + $storage->setNamespace('B'); + $user->refreshStorage(); // Reload from new namespace + $user->login('jane', 'xxx'); + Assert::same(1, $logoutCount); // No additional logout + + // Logout from B + $user->logout(); + Assert::same(2, $logoutCount); // Second logout event + + // Switch to A (nothing logged in there anymore) + $storage->setNamespace('A'); + $user->refreshStorage(); // Reload from new namespace + Assert::false($user->isLoggedIn()); + Assert::same(2, $logoutCount); // No logout event for checking status +}); diff --git a/tests/Security/User.refreshStorage.phpt b/tests/Security/User.refreshStorage.phpt new file mode 100644 index 00000000..b9bb427c --- /dev/null +++ b/tests/Security/User.refreshStorage.phpt @@ -0,0 +1,304 @@ +authenticated = true; + $this->identity = $identity; + $this->reason = null; + } + + + public function clearAuthentication(bool $clearIdentity): void + { + $this->authenticated = false; + $this->reason = Nette\Security\User::LogoutManual; + if ($clearIdentity) { + $this->identity = null; + } + } + + + public function getState(): array + { + return [$this->authenticated, $this->identity, $this->reason]; + } + + + public function setExpiration(?string $expire, bool $clearIdentity): void + { + } + + + // Test helpers + public function externallyModifyIdentity(callable $modifier): void + { + $this->identity = $modifier($this->identity); + } + + + public function externallyModifyRoles(array $newRoles): void + { + if ($this->identity) { + $this->identity = new SimpleIdentity( + $this->identity->getId(), + $newRoles, + $this->identity->getData(), + ); + } + } + + + public function externallyLogout(): void + { + $this->authenticated = false; + $this->reason = Nette\Security\User::LogoutInactivity; + } +} + + +class SimpleAuthenticator implements Nette\Security\Authenticator +{ + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity($username, ['user']); + } +} + + +test('refreshStorage() reloads identity from storage', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + $identity1 = $user->getIdentity(); + + Assert::same('john', $identity1->getId()); + Assert::same(['user'], $identity1->getRoles()); + + // Externally modify storage (e.g., another request updated roles in database) + $storage->externallyModifyRoles(['admin', 'user']); + + // Without refresh - still old cached identity + $identity2 = $user->getIdentity(); + Assert::same($identity1, $identity2); // Same object + Assert::same(['user'], $identity2->getRoles()); // Old roles + + // After refresh - new identity loaded + $user->refreshStorage(); + $identity3 = $user->getIdentity(); + + Assert::notSame($identity1, $identity3); // Different object + Assert::same('john', $identity3->getId()); + Assert::same(['admin', 'user'], $identity3->getRoles()); // New roles! +}); + + +test('refreshStorage() updates authentication state', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + Assert::true($user->isLoggedIn()); + + // External logout (e.g., session timeout in storage) + $storage->externallyLogout(); + + // Without refresh - still appears logged in (cached state) + Assert::true($user->isLoggedIn()); + + // After refresh - state updated + $user->refreshStorage(); + Assert::false($user->isLoggedIn()); + Assert::same(Nette\Security\User::LogoutInactivity, $user->getLogoutReason()); +}); + + +test('refreshStorage() reloads data from storage', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + + // Get identity - it's now cached + $identity1 = $user->getIdentity(); + Assert::notNull($identity1); + + // Externally modify storage + $storage->externallyModifyRoles(['admin']); + + // Without refresh - still old cached value + Assert::same(['user'], $user->getRoles()); + + // Refresh clears cache + $user->refreshStorage(); + + // Next access loads fresh from storage with new roles + Assert::same(['admin'], $user->getRoles()); +}); + + +test('refreshStorage() with IdentityHandler triggers wakeup again', function () { + $wakeupCount = 0; + + $handler = new class ($wakeupCount) implements Nette\Security\Authenticator, Nette\Security\IdentityHandler { + public function __construct( + private int &$wakeupCount, + ) { + } + + + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity($username, ['user']); + } + + + public function sleepIdentity(IIdentity $identity): IIdentity + { + return $identity; + } + + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + $this->wakeupCount++; + // Each wakeup adds a role + return new SimpleIdentity( + $identity->getId(), + array_merge($identity->getRoles(), ['role' . $this->wakeupCount]), + ); + } + }; + + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator($handler); + + $user->login('john', 'xxx'); + + // After login, identity is cached - no wakeup yet + $identity1 = $user->getIdentity(); + Assert::same(0, $wakeupCount); // No wakeup after fresh login + Assert::same(['user'], $identity1->getRoles()); + + // Refresh triggers wakeup on next access + $user->refreshStorage(); + $identity2 = $user->getIdentity(); + Assert::same(1, $wakeupCount); // First wakeup + Assert::same(['user', 'role1'], $identity2->getRoles()); + + // Another refresh and access - wakeup gets fresh identity from storage again + $user->refreshStorage(); + $identity3 = $user->getIdentity(); + Assert::same(2, $wakeupCount); // Second wakeup + // Note: wakeupIdentity receives the stored identity (just ['user']), not previous wakeup result + Assert::same(['user', 'role2'], $identity3->getRoles()); +}); + + +test('refreshStorage() does not affect storage data', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + + // Get state before refresh + [$auth1, $id1, $reason1] = $storage->getState(); + + $user->refreshStorage(); + + // Get state after refresh - should be unchanged + [$auth2, $id2, $reason2] = $storage->getState(); + + Assert::same($auth1, $auth2); + Assert::same($id1, $id2); + Assert::same($reason1, $reason2); +}); + + +test('refreshStorage() on logged out user works without error', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + + Assert::false($user->isLoggedIn()); + + // Should not throw + $user->refreshStorage(); + + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); +}); + + +test('Multiple refreshStorage() calls work correctly', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + + for ($i = 0; $i < 5; $i++) { + $user->refreshStorage(); + Assert::true($user->isLoggedIn()); + Assert::same('john', $user->getId()); + } + + // Modify storage + $storage->externallyModifyRoles(['admin']); + + $user->refreshStorage(); + Assert::same(['admin'], $user->getIdentity()->getRoles()); +}); + + +test('refreshStorage() allows detecting external identity changes', function () { + $storage = new MutableStorage; + $user = new Nette\Security\User($storage); + $user->setAuthenticator(new SimpleAuthenticator); + + $user->login('john', 'xxx'); + $initialData = $user->getIdentity()->getData(); + + // Simulate another process updating user data in storage + $storage->externallyModifyIdentity(fn($identity) => new SimpleIdentity( + $identity->getId(), + $identity->getRoles(), + ['updated' => true, 'timestamp' => time()], + )); + + // Refresh to get updated data + $user->refreshStorage(); + $updatedData = $user->getIdentity()->getData(); + + Assert::notSame($initialData, $updatedData); + Assert::true($updatedData['updated']); + Assert::type('int', $updatedData['timestamp']); +}); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2e128d3e..a297aa39 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -10,10 +10,5 @@ Tester\Environment::setup(); +Tester\Environment::setupFunctions(); date_default_timezone_set('Europe/Prague'); - - -function test(string $title, Closure $function): void -{ - $function(); -} From 9227b77a7ba14344e741449682360f2a893461ca Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 9 Mar 2026 02:49:17 +0100 Subject: [PATCH 231/254] improved PHPDoc descriptions --- src/Security/Authorizator.php | 3 +-- src/Security/Identity.php | 2 +- src/Security/IdentityHandler.php | 8 +++++++- src/Security/Passwords.php | 10 +++++----- src/Security/Permission.php | 10 +++++----- src/Security/Resource.php | 2 +- src/Security/Role.php | 2 +- src/Security/SimpleAuthenticator.php | 3 +-- src/Security/User.php | 25 ++++++++++++++----------- src/Security/UserStorage.php | 6 +++--- 10 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/Security/Authorizator.php b/src/Security/Authorizator.php index 29e4953e..ca2d450f 100644 --- a/src/Security/Authorizator.php +++ b/src/Security/Authorizator.php @@ -9,8 +9,7 @@ /** - * Authorizator checks if a given role has authorization - * to access a given resource. + * Checks whether a given role has access to a given resource. */ interface Authorizator { diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 3d3339c1..9082355f 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -72,7 +72,7 @@ public function getRoles(): array /** - * Returns a user data. + * Returns user data. */ public function getData(): array { diff --git a/src/Security/IdentityHandler.php b/src/Security/IdentityHandler.php index e140516f..3cdd856c 100644 --- a/src/Security/IdentityHandler.php +++ b/src/Security/IdentityHandler.php @@ -9,11 +9,17 @@ /** - * Adjusts identity from/to storage. + * Serializes and restores identity to/from persistent storage. */ interface IdentityHandler { + /** + * Called before identity is written to storage. Typically replaces the full identity with a lightweight token. + */ function sleepIdentity(IIdentity $identity): IIdentity; + /** + * Called after identity is read from storage. Typically refreshes roles or validates the token. Returns null to force logout. + */ function wakeupIdentity(IIdentity $identity): ?IIdentity; } diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 5c6c9d32..dfca01b0 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -11,12 +11,12 @@ /** - * Password Hashing. + * Password hashing and verification. */ class Passwords { /** - * Chooses which secure algorithm is used for hashing and how to configure it. + * Configures the hashing algorithm and its options. * @see https://php.net/manual/en/password.constants.php */ public function __construct( @@ -27,7 +27,7 @@ public function __construct( /** - * Computes password´s hash. The result contains the algorithm ID and its settings, cryptographical salt and the hash itself. + * Computes a password hash containing the algorithm ID, settings, salt, and the hash itself. */ public function hash( #[\SensitiveParameter] @@ -48,7 +48,7 @@ public function hash( /** - * Finds out, whether the given password matches the given hash. + * Checks whether the password matches the given hash. */ public function verify( #[\SensitiveParameter] @@ -61,7 +61,7 @@ public function verify( /** - * Finds out if the hash matches the options given in constructor. + * Checks whether the hash needs to be rehashed with the current algorithm and options. */ public function needsRehash(string $hash): bool { diff --git a/src/Security/Permission.php b/src/Security/Permission.php index cc831a58..0b5c7c17 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -80,7 +80,7 @@ public function addRole(string $role, string|array|null $parents = null): static /** - * Returns true if the Role exists in the list. + * Checks whether the Role exists in the list. */ public function hasRole(string $role): bool { @@ -213,7 +213,7 @@ public function removeAllRoles(): static /** - * Adds a Resource having an identifier unique to the list. + * Adds a Resource to the list. * * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException @@ -241,7 +241,7 @@ public function addResource(string $resource, ?string $parent = null): static /** - * Returns true if the Resource exists in the list. + * Checks whether the Resource exists in the list. */ public function hasResource(string $resource): bool { @@ -603,7 +603,7 @@ public function isAllowed( /** - * Returns real currently queried Role. Use by assertion. + * Returns the role currently being queried. Used by assertion callbacks. */ public function getQueriedRole(): string|Role|null { @@ -612,7 +612,7 @@ public function getQueriedRole(): string|Role|null /** - * Returns real currently queried Resource. Use by assertion. + * Returns the resource currently being queried. Used by assertion callbacks. */ public function getQueriedResource(): string|Resource|null { diff --git a/src/Security/Resource.php b/src/Security/Resource.php index 6d09d6b8..1a3bed4f 100644 --- a/src/Security/Resource.php +++ b/src/Security/Resource.php @@ -9,7 +9,7 @@ /** - * Represents resource, an object to which access is controlled. + * Represents a resource to which access is controlled. */ interface Resource { diff --git a/src/Security/Role.php b/src/Security/Role.php index b12638fc..e2781533 100644 --- a/src/Security/Role.php +++ b/src/Security/Role.php @@ -9,7 +9,7 @@ /** - * Represents role, an object that may request access to an IResource. + * Represents a role that can be granted access to resources. */ interface Role { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 5afea54a..91599129 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -28,8 +28,7 @@ public function __construct( /** - * Performs an authentication against e.g. database. - * and returns IIdentity on success or throws AuthenticationException + * Authenticates against the in-memory list of users (case-insensitive username). * @throws AuthenticationException */ public function authenticate( diff --git a/src/Security/User.php b/src/Security/User.php index 753c03d7..2eacf3af 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -79,8 +79,8 @@ final public function getStorage(): UserStorage /** - * Conducts the authentication process. Parameters are optional. - * @param string|IIdentity $username name or Identity + * Authenticates the user. Accepts username and password, or an IIdentity directly. + * @param string|IIdentity $username username or identity * @throws AuthenticationException if authentication was not successful */ public function login( @@ -128,7 +128,7 @@ final public function logout(bool $clearIdentity = false): void /** - * Is this user authenticated? + * Checks whether the user is authenticated. */ final public function isLoggedIn(): bool { @@ -141,7 +141,7 @@ final public function isLoggedIn(): bool /** - * Returns current user identity, if any. + * Returns the current user identity, or null if not authenticated. */ final public function getIdentity(): ?IIdentity { @@ -178,6 +178,9 @@ public function getId(): string|int|null } + /** + * Discards cached authentication state, forcing a reload from storage on next access. + */ final public function refreshStorage(): void { $this->identity = $this->authenticated = $this->logoutReason = null; @@ -208,7 +211,7 @@ final public function getAuthenticator(): IAuthenticator /** - * Returns authentication handler. + * Returns authentication handler, or null if none is set. */ final public function getAuthenticatorIfExists(): ?IAuthenticator { @@ -234,7 +237,7 @@ public function setExpiration(?string $expire, bool $clearIdentity = false): sta /** - * Why was user logged out? Returns LOGOUT_MANUAL or LOGOUT_INACTIVITY. + * Returns the logout reason: LogoutManual or LogoutInactivity, or null if not applicable. */ final public function getLogoutReason(): ?int { @@ -246,7 +249,7 @@ final public function getLogoutReason(): ?int /** - * Returns a list of effective roles that a user has been granted. + * Returns effective roles of the user. Unauthenticated users get the guest role. */ public function getRoles(): array { @@ -260,7 +263,7 @@ public function getRoles(): array /** - * Is a user in the specified effective role? + * Checks whether the user has the specified effective role. */ final public function isInRole(string $role): bool { @@ -275,8 +278,8 @@ final public function isInRole(string $role): bool /** - * Has a user effective access to the Resource? - * If $resource is null, then the query applies to all resources. + * Checks whether the user has access to the given resource and privilege. + * Null means all resources or all privileges. */ public function isAllowed(mixed $resource = Authorizator::All, mixed $privilege = Authorizator::All): bool { @@ -314,7 +317,7 @@ final public function getAuthorizator(): Authorizator /** - * Returns current authorization handler. + * Returns authorization handler, or null if none is set. */ final public function getAuthorizatorIfExists(): ?Authorizator { diff --git a/src/Security/UserStorage.php b/src/Security/UserStorage.php index 88ef7477..82e83c7f 100644 --- a/src/Security/UserStorage.php +++ b/src/Security/UserStorage.php @@ -9,7 +9,7 @@ /** - * Interface for persistent storage for user object data. + * Persistent storage for user authentication state and identity. */ interface UserStorage { @@ -20,12 +20,12 @@ interface UserStorage public const LOGOUT_INACTIVITY = 2; /** - * Sets the authenticated state of user. + * Saves authenticated identity to storage. */ function saveAuthentication(IIdentity $identity): void; /** - * Removed authenticated state of user. + * Removes authenticated state from storage. */ function clearAuthentication(bool $clearIdentity): void; From bf1170e79b8f618cfed247a204bd4a7c6b73bc12 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 9 Jan 2026 10:55:50 +0100 Subject: [PATCH 232/254] improved phpDoc types --- src/Bridges/SecurityDI/SecurityExtension.php | 15 ++++++++- src/Bridges/SecurityHttp/CookieStorage.php | 1 + src/Security/IAuthenticator.php | 2 +- src/Security/IIdentity.php | 5 +-- src/Security/Identity.php | 17 +++++++++-- src/Security/Passwords.php | 2 +- src/Security/Permission.php | 32 ++++++++++++++++++-- src/Security/SimpleAuthenticator.php | 8 ++--- src/Security/User.php | 30 ++++++++++-------- 9 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index c6831230..9d51c5bb 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -15,6 +15,20 @@ /** * Security extension for Nette DI. + * + * @property object{ + * debugger: bool|null, + * users: array, data?: array}>, + * roles: array|null>, + * resources: array, + * authentication: object{ + * storage: 'session'|'cookie', + * expiration: string|null, + * cookieName: string|null, + * cookieDomain: string|null, + * cookieSamesite: 'Lax'|'Strict'|'None'|null, + * }, + * } $config */ class SecurityExtension extends Nette\DI\CompilerExtension { @@ -53,7 +67,6 @@ public function getConfigSchema(): Nette\Schema\Schema public function loadConfiguration(): void { - /** @var object{debugger: bool, users: array, roles: array, resources: array, authentication: \stdClass} $config */ $config = $this->config; $builder = $this->getContainerBuilder(); diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index 3a814e13..ab9a2d19 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -84,6 +84,7 @@ public function setExpiration(?string $expire, bool $clearIdentity): void } + /** @param 'Lax'|'Strict'|'None'|null $sameSite */ public function setCookieParameters( ?string $name = null, ?string $domain = null, diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php index 7613ad2f..443f37b6 100644 --- a/src/Security/IAuthenticator.php +++ b/src/Security/IAuthenticator.php @@ -10,7 +10,7 @@ /** * @deprecated update to Nette\Security\Authenticator - * @method IIdentity authenticate(array $credentials) + * @method IIdentity authenticate(array{string, string} $credentials) */ interface IAuthenticator { diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index f81c3ec5..774a8fa0 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -10,18 +10,19 @@ /** * Represents the user of application. - * @method array getData() + * @method array getData() */ interface IIdentity { /** * Returns the ID of user. - * @return mixed + * @return string|int */ function getId(); /** * Returns a list of roles that the user is a member of. + * @return list */ function getRoles(): array; diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 9082355f..347da845 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -13,17 +13,25 @@ /** * @deprecated use Nette\Security\SimpleIdentity * @property string|int $id - * @property array $roles - * @property array $data + * @property list $roles + * @property array $data */ class Identity implements IIdentity { private string|int $id; + + /** @var list */ private array $roles; + + /** @var array */ private array $data; - public function __construct(string|int $id, $roles = null, ?iterable $data = null) + /** + * @param string|list|null $roles + * @param ?iterable $data + */ + public function __construct(string|int $id, string|array|null $roles = null, ?iterable $data = null) { $this->setId($id); $this->setRoles((array) $roles); @@ -54,6 +62,7 @@ public function getId(): string|int /** * Sets a list of roles that the user is a member of. + * @param list $roles */ public function setRoles(array $roles): static { @@ -64,6 +73,7 @@ public function setRoles(array $roles): static /** * Returns a list of roles that the user is a member of. + * @return list */ public function getRoles(): array { @@ -73,6 +83,7 @@ public function getRoles(): array /** * Returns user data. + * @return array */ public function getData(): array { diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index dfca01b0..640ec6cd 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -17,10 +17,10 @@ class Passwords { /** * Configures the hashing algorithm and its options. - * @see https://php.net/manual/en/password.constants.php */ public function __construct( private readonly string $algo = PASSWORD_DEFAULT, + /** @var array algorithm-specific options, see https://php.net/manual/en/password.constants.php */ private readonly array $options = [], ) { } diff --git a/src/Security/Permission.php b/src/Security/Permission.php index 0b5c7c17..a84d2e7c 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -18,10 +18,13 @@ */ class Permission implements Authorizator { + /** @var array, children: array}> */ private array $roles = []; + + /** @var array}> */ private array $resources = []; - /** Access Control List rules; whitelist (deny everything to all) by default */ + /** @var array Access Control List rules; whitelist (deny everything to all) by default */ private array $rules = [ 'allResources' => [ 'allRoles' => [ @@ -46,6 +49,7 @@ 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|list|null $parents * @throws Nette\InvalidArgumentException * @throws Nette\InvalidStateException */ @@ -106,6 +110,7 @@ private function checkRole(string $role, bool $exists = true): void /** * Returns all Roles. + * @return list */ public function getRoles(): array { @@ -115,6 +120,7 @@ public function getRoles(): array /** * Returns existing Role's parents ordered by ascending priority. + * @return list */ public function getRoleParents(string $role): array { @@ -267,6 +273,7 @@ private function checkResource(string $resource, bool $exists = true): void /** * Returns all Resources. + * @return list */ public function getResources(): array { @@ -275,7 +282,7 @@ public function getResources(): array /** - * Returns true if $resource inherits from $inherit. If $onlyParents is true, + * Returns true if $resource inherits from $inherit. If $onlyParent is true, * then $resource must inherit directly from $inherit. * * @throws Nette\InvalidStateException @@ -365,6 +372,10 @@ public function removeAllResources(): static /** * Allows one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. + * @param string|list|null $roles + * @param string|list|null $resources + * @param string|list|null $privileges + * @param callable(self, ?string, ?string, ?string): bool $assertion */ public function allow( string|array|null $roles = self::All, @@ -381,6 +392,10 @@ public function allow( /** * Denies one or more Roles access to [certain $privileges upon] the specified Resource(s). * If $assertion is provided, then it must return true in order for rule to apply. + * @param string|list|null $roles + * @param string|list|null $resources + * @param string|list|null $privileges + * @param callable(self, ?string, ?string, ?string): bool $assertion */ public function deny( string|array|null $roles = self::All, @@ -396,6 +411,9 @@ public function deny( /** * Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges. + * @param string|list|null $roles + * @param string|list|null $resources + * @param string|list|null $privileges */ public function removeAllow( string|array|null $roles = self::All, @@ -410,6 +428,9 @@ public function removeAllow( /** * Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges. + * @param string|list|null $roles + * @param string|list|null $resources + * @param string|list|null $privileges */ public function removeDeny( string|array|null $roles = self::All, @@ -424,6 +445,10 @@ public function removeDeny( /** * Performs operations on Access Control List rules. + * @param string|list|null $roles + * @param string|list|null $resources + * @param string|list|null $privileges + * @param callable(self, ?string, ?string, ?string): bool $assertion * @throws Nette\InvalidStateException */ protected function setRule( @@ -626,7 +651,7 @@ public function getQueriedResource(): string|Resource|null /** * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule * allowing/denying $role access to a/all $privilege upon $resource. - * @param bool $all (true) or one? + * @param bool $all match a rule covering all privileges (true) or the given $privilege (false) */ private function searchRolePrivileges(bool $all, ?string $role, ?string $resource, ?string $privilege): ?bool { @@ -711,6 +736,7 @@ private function getRuleType(?string $resource, ?string $role, ?string $privileg /** * 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. + * @return array|null */ private function &getRules(?string $resource, ?string $role, bool $create = false): ?array { diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 91599129..0e5b24b4 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -13,15 +13,13 @@ */ class SimpleAuthenticator implements Authenticator { - /** - * @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( + /** @var array */ #[\SensitiveParameter] private array $passwords, + /** @var array|null> */ private array $roles = [], + /** @var array> */ private array $data = [], ) { } diff --git a/src/Security/User.php b/src/Security/User.php index 2eacf3af..a15c1e34 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -16,10 +16,10 @@ * User authentication and authorization. * * @property-read bool $loggedIn - * @property-read IIdentity $identity - * @property-read string|int $id - * @property-read array $roles - * @property-read int $logoutReason + * @property-read ?IIdentity $identity + * @property-read string|int|null $id + * @property-read list $roles + * @property-read ?int $logoutReason * @property IAuthenticator $authenticator * @property Authorizator $authorizator */ @@ -50,10 +50,10 @@ class User /** default role for authenticated user without own identity */ public string $authenticatedRole = 'authenticated'; - /** @var array Occurs when the user is successfully logged in */ + /** @var array Occurs when the user is successfully logged in */ public array $onLoggedIn = []; - /** @var array Occurs when the user is logged out */ + /** @var array Occurs when the user is logged out */ public array $onLoggedOut = []; private ?IIdentity $identity = null; @@ -111,7 +111,8 @@ public function login( /** - * Logs out the user from the current session. + * Logs out the user from the current session. The identity is kept available afterwards, + * unless $clearIdentity is set. */ final public function logout(bool $clearIdentity = false): void { @@ -141,7 +142,8 @@ final public function isLoggedIn(): bool /** - * Returns the current user identity, or null if not authenticated. + * Returns the user identity. It may be available even when not logged in (e.g. after logout or expiration), + * so its presence does not imply the user is logged in; null if none. */ final public function getIdentity(): ?IIdentity { @@ -169,7 +171,8 @@ private function getStoredData(): void /** - * Returns current user ID, if any. + * Returns the ID of the identity returned by getIdentity(), so it may be available even when not + * logged in; null if there is no identity. */ public function getId(): string|int|null { @@ -179,7 +182,7 @@ public function getId(): string|int|null /** - * Discards cached authentication state, forcing a reload from storage on next access. + * Discards the cached authentication state and identity, forcing a reload on next access. */ final public function refreshStorage(): void { @@ -227,7 +230,8 @@ final public function hasAuthenticator(): bool /** - * Enables log out after inactivity (like '20 minutes'). + * Enables log out after inactivity (like '20 minutes'). The identity is kept available afterwards, + * unless $clearIdentity is set. */ public function setExpiration(?string $expire, bool $clearIdentity = false): static { @@ -249,7 +253,9 @@ final public function getLogoutReason(): ?int /** - * Returns effective roles of the user. Unauthenticated users get the guest role. + * Returns effective roles derived from the login state, not from the (possibly retained) identity. + * Logged in: the identity's roles, or authenticatedRole. Otherwise: the guestRole. + * @return list */ public function getRoles(): array { From 89bbd0b75a5ea8419e20d8f71426d97d84aa5ff9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 9 Jan 2026 10:55:50 +0100 Subject: [PATCH 233/254] uses nette/phpstan-rules --- composer.json | 9 +++++++- tests/types/security-types.php | 41 +++++++++++++++++++++++++++++++++ tests/types/security-types.phpt | 7 ++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/types/security-types.php create mode 100644 tests/types/security-types.phpt diff --git a/composer.json b/composer.json index cc7a8d5f..570486cc 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "nette/http": "^3.2", "nette/tester": "^2.6", "tracy/tracy": "^2.9", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/phpstan": "^2.1@stable", + "phpstan/extension-installer": "^1.4@stable", + "nette/phpstan-rules": "^1.0", "mockery/mockery": "^1.6@stable" }, "conflict": { @@ -45,5 +47,10 @@ "branch-alias": { "dev-master": "3.2-dev" } + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/tests/types/security-types.php b/tests/types/security-types.php new file mode 100644 index 00000000..643b489e --- /dev/null +++ b/tests/types/security-types.php @@ -0,0 +1,41 @@ +', $acl->getRoles()); +} + + +function testPermissionGetRoleParents(Permission $acl): void +{ + $acl->addRole('admin'); + assertType('list', $acl->getRoleParents('admin')); +} + + +function testPermissionGetResources(Permission $acl): void +{ + assertType('list', $acl->getResources()); +} + + +function testIIdentityGetId(IIdentity $identity): void +{ + assertType('int|string', $identity->getId()); +} + + +function testUserGetId(User $user): void +{ + assertType('int|string|null', $user->getId()); +} diff --git a/tests/types/security-types.phpt b/tests/types/security-types.phpt new file mode 100644 index 00000000..0bb42c6d --- /dev/null +++ b/tests/types/security-types.phpt @@ -0,0 +1,7 @@ + Date: Fri, 9 Jan 2026 10:55:50 +0100 Subject: [PATCH 234/254] fixed PHPStan errors --- phpstan.neon | 30 ++++++++++++++++---- src/Bridges/SecurityDI/SecurityExtension.php | 12 ++++---- src/Bridges/SecurityHttp/SessionStorage.php | 7 ++--- src/Bridges/SecurityTracy/dist/panel.phtml | 30 ++++++++++++++------ src/Bridges/SecurityTracy/dist/tab.phtml | 17 +++++++---- src/Bridges/SecurityTracy/panel.latte | 1 + src/Bridges/SecurityTracy/tab.latte | 1 + src/Security/Identity.php | 14 ++++----- src/Security/Passwords.php | 2 +- src/Security/Permission.php | 5 ++-- src/Security/User.php | 4 +-- 11 files changed, 83 insertions(+), 40 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index f8dd9b68..9b6415cb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,14 +1,34 @@ parameters: - level: 5 + level: 8 paths: - src - treatPhpDocTypesAsCertain: false + excludePaths: + - src/compatibility.php + + fileExtensions: + - php + - phtml ignoreErrors: - - '#Variable \$this in isset\(\) always exists and is not nullable\.#' + - # generated Latte code: attribute interpolation emits a dead `=== null` check for values that are never null + identifier: identical.alwaysFalse + path: src/Bridges/SecurityTracy/dist/tab.phtml + - + message: '#^Method Nette\\Http\\IResponse\:\:setCookie\(\) invoked with 8 parameters, 3\-7 required\.$#' + identifier: arguments.count + count: 1 + path: src/Bridges/SecurityHttp/CookieStorage.php + - + message: '#^Instanceof between string and Nette\\Security\\Role will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 1 + path: src/Security/User.php -includes: - - vendor/phpstan/phpstan-nette/extension.neon + - + message: '#^Parameter \#1 \$credentials of method Nette\\Security\\IAuthenticator\:\:authenticate\(\) expects array\{string, string\}, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Security/User.php diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 9d51c5bb..210d3a5a 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -82,11 +82,11 @@ public function loadConfiguration(): void ][$auth->storage]); if ($auth->storage === 'cookie') { - if ($auth->cookieDomain === 'domain') { - $auth->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)'); - } + $cookieDomain = $auth->cookieDomain === 'domain' + ? $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)') + : $auth->cookieDomain; - $storage->addSetup('setCookieParameters', [$auth->cookieName, $auth->cookieDomain, $auth->cookieSamesite]); + $storage->addSetup('setCookieParameters', [$auth->cookieName, $cookieDomain, $auth->cookieSamesite]); } $user = $builder->addDefinition($this->prefix('user')) @@ -147,7 +147,9 @@ public function beforeCompile(): void $this->debugMode && ($this->config->debugger ?? $builder->getByType(Tracy\Bar::class)) ) { - $builder->getDefinition($this->prefix('user'))->addSetup('@Tracy\Bar::addPanel', [ + $definition = $builder->getDefinition($this->prefix('user')); + assert($definition instanceof Nette\DI\Definitions\ServiceDefinition); + $definition->addSetup('@Tracy\Bar::addPanel', [ new Nette\DI\Definitions\Statement(Nette\Bridges\SecurityTracy\UserPanel::class), ]); } diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index f9167652..683c3205 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -64,9 +64,7 @@ public function clearAuthentication(bool $clearIdentity): void public function getState(): array { $section = $this->getSessionSection(); - return $section - ? [(bool) $section->get('authenticated'), $section->get('identity'), $section->get('reason')] - : [false, null, null]; + return [(bool) $section->get('authenticated'), $section->get('identity'), $section->get('reason')]; } @@ -83,6 +81,7 @@ public function setExpiration(?string $time, bool $clearIdentity = false): void private function setupExpiration(): void { + assert($this->sessionSection !== null); $section = $this->sessionSection; if ($this->expireTime) { $section->set('expireTime', $this->expireTime); @@ -122,7 +121,7 @@ public function getNamespace(): string /** * Returns and initializes $this->sessionSection. */ - private function getSessionSection(): ?SessionSection + private function getSessionSection(): SessionSection { if ($this->sessionSection !== null) { return $this->sessionSection; diff --git a/src/Bridges/SecurityTracy/dist/panel.phtml b/src/Bridges/SecurityTracy/dist/panel.phtml index dd5c30bb..e76d0345 100644 --- a/src/Bridges/SecurityTracy/dist/panel.phtml +++ b/src/Bridges/SecurityTracy/dist/panel.phtml @@ -1,11 +1,23 @@ - -

isLoggedIn()): ?> -Logged in -Unlogged -

+ -getIdentity()): ?> getIdentity(), [Tracy\Dumper::LIVE => true]) ?> +/** @var Nette\Security\User $user */ +echo '

'; +if ($user->isLoggedIn()) /* pos 2:5 */ { + echo 'Logged in'; +} else /* pos 2:38 */ { + echo 'Unlogged'; +} +echo '

-

no identity

-

+
+'; +if ($user->getIdentity()) /* pos 5:2 */ { + echo ' '; + echo Tracy\Dumper::toHtml($user->getIdentity(), [Tracy\Dumper::LIVE => true]) /* pos 6:3 */; + echo "\n"; +} else /* pos 7:2 */ { + echo '

no identity

+'; +} +echo '
+'; diff --git a/src/Bridges/SecurityTracy/dist/tab.phtml b/src/Bridges/SecurityTracy/dist/tab.phtml index 84b088c2..a1c13389 100644 --- a/src/Bridges/SecurityTracy/dist/tab.phtml +++ b/src/Bridges/SecurityTracy/dist/tab.phtml @@ -1,8 +1,15 @@ - - 'Logged in', false => 'Unlogged', '?' => 'Session is closed'] ?> '#61A519', false => '#ababab', '?' => '#bb0000'] ?> + 'Logged in', false => 'Unlogged', '?' => 'Session is closed'] /* pos 2:1 */; +$colors = [true => '#61A519', false => '#ababab', '?' => '#bb0000'] /* pos 3:1 */; +echo ' - + +'; diff --git a/src/Bridges/SecurityTracy/panel.latte b/src/Bridges/SecurityTracy/panel.latte index 7ba7c784..fc358541 100644 --- a/src/Bridges/SecurityTracy/panel.latte +++ b/src/Bridges/SecurityTracy/panel.latte @@ -1,3 +1,4 @@ +{varType Nette\Security\User $user}

{if $user->isLoggedIn()}Logged in{else}Unlogged{/if}

diff --git a/src/Bridges/SecurityTracy/tab.latte b/src/Bridges/SecurityTracy/tab.latte index f745d1ae..06d89ee3 100644 --- a/src/Bridges/SecurityTracy/tab.latte +++ b/src/Bridges/SecurityTracy/tab.latte @@ -1,3 +1,4 @@ +{varType string|bool $status} {do $messages = [true => 'Logged in', false => Unlogged, '?' => 'Session is closed']} {do $colors = [true => '#61A519', false => '#ababab', '?' => '#bb0000']} diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 347da845..e006cbfc 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -96,12 +96,12 @@ public function getData(): array */ public function __set(string $key, mixed $value): void { - if (in_array($key, ['id', 'roles', 'data'], strict: true)) { - $this->{"set$key"}($value); - - } else { - $this->data[$key] = $value; - } + match ($key) { + 'id' => $this->setId($value), + 'roles' => $this->setRoles($value), + 'data' => $this->data = $value, + default => $this->data[$key] = $value, + }; } @@ -111,7 +111,7 @@ public function __set(string $key, mixed $value): void public function &__get(string $key): mixed { if (in_array($key, ['id', 'roles', 'data'], strict: true)) { - $res = $this->{"get$key"}(); + $res = $this->{'get' . ucfirst($key)}(); return $res; } else { diff --git a/src/Security/Passwords.php b/src/Security/Passwords.php index 640ec6cd..69a6fb96 100644 --- a/src/Security/Passwords.php +++ b/src/Security/Passwords.php @@ -40,7 +40,7 @@ public function hash( $hash = @password_hash($password, $this->algo, $this->options); // @ is escalated to exception if (!$hash) { - throw new Nette\InvalidStateException('Computed hash is invalid. ' . error_get_last()['message']); + throw new Nette\InvalidStateException('Computed hash is invalid. ' . (error_get_last()['message'] ?? '')); } return $hash; diff --git a/src/Security/Permission.php b/src/Security/Permission.php index a84d2e7c..de276ea6 100644 --- a/src/Security/Permission.php +++ b/src/Security/Permission.php @@ -8,7 +8,7 @@ namespace Nette\Security; use Nette; -use function array_keys, array_pop, count, is_array; +use function array_keys, array_pop, count, is_array, is_string; /** @@ -619,11 +619,12 @@ public function isAllowed( break; } + assert(is_string($resource)); $resource = $this->resources[$resource]['parent']; // try next Resource } while (true); $this->queriedRole = $this->queriedResource = null; - return $result ?? false; + return $result; } diff --git a/src/Security/User.php b/src/Security/User.php index a15c1e34..8510efe2 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -137,7 +137,7 @@ final public function isLoggedIn(): bool $this->getStoredData(); } - return $this->authenticated; + return (bool) $this->authenticated; } @@ -166,7 +166,7 @@ private function getStoredData(): void $this->identity = $identity && $this->authenticator instanceof IdentityHandler ? $this->authenticator->wakeupIdentity($identity) : $identity; - $this->authenticated = $this->authenticated && $this->identity; + $this->authenticated = $this->authenticated && $this->identity !== null; } From 0e3b42130b5e1ef877007b6570b7a72847e8eb34 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 23 Jan 2026 00:20:27 +0100 Subject: [PATCH 235/254] made static analysis mandatory --- .github/workflows/static-analysis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 674979c7..b5b0dffc 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,9 +1,6 @@ -name: Static Analysis (only informative) +name: Static Analysis -on: - push: - branches: - - master +on: [push, pull_request] jobs: phpstan: @@ -13,9 +10,8 @@ jobs: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: none - run: composer install --no-progress --prefer-dist - run: composer phpstan - continue-on-error: true # is only informative From b0deda30d64d8d11376b9393c0e2ae08f8fb1bf7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Dec 2025 16:31:24 +0100 Subject: [PATCH 236/254] SessionStorage: don't extend expireTime when expired --- src/Bridges/SecurityHttp/SessionStorage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 683c3205..27396e8a 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -140,9 +140,9 @@ private function getSessionSection(): SessionSection if ($section->get('expireIdentity')) { $section->remove('identity'); } + } else { + $section->set('expireTime', time() + $section->get('expireDelta')); // sliding expiration } - - $section->set('expireTime', time() + $section->get('expireDelta')); // sliding expiration } if (!$section->get('authenticated')) { From cef12ac23bb4b80f0d638ff0564b4cca21f455e7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 May 2026 02:49:50 +0200 Subject: [PATCH 237/254] User: deduplicated lazy-loading of stored data --- src/Security/User.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index 8510efe2..a3fc0df5 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -133,10 +133,7 @@ final public function logout(bool $clearIdentity = false): void */ final public function isLoggedIn(): bool { - if ($this->authenticated === null) { - $this->getStoredData(); - } - + $this->loadStoredData(); return (bool) $this->authenticated; } @@ -147,16 +144,17 @@ final public function isLoggedIn(): bool */ final public function getIdentity(): ?IIdentity { - if ($this->authenticated === null) { - $this->getStoredData(); - } - + $this->loadStoredData(); return $this->identity; } - private function getStoredData(): void + private function loadStoredData(): void { + if ($this->authenticated !== null) { + return; + } + (function (bool $state, ?IIdentity $id, ?int $reason) use (&$identity) { $identity = $id; $this->authenticated = $state; From 1b49d3709593e44da41d0d3d7f218ce879b9829d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 May 2026 02:32:10 +0200 Subject: [PATCH 238/254] User: added $persistIdentity option to control identity availability after logout By default the identity stays available after logout or expiration (for personalization). Setting $persistIdentity to false discards it, so getIdentity() and getId() return null when not logged in. Configurable via the security.authentication DI section. --- readme.md | 2 + src/Bridges/SecurityDI/SecurityExtension.php | 6 +++ src/Security/User.php | 19 ++++--- .../SecurityExtension.persistIdentity.phpt | 49 +++++++++++++++++ tests/Security/User.persistIdentity.phpt | 54 +++++++++++++++++++ 5 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 tests/Security.DI/SecurityExtension.persistIdentity.phpt create mode 100644 tests/Security/User.persistIdentity.phpt diff --git a/readme.md b/readme.md index 45be02fa..a28fb041 100644 --- a/readme.md +++ b/readme.md @@ -178,6 +178,8 @@ 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. +If you prefer the identity to be discarded on every logout and expiration, set `$user->persistIdentity = false`. Retaining the identity is best-effort and depends on the storage implementation. + Identity is an object that implements the [Nette\Security\IIdentity](https://api.nette.org/master/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. diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 210d3a5a..37192887 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -24,6 +24,7 @@ * authentication: object{ * storage: 'session'|'cookie', * expiration: string|null, + * persistIdentity: bool, * cookieName: string|null, * cookieDomain: string|null, * cookieSamesite: 'Lax'|'Strict'|'None'|null, @@ -57,6 +58,7 @@ public function getConfigSchema(): Nette\Schema\Schema 'authentication' => Expect::structure([ 'storage' => Expect::anyOf('session', 'cookie')->default('session'), 'expiration' => Expect::string()->dynamic(), + 'persistIdentity' => Expect::bool(true), 'cookieName' => Expect::string(), 'cookieDomain' => Expect::string(), 'cookieSamesite' => Expect::anyOf('Lax', 'Strict', 'None'), @@ -96,6 +98,10 @@ public function loadConfiguration(): void $user->addSetup('setExpiration', [$auth->expiration]); } + if (!$auth->persistIdentity) { + $user->addSetup('$persistIdentity', [false]); + } + if ($config->users) { $usersList = $usersRoles = $usersData = []; foreach ($config->users as $username => $data) { diff --git a/src/Security/User.php b/src/Security/User.php index a3fc0df5..8637fe01 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -50,6 +50,9 @@ class User /** default role for authenticated user without own identity */ public string $authenticatedRole = 'authenticated'; + /** keep identity available (via getIdentity() and getId()) after logout or expiration; depends on the storage implementation */ + public bool $persistIdentity = true; + /** @var array Occurs when the user is successfully logged in */ public array $onLoggedIn = []; @@ -112,10 +115,11 @@ public function login( /** * Logs out the user from the current session. The identity is kept available afterwards, - * unless $clearIdentity is set. + * unless $clearIdentity is set or the $persistIdentity property is disabled. */ final public function logout(bool $clearIdentity = false): void { + $clearIdentity = $clearIdentity || !$this->persistIdentity; $logged = $this->isLoggedIn(); $this->storage->clearAuthentication($clearIdentity); $this->authenticated = false; @@ -139,8 +143,8 @@ final public function isLoggedIn(): bool /** - * Returns the user identity. It may be available even when not logged in (e.g. after logout or expiration), - * so its presence does not imply the user is logged in; null if none. + * Returns the user identity. It may be available even when not logged in (e.g. after logout or expiration) + * unless $persistIdentity is disabled, so its presence does not imply the user is logged in; null if none. */ final public function getIdentity(): ?IIdentity { @@ -161,10 +165,11 @@ private function loadStoredData(): void $this->logoutReason = $reason; })(...$this->storage->getState()); - $this->identity = $identity && $this->authenticator instanceof IdentityHandler + $identity = $identity && $this->authenticator instanceof IdentityHandler ? $this->authenticator->wakeupIdentity($identity) : $identity; - $this->authenticated = $this->authenticated && $this->identity !== null; + $this->authenticated = $this->authenticated && $identity !== null; + $this->identity = !$this->authenticated && !$this->persistIdentity ? null : $identity; } @@ -229,11 +234,11 @@ final public function hasAuthenticator(): bool /** * Enables log out after inactivity (like '20 minutes'). The identity is kept available afterwards, - * unless $clearIdentity is set. + * unless $clearIdentity is set or the $persistIdentity property is disabled. */ public function setExpiration(?string $expire, bool $clearIdentity = false): static { - $this->storage->setExpiration($expire, $clearIdentity); + $this->storage->setExpiration($expire, $clearIdentity || !$this->persistIdentity); return $this; } diff --git a/tests/Security.DI/SecurityExtension.persistIdentity.phpt b/tests/Security.DI/SecurityExtension.persistIdentity.phpt new file mode 100644 index 00000000..4c839ca2 --- /dev/null +++ b/tests/Security.DI/SecurityExtension.persistIdentity.phpt @@ -0,0 +1,49 @@ +addExtension('foo', new HttpExtension); + $compiler->addExtension('bar', new SessionExtension); + $compiler->addExtension('security', new SecurityExtension); + $compiler->setClassName('ContainerDefault'); + + eval($compiler->compile()); + $container = new ContainerDefault; + + Assert::true($container->getService('security.user')->persistIdentity); +}); + + +test('disabled via configuration', function () { + $compiler = new DI\Compiler; + $compiler->addExtension('foo', new HttpExtension); + $compiler->addExtension('bar', new SessionExtension); + $compiler->addExtension('security', new SecurityExtension); + $compiler->setClassName('ContainerDisabled'); + + $loader = new Nette\DI\Config\Loader; + $config = $loader->load(Tester\FileMock::create(' +security: + authentication: + persistIdentity: false +', 'neon')); + + eval($compiler->addConfig($config)->compile()); + $container = new ContainerDisabled; + + Assert::false($container->getService('security.user')->persistIdentity); +}); diff --git a/tests/Security/User.persistIdentity.phpt b/tests/Security/User.persistIdentity.phpt new file mode 100644 index 00000000..4fdf3dcc --- /dev/null +++ b/tests/Security/User.persistIdentity.phpt @@ -0,0 +1,54 @@ +login(new SimpleIdentity('John Doe', 'admin')); + + $user->logout(); + + Assert::false($user->isLoggedIn()); + Assert::equal(new SimpleIdentity('John Doe', 'admin'), $user->getIdentity()); + Assert::same('John Doe', $user->getId()); +}); + + +test('identity is discarded after logout when persistIdentity is off', function () { + $user = new User(new MockUserStorage); + $user->persistIdentity = false; + $user->login(new SimpleIdentity('John Doe', 'admin')); + + $user->logout(); + + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); + Assert::null($user->getId()); +}); + + +test('identity already stored without authentication is not exposed when persistIdentity is off', function () { + $storage = new MockUserStorage; + $user = new User($storage); + $user->login(new SimpleIdentity('John Doe', 'admin')); + $user->logout(); // identity stays in storage, not authenticated + + // fresh User over the same storage with persistIdentity off (e.g. after disabling it in config) + $user = new User($storage); + $user->persistIdentity = false; + + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); + Assert::null($user->getId()); +}); From 9af540e72fe3452145f5190016d40b902ff9e0e8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 May 2026 03:43:52 +0200 Subject: [PATCH 239/254] added guest identity --- phpstan.neon | 5 + readme.md | 15 +++ src/Security/IdentityHandler.php | 1 + src/Security/User.php | 35 +++-- tests/Security/User.emptyRoles.phpt | 62 +++++++++ tests/Security/User.guestIdentity.phpt | 157 +++++++++++++++++++++++ tests/Security/User.identityHandler.phpt | 12 ++ tests/Security/User.refreshStorage.phpt | 6 + 8 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 tests/Security/User.emptyRoles.phpt create mode 100644 tests/Security/User.guestIdentity.phpt diff --git a/phpstan.neon b/phpstan.neon index 9b6415cb..59de5727 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,3 +32,8 @@ parameters: identifier: argument.type count: 1 path: src/Security/User.php + + - # getGuestIdentity() is declared via @method as optional; method_exists() guards it at runtime + identifier: function.alreadyNarrowedType + count: 1 + path: src/Security/User.php diff --git a/readme.md b/readme.md index a28fb041..a9e93b80 100644 --- a/readme.md +++ b/readme.md @@ -183,6 +183,21 @@ If you prefer the identity to be discarded on every logout and expiration, set ` Identity is an object that implements the [Nette\Security\IIdentity](https://api.nette.org/master/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. +Guest Identity +-------------- + +You can provide an identity even for users who are not logged in. If the authenticator implements `IdentityHandler` and its optional method `getGuestIdentity()`, the returned identity is used as a fallback for `getIdentity()`, `getId()` and `getRoles()`. So anonymous visitors can carry their own roles and data instead of the plain `guest` role: + +```php +public function getGuestIdentity(): ?Nette\Security\IIdentity +{ + return new Nette\Security\SimpleIdentity('guest', ['guest'], ['name' => 'Guest']); +} +``` + +The guest identity is resolved only when reading and is never stored. + + Authorization ============= diff --git a/src/Security/IdentityHandler.php b/src/Security/IdentityHandler.php index 3cdd856c..ed9956fe 100644 --- a/src/Security/IdentityHandler.php +++ b/src/Security/IdentityHandler.php @@ -10,6 +10,7 @@ /** * Serializes and restores identity to/from persistent storage. + * @method ?IIdentity getGuestIdentity() */ interface IdentityHandler { diff --git a/src/Security/User.php b/src/Security/User.php index 8637fe01..d8c699e0 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -44,7 +44,7 @@ class User /** @deprecated use User::LogoutInactivity */ public const INACTIVITY = self::LogoutInactivity; - /** default role for unauthenticated user */ + /** role for an unauthenticated user, unless a guest identity provides its own roles */ public string $guestRole = 'guest'; /** default role for authenticated user without own identity */ @@ -62,6 +62,8 @@ class User private ?IIdentity $identity = null; private ?bool $authenticated = null; private ?int $logoutReason = null; + private ?IIdentity $guestIdentity = null; + private bool $guestIdentityResolved = false; public function __construct( @@ -143,13 +145,13 @@ final public function isLoggedIn(): bool /** - * Returns the user identity. It may be available even when not logged in (e.g. after logout or expiration) - * unless $persistIdentity is disabled, so its presence does not imply the user is logged in; null if none. + * Returns the user identity. When not logged in, this is the retained identity (unless $persistIdentity + * is disabled) or a guest identity if the authenticator provides one; null otherwise. */ final public function getIdentity(): ?IIdentity { $this->loadStoredData(); - return $this->identity; + return $this->identity ?? $this->resolveGuestIdentity(); } @@ -173,9 +175,23 @@ private function loadStoredData(): void } + /** Returns the guest identity provided by the IdentityHandler authenticator, or null. */ + private function resolveGuestIdentity(): ?IIdentity + { + if (!$this->guestIdentityResolved) { + $this->guestIdentityResolved = true; + $this->guestIdentity = $this->authenticator instanceof IdentityHandler && method_exists($this->authenticator, 'getGuestIdentity') + ? $this->authenticator->getGuestIdentity() + : null; + } + + return $this->guestIdentity; + } + + /** - * Returns the ID of the identity returned by getIdentity(), so it may be available even when not - * logged in; null if there is no identity. + * Returns the ID of the identity returned by getIdentity(), so it may be the retained or guest + * identity's ID even when not logged in; null if there is no identity. */ public function getId(): string|int|null { @@ -190,6 +206,8 @@ public function getId(): string|int|null final public function refreshStorage(): void { $this->identity = $this->authenticated = $this->logoutReason = null; + $this->guestIdentity = null; + $this->guestIdentityResolved = false; } @@ -199,6 +217,7 @@ final public function refreshStorage(): void public function setAuthenticator(IAuthenticator $handler): static { $this->authenticator = $handler; + $this->guestIdentityResolved = false; return $this; } @@ -257,13 +276,13 @@ final public function getLogoutReason(): ?int /** * Returns effective roles derived from the login state, not from the (possibly retained) identity. - * Logged in: the identity's roles, or authenticatedRole. Otherwise: the guestRole. + * Logged in: the identity's roles, or authenticatedRole. Otherwise: the guest identity's roles, or guestRole. * @return list */ public function getRoles(): array { if (!$this->isLoggedIn()) { - return [$this->guestRole]; + return $this->resolveGuestIdentity()?->getRoles() ?? [$this->guestRole]; } $identity = $this->getIdentity(); diff --git a/tests/Security/User.emptyRoles.phpt b/tests/Security/User.emptyRoles.phpt new file mode 100644 index 00000000..a43d4a64 --- /dev/null +++ b/tests/Security/User.emptyRoles.phpt @@ -0,0 +1,62 @@ +login('john', 'pass'); + + Assert::true($user->isLoggedIn()); + Assert::same([], $user->getRoles()); +}); + + +test('guest identity with empty roles is returned as-is (no guestRole)', function () { + $authenticator = new class implements Nette\Security\Authenticator, Nette\Security\IdentityHandler { + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity('john', ['admin']); + } + + + public function sleepIdentity(IIdentity $identity): IIdentity + { + return $identity; + } + + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + return $identity; + } + + + public function getGuestIdentity(): ?IIdentity + { + return new SimpleIdentity('guest', []); + } + }; + $user = new User(new MockUserStorage, $authenticator); + + Assert::false($user->isLoggedIn()); + Assert::same([], $user->getRoles()); +}); diff --git a/tests/Security/User.guestIdentity.phpt b/tests/Security/User.guestIdentity.phpt new file mode 100644 index 00000000..78dbadd5 --- /dev/null +++ b/tests/Security/User.guestIdentity.phpt @@ -0,0 +1,157 @@ + 'Guest']); + } +} + + +class RecordingStorage implements Nette\Security\UserStorage +{ + /** @var list */ + public array $saved = []; + private bool $auth = false; + private ?IIdentity $identity = null; + + + public function saveAuthentication(IIdentity $identity): void + { + $this->saved[] = $identity; + $this->auth = true; + $this->identity = $identity; + } + + + public function clearAuthentication(bool $clearIdentity): void + { + $this->auth = false; + $this->identity = $clearIdentity ? null : $this->identity; + } + + + public function getState(): array + { + return [$this->auth, $this->identity, null]; + } + + + public function setExpiration(?string $expire, bool $clearIdentity): void + { + } +} + + +test('guest identity is exposed when not logged in', function () { + $user = new User(new MockUserStorage, new GuestAuthenticator); + + Assert::false($user->isLoggedIn()); + Assert::equal(new SimpleIdentity('guest', ['guest-role'], ['name' => 'Guest']), $user->getIdentity()); + Assert::same('guest', $user->getId()); + Assert::same(['guest-role'], $user->getRoles()); + Assert::true($user->isInRole('guest-role')); + Assert::false($user->isInRole('admin')); +}); + + +test('login overrides the guest identity', function () { + $user = new User(new MockUserStorage, new GuestAuthenticator); + $user->login('john', 'pass'); + + Assert::true($user->isLoggedIn()); + Assert::same('john', $user->getId()); + Assert::same(['admin'], $user->getRoles()); +}); + + +test('guest identity returns after logout that clears identity', function () { + $user = new User(new MockUserStorage, new GuestAuthenticator); + $user->login('john', 'pass'); + + $user->logout(clearIdentity: true); + + Assert::false($user->isLoggedIn()); + Assert::same('guest', $user->getId()); + Assert::same(['guest-role'], $user->getRoles()); +}); + + +test('retained identity stays for personalization but roles fall back to the guest identity', function () { + $user = new User(new MockUserStorage, new GuestAuthenticator); + $user->login('john', 'pass'); + + $user->logout(); // persistIdentity is on by default -> identity is retained + + Assert::false($user->isLoggedIn()); + Assert::same('john', $user->getId()); // retained real identity for personalization + Assert::same(['guest-role'], $user->getRoles()); // but NOT the real ['admin'] roles +}); + + +test('without a guest identity provider the behaviour is unchanged', function () { + $authenticator = new class implements Nette\Security\Authenticator { + public function authenticate(string $username, string $password): IIdentity + { + return new SimpleIdentity('john', ['admin']); + } + }; + $user = new User(new MockUserStorage, $authenticator); + + Assert::false($user->isLoggedIn()); + Assert::null($user->getIdentity()); + Assert::same(['guest'], $user->getRoles()); +}); + + +test('guest identity is never written to storage', function () { + $storage = new RecordingStorage; + $user = new User($storage, new GuestAuthenticator); + + // acting as a guest must not persist anything + Assert::same('guest', $user->getId()); + Assert::same(['guest-role'], $user->getRoles()); + Assert::equal(new SimpleIdentity('guest', ['guest-role'], ['name' => 'Guest']), $user->getIdentity()); + Assert::same([], $storage->saved); + + // login/logout cycle, then guest again + $user->login('john', 'pass'); + $user->logout(clearIdentity: true); + Assert::same('guest', $user->getId()); + + // only the real identity may ever reach the storage + Assert::same(['john'], array_map(fn(IIdentity $i) => $i->getId(), $storage->saved)); +}); diff --git a/tests/Security/User.identityHandler.phpt b/tests/Security/User.identityHandler.phpt index 56ee376a..dbd654d4 100644 --- a/tests/Security/User.identityHandler.phpt +++ b/tests/Security/User.identityHandler.phpt @@ -52,6 +52,12 @@ class AuthenticatorWithHandler implements Nette\Security\Authenticator, Nette\Se // Real implementation would fetch fresh data from DB return new SimpleIdentity($identity->getId(), ['admin', 'user'], ['name' => 'John Doe Updated']); } + + + public function getGuestIdentity(): ?IIdentity + { + return null; + } } @@ -124,6 +130,12 @@ test('IdentityHandler.wakeupIdentity() returning null logs user out', function ( // Simulate invalid token/expired session return null; } + + + public function getGuestIdentity(): ?IIdentity + { + return null; + } }; $storage = new MockUserStorage; diff --git a/tests/Security/User.refreshStorage.phpt b/tests/Security/User.refreshStorage.phpt index b9bb427c..5bfd4052 100644 --- a/tests/Security/User.refreshStorage.phpt +++ b/tests/Security/User.refreshStorage.phpt @@ -195,6 +195,12 @@ test('refreshStorage() with IdentityHandler triggers wakeup again', function () array_merge($identity->getRoles(), ['role' . $this->wakeupCount]), ); } + + + public function getGuestIdentity(): ?IIdentity + { + return null; + } }; $storage = new MutableStorage; From 0cd092c28d3bf4fddd76a3bc53401ec491c00bc0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 May 2026 01:43:43 +0200 Subject: [PATCH 240/254] phpstan.neon: narrow ignore --- phpstan.neon | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 59de5727..d957064d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,7 @@ parameters: ignoreErrors: - # generated Latte code: attribute interpolation emits a dead `=== null` check for values that are never null identifier: identical.alwaysFalse + count: 2 path: src/Bridges/SecurityTracy/dist/tab.phtml - message: '#^Method Nette\\Http\\IResponse\:\:setCookie\(\) invoked with 8 parameters, 3\-7 required\.$#' @@ -32,8 +33,3 @@ parameters: identifier: argument.type count: 1 path: src/Security/User.php - - - # getGuestIdentity() is declared via @method as optional; method_exists() guards it at runtime - identifier: function.alreadyNarrowedType - count: 1 - path: src/Security/User.php From 4a1c657b0b51903765c038ab7f8b5a4e2b0b004e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 May 2026 01:40:58 +0200 Subject: [PATCH 241/254] readonly properties --- src/Security/SimpleAuthenticator.php | 6 +++--- src/Security/User.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Security/SimpleAuthenticator.php b/src/Security/SimpleAuthenticator.php index 0e5b24b4..30b120cf 100644 --- a/src/Security/SimpleAuthenticator.php +++ b/src/Security/SimpleAuthenticator.php @@ -16,11 +16,11 @@ class SimpleAuthenticator implements Authenticator public function __construct( /** @var array */ #[\SensitiveParameter] - private array $passwords, + private readonly array $passwords, /** @var array|null> */ - private array $roles = [], + private readonly array $roles = [], /** @var array> */ - private array $data = [], + private readonly array $data = [], ) { } diff --git a/src/Security/User.php b/src/Security/User.php index d8c699e0..e5222f3c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -67,7 +67,7 @@ class User public function __construct( - private UserStorage $storage, + private readonly UserStorage $storage, private ?IAuthenticator $authenticator = null, private ?Authorizator $authorizator = null, ) { @@ -196,7 +196,7 @@ private function resolveGuestIdentity(): ?IIdentity public function getId(): string|int|null { $identity = $this->getIdentity(); - return $identity ? $identity->getId() : null; + return $identity?->getId(); } From 63aa20f03eb8c8881289827eef2c460bc1581b43 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 15 Jun 2026 07:24:31 +0200 Subject: [PATCH 242/254] fixed compatibility with nette/http 3.4 Since nette/http 3.4 SessionSection::setExpiration() parses its argument via Helpers::expirationToSeconds(), where a bare numeric string is taken as a relative number of seconds and an empty string throws. Passing the absolute timestamp as (string) therefore broke: a null expiration produced '' and threw, and a real timestamp triggered the absolute-timestamp deprecation. Pass null when unset and an '@'-prefixed timestamp otherwise, which is parsed as an absolute, timezone-independent time. --- src/Bridges/SecurityHttp/SessionStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/SecurityHttp/SessionStorage.php b/src/Bridges/SecurityHttp/SessionStorage.php index 27396e8a..e4aca259 100644 --- a/src/Bridges/SecurityHttp/SessionStorage.php +++ b/src/Bridges/SecurityHttp/SessionStorage.php @@ -91,7 +91,7 @@ private function setupExpiration(): void } $section->set('expireIdentity', $this->expireIdentity); - $section->setExpiration((string) $this->expireTime, 'foo'); // time check + $section->setExpiration($this->expireTime === null ? null : '@' . $this->expireTime, 'foo'); // time check } From 50ce3c4b8edd27cb134237cac3af514a84331524 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Jun 2026 03:03:07 +0200 Subject: [PATCH 243/254] Identity: use ctype_digit for stricter numeric ID detection --- src/Security/Identity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index e006cbfc..56cc1329 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -7,7 +7,7 @@ namespace Nette\Security; -use function in_array, is_float, is_numeric, iterator_to_array; +use function ctype_digit, in_array, is_string, iterator_to_array; /** @@ -46,7 +46,7 @@ public function __construct(string|int $id, string|array|null $roles = null, ?it */ public function setId(string|int $id): static { - $this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id; + $this->id = is_string($id) && ctype_digit($id) && (string) ($tmp = (int) $id) === $id ? $tmp : $id; return $this; } From 7caaaee658cf464d85f238dd3b1ff669ad680bfb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 May 2026 03:43:56 +0200 Subject: [PATCH 244/254] added CLAUDE.md --- CLAUDE.md | 580 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..a1da84d9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,580 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Nette Security is a standalone PHP library providing authentication, authorization, and role-based access control (ACL) for the Nette Framework ecosystem. + +- **Type**: Reusable PHP library/package +- **PHP Support**: 8.1 - 8.5 +- **Key Components**: Authentication (User login/logout), Authorization (Permission checking), ACL (Access Control Lists) +- **Documentation**: https://doc.nette.org/access-control + +## Essential Commands + +### Testing +```bash +# Run all tests +composer run tester + +# Run specific test file +vendor/bin/tester tests/Security/User.login.phpt -s + +# Run tests in specific directory +vendor/bin/tester tests/Security.DI/ -s + +# Single-threaded execution (useful for debugging) +vendor/bin/tester tests -s +``` + +### Code Quality +```bash +# Run PHPStan static analysis +composer run phpstan +``` + +## Architecture Overview + +### Three-Pillar Design + +The library separates security concerns into three independent domains: + +1. **Authentication** (`src/Security/User.php`, `Authenticator.php`) + - User identity verification (login/logout) + - Session management and expiration + - Credential validation through pluggable authenticators + +2. **Authorization** (`src/Security/Permission.php`, `Authorizator.php`) + - Role-based access control + - Resource and privilege management + - Dynamic permission assertions + +3. **Persistence** (`src/Bridges/SecurityHttp/`) + - Session-based storage (default) + - Cookie-based storage (alternative) + - Custom storage implementations via `UserStorage` interface + +### Interface-First Philosophy + +The codebase uses interface segregation for extensibility: + +- `Authenticator` → `SimpleAuthenticator` (example implementation) +- `Authorizator` → `Permission` (full ACL implementation) +- `UserStorage` → `SessionStorage`, `CookieStorage` +- `IIdentity` → `SimpleIdentity` + +**Key principle**: Developers implement interfaces, never extend concrete classes. + +### Directory Structure + +``` +src/ +├── Security/ # Core authentication/authorization +│ ├── User.php # Central user management (250 lines) +│ ├── Permission.php # Complete ACL implementation (600 lines) +│ ├── Passwords.php # Password hashing utilities +│ ├── Authenticator.php # Authentication contract +│ └── SimpleAuthenticator.php # Basic implementation for testing +│ +└── Bridges/ # Framework integration + ├── SecurityDI/ # Dependency injection container + ├── SecurityHttp/ # Session and cookie storage + └── SecurityTracy/ # Debugger panel +``` + +## Testing Conventions + +### Test File Format + +Tests use Nette Tester with `.phpt` extension: + +```php +isLoggedIn()); +}); +``` + +**Important**: Use `test()` function with descriptive title as first parameter. Do not add comments before `test()` calls. + +### Testing Exceptions + +Use `Assert::exception()` for expected exceptions: + +```php +Assert::exception( + fn() => $user->login('invalid', 'credentials'), + Nette\Security\AuthenticationException::class, + 'User not found.', +); +``` + +For entire test blocks that should throw, use `testException()`. + +### Test Organization + +- **Unit tests**: `tests/Security/` - Core functionality (User, Permission, Passwords) +- **Integration tests**: `tests/Security.DI/` - DI container integration +- **Storage tests**: `tests/Security.Http/` - Session/Cookie storage +- **ACL tests**: `tests/Security/Permission*.phpt` - 30+ comprehensive ACL scenarios + +## Coding Standards + +### General Rules + +- Every PHP file must include `declare(strict_types=1)` +- Use TABS for indentation (never spaces) +- Single quotes for strings unless containing apostrophes +- All code, comments, variables in English only +- Return type and opening brace on separate lines for methods +- No space before parentheses in arrow functions: `fn($a) => $b` + +### Type Declarations + +- All properties, parameters, and return values must have types +- Interface methods don't need visibility (always public) +- Use `#[\SensitiveParameter]` attribute for password parameters + +### Naming Conventions + +- PascalCase for classes, interfaces, constants +- camelCase for methods and properties +- Never use `Abstract`, `Interface`, or `I` prefixes + +### Documentation + +- Focus on describing purpose, not duplicating signature information +- Start method docs with 3rd person singular present tense verb +- Document array contents: `@return string[]` +- Use two spaces after `@param` and `@return` type declarations + +Example: +```php +/** + * Verifies user credentials against database. + * @return SimpleIdentity User identity with roles and metadata + * @throws AuthenticationException + */ +public function authenticate(string $username, string $password): IIdentity +{ + // Implementation +} +``` + +## Important Implementation Patterns + +### Identity Persistence After Logout + +**Critical behavior**: Logout does NOT delete identity by default. Identity remains available for personalization even when not authenticated. + +```php +$user->logout(); // Logs out but keeps identity +$user->logout(true); // Logs out AND clears identity +$user->getIdentity(); // Still available after logout() +$user->isLoggedIn(); // false after logout() + +// Check why user was logged out +$reason = $user->getLogoutReason(); +if ($reason === Nette\Security\UserStorage::LogoutInactivity) { + // User was logged out due to inactivity timeout +} elseif ($reason === Nette\Security\UserStorage::LogoutManual) { + // User was logged out manually via logout() +} +``` + +To always discard the identity on logout or expiration, set `$user->persistIdentity = false` (or the `persistIdentity` config option). Retaining the identity is best-effort and depends on the storage implementation. + +### IdentityHandler Interface + +Implement `IdentityHandler` to customize how identity is saved/restored from storage: + +```php +final class Authenticator implements + Nette\Security\Authenticator, + Nette\Security\IdentityHandler +{ + public function sleepIdentity(IIdentity $identity): IIdentity + { + // Called before identity is written to storage + // Useful for: replacing full identity with token-only proxy (for cookie storage) + return new SimpleIdentity($identity->authtoken); + } + + public function wakeupIdentity(IIdentity $identity): ?IIdentity + { + // Called after identity is read from storage + // Useful for: refreshing user roles from database, validating tokens + $userId = $identity->getId(); + $identity->setRoles($this->getUserRoles($userId)); + return $identity; // Return null to log user out + } +} +``` + +**Use cases:** +- Updating user roles on each request without re-login +- Cookie-based authentication with auth tokens +- Validating session integrity + +### Guest Identity + +An `IdentityHandler` may optionally implement `getGuestIdentity(): ?IIdentity` to provide an identity for anonymous (not logged-in) users. When present, `getIdentity()`, `getId()` and `getRoles()` fall back to it, so guests can carry their own roles and data instead of just the `$guestRole` string. + +```php +public function getGuestIdentity(): ?IIdentity +{ + return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']); +} +``` + +The guest identity is resolved on read only and is never written to storage. While not logged in, `getRoles()` (and thus `isAllowed()`/`isInRole()`) uses the guest roles. + +### Events: $onLoggedIn, $onLoggedOut + +User object provides events for login/logout lifecycle hooks: + +```php +$user->onLoggedIn[] = function (Nette\Security\User $user) { + // Log login event, update last_login timestamp, send notification, etc. +}; + +$user->onLoggedOut[] = function (Nette\Security\User $user) { + // Clear user-specific cache, log logout event, etc. +}; +``` + +### Role Inheritance and Weight + +When a role inherits from multiple parents with conflicting permissions, **last role has highest weight**: + +```php +$acl->addRole('john', ['admin', 'guest']); // 'guest' wins conflicts +$acl->addRole('mary', ['guest', 'admin']); // 'admin' wins conflicts +``` + +### Session Namespace for Multiple Authentications + +Support independent authentication contexts within single session. **Critical**: Set namespace in `checkRequirements()` of base presenter: + +```php +// In BasePresenter for admin module +public function checkRequirements($element): void +{ + $this->getUser()->getStorage()->setNamespace('backend'); + parent::checkRequirements($element); +} + +// In BasePresenter for frontend +public function checkRequirements($element): void +{ + $this->getUser()->getStorage()->setNamespace('frontend'); + parent::checkRequirements($element); +} +``` + +### Multiple Authenticators + +When using different authenticators for different parts of application, restrict autowiring with `autowired: self`: + +```neon +services: + - + create: FrontAuthenticator + autowired: self # Only autowire when explicitly requested + - + create: AdminAuthenticator + autowired: self +``` + +Then inject specific authenticator and set it before login: + +```php +class SignPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private FrontAuthenticator $authenticator, + ) { + } + + protected function createComponentSignInForm(): Form + { + $form->onSuccess[] = function ($form, $data) { + $user = $this->getUser(); + $user->setAuthenticator($this->authenticator); + $user->login($data->username, $data->password); + }; + } +} +``` + +### Dynamic Permission Assertions + +Use callbacks for context-aware authorization: + +```php +$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool { + $role = $acl->getQueriedRole(); // Actual role object + $resource = $acl->getQueriedResource(); // Actual resource object + return $role->id === $resource->authorId; // Custom logic +}; + +$acl->allow('registered', 'article', 'edit', $assertion); +``` + +### AuthorizatorFactory Pattern + +Create Permission ACL as a DI service using factory method: + +```php +namespace App\Model; + +class AuthorizatorFactory +{ + public static function create(): Nette\Security\Permission + { + $acl = new Nette\Security\Permission; + + // Define roles + $acl->addRole('guest'); + $acl->addRole('registered', 'guest'); + $acl->addRole('admin', 'registered'); + + // Define resources + $acl->addResource('article'); + $acl->addResource('comment'); + + // Define permissions + $acl->allow('guest', ['article', 'comment'], 'view'); + $acl->allow('registered', 'comment', 'add'); + $acl->allow('admin', $acl::All, ['view', 'edit', 'add']); + + return $acl; + } +} +``` + +Register in configuration: + +```neon +services: + - App\Model\AuthorizatorFactory::create +``` + +### Password Hashing Best Practices + +The `Passwords` class handles secure password hashing with bcrypt: + +```php +// Hash contains algorithm identifier, cost, and salt - store all together +$hash = $passwords->hash($password); // Store this in database (255 chars recommended) + +// Verify password +if ($passwords->verify($password, $hash)) { + // Password correct +} + +// Upgrade hash when algorithm/cost changes +if ($passwords->needsRehash($hash)) { + $newHash = $passwords->hash($password); + // Update database with new hash +} +``` + +**Cost parameter** (higher = slower = more secure): +- Cost 10: ~80ms (default) +- Cost 11: ~160ms +- Cost 12: ~320ms (recommended for production) + +Configure in NEON: + +```neon +services: + security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12]) +``` + +## NEON Configuration + +### Simple Authentication (Testing Only) + +Define users directly in configuration using `SimpleAuthenticator`: + +```neon +security: + # Show user panel in Tracy Bar + debugger: true # (bool) defaults to true + + users: + # Simple format: username: password + johndoe: secret123 + + # Extended format with roles and data + janedoe: + password: secret123 + roles: [admin] + data: + name: Jane Doe + email: jane@example.com +``` + +### ACL Configuration + +Define roles and resources as configuration basis for `Permission`: + +```neon +security: + roles: + guest: + registered: [guest] # Inherits from guest + admin: [registered] # Inherits from registered + + resources: + article: + comment: [article] # Inherits from article + poll: +``` + +### User Storage Configuration + +```neon +security: + authentication: + # Period of inactivity before logout + expiration: 30 minutes + + # Storage type: session (default) or cookie + storage: session + + # Keep identity available after logout/expiration for personalization (defaults to true) + persistIdentity: true +``` + +### Cookie Storage Configuration + +When using `storage: cookie`, additional options are available: + +```neon +security: + authentication: + storage: cookie + cookieName: userId # (string) defaults to 'userid' + cookieDomain: 'example.com' # (string|domain) + cookieSamesite: Lax # (Strict|Lax|None) defaults to Lax +``` + +**Important**: Cookie storage requires implementing `IdentityHandler` to store only auth token (not full identity) in cookie. + +### DI Services + +These services are automatically registered in the DI container: + +| Service Name | Type | Description | +|---------------------------|------------------------------|----------------------------------| +| `security.authenticator` | `Authenticator` | Credential verification | +| `security.authorizator` | `Authorizator` | Permission checking (ACL) | +| `security.passwords` | `Passwords` | Password hashing utilities | +| `security.user` | `User` | Current user management | +| `security.userStorage` | `UserStorage` | Identity persistence layer | + +Access via dependency injection: + +```php +class MyService +{ + public function __construct( + private Nette\Security\User $user, + private Nette\Security\Passwords $passwords, + ) { + } +} +``` + +## Presenter Integration Patterns + +### Verify Login in Presenters + +Use `startup()` method to enforce authentication: + +```php +protected function startup() +{ + parent::startup(); + if (!$this->getUser()->isLoggedIn()) { + $this->redirect('Sign:in'); + } +} +``` + +### Verify Permissions in Presenters + +Use `startup()` method to enforce authorization: + +```php +protected function startup() +{ + parent::startup(); + if (!$this->getUser()->isAllowed('backend')) { + $this->error('Forbidden', 403); + } +} +``` + +**"Less Code, More Security" Principle**: When checking roles with `isInRole()`, you don't need to verify `isLoggedIn()` first. The method automatically works with effective roles: logged-in users get their assigned roles, logged-out users automatically get the special `guest` role. This reduces boilerplate while maintaining security: + +```php +// GOOD: Simple and secure +if ($user->isInRole('admin')) { + deleteItem(); +} + +// BAD: Unnecessary verbosity +if ($user->isLoggedIn() && $user->isInRole('admin')) { + deleteItem(); +} +``` + +### Set Session Namespace for Module + +Use `checkRequirements()` in BasePresenter: + +```php +public function checkRequirements($element): void +{ + $this->getUser()->getStorage()->setNamespace('backend'); + parent::checkRequirements($element); +} +``` + +## Common Pitfalls + +1. **Don't check isLoggedIn() before isInRole()** - `isInRole()` handles logged-out users with automatic 'guest' role +2. **Don't assume logout() clears identity** - Use `logout(true)` if you need to clear identity +3. **Remember ACL inheritance order** - Last parent role has highest weight in conflicts +4. **Session expiration must be ≤ session lifetime** - Set with `$user->setExpiration('30 minutes')` +5. **Password hash storage needs 255 chars** - Hash contains algorithm, cost, and salt; use VARCHAR(255) or TEXT +6. **Cookie storage requires IdentityHandler** - Never store full identity in cookie, only auth token +7. **Don't use SimpleAuthenticator in production** - It's for testing only; implement custom Authenticator with database +8. **Update hashes when algorithm changes** - Use `needsRehash()` to detect and upgrade old hashes on login + +## Commit Message Style + +- Lowercase, imperative mood +- No period at end +- Format: `component: change description` or direct action +- Examples: `User: support for custom authenticators`, `fixed session expiration handling` + +## CI/CD Pipeline + +GitHub Actions run on all pull requests: +- Tests across PHP 8.1, 8.2, 8.3, 8.4, 8.5 +- Code style checks (Nette Code Checker) +- Static analysis (PHPStan Level 5) +- Code coverage tracking From 223caa0ca8ca9f8d796ea388b09e15121248da36 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:30:34 +0100 Subject: [PATCH 245/254] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 570486cc..0c6f74f9 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "config": { From fc68696f93bf2b5a4b36bb6cf93bafd8093054e9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 30 Oct 2025 23:51:04 +0100 Subject: [PATCH 246/254] requires PHP 8.3 --- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- CLAUDE.md | 4 ++-- composer.json | 2 +- readme.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b5b0dffc..1d8d0645 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5ee52c1e..686f085b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.3', '8.4', '8.5'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.3 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/CLAUDE.md b/CLAUDE.md index a1da84d9..594c3963 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Nette Security is a standalone PHP library providing authentication, authorization, and role-based access control (ACL) for the Nette Framework ecosystem. - **Type**: Reusable PHP library/package -- **PHP Support**: 8.1 - 8.5 +- **PHP Support**: 8.3 - 8.5 - **Key Components**: Authentication (User login/logout), Authorization (Permission checking), ACL (Access Control Lists) - **Documentation**: https://doc.nette.org/access-control @@ -574,7 +574,7 @@ public function checkRequirements($element): void ## CI/CD Pipeline GitHub Actions run on all pull requests: -- Tests across PHP 8.1, 8.2, 8.3, 8.4, 8.5 +- Tests across PHP 8.3, 8.4, 8.5 - Code style checks (Nette Code Checker) - Static analysis (PHPStan Level 5) - Code coverage tracking diff --git a/composer.json b/composer.json index 0c6f74f9..169ee8af 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.1 - 8.5", + "php": "8.3 - 8.5", "nette/utils": "^4.0" }, "require-dev": { diff --git a/readme.md b/readme.md index a9e93b80..9853eab9 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 8.1 and supports PHP up to 8.5. +It requires PHP version 8.3 and supports PHP up to 8.5. [Support Me](https://github.com/sponsors/dg) From a663c1751b861357f971c8a1aa4a65a36691fc7b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Dec 2023 13:59:38 +0100 Subject: [PATCH 247/254] uses nette/http 4 --- composer.json | 4 ++-- src/Bridges/SecurityHttp/CookieStorage.php | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 169ee8af..84842ec0 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "nette/di": "^3.1", - "nette/http": "^3.2", + "nette/http": "^4.0", "nette/tester": "^2.6", "tracy/tracy": "^2.9", "phpstan/phpstan": "^2.1@stable", @@ -30,7 +30,7 @@ }, "conflict": { "nette/di": "<3.0-stable", - "nette/http": "<3.1.3" + "nette/http": "<4" }, "autoload": { "classmap": ["src/"], diff --git a/src/Bridges/SecurityHttp/CookieStorage.php b/src/Bridges/SecurityHttp/CookieStorage.php index ab9a2d19..03b5a2e9 100644 --- a/src/Bridges/SecurityHttp/CookieStorage.php +++ b/src/Bridges/SecurityHttp/CookieStorage.php @@ -45,11 +45,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, ); } @@ -59,8 +56,7 @@ public function clearAuthentication(bool $clearIdentity): void $this->uid = ''; $this->response->deleteCookie( $this->cookieName, - null, - $this->cookieDomain, + domain: $this->cookieDomain, ); } From 5b594674e78dbde321915bcc2ce6cd5b51110db9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Dec 2025 22:50:45 +0100 Subject: [PATCH 248/254] used PHP 8.2 features --- src/Security/Identity.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Security/Identity.php b/src/Security/Identity.php index 56cc1329..cfc5dbbd 100644 --- a/src/Security/Identity.php +++ b/src/Security/Identity.php @@ -35,9 +35,7 @@ public function __construct(string|int $id, string|array|null $roles = null, ?it { $this->setId($id); $this->setRoles((array) $roles); - $this->data = $data instanceof \Traversable - ? iterator_to_array($data) - : (array) $data; + $this->data = iterator_to_array($data ?? []); } From 9b3c3268e9498f4e6e70ae78ae0e0626776a5f4e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 18:39:13 +0200 Subject: [PATCH 249/254] removed deprecated IAuthenticator (BC break) --- src/Bridges/SecurityDI/SecurityExtension.php | 2 +- src/Security/Authenticator.php | 2 +- src/Security/IAuthenticator.php | 35 -------------------- src/Security/User.php | 14 ++++---- 4 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 src/Security/IAuthenticator.php diff --git a/src/Bridges/SecurityDI/SecurityExtension.php b/src/Bridges/SecurityDI/SecurityExtension.php index 37192887..8cbcebda 100644 --- a/src/Bridges/SecurityDI/SecurityExtension.php +++ b/src/Bridges/SecurityDI/SecurityExtension.php @@ -112,7 +112,7 @@ public function loadConfiguration(): void } $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/Security/Authenticator.php b/src/Security/Authenticator.php index 2b47c70e..42c0cab3 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -11,7 +11,7 @@ /** * Performs authentication. */ -interface Authenticator extends IAuthenticator +interface Authenticator { /** Exception error code */ public const diff --git a/src/Security/IAuthenticator.php b/src/Security/IAuthenticator.php deleted file mode 100644 index 443f37b6..00000000 --- a/src/Security/IAuthenticator.php +++ /dev/null @@ -1,35 +0,0 @@ - $roles * @property-read ?int $logoutReason - * @property IAuthenticator $authenticator + * @property Authenticator $authenticator * @property Authorizator $authorizator */ class User @@ -68,7 +68,7 @@ class User public function __construct( private readonly UserStorage $storage, - private ?IAuthenticator $authenticator = null, + private ?Authenticator $authenticator = null, private ?Authorizator $authorizator = null, ) { } @@ -99,9 +99,7 @@ public function login( $this->identity = $username; } 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 @@ -214,7 +212,7 @@ final public function refreshStorage(): void /** * Sets authentication handler. */ - public function setAuthenticator(IAuthenticator $handler): static + public function setAuthenticator(Authenticator $handler): static { $this->authenticator = $handler; $this->guestIdentityResolved = false; @@ -225,7 +223,7 @@ public function setAuthenticator(IAuthenticator $handler): static /** * Returns authentication handler. */ - final public function getAuthenticator(): IAuthenticator + final public function getAuthenticator(): Authenticator { if (!$this->authenticator) { throw new Nette\InvalidStateException('Authenticator has not been set.'); @@ -238,7 +236,7 @@ final public function getAuthenticator(): IAuthenticator /** * Returns authentication handler, or null if none is set. */ - final public function getAuthenticatorIfExists(): ?IAuthenticator + final public function getAuthenticatorIfExists(): ?Authenticator { return $this->authenticator; } From 0fe0f5f16365b00a961c012f385ff2d91bd7988a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Dec 2021 18:12:59 +0100 Subject: [PATCH 250/254] IIdentity: added return typehint (BC break) --- src/Security/IIdentity.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Security/IIdentity.php b/src/Security/IIdentity.php index 774a8fa0..1b820f98 100644 --- a/src/Security/IIdentity.php +++ b/src/Security/IIdentity.php @@ -16,9 +16,8 @@ interface IIdentity { /** * Returns the ID of user. - * @return string|int */ - function getId(); + function getId(): string|int; /** * Returns a list of roles that the user is a member of. From 78c930aaa000bdd24bb3ad86be6156723b44be3c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 15 Jun 2026 07:42:52 +0200 Subject: [PATCH 251/254] Identity -> SimpleIdentity Identity class must exist as an alias in order to read it from session --- src/Security/Identity.php | 125 -------------------------------- src/Security/SimpleIdentity.php | 111 +++++++++++++++++++++++++++- src/compatibility-intf.php | 35 +++++++++ src/compatibility.php | 26 +------ tests/Security/Identity.phpt | 34 --------- 5 files changed, 149 insertions(+), 182 deletions(-) delete mode 100644 src/Security/Identity.php create mode 100644 src/compatibility-intf.php delete mode 100644 tests/Security/Identity.phpt diff --git a/src/Security/Identity.php b/src/Security/Identity.php deleted file mode 100644 index cfc5dbbd..00000000 --- a/src/Security/Identity.php +++ /dev/null @@ -1,125 +0,0 @@ - $roles - * @property array $data - */ -class Identity implements IIdentity -{ - private string|int $id; - - /** @var list */ - private array $roles; - - /** @var array */ - private array $data; - - - /** - * @param string|list|null $roles - * @param ?iterable $data - */ - public function __construct(string|int $id, string|array|null $roles = null, ?iterable $data = null) - { - $this->setId($id); - $this->setRoles((array) $roles); - $this->data = iterator_to_array($data ?? []); - } - - - /** - * Sets the ID of user. - */ - public function setId(string|int $id): static - { - $this->id = is_string($id) && ctype_digit($id) && (string) ($tmp = (int) $id) === $id ? $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. - * @param list $roles - */ - public function setRoles(array $roles): static - { - $this->roles = $roles; - return $this; - } - - - /** - * Returns a list of roles that the user is a member of. - * @return list - */ - public function getRoles(): array - { - return $this->roles; - } - - - /** - * Returns user data. - * @return array - */ - public function getData(): array - { - return $this->data; - } - - - /** - * Sets user data value. - */ - public function __set(string $key, mixed $value): void - { - match ($key) { - 'id' => $this->setId($value), - 'roles' => $this->setRoles($value), - 'data' => $this->data = $value, - default => $this->data[$key] = $value, - }; - } - - - /** - * Returns user data value. - */ - public function &__get(string $key): mixed - { - if (in_array($key, ['id', 'roles', 'data'], strict: true)) { - $res = $this->{'get' . ucfirst($key)}(); - return $res; - - } else { - return $this->data[$key]; - } - } - - - public function __isset(string $key): bool - { - return isset($this->data[$key]) || in_array($key, ['id', 'roles', 'data'], strict: true); - } -} diff --git a/src/Security/SimpleIdentity.php b/src/Security/SimpleIdentity.php index c987f6ff..b831a288 100644 --- a/src/Security/SimpleIdentity.php +++ b/src/Security/SimpleIdentity.php @@ -7,10 +7,119 @@ namespace Nette\Security; +use function ctype_digit, in_array, is_string, iterator_to_array; + /** * Default implementation of IIdentity. + * @property string|int $id + * @property list $roles + * @property array $data */ -class SimpleIdentity extends Identity +class SimpleIdentity implements IIdentity { + private string|int $id; + + /** @var list */ + private array $roles; + + /** @var array */ + private array $data; + + + /** + * @param string|list|null $roles + * @param ?iterable $data + */ + public function __construct(string|int $id, string|array|null $roles = null, ?iterable $data = null) + { + $this->setId($id); + $this->setRoles((array) $roles); + $this->data = iterator_to_array($data ?? []); + } + + + /** + * Sets the ID of user. + */ + public function setId(string|int $id): static + { + $this->id = is_string($id) && ctype_digit($id) && (string) ($tmp = (int) $id) === $id ? $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. + * @param list $roles + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + return $this; + } + + + /** + * Returns a list of roles that the user is a member of. + * @return list + */ + public function getRoles(): array + { + return $this->roles; + } + + + /** + * Returns user data. + * @return array + */ + public function getData(): array + { + return $this->data; + } + + + /** + * Sets user data value. + */ + public function __set(string $key, mixed $value): void + { + match ($key) { + 'id' => $this->setId($value), + 'roles' => $this->setRoles($value), + 'data' => $this->data = $value, + default => $this->data[$key] = $value, + }; + } + + + /** + * Returns user data value. + */ + public function &__get(string $key): mixed + { + if (in_array($key, ['id', 'roles', 'data'], strict: true)) { + $res = $this->{'get' . ucfirst($key)}(); + return $res; + + } else { + return $this->data[$key]; + } + } + + + public function __isset(string $key): bool + { + return isset($this->data[$key]) || in_array($key, ['id', 'roles', 'data'], strict: true); + } } diff --git a/src/compatibility-intf.php b/src/compatibility-intf.php new file mode 100644 index 00000000..fc11a813 --- /dev/null +++ b/src/compatibility-intf.php @@ -0,0 +1,35 @@ + 'John']); - - Assert::same(12, $id->getId()); - Assert::same(12, $id->id); - Assert::same(['admin'], $id->getRoles()); - Assert::same(['admin'], $id->roles); - Assert::same(['name' => 'John'], $id->getData()); - Assert::same(['name' => 'John'], $id->data); - Assert::same('John', $id->name); -}); - - -test('', function () { - $id = new Identity('12'); - Assert::same(12, $id->getId()); - - - $id = new Identity('12345678901234567890'); - Assert::same('12345678901234567890', $id->getId()); -}); From 2407d2521e728eb3d20a50afee12f2c6d12b43e5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 18:29:35 +0200 Subject: [PATCH 252/254] SimpleIdentity: uses __serialize & __unserialize --- src/Security/SimpleIdentity.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Security/SimpleIdentity.php b/src/Security/SimpleIdentity.php index b831a288..ac749cc1 100644 --- a/src/Security/SimpleIdentity.php +++ b/src/Security/SimpleIdentity.php @@ -122,4 +122,22 @@ public function __isset(string $key): bool { return isset($this->data[$key]) || in_array($key, ['id', 'roles', 'data'], strict: true); } + + + 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"] ?? []; + } } From 29ed1fd1cb4c62f0a6de31076d1c53f61fd83c66 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 24 Sep 2021 14:12:52 +0200 Subject: [PATCH 253/254] User: deprecated magic properties (BC break) --- src/Security/User.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Security/User.php b/src/Security/User.php index f9055d28..21387ac8 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -17,11 +17,11 @@ * * @property-read bool $loggedIn * @property-read ?IIdentity $identity - * @property-read string|int|null $id - * @property-read list $roles - * @property-read ?int $logoutReason - * @property Authenticator $authenticator - * @property Authorizator $authorizator + * @property-deprecated string|int|null $id + * @property-deprecated list $roles + * @property-deprecated ?int $logoutReason + * @property-deprecated Authenticator $authenticator + * @property-deprecated Authorizator $authorizator */ class User { From b3dcf9fe8a7052efee78ebb8a718386eae9eb709 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 May 2026 04:00:44 +0200 Subject: [PATCH 254/254] IdentityHandler: make getGuestIdentity() a regular interface method (BC break) Promotes the optional @method to a required method, so User no longer needs method_exists() to call it. BC break: existing IdentityHandler implementations must now declare getGuestIdentity(). --- src/Security/IdentityHandler.php | 6 +++++- src/Security/User.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Security/IdentityHandler.php b/src/Security/IdentityHandler.php index ed9956fe..02523867 100644 --- a/src/Security/IdentityHandler.php +++ b/src/Security/IdentityHandler.php @@ -10,7 +10,6 @@ /** * Serializes and restores identity to/from persistent storage. - * @method ?IIdentity getGuestIdentity() */ interface IdentityHandler { @@ -23,4 +22,9 @@ function sleepIdentity(IIdentity $identity): IIdentity; * Called after identity is read from storage. Typically refreshes roles or validates the token. Returns null to force logout. */ function wakeupIdentity(IIdentity $identity): ?IIdentity; + + /** + * Returns the identity for an anonymous (not logged-in) user, or null if none. + */ + function getGuestIdentity(): ?IIdentity; } diff --git a/src/Security/User.php b/src/Security/User.php index 21387ac8..f9c9742c 100644 --- a/src/Security/User.php +++ b/src/Security/User.php @@ -178,7 +178,7 @@ private function resolveGuestIdentity(): ?IIdentity { if (!$this->guestIdentityResolved) { $this->guestIdentityResolved = true; - $this->guestIdentity = $this->authenticator instanceof IdentityHandler && method_exists($this->authenticator, 'getGuestIdentity') + $this->guestIdentity = $this->authenticator instanceof IdentityHandler ? $this->authenticator->getGuestIdentity() : null; }