Passed
Push — master ( 0e7627...09a47c )
by BENOIT
01:58
created

GenerateJWTCommand::execute()   F

Complexity

Conditions 18
Paths 4609

Size

Total Lines 98
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 63
c 1
b 0
f 0
nc 4609
nop 2
dl 0
loc 98
rs 0.7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BenTools\MercurePHP\Command;
4
5
use BenTools\MercurePHP\Configuration\Configuration;
6
use Lcobucci\JWT\Builder;
7
use Lcobucci\JWT\Signer\Key;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Style\SymfonyStyle;
13
14
use function BenTools\MercurePHP\get_signer;
15
16
final class GenerateJWTCommand extends Command
17
{
18
    private const TARGET_PUBLISHERS = 'publishers';
19
    private const TARGET_SUBSCRIBERS = 'subscribers';
20
    private const TARGET_BOTH = 'both';
21
    private const VALID_TARGETS = [
22
        self::TARGET_PUBLISHERS,
23
        self::TARGET_SUBSCRIBERS,
24
        self::TARGET_BOTH,
25
    ];
26
27
    protected static $defaultName = 'mercure:jwt:generate';
28
29
    protected function execute(InputInterface $input, OutputInterface $output): int
30
    {
31
        $io = new SymfonyStyle($input, $output);
32
        $config = Configuration::bootstrapFromCLI($input)->asArray();
33
34
        $target = $input->getOption('target') ?? self::TARGET_BOTH;
35
        if (!\in_array($target, self::VALID_TARGETS, true)) {
36
            $io->error(\sprintf('Invalid target `%s`.', $target));
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type string[]; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

36
            $io->error(\sprintf('Invalid target `%s`.', /** @scrutinizer ignore-type */ $target));
Loading history...
37
38
            return self::FAILURE;
39
        }
40
41
        $values = [
42
            'publish' => $input->getOption('publish'),
43
            'publish_exclude' => $input->getOption('publish-exclude'),
44
            'subscribe' => $input->getOption('subscribe'),
45
            'subscribe_exclude' => $input->getOption('subscribe-exclude'),
46
        ];
47
48
        $claim = [];
49
50
        if (\in_array($target, [self::TARGET_PUBLISHERS, self::TARGET_BOTH], true)) {
51
            $claim = [
52
                'publish' => $values['publish'],
53
                'publish_exclude' => $values['publish_exclude'],
54
            ];
55
        }
56
57
        if (\in_array($target, [self::TARGET_SUBSCRIBERS, self::TARGET_BOTH], true)) {
58
            $claim = \array_merge(
0 ignored issues
show
Unused Code introduced by
The assignment to $claim is dead and can be removed.
Loading history...
59
                $claim,
60
                [
61
                    'subscribe' => $values['subscribe'],
62
                    'subscribe_exclude' => $values['subscribe_exclude'],
63
                ]
64
            );
65
        }
66
67
        $claim = \array_filter($values, fn(array $claim) => [] !== $claim);
68
        $containsPublishTopics = isset($claim['publish']) || isset($claim['publish_exclude']);
69
        $containsSubscribeTopics = isset($claim['subscribe']) || isset($claim['subscribe_exclude']);
70
        $builder = (new Builder())->withClaim('mercure', $claim);
71
72
        if (null !== $input->getOption('ttl')) {
73
            $builder = $builder->expiresAt(\time() + (int) $input->getOption('ttl'));
74
        }
75
76
        $defaultKey = $config[Configuration::JWT_KEY];
77
        $defaultAlgorithm = $config[Configuration::JWT_ALGORITHM];
78
79
        if (isset($config[Configuration::PUBLISHER_JWT_KEY])) {
80
            $publisherKey = $config[Configuration::PUBLISHER_JWT_KEY];
81
            $publisherAlgorithm = $config[Configuration::PUBLISHER_JWT_ALGORITHM] ?? $config[Configuration::JWT_ALGORITHM];
82
        }
83
84
        if (isset($config[Configuration::SUBSCRIBER_JWT_KEY])) {
85
            $subscriberKey = $config[Configuration::SUBSCRIBER_JWT_KEY];
86
            $subscriberAlgorithm = $config[Configuration::SUBSCRIBER_JWT_ALGORITHM] ?? $config[Configuration::JWT_ALGORITHM];
87
        }
88
89
        if (true === $containsPublishTopics && false === $containsSubscribeTopics) {
90
            $target = self::TARGET_PUBLISHERS;
91
        } elseif (false === $containsPublishTopics && true === $containsSubscribeTopics) {
92
            $target = self::TARGET_SUBSCRIBERS;
93
        }
94
95
        switch ($target) {
96
            case self::TARGET_PUBLISHERS:
97
                $key = $publisherKey ?? $defaultKey;
98
                $algorithm = $publisherAlgorithm ?? $defaultAlgorithm;
99
                break;
100
            case self::TARGET_SUBSCRIBERS:
101
                $key = $subscriberKey ?? $defaultKey;
102
                $algorithm = $subscriberAlgorithm ?? $defaultAlgorithm;
103
                break;
104
            case self::TARGET_BOTH:
105
            default:
106
                $key = $defaultKey;
107
                $algorithm = $defaultAlgorithm;
108
        }
109
110
        try {
111
            $token = $builder->getToken(
112
                get_signer($algorithm),
113
                new Key($key),
114
            );
115
        } catch (\Exception $e) {
116
            $io->error('Unable to sign your token.');
117
118
            return self::FAILURE;
119
        }
120
121
        if (false === $input->getOption('raw')) {
122
            $io->success('Here is your token! ⤵️');
123
        }
124
        $output->writeln((string) $token);
125
126
        return self::SUCCESS;
127
    }
128
129
    protected function interact(InputInterface $input, OutputInterface $output): void
130
    {
131
        $io = new SymfonyStyle($input, $output);
132
        $config = Configuration::bootstrapFromCLI($input)->asArray();
133
        $publishersOnly = !empty($config[Configuration::PUBLISHER_JWT_KEY]);
134
        $subscribersOnly = !empty($config[Configuration::SUBSCRIBER_JWT_KEY]);
135
        $forBothTargets = false === $publishersOnly && false === $subscribersOnly;
136
137
        if (!$forBothTargets && empty($input->getOption('target'))) {
138
            $value = $io->choice(
139
                'Do you want to generate a JWT for publishers or subscribers?',
140
                [
141
                    self::TARGET_PUBLISHERS,
142
                    self::TARGET_SUBSCRIBERS,
143
                ]
144
            );
145
146
            $input->setOption('target', $value);
147
        }
148
149
        if ($forBothTargets || self::TARGET_PUBLISHERS === $input->getOption('target')) {
150
            $values = (array) $input->getOption('publish');
151
            if (empty($values)) {
152
                ASK_PUBLISH:
153
                $value = $io->ask(
154
                    'Add a topic selector for the `publish` key (or just hit ENTER when you\'re done)'
155
                );
156
                if (null !== $value) {
157
                    $values[] = $value;
158
                    goto ASK_PUBLISH;
159
                }
160
                $input->setOption('publish', $values);
161
            }
162
163
            $values = (array) $input->getOption('publish-exclude');
164
            if (empty($values)) {
165
                ASK_PUBLISH_EXCLUDE:
166
                $value = $io->ask(
167
                    'Add a topic selector for the `publish-exclude` key (or just hit ENTER when you\'re done)'
168
                );
169
                if (null !== $value) {
170
                    $values[] = $value;
171
                    goto ASK_PUBLISH_EXCLUDE;
172
                }
173
                $input->setOption('publish-exclude', $values);
174
            }
175
        }
176
177
        if ($forBothTargets || self::TARGET_SUBSCRIBERS === $input->getOption('target')) {
178
            $values = (array) $input->getOption('subscribe');
179
            if (empty($values)) {
180
                ASK_SUBSCRIBE:
181
                $value = $io->ask(
182
                    'Add a topic selector for the `subscribe` key (or just hit ENTER when you\'re done)'
183
                );
184
                if (null !== $value) {
185
                    $values[] = $value;
186
                    goto ASK_SUBSCRIBE;
187
                }
188
                $input->setOption('subscribe', $values);
189
            }
190
191
            $values = (array) $input->getOption('subscribe-exclude');
192
            if (empty($values)) {
193
                ASK_SUBSCRIBE_EXCLUDE:
194
                $value = $io->ask(
195
                    'Add a topic selector for the `subscribe-exclude` key (or just hit ENTER when you\'re done)'
196
                );
197
                if (null !== $value) {
198
                    $values[] = $value;
199
                    goto ASK_SUBSCRIBE_EXCLUDE;
200
                }
201
                $input->setOption('subscribe-exclude', $values);
202
            }
203
        }
204
205
        if (null === $input->getOption('ttl')) {
206
            $value = $io->ask(
207
                'TTL of this token in seconds (or hit ENTER for no expiration):',
208
                null,
209
                function ($value) {
210
                    if (null === $value) {
211
                        return null;
212
                    }
213
                    if (!\is_numeric($value) || $value <= 0) {
214
                        throw new \RuntimeException('Invalid number.');
215
                    }
216
217
                    return $value;
218
                }
219
            );
220
221
            $input->setOption('ttl', $value);
222
        }
223
    }
224
225
    protected function configure(): void
226
    {
227
        $this->setDescription('Generates a JWT key to use on this hub.');
228
        $this
229
            ->addOption(
230
                'publish',
231
                null,
232
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
233
                'Allowed topic selectors for publishing.',
234
                []
235
            )
236
            ->addOption(
237
                'publish-exclude',
238
                null,
239
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
240
                'Denied topic selectors for publishing.',
241
                []
242
            )
243
            ->addOption(
244
                'subscribe',
245
                null,
246
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
247
                'Allowed topic selectors for subscribing.',
248
                []
249
            )
250
            ->addOption(
251
                'subscribe-exclude',
252
                null,
253
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
254
                'Denied topic selectors for subscribing.',
255
                []
256
            )
257
            ->addOption(
258
                'target',
259
                null,
260
                InputOption::VALUE_OPTIONAL
261
            )
262
            ->addOption(
263
                'ttl',
264
                null,
265
                InputOption::VALUE_OPTIONAL,
266
                'TTL of this token, in seconds.'
267
            )
268
            ->addOption(
269
                'raw',
270
                null,
271
                InputOption::VALUE_NONE,
272
                'Enable raw output'
273
            )
274
            ->addOption(
275
                'jwt-key',
276
                null,
277
                InputOption::VALUE_OPTIONAL,
278
                'The JWT key to use for both publishers and subscribers',
279
            )
280
            ->addOption(
281
                'jwt-algorithm',
282
                null,
283
                InputOption::VALUE_OPTIONAL,
284
                'The JWT verification algorithm to use for both publishers and subscribers, e.g. HS256 (default) or RS512.',
285
            )
286
            ->addOption(
287
                'publisher-jwt-key',
288
                null,
289
                InputOption::VALUE_OPTIONAL,
290
                'Must contain the secret key to valid publishers\' JWT, can be omitted if jwt_key is set.',
291
            )
292
            ->addOption(
293
                'publisher-jwt-algorithm',
294
                null,
295
                InputOption::VALUE_OPTIONAL,
296
                'The JWT verification algorithm to use for publishers, e.g. HS256 (default) or RS512.',
297
            )
298
            ->addOption(
299
                'subscriber-jwt-key',
300
                null,
301
                InputOption::VALUE_OPTIONAL,
302
                'Must contain the secret key to valid subscribers\' JWT, can be omitted if jwt_key is set.',
303
            )
304
            ->addOption(
305
                'subscriber-jwt-algorithm',
306
                null,
307
                InputOption::VALUE_OPTIONAL,
308
                'The JWT verification algorithm to use for subscribers, e.g. HS256 (default) or RS512.',
309
            );
310
    }
311
}
312