Skip to content

Commit d32d599

Browse files
committed
Merge branch 'release/4.3'
2 parents 2612136 + 41b0f0a commit d32d599

19 files changed

+1651
-41
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# 4.3.1
2+
**Bugfix**
3+
* Update metadata.xml template reference
4+
5+
# 4.3.0
6+
**New feature**
7+
* Further support Symfony 4 by adhering to the Twig environment in favor of
8+
the old 'Environment' solution
9+
110
# 4.2.0
211
**New feature**
312
* Support for SAML extensions on Authn SAML requests

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,25 @@ surfnet_saml:
6666
assertion_consumer_service_url: "%surfnet_saml_remote_sp_acs%"
6767
```
6868
69-
The hosted configuration lists the configuration for the services (SP, IdP or both) that your application offers. SP and IdP
69+
The `hosted:` configuration lists the configuration for the services (SP, IdP or both) that your application offers. SP and IdP
7070
functionality can be turned off and on individually through the repective `enabled` flags.
71-
The remote configuration lists, if enabled, the configuration for a remote IdP to connect to.
71+
72+
The `remote:` configuration lists, if enabled, the configuration for one or more remote service providers and identity providers to connect to.
73+
If your application authenticates with a single identity provider, you can use the `identity_provider:` option as shown above. The identity
74+
provider can be accessed runtime using the `@surfnet_saml.remote.idp` service.
75+
76+
If your application authenticates with more than one identity providers, you can omit the `identity_provider:` key from configuration and list all
77+
identity providers under `identity_providers:`. The identity providers can be accessed by using the `@surfnet_saml.remote.identity_providers` service.
78+
```yaml
79+
remote:
80+
identity_providers:
81+
- enabled: true
82+
entity_id: %surfnet_saml_remote_idp_entity_id%
83+
sso_url: %surfnet_saml_remote_idp_sso_url%
84+
certificate: %surfnet_saml_remote_idp_certificate%
85+
86+
```
87+
7288
The inlined certificate in the last line can be replaced with `certificate_file` containing a filesystem path to
7389
a file which contains said certificate.
7490
It is recommended to use parameters as listed above. The various `publickey` and `privatekey` variables are the

composer.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,24 @@
77
"minimum-stability": "stable",
88
"require": {
99
"php": ">=7.2,<8.0-dev",
10+
"ext-dom": "*",
1011
"ext-openssl": "*",
1112
"robrichards/xmlseclibs": "^3.0.4",
1213
"simplesamlphp/saml2": "3.2.*",
1314
"symfony/dependency-injection": "^4.4",
1415
"symfony/framework-bundle": "^4.4",
1516
"symfony/templating": "^4.4",
16-
"ext-dom": "*"
17+
"twig/twig": "^2"
1718
},
1819
"require-dev": {
20+
"jasny/phpunit-xsdvalidation": "^1.0",
21+
"mockery/mockery": "~0.9",
1922
"phpmd/phpmd": "^2.6",
2023
"phpunit/phpunit": "^5.7",
24+
"psr/log": "~1.0",
2125
"sebastian/exporter": "~2.0",
2226
"sebastian/phpcpd": "^2.0",
2327
"squizlabs/php_codesniffer": "^3.0",
24-
"mockery/mockery": "~0.9",
25-
"psr/log": "~1.0",
2628
"symfony/phpunit-bridge": "^4.4"
2729
},
2830
"config": {

src/DependencyInjection/Configuration.php

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
namespace Surfnet\SamlBundle\DependencyInjection;
2020

2121
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
22+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
2223
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
2324
use Symfony\Component\Config\Definition\ConfigurationInterface;
2425

@@ -34,8 +35,8 @@ class Configuration implements ConfigurationInterface
3435
*/
3536
public function getConfigTreeBuilder()
3637
{
37-
$treeBuilder = new TreeBuilder();
38-
$rootNode = $treeBuilder->root('surfnet_saml');
38+
$treeBuilder = new TreeBuilder('surfnet_saml');
39+
$rootNode = $treeBuilder->getRootNode();
3940

4041
$this->addHostedSection($rootNode);
4142
$this->addRemoteSection($rootNode);
@@ -137,39 +138,69 @@ private function addRemoteSection(ArrayNodeDefinition $rootNode)
137138
->children()
138139
->arrayNode('remote');
139140

140-
$this->addRemoteIdentityProviderSection($remoteNode);
141+
$this->addRemoteIdentityProvidersSection($remoteNode);
141142
$this->addRemoteServiceProvidersSection($remoteNode);
143+
144+
// For backwards compatibility: support configuring a single remote IDP
145+
$this->addRemoteIdentityProviderSection($remoteNode);
142146
}
143147

144148
private function addRemoteIdentityProviderSection(ArrayNodeDefinition $remoteNode)
145149
{
146-
$remoteNode
150+
$arrayNode = $remoteNode
147151
->children()
148152
->arrayNode('identity_provider')
149153
->canBeEnabled()
150-
->children()
151-
->scalarNode('entity_id')
152-
->isRequired()
153-
->info('The EntityID of the remote identity provider')
154-
->end()
155-
->scalarNode('sso_url')
156-
->isRequired()
157-
->info('The name of the route to generate the SSO URL')
158-
->end()
159-
->scalarNode('certificate')
160-
->info(
161-
'The contents of the certificate used to sign the AuthnResponse with'
162-
)
163-
->end()
164-
->scalarNode('certificate_file')
165-
->info(
166-
'A file containing the certificate used to sign the AuthnResponse with'
167-
)
168-
->end()
154+
->children();
155+
156+
$this->addRemoteIdentityProviderConfiguration($arrayNode);
157+
158+
$arrayNode
169159
->end()
170160
->end();
171161
}
172162

163+
164+
private function addRemoteIdentityProviderConfiguration(NodeBuilder $arrayNode)
165+
{
166+
$arrayNode
167+
->scalarNode('entity_id')
168+
->isRequired()
169+
->info('The EntityID of the remote identity provider')
170+
->end()
171+
->scalarNode('sso_url')
172+
->isRequired()
173+
->info('The name of the route to generate the SSO URL')
174+
->end()
175+
->scalarNode('certificate')
176+
->info(
177+
'The contents of the certificate used to sign the AuthnResponse with'
178+
)
179+
->end()
180+
->scalarNode('certificate_file')
181+
->info(
182+
'A file containing the certificate used to sign the AuthnResponse with'
183+
)
184+
->end();
185+
}
186+
187+
private function addRemoteIdentityProvidersSection(ArrayNodeDefinition $remoteNode)
188+
{
189+
$arrayNode = $remoteNode
190+
->children()
191+
->arrayNode('identity_providers')
192+
->prototype('array')
193+
->children();
194+
195+
$this->addRemoteIdentityProviderConfiguration($arrayNode);
196+
197+
$arrayNode
198+
->end()
199+
->end()
200+
->end()
201+
->end();
202+
}
203+
173204
private function addRemoteServiceProvidersSection(ArrayNodeDefinition $remoteNode)
174205
{
175206
$remoteNode

src/DependencyInjection/SurfnetSamlExtension.php

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
use Surfnet\SamlBundle\Entity\IdentityProvider;
2222
use Surfnet\SamlBundle\Entity\ServiceProvider;
23+
use Surfnet\SamlBundle\Entity\StaticIdentityProviderRepository;
2324
use Surfnet\SamlBundle\Entity\StaticServiceProviderRepository;
2425
use Surfnet\SamlBundle\Exception\SamlInvalidConfigurationException;
2526
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
@@ -149,20 +150,46 @@ private function parseMetadataConfiguration(array $configuration, ContainerBuild
149150
*/
150151
private function parseRemoteConfiguration(array $remoteConfiguration, ContainerBuilder $container)
151152
{
152-
$this->parseRemoteIdentityProviderConfiguration($remoteConfiguration['identity_provider'], $container);
153153
$this->parseRemoteServiceProviderConfigurations($remoteConfiguration['service_providers'], $container);
154+
155+
// Parse a configuration where multiple remote IDPs are configured (identity_providers:)
156+
$this->parseRemoteIdentityProviderConfigurations($remoteConfiguration['identity_providers'], $container);
157+
158+
// Parse a single remote IDP configuration (identity_provider:)
159+
if (!empty($remoteConfiguration['identity_provider']['enabled'])) {
160+
$definition = $this->parseRemoteIdentityProviderConfiguration($remoteConfiguration['identity_provider']);
161+
162+
if ($definition !== null) {
163+
$container->setDefinition('surfnet_saml.remote.idp', $definition);
164+
}
165+
}
154166
}
155167

156168
/**
157-
* @param array $identityProvider
158-
* @param ContainerBuilder $container
169+
* @param array $identityProviders
170+
* @param $container
171+
* @throws \Surfnet\SamlBundle\Exception\SamlInvalidConfigurationException
159172
*/
160-
private function parseRemoteIdentityProviderConfiguration(array $identityProvider, ContainerBuilder $container)
173+
private function parseRemoteIdentityProviderConfigurations(array $identityProviders, ContainerBuilder $container)
161174
{
162-
if (!$identityProvider['enabled']) {
163-
return;
164-
}
175+
$definitions = array_map(function ($config) {
176+
return $this->parseRemoteIdentityProviderConfiguration($config);
177+
}, $identityProviders);
178+
179+
$definition = new Definition(StaticIdentityProviderRepository::class, [
180+
$definitions
181+
]);
182+
$definition->setPublic(true);
183+
$container->setDefinition('surfnet_saml.remote.identity_providers', $definition);
184+
}
165185

186+
/**
187+
* @param array $identityProvider
188+
*
189+
* @return Definition
190+
*/
191+
private function parseRemoteIdentityProviderConfiguration(array $identityProvider)
192+
{
166193
$definition = new Definition(IdentityProvider::class);
167194
$configuration = [
168195
'entityId' => $identityProvider['entity_id'],
@@ -175,7 +202,9 @@ private function parseRemoteIdentityProviderConfiguration(array $identityProvide
175202
);
176203

177204
$definition->setArguments([$configuration]);
178-
$container->setDefinition('surfnet_saml.remote.idp', $definition);
205+
$definition->setPublic(true);
206+
207+
return $definition;
179208
}
180209

181210
/**
@@ -192,6 +221,7 @@ private function parseRemoteServiceProviderConfigurations(array $serviceProvider
192221
$definition = new Definition(StaticServiceProviderRepository::class, [
193222
$definitions
194223
]);
224+
$definition->setPublic(true);
195225
$container->setDefinition('surfnet_saml.remote.service_providers', $definition);
196226
}
197227

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2017 SURFnet bv
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace Surfnet\SamlBundle\Entity;
20+
21+
interface IdentityProviderRepository
22+
{
23+
/**
24+
* @param string $entityId
25+
* @return IdentityProvider
26+
*/
27+
public function getIdentityProvider($entityId);
28+
29+
/**
30+
* @param string $entityId
31+
* @return bool
32+
*/
33+
public function hasIdentityProvider($entityId);
34+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2017 SURFnet bv
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace Surfnet\SamlBundle\Entity\ImmutableCollection;
20+
21+
use Surfnet\SamlBundle\Entity\IdentityProvider;
22+
23+
/**
24+
* Collection of identity providers
25+
*
26+
* Protects the integrity and provides low level logic functions.
27+
*/
28+
final class IdentityProviders
29+
{
30+
private $identityProviders;
31+
32+
/**
33+
*
34+
* @param IdentityProvider[] $identityProviders
35+
*/
36+
public function __construct(array $identityProviders)
37+
{
38+
$this->identityProviders = array_values($identityProviders);
39+
}
40+
41+
public function hasByEntityId($entityId)
42+
{
43+
return $this->findByEntityId($entityId) !== null;
44+
}
45+
46+
public function findByEntityId($entityId)
47+
{
48+
return $this->find(function (IdentityProvider $provider) use ($entityId) {
49+
return $provider->getEntityId() === $entityId;
50+
});
51+
}
52+
53+
private function find(callable $callback)
54+
{
55+
foreach ($this->identityProviders as $provider) {
56+
if ($callback($provider)) {
57+
return $provider;
58+
}
59+
}
60+
return null;
61+
}
62+
}

0 commit comments

Comments
 (0)