InstitutionConfigurationController::show()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 44
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 31
nc 2
nop 0
dl 0
loc 44
rs 9.424
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright 2016 SURFnet B.V.
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\StepupMiddleware\ManagementBundle\Controller;
20
21
use DateTime;
22
use Exception;
23
use Psr\Log\LoggerInterface;
24
use Ramsey\Uuid\Uuid;
25
use Surfnet\Stepup\Configuration\Value\Institution;
26
use Surfnet\Stepup\Configuration\Value\InstitutionRole;
27
use Surfnet\Stepup\Helper\JsonHelper;
28
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
29
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionAuthorizationService;
30
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
31
use Surfnet\StepupMiddleware\ApiBundle\Exception\BadCommandRequestException;
32
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\AbstractCommand;
33
use Surfnet\StepupMiddleware\CommandHandlingBundle\Configuration\Command\ReconfigureInstitutionConfigurationOptionsCommand;
34
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\ForbiddenException;
35
use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\TransactionAwarePipeline;
36
use Surfnet\StepupMiddleware\ManagementBundle\Service\DBALConnectionHelper;
37
use Surfnet\StepupMiddleware\ManagementBundle\Validator\Constraints\ValidReconfigureInstitutionsRequest;
38
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
39
use Symfony\Component\HttpFoundation\JsonResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
42
use Symfony\Component\Validator\Validator\ValidatorInterface;
43
44
/**
45
 * @SuppressWarnings("PHPMD.CouplingBetweenObjects")
46
 */
