Completed
Push — master ( 525d8b...576186 )
by
unknown
02:20
created

StepUpAuthenticationService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
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\ServiceProvider;
24
use Surfnet\StepupBundle\Command\SendSmsChallengeCommand as StepupSendSmsChallengeCommand;
25
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneCommand;
26
use Surfnet\StepupBundle\Service\LoaResolutionService;
27
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
28
use Surfnet\StepupBundle\Service\SmsSecondFactor\OtpVerification;
29
use Surfnet\StepupBundle\Service\SmsSecondFactorService;
30
use Surfnet\StepupBundle\Value\Loa;
31
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
32
use Surfnet\StepupBundle\Value\YubikeyOtp;
33
use Surfnet\StepupBundle\Value\YubikeyPublicId;
34
use Surfnet\StepupGateway\ApiBundle\Dto\Otp as ApiOtp;
35
use Surfnet\StepupGateway\ApiBundle\Dto\Requester;
36
use Surfnet\StepupGateway\ApiBundle\Service\YubikeyService;
37
use Surfnet\StepupGateway\GatewayBundle\Command\SendSmsChallengeCommand;
38
use Surfnet\StepupGateway\GatewayBundle\Command\VerifyYubikeyOtpCommand;
39
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor;
40
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactorRepository;
41
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
42
use Surfnet\StepupGateway\GatewayBundle\Service\StepUp\YubikeyOtpVerificationResult;
43
use Symfony\Component\Translation\TranslatorInterface;
44
45
/**
46
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
47
 */
