VettingController::verifyIdentityAction()   C
last analyzed

Complexity

Conditions 10
Paths 16

Size

Total Lines 84

Duplication

Lines 10
Ratio 11.9 %

Importance

Changes 0
Metric Value
dl 10
loc 84
rs 6.4824
c 0
b 0
f 0
cc 10
nc 16
nop 2

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
/**
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\StepupRa\RaBundle\Controller;
20
21
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
22
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
23
use Surfnet\StepupBundle\Value\SecondFactorType;
24
use Surfnet\StepupRa\RaBundle\Command\StartVettingProcedureCommand;
25
use Surfnet\StepupRa\RaBundle\Command\VerifyIdentityCommand;
26
use Surfnet\StepupRa\RaBundle\Exception\DomainException;
27
use Surfnet\StepupRa\RaBundle\Exception\RuntimeException;
28
use Surfnet\StepupRa\RaBundle\Form\Type\StartVettingProcedureType;
29
use Surfnet\StepupRa\RaBundle\Form\Type\VerifyIdentityType;
30
use Surfnet\StepupRa\RaBundle\Security\Authentication\Token\SamlToken;
31
use Surfnet\StepupRa\RaBundle\Service\SecondFactorService;
32
use Surfnet\StepupRa\RaBundle\Service\VettingService;
33
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
34
use Symfony\Component\Form\SubmitButton;
35
use Symfony\Component\HttpFoundation\Request;
36
use Symfony\Component\HttpFoundation\Response;
37
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
38
use Symfony\Component\Translation\TranslatorInterface;
39
40
/**
41
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
42
 * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
43
 */
