Skip to content

Commit

Permalink
feature #19490 [SecurityBundle] Integrate current firewall in Profile…
Browse files Browse the repository at this point in the history
…r (chalasr)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[SecurityBundle] Integrate current firewall in Profiler

| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | no |
| Tests pass? | yes |
| Fixed tickets | n/a |
| License | MIT |

 Based on #19398.

This integrates current firewall information into the Profiler.

**Toolbar**
![Profiler toolbar](http://image.prntscr.com/image/bedec39cea4945e994c8531b80241cf6.png)

**Panel**
![Profiler panel](http://image.prntscr.com/image/3b656c1346844c6194a0db42cb8f9fdc.png)

Examples:

<details>
 <summary>

Show config</summary>

``` yaml
main:
    pattern:   ^/
    anonymous: false
    stateless: true
    provider: in_memory
    access_denied_url: /access_denied
    http_basic: ~
```

</details>

![Panel](http://image.prntscr.com/image/057062a1da744f3c8e00c3c77ded46a8.png)

<details>
 <summary>

Show config</summary>

``` yaml
main:
    pattern:   ^/
    anonymous: true
    stateless: false
    provider: in_memory
    context: dummy
    access_denied_url: /access_denied
    http_basic: ~
```

</details>

![Panel](http://image.prntscr.com/image/a44e54cf018d4bc98c3e0ecf92c37416.png)

<details>
 <summary>

Show config</summary>

``` yaml
api:
    pattern:   ^/
    security: false
```

</details>

![Panel](http://image.prntscr.com/image/c4ea3d7c792447b2ae2b18cd4e08d0dd.png)

Commits
-------

75e208e Integrate current firewall in profiler
  • Loading branch information
fabpot committed Nov 2, 2016
2 parents 904e90b + 75e208e commit f747fff
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 46 deletions.
Expand Up @@ -21,6 +21,8 @@
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager;
use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallMAp;


/** /**
* SecurityDataCollector. * SecurityDataCollector.
Expand All @@ -33,6 +35,7 @@ class SecurityDataCollector extends DataCollector
private $roleHierarchy; private $roleHierarchy;
private $logoutUrlGenerator; private $logoutUrlGenerator;
private $accessDecisionManager; private $accessDecisionManager;
private $firewallMap;


/** /**
* Constructor. * Constructor.
Expand All @@ -41,13 +44,15 @@ class SecurityDataCollector extends DataCollector
* @param RoleHierarchyInterface|null $roleHierarchy * @param RoleHierarchyInterface|null $roleHierarchy
* @param LogoutUrlGenerator|null $logoutUrlGenerator * @param LogoutUrlGenerator|null $logoutUrlGenerator
* @param AccessDecisionManagerInterface|null $accessDecisionManager * @param AccessDecisionManagerInterface|null $accessDecisionManager
* @param FirewallMapInterface|null $firewallMap
*/ */
public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null) public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null)
{ {
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->roleHierarchy = $roleHierarchy; $this->roleHierarchy = $roleHierarchy;
$this->logoutUrlGenerator = $logoutUrlGenerator; $this->logoutUrlGenerator = $logoutUrlGenerator;
$this->accessDecisionManager = $accessDecisionManager; $this->accessDecisionManager = $accessDecisionManager;
$this->firewallMap = $firewallMap;
} }


/** /**
Expand Down Expand Up @@ -132,6 +137,28 @@ public function collect(Request $request, Response $response, \Exception $except
$this->data['voter_strategy'] = 'unknown'; $this->data['voter_strategy'] = 'unknown';
$this->data['voters'] = array(); $this->data['voters'] = array();
} }

// collect firewall context information
$this->data['firewall'] = null;
if ($this->firewallMap instanceof FirewallMap) {
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
if (null !== $firewallConfig) {
$this->data['firewall'] = array(
'name' => $firewallConfig->getName(),
'allows_anonymous' => $firewallConfig->allowsAnonymous(),
'request_matcher' => $firewallConfig->getRequestMatcher(),
'security_enabled' => $firewallConfig->isSecurityEnabled(),
'stateless' => $firewallConfig->isStateless(),
'provider' => $firewallConfig->getProvider(),
'context' => $firewallConfig->getContext(),
'entry_point' => $firewallConfig->getEntryPoint(),
'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(),
'access_denied_url' => $firewallConfig->getAccessDeniedUrl(),
'user_checker' => $firewallConfig->getUserChecker(),
'listeners' => $this->cloneVar($firewallConfig->getListeners()),
);
}
}
} }


/** /**
Expand Down Expand Up @@ -255,6 +282,16 @@ public function getAccessDecisionLog()
return $this->data['access_decision_log']; return $this->data['access_decision_log'];
} }


/**
* Returns the configuration of the current firewall context.
*
* @return array
*/
public function getFirewall()
{
return $this->data['firewall'];
}

/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
Expand Down
Expand Up @@ -11,6 +11,7 @@
<argument type="service" id="security.role_hierarchy" /> <argument type="service" id="security.role_hierarchy" />
<argument type="service" id="security.logout_url_generator" /> <argument type="service" id="security.logout_url_generator" />
<argument type="service" id="security.access.decision_manager" /> <argument type="service" id="security.access.decision_manager" />
<argument type="service" id="security.firewall.map" />
</service> </service>
</services> </services>
</container> </container>
Expand Up @@ -33,6 +33,12 @@
<span>{{ collector.tokenClass|abbr_class }}</span> <span>{{ collector.tokenClass|abbr_class }}</span>
</div> </div>
{% endif %} {% endif %}
{% if collector.firewall %}
<div class="sf-toolbar-info-piece">
<b>Firewall name</b>
<span>{{ collector.firewall.name }}</span>
</div>
{% endif %}
{% if collector.logoutUrl %} {% if collector.logoutUrl %}
<div class="sf-toolbar-info-piece"> <div class="sf-toolbar-info-piece">
<b>Actions</b> <b>Actions</b>
Expand Down Expand Up @@ -63,57 +69,127 @@
{% block panel %} {% block panel %}
<h2>Security Token</h2> <h2>Security Token</h2>


{% if collector.token %} {% if collector.enabled %}
<div class="metrics"> {% if collector.token %}
<div class="metric"> <div class="metrics">
<span class="value">{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }}</span> <div class="metric">
<span class="label">Username</span> <span class="value">{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }}</span>
<span class="label">Username</span>
</div>

<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Authenticated</span>
</div>
</div> </div>


<div class="metric"> <table>
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }}</span> <thead>
<span class="label">Authenticated</span> <tr>
<th scope="col" class="key">Property</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>Roles</th>
<td>
{{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }}

