Completed
Push — bugfix/consider-sp-specific-lo... ( a05dff...32587a )
by
unknown
02:10
created

StepUpAuthenticationService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 1
eloc 17
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\StepupGateway\GatewayBundle\Service;
20
21
use Doctrine\Common\Collections\ArrayCollection;
22
use Psr\Log\LoggerInterface;
23
use Surfnet\SamlBundle\Entity\IdentityProvider;
24
use Surfnet\SamlBundle\Entity\ServiceProvider;
25
use Surfnet\StepupBundle\Command\SendSmsChallengeCommand as StepupSendSmsChallengeCommand;
26
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneCommand;
27
use Surfnet\StepupBundle\Service\LoaResolutionService;
28
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
29
use Surfnet\StepupBundle\Service\SmsSecondFactor\OtpVerification;
30
use Surfnet\StepupBundle\Service\SmsSecondFactorService;
31
use Surfnet\StepupBundle\Value\Loa;
32
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
33
use Surfnet\StepupBundle\Value\YubikeyOtp;
34
use Surfnet\StepupBundle\Value\YubikeyPublicId;
35
use Surfnet\StepupGateway\ApiBundle\Dto\Otp as ApiOtp;
36
use Surfnet\StepupGateway\ApiBundle\Dto\Requester;
37
use Surfnet\StepupGateway\ApiBundle\Service\YubikeyService;
38
use Surfnet\StepupGateway\GatewayBundle\Command\SendSmsChallengeCommand;
39
use Surfnet\StepupGateway\GatewayBundle\Command\VerifyYubikeyOtpCommand;
40
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor;
41
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactorRepository;
42
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
43
use Surfnet\StepupGateway\GatewayBundle\Service\StepUp\YubikeyOtpVerificationResult;
44
use Symfony\Component\Translation\TranslatorInterface;
45
46
/**
47
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
48
 */
