Permalink
Browse files

feature #19490 [SecurityBundle] Integrate current firewall in Profile…

…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 f747fffeb4071880957f33aa8becea21369609a5
@@ -21,6 +21,8 @@
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallMAp;
/**
* SecurityDataCollector.
@@ -33,6 +35,7 @@ class SecurityDataCollector extends DataCollector
private $roleHierarchy;
private $logoutUrlGenerator;
private $accessDecisionManager;
private $firewallMap;
/**
* Constructor.
@@ -41,13 +44,15 @@ class SecurityDataCollector extends DataCollector
* @param RoleHierarchyInterface|null $roleHierarchy
* @param LogoutUrlGenerator|null $logoutUrlGenerator
* @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->roleHierarchy = $roleHierarchy;
$this->logoutUrlGenerator = $logoutUrlGenerator;
$this->accessDecisionManager = $accessDecisionManager;
$this->firewallMap = $firewallMap;
}
/**
@@ -132,6 +137,28 @@ public function collect(Request $request, Response $response, \Exception $except
$this->data['voter_strategy'] = 'unknown';
$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()),
);
}
}
}
/**
@@ -255,6 +282,16 @@ public function getAccessDecisionLog()
return $this->data['access_decision_log'];
}
/**
* Returns the configuration of the current firewall context.
*
* @return array
*/
public function getFirewall()
{
return $this->data['firewall'];
}
/**
* {@inheritdoc}
*/
@@ -11,6 +11,7 @@
<argument type="service" id="security.role_hierarchy" />
<argument type="service" id="security.logout_url_generator" />
<argument type="service" id="security.access.decision_manager" />
<argument type="service" id="security.firewall.map" />
</service>
</services>
</container>
@@ -33,6 +33,12 @@
<span>{{ collector.tokenClass|abbr_class }}</span>
</div>
{% endif %}
{% if collector.firewall %}
<div class="sf-toolbar-info-piece">
<b>Firewall name</b>
<span>{{ collector.firewall.name }}</span>
</div>
{% endif %}
{% if collector.logoutUrl %}
<div class="sf-toolbar-info-piece">
<b>Actions</b>
@@ -63,57 +69,127 @@
{% block panel %}
<h2>Security Token</h2>
{% if collector.token %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }}</span>
<span class="label">Username</span>
{% if collector.enabled %}
{% if collector.token %}
<div class="metrics">
<div class="metric">
<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 class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Authenticated</span>
<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 %}
<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>
{% 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 %}
<tr>
<th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
</tr>
{% endif %}
<h2>Security Firewall</h2>
{% 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>
{% if collector.firewall %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.firewall.name }}</span>
<span class="label">Name</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Security enabled</span>
</div>
<div class="metric">
<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 %}
<div class="empty">
<p>The security component is disabled.</p>
@@ -12,10 +12,13 @@
namespace Symfony\Bundle\SecurityBundle\Tests\DataCollector;
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\UsernamePasswordToken;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Http\FirewallMapInterface;
class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase
{
@@ -32,6 +35,7 @@ public function testCollectWhenSecurityIsDisabled()
$this->assertCount(0, $collector->getRoles());
$this->assertCount(0, $collector->getInheritedRoles());
$this->assertEmpty($collector->getUser());
$this->assertNull($collector->getFirewall());
}
public function testCollectWhenAuthenticationTokenIsNull()
@@ -47,6 +51,7 @@ public function testCollectWhenAuthenticationTokenIsNull()
$this->assertCount(0, $collector->getRoles());
$this->assertCount(0, $collector->getInheritedRoles());
$this->assertEmpty($collector->getUser());
$this->assertNull($collector->getFirewall());
}
/** @dataProvider provideRoles */
@@ -71,6 +76,70 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm
$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()
{
return array(

0 comments on commit f747fff

Please sign in to comment.