Completed
Push — feature/gssp_app_urls ( 06701c )
by
unknown
02:08
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
    /**
32
     * {@inheritdoc}
33
     */
34
    public function load(array $configs, ContainerBuilder $container)
35
    {
36
        $configuration = new Configuration();
37
        $config = $this->processConfiguration($configuration, $configs);
38
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
39
        $loader->load('services.yml');
40
41
        foreach ($config['providers'] as $provider => $providerConfiguration) {
42
            // may seem a bit strange, but this prevents casing issue when getting/setting/creating provider
43
            // service definitions etc.
44
            if ($provider !== strtolower($provider)) {
45
                throw new InvalidConfigurationException('The provider name must be completely lowercase');
46
            }
47
48
            $this->loadProviderConfiguration($provider, $providerConfiguration, $config['routes'], $container);
49
        }
50
    }
51
52
    private function loadProviderConfiguration(
53
        $provider,
54
        array $configuration,
55
        array $routes,
56
        ContainerBuilder $container
57
    ) {
58
59
        if ($container->has('gssp.provider.' . $provider)) {
60
            throw new InvalidConfigurationException(sprintf('Cannot create the same provider "%s" twice', $provider));
61
        }
62
63
        $this->createHostedDefinitions($provider, $configuration['hosted'], $routes, $container);
64
        $this->createMetadataDefinition($provider, $configuration['hosted'], $routes, $container);
65
        $this->createRemoteDefinition($provider, $configuration['remote'], $container);
66
67
        $stateHandlerDefinition = new Definition(
68
            'Surfnet\StepupSelfService\SamlStepupProviderBundle\Saml\StateHandler',
69
            [
70
                new Reference('gssp.sessionbag'),
71
                $provider
72
            ]
73
        );
74
        $container->setDefinition('gssp.provider.' . $provider . '.statehandler', $stateHandlerDefinition);
75
76
        $providerDefinition = new Definition('Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\Provider', [
77
            $provider,
78
            new Reference('gssp.provider.' . $provider . '.hosted.sp'),
79
            new Reference('gssp.provider.' . $provider . '.remote.idp'),
80
            new Reference('gssp.provider.' . $provider . '.statehandler')
81
        ]);
82
83
        $providerDefinition->setPublic(false);
84
        $container->setDefinition('gssp.provider.' . $provider, $providerDefinition);
85
86
        // When the android url is set, the description should contain the android play store url parameter.
87
        // The same goes for the iOs app url.
88
        $this->validateDescriptions(
89
            $configuration['view_config']['description'],
90
            $configuration['view_config']['app_android_url'],
91
            $provider,
92
            'android'
93
        );
94
95
        $this->validateDescriptions(
96
            $configuration['view_config']['description'],
97
            $configuration['view_config']['app_ios_url'],
98
            $provider,
99
            'ios'
100
        );
101
102
        $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...
103
            new Reference('request'),
104
            $configuration['view_config']['loa'],
105
            $configuration['view_config']['logo'],
106
            $configuration['view_config']['app_android_url'],
107
            $configuration['view_config']['app_ios_url'],
108
            $configuration['view_config']['alt'],
109
            $configuration['view_config']['title'],
110
            $configuration['view_config']['description'],
111
            $configuration['view_config']['button_use'],
112
            $configuration['view_config']['initiate_title'],
113
            $configuration['view_config']['initiate_button'],
114
            $configuration['view_config']['explanation'],
115
            $configuration['view_config']['authn_failed'],
116
            $configuration['view_config']['pop_failed'],
117
        ]);
118
        $viewConfigDefinition->setScope('request');
119
120
        $container->setDefinition('gssp.view_config.' . $provider, $viewConfigDefinition);
121
122
        $container
123
            ->getDefinition('gssp.provider_repository')
124
            ->addMethodCall('addProvider', [new Reference('gssp.provider.' . $provider)]);
125
    }
126
127
    /**
128
     * @param string           $provider
129
     * @param array            $configuration
130
     * @param array            $routes
131
     * @param ContainerBuilder $container
132
     */
133
    private function createHostedDefinitions(
134
        $provider,
135
        array $configuration,
136
        array $routes,
137
        ContainerBuilder $container
138
    ) {
139
        $hostedDefinition = $this->buildHostedEntityDefinition($provider, $configuration, $routes);
140
        $container->setDefinition('gssp.provider.' . $provider . '.hosted_entities', $hostedDefinition);
141
142
        $hostedSpDefinition  = (new Definition())
143
            ->setClass('Surfnet\SamlBundle\Entity\ServiceProvider')
144
            ->setFactory([
145
                new Reference('gssp.provider.' . $provider . '.hosted_entities'),
146
                'getServiceProvider'
147
            ])
148
            ->setPublic(false);
149
        $container->setDefinition('gssp.provider.' . $provider . '.hosted.sp', $hostedSpDefinition);
150
    }