{% if not collector.authenticated and collector.roles is empty %}
<p class="help">User is not authenticated probably because they have no roles.</p>
{% endif %}
</td>
</tr>

{% if collector.supportsRoleHierarchy %}
<tr>
<th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
</tr>
{% endif %}

{% if collector.token %}
<tr>
<th>Token</th>
<td>{{ profiler_dump(collector.token) }}</td>
</tr>
{% endif %}
</tbody>
</table>
{% elseif collector.enabled %}
<div class="empty">
<p>There is no security token.</p>
</div> </div>
</div> {% endif %}


<table>
<thead>
<tr>
<th scope="col" class="key">Property</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>Roles</th>
<td>
{{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }}

{% if not collector.authenticated and collector.roles is empty %}
<p class="help">User is not authenticated probably because they have no roles.</p>
{% endif %}
</td>
</tr>


{% if collector.supportsRoleHierarchy %} <h2>Security Firewall</h2>
<tr>
<th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
</tr>
{% endif %}


{% if collector.token %} {% if collector.firewall %}
<tr> <div class="metrics">
<th>Token</th> <div class="metric">
<td>{{ profiler_dump(collector.token) }}</td> <span class="value">{{ collector.firewall.name }}</span>
</tr> <span class="label">Name</span>
{% endif %} </div>
</tbody> <div class="metric">
</table> <span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }}</span>
{% elseif collector.enabled %} <span class="label">Security enabled</span>
<div class="empty"> </div>
<p>There is no security token.</p> <div class="metric">
</div> <span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Stateless</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.allows_anonymous ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Allows anonymous</span>
</div>
</div>
{% if collector.firewall.security_enabled %}
<table>
<thead>
<tr>
<th scope="col" class="key">Key</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>provider</th>
<td>{{ collector.firewall.provider }}</td>
</tr>
<tr>
<th>context</th>
<td>{{ collector.firewall.context }}</td>
</tr>
<tr>
<th>entry_point</th>
<td>{{ collector.firewall.entry_point }}</td>
</tr>
<tr>
<th>user_checker</th>
<td>{{ collector.firewall.user_checker }}</td>
</tr>
<tr>
<th>access_denied_handler</th>
<td>{{ collector.firewall.access_denied_handler }}</td>
</tr>
<tr>
<th>access_denied_url</th>
<td>{{ collector.firewall.access_denied_url }}</td>
</tr>
<tr>
<th>listeners</th>
<td>{{ collector.firewall.listeners is empty ? 'none' : profiler_dump(collector.firewall.listeners, maxDepth=1) }}</td>
</tr>
</tbody>
</table>
{% endif %}
{% elseif collector.enabled %}
<div class="empty">
<p>There is no firewall.</p>
</div>
{% endif %}
{% else %} {% else %}
<div class="empty"> <div class="empty">
<p>The security component is disabled.</p> <p>The security component is disabled.</p>
Expand Down
Expand Up @@ -12,10 +12,13 @@
namespace Symfony\Bundle\SecurityBundle\Tests\DataCollector; namespace Symfony\Bundle\SecurityBundle\Tests\DataCollector;


