Completed
Pull Request — master (#112)
by David
14:49 queued 04:51
created

Configuration::configureSharedPlugins()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Http\HttplugBundle\DependencyInjection;
4
5
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
6
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
7
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
8
use Symfony\Component\Config\Definition\ConfigurationInterface;
9
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
10
11
/**
12
 * This class contains the configuration information for the bundle.
13
 *
14
 * This information is solely responsible for how the different configuration
15
 * sections are normalized, and merged.
16
 *
17
 * @author David Buchmann <[email protected]>
18
 * @author Tobias Nyholm <[email protected]>
19
 */
20
class Configuration implements ConfigurationInterface
21
{
22
    /**
23
     * Whether to use the debug mode.
24
     *
25
     * @see https://github.com/doctrine/DoctrineBundle/blob/v1.5.2/DependencyInjection/Configuration.php#L31-L41
26
     *
27
     * @var bool
28
     */
29
    private $debug;
30
31
    /**
32
     * @param bool $debug
33
     */
34 14
    public function __construct($debug)
35
    {
36 14
        $this->debug = (bool) $debug;
37 14
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 14
    public function getConfigTreeBuilder()
43
    {
44 14
        $treeBuilder = new TreeBuilder();
45 14
        $rootNode = $treeBuilder->root('httplug');
46
47 14
        $this->configureClients($rootNode);
48 14
        $this->configureSharedPlugins($rootNode);
49
50
        $rootNode
51 14
            ->validate()
52
                ->ifTrue(function ($v) {
53 12
                    return !empty($v['classes']['client'])
54 12
                        || !empty($v['classes']['message_factory'])
55 9
                        || !empty($v['classes']['uri_factory'])
56 12
                        || !empty($v['classes']['stream_factory']);
57 14
                })
58
                ->then(function ($v) {
59 3
                    foreach ($v['classes'] as $key => $class) {
60 3
                        if (null !== $class && !class_exists($class)) {
61 1
                            throw new InvalidConfigurationException(sprintf(
62 1
                                'Class %s specified for httplug.classes.%s does not exist.',
63 1
                                $class,
64
                                $key
65 1
                            ));
66
                        }
67 2
                    }
68
69 2
                    return $v;
70 14
                })
71 14
            ->end()
72 14
            ->beforeNormalization()
73
                ->ifTrue(function ($v) {
74 14
                    return is_array($v) && array_key_exists('toolbar', $v) && is_array($v['toolbar']);
75 14
                })
76
                ->then(function ($v) {
77 4
                    if (array_key_exists('profiling', $v)) {
78 1
                        throw new InvalidConfigurationException('Can\'t configure both "toolbar" and "profiling" section. The "toolbar" config is deprecated as of version 1.3.0, please only use "profiling".');
79
                    }
80
81 3
                    @trigger_error('"httplug.toolbar" config is deprecated since version 1.3 and will be removed in 2.0. Use "httplug.profiling" instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
82
83 3
                    if (array_key_exists('enabled', $v['toolbar']) && 'auto' === $v['toolbar']['enabled']) {
84 1
                        @trigger_error('"auto" value in "httplug.toolbar" config is deprecated since version 1.3 and will be removed in 2.0. Use a boolean value instead.', E_USER_DEPRECATED);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
85 1
                        $v['toolbar']['enabled'] = $this->debug;
86 1
                    }
87
88 3
                    $v['profiling'] = $v['toolbar'];
89
90 3
                    unset($v['toolbar']);
91
92 3
                    return $v;
93 14
                })
94 14
            ->end()
95 14
            ->fixXmlConfig('client')
96 14
            ->children()
97 14
                ->arrayNode('main_alias')
98 14
                    ->addDefaultsIfNotSet()
99 14
                    ->info('Configure which service the main alias point to.')
100 14
                    ->children()
101 14
                        ->scalarNode('client')->defaultValue('httplug.client.default')->end()
102 14
                        ->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
103 14
                        ->scalarNode('uri_factory')->defaultValue('httplug.uri_factory.default')->end()
104 14
                        ->scalarNode('stream_factory')->defaultValue('httplug.stream_factory.default')->end()
105 14
                    ->end()
106 14
                ->end()
107 14
                ->arrayNode('classes')
108 14
                    ->addDefaultsIfNotSet()
109 14
                    ->info('Overwrite a service class instead of using the discovery mechanism.')
110 14
                    ->children()
111 14
                        ->scalarNode('client')->defaultNull()->end()
112 14
                        ->scalarNode('message_factory')->defaultNull()->end()
113 14
                        ->scalarNode('uri_factory')->defaultNull()->end()
114 14
                        ->scalarNode('stream_factory')->defaultNull()->end()
115 14
                    ->end()
116 14
                ->end()
117 14
                ->arrayNode('profiling')
118 14
                    ->addDefaultsIfNotSet()
119 14
                    ->treatFalseLike(['enabled' => false])
120 14
                    ->treatTrueLike(['enabled' => true])
121 14
                    ->treatNullLike(['enabled' => $this->debug])
122 14
                    ->info('Extend the debug profiler with information about requests.')
123 14
                    ->children()
124 14
                        ->booleanNode('enabled')
125 14
                            ->info('Turn the toolbar on or off. Defaults to kernel debug mode.')
126 14
                            ->defaultValue($this->debug)
127 14
                        ->end()
128 14
                        ->scalarNode('formatter')->defaultNull()->end()
129 14
                        ->integerNode('captured_body_length')
130 14
                            ->defaultValue(0)
131 14
                            ->info('Limit long HTTP message bodies to x characters. If set to 0 we do not read the message body. Only available with the default formatter (FullHttpMessageFormatter).')
132 14
                        ->end()
133 14
                    ->end()
134 14
                ->end()
135 14
                ->arrayNode('discovery')
136 14
                    ->addDefaultsIfNotSet()
137 14
                    ->info('Control what clients should be found by the discovery.')
138 14
                    ->children()
139 14
                        ->scalarNode('client')
140 14
                            ->defaultValue('auto')
141 14
                            ->info('Set to "auto" to see auto discovered client in the web profiler. If provided a service id for a client then this client will be found by auto discovery.')
142 14
                        ->end()
143 14
                        ->scalarNode('async_client')
144 14
                            ->defaultNull()
145 14
                            ->info('Set to "auto" to see auto discovered client in the web profiler. If provided a service id for a client then this client will be found by auto discovery.')
146 14
                        ->end()
147 14
                    ->end()
148 14
                ->end()
149 14
            ->end();
150
151 14
        return $treeBuilder;
152
    }
153
154 14
    private function configureClients(ArrayNodeDefinition $root)
155
    {
156 14
        $root->children()
157 14
            ->arrayNode('clients')
158 14
                ->validate()
159
                    ->ifTrue(function ($clients) {
160 5
                        foreach ($clients as $name => $config) {
161
                            // Make sure we only allow one of these to be true
162 5
                            return (bool) $config['flexible_client'] + (bool) $config['http_methods_client'] + (bool) $config['batch_client'] >= 2;
163
                        }
164
165
                        return false;
166 14
                    })
167 14
                    ->thenInvalid('A http client can\'t be decorated with both FlexibleHttpClient and HttpMethodsClient. Only one of the following options can be true. ("flexible_client", "http_methods_client")')->end()
168 14
                ->useAttributeAsKey('name')
169 14
                ->prototype('array')
170 14
                ->fixXmlConfig('plugin')
171 14
                ->children()
172 14
                    ->scalarNode('factory')
173 14
                        ->isRequired()
174 14
                        ->cannotBeEmpty()
175 14
                        ->info('The service id of a factory to use when creating the adapter.')
176 14
                    ->end()
177 14
                    ->booleanNode('flexible_client')
178 14
                        ->defaultFalse()
179 14
                        ->info('Set to true to get the client wrapped in a FlexibleHttpClient which emulates async or sync behavior.')
180 14
                    ->end()
181 14
                    ->booleanNode('http_methods_client')
182 14
                        ->defaultFalse()
183 14
                        ->info('Set to true to get the client wrapped in a HttpMethodsClient which emulates provides functions for HTTP verbs.')
184 14
                    ->end()
185 14
                    ->booleanNode('batch_client')
186 14
                        ->defaultFalse()
187 14
                        ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.')
188 14
                    ->end()
189 14
                    ->variableNode('config')->defaultValue([])->end()
190 14
                    ->append($this->createClientPluginNode())
191 14
                ->end()
192 14
            ->end()
193 14
        ->end();
194 14
    }
195 14
196
    /**
197
     * @param ArrayNodeDefinition $root
198
     */
199
    private function configureSharedPlugins(ArrayNodeDefinition $root)
200 14
    {
201
        $pluginsNode = $root
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method addDefaultsIfNotSet() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
202 14
            ->children()
203 14
                ->arrayNode('plugins')
204 14
                ->info('Global plugin configuration. Plugins need to be explicitly added to clients.')
205 14
                ->addDefaultsIfNotSet()
206 14
            // don't call end to get the plugins node
207
        ;
208 14
        $this->addSharedPluginNodes($pluginsNode);
209 14
    }
210 14
211 14
    /**
212 14
     * Createplugins node of a client.
213 14
     *
214 14
     * @return ArrayNodeDefinition The plugin node
215 14
     */
216 14
    private function createClientPluginNode()
217 14
    {
218 14
        $builder = new TreeBuilder();
219 14
        $node = $builder->root('plugins');
220 14
221 14
        /** @var ArrayNodeDefinition $pluginList */
222 14
        $pluginList = $node
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method prototype() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
223 14
            ->info('A list of plugin service ids and client specific plugin definitions. The order is important.')
224 14
            ->prototype('array')
225 14
        ;
226 14
        $pluginList
227 14
            // support having just a service id in the list
228 14
            ->beforeNormalization()
229 14
                ->always(function ($plugin) {
230 14
                    if (is_string($plugin)) {
231
                        return [
232 14
                            'reference' => [
233 14
                                'enabled' => true,
234 14
                                'id' => $plugin,
235 14
                            ],
236 14
                        ];
237 14
                    }
238 14
239 14
                    return $plugin;
240 14
                })
241 14
            ->end()
242
243 14
            ->validate()
244 14
                ->always(function ($plugins) {
245 14
                    foreach ($plugins as $name => $definition) {
246 14
                        if ('authentication' === $name) {
247 14
                            if (!count($definition)) {
248 14
                                unset($plugins['authentication']);
249 14
                            }
250
                        } elseif (!$definition['enabled']) {
251 14
                            unset($plugins[$name]);
252 14
                        }
253 14
                    }
254 14
255 14
                    return $plugins;
256 14
                })
257 14
            ->end()
258 14
        ;
259 14
        $this->addSharedPluginNodes($pluginList, true);
260 14
261
        $pluginList
262 14
            ->children()
263 14
                ->arrayNode('reference')
264 14
                    ->canBeEnabled()
265 14
                    ->info('Reference to a plugin service')
266 14
                    ->children()
267 14
                        ->scalarNode('id')
268 14
                            ->info('Service id of a plugin')
269 14
                            ->isRequired()
270 14
                            ->cannotBeEmpty()
271 14
                        ->end()
272 14
                    ->end()
273 14
                ->end()
274 14
                ->arrayNode('add_host')
275 14
                    ->canBeEnabled()
276 14
                    ->addDefaultsIfNotSet()
277
                    ->info('Set scheme, host and port in the request URI.')
278 14
                    ->children()
279 14
                        ->scalarNode('host')
280 14
                            ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000')
281 14
                            ->isRequired()
282 14
                            ->cannotBeEmpty()
283 14
                        ->end()
284 14
                        ->scalarNode('replace')
285 14
                            ->info('Whether to replace the host if request already specifies one')
286
                            ->defaultValue(false)
287 14
                        ->end()
288 14
                    ->end()
289 14
                ->end()
290 14
                ->arrayNode('header_append')
291 14
                    ->canBeEnabled()
292 14
                    ->info('Append headers to the request. If the header already exists the value will be appended to the current value.')
293 14
                    ->fixXmlConfig('header')
294
                    ->children()
295 14
                        ->arrayNode('headers')
296 14
                            ->info('Keys are the header names, values the header values')
297 14
                            ->normalizeKeys(false)
298 14
                            ->useAttributeAsKey('name')
299 14
                            ->prototype('scalar')->end()
300 14
                        ->end()
301 14
                    ->end()
302 14
                ->end()
303 14
                ->arrayNode('header_defaults')
304 14
                    ->canBeEnabled()
305 14
                    ->info('Set header to default value if it does not exist.')
306
                    ->fixXmlConfig('header')
307 14
                    ->children()
308 14
                        ->arrayNode('headers')
309 14
                            ->info('Keys are the header names, values the header values')
310 14
                            ->normalizeKeys(false)
311
                            ->useAttributeAsKey('name')
312
                            ->prototype('scalar')->end()
313
                        ->end()
314
                    ->end()
315
                ->end()
316
                ->arrayNode('header_set')
317 14
                    ->canBeEnabled()
318
                    ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.')
319 14
                    ->fixXmlConfig('header')
320 14
                    ->children()
321
                        ->arrayNode('headers')
322 14
                            ->info('Keys are the header names, values the header values')
323 14
                            ->normalizeKeys(false)
324 14
                            ->useAttributeAsKey('name')
325 14
                            ->prototype('scalar')->end()
326 14
                        ->end()
327 2
                    ->end()
328 2
                ->end()
329 1
                ->arrayNode('header_remove')
330 1
                    ->canBeEnabled()
331 2
                    ->info('Remove headers from requests.')
332 1
                    ->fixXmlConfig('header')
333 1
                    ->children()
334 2
                        ->arrayNode('headers')
335 2
                            ->info('List of header names to remove')
336 1
                            ->prototype('scalar')->end()
337 1
                        ->end()
338 1
                    ->end()
339 1
                ->end()
340 1
            ->end()
341
        ->end();
342 1
343 14
        return $node;
344 14
    }
345 14
346 14
    /**
347 14
     * Add the definitions for shared plugin configurations.
348 14
     *
349 14
     * @param ArrayNodeDefinition $pluginNode The node to add to.
350 14
     * @param bool                $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default.
351 14
     */
352 14
    private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false)
353 14
    {
354 14
        $children = $pluginNode->children();
355 14
356 14
        $children->append($this->createAuthenticationPluginNode());
357 14
358
        $children->arrayNode('cache')
359 14
            ->canBeEnabled()
360
            ->addDefaultsIfNotSet()
361
                ->children()
362
                    ->scalarNode('cache_pool')
363
                        ->info('This must be a service id to a service implementing Psr\Cache\CacheItemPoolInterface')
364
                        ->isRequired()
365
                        ->cannotBeEmpty()
366
                    ->end()
367
                    ->scalarNode('stream_factory')
368
                        ->info('This must be a service id to a service implementing Http\Message\StreamFactory')
369
                        ->defaultValue('httplug.stream_factory')
370
                        ->cannotBeEmpty()
371 2
                    ->end()
372
                    ->arrayNode('config')
373 2
                        ->addDefaultsIfNotSet()
374 2
                        ->children()
375 2
                            ->scalarNode('default_ttl')->defaultNull()->end()
376 2
                            ->scalarNode('respect_cache_headers')->defaultTrue()->end()
377
                        ->end()
378 2
                    ->end()
379 1
                ->end()
380
            ->end();
381
        // End cache plugin
382 1
383 1
        $children->arrayNode('cookie')
384 1
            ->canBeEnabled()
385 1
                ->children()
386 1
                    ->scalarNode('cookie_jar')
387 1
                        ->info('This must be a service id to a service implementing Http\Message\CookieJar')
388
                        ->isRequired()
389
                        ->cannotBeEmpty()
390
                    ->end()
391
                ->end()
392
            ->end();
393
        // End cookie plugin
394
395
        $decoder = $children->arrayNode('decoder');
396
        $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled();
397
        $decoder->addDefaultsIfNotSet()
398
            ->children()
399
                ->scalarNode('use_content_encoding')->defaultTrue()->end()
400
            ->end()
401
        ->end();
402
        // End decoder plugin
403
404
        $children->arrayNode('history')
405
            ->canBeEnabled()
406
                ->children()
407
                    ->scalarNode('journal')
408
                        ->info('This must be a service id to a service implementing Http\Client\Plugin\Journal')
409
                        ->isRequired()
410
                        ->cannotBeEmpty()
411
                    ->end()
412
                ->end()
413
            ->end();
414
        // End history plugin
415
416
        $logger = $children->arrayNode('logger');
417
        $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled();
418
        $logger->addDefaultsIfNotSet()
419
            ->children()
420
                ->scalarNode('logger')
421
                    ->info('This must be a service id to a service implementing Psr\Log\LoggerInterface')
422
                    ->defaultValue('logger')
423
                    ->cannotBeEmpty()
424
                ->end()
425
                ->scalarNode('formatter')
426
                    ->info('This must be a service id to a service implementing Http\Message\Formatter')
427
                    ->defaultNull()
428
                ->end()
429
            ->end()
430
        ->end();
431
        // End logger plugin
432
433
        $redirect = $children->arrayNode('redirect');
434
        $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled();
435
        $redirect->addDefaultsIfNotSet()
436
            ->children()
437
                ->scalarNode('preserve_header')->defaultTrue()->end()
438
                ->scalarNode('use_default_for_multiple')->defaultTrue()->end()
439
            ->end()
440
        ->end();
441
        // End redirect plugin
442
443
        $retry = $children->arrayNode('retry');
444
        $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled();
445
        $retry->addDefaultsIfNotSet()
446
            ->children()
447
                ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class
448
            ->end()
449
        ->end();
450
        // End retry plugin
451
452
        $stopwatch = $children->arrayNode('stopwatch');
453
        $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled();
454
        $stopwatch->addDefaultsIfNotSet()
455
            ->children()
456
                ->scalarNode('stopwatch')
457
                    ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch')
458
                    ->defaultValue('debug.stopwatch')
459
                    ->cannotBeEmpty()
460
                ->end()
461
            ->end()
462
        ->end();
463
        // End stopwatch plugin
464
    }
465
466
    /**
467
     * Create configuration for authentication plugin.
468
     *
469
     * @return NodeDefinition Definition for the authentication node in the plugins list.
470
     */
471
    private function createAuthenticationPluginNode()
472
    {
473
        $builder = new TreeBuilder();
474
        $node = $builder->root('authentication');
475
        $node
476
            ->useAttributeAsKey('name')
477
            ->prototype('array')
478
                ->validate()
479
                    ->always()
480
                    ->then(function ($config) {
481
                        switch ($config['type']) {
482
                            case 'basic':
483
                                $this->validateAuthenticationType(['username', 'password'], $config, 'basic');
484
                                break;
485
                            case 'bearer':
486
                                $this->validateAuthenticationType(['token'], $config, 'bearer');
487
                                break;
488
                            case 'service':
489
                                $this->validateAuthenticationType(['service'], $config, 'service');
490
                                break;
491
                            case 'wsse':
492
                                $this->validateAuthenticationType(['username', 'password'], $config, 'wsse');
493
                                break;
494
                        }
495
496
                        return $config;
497
                    })
498
                ->end()
499
                ->children()
500
                    ->enumNode('type')
501
                        ->values(['basic', 'bearer', 'wsse', 'service'])
502
                        ->isRequired()
503
                        ->cannotBeEmpty()
504
                    ->end()
505
                    ->scalarNode('username')->end()
506
                    ->scalarNode('password')->end()
507
                    ->scalarNode('token')->end()
508
                    ->scalarNode('service')->end()
509
                    ->end()
510
                ->end()
511
            ->end(); // End authentication plugin
512
513
        return $node;
514
    }
515
516
    /**
517
     * Validate that the configuration fragment has the specified keys and none other.
518
     *
519
     * @param array  $expected Fields that must exist
520
     * @param array  $actual   Actual configuration hashmap
521
     * @param string $authName Name of authentication method for error messages
522
     *
523
     * @throws InvalidConfigurationException If $actual does not have exactly the keys specified in $expected (plus 'type')
524
     */
525
    private function validateAuthenticationType(array $expected, array $actual, $authName)
526
    {
527
        unset($actual['type']);
528
        $actual = array_keys($actual);
529
        sort($actual);
530
        sort($expected);
531
532
        if ($expected === $actual) {
533
            return;
534
        }
535
536
        throw new InvalidConfigurationException(sprintf(
537
            'Authentication "%s" requires %s but got %s',
538
            $authName,
539
            implode(', ', $expected),
540
            implode(', ', $actual)
541
        ));
542
    }
543
}
544