Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3aa0f2d

Browse files
committed
Add voter individual decisions to profiler
1 parent 3cfdc9e commit 3aa0f2d

File tree

9 files changed

+675
-57
lines changed

9 files changed

+675
-57
lines changed

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener;
1515
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
16+
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
1617
use Symfony\Component\Security\Core\Role\Role;
1718
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
1819
use Symfony\Component\HttpFoundation\Request;
@@ -136,15 +137,38 @@ 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+
// Voter constants
143+
$reflectionClass = new \ReflectionClass(VoterInterface::class);
144+
// Allows to get the access constant name from the vote log
145+
$voterConstants = array_flip($reflectionClass->getConstants());
146+
147+
$voterDetails = array();
148+
$voters = array();
149+
foreach ($decisionLog as $key => $log) {
150+
$voterDetails[$key] = array();
151+
foreach ($log['voterDetails'] as $voterClass => $voterVote) {
152+
$classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass;
153+
$voterDetails[$key][] = array(
154+
'class' => $classData,
155+
'vote' => $voterVote,
156+
);
157+
158+
if (!in_array($classData, $voters)) {
159+
$voters[] = $classData;
160+
}
161+
}
144162
}
163+
164+
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
165+
$this->data['voter_details'] = $voterDetails;
166+
$this->data['access_decision_log'] = $decisionLog;
167+
$this->data['voters'] = $voters;
145168
} else {
146169
$this->data['access_decision_log'] = array();
147170
$this->data['voter_strategy'] = 'unknown';
171+
$this->data['voter_details'] = array();
148172
$this->data['voters'] = array();
149173
}
150174

@@ -309,6 +333,8 @@ public function getLogoutUrl()
309333
* Returns the FQCN of the security voters enabled in the application.
310334
*
311335
* @return string[]
336+
*
337+
* @deprecated Use the data returned by {@link getVoterDetails()} instead
312338
*/
313339
public function getVoters()
314340
{
@@ -335,6 +361,16 @@ public function getAccessDecisionLog()
335361
return $this->data['access_decision_log'];
336362
}
337363

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

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\CompilerPassInterface;
1717
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
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 least 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)