Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
ff035fc4
Commit
ff035fc4
authored
Sep 22, 2014
by
Alex Pott
Browse files
Issue
#2340507
by chx, Wim Leers: Make the new AccessResult API and implementation even better.
parent
9ff1ef39
Changes
48
Hide whitespace changes
Inline
Side-by-side
core/lib/Drupal/Core/Access/AccessManager.php
View file @
ff035fc4
...
...
@@ -229,7 +229,7 @@ public function check(RouteMatchInterface $route_match, AccountInterface $accoun
$checks
=
array_diff
(
$checks
,
$this
->
checkNeedsRequest
);
}
$result
=
AccessResult
::
create
();
$result
=
AccessResult
::
neutral
();
if
(
!
empty
(
$checks
))
{
$arguments_resolver
=
$this
->
argumentsResolverFactory
->
getArgumentsResolver
(
$route_match
,
$account
,
$request
);
if
(
$conjunction
==
static
::
ACCESS_MODE_ALL
)
{
...
...
@@ -256,34 +256,18 @@ public function check(RouteMatchInterface $route_match, AccountInterface $accoun
* @see \Drupal\Core\Access\AccessResultInterface::andIf()
*/
protected
function
checkAll
(
array
$checks
,
ArgumentsResolverInterface
$arguments_resolver
)
{
$results
=
array
();
// Without checks no opinion can be formed.
if
(
!
$checks
)
{
return
AccessResult
::
neutral
();
}
$result
=
AccessResult
::
allowed
();
foreach
(
$checks
as
$service_id
)
{
if
(
empty
(
$this
->
checks
[
$service_id
]))
{
$this
->
loadCheck
(
$service_id
);
}
$result
=
$this
->
performCheck
(
$service_id
,
$arguments_resolver
);
$results
[]
=
$result
;
// Stop as soon as the first non-allowed check is encountered.
if
(
!
$result
->
isAllowed
())
{
break
;
}
}
if
(
empty
(
$results
))
{
// No opinion.
return
AccessResult
::
create
();
}
else
{
/** @var \Drupal\Core\Access\AccessResultInterface $result */
$result
=
array_shift
(
$results
);
foreach
(
$results
as
$other
)
{
$result
->
andIf
(
$other
);
}
return
$result
;
$result
=
$result
->
andIf
(
$this
->
performCheck
(
$service_id
,
$arguments_resolver
));
}
return
$result
;
}
/**
...
...
@@ -301,7 +285,7 @@ protected function checkAll(array $checks, ArgumentsResolverInterface $arguments
*/
protected
function
checkAny
(
array
$checks
,
ArgumentsResolverInterface
$arguments_resolver
)
{
// No opinion by default.
$result
=
AccessResult
::
create
();
$result
=
AccessResult
::
neutral
();
foreach
(
$checks
as
$service_id
)
{
if
(
empty
(
$this
->
checks
[
$service_id
]))
{
...
...
core/lib/Drupal/Core/Access/AccessResult.php
View file @
ff035fc4
...
...
@@ -14,34 +14,18 @@
/**
* Value object for passing an access result with cacheability metadata.
*
* The access result itself — excluding the cacheability metadata — is
* immutable. There are subclasses for each of the three possible access results
* themselves:
*
* @see \Drupal\Core\Access\AccessResultAllowed
* @see \Drupal\Core\Access\AccessResultForbidden
* @see \Drupal\Core\Access\AccessResultNeutral
*
* When using ::orIf() and ::andIf(), cacheability metadata will be merged
* accordingly as well.
*/
class
AccessResult
implements
AccessResultInterface
,
CacheableInterface
{
/**
* The value that explicitly allows access.
*/
const
ALLOW
=
'ALLOW'
;
/**
* The value that neither explicitly allows nor explicitly forbids access.
*/
const
DENY
=
'DENY'
;
/**
* The value that explicitly forbids access.
*/
const
KILL
=
'KILL'
;
/**
* The access result value.
*
* A \Drupal\Core\Access\AccessResultInterface constant value.
*
* @var string
*/
protected
$value
;
abstract
class
AccessResult
implements
AccessResultInterface
,
CacheableInterface
{
/**
* Whether the access result is cacheable.
...
...
@@ -78,7 +62,6 @@ class AccessResult implements AccessResultInterface, CacheableInterface {
* Constructs a new AccessResult object.
*/
public
function
__construct
()
{
$this
->
resetAccess
();
$this
->
setCacheable
(
TRUE
)
->
resetCacheContexts
()
->
resetCacheTags
()
...
...
@@ -88,64 +71,67 @@ public function __construct() {
}
/**
* Instantiates a new AccessResult object.
*
* This factory method exists to improve DX; it allows developers to fluently
* create access results.
*
* Defaults to a cacheable access result that neither explicitly allows nor
* explicitly forbids access.
* Creates an AccessResultInterface object with isNeutral() === TRUE.
*
* @return \Drupal\Core\Access\AccessResult
* isNeutral() will be TRUE.
*/
public
static
function
create
()
{
return
new
static
();
public
static
function
neutral
()
{
return
new
AccessResultNeutral
();
}
/**
* C
onvenience method, c
reates an AccessResult
object and calls allow()
.
* Creates an AccessResult
Interface object with isAllowed() === TRUE
.
*
* @return \Drupal\Core\Access\AccessResult
* isAllowed() will be TRUE.
*/
public
static
function
allowed
()
{
return
static
::
create
()
->
a
llow
();
return
new
AccessResultA
llow
ed
();
}
/**
* C
onvenience method, c
reates an AccessResult
object and calls forbid()
.
* Creates an AccessResult
Interface object with isForbidden() === TRUE
.
*
* @return \Drupal\Core\Access\AccessResult
* isForbidden() will be TRUE.
*/
public
static
function
forbidden
()
{
return
static
::
create
()
->
f
orbid
();
return
new
AccessResultF
orbid
den
();
}
/**
* C
onvenience method, creates an A
ccess
R
esult
object and calls allowIf()
.
* C
reates an allowed or neutral a
ccess
r
esult.
*
* @param bool $condition
* The condition to evaluate.
If TRUE, ::allow() will be called.
* The condition to evaluate.
*
* @return \Drupal\Core\Access\AccessResult
* If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral()
* will be TRUE.
*/
public
static
function
allowedIf
(
$condition
)
{
return
static
::
create
()
->
allowIf
(
$condition
);
return
$condition
?
static
::
allowed
()
:
static
::
neutral
(
);
}
/**
* C
onvenience method, creates an AccessResult object and calls forbiddenIf()
.
* C
reates a forbidden or neutral access result
.
*
* @param bool $condition
* The condition to evaluate.
If TRUE, ::forbid() will be called.
* The condition to evaluate.
*
* @return \Drupal\Core\Access\AccessResult
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
* will be TRUE.
*/
public
static
function
forbiddenIf
(
$condition
)
{
return
static
::
create
()
->
forbidIf
(
$condition
);
return
$condition
?
static
::
forbidden
()
:
static
::
neutral
(
);
}
/**
* Convenience method, creates an AccessResult object and calls allowIfHasPermission().
* Creates an allowed access result if the permission is present, neutral otherwise.
*
* Convenience method, checks the permission and calls ::cachePerRole().
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check a permission.
...
...
@@ -153,83 +139,38 @@ public static function forbiddenIf($condition) {
* The permission to check for.
*
* @return \Drupal\Core\Access\AccessResult
* If the account has the permission, isAlowed() will be TRUE, otherwise
* isNeutral() will be TRUE.
*/
public
static
function
allowedIfHasPermission
(
AccountInterface
$account
,
$permission
)
{
return
static
::
create
()
->
allowIfHasPermission
(
$account
,
$permission
);
return
static
::
allowedIf
(
$account
->
hasPermission
(
$permission
))
->
cachePerRole
(
);
}
/**
* {@inheritdoc}
*
* @see \Drupal\Core\Access\AccessResultAllowed
*/
public
function
isAllowed
()
{
return
$this
->
value
===
static
::
ALLOW
;
return
FALSE
;
}
/**
* {@inheritdoc}
*/
public
function
isForbidden
()
{
return
$this
->
value
===
static
::
KILL
;
}
/**
* Explicitly allows access.
*
* @return $this
*/
public
function
allow
()
{
$this
->
value
=
static
::
ALLOW
;
return
$this
;
}
/**
* Explicitly forbids access.
*
* @return $this
*/
public
function
forbid
()
{
$this
->
value
=
static
::
KILL
;
return
$this
;
}
/**
* Neither explicitly allows nor explicitly forbids access.
*
* @
return $this
* @
see \Drupal\Core\Access\AccessResultForbidden
*/
public
function
resetAccess
()
{
$this
->
value
=
static
::
DENY
;
return
$this
;
}
/**
* Conditionally calls ::allow().
*
* @param bool $condition
* The condition to evaluate. If TRUE, ::allow() will be called.
*
* @return $this
*/
public
function
allowIf
(
$condition
)
{
if
(
$condition
)
{
$this
->
allow
();
}
return
$this
;
public
function
isForbidden
()
{
return
FALSE
;
}
/**
* Conditionally calls ::forbid().
*
* @param bool $condition
* The condition to evaluate. If TRUE, ::forbid() will be called.
* {@inheritdoc}
*
* @
return $this
* @
see \Drupal\Core\Access\AccessResultNeutral
*/
public
function
forbidIf
(
$condition
)
{
if
(
$condition
)
{
$this
->
forbid
();
}
return
$this
;
public
function
isNeutral
()
{
return
FALSE
;
}
/**
...
...
@@ -391,77 +332,89 @@ public function cacheUntilEntityChanges(EntityInterface $entity) {
return
$this
;
}
/**
* Convenience method, checks permission and calls ::cachePerRole().
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check a permission.
* @param string $permission
* The permission to check for.
*
* @return $this
*/
public
function
allowIfHasPermission
(
AccountInterface
$account
,
$permission
)
{
$this
->
allowIf
(
$account
->
hasPermission
(
$permission
))
->
cachePerRole
();
return
$this
;
}
/**
* {@inheritdoc}
*/
public
function
orIf
(
AccessResultInterface
$other
)
{
// If this AccessResult already is forbidden, then that already is the
// conclusion. We can completely disregard $other.
if
(
$this
->
isForbidden
())
{
return
$this
;
// $other's cacheability metadata is merged if $merge_other gets set to TRUE
// and this happens in two cases:
// 1. $other's access result is the one that determines the combined access
// result.
// 2. This access result is not cacheable and $other's access result is the
// same. i.e. attempt to return a cacheable access result.
$merge_other
=
FALSE
;
if
(
$this
->
isForbidden
()
||
$other
->
isForbidden
())
{
$result
=
static
::
forbidden
();
if
(
!
$this
->
isForbidden
()
||
(
!
$this
->
isCacheable
()
&&
$other
->
isForbidden
()))
{
$merge_other
=
TRUE
;
}
}
// Otherwise, we make this AccessResult forbidden if the other is, or
// allowed if the other is, and we merge in the cacheability metadata if the
// other access result also has cacheability metadata.
else
{
if
(
$other
->
isForbidden
())
{
$this
->
forbid
();
elseif
(
$this
->
isAllowed
()
||
$other
->
isAllowed
())
{
$result
=
static
::
allowed
();
if
(
!
$this
->
isAllowed
()
||
(
!
$this
->
isCacheable
()
&&
$other
->
isAllowed
()))
{
$merge_other
=
TRUE
;
}
else
if
(
$other
->
isAllowed
())
{
$this
->
allow
();
}
else
{
$result
=
static
::
neutral
();
if
(
!
$this
->
isNeutral
()
||
(
!
$this
->
isCacheable
()
&&
$other
->
isNeutral
()))
{
$merge_other
=
TRUE
;
}
$this
->
mergeCacheabilityMetadata
(
$other
);
return
$this
;
}
$result
->
inheritCacheability
(
$this
);
if
(
$merge_other
)
{
$result
->
inheritCacheability
(
$other
);
}
return
$result
;
}
/**
* {@inheritdoc}
*/
public
function
andIf
(
AccessResultInterface
$other
)
{
// If this AccessResult already is forbidden or is merely not explicitly
// allowed, then that already is the conclusion. We can completely disregard
// $other.
if
(
$this
->
isForbidden
()
||
!
$this
->
isAllowed
())
{
return
$this
;
// The other access result's cacheability metadata is merged if $merge_other
// gets set to TRUE. It gets set to TRUE in one case: if the other access
// result is used.
$merge_other
=
FALSE
;
if
(
$this
->
isForbidden
()
||
$other
->
isForbidden
())
{
$result
=
static
::
forbidden
();
if
(
!
$this
->
isForbidden
())
{
$merge_other
=
TRUE
;
}
}
elseif
(
$this
->
isAllowed
()
&&
$other
->
isAllowed
())
{
$result
=
static
::
allowed
();
$merge_other
=
TRUE
;
}
// Otherwise, we make this AccessResult forbidden if the other is, or not
// explicitly allowed if the other isn't, and we merge in the cacheability
// metadata if the other access result also has cacheability metadata.
else
{
if
(
$other
->
isForbidden
())
{
$this
->
forbid
();
$result
=
static
::
neutral
();
if
(
!
$this
->
isNeutral
())
{
$merge_other
=
TRUE
;
}
else
if
(
!
$other
->
isAllowed
())
{
$this
->
resetAccess
();
}
$result
->
inheritCacheability
(
$this
);
if
(
$merge_other
)
{
$result
->
inheritCacheability
(
$other
);
// If this access result is not cacheable, then an AND with another access
// result must also not be cacheable, except if the other access result
// has isForbidden() === TRUE. isForbidden() access results are contagious
// in that they propagate regardless of the other value.
if
(
!
$this
->
isCacheable
()
&&
!
$result
->
isForbidden
())
{
$result
->
setCacheable
(
FALSE
);
}
$this
->
mergeCacheabilityMetadata
(
$other
);
return
$this
;
}
return
$result
;
}
/**
*
Merge
s the cacheability
metadata
of the other access result, if any.
*
Inherit
s the cacheability of the other access result, if any.
*
* @param \Drupal\Core\Access\AccessResultInterface $other
* The other access result, whose cacheability data (if any) to merge.
* The other access result, whose cacheability (if any) to inherit.
*
* @return $this
*/
p
rotected
function
merge
Cacheability
Metadata
(
AccessResultInterface
$other
)
{
p
ublic
function
inherit
Cacheability
(
AccessResultInterface
$other
)
{
if
(
$other
instanceof
CacheableInterface
)
{
$this
->
setCacheable
(
$other
->
isCacheable
());
$this
->
addCacheContexts
(
$other
->
getCacheKeys
());
...
...
@@ -481,6 +434,7 @@ protected function mergeCacheabilityMetadata(AccessResultInterface $other) {
else
{
$this
->
setCacheable
(
FALSE
);
}
return
$this
;
}
}
core/lib/Drupal/Core/Access/AccessResultAllowed.php
0 → 100644
View file @
ff035fc4
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessResultAllowed.
*/
namespace
Drupal\Core\Access
;
/**
* Value object indicating an allowed access result, with cacheability metadata.
*/
class
AccessResultAllowed
extends
AccessResult
{
/**
* {@inheritdoc}
*/
public
function
isAllowed
()
{
return
TRUE
;
}
}
core/lib/Drupal/Core/Access/AccessResultForbidden.php
0 → 100644
View file @
ff035fc4
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessResultForbidden.
*/
namespace
Drupal\Core\Access
;
/**
* Value object indicating a forbidden access result, with cacheability metadata.
*/
class
AccessResultForbidden
extends
AccessResult
{
/**
* {@inheritdoc}
*/
public
function
isForbidden
()
{
return
TRUE
;
}
}
core/lib/Drupal/Core/Access/AccessResultInterface.php
View file @
ff035fc4
...
...
@@ -23,12 +23,10 @@
* would never enter the else-statement and hence introduce a critical security
* issue.
*
* Note: you can check whether access is neither explicitly allowed nor
* explicitly forbidden:
*
* @code
* $no_opinion = !$access->isAllowed() && !$access->isForbidden();
* @endcode
* Objects implementing this interface are using Kleene's weak three-valued
* logic with the isAllowed() state being TRUE, the isForbidden() state being
* the intermediate truth value and isNeutral() being FALSE. See
* http://en.wikipedia.org/wiki/Many-valued_logic for more.
*/
interface
AccessResultInterface
{
...
...
@@ -36,43 +34,56 @@ interface AccessResultInterface {
* Checks whether this access result indicates access is explicitly allowed.
*
* @return bool
* When TRUE then isForbidden() and isNeutral() are FALSE.
*/
public
function
isAllowed
();
/**
* Checks whether this access result indicates access is explicitly forbidden.
*
* This is a kill switch — both orIf() and andIf() will result in
* isForbidden() if either results are isForbidden().
*
* @return bool
* When TRUE then isAllowed() and isNeutral() are FALSE.
*/
public
function
isForbidden
();
/**
* Checks whether this access result indicates access is not yet determined.
*
* @return bool
* When TRUE then isAllowed() and isForbidden() are FALSE.
*/
public
function
isNeutral
();
/**
* Combine this access result with another using OR.
*
* When OR-ing two access results, the result is:
* - isForbidden() in either ⇒ isForbidden()
* - isAllowed() in either ⇒ isAllowed()
* -
nei
ther
is
Forbidden() nor isAllowed() => !isAllowed() && !isForbidden
()
* -
otherwise if
isAllowed() in either ⇒ isAllowed()
* -
o
ther
w
is
e both must be isNeutral() ⇒ isNeutral
()
*
* @param \Drupal\Core\Access\AccessResultInterface $other
* The other access result to OR this one with.
*
* @return
$this
* @return
static
*/
public
function
orIf
(
AccessResultInterface
$other
);
/**
* Combine this access result with another using AND.
*
* When
OR
-ing two access results, the result is:
* When
AND
-ing two access results, the result is:
* - isForbidden() in either ⇒ isForbidden()
* - isAllowed() in both ⇒ isAllowed()
* -
nei
ther
is
Forbidden() nor isAllowed() => !isAllowed() && !isForbidden
()
* -
otherwise, if
isAllowed() in both ⇒ isAllowed()
* -
o
ther
w
is
e, one of them is isNeutral() ⇒ isNeutral
()
*
* @param \Drupal\Core\Access\AccessResultInterface $other
* The other access result to AND this one with.
*
* @return
$this
* @return
static
*/
public
function
andIf
(
AccessResultInterface
$other
);
...
...
core/lib/Drupal/Core/Access/AccessResultNeutral.php
0 → 100644
View file @
ff035fc4
<?php
/**
* @file
* Contains \Drupal\Core\Access\AccessResultNeutral.
*/
namespace
Drupal\Core\Access
;
/**
* Value object indicating a neutral access result, with cacheability metadata.
*/
class
AccessResultNeutral
extends
AccessResult
{
/**
* {@inheritdoc}
*/
public
function
isNeutral
()
{
return
TRUE
;
}
}
core/lib/Drupal/Core/Access/CsrfAccessCheck.php
View file @
ff035fc4
...
...
@@ -49,17 +49,16 @@ public function __construct(CsrfTokenGenerator $csrf_token) {
* The access result.
*/
public
function
access
(
Route
$route
,
Request
$request
)
{
// Not cacheable because the CSRF token is highly dynamic.
$access
=
AccessResult
::
create
()
->
setCacheable
(
FALSE
);
// @todo Remove dependency on the internal _system_path attribute:
// https://www.drupal.org/node/2293501.
if
(
$this
->
csrfToken
->
validate
(
$request
->
query
->
get
(
'token'
),
$request
->
attributes
->
get
(
'_system_path'
)))
{
$
access
->
allow
();
$
result
=
AccessResult
::
allow
ed
();
}
else
{
$
access
->
forbid
();
$
result
=
AccessResult
::
forbid
den
();
}
return
$access
;
// Not cacheable because the CSRF token is highly dynamic.
return
$result
->
setCacheable
(
FALSE
);
}
}
core/lib/Drupal/Core/Access/DefaultAccessCheck.php
View file @
ff035fc4
...
...
@@ -32,7 +32,7 @@ public function access(Route $route) {