Completed
Pull Request — 0.3.x (#4)
by Alexandru-Daniel
05:11 queued 53s
created

authenticate()   C

Complexity

Conditions 11
Paths 56

Size

Total Lines 38
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 3
Bugs 2 Features 1
Metric Value
c 3
b 2
f 1
dl 0
loc 38
ccs 0
cts 31
cp 0
rs 5.2653
cc 11
eloc 22
nc 56
nop 1
crap 132

How to fix   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 Krtv\Bundle\SingleSignOnServiceProviderBundle\Authentication\Provider;
4
5
use Krtv\Bundle\SingleSignOnServiceProviderBundle\Authentication\Token\OneTimePasswordToken;
6
use Krtv\SingleSignOn\Model\OneTimePassword;
7
use Krtv\SingleSignOn\Encoder\OneTimePasswordEncoder;
8
use Krtv\SingleSignOn\Manager\OneTimePasswordManagerInterface;
9
use Krtv\SingleSignOn\Model\OneTimePasswordInterface;
10
use Psr\Log\LoggerInterface;
11
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
12
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
13
use Symfony\Component\Security\Core\User\UserCheckerInterface;
14
use Symfony\Component\Security\Core\User\UserInterface;
15
use Symfony\Component\Security\Core\User\UserProviderInterface;
16
use Symfony\Component\Security\Core\Exception\AuthenticationException;
17
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
18
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
20
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
21
22
/**
23
 * Class OneTimePasswordAuthenticationProvider
24
 * @package Krtv\Bundle\SingleSignOnServiceProviderBundle\Authentication\Provider
25
 */
26
class OneTimePasswordAuthenticationProvider implements AuthenticationProviderInterface
27
{
28
    /**
29
     * @var UserProviderInterface
30
     */
31
    private $userProvider;
32
33
    /**
34
     * @var UserCheckerInterface
35
     */
36
    private $userChecker;
37
38
    /**
39
     * @var OneTimePasswordEncoder
40
     */
41
    private $otpEncoder;
42
43
    /**
44
     * @var OneTimePasswordManagerInterface
45
     */
46
    private $otpManager;
47
48
    /**
49
     * @var string
50
     */
51
    private $providerKey;
52
53
    /**
54
     * @var LoggerInterface
55
     */
56
    private $logger;
57
58
    /**
59
     * @param UserProviderInterface $userProvider
60
     * @param UserCheckerInterface $userChecker
61
     * @param OneTimePasswordManagerInterface $otpManager
62
     * @param OneTimePasswordEncoder $otpEncoder
63
     * @param string $providerKey
64
     * @param LoggerInterface $logger
65
     */
66
    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, OneTimePasswordManagerInterface $otpManager, OneTimePasswordEncoder $otpEncoder, $providerKey, LoggerInterface $logger = null)
67
    {
68
        $this->userProvider = $userProvider;
69
        $this->userChecker = $userChecker;
70
        $this->otpManager = $otpManager;
71
        $this->otpEncoder = $otpEncoder;
72
        $this->providerKey = $providerKey;
73
        $this->logger = $logger;
74
    }
75
76
    /**
77
     * @return UserProviderInterface
78
     */
79
    public function getUserProvider()
80
    {
81
        return $this->userProvider;
82
    }
83
84
    /**
85
     * @param TokenInterface $token
86
     * @return PreAuthenticatedToken
87
     */
88
    public function authenticate(TokenInterface $token)
89
    {
90
        try {
91
            $otp = $this->otpManager->get($token->getCredentials());
92
93
            if (!$otp || !$this->otpManager->isValid($otp)) {
94
                throw new AuthenticationException('OTP is not valid.');
95
            }
96
97
            $user = $this->authenticateOneTimePassword($otp);
98
99
            $this->otpManager->invalidate($otp);
100
101
            if (!$user instanceof UserInterface) {
102
                throw new \RuntimeException('OneTimePassword authenticator did not return a UserInterface implementation.');
103
            }
104
105
            if (null !== $this->logger) {
106
                $this->logger->info('OTP accepted.');
107
            }
108
109
            return new PreAuthenticatedToken($user, $user->getPassword(), $this->providerKey, $user->getRoles());
110
        } catch (UsernameNotFoundException $notFound) {
111
            if (null !== $this->logger) {
112
                $this->logger->info('User for OneTimePassword not found.');
113
            }
114
        } catch (UnsupportedUserException $unSupported) {
115
            if (null !== $this->logger) {
116
                $this->logger->warning('User class for OneTimePassword not supported.');
117
            }
118
        } catch (AuthenticationException $invalid) {
119
            if (null !== $this->logger) {
120
                $this->logger->debug('OneTimePassword authentication failed: '.$invalid->getMessage());
121
            }
122
        }
123
124
        throw new AuthenticationException('OneTimePassword authentication failed.');
125
    }
126
127
    /**
128
     * @param OneTimePasswordInterface $otp
129
     * @return UserInterface
130
     */
131
    public function authenticateOneTimePassword(OneTimePasswordInterface $otp)
132
    {
133
        $parts = $this->otpEncoder->decodeHash($otp->getHash());
134
135
        if (count($parts) !== 3) {
136
            throw new AuthenticationException('The hash is invalid.');
137
        }
138
139
        list($username, $expires, $hash) = $parts;
140
        if (false === $username = base64_decode($username, true)) {
141
            throw new AuthenticationException('$username contains a character from outside the base64 alphabet.');
142
        }
143
144
        try {
145
            $user = $this->getUserProvider()->loadUserByUsername($username);
146
147
            if (!$user instanceof UserInterface) {
148
                throw new AuthenticationServiceException('loadUserByUsername() must return a UserInterface.');
149
            }
150
151
            $this->userChecker->checkPreAuth($user);
152
153
            if (true !== $this->otpEncoder->compareHashes($hash, $this->otpEncoder->generateHash($username, $expires))) {
154
                throw new AuthenticationException('The hash is invalid.');
155
            }
156
157
            if ($expires < microtime(true)) {
158
                throw new AuthenticationException('The hash has expired.');
159
            }
160
161
            $this->userChecker->checkPostAuth($user);
162
163
            return $user;
164
        } catch (\Exception $ex) {
165
            if (!$ex instanceof AuthenticationException) {
166
                $ex = new AuthenticationException($ex->getMessage(), $ex->getCode(), $ex);
167
            }
168
169
            throw $ex;
170
        }
171
    }
172
173
    /**
174
     * @param TokenInterface $token
175
     * @return bool
176
     */
177
    public function supports(TokenInterface $token)
178
    {
179
        return $token instanceof OneTimePasswordToken;
180
    }
181
}