CsrfComponent::_validateToken()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 14

Duplication

Lines 3
Ratio 21.43 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 3
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         3.0.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Controller\Component;
16
17
use Cake\Controller\Component;
18
use Cake\Event\Event;
19
use Cake\Http\Cookie\Cookie;
20
use Cake\Http\Exception\InvalidCsrfTokenException;
21
use Cake\Http\Response;
22
use Cake\Http\ServerRequest;
23
use Cake\I18n\Time;
24
use Cake\Utility\Security;
25
26
/**
27
 * Provides CSRF protection & validation.
28
 *
29
 * This component adds a CSRF token to a cookie. The cookie value is compared to
30
 * request data, or the X-CSRF-Token header on each PATCH, POST,
31
 * PUT, or DELETE request.
32
 *
33
 * If the request data is missing or does not match the cookie data,
34
 * an InvalidCsrfTokenException will be raised.
35
 *
36
 * This component integrates with the FormHelper automatically and when
37
 * used together your forms will have CSRF tokens automatically added
38
 * when `$this->Form->create(...)` is used in a view.
39
 *
40
 * @deprecated 3.5.0 Use Cake\Http\Middleware\CsrfProtectionMiddleware instead.
41
 */
42
class CsrfComponent extends Component
43
{
44
    /**
45
     * Default config for the CSRF handling.
46
     *
47
     *  - cookieName = The name of the cookie to send.
48
     *  - expiry = How long the CSRF token should last. Defaults to browser session.
49
     *  - secure = Whether or not the cookie will be set with the Secure flag. Defaults to false.
50
     *  - httpOnly = Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
51
     *  - field = The form field to check. Changing this will also require configuring
52
     *    FormHelper.
53
     *
54
     * @var array
55
     */
56
    protected $_defaultConfig = [
57
        'cookieName' => 'csrfToken',
58
        'expiry' => 0,
59
        'secure' => false,
60
        'httpOnly' => false,
61
        'field' => '_csrfToken',
62
    ];
63
64
    /**
65
     * Warn if CsrfComponent is used together with CsrfProtectionMiddleware
66
     *
67
     * @param array $config The config data.
68
     * @return void
69
     */
70
    public function initialize(array $config)
71
    {
72
        if ($this->getController()->getRequest()->getParam('_csrfToken') !== false) {
73
            deprecationWarning('Loading CsrfComponent while CsrfProtectionMiddleware is active ' .
74
                'will corrupt CSRF data and form submitting will fail.');
75
        }
76
    }
77
78
    /**
79
     * Startup callback.
80
     *
81
     * Validates the CSRF token for POST data. If
82
     * the request is a GET request, and the cookie value is absent a cookie will be set.
83
     *
84
     * Once a cookie is set it will be copied into request->getParam('_csrfToken')
85
     * so that application and framework code can easily access the csrf token.
86
     *
87
     * RequestAction requests do not get checked, nor will
88
     * they set a cookie should it be missing.
89
     *
90
     * @param \Cake\Event\Event $event Event instance.
91
     * @return void
92
     */
93
    public function startup(Event $event)
94
    {
95
        /** @var \Cake\Controller\Controller $controller */
96
        $controller = $event->getSubject();
97
        $request = $controller->getRequest();
98
        $response = $controller->getResponse();
99
        $cookieName = $this->_config['cookieName'];
100
101
        $cookieData = $request->getCookie($cookieName);
102
        if ($cookieData) {
103
            $request = $request->withParam('_csrfToken', $cookieData);
104
        }
105
106
        if ($request->is('requested')) {
107
            $controller->setRequest($request);
108
109
            return;
110
        }
111
112
        if ($request->is('get') && $cookieData === null) {
113
            list($request, $response) = $this->_setCookie($request, $response);
114
            $controller->setResponse($response);
115
        }
116
        if ($request->is(['put', 'post', 'delete', 'patch']) || $request->getData()) {
117
            $this->_validateToken($request);
118
            $request = $request->withoutData($this->_config['field']);
119
        }
120
        $controller->setRequest($request);
121
    }
122
123
    /**
124
     * Events supported by this component.
125
     *
126
     * @return array
127
     */
128
    public function implementedEvents()
129
    {
130
        return [
131
            'Controller.startup' => 'startup',
132
        ];
133
    }
134
135
    /**
136
     * Set the cookie in the response.
137
     *
138
     * Also sets the request->params['_csrfToken'] so the newly minted
139
     * token is available in the request data.
140
     *
141
     * @param \Cake\Http\ServerRequest $request The request object.
142
     * @param \Cake\Http\Response $response The response object.
143
     * @return array An array of the modified request, response.
144
     */
145
    protected function _setCookie(ServerRequest $request, Response $response)
146
    {
147
        $expiry = new Time($this->_config['expiry']);
148
        $value = hash('sha512', Security::randomBytes(16), false);
149
150
        $request = $request->withParam('_csrfToken', $value);
151
152
        $cookie = new Cookie(
153
            $this->_config['cookieName'],
154
            $value,
155
            $expiry,
156
            $request->getAttribute('webroot'),
157
            '',
158
            (bool)$this->_config['secure'],
159
            (bool)$this->_config['httpOnly']
160
        );
161
162
        $response = $response->withCookie($cookie);
163
164
        return [$request, $response];
165
    }
166
167
    /**
168
     * Validate the request data against the cookie token.
169
     *
170
     * @param \Cake\Http\ServerRequest $request The request to validate against.
171
     * @throws \Cake\Http\Exception\InvalidCsrfTokenException when the CSRF token is invalid or missing.
172
     * @return void
173
     */
174
    protected function _validateToken(ServerRequest $request)
175
    {
176
        $cookie = $request->getCookie($this->_config['cookieName']);
177
        $post = $request->getData($this->_config['field']);
178
        $header = $request->getHeaderLine('X-CSRF-Token');
179
180
        if (!$cookie) {
181
            throw new InvalidCsrfTokenException(__d('cake', 'Missing CSRF token cookie'));
182
        }
183
184 View Code Duplication
        if (!Security::constantEquals($post, $cookie) && !Security::constantEquals($header, $cookie)) {
185
            throw new InvalidCsrfTokenException(__d('cake', 'CSRF token mismatch.'));
186
        }
187
    }
188
}
189