This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Yokai\SecurityExtraBundle\Voter; |
||
4 | |||
5 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
||
6 | use Symfony\Component\Security\Core\Authorization\Voter\Voter; |
||
7 | use Yokai\SecurityExtraBundle\Exception\LogicException; |
||
8 | |||
9 | /** |
||
10 | * @author Yann Eugoné <[email protected]> |
||
11 | */ |
||
12 | class CallableCollectionVoter extends Voter |
||
13 | { |
||
14 | /** |
||
15 | * Attribute list this is supporting. |
||
16 | * |
||
17 | * @var array<string> |
||
18 | */ |
||
19 | private $supportedAttributes; |
||
20 | |||
21 | /** |
||
22 | * Subject types list this is supporting. |
||
23 | * |
||
24 | * @var array<string> |
||
25 | */ |
||
26 | private $supportedSubjects; |
||
27 | |||
28 | /** |
||
29 | * Callable collection this must call. |
||
30 | * |
||
31 | * @var array<mixed> |
||
32 | */ |
||
33 | private $callables; |
||
34 | |||
35 | /** |
||
36 | * @param array<string> $supportedAttributes Attribute list this is supporting |
||
37 | * @param array<string> $supportedSubjects Subject types list this is supporting |
||
38 | * @param array<mixed> $callables Callable collection this must call |
||
39 | */ |
||
40 | 9 | public function __construct($supportedAttributes, $supportedSubjects, $callables) |
|
41 | { |
||
42 | 9 | $this->supportedAttributes = $supportedAttributes; |
|
43 | 9 | $this->supportedSubjects = $supportedSubjects; |
|
44 | 9 | $this->callables = $callables; |
|
45 | 9 | } |
|
46 | |||
47 | /** |
||
48 | * @inheritDoc |
||
49 | */ |
||
50 | 9 | protected function supports($attribute, $subject) |
|
51 | { |
||
52 | // if at least one supported attribute is configured |
||
53 | // check if provided attribute is in that list |
||
54 | 9 | if (count($this->supportedAttributes) > 0 && !in_array($attribute, $this->supportedAttributes, true)) { |
|
55 | 3 | return false; |
|
56 | } |
||
57 | |||
58 | // if there is no subject |
||
59 | // of if there is not at least one supported subject |
||
60 | // this is supporting |
||
61 | 9 | if ($subject === null || count($this->supportedSubjects) === 0) { |
|
62 | 6 | return true; |
|
63 | } |
||
64 | |||
65 | // iterate over supported subjects |
||
66 | 4 | foreach ($this->supportedSubjects as $supportedSubject) { |
|
67 | // if supported subject is a class (or interface) |
||
68 | // this supports if subject is an instance of |
||
69 | 4 | if (class_exists($supportedSubject)) { |
|
70 | 4 | if ($subject instanceof $supportedSubject) { |
|
71 | 4 | return true; |
|
72 | } |
||
73 | |||
74 | 2 | continue; |
|
75 | } |
||
76 | |||
77 | // if supported subject is not a class, it must be an internal type |
||
78 | // this support if subject type is the same |
||
79 | 1 | if (gettype($subject) === $supportedSubject) { |
|
80 | 1 | return true; |
|
81 | } |
||
82 | } |
||
83 | |||
84 | // supported attribute but unsupported subject |
||
85 | |||
86 | 1 | return false; |
|
87 | } |
||
88 | |||
89 | /** |
||
90 | * @inheritDoc |
||
91 | */ |
||
92 | 9 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token) |
|
93 | { |
||
94 | // iterate over configured callables |
||
95 | 9 | foreach ($this->callables as $callable) { |
|
96 | 9 | $callable = $this->normalizeCallable($callable, $subject); |
|
97 | 5 | $parameters = $this->gatherParameters($callable, $attribute, $subject, $token); |
|
98 | |||
99 | // if one callable returns falsy result |
||
100 | // this deny access |
||
101 | 4 | if (!(bool) call_user_func_array($callable, $parameters)) { |
|
102 | 4 | return false; |
|
103 | } |
||
104 | } |
||
105 | |||
106 | // no callable returns falsy result |
||
107 | // this grand access |
||
108 | |||
109 | 4 | return true; |
|
110 | } |
||
111 | |||
112 | /** |
||
113 | * Normalizes a callable. |
||
114 | * Will return a callable array. See http://php.net/manual/en/language.types.callable.php . |
||
115 | * |
||
116 | * @param string|object|array $callable The callable to normalize |
||
117 | * @param mixed $subject The subject being voting on |
||
118 | * |
||
119 | * @return array The normalized callable |
||
120 | * @throws LogicException |
||
121 | */ |
||
122 | 9 | private function normalizeCallable($callable, $subject) |
|
0 ignored issues
–
show
|
|||
123 | { |
||
124 | // callable is a string |
||
125 | // it should be a method to call on subject |
||
126 | 9 | if (is_string($callable)) { |
|
127 | 3 | if (!is_object($subject) || !is_callable([$subject, $callable])) { |
|
128 | 1 | throw new LogicException( |
|
129 | sprintf( |
||
130 | 1 | 'Provided string callable "%s", but subject "%s" has no callable method with that name.', |
|
131 | 1 | (string) $callable, |
|
132 | 1 | is_object($subject) ? get_class($subject) : gettype($subject) |
|
133 | ) |
||
134 | ); |
||
135 | } |
||
136 | |||
137 | 2 | return [$subject, $callable]; |
|
138 | } |
||
139 | |||
140 | // callable is an object |
||
141 | // it should have an __invoke method |
||
142 | 8 | if (is_object($callable)) { |
|
143 | 5 | if (!is_callable([$callable, '__invoke'])) { |
|
144 | 1 | throw new LogicException( |
|
145 | sprintf( |
||
146 | 1 | 'Provided object callable "%s", but it has no "__invoke" method.', |
|
147 | 1 | is_object($callable) ? get_class($callable) : gettype($callable) |
|
148 | ) |
||
149 | ); |
||
150 | } |
||
151 | |||
152 | 4 | return [$callable, '__invoke']; |
|
153 | } |
||
154 | |||
155 | // callable is an array |
||
156 | // it should be an array with [0] and [1] |
||
157 | 5 | if (is_array($callable)) { |
|
158 | 4 | if (!isset($callable[0]) || !isset($callable[1]) || !is_callable([$callable[0], $callable[1]])) { |
|
159 | 1 | throw new LogicException('Provided array callable, but it is not callable.'); |
|
160 | } |
||
161 | |||
162 | 3 | return [$callable[0], $callable[1]]; |
|
163 | } |
||
164 | |||
165 | 1 | throw new LogicException( |
|
166 | sprintf( |
||
167 | 1 | 'Unable to normalize callable "%". Please review your configuration.', |
|
168 | 1 | is_object($callable) ? get_class($callable) : gettype($callable) |
|
169 | ) |
||
170 | ); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Analyzes callable and determine the required parameters. |
||
175 | * |
||
176 | * @param array $callable The callable for which to gather parameters |
||
177 | * @param string $attribute The attribute being voting for |
||
178 | * @param mixed $subject The subject being voting on |
||
179 | * @param TokenInterface $token The authentication being voting for |
||
180 | * |
||
181 | * @return array<mixed> The parameters list |
||
182 | * @throws LogicException |
||
183 | */ |
||
184 | 5 | private function gatherParameters($callable, $attribute, $subject, TokenInterface $token) |
|
185 | { |
||
186 | 5 | if ($callable[0] instanceof \Closure) { |
|
187 | // don't know why but, it seems that ['\Closure', '__invoke'] is not ok with \ReflectionMethod |
||
188 | 3 | $reflection = new \ReflectionFunction($callable[0]); |
|
189 | } else { |
||
190 | 3 | $reflection = new \ReflectionMethod(get_class($callable[0]), $callable[1]); |
|
191 | } |
||
192 | |||
193 | 5 | $parameters = []; |
|
194 | |||
195 | // iterating over all parameters for method |
||
196 | 5 | foreach ($reflection->getParameters() as $parameter) { |
|
197 | 4 | $parameterType = $parameter->getType(); |
|
198 | 4 | if (method_exists($parameterType, 'getName')) { |
|
199 | $parameterType = $parameterType->getName(); |
||
200 | } else { |
||
201 | 4 | $parameterType = (string) $parameterType; // PHP < 7.1 supports |
|
202 | } |
||
203 | 4 | $parameterName = $parameter->getName(); |
|
204 | 4 | $parameterPosition = $parameter->getPosition(); |
|
205 | switch (true) { |
||
206 | // attribute is a bit tricky, cannot use any type to determine whether or not it is required |
||
207 | // if the parameter name is "attribute" this assume it should be provided |
||
208 | // adding subject to required parameters |
||
209 | 4 | case $parameterName === 'attribute': |
|
210 | 2 | $parameters[$parameterPosition] = $attribute; |
|
211 | 2 | break; |
|
212 | |||
213 | // parameter looks like the subject being voting on |
||
214 | // adding subject to required parameters |
||
215 | 4 | case is_a($subject, $parameterType) || gettype($subject) === $parameterType: |
|
216 | 3 | $parameters[$parameterPosition] = $subject; |
|
217 | 3 | break; |
|
218 | |||
219 | // parameter looks like a security token |
||
220 | // adding token to required parameters |
||
221 | 4 | case is_a($token, $parameterType): |
|
222 | 3 | $parameters[$parameterPosition] = $token; |
|
223 | 3 | break; |
|
224 | |||
225 | // parameter looks like a security user |
||
226 | // adding user to required parameters |
||
227 | 4 | case is_a($token->getUser(), $parameterType): |
|
228 | 3 | $parameters[$parameterPosition] = $token->getUser(); |
|
229 | 4 | break; |
|
230 | } |
||
231 | } |
||
232 | |||
233 | // this gathered parameters, but the callable needs something more |
||
234 | // calling with these parameters will probably results to an error |
||
235 | // so throwing an exception is the only thing to do |
||
236 | 5 | if ($reflection->getNumberOfRequiredParameters() !== count($parameters)) { |
|
237 | 1 | throw new LogicException( |
|
238 | sprintf( |
||
239 | 1 | 'The callable method "%s"->"%s"() needs parameters that cannot be provided.', |
|
240 | 1 | get_class($callable[0]), |
|
241 | 1 | $callable[1] |
|
242 | ) |
||
243 | ); |
||
244 | } |
||
245 | |||
246 | // return sorted parameters |
||
247 | 4 | ksort($parameters); |
|
248 | |||
249 | 4 | return $parameters; |
|
250 | } |
||
251 | } |
||
252 |
A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.
You can also find more information in the “Code” section of your repository.