49
class StepUpAuthenticationService
50
{
51
    /**
52
     * @var \Surfnet\StepupBundle\Service\LoaResolutionService
53
     */
54
    private $loaResolutionService;
55
56
    /**
57
     * @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactorRepository
58
     */
59
    private $secondFactorRepository;
60
61
    /**
62
     * @var \Surfnet\StepupGateway\ApiBundle\Service\YubikeyService
63
     */
64
    private $yubikeyService;
65
66
    /**
67
     * @var \Surfnet\StepupBundle\Service\SmsSecondFactorService
68
     */
69
    private $smsService;
70
71
    /** @var InstitutionMatchingHelper */
72
    private $institutionMatchingHelper;
73
74
    /**
75
     * @var \Symfony\Component\Translation\TranslatorInterface
76
     */
77
    private $translator;
78
79
    /**
80
     * @var \Psr\Log\LoggerInterface
81
     */
82
    private $logger;
83
84
    /**
85
     * @var SecondFactorTypeService
86
     */
87
    private $secondFactorTypeService;
88
89
    /**
90
     * @param LoaResolutionService   $loaResolutionService
91
     * @param SecondFactorRepository $secondFactorRepository
92
     * @param YubikeyService         $yubikeyService
93
     * @param SmsSecondFactorService $smsService
94
     * @param InstitutionMatchingHelper $institutionMatchingHelper
95
     * @param TranslatorInterface    $translator
96
     * @param LoggerInterface        $logger
97
     * @param SecondFactorTypeService $secondFactorTypeService
98
     */
99
    public function __construct(
100
        LoaResolutionService $loaResolutionService,
101
        SecondFactorRepository $secondFactorRepository,
102
        YubikeyService $yubikeyService,
103
        SmsSecondFactorService $smsService,
104
        InstitutionMatchingHelper $institutionMatchingHelper,
105
        TranslatorInterface $translator,
106
        LoggerInterface $logger,
107
        SecondFactorTypeService $secondFactorTypeService
108
    ) {
109
        $this->loaResolutionService = $loaResolutionService;
110
        $this->secondFactorRepository = $secondFactorRepository;
111
        $this->yubikeyService = $yubikeyService;
112
        $this->smsService = $smsService;
113
        $this->institutionMatchingHelper = $institutionMatchingHelper;
114
        $this->translator = $translator;
115
        $this->logger = $logger;
116
        $this->secondFactorTypeService = $secondFactorTypeService;
117
    }
118
119
    /**
120
     * @param string          $identityNameId
121
     * @param Loa             $requiredLoa
122
     * @return \Doctrine\Common\Collections\ArrayCollection
123
     */
124
    public function determineViableSecondFactors(
125
        $identityNameId,
126
        Loa $requiredLoa
127
    ) {
128
129
        $candidateSecondFactors = $this->secondFactorRepository->getAllMatchingFor(
130
            $requiredLoa,
131
            $identityNameId,
132
            $this->secondFactorTypeService
133
        );
134
        $this->logger->info(
135
            sprintf('Loaded %d matching candidate second factors', count($candidateSecondFactors))
136
        );
137
138
        return $candidateSecondFactors;
139
    }
140
141
    /**
142
     * @param string           $requestedLoa
143
     * @param string           $identityNameId
144
     * @param ServiceProvider  $serviceProvider
145
     * @param IdentityProvider $authenticatingIdp
0 ignored issues
show
Documentation introduced by
Should the type for parameter $authenticatingIdp not be null|IdentityProvider?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
146
     * @return null|Loa
147
     *
148
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) see https://www.pivotaltracker.com/story/show/96065350
149
     * @SuppressWarnings(PHPMD.NPathComplexity)      see https://www.pivotaltracker.com/story/show/96065350
150
     */
151
    public function resolveHighestRequiredLoa(
152
        $requestedLoa,
153
        $identityNameId,
154
        ServiceProvider $serviceProvider,
155
        IdentityProvider $authenticatingIdp = null
0 ignored issues
show
Unused Code introduced by
The parameter $authenticatingIdp is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
156
    ) {
157
        $loaCandidates = new ArrayCollection();
158
159
        if ($requestedLoa) {
160
            $loaCandidates->add($requestedLoa);
161
            $this->logger->info(sprintf('Added requested Loa "%s" as candidate', $requestedLoa));
162
        }
163
164
        $spConfiguredLoas = $serviceProvider->get('configuredLoas');
165
        $loaCandidates->add($spConfiguredLoas['__default__']);
166
        $this->logger->info(sprintf('Added SP\'s default Loa "%s" as candidate', $spConfiguredLoas['__default__']));
167
168
        $institutions = $this->determineInstitutionsByIdentityNameId($identityNameId);
169
        $this->logger->info(sprintf('Loaded institution(s) for "%s"', $identityNameId));
170
171
        $matchingInstitutions = $this->institutionMatchingHelper->findMatches(
172
            array_keys($spConfiguredLoas),
173
            $institutions
174
        );
175
176
        if (count($matchingInstitutions) > 0) {
177
            $this->logger->info(sprintf('Found matching SP configured LoA\'s'));
178
            foreach ($matchingInstitutions as $matchingInstitution) {
179
                $loaCandidates->add($spConfiguredLoas[$matchingInstitution]);
180
                $this->logger->info(sprintf(
181
                    'Added SP\'s Loa "%s" as candidate',
182
                    $spConfiguredLoas[$matchingInstitution]
183
                ));
184
            }
185
        }
186
187
        if (!count($loaCandidates)) {
188
            throw new RuntimeException('No Loa can be found, at least one Loa (SP default) should be found');
189
        }
190
191
        $actualLoas = new ArrayCollection();
192
        foreach ($loaCandidates as $loaDefinition) {
193
            $loa = $this->loaResolutionService->getLoa($loaDefinition);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $loa is correct as $this->loaResolutionServ...>getLoa($loaDefinition) (which targets Surfnet\StepupBundle\Ser...lutionService::getLoa()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
194
            if ($loa) {
195
                $actualLoas->add($loa);
196
            }
197
        }
198
199
        if (!count($actualLoas)) {
200
            $this->logger->info(sprintf(
201
                'Out of "%d" candidates, no existing Loa could be found, no authentication is possible.',
202
                count($loaCandidates)
203
            ));
204
205
            return null;
206
        }
207
208
        /** @var \Surfnet\StepupBundle\Value\Loa $highestLoa */
209
        $highestLoa = $actualLoas->first();
210
        foreach ($actualLoas as $loa) {
211
            // if the current highest Loa cannot satisfy the next Loa, that must be of a higher level...
212
            if (!$highestLoa->canSatisfyLoa($loa)) {
213
                $highestLoa = $loa;
214
            }
215
        }
216
217
        $this->logger->info(
218
            sprintf('Out of %d candidate Loa\'s, Loa "%s" is the highest', count($loaCandidates), $highestLoa)
219
        );
220
221
        return $highestLoa;
222
    }
223
224
    /**
225
     * Returns whether the given Loa identifier identifies the minimum Loa, intrinsic to being authenticated via an IdP.
226
     *
227
     * @param Loa $loa
228
     * @return bool
229
     */
230
    public function isIntrinsicLoa(Loa $loa)
231
    {
232
        return $loa->levelIsLowerOrEqualTo(Loa::LOA_1);
233
    }
234
235
    /**
236
     * @param VerifyYubikeyOtpCommand $command
237
     * @return YubikeyOtpVerificationResult
238
     */
239
    public function verifyYubikeyOtp(VerifyYubikeyOtpCommand $command)
240
    {
241
        /** @var SecondFactor $secondFactor */
242
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($command->secondFactorId);
243
244
        $requester = new Requester();
245
        $requester->identity = $secondFactor->identityId;
246
        $requester->institution = $secondFactor->institution;
247
248
        $otp = new ApiOtp();
249
        $otp->value = $command->otp;
250
251
        $result = $this->yubikeyService->verify($otp, $requester);
252
253
        if (!$result->isSuccessful()) {
254
            return new YubikeyOtpVerificationResult(YubikeyOtpVerificationResult::RESULT_OTP_VERIFICATION_FAILED, null);
255
        }
256
257
        $otp = YubikeyOtp::fromString($command->otp);
258
        $publicId = YubikeyPublicId::fromOtp($otp);
259
260
        if (!$publicId->equals(new YubikeyPublicId($secondFactor->secondFactorIdentifier))) {
261
            return new YubikeyOtpVerificationResult(
262
                YubikeyOtpVerificationResult::RESULT_PUBLIC_ID_DID_NOT_MATCH,
263
                $publicId
264
            );
265
        }
266
267
        return new YubikeyOtpVerificationResult(YubikeyOtpVerificationResult::RESULT_PUBLIC_ID_MATCHED, $publicId);
268
    }
269
270
    /**
271
     * @param string $secondFactorId
272
     * @return string
273
     */
274
    public function getSecondFactorIdentifier($secondFactorId)
275
    {
276
        /** @var SecondFactor $secondFactor */
277
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($secondFactorId);
278
279
        return $secondFactor->secondFactorIdentifier;
280
    }
281
282
    /**
283
     * @return int
284
     */
285
    public function getSmsOtpRequestsRemainingCount()
286
    {
287
        return $this->smsService->getOtpRequestsRemainingCount();
288
    }
289
290
    /**
291
     * @return int
292
     */
293
    public function getSmsMaximumOtpRequestsCount()
294
    {
295
        return $this->smsService->getMaximumOtpRequestsCount();
296
    }
297
298
    /**
299
     * @param SendSmsChallengeCommand $command
300
     * @return bool
301
     */
302
    public function sendSmsChallenge(SendSmsChallengeCommand $command)
303
    {
304
        /** @var SecondFactor $secondFactor */
305
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($command->secondFactorId);
306
307
        $phoneNumber = InternationalPhoneNumber::fromStringFormat($secondFactor->secondFactorIdentifier);
308
309
        $stepupCommand = new StepupSendSmsChallengeCommand();
310
        $stepupCommand->phoneNumber = $phoneNumber;
311
        $stepupCommand->body = $this->translator->trans('gateway.second_factor.sms.challenge_body');
312
        $stepupCommand->identity = $secondFactor->identityId;
313
        $stepupCommand->institution = $secondFactor->institution;
314
315
        return $this->smsService->sendChallenge($stepupCommand);
316
    }
317
318
    /**
319
     * @param VerifyPossessionOfPhoneCommand $command
320
     * @return OtpVerification
321
     */
322
    public function verifySmsChallenge(VerifyPossessionOfPhoneCommand $command)
323
    {
324
        return $this->smsService->verifyPossession($command);
325
    }
326
327
    public function clearSmsVerificationState()
328
    {
329
        $this->smsService->clearSmsVerificationState();
330
    }
331
332
    private function determineInstitutionsByIdentityNameId($identityNameId)
333
    {
334
        return $this->secondFactorRepository->getAllInstitutions($identityNameId);
335
    }
336
}
337