47
final class InstitutionConfigurationController extends AbstractController
48
{
49
    public function __construct(
50
        private readonly InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
51
        private readonly InstitutionAuthorizationService $institutionAuthorizationService,
52
        private readonly ValidatorInterface $validator,
53
        private readonly AllowedSecondFactorListService $allowedSecondFactorListService,
54
        private readonly LoggerInterface $logger,
55
        private readonly TransactionAwarePipeline $pipeline,
56
        private DBALConnectionHelper $connectionHelper,
57
    ) {
58
    }
59
60
    public function show(): JsonResponse
61
    {
62
        $this->denyAccessUnlessGranted('ROLE_MANAGEMENT');
63
64
        $institutionConfigurationOptions = $this->institutionConfigurationOptionsService
65
            ->findAllInstitutionConfigurationOptions();
66
67
        $allowedSecondFactorMap = $this->allowedSecondFactorListService->getAllowedSecondFactorMap();
68
69
        $overview = [];
70
        foreach ($institutionConfigurationOptions as $options) {
71
            // Load the numberOfTokensPerIdentity from the institution config options service
72
            $numberOfTokensPerIdentity = $this->institutionConfigurationOptionsService
73
                ->getMaxNumberOfTokensFor(new Institution($options->institution->getInstitution()));
74
75
            // Get the authorization options for this institution
76
            $institutionConfigurationOptionsMap = $this->institutionAuthorizationService
77
                ->findAuthorizationsFor($options->institution);
78
79
            $overview[$options->institution->getInstitution()] = [
80
                'use_ra_locations' => $options->useRaLocationsOption,
81
                'show_raa_contact_information' => $options->showRaaContactInformationOption,
82
                'verify_email' => $options->verifyEmailOption,
83
                'self_vet' => $options->selfVetOption,
84
                'sso_on_2fa' => $options->ssoOn2faOption,
85
                'sso_registration_bypass' => $options->ssoRegistrationBypassOption,
86
                'allow_self_asserted_tokens' => $options->selfAssertedTokensOption,
87
                'number_of_tokens_per_identity' => $numberOfTokensPerIdentity,
88
                'allowed_second_factors' => $allowedSecondFactorMap->getAllowedSecondFactorListFor(
89
                    $options->institution,
90
                ),
91
                'use_ra' => $institutionConfigurationOptionsMap->getAuthorizationOptionsByRole(
92
                    InstitutionRole::useRa(),
93
                )->jsonSerialize(),
94
                'use_raa' => $institutionConfigurationOptionsMap->getAuthorizationOptionsByRole(
95
                    InstitutionRole::useRaa(),
96
                )->jsonSerialize(),
97
                'select_raa' => $institutionConfigurationOptionsMap->getAuthorizationOptionsByRole(
98
                    InstitutionRole::selectRaa(),
99
                )->jsonSerialize(),
100
            ];
101
        }
102
103
        return new JsonResponse($overview);
104
    }
105
106
    public function reconfigure(Request $request): JsonResponse
107
    {
108
        $this->denyAccessUnlessGranted('ROLE_MANAGEMENT');
109
110
        $configuration = JsonHelper::decode($request->getContent());
111
112
        $violations = $this->validator->validate($configuration, new ValidReconfigureInstitutionsRequest());
113
        if ($violations->count() > 0) {
114
            throw BadCommandRequestException::withViolations('Invalid reconfigure institutions request', $violations);
115
        }
116
117
        if (empty($configuration)) {
118
            $this->logger->notice('No institutions to reconfigure: empty configuration received');
119
120
            return new JsonResponse([
121
                'status' => 'OK',
122
                'processed_by' => $request->server->get('SERVER_NAME') ?: $request->server->get('SERVER_ADDR'),
123
                'applied_at' => (new DateTime())->format(DateTime::ISO8601),
124
            ]);
125
        }
126
127
128
        $commands = [];
129
        foreach ($configuration as $institution => $options) {
130
            assert(is_array($options));
131
            $command = new ReconfigureInstitutionConfigurationOptionsCommand();
132
            $command->UUID = (string)Uuid::uuid4();
133
            $command->institution = $institution;
134
            $command->useRaLocationsOption = $options['use_ra_locations'];
135
            $command->showRaaContactInformationOption = $options['show_raa_contact_information'];
136
            $command->verifyEmailOption = $options['verify_email'];
137
            $command->numberOfTokensPerIdentityOption = $options['number_of_tokens_per_identity'];
138
            $command->allowedSecondFactors = $options['allowed_second_factors'];
139
            // The useRa, useRaa and selectRaa options are optional
140
            $command->useRaOption = $options['use_ra'] ?? null;
141
            $command->useRaaOption = $options['use_raa'] ?? null;
142
            $command->selectRaaOption = $options['select_raa'] ?? null;
143
            // So are sso_on_2fa and the allow_self_asserted_tokens options
144
            $command->selfVetOption = $options['self_vet'] ?? null;
145
            $command->ssoOn2faOption = $options['sso_on_2fa'] ?? null;
146
            $command->ssoRegistrationBypassOption = $options['sso_registration_bypass'] ?? null;
147
            $command->selfAssertedTokensOption = $options['allow_self_asserted_tokens'] ?? null;
148
149
            $commands[] = $command;
150
        }
151
152
        $this->logger->notice(
153
            sprintf('Executing %s reconfigure institution configuration options commands', count($commands)),
154
        );
155
156
        $this->handleCommands($commands);
157
158
        return new JsonResponse([
159
            'status' => 'OK',
160
            'processed_by' => $request->server->get('SERVER_NAME') ?: $request->server->get('SERVER_ADDR'),
161
            'applied_at' => (new DateTime())->format(DateTime::ISO8601),
162
        ]);
163
    }
164
165
    /**
166
     * @param AbstractCommand[] $commands
167
     * @throws Exception
168
     */
169
    private function handleCommands(array $commands): void
170
    {
171
        $connectionHelper = $this->connectionHelper;
172
173
        $connectionHelper->beginTransaction();
174
175
        foreach ($commands as $command) {
176
            try {
177
                $this->pipeline->process($command);
178
            } catch (ForbiddenException $e) {
179
                $connectionHelper->rollBack();
180
181
                throw new AccessDeniedHttpException(
182
                    sprintf('Processing of command "%s" is forbidden for this client', $command),
183
                    $e,
184
                );
185
            } catch (Exception $exception) {
186
                $connectionHelper->rollBack();
187
188
                throw $exception;
189
            }
190
        }
191
192
        $connectionHelper->commit();
193
    }
194
}
195