use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector;
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\FirewallMapInterface;


class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase
{ {
Expand All @@ -32,6 +35,7 @@ public function testCollectWhenSecurityIsDisabled()
$this->assertCount(0, $collector->getRoles()); $this->assertCount(0, $collector->getRoles());
$this->assertCount(0, $collector->getInheritedRoles()); $this->assertCount(0, $collector->getInheritedRoles());
$this->assertEmpty($collector->getUser()); $this->assertEmpty($collector->getUser());
$this->assertNull($collector->getFirewall());
} }


public function testCollectWhenAuthenticationTokenIsNull() public function testCollectWhenAuthenticationTokenIsNull()
Expand All @@ -47,6 +51,7 @@ public function testCollectWhenAuthenticationTokenIsNull()
$this->assertCount(0, $collector->getRoles()); $this->assertCount(0, $collector->getRoles());
$this->assertCount(0, $collector->getInheritedRoles()); $this->assertCount(0, $collector->getInheritedRoles());
$this->assertEmpty($collector->getUser()); $this->assertEmpty($collector->getUser());
$this->assertNull($collector->getFirewall());
} }


/** @dataProvider provideRoles */ /** @dataProvider provideRoles */
Expand All @@ -71,6 +76,70 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm
$this->assertSame('hhamon', $collector->getUser()); $this->assertSame('hhamon', $collector->getUser());
} }


public function testGetFirewall()
{
$firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy');
$request = $this->getRequest();

$firewallMap = $this
->getMockBuilder(FirewallMap::class)
->disableOriginalConstructor()
->getMock();
$firewallMap
->expects($this->once())
->method('getFirewallConfig')
->with($request)
->willReturn($firewallConfig);

$collector = new SecurityDataCollector(null, null, null, null, $firewallMap);
$collector->collect($request, $this->getResponse());
$collected = $collector->getFirewall();

$this->assertSame($firewallConfig->getName(), $collected['name']);
$this->assertSame($firewallConfig->allowsAnonymous(), $collected['allows_anonymous']);
$this->assertSame($firewallConfig->getRequestMatcher(), $collected['request_matcher']);
$this->assertSame($firewallConfig->isSecurityEnabled(), $collected['security_enabled']);
$this->assertSame($firewallConfig->isStateless(), $collected['stateless']);
$this->assertSame($firewallConfig->getProvider(), $collected['provider']);
$this->assertSame($firewallConfig->getContext(), $collected['context']);
$this->assertSame($firewallConfig->getEntryPoint(), $collected['entry_point']);
$this->assertSame($firewallConfig->getAccessDeniedHandler(), $collected['access_denied_handler']);
$this->assertSame($firewallConfig->getAccessDeniedUrl(), $collected['access_denied_url']);
$this->assertSame($firewallConfig->getUserChecker(), $collected['user_checker']);
$this->assertSame($firewallConfig->getListeners(), $collected['listeners']->getRawData()[0][0]);
}

public function testGetFirewallReturnsNull()
{
$request = $this->getRequest();
$response = $this->getResponse();

// Don't inject any firewall map
$collector = new SecurityDataCollector();
$collector->collect($request, $response);
$this->assertNull($collector->getFirewall());

// Inject an instance that is not context aware
$firewallMap = $this
->getMockBuilder(FirewallMapInterface::class)
->disableOriginalConstructor()
->getMock();

$collector = new SecurityDataCollector(null, null, null, null, $firewallMap);
$collector->collect($request, $response);
$this->assertNull($collector->getFirewall());

// Null config
$firewallMap = $this
->getMockBuilder(FirewallMap::class)
->disableOriginalConstructor()
->getMock();

$collector = new SecurityDataCollector(null, null, null, null, $firewallMap);
$collector->collect($request, $response);
$this->assertNull($collector->getFirewall());
}

public function provideRoles() public function provideRoles()
{ {
return array( return array(
Expand Down

0 comments on commit f747fff

Please sign in to comment.