Completed
Push — master ( 3e34f4...b50cde )
by Antonio
22:40 queued 07:41
created

src/Manager.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 SecretKeyValidator
38
     */
39
    protected $secretKeyValidator;
40
    /**
41
     * @var Encoder
42
     */
43
    protected $encoder;
44
45
    /**
46
     * Auth constructor.
47
     *
48
     * @param SecretKeyValidator|null $secretKeyValidator
49
     * @param TotpEncoderInterface|null $encoder
50
     */
51
    public function __construct(SecretKeyValidator $secretKeyValidator = null, TotpEncoderInterface $encoder = null)
52
    {
53
        $this->secretKeyValidator = $secretKeyValidator ?: new SecretKeyValidator();
54
        $this->encoder = $encoder ?: new Encoder();
0 ignored issues
show
Documentation Bug introduced by
$encoder ?: new \Da\TwoFA\Encoder() is of type object<Da\TwoFA\Contracts\TotpEncoderInterface>, but the property $encoder was declared to be of type object<Da\TwoFA\Encoder>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
55
    }
56
57
    /**
58
     * @return Manager
59
     */
60
    public function disableGoogleAuthenticatorCompatibility()
61
    {
62
        $cloned = clone $this;
63
        $cloned->secretKeyValidator = new SecretKeyValidator(false);
64
        $cloned->encoder = new Encoder($cloned->secretKeyValidator);
65
66
        return $cloned;
67
    }
68
69
    /**
70
     * @return $this|Manager
71
     */
72
    public function enableGoogleAuthenticatorCompatibility()
73
    {
74
        if (!$this->secretKeyValidator->isGoogleAuthenticatorCompatibilityEnforced()) {
75
            $cloned = clone $this;
76
            $cloned->secretKeyValidator = new SecretKeyValidator();
77
            $cloned->encoder = new Encoder($cloned->secretKeyValidator);
78
79
            return $cloned;
80
        }
81
82
        return $this;
83
    }
84
85
    /**
86
     * @return int
87
     */
88
    public function getTokenLength()
89
    {
90
        return $this->tokenLength;
91
    }
92
93
    /**
94
     * @param int $tokenLength
95
     *
96
     * @return Manager
97
     */
98
    public function setTokenLength($tokenLength)
99
    {
100
        $cloned = clone $this;
101
        $cloned->tokenLength = $tokenLength;
102
103
        return $cloned;
104
    }
105
106
    /**
107
     * Wrapper function to Encoder::generateBase32RandomKey method.
108
     *
109
     * @param int $length
110
     * @param string $prefix
111
     *
112
     * @return mixed|string
113
     */
114
    public function generateSecretKey($length = 16, $prefix = '')
115
    {
116
        return $this->encoder->generateBase32RandomKey($length, $prefix);
117
    }
118
119
    /**
120
     * @param int $value
121
     *
122
     * @return Manager
123
     */
124
    public function setCycles($value)
125
    {
126
        $cloned = clone $this;
127
        $cloned->cycles = $value;
128
129
        return $cloned;
130
    }
131
132
    /**
133
     * @return int
134
     */
135
    public function getCycles()
136
    {
137
        return (int)$this->cycles;
138
    }
139
140
    /**
141
     * @param $value
142
     *
143
     * @return Manager
144
     */
145
    public function setCounter($value)
146
    {
147
        $cloned = clone $this;
148
        $cloned->counter = (int)$value;
149
150
        return $cloned;
151
    }
152
153
    /**
154
     * @return int
155
     */
156
    public function getCounter()
157
    {
158
        return (int)$this->counter;
159
    }
160
161
    /**
162
     * Returns the current Unix Timestamp divided by the $counter period.
163
     *
164
     * @return int
165
     **/
166
    public function getTimestamp()
167
    {
168
        return (int)floor(microtime(true) / $this->getCounter());
169
    }
170
171
    /**
172
     * Get the current one time password for a base32 encoded secret.
173
     *
174
     * @param string $secret
175
     *
176
     * @return string
177
     */
178
    public function getCurrentOneTimePassword($secret)
179
    {
180
        $timestamp = $this->getTimestamp();
181
        $secret = $this->encoder->fromBase32($secret);
182
183
        return $this->oathHotp($secret, $timestamp);
184
    }
185
186
    /**
187
     * Verifies user's key vs current timestamp.
188
     *
189
     * @param string $key the user's input key
190
     * @param string $secret the secret used to
191
     * @param null $previousTime
192
     * @param null $time
193
     *
194
     * @throws InvalidSecretKeyException
195
     * @return bool|int
196
     */
197
    public function verify($key, $secret, $previousTime = null, $time = null)
198
    {
199
        $time = $time ? (int)$time : $this->getTimestamp();
200
        $cycles = $this->getCycles();
201
        $startTime = null === $previousTime
202
            ? $time - $cycles
203
            : max($time - $cycles, $previousTime + 1);
204
205
        $seed = $this->encoder->fromBase32($secret);
206
207
        if (strlen($seed) < 8) {
208
            throw new InvalidSecretKeyException('Secret key is too short');
209
        }
210
211
        return $this->validateOneTimePassword($key, $seed, $startTime, $time, $previousTime);
212
    }
213
214
    /**
215
     * Validates the key (OTP) and returns true if valid, false otherwise.
216
     *
217
     * @param string $key
218
     * @param string $seed
219
     * @param int $startTime
220
     * @param int $time
221
     * @param int|null $previousTime
222
     *
223
     * @return bool
224
     */
225
    protected function validateOneTimePassword($key, $seed, $startTime, $time, $previousTime = null)
226
    {
227
        return (new OneTimePasswordValidator(
228
            $seed,
229
            $this->getCycles(),
230
            $this->getTokenLength(),
231
            $startTime,
232
            $time,
233
            $previousTime
234
        ))
235
            ->validate($key);
236
    }
237
}
238