Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ CHANGELOG

7.4
---

* Add role hierarchy graph to the profiler security panel
* Add `debug:security:role-hierarchy` command to dump role hierarchy graphs in the Mermaid.js flowchart format
* Add `Security::getAccessDecision()` and `getAccessDecisionForUser()` helpers
* Add `Security::getAccessDecision()` and `getAccessDecisionForUser()` helpers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Add `Security::getAccessDecision()` and `getAccessDecisionForUser()` helpers
* Add `Security::getAccessDecision()` and `getAccessDecisionForUser()` helpers

* Add options to configure a cache pool and storage service for login throttling rate limiters
* Register alias for argument for password hasher when its key is not a class name:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager;
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
use Symfony\Component\Security\Core\Dumper\MermaidDumper;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\FirewallMapInterface;
Expand All @@ -46,6 +47,7 @@ public function __construct(
private ?AccessDecisionManagerInterface $accessDecisionManager = null,
private ?FirewallMapInterface $firewallMap = null,
private ?TraceableFirewallListener $firewall = null,
private MermaidDumper $mermaidDumper = new MermaidDumper(),
) {
$this->hasVarDumper = class_exists(ClassStub::class);
}
Expand Down Expand Up @@ -92,7 +94,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
$impersonatorUser = $originalToken->getUserIdentifier();
}

if (null !== $this->roleHierarchy) {
if ($this->roleHierarchy) {
foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) {
if (!\in_array($role, $assignedRoles, true)) {
$inheritedRoles[] = $role;
Expand Down Expand Up @@ -221,6 +223,9 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
$response->headers->clearCookie($authCookieName);
}
}
if ($this->roleHierarchy) {
$this->data['roles_diagram'] = $this->mermaidDumper->dump($this->roleHierarchy);
}
}

public function reset(): void
Expand Down Expand Up @@ -379,4 +384,9 @@ public function getName(): string
{
return 'security';
}

public function getRolesDiagram(): ?string
{
return $this->data['roles_diagram'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,48 @@
</td>
</tr>

{% if collector.supportsRoleHierarchy %}
{% if collector.supportsRoleHierarchy and collector.rolesDiagram is not empty %}
<script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps check if the data is not null/not empty before loading all this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea indeed.

{{ source('@WebProfiler/Script/Mermaid/mermaid-flowchart-v2.min.js') }}
const isDarkMode = document.querySelector('body').classList.contains('theme-dark');
mermaid.initialize({
flowchart: {
useMaxWidth: true,
},
securityLevel: 'loose',
theme: 'base',
themeVariables: {
darkMode: isDarkMode,
fontFamily: 'var(--font-family-system)',
fontSize: 'var(--font-size-body)',
// the properties below don't support CSS variables
primaryColor: isDarkMode ? 'lightsteelblue' : 'aliceblue',
primaryTextColor: isDarkMode ? '#000' : '#000',
primaryBorderColor: isDarkMode ? 'steelblue' : 'lightsteelblue',
lineColor: isDarkMode ? '#939393' : '#d4d4d4',
secondaryColor: isDarkMode ? 'lightyellow' : 'lightyellow',
tertiaryColor: isDarkMode ? 'lightSalmon' : 'lightSalmon',
}
});
document.addEventListener('DOMContentLoaded', () => {
const mEl = document.querySelector('.sf-mermaid');
mermaid.run({
nodes: [mEl],
});
});
</script>
<tr>
<th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth: 1) }}</td>
</tr>
<tr>
<th>Available Roles</th>
<td>
<pre class="sf-mermaid">
{{ collector.rolesDiagram|raw }}
</pre>
</td>
</tr>
{% endif %}

{% if collector.token %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Dumper\MermaidDumper;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Http\Firewall\AbstractListener;
Expand Down Expand Up @@ -64,7 +65,15 @@ public function testCollectWhenSecurityIsDisabled()
public function testCollectWhenAuthenticationTokenIsNull()
{
$tokenStorage = new TokenStorage();
$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null);
$mermaidDumper = $this->createMock(MermaidDumper::class);

$mermaidDumper
->expects($this->once())
->method('dump')
->with($this->getRoleHierarchy())
->willReturn('graph TD; A-->B;');

$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, $mermaidDumper);
$collector->collect(new Request(), new Response());

$this->assertTrue($collector->isEnabled());
Expand All @@ -85,8 +94,15 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm
{
$tokenStorage = new TokenStorage();
$tokenStorage->setToken(new UsernamePasswordToken(new InMemoryUser('hhamon', 'P4$$w0rD', $roles), 'provider', $roles));
$mermaidDumper = $this->createMock(MermaidDumper::class);

$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null);
$mermaidDumper
->expects($this->once())
->method('dump')
->with($this->getRoleHierarchy())
->willReturn('graph TD; A-->B;');

$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, $mermaidDumper);
$collector->collect(new Request(), new Response());
$collector->lateCollect();

Expand All @@ -108,8 +124,15 @@ public function testCollectSwitchUserToken()

$tokenStorage = new TokenStorage();
$tokenStorage->setToken(new SwitchUserToken(new InMemoryUser('hhamon', 'P4$$w0rD', ['ROLE_USER']), 'provider', ['ROLE_USER'], $adminToken));
$mermaidDumper = $this->createMock(MermaidDumper::class);

$mermaidDumper
->expects($this->once())
->method('dump')
->with($this->getRoleHierarchy())
->willReturn('graph TD; A-->B;');

$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null);
$collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, $mermaidDumper);
$collector->collect(new Request(), new Response());
$collector->lateCollect();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function dump(RoleHierarchyInterface $roleHierarchy, MermaidDirection $di
$hierarchy = $this->extractHierarchy($roleHierarchy);

if (!$hierarchy) {
return "graph {$direction->value}\n classDef default fill:#e1f5fe;";
return '';
}

$output = ["graph {$direction->value}"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ public function testDumpEmptyHierarchy()
$dumper = new MermaidDumper();
$output = $dumper->dump($roleHierarchy);

$this->assertStringContainsString('graph TB', $output);
$this->assertStringContainsString('classDef default fill:#e1f5fe;', $output);
$this->assertEmpty($output);
}

public function testDumpComplexHierarchy()
Expand Down
Loading