48
class StepUpAuthenticationService
49
{
50
    /**
51
     * @var \Surfnet\StepupBundle\Service\LoaResolutionService
52
     */
53
    private $loaResolutionService;
54
55
    /**
56
     * @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactorRepository
57
     */
58
    private $secondFactorRepository;
59
60
    /**
61
     * @var \Surfnet\StepupGateway\ApiBundle\Service\YubikeyService
62
     */
63
    private $yubikeyService;
64
65
    /**
66
     * @var \Surfnet\StepupBundle\Service\SmsSecondFactorService
67
     */
68
    private $smsService;
69
70
    /** @var InstitutionMatchingHelper */
71
    private $institutionMatchingHelper;
72
73
    /**
74
     * @var \Symfony\Component\Translation\TranslatorInterface
75
     */
76
    private $translator;
77
78
    /**
79
     * @var \Psr\Log\LoggerInterface
80
     */
81
    private $logger;
82
83
    /**
84
     * @var SecondFactorTypeService
85
     */
86
    private $secondFactorTypeService;
87
88
    /**
89
     * @param LoaResolutionService   $loaResolutionService
90
     * @param SecondFactorRepository $secondFactorRepository
91
     * @param YubikeyService         $yubikeyService
92
     * @param SmsSecondFactorService $smsService
93
     * @param InstitutionMatchingHelper $institutionMatchingHelper
94
     * @param TranslatorInterface    $translator
95
     * @param LoggerInterface        $logger
96
     * @param SecondFactorTypeService $secondFactorTypeService
97
     */
98
    public function __construct(
99
        LoaResolutionService $loaResolutionService,
100
        SecondFactorRepository $secondFactorRepository,
101
        YubikeyService $yubikeyService,
102
        SmsSecondFactorService $smsService,
103
        InstitutionMatchingHelper $institutionMatchingHelper,
104
        TranslatorInterface $translator,
105
        LoggerInterface $logger,
106
        SecondFactorTypeService $secondFactorTypeService
107
    ) {
108
        $this->loaResolutionService = $loaResolutionService;
109
        $this->secondFactorRepository = $secondFactorRepository;
110
        $this->yubikeyService = $yubikeyService;
111
        $this->smsService = $smsService;
112
        $this->institutionMatchingHelper = $institutionMatchingHelper;
113
        $this->translator = $translator;
114
        $this->logger = $logger;
115
        $this->secondFactorTypeService = $secondFactorTypeService;
116
    }
117
118
    /**
119
     * @param string          $identityNameId
120
     * @param Loa             $requiredLoa
121
     * @return \Doctrine\Common\Collections\Collection
122
     */
123
    public function determineViableSecondFactors(
124
        $identityNameId,
125
        Loa $requiredLoa
126
    ) {
127
128
        $candidateSecondFactors = $this->secondFactorRepository->getAllMatchingFor(
129
            $requiredLoa,
130
            $identityNameId,
131
            $this->secondFactorTypeService
132
        );
133
        $this->logger->info(
134
            sprintf('Loaded %d matching candidate second factors', count($candidateSecondFactors))
135
        );
136
137
        return $candidateSecondFactors;
138
    }
139
140
    /**
141
     * @param string           $requestedLoa
142
     * @param string           $identityNameId
143
     * @param ServiceProvider  $serviceProvider
144
     * @return null|Loa
145
     *
146
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) see https://www.pivotaltracker.com/story/show/96065350
147
     * @SuppressWarnings(PHPMD.NPathComplexity)      see https://www.pivotaltracker.com/story/show/96065350
148
     */
149
    public function resolveHighestRequiredLoa(
150
        $requestedLoa,
151
        $identityNameId,
152
        ServiceProvider $serviceProvider
153
    ) {
154
        $loaCandidates = new ArrayCollection();
155
156
        if ($requestedLoa) {
157
            $loaCandidates->add($requestedLoa);
158
            $this->logger->info(sprintf('Added requested Loa "%s" as candidate', $requestedLoa));
159
        }
160
161
        $spConfiguredLoas = $serviceProvider->get('configuredLoas');
162
        $loaCandidates->add($spConfiguredLoas['__default__']);
163
        $this->logger->info(sprintf('Added SP\'s default Loa "%s" as candidate', $spConfiguredLoas['__default__']));
164
165
        $institutions = $this->determineInstitutionsByIdentityNameId($identityNameId);
166
        $this->logger->info(sprintf('Loaded institution(s) for "%s"', $identityNameId));
167
168
        $matchingInstitutions = $this->institutionMatchingHelper->findMatches(
169
            array_keys($spConfiguredLoas),
170
            $institutions
171
        );
172
173
        if (count($matchingInstitutions) > 0) {
174
            $this->logger->info('Found matching SP configured LoA\'s');
175
            foreach ($matchingInstitutions as $matchingInstitution) {
176
                $loaCandidates->add($spConfiguredLoas[$matchingInstitution]);
177
                $this->logger->info(sprintf(
178
                    'Added SP\'s Loa "%s" as candidate',
179
                    $spConfiguredLoas[$matchingInstitution]
180
                ));
181
            }
182
        }
183
184
        if (!count($loaCandidates)) {
185
            throw new RuntimeException('No Loa can be found, at least one Loa (SP default) should be found');
186
        }
187
188
        $actualLoas = new ArrayCollection();
189
        foreach ($loaCandidates as $loaDefinition) {
190
            $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...
191
            if ($loa) {
192
                $actualLoas->add($loa);
193
            }
194
        }
195
196
        if (!count($actualLoas)) {
197
            $this->logger->info(sprintf(
198
                'Out of "%d" candidates, no existing Loa could be found, no authentication is possible.',
199
                count($loaCandidates)
200
            ));
201
202
            return null;
203
        }
204
205
        /** @var \Surfnet\StepupBundle\Value\Loa $highestLoa */
206
        $highestLoa = $actualLoas->first();
207
        foreach ($actualLoas as $loa) {
208
            // if the current highest Loa cannot satisfy the next Loa, that must be of a higher level...
209
            if (!$highestLoa->canSatisfyLoa($loa)) {
210
                $highestLoa = $loa;
211
            }
212
        }
213
214
        $this->logger->info(
215
            sprintf('Out of %d candidate Loa\'s, Loa "%s" is the highest', count($loaCandidates), $highestLoa)
216
        );
217
218
        return $highestLoa;
219
    }
220
221
    /**
222
     * Returns whether the given Loa identifier identifies the minimum Loa, intrinsic to being authenticated via an IdP.
223
     *
224
     * @param Loa $loa
225
     * @return bool
226
     */
227
    public function isIntrinsicLoa(Loa $loa)
228
    {
229
        return $loa->levelIsLowerOrEqualTo(Loa::LOA_1);
230
    }
231
232
    /**
233
     * @param VerifyYubikeyOtpCommand $command
234
     * @return YubikeyOtpVerificationResult
235
     */
236
    public function verifyYubikeyOtp(VerifyYubikeyOtpCommand $command)
237
    {
238
        /** @var SecondFactor $secondFactor */
239
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($command->secondFactorId);
240
241
        $requester = new Requester();
242
        $requester->identity = $secondFactor->identityId;
243
        $requester->institution = $secondFactor->institution;
244
245
        $otp = new ApiOtp();
246
        $otp->value = $command->otp;
247
248
        $result = $this->yubikeyService->verify($otp, $requester);
249
250
        if (!$result->isSuccessful()) {
251
            return new YubikeyOtpVerificationResult(YubikeyOtpVerificationResult::RESULT_OTP_VERIFICATION_FAILED, null);
252
        }
253
254
        $otp = YubikeyOtp::fromString($command->otp);
255
        $publicId = YubikeyPublicId::fromOtp($otp);
256
257
        if (!$publicId->equals(new YubikeyPublicId($secondFactor->secondFactorIdentifier))) {
258
            return new YubikeyOtpVerificationResult(
259
                YubikeyOtpVerificationResult::RESULT_PUBLIC_ID_DID_NOT_MATCH,
260
                $publicId
261
            );
262
        }
263
264
        return new YubikeyOtpVerificationResult(YubikeyOtpVerificationResult::RESULT_PUBLIC_ID_MATCHED, $publicId);
265
    }
266
267
    /**
268
     * @param string $secondFactorId
269
     * @return string
270
     */
271
    public function getSecondFactorIdentifier($secondFactorId)
272
    {
273
        /** @var SecondFactor $secondFactor */
274
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($secondFactorId);
275
276
        return $secondFactor->secondFactorIdentifier;
277
    }
278
279
    /**
280
     * @return int
281
     */
282
    public function getSmsOtpRequestsRemainingCount()
283
    {
284
        return $this->smsService->getOtpRequestsRemainingCount();
285
    }
286
287
    /**
288
     * @return int
289
     */
290
    public function getSmsMaximumOtpRequestsCount()
291
    {
292
        return $this->smsService->getMaximumOtpRequestsCount();
293
    }
294
295
    /**
296
     * @param SendSmsChallengeCommand $command
297
     * @return bool
298
     */
299
    public function sendSmsChallenge(SendSmsChallengeCommand $command)
300
    {
301
        /** @var SecondFactor $secondFactor */
302
        $secondFactor = $this->secondFactorRepository->findOneBySecondFactorId($command->secondFactorId);
303
304
        $phoneNumber = InternationalPhoneNumber::fromStringFormat($secondFactor->secondFactorIdentifier);
305
306
        $stepupCommand = new StepupSendSmsChallengeCommand();
307
        $stepupCommand->phoneNumber = $phoneNumber;
308
        $stepupCommand->body = $this->translator->trans('gateway.second_factor.sms.challenge_body');
309
        $stepupCommand->identity = $secondFactor->identityId;
310
        $stepupCommand->institution = $secondFactor->institution;
311
312
        return $this->smsService->sendChallenge($stepupCommand);
313
    }
314
315
    /**
316
     * @param VerifyPossessionOfPhoneCommand $command
317
     * @return OtpVerification
318
     */
319
    public function verifySmsChallenge(VerifyPossessionOfPhoneCommand $command)
320
    {
321
        return $this->smsService->verifyPossession($command);
322
    }
323
324
    public function clearSmsVerificationState()
325
    {
326
        $this->smsService->clearSmsVerificationState();
327
    }
328
329
    private function determineInstitutionsByIdentityNameId($identityNameId)
330
    {
331
        return $this->secondFactorRepository->getAllInstitutions($identityNameId);
332
    }
333
}
334