CSRF::setCookieAndSession()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use CILogon\Service\Loggit;
7
8
/**
9
 * CSRF
10
 *
11
 * This class creates and manages CSRF (cross-site request forgery)
12
 * values.  Upon creation of a new csrf object, a random value is
13
 * created that can be (a) stored in a cookie and (b) written to a
14
 * hidden form element or saved to a PHP session.  There are functions
15
 * to see if a previously set csrf cookie value matches a form-submitted
16
 * csrf element or PHP session value. Note that cookies must be set
17
 * before any HTML is printed out.
18
 *
19
 * Example usage:
20
 *    // Set cookie and output hidden element in HTML <form> block
21
 *    require_once 'CSRF.php';
22
 *    $csrf = new csrf();
23
 *    $csrf->setTheCookie();
24
 *    // Output an HTML <form> block
25
 *    echo $csrf->hiddenFormElement();
26
 *    // Close </form> block
27
 *
28
 *    // When user submits the form, first check for csrf equality
29
 *    if ($csrf->isCookieEqualToForm()) {
30
 *        // Form submission is okay - process it
31
 *    } else {
32
 *        $csrf->removeTheCookie();
33
 *    }
34
 *
35
 *    // Alternatively, set cookie and PHP session value and compare
36
 *    require_once 'CSRF.php';
37
 *    session_start();
38
 *    $csrf = new csrf();
39
 *    $csrf->setCookieAndSession();
40
 *
41
 *    // When the user (re)loads the page, check for csrf equality
42
 *    session_start();
43
 *    if ($csrf->isCookieEqualToSession()) {
44
 *        // Session csrf value was okay - process as normal
45
 *    } else {
46
 *        $csrf->removeTheCookie();
47
 *        $csrf->removeTheSession();
48
 *    }
49
 */
