SensioSecurityExpressionVoter::voteOnAttribute()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 11
rs 10
c 0
b 0
f 0
ccs 5
cts 5
cp 1
cc 1
nc 1
nop 3
crap 1
1
<?php
2
/*
3
 *
4
 * (c) Yaroslav Honcharuk <[email protected]>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Yarhon\RouteGuardBundle\Security\Authorization;
11
12
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
15
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
17
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
18
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
19
use Yarhon\RouteGuardBundle\ExpressionLanguage\ExpressionDecorator;
20
21
/**
22
 * Sensio FrameworkExtraBundle executes security expressions in place, bypassing the traditional flow to
23
 * \Symfony\Component\Security\Core\Authorization\AuthorizationChecker and further to the Voter(s).
24
 * SensioSecurityExpressionVoter is intended to execute those security expressions using traditional flow.
25
 *
26
 * @see \Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener
27
 *
28
 * @author Yaroslav Honcharuk <[email protected]>
29
 * @author Fabien Potencier <[email protected]>
30
 */
31
class SensioSecurityExpressionVoter extends Voter
32
{
33
    protected static $variableNames = [
34
        'token',
35
        'user',
36
        'object',
37
        'subject',
38
        'roles',
39
        'trust_resolver',
40
        'auth_checker',
41
        'request',
42
    ];
43
44
    /**
45
     * @var ExpressionLanguage
46
     */
47
    protected $expressionLanguage;
48
49
    /**
50
     * @var AuthenticationTrustResolverInterface
51
     */
52
    protected $trustResolver;
53
54
    /**
55
     * @var AuthorizationCheckerInterface
56
     */
57
    protected $authChecker;
58
59
    /**
60
     * @var RoleHierarchyInterface|null
61
     */
62
    protected $roleHierarchy;
63
64
    /**
65
     * @param ExpressionLanguage                   $expressionLanguage
66
     * @param AuthenticationTrustResolverInterface $trustResolver
67
     * @param AuthorizationCheckerInterface        $authChecker
68
     * @param RoleHierarchyInterface|null          $roleHierarchy
69
     */
70 10
    public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, RoleHierarchyInterface $roleHierarchy = null)
71
    {
72 10
        $this->expressionLanguage = $expressionLanguage;
73 10
        $this->trustResolver = $trustResolver;
74 10
        $this->authChecker = $authChecker;
75 10
        $this->roleHierarchy = $roleHierarchy;
76 10
    }
77
78
    /**
79
     * @return array
80
     */
81 19
    public static function getVariableNames()
82
    {
83 19
        return self::$variableNames;
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 9
    protected function supports($attribute, $subject)
90
    {
91 9
        return $attribute instanceof ExpressionDecorator;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 6
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
98
    {
99
        /** @var ExpressionDecorator $attribute */
100 6
        $expressionVariables = $attribute->getVariables();
101
102 6
        $variables = $this->getVariables($token, $subject);
103
104
        // In case of overlap, built-in variables win.
105 6
        $variables = array_merge($expressionVariables, $variables);
106
107 6
        return $this->expressionLanguage->evaluate($attribute->getExpression(), $variables);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->expression...pression(), $variables) returns the type array which is incompatible with the return type mandated by Symfony\Component\Securi...oter::voteOnAttribute() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
108
    }
109
110
    /**
111
     * @codeCoverageIgnore
112
     *
113
     * @param TokenInterface $token
114
     * @param mixed          $subject
115
     *
116
     * @return array
117
     */
118
    protected function getVariables(TokenInterface $token, $subject)
119
    {
120
        if (null !== $this->roleHierarchy) {
121
            $roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
122
        } else {
123
            $roles = $token->getRoles();
124
        }
125
126
        $variables = [
127
            'token' => $token,
128
            'user' => $token->getUser(),
129
            'object' => $subject,
130
            'subject' => $subject,
131
            'roles' => array_map(function ($role) { return $role->getRole(); }, $roles),
132
            'trust_resolver' => $this->trustResolver,
133
            // "auth_checker" variable is used by Sensio security expressions (see \Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener::getVariables),
134
            // and would be available by default since Symfony 4.2 (see \Symfony\Component\Security\Core\Authorization\Voter\SymfonySecurityExpressionVoter::getVariables).
135
            'auth_checker' => $this->authChecker,
136
        ];
137
138
        // this is mainly to propose a better experience when the expression is used
139
        // in an access control rule, as the developer does not know that it's going
140
        // to be handled by this voter
141
        if ($subject instanceof Request) {
142
            $variables['request'] = $subject;
143
        }
144
145
        return $variables;
146
    }
147
}
148