Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
CsrfTokenSet
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getToken
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 matchTokenField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 matchToken
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Session;
22
23use MediaWiki\Request\WebRequest;
24use MediaWiki\User\LoggedOutEditToken;
25
26/**
27 * Stores and matches CSRF tokens belonging to a given session user.
28 * @since 1.37
29 * @package MediaWiki\Session
30 */
31class CsrfTokenSet {
32
33    /**
34     * @var string default name for the form field to place the token in.
35     */
36    public const DEFAULT_FIELD_NAME = 'wpEditToken';
37
38    private WebRequest $request;
39
40    /**
41     * @param WebRequest $request
42     */
43    public function __construct( WebRequest $request ) {
44        $this->request = $request;
45    }
46
47    /**
48     * Initialize (if necessary) and return a current user CSRF token
49     * value which can be used in edit forms to show that the user's
50     * login credentials aren't being hijacked with a foreign form
51     * submission.
52     *
53     * The $salt for 'edit' and 'csrf' tokens is the default (empty string).
54     *
55     * @param string|string[] $salt Optional function-specific data for hashing
56     * @return Token
57     * @since 1.37
58     */
59    public function getToken( $salt = '' ): Token {
60        $session = $this->request->getSession();
61        if ( !$session->getUser()->isRegistered() ) {
62            return new LoggedOutEditToken();
63        }
64        return $session->getToken( $salt );
65    }
66
67    /**
68     * Check if a request contains a value named $valueName with the token value
69     * stored in the session.
70     *
71     * @param string $fieldName
72     * @param string|string[] $salt
73     * @return bool
74     * @since 1.37
75     * @see self::matchCSRFToken
76     */
77    public function matchTokenField(
78        string $fieldName = self::DEFAULT_FIELD_NAME,
79        $salt = ''
80    ): bool {
81        return $this->matchToken( $this->request->getVal( $fieldName ), $salt );
82    }
83
84    /**
85     * Check if a value matches with the token value stored in the session.
86     * A match should confirm that the form was submitted from the user's own
87     * login session, not a form submission from a third-party site.
88     *
89     * @param string|null $value
90     * @param string|string[] $salt
91     * @return bool
92     * @since 1.37
93     */
94    public function matchToken(
95        ?string $value,
96        $salt = ''
97    ): bool {
98        if ( !$value ) {
99            return false;
100        }
101        $session = $this->request->getSession();
102        // It's expensive to generate a new registered user token, so take a shortcut.
103        // Anon tokens are cheap and all the same, so we can afford to generate one just to match.
104        if ( $session->getUser()->isRegistered() && !$session->hasToken() ) {
105            return false;
106        }
107        return $this->getToken( $salt )->match( $value );
108    }
109}