44
class VettingController extends Controller
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Bundle\Framework...e\Controller\Controller has been deprecated with message: since Symfony 4.2, use "Symfony\Bundle\FrameworkBundle\Controller\AbstractController" instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
45
{
46
    /**
47
     * @Template
48
     * @param Request $request
49
     * @return array|Response
50
     *
51
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) https://www.pivotaltracker.com/story/show/135045063
52
     * @SuppressWarnings(PHPMD.NPathComplexity)      https://www.pivotaltracker.com/story/show/135045063
53
     */
54
    public function startProcedureAction(Request $request)
55
    {
56
        $this->denyAccessUnlessGranted(['ROLE_RA']);
57
        $logger = $this->get('logger');
58
        $identity = $this->getIdentity();
59
60
        $logger->notice('Vetting Procedure Search started');
61
62
        $command = new StartVettingProcedureCommand();
63
64
        $form = $this->createForm(StartVettingProcedureType::class, $command)->handleRequest($request);
65
66 View Code Duplication
        if (!$form->isSubmitted() || !$form->isValid()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
            $logger->notice('No search submitted, displaying search by registration code form');
68
69
            return ['form' => $form->createView()];
70
        }
71
72
        $secondFactor = $this->getSecondFactorService()
73
            ->findVerifiedSecondFactorByRegistrationCode($command->registrationCode, $identity->id);
74
75
        if ($secondFactor === null) {
76
            $this->addFlash('error', 'ra.form.start_vetting_procedure.unknown_registration_code');
77
            $logger->notice('Cannot start new vetting procedure, no second factor found');
78
79
            return ['form' => $form->createView()];
80
        }
81
82
        $enabledSecondFactors = $this->container->getParameter('surfnet_stepup_ra.enabled_second_factors');
83
        if (!in_array($secondFactor->type, $enabledSecondFactors, true)) {
84
            $logger->warning(
85
                sprintf(
86
                    'An RA attempted vetting of disabled second factor "%s" of type "%s"',
87
                    $secondFactor->id,
88
                    $secondFactor->type
89
                )
90
            );
91
92
            return $this
93
                ->render(
94
                    'SurfnetStepupRaRaBundle:vetting:second_factor_type_disabled.html.twig',
95
                    ['secondFactorType' => $secondFactor->type]
96
                )
97
                ->setStatusCode(Response::HTTP_BAD_REQUEST);
98
        }
99
100
        /** @var SamlToken $token */
101
        $token = $this->get('security.token_storage')->getToken();
102
        $command->authorityId = $this->getIdentity()->id;
103
        $command->authorityLoa = $token->getLoa();
104
        $command->secondFactor = $secondFactor;
0 ignored issues
show
Documentation Bug introduced by
It seems like $secondFactor can also be of type false. However, the property $secondFactor is declared as type object<Surfnet\StepupMid...o\VerifiedSecondFactor>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
105
106
        if ($this->getVettingService()->isExpiredRegistrationCode($command)) {
107
            $this->addFlash(
108
                'error',
109
                $this->getTranslator()
110
                    ->trans(
111
                        'ra.verify_identity.registration_code_expired',
112
                        [
113
                            '%self_service_url%' => $this->getParameter('surfnet_stepup_ra.self_service_url'),
114
                        ]
115
                    )
116
            );
117
118
            $logger->notice(
119
                'Second factor registration code is expired',
120
                ['registration_requested_at' => $secondFactor->registrationRequestedAt->format('Y-m-d')]
121
            );
122
123
            return ['form' => $form->createView()];
124
        }
125
126
        if (!$this->getVettingService()->isLoaSufficientToStartProcedure($command)) {
127
            $this->addFlash('error', 'ra.form.start_vetting_procedure.loa_insufficient');
128
129
            $logger->notice('Cannot start new vetting procedure, Authority LoA is insufficient');
130
131
            return ['form' => $form->createView()];
132
        }
133
134
        $procedureId = $this->getVettingService()->startProcedure($command);
135
136
        $this->get('ra.procedure_logger')
137
            ->forProcedure($procedureId)
138
            ->notice(sprintf('Starting new Vetting Procedure for second factor of type "%s"', $secondFactor->type));
139
140
141
        if ($this->getVettingService()->isProvePossessionSkippable($procedureId)) {
142
            $this->get('ra.procedure_logger')
143
                ->forProcedure($procedureId)
144
                ->notice(sprintf('Vetting Procedure for second factor of type "%s" skips the possession proven step', $secondFactor->type));
145
146
            return $this->redirectToRoute('ra_vetting_verify_identity', ['procedureId' => $procedureId]);
147
        }
148
149
        $secondFactorType = new SecondFactorType($secondFactor->type);
150
        if ($secondFactorType->isYubikey()) {
151
            return $this->redirectToRoute('ra_vetting_yubikey_verify', ['procedureId' => $procedureId]);
152
        } elseif ($secondFactorType->isSms()) {
153
            return $this->redirectToRoute('ra_vetting_sms_send_challenge', ['procedureId' => $procedureId]);
154
        } elseif ($this->getSecondFactorTypeService()->isGssf($secondFactorType)) {
155
            return $this->redirectToRoute(
156
                'ra_vetting_gssf_initiate',
157
                [
158
                    'procedureId' => $procedureId,
159
                    'provider'    => $secondFactor->type
160
                ]
161
            );
162
        } elseif ($secondFactorType->isU2f()) {
163
            return $this->redirectToRoute('ra_vetting_u2f_start_authentication', ['procedureId' => $procedureId]);
164
        } else {
165
            throw new RuntimeException(
166
                sprintf('RA does not support vetting procedure for second factor type "%s"', $secondFactor->type)
167
            );
168
        }
169
    }
170
171
    public function cancelProcedureAction($procedureId)
172
    {
173
        $logger = $this->get('ra.procedure_logger')->forProcedure($procedureId);
174
175 View Code Duplication
        if (!$this->getVettingService()->hasProcedure($procedureId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
            $logger->notice(sprintf('Vetting procedure "%s" not found', $procedureId));
177
            throw new NotFoundHttpException(sprintf('Vetting procedure "%s" not found', $procedureId));
178
        }
179
180
        $this->getVettingService()->cancelProcedure($procedureId);
181
        $this->addFlash('info', $this->get('translator')->trans('ra.vetting.flash.cancelled'));
182
183
        return $this->redirectToRoute('ra_vetting_search');
184
    }
185
186
    /**
187
     * @Template
188
     * @param Request $request
189
     * @param string $procedureId
190
     * @return array|Response
191
     *
192
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
193
     * @SuppressWarnings(PHPMD.NPathComplexity)
194
     */
195
    public function verifyIdentityAction(Request $request, $procedureId)
196
    {
197
        $this->denyAccessUnlessGranted(['ROLE_RA']);
198
199
        $logger = $this->get('ra.procedure_logger')->forProcedure($procedureId);
200
        $logger->notice('Verify Identity Form requested');
201
202 View Code Duplication
        if (!$this->getVettingService()->hasProcedure($procedureId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
            $logger->notice(sprintf('Vetting procedure "%s" not found', $procedureId));
204
            throw new NotFoundHttpException(sprintf('Vetting procedure "%s" not found', $procedureId));
205
        }
206
207
        $command = new VerifyIdentityCommand();
208
        $form = $this->createForm(VerifyIdentityType::class, $command)->handleRequest($request);
209
210
        /** @var SubmitButton $cancelButton */
211
        $cancelButton = $form->get('cancel');
212 View Code Duplication
        if ($cancelButton->isClicked()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213
            $this->getVettingService()->cancelProcedure($procedureId);
214
            $this->addFlash('info', $this->get('translator')->trans('ra.vetting.flash.cancelled'));
215
216
            return $this->redirectToRoute('ra_vetting_search');
217
        }
218
219
        $vettingService = $this->getVettingService();
220
        $commonName = $vettingService->getIdentityCommonName($procedureId);
221
222
        $showForm = function ($error = null) use ($form, $commonName) {
223
            if ($error) {
224
                $this->addFlash('error', $error);
225
            }
226
227
            return ['commonName' => $commonName, 'form' => $form->createView()];
228
        };
229
230
        if (!$form->isSubmitted() || !$form->isValid()) {
231
            $logger->notice('Verify Identity Form not submitted, displaying form');
232
233
            return $showForm();
234
        }
235
236
        try {
237
            $vettingService->verifyIdentity($procedureId, $command);
238
        } catch (DomainException $e) {
239
            $this->get('logger')->error(
240
                "RA attempted to verify identity, but the vetting procedure does not allow it",
241
                ['exception' => $e, 'procedure' => $procedureId]
242
            );
243
244
            return $showForm('ra.verify_identity.identity_verification_failed');
245
        }
246
247
        try {
248
            $vetting = $vettingService->vet($procedureId);
249
            if ($vetting->isSuccessful()) {
250
                $logger->notice('Identity Verified, vetting completed');
251
252
                return $this->redirectToRoute('ra_vetting_completed', ['procedureId' => $procedureId]);
253
            }
254
255
            $logger->error('RA attempted to vet second factor, but the command failed');
256
257
            if (in_array(VettingService::REGISTRATION_CODE_EXPIRED_ERROR, $vetting->getErrors())) {
258
                $registrationCodeExpiredError = $this->getTranslator()
259
                    ->trans(
260
                        'ra.verify_identity.registration_code_expired',
261
                        [
262
                            '%self_service_url%' => $this->getParameter('surfnet_stepup_ra.self_service_url'),
263
                        ]
264
                    );
265
266
                return $showForm($registrationCodeExpiredError);
267
            }
268
269
            return $showForm('ra.verify_identity.second_factor_vetting_failed');
270
        } catch (DomainException $e) {
271
            $logger->error(
272
                "RA attempted to vet second factor, but the vetting procedure didn't allow it",
273
                ['exception' => $e]
274
            );
275
276
            return $showForm('ra.verify_identity.second_factor_vetting_failed');
277
        }
278
    }
279
280
    /**
281
     * @Template
282
     */
283
    public function vettingCompletedAction()
284
    {
285
        return [];
286
    }
287
288
    /**
289
     * @return SecondFactorService
290
     */
291
    private function getSecondFactorService()
292
    {
293
        return $this->get('ra.service.second_factor');
294
    }
295
296
    /**
297
     * @return SecondFactorTypeService
298
     */
299
    private function getSecondFactorTypeService()
300
    {
301
        return $this->get('surfnet_stepup.service.second_factor_type');
302
    }
303
304
    /**
305
     * @return VettingService
306
     */
307
    private function getVettingService()
308
    {
309
        return $this->get('ra.service.vetting');
310
    }
311
312
    /**
313
     * @return \Surfnet\StepupMiddlewareClientBundle\Identity\Dto\Identity
314
     */
315
    private function getIdentity()
316
    {
317
        return $this->get('security.token_storage')->getToken()->getUser();
318
    }
319
320
    /**
321
     * @return TranslatorInterface
322
     */
323
    private function getTranslator()
324
    {
325
        return $this->get('translator');
326
    }
327
}
328