no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
@@ -0,0 +1,96 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\SecurityDI;
use Nette;
/**
* Security extension for Nette DI.
*/
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]
];
/** @var bool */
private $debugMode;
public function __construct($debugMode = false)
{
$this->debugMode = $debugMode;
}
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);
$user = $builder->addDefinition($this->prefix('user'))
->setFactory(Nette\Security\User::class);
if ($this->debugMode && $config['debugger']) {
$user->addSetup('@Tracy\Bar::addPanel', [
new Nette\DI\Statement(Nette\Bridges\SecurityTracy\UserPanel::class),
]);
}
if ($config['users']) {
$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"));
$usersList[$username] = $data['password'];
$usersRoles[$username] = isset($data['roles']) ? $data['roles'] : null;
}
$builder->addDefinition($this->prefix('authenticator'))
->setClass(Nette\Security\IAuthenticator::class)
->setFactory(Nette\Security\SimpleAuthenticator::class, [$usersList, $usersRoles]);
if ($this->name === 'security') {
$builder->addAlias('nette.authenticator', $this->prefix('authenticator'));
}
}
if ($config['roles'] || $config['resources']) {
$authorizator = $builder->addDefinition($this->prefix('authorizator'))
->setClass(Nette\Security\IAuthorizator::class)
->setFactory(Nette\Security\Permission::class);
foreach ($config['roles'] as $role => $parents) {
$authorizator->addSetup('addRole', [$role, $parents]);
}
foreach ($config['resources'] as $resource => $parents) {
$authorizator->addSetup('addResource', [$resource, $parents]);
}
if ($this->name === 'security') {
$builder->addAlias('nette.authorizator', $this->prefix('authorizator'));
}
}
if ($this->name === 'security') {
$builder->addAlias('user', $this->prefix('user'));
$builder->addAlias('nette.userStorage', $this->prefix('userStorage'));
}
}
}
@@ -0,0 +1,59 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\SecurityTracy;
use Nette;
use Tracy;
/**
* User panel for Debugger Bar.
*/
class UserPanel implements Tracy\IBarPanel
{
use Nette\SmartObject;
/** @var Nette\Security\User */
private $user;
public function __construct(Nette\Security\User $user)
{
$this->user = $user;
}
/**
* Renders tab.
* @return string
*/
public function getTab()
{
if (headers_sent() && !session_id()) {
return;
}
ob_start(function () {});
$user = $this->user;
require __DIR__ . '/templates/UserPanel.tab.phtml';
return ob_get_clean();
}
/**
* Renders panel.
* @return string
*/
public function getPanel()
{
ob_start(function () {});
$user = $this->user;
require __DIR__ . '/templates/UserPanel.panel.phtml';
return ob_get_clean();
}
}
@@ -0,0 +1,12 @@
<?php
namespace Nette\Bridges\SecurityTracy;
use Tracy\Dumper;
?>
<div class="nette-UserPanel">
<h1><?php if ($user->isLoggedIn()): ?>Logged in<?php else: ?>Unlogged<?php endif ?></h1>
<?php if ($user->getIdentity()): echo Dumper::toHtml($user->getIdentity(), [Dumper::LIVE => true]); else: ?><p>no identity</p><?php endif ?>
</div>
@@ -0,0 +1,8 @@
<?php
namespace Nette\Bridges\SecurityTracy;
?>
<span title="<?= $user->isLoggedIn() ? 'Logged in' : 'Unlogged' ?>">
<svg viewBox="0 -50 2048 2048"><path fill="<?= $user->isLoggedIn() ? '#61A519' : '#ababab' ?>" d="m1615 1803.5c-122 17-246 7-369 8-255 1-510 3-765-1-136-2-266-111-273-250-11-192 11-290.5 115-457.5 62-100 192-191 303-147 110 44 201 130 321 149 160 25 317-39 446-130 82-58 200-9 268 51 157 173 186.8 275.49 184 484.49-1.9692 147.11-108.91 271.41-230 293zm-144-1226.5c0 239-208 447-447 447s-447-208-447-447 208-447 447-447c240 1 446 207 447 447z"/></svg>
</span>
@@ -0,0 +1,16 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Authentication exception.
*/
class AuthenticationException extends \Exception
{
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Performs authentication.
*/
interface IAuthenticator
{
/** Credential key */
const
USERNAME = 0,
PASSWORD = 1;
/** Exception error code */
const
IDENTITY_NOT_FOUND = 1,
INVALID_CREDENTIAL = 2,
FAILURE = 3,
NOT_APPROVED = 4;
/**
* Performs an authentication against e.g. database.
* and returns IIdentity on success or throws AuthenticationException
* @return IIdentity
* @throws AuthenticationException
*/
function authenticate(array $credentials);
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Authorizator checks if a given role has authorization
* to access a given resource.
*/
interface IAuthorizator
{
/** Set type: all */
const ALL = null;
/** Permission type: allow */
const ALLOW = true;
/** Permission type: deny */
const DENY = false;
/**
* Performs a role-based authorization.
* @param string|null
* @param string|null
* @param string|null
* @return bool
*/
function isAllowed($role, $resource, $privilege);
}
+28
View File
@@ -0,0 +1,28 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Represents the user of application.
*/
interface IIdentity
{
/**
* Returns the ID of user.
* @return mixed
*/
function getId();
/**
* Returns a list of roles that the user is a member of.
* @return array
*/
function getRoles();
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Represents resource, an object to which access is controlled.
*/
interface IResource
{
/**
* Returns a string identifier of the Resource.
* @return string
*/
function getResourceId();
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Represents role, an object that may request access to an IResource.
*/
interface IRole
{
/**
* Returns a string identifier of the Role.
* @return string
*/
function getRoleId();
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
/**
* Interface for persistent storage for user object data.
*/
interface IUserStorage
{
/** Log-out reason {@link IUserStorage::getLogoutReason()} */
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
* @return static
*/
function setAuthenticated($state);
/**
* Is this user authenticated?
* @return bool
*/
function isAuthenticated();
/**
* Sets the user identity.
* @return static
*/
function setIdentity(IIdentity $identity = null);
/**
* Returns current user identity, if any.
* @return IIdentity|null
*/
function getIdentity();
/**
* Enables log out from the persistent storage after inactivity.
* @param string|int|\DateTimeInterface number of seconds or timestamp
* @param int flag IUserStorage::CLEAR_IDENTITY
* @return static
*/
function setExpiration($time, $flags = 0);
/**
* Why was user logged out?
* @return int|null
*/
function getLogoutReason();
}
+145
View File
@@ -0,0 +1,145 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
use Nette;
/**
* Default implementation of IIdentity.
*
* @property mixed $id
* @property array $roles
* @property array $data
*/
class Identity implements IIdentity
{
use Nette\SmartObject {
__get as private parentGet;
__set as private parentSet;
__isset as private parentIsSet;
}
/** @var mixed */
private $id;
/** @var array */
private $roles;
/** @var array */
private $data;
/**
* @param mixed
* @param mixed
* @param iterable
*/
public function __construct($id, $roles = null, $data = null)
{
$this->setId($id);
$this->setRoles((array) $roles);
$this->data = $data instanceof \Traversable ? iterator_to_array($data) : (array) $data;
}
/**
* Sets the ID of user.
* @param mixed
* @return static
*/
public function setId($id)
{
$this->id = is_numeric($id) && !is_float($tmp = $id * 1) ? $tmp : $id;
return $this;
}
/**
* Returns the ID of user.
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* Sets a list of roles that the user is a member of.
* @return static
*/
public function setRoles(array $roles)
{
$this->roles = $roles;
return $this;
}
/**
* Returns a list of roles that the user is a member of.
* @return array
*/
public function getRoles()
{
return $this->roles;
}
/**
* Returns a user data.
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* Sets user data value.
* @param string
* @param mixed
* @return void
*/
public function __set($key, $value)
{
if ($this->parentIsSet($key)) {
$this->parentSet($key, $value);
} else {
$this->data[$key] = $value;
}
}
/**
* Returns user data value.
* @param string
* @return mixed
*/
public function &__get($key)
{
if ($this->parentIsSet($key)) {
return $this->parentGet($key);
} else {
return $this->data[$key];
}
}
/**
* @param string
* @return bool
*/
public function __isset($key)
{
return isset($this->data[$key]) || $this->parentIsSet($key);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
use Nette;
/**
* Passwords tools.
*/
class Passwords
{
use Nette\SmartObject;
/** @deprecated */
const BCRYPT_COST = 10;
/**
* Computes salted password hash.
* @param string
* @param array with cost (4-31)
* @return string 60 chars long
*/
public static function hash($password, array $options = [])
{
$hash = @password_hash($password, PASSWORD_BCRYPT, $options); // @ is escalated to exception
if (!$hash) {
throw new Nette\InvalidStateException('Computed hash is invalid. ' . error_get_last()['message']);
}
return $hash;
}
/**
* Verifies that a password matches a hash.
* @return bool
*/
public static function verify($password, $hash)
{
return password_verify($password, $hash);
}
/**
* Checks if the given hash matches the options.
* @param string
* @param array with cost (4-31)
* @return bool
*/
public static function needsRehash($hash, array $options = [])
{
return password_needs_rehash($hash, PASSWORD_BCRYPT, $options);
}
}
+803
View File
@@ -0,0 +1,803 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
use Nette;
/**
* 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
*
* @copyright Copyright (c) 2005, 2007 Zend Technologies USA Inc.
*/
class Permission implements IAuthorizator
{
use Nette\SmartObject;
/** @var array Role storage */
private $roles = [];
/** @var array Resource storage */
private $resources = [];
/** @var array Access Control List rules; whitelist (deny everything to all) by default */
private $rules = [
'allResources' => [
'allRoles' => [
'allPrivileges' => [
'type' => self::DENY,
'assert' => null,
],
'byPrivilege' => [],
],
'byRole' => [],
],
'byResource' => [],
];
/** @var mixed */
private $queriedRole;
private $queriedResource;
/********************* roles ****************d*g**/
/**
* Adds a Role to the list. The most recently added parent
* takes precedence over parents that were previously added.
* @param string
* @param string|array
* @throws Nette\InvalidArgumentException
* @throws Nette\InvalidStateException
* @return static
*/
public function addRole($role, $parents = null)
{
$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 (!is_array($parents)) {
$parents = [$parents];
}
foreach ($parents as $parent) {
$this->checkRole($parent);
$roleParents[$parent] = true;
$this->roles[$parent]['children'][$role] = true;
}
}
$this->roles[$role] = [
'parents' => $roleParents,
'children' => [],
];
return $this;
}
/**
* Returns true if the Role exists in the list.
* @param string
* @return bool
*/
public function hasRole($role)
{
$this->checkRole($role, false);
return isset($this->roles[$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)
{
if (!is_string($role) || $role === '') {
throw new Nette\InvalidArgumentException('Role must be a non-empty string.');
} elseif ($throw && !isset($this->roles[$role])) {
throw new Nette\InvalidStateException("Role '$role' does not exist.");
}
}
/**
* Returns all Roles.
* @return array
*/
public function getRoles()
{
return array_keys($this->roles);
}
/**
* Returns existing Role's parents ordered by ascending priority.
* @param string
* @return array
*/
public function getRoleParents($role)
{
$this->checkRole($role);
return array_keys($this->roles[$role]['parents']);
}
/**
* 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)
{
$this->checkRole($role);
$this->checkRole($inherit);
$inherits = isset($this->roles[$role]['parents'][$inherit]);
if ($inherits || $onlyParents) {
return $inherits;
}
foreach ($this->roles[$role]['parents'] as $parent => $foo) {
if ($this->roleInheritsFrom($parent, $inherit)) {
return true;
}
}
return false;
}
/**
* Removes the Role from the list.
*
* @param string
* @throws Nette\InvalidStateException
* @return static
*/
public function removeRole($role)
{
$this->checkRole($role);
foreach ($this->roles[$role]['children'] as $child => $foo) {
unset($this->roles[$child]['parents'][$role]);
}
foreach ($this->roles[$role]['parents'] as $parent => $foo) {
unset($this->roles[$parent]['children'][$role]);
}
unset($this->roles[$role]);
foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
if ($role === $roleCurrent) {
unset($this->rules['allResources']['byRole'][$roleCurrent]);
}
}
foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
if (isset($visitor['byRole'])) {
foreach ($visitor['byRole'] as $roleCurrent => $rules) {
if ($role === $roleCurrent) {
unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
}
}
}
}
return $this;
}
/**
* Removes all Roles from the list.
*
* @return static
*/
public function removeAllRoles()
{
$this->roles = [];
foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
unset($this->rules['allResources']['byRole'][$roleCurrent]);
}
foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
foreach ($visitor['byRole'] as $roleCurrent => $rules) {
unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
}
}
return $this;
}
/********************* resources ****************d*g**/
/**
* 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)
{
$this->checkResource($resource, false);
if (isset($this->resources[$resource])) {
throw new Nette\InvalidStateException("Resource '$resource' already exists in the list.");
}
if ($parent !== null) {
$this->checkResource($parent);
$this->resources[$parent]['children'][$resource] = true;
}
$this->resources[$resource] = [
'parent' => $parent,
'children' => [],
];
return $this;
}
/**
* Returns true if the Resource exists in the list.
* @param string
* @return bool
*/
public function hasResource($resource)
{
$this->checkResource($resource, false);
return isset($this->resources[$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)
{
if (!is_string($resource) || $resource === '') {
throw new Nette\InvalidArgumentException('Resource must be a non-empty string.');
} elseif ($throw && !isset($this->resources[$resource])) {
throw new Nette\InvalidStateException("Resource '$resource' does not exist.");
}
}
/**
* Returns all Resources.
* @return array
*/
public function getResources()
{
return array_keys($this->resources);
}
/**
* 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)
{
$this->checkResource($resource);
$this->checkResource($inherit);
if ($this->resources[$resource]['parent'] === null) {
return false;
}
$parent = $this->resources[$resource]['parent'];
if ($inherit === $parent) {
return true;
} elseif ($onlyParent) {
return false;
}
while ($this->resources[$parent]['parent'] !== null) {
$parent = $this->resources[$parent]['parent'];
if ($inherit === $parent) {
return true;
}
}
return false;
}
/**
* Removes a Resource and all of its children.
*
* @param string
* @throws Nette\InvalidStateException
* @return static
*/
public function removeResource($resource)
{
$this->checkResource($resource);
$parent = $this->resources[$resource]['parent'];
if ($parent !== null) {
unset($this->resources[$parent]['children'][$resource]);
}
$removed = [$resource];
foreach ($this->resources[$resource]['children'] as $child => $foo) {
$this->removeResource($child);
$removed[] = $child;
}
foreach ($removed as $resourceRemoved) {
foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
if ($resourceRemoved === $resourceCurrent) {
unset($this->rules['byResource'][$resourceCurrent]);
}
}
}
unset($this->resources[$resource]);
return $this;
}
/**
* Removes all Resources.
* @return static
*/
public function removeAllResources()
{
foreach ($this->resources as $resource => $foo) {
foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
if ($resource === $resourceCurrent) {
unset($this->rules['byResource'][$resourceCurrent]);
}
}
}
$this->resources = [];
return $this;
}
/********************* defining rules ****************d*g**/
/**
* 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
* @param string|string[]|null
* @param string|string[]|null
* @param callable assertion
* @return static
*/
public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = null)
{
$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.
*
* @param string|string[]|null
* @param string|string[]|null
* @param string|string[]|null
* @param callable assertion
* @return static
*/
public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = null)
{
$this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion);
return $this;
}
/**
* Removes "allow" permissions from the list in the context of the given Roles, Resources, and privileges.
*
* @param string|string[]|null
* @param string|string[]|null
* @param string|string[]|null
* @return static
*/
public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
{
$this->setRule(false, self::ALLOW, $roles, $resources, $privileges);
return $this;
}
/**
* Removes "deny" restrictions from the list in the context of the given Roles, Resources, and privileges.
*
* @param string|string[]|null
* @param string|string[]|null
* @param string|string[]|null
* @return static
*/
public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
{
$this->setRule(false, self::DENY, $roles, $resources, $privileges);
return $this;
}
/**
* Performs operations on Access Control List rules.
* @param bool operation add?
* @param bool type
* @param string|string[]|null
* @param string|string[]|null
* @param string|string[]|null
* @param callable assertion
* @throws Nette\InvalidStateException
* @return static
*/
protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = null)
{
// ensure that all specified Roles exist; normalize input to array of Roles or null
if ($roles === self::ALL) {
$roles = [self::ALL];
} else {
if (!is_array($roles)) {
$roles = [$roles];
}
foreach ($roles as $role) {
$this->checkRole($role);
}
}
// ensure that all specified Resources exist; normalize input to array of Resources or null
if ($resources === self::ALL) {
$resources = [self::ALL];
} else {
if (!is_array($resources)) {
$resources = [$resources];
}
foreach ($resources as $resource) {
$this->checkResource($resource);
}
}
// normalize privileges to array
if ($privileges === self::ALL) {
$privileges = [];
} elseif (!is_array($privileges)) {
$privileges = [$privileges];
}
if ($toAdd) { // add to the rules
foreach ($resources as $resource) {
foreach ($roles as $role) {
$rules = &$this->getRules($resource, $role, true);
if (count($privileges) === 0) {
$rules['allPrivileges']['type'] = $type;
$rules['allPrivileges']['assert'] = $assertion;
if (!isset($rules['byPrivilege'])) {
$rules['byPrivilege'] = [];
}
} else {
foreach ($privileges as $privilege) {
$rules['byPrivilege'][$privilege]['type'] = $type;
$rules['byPrivilege'][$privilege]['assert'] = $assertion;
}
}
}
}
} else { // remove from the rules
foreach ($resources as $resource) {
foreach ($roles as $role) {
$rules = &$this->getRules($resource, $role);
if ($rules === null) {
continue;
}
if (count($privileges) === 0) {
if ($resource === self::ALL && $role === self::ALL) {
if ($type === $rules['allPrivileges']['type']) {
$rules = [
'allPrivileges' => [
'type' => self::DENY,
'assert' => null,
],
'byPrivilege' => [],
];
}
continue;
}
if ($type === $rules['allPrivileges']['type']) {
unset($rules['allPrivileges']);
}
} else {
foreach ($privileges as $privilege) {
if (isset($rules['byPrivilege'][$privilege]) &&
$type === $rules['byPrivilege'][$privilege]['type']
) {
unset($rules['byPrivilege'][$privilege]);
}
}
}
}
}
}
return $this;
}
/********************* querying the ACL ****************d*g**/
/**
* 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,
* 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|null $privilege
* @throws Nette\InvalidStateException
* @return bool
*/
public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
{
$this->queriedRole = $role;
if ($role !== self::ALL) {
if ($role instanceof IRole) {
$role = $role->getRoleId();
}
$this->checkRole($role);
}
$this->queriedResource = $resource;
if ($resource !== self::ALL) {
if ($resource instanceof IResource) {
$resource = $resource->getResourceId();
}
$this->checkResource($resource);
}
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) {
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 (($result = $this->getRuleType($resource, null, $privilege)) === self::DENY) {
break 2;
}
}
if (($result = $this->getRuleType($resource, null, null)) !== null) {
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;
}
}
$resource = $this->resources[$resource]['parent']; // try next Resource
} while (true);
$this->queriedRole = $this->queriedResource = null;
return $result;
}
/**
* Returns real currently queried Role. Use by assertion.
* @return mixed
*/
public function getQueriedRole()
{
return $this->queriedRole;
}
/**
* Returns real currently queried Resource. Use by assertion.
* @return mixed
*/
public function getQueriedResource()
{
return $this->queriedResource;
}
/********************* internals ****************d*g**/
/**
* 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)
{
$dfs = [
'visited' => [],
'stack' => [$role],
];
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 ($this->getRuleType($resource, $role, $privilege2) === self::DENY) {
return self::DENY;
}
}
if (($type = $this->getRuleType($resource, $role, null)) !== null) {
return $type;
}
}
} else {
if (($type = $this->getRuleType($resource, $role, $privilege)) !== null) {
return $type;
} elseif (($type = $this->getRuleType($resource, $role, null)) !== null) {
return $type;
}
}
$dfs['visited'][$role] = true;
foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
$dfs['stack'][] = $roleParent;
}
}
return null;
}
/**
* 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)
{
if (!$rules = $this->getRules($resource, $role)) {
return null;
}
if ($privilege === self::ALL) {
if (isset($rules['allPrivileges'])) {
$rule = $rules['allPrivileges'];
} else {
return null;
}
} elseif (!isset($rules['byPrivilege'][$privilege])) {
return null;
} else {
$rule = $rules['byPrivilege'][$privilege];
}
if ($rule['assert'] === null || call_user_func($rule['assert'], $this, $role, $resource, $privilege)) {
return $rule['type'];
} elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
return null;
} elseif ($rule['type'] === self::ALLOW) {
return self::DENY;
} else {
return self::ALLOW;
}
}
/**
* 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
* @param bool
* @return array|null
*/
private function &getRules($resource, $role, $create = false)
{
$null = null;
if ($resource === self::ALL) {
$visitor = &$this->rules['allResources'];
} else {
if (!isset($this->rules['byResource'][$resource])) {
if (!$create) {
return $null;
}
$this->rules['byResource'][$resource] = [];
}
$visitor = &$this->rules['byResource'][$resource];
}
if ($role === self::ALL) {
if (!isset($visitor['allRoles'])) {
if (!$create) {
return $null;
}
$visitor['allRoles']['byPrivilege'] = [];
}
return $visitor['allRoles'];
}
if (!isset($visitor['byRole'][$role])) {
if (!$create) {
return $null;
}
$visitor['byRole'][$role]['byPrivilege'] = [];
}
return $visitor['byRole'][$role];
}
}
@@ -0,0 +1,58 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
use Nette;
/**
* Trivial implementation of IAuthenticator.
*/
class SimpleAuthenticator implements IAuthenticator
{
use Nette\SmartObject;
/** @var array */
private $userlist;
/** @var array */
private $usersRoles;
/**
* @param array list of pairs username => password
* @param array list of pairs username => role[]
*/
public function __construct(array $userlist, array $usersRoles = [])
{
$this->userlist = $userlist;
$this->usersRoles = $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)
{
list($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);
} else {
throw new AuthenticationException('Invalid password.', self::INVALID_CREDENTIAL);
}
}
}
throw new AuthenticationException("User '$username' not found.", self::IDENTITY_NOT_FOUND);
}
}
+264
View File
@@ -0,0 +1,264 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Security;
use Nette;
/**
* User authentication and authorization.
*
* @property-read bool $loggedIn
* @property-read IIdentity $identity
* @property-read mixed $id
* @property-read array $roles
* @property-read int $logoutReason
* @property IAuthenticator $authenticator
* @property IAuthorizator $authorizator
*/
class User
{
use Nette\SmartObject;
/** @deprecated */
const
MANUAL = IUserStorage::MANUAL,
INACTIVITY = IUserStorage::INACTIVITY;
/** @deprecated */
const BROWSER_CLOSED = IUserStorage::BROWSER_CLOSED;
/** @var string default role for unauthenticated user */
public $guestRole = 'guest';
/** @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 */
public $onLoggedIn;
/** @var callable[] function (User $sender); Occurs when the user is logged out */
public $onLoggedOut;
/** @var IUserStorage Session storage for current user */
private $storage;
/** @var IAuthenticator|null */
private $authenticator;
/** @var IAuthorizator|null */
private $authorizator;
public function __construct(IUserStorage $storage, IAuthenticator $authenticator = null, IAuthorizator $authorizator = null)
{
$this->storage = $storage;
$this->authenticator = $authenticator;
$this->authorizator = $authorizator;
}
/**
* @return IUserStorage
*/
public function getStorage()
{
return $this->storage;
}
/********************* Authentication ****************d*g**/
/**
* Conducts the authentication process. Parameters are optional.
* @param string|IIdentity username or Identity
* @param string
* @return void
* @throws AuthenticationException if authentication was not successful
*/
public function login($user, $password = null)
{
$this->logout(true);
if (!$user instanceof IIdentity) {
$user = $this->getAuthenticator()->authenticate(func_get_args());
}
$this->storage->setIdentity($user);
$this->storage->setAuthenticated(true);
$this->onLoggedIn($this);
}
/**
* Logs out the user from the current session.
* @param bool clear the identity from persistent storage?
* @return void
*/
public function logout($clearIdentity = false)
{
if ($this->isLoggedIn()) {
$this->onLoggedOut($this);
$this->storage->setAuthenticated(false);
}
if ($clearIdentity) {
$this->storage->setIdentity(null);
}
}
/**
* Is this user authenticated?
* @return bool
*/
public function isLoggedIn()
{
return $this->storage->isAuthenticated();
}
/**
* Returns current user identity, if any.
* @return IIdentity|null
*/
public function getIdentity()
{
return $this->storage->getIdentity();
}
/**
* Returns current user ID, if any.
* @return mixed
*/
public function getId()
{
$identity = $this->getIdentity();
return $identity ? $identity->getId() : null;
}
/**
* Sets authentication handler.
* @return static
*/
public function setAuthenticator(IAuthenticator $handler)
{
$this->authenticator = $handler;
return $this;
}
/**
* Returns authentication handler.
* @return IAuthenticator|null
*/
public function getAuthenticator($throw = true)
{
if ($throw && !$this->authenticator) {
throw new Nette\InvalidStateException('Authenticator has not been set.');
}
return $this->authenticator;
}
/**
* 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)
* @return static
*/
public function setExpiration($time, $flags = null, $clearIdentity = false)
{
$clearIdentity = $clearIdentity || $flags === IUserStorage::CLEAR_IDENTITY;
$this->storage->setExpiration($time, $clearIdentity ? IUserStorage::CLEAR_IDENTITY : 0);
return $this;
}
/**
* Why was user logged out?
* @return int|null
*/
public function getLogoutReason()
{
return $this->storage->getLogoutReason();
}
/********************* Authorization ****************d*g**/
/**
* Returns a list of effective roles that a user has been granted.
* @return array
*/
public function getRoles()
{
if (!$this->isLoggedIn()) {
return [$this->guestRole];
}
$identity = $this->getIdentity();
return $identity && $identity->getRoles() ? $identity->getRoles() : [$this->authenticatedRole];
}
/**
* Is a user in the specified effective role?
* @param string
* @return bool
*/
public function isInRole($role)
{
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.
* @param string resource
* @param string privilege
* @return bool
*/
public function isAllowed($resource = IAuthorizator::ALL, $privilege = IAuthorizator::ALL)
{
foreach ($this->getRoles() as $role) {
if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) {
return true;
}
}
return false;
}
/**
* Sets authorization handler.
* @return static
*/
public function setAuthorizator(IAuthorizator $handler)
{
$this->authorizator = $handler;
return $this;
}
/**
* Returns current authorization handler.
* @return IAuthorizator|null
*/
public function getAuthorizator($throw = true)
{
if ($throw && !$this->authorizator) {
throw new Nette\InvalidStateException('Authorizator has not been set.');
}
return $this->authorizator;
}
}