Skip to content
This repository was archived by the owner on Sep 16, 2021. It is now read-only.

Commit 548e312

Browse files
authored
Merge pull request #47 from symfony-cmf/issue-45/default-voter
Added security configuration
2 parents 67e6d04 + 2dd330c commit 548e312

File tree

14 files changed

+280
-9
lines changed

14 files changed

+280
-9
lines changed

src/DependencyInjection/CmfResourceRestExtension.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ public function load(array $configs, ContainerBuilder $container)
4141
$loader->load('resource-rest.xml');
4242

4343
$this->configurePayloadAliasRegistry($container, $config['payload_alias_map']);
44+
$this->configureSecurityVoter($loader, $container, $config['security']);
45+
}
46+
47+
private function configureSecurityVoter(XmlFileLoader $loader, ContainerBuilder $container, array $config)
48+
{
49+
if ([] === $config['access_control']) {
50+
return;
51+
}
52+
53+
$container->setParameter('cmf_resource_rest.security.access_map', $config['access_control']);
54+
55+
$loader->load('security.xml');
4456
}
4557

4658
public function getNamespace()

src/DependencyInjection/Configuration.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Cmf\Bundle\ResourceRestBundle\DependencyInjection;
1313

14+
use Symfony\Cmf\Bundle\ResourceRestBundle\Controller\ResourceController;
1415
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1516
use Symfony\Component\Config\Definition\ConfigurationInterface;
1617

@@ -29,6 +30,39 @@ public function getConfigTreeBuilder()
2930
->children()
3031
->integerNode('max_depth')->defaultValue(2)->end()
3132
->booleanNode('expose_payload')->defaultFalse()->end()
33+
34+
->arrayNode('security')
35+
->fixXmlConfig('rule', 'access_control')
36+
->addDefaultsIfNotSet()
37+
->children()
38+
->arrayNode('access_control')
39+
->defaultValue([])
40+
->prototype('array')
41+
->fixXmlConfig('attribute')
42+
->children()
43+
->scalarNode('pattern')->defaultValue('^/')->end()
44+
->scalarNode('repository')->defaultNull()->end()
45+
->arrayNode('attributes')
46+
->defaultValue([ResourceController::ROLE_RESOURCE_READ, ResourceController::ROLE_RESOURCE_WRITE])
47+
->prototype('scalar')->end()
48+
->end()
49+
->arrayNode('require')
50+
->isRequired()
51+
->requiresAtLeastOneElement()
52+
->beforeNormalization()
53+
->ifString()
54+
->then(function ($v) {
55+
return [$v];
56+
})
57+
->end()
58+
->prototype('scalar')->end()
59+
->end() // roles
60+
->end()
61+
->end()
62+
->end() // access_control
63+
->end()
64+
->end() // security
65+
3266
->arrayNode('payload_alias_map')
3367
->useAttributeAsKey('name')
3468
->prototype('array')
@@ -37,7 +71,7 @@ public function getConfigTreeBuilder()
3771
->scalarNode('type')->end()
3872
->end()
3973
->end()
40-
->end()
74+
->end() // payload_alias_map
4175
->end();
4276

4377
return $treeBuilder;

src/Resources/config/schema/resource-rest.xsd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<xsd:complexType name="config">
1010
<xsd:sequence>
1111
<xsd:element name="payload-alias" type="payload_alias" minOccurs="0" />
12+
<xsd:element name="rule" type="rule" minOccurs="0" />
1213
</xsd:sequence>
1314

1415
<xsd:attribute name="max-depth" type="xsd:integer" default="2" />
@@ -19,5 +20,17 @@
1920
<xsd:attribute name="type" type="xsd:string" />
2021
</xsd:complexType>
2122

23+
<xsd:complexType name="rule">
24+
<xsd:sequence>
25+
<xsd:element name="require" type="xsd:string" minOccurs="0" />
26+
<xsd:element name="attribute" type="xsd:string" minOccurs="0" />
27+
</xsd:sequence>
28+
29+
<xsd:attribute name="pattern" type="xsd:string" />
30+
<xsd:attribute name="repository" type="xsd:string" />
31+
<xsd:attribute name="require" type="xsd:string" />
32+
<xsd:attribute name="attribute" type="xsd:string" />
33+
</xsd:complexType>
34+
2235
</xsd:schema>
2336

src/Resources/config/security.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="cmf_resource_rest.security.resource_path_voter" class="Symfony\Cmf\Bundle\ResourceRestBundle\Security\ResourcePathVoter" public="false">
9+
<argument type="service" id="security.access.decision_manager" />
10+
<argument>%cmf_resource_rest.security.access_map%</argument>
11+
12+
<tag name="security.voter"/>
13+
</service>
14+
</services>
15+
</container>

