Completed
Branch master (e88241)
by Antonio
04:44 queued 03:29
created

isGoogleAuthenticatorCompatibilityEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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