Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Token
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
7 / 7
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getTimestamp
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 toStringAtTimestamp
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 match
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 wasNew
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
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
24namespace MediaWiki\Session;
25
26/**
27 * Value object representing a CSRF token
28 *
29 * @ingroup Session
30 * @since 1.27
31 */
32class 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}