151
152
    /**
153
     * @param string $provider
154
     * @param array  $configuration
155
     * @param array  $routes
156
     * @return Definition
157
     */
158
    private function buildHostedEntityDefinition($provider, array $configuration, array $routes)
159
    {
160
        $entityId = ['entity_id_route' => $this->createRouteConfig($provider, $routes['metadata'])];
161
        $spAdditional = [
162
            'enabled' => true,
163
            'assertion_consumer_route' => $this->createRouteConfig($provider, $routes['consume_assertion'])
164
        ];
165
        $idpAdditional = [
166
            'enabled' => false,
167
        ];
168
169
        $serviceProvider  = array_merge($configuration['service_provider'], $spAdditional, $entityId);
170
        $identityProvider = array_merge($idpAdditional, $entityId);
171
172
        $hostedDefinition = new Definition('Surfnet\SamlBundle\Entity\HostedEntities', [
173
            new Reference('router'),
174
            new Reference('request_stack'),
175
            $serviceProvider,
176
            $identityProvider
177
        ]);
178
179
        $hostedDefinition->setPublic(false);
180
181
        return $hostedDefinition;
182
    }
183
184
    /**
185
     * @param string           $provider
186
     * @param array            $configuration
187
     * @param ContainerBuilder $container
188
     */
189
    private function createRemoteDefinition($provider, array $configuration, ContainerBuilder $container)
190
    {
191
        $definition    = new Definition('Surfnet\SamlBundle\Entity\IdentityProvider', [
192
            [
193
                'entityId'        => $configuration['entity_id'],
194
                'ssoUrl'          => $configuration['sso_url'],
195
                'certificateData' => $configuration['certificate'],
196
            ]
197
        ]);
198
199
        $definition->setPublic(false);
200
        $container->setDefinition('gssp.provider.' . $provider . '.remote.idp', $definition);
201
    }
202
203
    /**
204
     * @param string           $provider
205
     * @param array            $configuration
206
     * @param array            $routes
207
     * @param ContainerBuilder $container
208
     * @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...
209
     */
210
    private function createMetadataDefinition(
211
        $provider,
212
        array $configuration,
213
        array $routes,
214
        ContainerBuilder $container
215
    ) {
216
        $metadataConfiguration = new Definition('Surfnet\SamlBundle\Metadata\MetadataConfiguration');
217
218
        $propertyMap = [
219
            'entityIdRoute'          => $this->createRouteConfig($provider, $routes['metadata']),
220
            'isSp'                   => true,
221
            'assertionConsumerRoute' => $this->createRouteConfig($provider, $routes['consume_assertion']),
222
            'isIdP'                  => false,
223
            'publicKey'              => $configuration['metadata']['public_key'],
224
            'privateKey'             => $configuration['metadata']['private_key'],
225
        ];
226
227
        $metadataConfiguration->setProperties($propertyMap);
228
        $metadataConfiguration->setPublic(false);
229
        $container->setDefinition('gssp.provider.' . $provider . 'metadata.configuration', $metadataConfiguration);
230
231
        $metadataFactory = new Definition('Surfnet\SamlBundle\Metadata\MetadataFactory', [
232
            new Reference('templating'),
233
            new Reference('router'),
234
            new Reference('surfnet_saml.signing_service'),
235
            new Reference('gssp.provider.' . $provider . 'metadata.configuration')
236
        ]);
237
        $container->setDefinition('gssp.provider.' . $provider . '.metadata.factory', $metadataFactory);
238
    }
239
240
    private function createRouteConfig($provider, $routeName)
241
    {
242
        // In the future, we ought to wrap this in an object.
243
        // https://www.pivotaltracker.com/story/show/90095392
244
        return [
245
            'route'      => $routeName,
246
            'parameters' => ['provider' => $provider]
247
        ];
248
    }
249
250
    private function validateDescriptions($descriptions, $appUrl, $provider, $type)
251
    {
252
        $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...
253
        foreach ($descriptions as $lang => $description) {
254
            if ($appUrl !== false && preg_match($regex, $description) === 0) {
255
                throw new InvalidConfigurationException(
256
                    sprintf(
257
                        'You have configured a GSSP provider with app URL\'s but the description is not ' .
258
                        'configured correctly yet. Missing "%%%1$s_link_start%%" or "%%%1$s_link_end%%" in ' .
259
                        'GSSP description for language "%2$s" in "providers.%3$s.view_config.description" of '.
260
                        'samlstepupproviders.yml',
261
                        $type,
262
                        $lang,
263
                        $provider
264
                    )
265
                );
266
            }
267
        }
268
    }
269
}
270