Manager   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 7
dl 0
loc 221
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 3
A isGoogleAuthenticatorCompatibilityEnabled() 0 4 1
A disableGoogleAuthenticatorCompatibility() 0 8 1
A enableGoogleAuthenticatorCompatibility() 0 12 2
A getTokenLength() 0 4 1
A setTokenLength() 0 7 1
A generateSecretKey() 0 4 1
A setCycles() 0 7 1
A getCycles() 0 4 1
A setCounter() 0 7 1
A getCounter() 0 4 1
A getTimestamp() 0 4 1
A getCurrentOneTimePassword() 0 7 1
A verify() 0 16 4
A validateOneTimePassword() 0 12 1
1
<?php
2
3
/*
4
 * This file is part of the 2amigos/2fa-library project.
5
 *
6
 * (c) 2amigOS! <http://2amigos.us/>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Da\TwoFA;
13
14
use Da\TwoFA\Contracts\TotpEncoderInterface;
15
use Da\TwoFA\Exception\InvalidSecretKeyException;
16
use Da\TwoFA\Support\Encoder;
17
use Da\TwoFA\Traits\OathTrait;
18
use Da\TwoFA\Traits\SecretValidationTrait;
19
use Da\TwoFA\Validator\OneTimePasswordValidator;
20
use Da\TwoFA\Validator\SecretKeyValidator;
21
22
class Manager
23
{
24
    use SecretValidationTrait;
25
    use OathTrait;
26
27
    /**
28
     * @var int the period parameter that defines the time (in seconds) the OTP token will be valid. Default is 30 for
29
     *          Google Authenticator.
30
     */
31
    protected $counter = 30;
32
    /**
33
     * @var int the times in 30 second cycles to avoid slight out of sync code verification. Setting this to 1 cycle
34
     *          will be valid for 60 seconds.
35
     */
36
    protected $cycles = 1;
37
    /**
38
     * @var TotpEncoderInterface
39
     */
40
    protected $encoder;
41
42
    /**
43
     * Auth constructor.
44
     *
45
     * @param SecretKeyValidator|null   $secretKeyValidator
46
     * @param TotpEncoderInterface|null $encoder
47
     */
48
    public function __construct(SecretKeyValidator $secretKeyValidator = null, TotpEncoderInterface $encoder = null)
49
    {
50
        $this->secretKeyValidator = $secretKeyValidator ?: new SecretKeyValidator();
51
        $this->encoder = $encoder ?: new Encoder();
52
    }
53
54
    /**
55
     * @return bool
56
     */
57
    public function isGoogleAuthenticatorCompatibilityEnabled()
58
    {
59
        return $this->secretKeyValidator->isGoogleAuthenticatorCompatibilityEnforced();
60
    }
61
62
    /**
63
     * @return Manager
64
     */
65
    public function disableGoogleAuthenticatorCompatibility()
66
    {
67
        $cloned = clone $this;
68
        $cloned->secretKeyValidator = new SecretKeyValidator(false);
69
        $cloned->encoder = new Encoder($cloned->secretKeyValidator);
70
71
        return $cloned;
72
    }
73
74
    /**
75
     * @return $this|Manager
76
     */
77
    public function enableGoogleAuthenticatorCompatibility()
78
    {
79
        if (!$this->secretKeyValidator->isGoogleAuthenticatorCompatibilityEnforced()) {
80
            $cloned = clone $this;
81
            $cloned->secretKeyValidator = new SecretKeyValidator();
82
            $cloned->encoder = new Encoder($cloned->secretKeyValidator);
83
84
            return $cloned;
85
        }
86
87
        return $this;
88
    }
89
90
    /**
91
     * @return int
92
     */
93
    public function getTokenLength()
94
    {
95
        return $this->tokenLength;
96
    }
97
98
    /**
99
     * @param int $tokenLength
100
     *
101
     * @return Manager
102
     */
103
    public function setTokenLength($tokenLength)
104
    {
105
        $cloned = clone $this;
106
        $cloned->tokenLength = $tokenLength;
107
108
        return $cloned;
109
    }
110
111
    /**
112
     * Wrapper function to Encoder::generateBase32RandomKey method.
113
     *
114
     * @param int    $length
115
     * @param string $prefix
116
     *
117
     * @return mixed|string
118
     */
119
    public function generateSecretKey($length = 16, $prefix = '')
120
    {
121
        return $this->encoder->generateBase32RandomKey($length, $prefix);
122
    }
123
124
    /**
125
     * @param int $value
126
     *
127
     * @return Manager
128
     */
129
    public function setCycles($value)
130
    {
131
        $cloned = clone $this;
132
        $cloned->cycles = $value;
133
134
        return $cloned;
135
    }
136
137
    /**
138
     * @return int
139
     */
140
    public function getCycles()
141
    {
142
        return (int)$this->cycles;
143
    }
144
145
    /**
146
     * @param $value
147
     *
148
     * @return Manager
149
     */
150
    public function setCounter($value)
151
    {
152
        $cloned = clone $this;
153
        $cloned->counter = (int)$value;
154
155
        return $cloned;
156
    }
157
158
    /**
159
     * @return int
160
     */
161
    public function getCounter()
162
    {
163
        return (int)$this->counter;
164
    }
165
166
    /**
167
     * Returns the current Unix Timestamp divided by the $counter period.
168
     *
169
     * @return int
170
     **/
171
    public function getTimestamp()
172
    {
173
        return (int)floor(microtime(true) / $this->getCounter());
174
    }
175
176
    /**
177
     * Get the current one time password for a base32 encoded secret.
178
     *
179
     * @param string $secret
180
     *
181
     * @return string
182
     */
183
    public function getCurrentOneTimePassword($secret)
184
    {
185
        $timestamp = $this->getTimestamp();
186
        $secret = $this->encoder->fromBase32($secret);
187
188
        return $this->oathHotp($secret, $timestamp);
189
    }
190
191
    /**
192
     * Verifies user's key vs current timestamp.
193
     *
194
     * @param string $key          the user's input key
195
     * @param string $secret       the secret used to
196
     * @param null   $previousTime
197
     * @param null   $time
198
     *
199
     * @throws InvalidSecretKeyException
200
     * @return bool|int
201
     */
202
    public function verify($key, $secret, $previousTime = null, $time = null)
203
    {
204
        $time = $time ? (int)$time : $this->getTimestamp();
205
        $cycles = $this->getCycles();
206
        $startTime = null === $previousTime
207
            ? $time - $cycles
208
            : max($time - $cycles, $previousTime + 1);
209
210
        $seed = $this->encoder->fromBase32($secret);
211
212
        if (strlen($seed) < 8) {
213
            throw new InvalidSecretKeyException('Secret key is too short');
214
        }
215
216
        return $this->validateOneTimePassword($key, $seed, $startTime, $time, $previousTime);
217
    }
218
219
    /**
220
     * Validates the key (OTP) and returns true if valid, false otherwise.
221
     *
222
     * @param string   $key
223
     * @param string   $seed
224
     * @param int      $startTime
225
     * @param int      $time
226
     * @param int|null $previousTime
227
     *
228
     * @return bool
229
     */
230
    protected function validateOneTimePassword($key, $seed, $startTime, $time, $previousTime = null)
231
    {
232
        return (new OneTimePasswordValidator(
233
            $seed,
234
            $this->getCycles(),
235
            $this->getTokenLength(),
236
            $startTime,
237
            $time,
238
            $previousTime
239
        ))
240
            ->validate($key);
241
    }
242
}
243