Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
25 / 25 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
1 / 1 |
Token | |
100.00% |
25 / 25 |
|
100.00% |
7 / 7 |
14 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getTimestamp | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
toStringAtTimestamp | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
match | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
wasNew | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * MediaWiki session token |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup Session |
22 | */ |
23 | |
24 | namespace MediaWiki\Session; |
25 | |
26 | /** |
27 | * Value object representing a CSRF token |
28 | * |
29 | * @ingroup Session |
30 | * @since 1.27 |
31 | */ |
32 | class Token { |
33 | /** CSRF token suffix. Plus and terminal backslash are included to stop |
34 | * editing from certain broken proxies. |
35 | */ |
36 | public const SUFFIX = '+\\'; |
37 | |
38 | /** @var string */ |
39 | private $secret; |
40 | |
41 | /** @var string */ |
42 | private $salt; |
43 | |
44 | /** @var bool */ |
45 | private $new; |
46 | |
47 | /** |
48 | * @param string $secret Token secret |
49 | * @param string $salt Token salt |
50 | * @param bool $new Whether the secret was newly-created |
51 | */ |
52 | public function __construct( $secret, $salt, $new = false ) { |
53 | $this->secret = $secret; |
54 | $this->salt = $salt; |
55 | $this->new = $new; |
56 | } |
57 | |
58 | /** |
59 | * Decode the timestamp from a token string |
60 | * |
61 | * Does not validate the token beyond the syntactic checks necessary to |
62 | * be able to extract the timestamp. |
63 | * |
64 | * @param string $token |
65 | * @return int|null |
66 | */ |
67 | public static function getTimestamp( $token ) { |
68 | $suffixLen = strlen( self::SUFFIX ); |
69 | $len = strlen( $token ); |
70 | if ( $len <= 32 + $suffixLen || |
71 | substr( $token, -$suffixLen ) !== self::SUFFIX || |
72 | strspn( $token, '0123456789abcdef' ) + $suffixLen !== $len |
73 | ) { |
74 | return null; |
75 | } |
76 | |
77 | return hexdec( substr( $token, 32, -$suffixLen ) ); |
78 | } |
79 | |
80 | /** |
81 | * Get the string representation of the token at a timestamp |
82 | * @param int $timestamp |
83 | * @return string |
84 | */ |
85 | protected function toStringAtTimestamp( $timestamp ) { |
86 | return hash_hmac( 'md5', $timestamp . $this->salt, $this->secret, false ) . |
87 | dechex( $timestamp ) . |
88 | self::SUFFIX; |
89 | } |
90 | |
91 | /** |
92 | * Get the string representation of the token |
93 | * @return string |
94 | */ |
95 | public function toString() { |
96 | return $this->toStringAtTimestamp( (int)wfTimestamp( TS_UNIX ) ); |
97 | } |
98 | |
99 | public function __toString() { |
100 | return $this->toString(); |
101 | } |
102 | |
103 | /** |
104 | * Test if the token-string matches this token |
105 | * @param string|null $userToken |
106 | * @param int|null $maxAge Return false if $userToken is older than this many seconds |
107 | * @return bool |
108 | */ |
109 | public function match( $userToken, $maxAge = null ) { |
110 | if ( !$userToken ) { |
111 | return false; |
112 | } |
113 | $timestamp = self::getTimestamp( $userToken ); |
114 | if ( $timestamp === null ) { |
115 | return false; |
116 | } |
117 | if ( $maxAge !== null && $timestamp < (int)wfTimestamp( TS_UNIX ) - $maxAge ) { |
118 | // Expired token |
119 | return false; |
120 | } |
121 | |
122 | $sessionToken = $this->toStringAtTimestamp( $timestamp ); |
123 | return hash_equals( $sessionToken, $userToken ); |
124 | } |
125 | |
126 | /** |
127 | * Indicate whether this token was just created |
128 | * @return bool |
129 | */ |
130 | public function wasNew() { |
131 | return $this->new; |
132 | } |
133 | |
134 | } |