Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
 
14
namespace OTPHP;
15
 
16
use Assert\Assertion;
17
 
18
final class TOTP extends OTP implements TOTPInterface
19
{
20
    /**
21
     * TOTP constructor.
22
     *
23
     * @param string|null $secret
24
     * @param int         $period
25
     * @param string      $digest
26
     * @param int         $digits
27
     * @param int         $epoch
28
     */
29
    protected function __construct($secret, int $period, string $digest, int $digits, int $epoch = 0)
30
    {
31
        parent::__construct($secret, $digest, $digits);
32
        $this->setPeriod($period);
33
        $this->setEpoch($epoch);
34
    }
35
 
36
    /**
37
     * TOTP constructor.
38
     *
39
     * @param string|null $secret
40
     * @param int         $period
41
     * @param string      $digest
42
     * @param int         $digits
43
     * @param int         $epoch
44
     *
45
     * @return self
46
     */
47
    public static function create($secret = null, int $period = 30, string $digest = 'sha1', int $digits = 6, int $epoch = 0): self
48
    {
49
        return new self($secret, $period, $digest, $digits, $epoch);
50
    }
51
 
52
    /**
53
     * @param int $period
54
     */
55
    protected function setPeriod(int $period)
56
    {
57
        $this->setParameter('period', $period);
58
    }
59
 
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function getPeriod(): int
64
    {
65
        return $this->getParameter('period');
66
    }
67
 
68
    /**
69
     * @param int $epoch
70
     */
71
    private function setEpoch(int $epoch)
72
    {
73
        $this->setParameter('epoch', $epoch);
74
    }
75
 
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function getEpoch(): int
80
    {
81
        return $this->getParameter('epoch');
82
    }
83
 
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function at(int $timestamp): string
88
    {
89
        return $this->generateOTP($this->timecode($timestamp));
90
    }
91
 
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function now(): string
96
    {
97
        return $this->at(time());
98
    }
99
 
100
    /**
101
     * If no timestamp is provided, the OTP is verified at the actual timestamp
102
     * {@inheritdoc}
103
     */
104
    public function verify(string $otp, $timestamp = null, $window = null): bool
105
    {
106
        $timestamp = $this->getTimestamp($timestamp);
107
 
108
        if (null === $window) {
109
            return $this->compareOTP($this->at($timestamp), $otp);
110
        }
111
 
112
        return $this->verifyOtpWithWindow($otp, $timestamp, $window);
113
    }
114
 
115
    /**
116
     * @param string $otp
117
     * @param int    $timestamp
118
     * @param int    $window
119
     *
120
     * @return bool
121
     */
122
    private function verifyOtpWithWindow(string $otp, int $timestamp, int $window): bool
123
    {
124
        $window = abs($window);
125
 
126
        for ($i = 0; $i <= $window; $i++) {
127
            $next = (int) $i * $this->getPeriod() + $timestamp;
128
            $previous = (int) -$i * $this->getPeriod() + $timestamp;
129
            $valid = $this->compareOTP($this->at($next), $otp) ||
130
                $this->compareOTP($this->at($previous), $otp);
131
 
132
            if ($valid) {
133
                return true;
134
            }
135
        }
136
 
137
        return false;
138
    }
139
 
140
    /**
141
     * @param int|null $timestamp
142
     *
143
     * @return int
144
     */
145
    private function getTimestamp($timestamp): int
146
    {
147
        $timestamp = $timestamp ?? time();
148
        Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.');
149
 
150
        return (int) $timestamp;
151
    }
152
 
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function getProvisioningUri(): string
157
    {
158
        $params = [];
159
        if (30 !== $this->getPeriod()) {
160
            $params['period'] = $this->getPeriod();
161
        }
162
 
163
        if (0 !== $this->getEpoch()) {
164
            $params['epoch'] = $this->getEpoch();
165
        }
166
 
167
        return $this->generateURI('totp', $params);
168
    }
169
 
170
    /**
171
     * @param int $timestamp
172
     *
173
     * @return int
174
     */
175
    private function timecode(int $timestamp): int
176
    {
177
        return (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod());
178
    }
179
 
180
    /**
181
     * {@inheritdoc}
182
     */
183
    protected function getParameterMap(): array
184
    {
185
        $v = array_merge(
186
            parent::getParameterMap(),
187
            [
188
                'period' => function ($value) {
189
                    Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.');
190
 
191
                    return (int) $value;
192
                },
193
                'epoch' => function ($value) {
194
                    Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.');
195
 
196
                    return (int) $value;
197
                },
198
            ]
199
        );
200
 
201
        return $v;
202
    }
203
 
204
    /**
205
     * {@inheritdoc}
206
     */
207
    protected function filterOptions(array &$options)
208
    {
209
        parent::filterOptions($options);
210
 
211
        if (isset($options['epoch']) && 0 === $options['epoch']) {
212
            unset($options['epoch']);
213
        }
214
 
215
        ksort($options);
216
    }
217
}