Completed
Push — master ( 990ac1...53601d )
by
unknown
02:13
created

validateDescriptions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 3
nop 4
1
<?php
2
3
/**
4
 * Copyright 2014 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\StepupSelfService\SamlStepupProviderBundle\DependencyInjection;
20
21
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
22
use Symfony\Component\Config\FileLocator;
23
use Symfony\Component\DependencyInjection\ContainerBuilder;
24
use Symfony\Component\DependencyInjection\Definition;
25
use Symfony\Component\DependencyInjection\Loader;
26
use Symfony\Component\DependencyInjection\Reference;
27
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
28
29
class SurfnetStepupSelfServiceSamlStepupProviderExtension extends Extension
30
{
31
    const VIEW_CONFIG_TAG_NAME = 'gssp.view_config';
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function load(array $configs, ContainerBuilder $container)
37
    {
38
        $configuration = new Configuration();
39
        $config = $this->processConfiguration($configuration, $configs);
40
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
41
        $loader->load('services.yml');
42
43
        foreach ($config['providers'] as $provider => $providerConfiguration) {
44
            // may seem a bit strange, but this prevents casing issue when getting/setting/creating provider
45
            // service definitions etc.
46
            if ($provider !== strtolower($provider)) {
47
                throw new InvalidConfigurationException('The provider name must be completely lowercase');
48
            }
49
50
            $this->loadProviderConfiguration($provider, $providerConfiguration, $config['routes'], $container);
51
        }
52
    }
53
54
    private function loadProviderConfiguration(
55
        $provider,
56
        array $configuration,
57
        array $routes,
58
        ContainerBuilder $container
59
    ) {
60
61
        if ($container->has('gssp.provider.' . $provider)) {
62
            throw new InvalidConfigurationException(sprintf('Cannot create the same provider "%s" twice', $provider));
63
        }
64
65
        $this->createHostedDefinitions($provider, $configuration['hosted'], $routes, $container);
66
        $this->createMetadataDefinition($provider, $configuration['hosted'], $routes, $container);
67
        $this->createRemoteDefinition($provider, $configuration['remote'], $container);
68
69
        $stateHandlerDefinition = new Definition(
70
            'Surfnet\StepupSelfService\SamlStepupProviderBundle\Saml\StateHandler',
71
            [
72
                new Reference('gssp.sessionbag'),
73
                $provider
74
            ]
75
        );
76
        $container->setDefinition('gssp.provider.' . $provider . '.statehandler', $stateHandlerDefinition);
77
78
        $providerDefinition = new Definition('Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\Provider', [
79
            $provider,
80
            new Reference('gssp.provider.' . $provider . '.hosted.sp'),
81
            new Reference('gssp.provider.' . $provider . '.remote.idp'),
82
            new Reference('gssp.provider.' . $provider . '.statehandler')
83
        ]);
84
85
        $providerDefinition->setPublic(false);
86
        $container->setDefinition('gssp.provider.' . $provider, $providerDefinition);
87
88
        // When the android url is set, the description should contain the android play store url parameter.
89
        // The same goes for the iOs app url.
90
        $this->validateDescriptions(
91
            $configuration['view_config']['description'],
92
            $configuration['view_config']['app_android_url'],
93
            $provider,
94
            'android'
95
        );
96
97
        $this->validateDescriptions(
98
            $configuration['view_config']['description'],
99
            $configuration['view_config']['app_ios_url'],
100
            $provider,
101
            'ios'
102
        );
103
104
        $viewConfigDefinition = new Definition('Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\ViewConfig', [
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
105
            new Reference('request_stack'),
106
            $configuration['view_config']['loa'],
107
            $configuration['view_config']['logo'],
108
            $configuration['view_config']['app_android_url'],
109
            $configuration['view_config']['app_ios_url'],
110
            $configuration['view_config']['alt'],
111
            $configuration['view_config']['title'],
112
            $configuration['view_config']['description'],
113
            $configuration['view_config']['button_use'],
114
            $configuration['view_config']['initiate_title'],
115
            $configuration['view_config']['initiate_button'],
116
            $configuration['view_config']['explanation'],
117
            $configuration['view_config']['authn_failed'],
118
            $configuration['view_config']['pop_failed'],
119
        ]);
120
        $viewConfigDefinition->addTag(self::VIEW_CONFIG_TAG_NAME);
121
122
        $container->setDefinition('gssp.view_config.' . $provider, $viewConfigDefinition);
123
124
        $container
125
            ->getDefinition('gssp.provider_repository')
126
            ->addMethodCall('addProvider', [new Reference('gssp.provider.' . $provider)]);
127
    }
128
129
    /**
130
     * @param string           $provider
131
     * @param array            $configuration
132
     * @param array            $routes
133
     * @param ContainerBuilder $container
134
     */
135
    private function createHostedDefinitions(
136
        $provider,
137
        array $configuration,
138
        array $routes,
139
        ContainerBuilder $container
140
    ) {
141
        $hostedDefinition = $this->buildHostedEntityDefinition($provider, $configuration, $routes);
142
        $container->setDefinition('gssp.provider.' . $provider . '.hosted_entities', $hostedDefinition);
143
144
        $hostedSpDefinition  = (new Definition())
145
            ->setClass('Surfnet\SamlBundle\Entity\ServiceProvider')
146
            ->setFactory([
147
                new Reference('gssp.provider.' . $provider . '.hosted_entities'),
148
                'getServiceProvider'
149
            ])
150
            ->setPublic(false);
151
        $container->setDefinition('gssp.provider.' . $provider . '.hosted.sp', $hostedSpDefinition);
152
    }