50
class CSRF
51
{
52
    /**
53
     * @var string DEFAULTTOKENNAME The default token name can be
54
     *      overridden in the constructor.
55
     */
56
    public const DEFAULTTOKENNAME = "CSRF";
57
58
    /**
59
     * @var string $tokenname The 'name' of the CSRF token, as saved
60
     *      in session and cookie.
61
     */
62
    private $tokenname;
63
64
    /**
65
     * @var string $tokenvalie The 'value' of the CSRF token, a random
66
     *      sequence of characters.
67
     */
68
    private $tokenvalue;
69
70
    /**
71
     * __construct
72
     *
73
     * Default constructor. This sets the value of the csrf token to
74
     * a random string of characters.
75
     *
76
     * @param string $tokenname (Optional) The 'name' of the csrf
77
     *        token. Defaults to 'CSRF'.
78
     */
79
    public function __construct($tokenname = self::DEFAULTTOKENNAME)
80
    {
81
        $this->setTokenName($tokenname);
82
        $this->tokenvalue = md5(uniqid(rand(), true));
83
    }
84
85
    /**
86
     * getTokenName
87
     *
88
     * Returns the name of the csrf token stored in the private
89
     * variable $tokenname.
90
     *
91
     * @return string The string name of the csrf token.
92
     */
93
    public function getTokenName()
94
    {
95
        return $this->tokenname;
96
    }
97
98
    /**
99
     * setTokenName
100
     *
101
     * Sets the private variable $tokenname to the name of the csrf
102
     * token. Use this method within other methods of the csrf class.
103
     *
104
     * @param string $tokenname The string name of the csrf token.
105
     */
106
    public function setTokenName($tokenname)
107
    {
108
        $this->tokenname = $tokenname;
109
    }
110
111
    /**
112
     * getTokenValue
113
     *
114
     * Returns the value of the csrf token stored in the private
115
     * variable $tokenvalue.
116
     *
117
     * @return string The value of the random csrf token.
118
     */
119
    public function getTokenValue()
120
    {
121
        return $this->tokenvalue;
122
    }
123
124
    /**
125
     * hiddenFormElement
126
     *
127
     * Returns an <input ...> form element of type 'hidden' with the
128
     * name and value set to the csrf tokenname and tokenvalue.
129
     *
130
     * @return string The string of an <input> HTML element.
131
     */
132
    public function hiddenFormElement()
133
    {
134
        return '<input type="hidden" name="' . $this->getTokenName() .
135
               '" value="' . $this->getTokenValue() .  '" />';
136
    }
137
138
    /**
139
     * setTheCookie
140
     *
141
     * Sets a session cookie with the csrf tokenname and tokenvalue.
142
     * You must call this method before you output any HTML.
143
     */
144
    public function setTheCookie()
145
    {
146
        Util::setCookieVar($this->getTokenName(), $this->getTokenValue(), 0);
147
    }
148
149
    /**
150
     * getTheCookie
151
     *
152
     * Returns the value of the CSRF cookie if it has been set, or
153
     * returns an empty string otherwise. Note that the token in the
154
     * cookie is actually the PREVIOUSLY SET token value, which is
155
     * different from the object instance's $tokenvalue. This is due
156
     * to the way cookies are processed on the NEXT page load.
157
     *
158
     * @return string The current value of the CSRF cookie (which was
159
     *         actually set on a previous page load), or empty string
160
     *         if it has not been set.
161
     */
162
    public function getTheCookie()
163
    {
164
        return Util::getCookieVar($this->getTokenName());
165
    }
166
167
    /**
168
     * removeTheCookie
169
     *
170
     * Removes the csrf cookie.  You must call this method before you
171
     * output any HTML.  Strictly speaking, the cookie is not removed,
172
     * rather it is set to an empty value with an expired time.
173
     */
174
    public function removeTheCookie()
175
    {
176
        Util::unsetCookieVar($this->getTokenName());
177
    }
178
179
    /**
180
     * setTheSession
181
     *
182
     * Sets a value in the PHP session csrf tokenname and tokenvalue.
183
     * You must have a valid PHP session (e.g. by calling
184
     * session_start()) before you call this method.
185
     */
186
    public function setTheSession()
187
    {
188
        Util::setSessionVar($this->getTokenName(), $this->getTokenValue());
189
    }
190
191
    /**
192
     * getTheSession
193
     *
194
     * Returns the value of the CSRF token as set in the PHP session,
195
     * or returns an empty string otherwise.
196
     *
197
     * @return string The current value of the CSRF in the PHP session,
198
     *         or empty string if it has not been set.
199
     */
200
    public function getTheSession()
201
    {
202
        return Util::getSessionVar($this->getTokenName());
203
    }
204
205
    /**
206
     * removeTheSession
207
     *
208
     * Removes the csrf value from the PHP session.
209
     */
210
    public function removeTheSession()
211
    {
212
        Util::unsetSessionVar($this->getTokenName());
213
    }
214
215
    /**
216
     * setCookieAndSession
217
     *
218
     * This is a convenience function which sets both the cookie and
219
     * the session tokens. You must have a valid PHP session (e.g. by
220
     * calling session_start()) before you call this method.
221
     */
222
    public function setCookieAndSession()
223
    {
224
        $this->setTheCookie();
225
        $this->setTheSession();
226
    }
227
228
    /**
229
     * isCookieEqualToForm
230
     *
231
     * This is a convenience method which compares the value of a
232
     * previously set csrf cookie to the value of a submitted csrf
233
     * form element.  If the two values are equal (and non-empty), then
234
     * this returns true and you can continue processing.  Otherwise,
235
     * false is returned and you should assume that the form was not
236
     * submitted properly.
237
     *
238
     * @return bool True if the csrf cookie value matches the submitted
239
     *         csrf form value, false otherwise.
240
     */
241
    public function isCookieEqualToForm()
242
    {
243
        $retval = false;  // Assume csrf values don't match
244
245
        $csrfcookievalue = $this->getTheCookie();
246
        $csrfformvalue = Util::getPostVar($this->getTokenName());
247
        if (
248
            (strlen($csrfcookievalue) > 0) &&
249
            (strlen($csrfformvalue) > 0) &&
250
            (strcmp($csrfcookievalue, $csrfformvalue) == 0)
251
        ) {
252
            $retval = true;
253
        }
254
255
        return $retval;
256
    }
257
258
    /**
259
     * isCookieEqualToSession
260
     *
261
     * This is a convenience method which compares the value of a
262
     * previously set csrf cookie to the csrf value in the current
263
     * PHP session.  If the two values are equal (and non-empty), then
264
     * this returns true and you can continue processing.  Otherwise,
265
     * false is returned and you should assume that the session was not
266
     * initialized properly.
267
     *
268
     * @return bool True if the csrf cookie value matches the csrf value
269
     *         in the PHP session, false otherwise.
270
     */
271
    public function isCookieEqualToSession()
272
    {
273
        $retval = false;  // Assume csrf values don't match
274
275
        $csrfcookievalue = $this->getTheCookie();
276
        $csrfsesionvalue = $this->getTheSession();
277
        if (
278
            (strlen($csrfcookievalue) > 0) &&
279
            (strlen($csrfsesionvalue) > 0) &&
280
            (strcmp($csrfcookievalue, $csrfsesionvalue) == 0)
281
        ) {
282
            $retval = true;
283
        }
284
285
        return $retval;
286
    }
287
288
    /**
289
     * verifyCookieAndGetSubmit
290
     *
291
     * This function assumes that one of the two following actions has
292
     * occurred:
293
     *   (1) The user has clicked a <form> submit button.
294
     *   (2) A PHP session variable has been set.
295
     * The function first checks the <form>'s hidden 'csrf' element
296
     * to see if it matches the csrf cookie.  If so, it checks the
297
     * the value of the submit button with the passed-in 'name'
298
     * attribute (defaults to 'submit').  If the value is non-empty,
299
     * then that value is returned.  However, if the <form> hidden csrf
300
     * element doesn't match the csrf cookie or the submit element is
301
     * empty, the function then checks the PHP session csrf value to
302
     * see if that matches the csrf cookie.  If so, it looks for a
303
     * variable with the passed-in parameter name and returns that
304
     * value.  In other words, a non-empty <form> submit button has
305
     * priority over a PHP session value.  In any case, if the csrf
306
     * test fails for both <form> and PHP session, the csrf cookie is
307
     * removed, and the empty string is returned.
308
     *
309
     * @param string $submit (Optional) The name of a <form>'s 'submit'
310
     *        button OR the key of a PHP session variable, defaults to
311
     *        'submit'.
312
     * @return string The value of the <form>'s clicked 'submit' button if
313
     *         the csrf cookie matches the hidden form element, or
314
     *         the value of the PHP session variable $submit if the
315
     *         csrf cookie matches the hidden PHP session csrf
316
     *         variable, or empty string otherwise.
317
     */
318
    public function verifyCookieAndGetSubmit($submit = 'submit')
319
    {
320
        $retval = '';
321
        // First, check <form> hidden csrf element
322
        if ($this->isCookieEqualToForm()) {
323
            $retval = Util::getPostVar($submit);
324
            // Hack for Duo Security - look for all uppercase e.g., 'SUBMIT'
325
            if (strlen($retval) == 0) {
326
                $retval = Util::getPostVar(strtoupper($submit));
327
            }
328
        }
329
        // If <form> element missing or bad, check PHP session csrf variable
330
        if ((strlen($retval) == 0) && ($this->isCookieEqualToSession())) {
331
            $retval = Util::getSessionVar($submit);
332
            $this->removeTheSession();  // No need to use it again
333
        }
334
        // If csrf failed or no 'submit' element in <form> or session,
335
        // remove the csrf cookie.
336
        if (strlen($retval) == 0) {
337
            $this->removeTheCookie();
338
            $log = new Loggit();
339
            $log->info('CSRF check failed.');
340
        }
341
        return $retval;
342
    }
343
}
344