8000 Add voter individual decisions to profiler · symfony/symfony@6794076 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6794076

Browse files
committed
Add voter individual decisions to profiler
1 parent 4e4b216 commit 6794076

File tree

12 files changed

+671
-57
lines changed

12 files changed

+671
-57
lines changed

UPGRADE-4.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ SecurityBundle
111111
`security.authentication.trust_resolver.rememberme_class` parameters to define
112112
the token classes is deprecated. To use
113113
custom tokens extend the existing AnonymousToken and RememberMeToken.
114+
* `SecurityDataCollector::getVoters` is deprecated. Use the data returned by `SecurityDataCollector::getVoterDetails` instead.
114115

115116
Serializer
116117
----------

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ CHANGELOG
1111
or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`.
1212
* Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass`
1313
* Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API.
14+
* Using `SecurityDataCollector::getVoters()` is deprecated
15+
* Added individual voter decisions to the profiler
1416

1517
4.1.0
1618
-----

src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
1919
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
2020
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
21+
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
2122
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
2223
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
2324
use Symfony\Component\Security\Core\Role\Role;
@@ -136,15 +137,33 @@ public function collect(Request $request, Response $response, \Exception $except
136137

137138
// collect voters and access decision manager information
138139
if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) {
139-
$this->data['access_decision_log'] = $this->accessDecisionManager->getDecisionLog();
140-
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
141-
142-
foreach ($this->accessDecisionManager->getVoters() as $voter) {
143-
$this->data['voters'][] = $this->hasVarDumper ? new ClassStub(\get_class($voter)) : \get_class($voter);
140+
$decisionLog = $this->accessDecisionManager->getDecisionLog();
141+
142+
$voterDetails = array();
143+
$voters = array();
144+
foreach ($decisionLog as $key => $log) {
145+
$voterDetails[$key] = array();
146+
foreach ($log['voterDetails'] as $voterClass => $voterVote) {
147+
$classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass;
148+
$voterDetails[$key][] = array(
149+
'class' => $classData,
150+
'vote' => $voterVote,
151+
);
152+
153+
if (!in_array($classData, $voters)) {
154+
$voters[] = $classData;
155+
}
156+
}
144157
}
158+
159+
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
160+
$this->data['voter_details'] = $voterDetails;
161+
$this->data['access_decision_log'] = $decisionLog;
162+
$this->data['voters'] = $voters;
145163
} else {
146164
$this->data['access_decision_log'] = array();
147165
$this->data['voter_strategy'] = 'unknown';
166+
$this->data['voter_details'] = array();
148167
$this->data['voters'] = array();
149168
}
150169

@@ -309,9 +328,13 @@ public function getLogoutUrl()
309328
* Returns the FQCN of the security voters enabled in the application.
310329
*
311330
* @return string[]
331+
*
332+
* @deprecated deprecated since Symfony 4.2. Use the data returned by {@link getVoterDetails()} instead
312333
*/
313334
public function getVoters()
314335
{
336+
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2. Use the data returned by getVoterDetails() instead', __METHOD__), E_USER_DEPRECATED);
337+
315338
return $this->data['voters'];
316339
}
317340

@@ -335,6 +358,16 @@ public function getAccessDecisionLog()
335358
return $this->data['access_decision_log'];
336359
}
337360