src/Security/ResourcePathVoter.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2017 Symfony CMF
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Cmf\Bundle\ResourceRestBundle\Security;
13+
14+
use Symfony\Cmf\Bundle\ResourceRestBundle\Controller\ResourceController;
15+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16+
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
17+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
18+
19+
/**
20+
* @author Wouter de Jong <[email protected]>
21+
*/
22+
class ResourcePathVoter extends Voter
23+
{
24+
private $accessDecisionManager;
25+
private $accessMap;
26+
27+
public function __construct(AccessDecisionManagerInterface $accessDecisionManager, array $accessMap)
28+
{
29+
$this->accessDecisionManager = $accessDecisionManager;
30+
$this->accessMap = $accessMap;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
protected function supports($attribute, $subject)
37+
{
38+
return in_array($attribute, [ResourceController::ROLE_RESOURCE_READ, ResourceController::ROLE_RESOURCE_WRITE])
39+
&& is_array($subject) && isset($subject['repository_name']) && isset($subject['path']);
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
46+
{
47+
foreach ($this->accessMap as $rule) {
48+
if (!$this->ruleMatches($rule, $attribute, $subject)) {
49+
continue;
50+
}
51+
52+
if ($this->accessDecisionManager->decide($token, $rule['require'])) {
53+
return true;
54+
}
55+
}
56+
57+
return false;
58+
}
59+
60+
private function ruleMatches($rule, $attribute, $subject)
61+
{
62+
if (!in_array($attribute, $rule['attributes'])) {
63+
return false;
64+
}
65+
66+
if (null !== $rule['repository'] && $rule['repository'] !== $subject['repository_name']) {
67+
return false;
68+
}
69+
70+
if (!preg_match('{'.$rule['pattern'].'}', $subject['path'])) {
71+
return false;
72+
}
73+
74+
return true;
75+
}
76+
}

tests/Features/nesting.feature

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ Feature: Nesting resources
1111
default:
1212
type: doctrine/phpcr-odm
1313
basepath: /tests/cmf/articles
14+
15+
cmf_resource_rest:
16+
security:
17+
access_control:
18+
- { pattern: '^/', require: IS_AUTHENTICATED_ANONYMOUSLY }
1419
"""
1520
And there exists an "Article" document at "/cmf/articles/foo":
1621
| title | Article 1 |

tests/Features/resource_api_phpcr.feature

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Feature: PHPCR resource repository
1515
1616
cmf_resource_rest:
1717
expose_payload: true
18+
security:
19+
access_control:
20+
- { pattern: '^/', require: IS_AUTHENTICATED_ANONYMOUSLY }
1821
"""
1922

2023

tests/Features/resource_api_phpcr_odm.feature

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Feature: PHPCR-ODM resource repository
1818
article:
1919
repository: doctrine/phpcr-odm
2020
type: "Symfony\\Cmf\\Bundle\\ResourceRestBundle\\Tests\\Resources\\TestBundle\\Document\\Article"
21+
security:
22+
access_control:
23+
- { pattern: '^/', require: IS_AUTHENTICATED_ANONYMOUSLY }
2124
"""
2225

2326

tests/Features/security.feature

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ Feature: Security
1111
security:
1212
type: phpcr/phpcr
1313
basepath: /tests/cmf/articles
14+
15+
cmf_resource_rest:
16+
security:
17+
access_control:
18+
- { pattern: '^/tests/cmf/articles/private', repository: security, require: ROLE_ADMIN }
1419
"""
1520
And there exists an "Article" document at "/private/foo":
1621
| title | Article 1 |

tests/Resources/app/config/config.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*/
1111

1212
use Symfony\Cmf\Bundle\ResourceRestBundle\Tests\Resources\TestBundle\Description\DummyEnhancer;
13-
use Symfony\Cmf\Bundle\ResourceRestBundle\Tests\Resources\TestBundle\Security\ResourceVoter;
1413

1514
$container->setParameter('cmf_testing.bundle_fqn', 'Symfony\Cmf\Bundle\ResourceRestBundle');
1615
$loader->import(CMF_TEST_CONFIG_DIR.'/dist/parameters.yml');
@@ -20,8 +19,5 @@
2019
$loader->import(CMF_TEST_CONFIG_DIR.'/dist/security.yml');
2120
$loader->import(CMF_TEST_CONFIG_DIR.'/phpcr_odm.php');
2221

23-
$container->register('app.resource_voter', ResourceVoter::class)
24-
->addTag('security.voter');
25-
2622
$container->register('app.dummy_enhancer', DummyEnhancer::class)
2723
->addTag('cmf_resource.description.enhancer', ['alias' => 'dummy']);

0 commit comments

Comments
 (0)