153
154
    /**
155
     * @param string $provider
156
     * @param array  $configuration
157
     * @param array  $routes
158
     * @return Definition
159
     */
160
    private function buildHostedEntityDefinition($provider, array $configuration, array $routes)
161
    {
162
        $entityId = ['entity_id_route' => $this->createRouteConfig($provider, $routes['metadata'])];
163
        $spAdditional = [
164
            'enabled' => true,
165
            'assertion_consumer_route' => $this->createRouteConfig($provider, $routes['consume_assertion'])
166
        ];
167
        $idpAdditional = [
168
            'enabled' => false,
169
        ];
170
171
        $serviceProvider  = array_merge($configuration['service_provider'], $spAdditional, $entityId);
172
        $identityProvider = array_merge($idpAdditional, $entityId);
173
174
        $hostedDefinition = new Definition('Surfnet\SamlBundle\Entity\HostedEntities', [
175
            new Reference('router'),
176
            new Reference('request_stack'),
177
            $serviceProvider,
178
            $identityProvider
179
        ]);
180
181
        $hostedDefinition->setPublic(false);
182
183
        return $hostedDefinition;
184
    }
185
186
    /**
187
     * @param string           $provider
188
     * @param array            $configuration
189
     * @param ContainerBuilder $container
190
     */
191
    private function createRemoteDefinition($provider, array $configuration, ContainerBuilder $container)
192
    {
193
        $definition    = new Definition('Surfnet\SamlBundle\Entity\IdentityProvider', [
194
            [
195
                'entityId'        => $configuration['entity_id'],
196
                'ssoUrl'          => $configuration['sso_url'],
197
                'certificateData' => $configuration['certificate'],
198
            ]
199
        ]);
200
201
        $definition->setPublic(false);
202
        $container->setDefinition('gssp.provider.' . $provider . '.remote.idp', $definition);
203
    }
204
205
    /**
206
     * @param string           $provider
207
     * @param array            $configuration
208
     * @param array            $routes
209
     * @param ContainerBuilder $container
210
     * @return Definition
0 ignored issues
show
Documentation introduced by
Should the return type not be Definition|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
211
     */
212
    private function createMetadataDefinition(
213
        $provider,
214
        array $configuration,
215
        array $routes,
216
        ContainerBuilder $container
217
    ) {
218
        $metadataConfiguration = new Definition('Surfnet\SamlBundle\Metadata\MetadataConfiguration');
219
220
        $propertyMap = [
221
            'entityIdRoute'          => $this->createRouteConfig($provider, $routes['metadata']),
222
            'isSp'                   => true,
223
            'assertionConsumerRoute' => $this->createRouteConfig($provider, $routes['consume_assertion']),
224
            'isIdP'                  => false,
225
            'publicKey'              => $configuration['metadata']['public_key'],
226
            'privateKey'             => $configuration['metadata']['private_key'],
227
        ];
228
229
        $metadataConfiguration->setProperties($propertyMap);
230
        $metadataConfiguration->setPublic(false);
231
        $container->setDefinition('gssp.provider.' . $provider . 'metadata.configuration', $metadataConfiguration);
232
233
        $metadataFactory = new Definition('Surfnet\SamlBundle\Metadata\MetadataFactory', [
234
            new Reference('templating'),
235
            new Reference('router'),
236
            new Reference('surfnet_saml.signing_service'),
237
            new Reference('gssp.provider.' . $provider . 'metadata.configuration')
238
        ]);
239
        $container->setDefinition('gssp.provider.' . $provider . '.metadata.factory', $metadataFactory);
240
    }
241
242
    private function createRouteConfig($provider, $routeName)
243
    {
244
        // In the future, we ought to wrap this in an object.
245
        // https://www.pivotaltracker.com/story/show/90095392
246
        return [
247
            'route'      => $routeName,
248
            'parameters' => ['provider' => $provider]
249
        ];
250
    }
251
252
    private function validateDescriptions($descriptions, $appUrl, $provider, $type)
253
    {
254
        $regex ="/%%{$type}_link_start%%[a-zA-Z0-9 ]+%%{$type}_link_end%%/";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $type instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
255
        foreach ($descriptions as $lang => $description) {
256
            if ($appUrl !== false && preg_match($regex, $description) === 0) {
257
                throw new InvalidConfigurationException(
258
                    sprintf(
259
                        'You have configured a GSSP provider with app URL\'s but the description is not ' .
260
                        'configured correctly yet. Missing "%%%1$s_link_start%%" or "%%%1$s_link_end%%" in ' .
261
                        'GSSP description for language "%2$s" in "providers.%3$s.view_config.description" of '.
262
                        'samlstepupproviders.yml',
263
                        $type,
264
                        $lang,
265
                        $provider
266
                    )
267
                );
268
            }
269
        }
270
    }
271
}
272