361+
/**
362+
* Returns the log of the votes processed in the access decision manager.
363+
*
364+
* @return Data|array
365+
*/
366+
public function getVoterDetails(): iterable
367+
{
368+
return $this->data['voter_details'];
369+
}
370+
338371
/**
339372
* Returns the configuration of the current firewall context.
340373
*

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
1818
use Symfony\Component\DependencyInjection\Exception\LogicException;
19+
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
1921
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
2022

2123
/**
@@ -41,16 +43,33 @@ public function process(ContainerBuilder $container)
4143
throw new LogicException('No security voters found. You need to tag at l 10000 east one with "security.voter".');
4244
}
4345

46+
$debug = $container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug');
47+
48+
$decisionManagerVoters = array();
4449
foreach ($voters as $voter) {
45-
$definition = $container->getDefinition((string) $voter);
50+
$voterServiceId = (string) $voter;
51+
$definition = $container->getDefinition($voterServiceId);
52+
4653
$class = $container->getParameterBag()->resolveValue($definition->getClass());
4754

4855
if (!is_a($class, VoterInterface::class, true)) {
4956
throw new LogicException(sprintf('%s must implement the %s when used as a voter.', $class, VoterInterface::class));
5057
}
58+
59+
if ($debug) {
60+
// Decorate original voters with TraceableVoter
61+
$debugVoterServiceId = 'debug.'.$voterServiceId;
62+
$container
63+
->register($debugVoterServiceId, TraceableVoter::class)
64+
->setDecoratedService($voterServiceId)
65+
->addArgument(new Reference($debugVoterServiceId.'.inner'))
66+
->setPublic(false);
67+
}
68+
69+
$decisionManagerVoters[] = $voter;
5170
}
5271

5372
$adm = $container->getDefinition('security.access.decision_manager');
54-
$adm->replaceArgument(0, new IteratorArgument($voters));
73+
$adm->replaceArgument(0, new IteratorArgument($decisionManagerVoters));
5574
}
5675
}

src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@
258258
</div>
259259
{% endif %}
260260

261-
{% if collector.voters|default([]) is not empty %}
262-
<h2>Security Voters <small>({{ collector.voters|length }})</small></h2>
261+
{% if collector.accessDecisionLog|default([]) is not empty %}
262+
<h2>Access decision log</h2>
263263

264264
<div class="metrics">
265265
<div class="metric">
@@ -268,47 +268,22 @@
268268
</div>
269269
</div>
270270

271-
<table class="voters">
272-
<thead>
273-
<tr>
274-
<th>#</th>
275-
<th>Voter class</th>
276-
</tr>
277-
</thead>
271+
{% for key, decision in collector.accessDecisionLog %}
272+
<table class="decision-log">
273+
<col style="width: 120px">
274+
<col style="width: 25%">
275+
<col style="width: 60%">
278276

279-
<tbody>
280-
{% for voter in collector.voters %}
277+
<thead>
281278
<tr>
282-
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
283-
<td class="font-normal">{{ profiler_dump(voter) }}</td>
279+
<th>Result</th>
280+
<th>Attributes</th>
281+
<th>Object</th>
284282
</tr>
285-
{% endfor %}
286-
</tbody>
287-
</table>
288-
{% endif %}
289-
290-
{% if collector.accessDecisionLog|default([]) is not empty %}
291-
<h2>Access decision log</h2>
283+
</thead>
292284

293-
<table class="decision-log">
294-
<col style="width: 30px">
295-
<col style="width: 120px">
296-
<col style="width: 25%">
297-
<col style="width: 60%">
298-
299-
<thead>
300-
<tr>
301-
<th>#</th>
302-
<th>Result</th>
303-
<th>Attributes</th>
304-
<th>Object</th>
305-
</tr>
306-
</thead>
307-
308-
<tbody>
309-
{% for decision in collector.accessDecisionLog %}
285+
<tbody>
310286
<tr>
311-
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
312287
<td class="font-normal">
313288
{{ decision.result
314289
? '<span class="label status-success same-width">GRANTED</span>'
@@ -331,8 +306,44 @@
331306
</td>
332307
<td>{{ profiler_dump(decision.seek('object')) }}</td>
333308
</tr>
334-
{% endfor %}
335-
</tbody>
336-
</table>
309+
</tbody>
310+
</table>
311+
{% if collector.voterDetails[key] is not empty %}
312+
{% set voter_details_id = 'voter-details-' ~ loop.index %}
313+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ voter_details_id }}" data-toggle-alt-content="Hide voter details">Show voter details</a>
314+
<div id="{{ voter_details_id }}" class="sf-toggle-content sf-toggle-hidden">
315+
<table class="voters">
316+
<thead>
317+
<tr>
318+
<th>#</th>
319+
<th>Voter class</th>
320+
<th>Vote result</th>
321+
</tr>
322+
</thead>
323+
<tbody>
324+
{% for voter_detail in collector.voterDetails[key] %}
325+
<tr>
326+
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
327+
<td class="font-normal">{{ profiler_dump(voter_detail['class']) }}</td>
328+
<td class="font-normal text-small">
329+
{% if voter_detail['vote'] is null %}
330+
-
331+
{% elseif voter_detail['vote'] == 1 %}
332+
ACCESS GRANTED
333+
{% elseif voter_detail['vote'] == 0 %}
334+
ACCESS ABSTAIN
335+
{% elseif voter_detail['vote'] == -1 %}
336+
ACCESS DENIED
337+
{% else %}
338+
unknown
339+
{% endif %}
340+
</td>
341+
</tr>
342+
{% endfor %}
343+
</tbody>
344+
</table>
345+
</div>
346+
{% endif %}
347+
{% endfor %}
337348
{% endif %}
338349
{% endblock %}

0 commit comments

Comments
 (0)
0