Cutelyst  2.3.0
csrfprotection.cpp
1 /*
2  * Copyright (C) 2017 Matthias Fehring <kontakt@buschmann23.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include "csrfprotection_p.h"
20 
21 #include <Cutelyst/Application>
22 #include <Cutelyst/Engine>
23 #include <Cutelyst/Context>
24 #include <Cutelyst/Request>
25 #include <Cutelyst/Response>
26 #include <Cutelyst/Plugins/Session/Session>
27 #include <Cutelyst/Headers>
28 #include <Cutelyst/Action>
29 #include <Cutelyst/Dispatcher>
30 #include <Cutelyst/Controller>
31 
32 #include <QLoggingCategory>
33 #include <QNetworkCookie>
34 #include <QUuid>
35 #include <QUrl>
36 #include <vector>
37 #include <utility>
38 #include <algorithm>
39 
40 #define DEFAULT_COOKIE_AGE Q_INT64_C(31449600) // approx. 1 year
41 #define DEFAULT_COOKIE_NAME "csrftoken"
42 #define DEFAULT_COOKIE_PATH "/"
43 #define DEFAULT_HEADER_NAME "X_CSRFTOKEN"
44 #define DEFAULT_FORM_INPUT_NAME "csrfprotectiontoken"
45 #define CSRF_SECRET_LENGTH 32
46 #define CSRF_TOKEN_LENGTH 2 * CSRF_SECRET_LENGTH
47 #define CSRF_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
48 #define CSRF_SESSION_KEY "_csrftoken"
49 #define CONTEXT_CSRF_COOKIE QStringLiteral("_c_csrfcookie")
50 #define CONTEXT_CSRF_COOKIE_USED QStringLiteral("_c_csrfcookieused")
51 #define CONTEXT_CSRF_COOKIE_NEEDS_RESET QStringLiteral("_c_csrfcookieneedsreset")
52 #define CONTEXT_CSRF_PROCESSING_DONE QStringLiteral("_c_csrfprocessingdone")
53 #define CONTEXT_CSRF_COOKIE_SET QStringLiteral("_c_csrfcookieset")
54 #define CONTEXT_CSRF_CHECK_PASSED QStringLiteral("_c_csrfcheckpassed")
55 
56 Q_LOGGING_CATEGORY(C_CSRFPROTECTION, "cutelyst.plugin.csrfprotection")
57 
58 using namespace Cutelyst;
59 
60 static thread_local CSRFProtection *csrf = nullptr;
61 const QRegularExpression CSRFProtectionPrivate::sanitizeRe = QRegularExpression(QStringLiteral("[^a-zA-Z0-9\\-_]"));
62 // Assume that anything not defined as 'safe' by RFC7231 needs protection
63 const QStringList CSRFProtectionPrivate::secureMethods = QStringList({QStringLiteral("GET"), QStringLiteral("HEAD"), QStringLiteral("OPTIONS"), QStringLiteral("TRACE")});
64 
65 CSRFProtection::CSRFProtection(Application *parent) : Plugin(parent)
66  , d_ptr(new CSRFProtectionPrivate)
67 {
68 
69 }
70 
72 {
73  delete d_ptr;
74 }
75 
77 {
78  Q_D(CSRFProtection);
79 
80  app->loadTranslations(QStringLiteral("plugin_csrfprotection"));
81 
82  const QVariantMap config = app->engine()->config(QStringLiteral("Cutelyst_CSRFProtection_Plugin"));
83 
84  d->cookieAge = config.value(QStringLiteral("cookie_age"), DEFAULT_COOKIE_AGE).value<qint64>();
85  if (d->cookieAge <= 0) {
86  d->cookieAge = DEFAULT_COOKIE_AGE;
87  }
88  d->cookieDomain = config.value(QStringLiteral("cookie_domain")).toString();
89  if (d->cookieName.isEmpty()) {
90  d->cookieName = QStringLiteral(DEFAULT_COOKIE_NAME);
91  }
92  d->cookiePath = QStringLiteral(DEFAULT_COOKIE_PATH);
93  d->cookieSecure = config.value(QStringLiteral("cookie_secure"), false).toBool();
94  if (d->headerName.isEmpty()) {
95  d->headerName = QStringLiteral(DEFAULT_HEADER_NAME);
96  }
97  d->trustedOrigins = config.value(QStringLiteral("trusted_origins")).toString().split(QLatin1Char(','), QString::SkipEmptyParts);
98  if (d->formInputName.isEmpty()) {
99  d->formInputName = QStringLiteral(DEFAULT_FORM_INPUT_NAME);
100  }
101  d->logFailedIp = config.value(QStringLiteral("log_failed_ip"), false).toBool();
102  if (d->errorMsgStashKey.isEmpty()) {
103  d->errorMsgStashKey = QStringLiteral("error_msg");
104  }
105 
106  connect(app, &Application::postForked, this, [](Application *app){
107  csrf = app->plugin<CSRFProtection *>();
108  });
109 
110  connect(app, &Application::beforeDispatch, this, [d](Context *c) {
111  d->beforeDispatch(c);
112  });
113 
114  return true;
115 }
116 
117 void CSRFProtection::setDefaultDetachTo(const QString &actionNameOrPath)
118 {
119  Q_D(CSRFProtection);
120  d->defaultDetachTo = actionNameOrPath;
121 }
122 
123 void CSRFProtection::setFormFieldName(const QString &fieldName)
124 {
125  Q_D(CSRFProtection);
126  if (!fieldName.isEmpty()) {
127  d->formInputName = fieldName;
128  } else {
129  d->formInputName = QStringLiteral(DEFAULT_FORM_INPUT_NAME);
130  }
131 }
132 
133 void CSRFProtection::setErrorMsgStashKey(const QString &keyName)
134 {
135  Q_D(CSRFProtection);
136  if (!keyName.isEmpty()) {
137  d->errorMsgStashKey = keyName;
138  } else {
139  d->errorMsgStashKey = QStringLiteral("error_msg");
140  }
141 }
142 
143 void CSRFProtection::setIgnoredNamespaces(const QStringList &namespaces)
144 {
145  Q_D(CSRFProtection);
146  d->ignoredNamespaces = namespaces;
147 }
148 
149 void CSRFProtection::setUseSessions(bool useSessions)
150 {
151  Q_D(CSRFProtection);
152  d->useSessions = useSessions;
153 }
154 
156 {
157  Q_D(CSRFProtection);
158  d->cookieHttpOnly = httpOnly;
159 }
160 
161 void CSRFProtection::setCookieName(const QString &cookieName)
162 {
163  Q_D(CSRFProtection);
164  d->cookieName = cookieName;
165 }
166 
167 void CSRFProtection::setHeaderName(const QString &headerName)
168 {
169  Q_D(CSRFProtection);
170  d->headerName = headerName;
171 }
172 
173 void CSRFProtection::setGenericErrorMessage(const QString &message)
174 {
175  Q_D(CSRFProtection);
176  d->genericErrorMessage = message;
177 }
178 
180 {
181  Q_D(CSRFProtection);
182  d->genericContentType = type;
183 }
184 
186 {
187  QByteArray token;
188 
189  const QByteArray contextCookie = c->stash(CONTEXT_CSRF_COOKIE).toByteArray();
190  QByteArray secret;
191  if (contextCookie.isEmpty()) {
192  secret = CSRFProtectionPrivate::getNewCsrfString();
193  token = CSRFProtectionPrivate::saltCipherSecret(secret);
194  c->setStash(CONTEXT_CSRF_COOKIE, token);
195  } else {
196  secret = CSRFProtectionPrivate::unsaltCipherToken(contextCookie);
197  token = CSRFProtectionPrivate::saltCipherSecret(secret);
198  }
199 
200  c->setStash(CONTEXT_CSRF_COOKIE_USED, true);
201 
202  return token;
203 }
204 
206 {
207  QString form;
208 
209  if (!csrf) {
210  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
211  return form;
212  }
213 
214  form = QStringLiteral("<input type=\"hidden\" name=\"%1\" value=\"%2\">").arg(csrf->d_ptr->formInputName, QString::fromLatin1(CSRFProtection::getToken(c)));
215 
216  return form;
217 }
218 
220 {
221  if (CSRFProtectionPrivate::secureMethods.contains(c->req()->method())) {
222  return true;
223  } else {
224  return c->stash(CONTEXT_CSRF_CHECK_PASSED).toBool();
225  }
226 }
227 
228 //void CSRFProtection::rotateToken(Context *c)
229 //{
230 // c->setStash(CONTEXT_CSRF_COOKIE_USED, true);
231 // c->setStash(CONTEXT_CSRF_COOKIE, CSRFProtectionPrivate::getNewCsrfToken());
232 // c->setStash(CONTEXT_CSRF_COOKIE_NEEDS_RESET, true);
233 //}
234 
239 QByteArray CSRFProtectionPrivate::getNewCsrfString()
240 {
241  QByteArray csrfString;
242 
243  while (csrfString.size() < CSRF_SECRET_LENGTH) {
244  csrfString.append(QUuid::createUuid().toRfc4122().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
245  }
246 
247  csrfString.resize(CSRF_SECRET_LENGTH);
248 
249  return csrfString;
250 }
251 
257 QByteArray CSRFProtectionPrivate::saltCipherSecret(const QByteArray &secret)
258 {
259  QByteArray salted;
260  salted.reserve(CSRF_TOKEN_LENGTH);
261 
262  const QByteArray salt = CSRFProtectionPrivate::getNewCsrfString();
263  const QByteArray chars = QByteArrayLiteral(CSRF_ALLOWED_CHARS);
264  std::vector<std::pair<int,int>> pairs;
265  pairs.reserve(std::min(secret.size(), salt.size()));
266  for (int i = 0; i < std::min(secret.size(), salt.size()); ++i) {
267  pairs.push_back(std::make_pair(chars.indexOf(secret.at(i)), chars.indexOf(salt.at(i))));
268  }
269 
270  QByteArray cipher;
271  cipher.reserve(CSRF_SECRET_LENGTH);
272  for (std::size_t i = 0; i < pairs.size(); ++i) {
273  const std::pair<int,int> p = pairs.at(i);
274  cipher.append(chars[(p.first + p.second) % chars.size()]);
275  }
276 
277  salted = salt + cipher;
278 
279  return salted;
280 }
281 
288 QByteArray CSRFProtectionPrivate::unsaltCipherToken(const QByteArray &token)
289 {
290  QByteArray secret;
291  secret.reserve(CSRF_SECRET_LENGTH);
292 
293  const QByteArray salt = token.left(CSRF_SECRET_LENGTH);
294  const QByteArray _token = token.mid(CSRF_SECRET_LENGTH);
295 
296  const QByteArray chars = QByteArrayLiteral(CSRF_ALLOWED_CHARS);
297  std::vector<std::pair<int,int>> pairs;
298  pairs.reserve(std::min(salt.size(), _token.size()));
299  for (int i = 0; i < std::min(salt.size(), _token.size()); ++i) {
300  pairs.push_back(std::make_pair(chars.indexOf(_token.at(i)), chars.indexOf(salt.at(i))));
301  }
302 
303 
304  for (std::size_t i = 0; i < pairs.size(); ++i) {
305  const std::pair<int,int> p = pairs.at(i);
306  int idx = p.first - p.second;
307  if (idx < 0) {
308  idx = chars.size() + idx;
309  }
310  secret.append(chars.at(idx));
311  }
312 
313  return secret;
314 }
315 
321 QByteArray CSRFProtectionPrivate::getNewCsrfToken()
322 {
323  return CSRFProtectionPrivate::saltCipherSecret(CSRFProtectionPrivate::getNewCsrfString());
324 }
325 
331 QByteArray CSRFProtectionPrivate::sanitizeToken(const QByteArray &token)
332 {
333  QByteArray sanitized;
334 
335  const QString tokenString = QString::fromLatin1(token);
336  if (tokenString.contains(CSRFProtectionPrivate::sanitizeRe)) {
337  sanitized = CSRFProtectionPrivate::getNewCsrfToken();
338  } else if (token.size() != CSRF_TOKEN_LENGTH) {
339  sanitized = CSRFProtectionPrivate::getNewCsrfToken();
340  } else {
341  sanitized = token;
342  }
343 
344  return sanitized;
345 }
346 
351 QByteArray CSRFProtectionPrivate::getToken(Context *c)
352 {
353  QByteArray token;
354 
355  if (!csrf) {
356  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
357  return token;
358  }
359 
360  if (csrf->d_ptr->useSessions) {
361  token = Session::value(c, QStringLiteral(CSRF_SESSION_KEY)).toByteArray();
362  } else {
363  QByteArray cookieToken = c->req()->cookie(csrf->d_ptr->cookieName).toLatin1();
364 
365  if (cookieToken.isEmpty()) {
366  return token;
367  }
368 
369  token = CSRFProtectionPrivate::sanitizeToken(cookieToken);
370  if (token != cookieToken) {
371  c->setStash(CONTEXT_CSRF_COOKIE_NEEDS_RESET, true);
372  }
373  }
374 
375  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from %s.", token.constData(), csrf->d_ptr->useSessions ? "session" : "cookie");
376 
377  return token;
378 }
379 
384 void CSRFProtectionPrivate::setToken(Context *c)
385 {
386  if (!csrf) {
387  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
388  return;
389  }
390 
391  if (csrf->d_ptr->useSessions) {
392  Session::setValue(c, QStringLiteral(CSRF_SESSION_KEY), c->stash(CONTEXT_CSRF_COOKIE).toByteArray());
393  } else {
394  QNetworkCookie cookie(csrf->d_ptr->cookieName.toLatin1(), c->stash(CONTEXT_CSRF_COOKIE).toByteArray());
395  if (!csrf->d_ptr->cookieDomain.isEmpty()) {
396  cookie.setDomain(csrf->d_ptr->cookieDomain);
397  }
398  cookie.setExpirationDate(QDateTime::currentDateTime().addSecs(csrf->d_ptr->cookieAge));
399  cookie.setHttpOnly(csrf->d_ptr->cookieHttpOnly);
400  cookie.setPath(csrf->d_ptr->cookiePath);
401  cookie.setSecure(csrf->d_ptr->cookieSecure);
402  c->res()->setCookie(cookie);
403  c->res()->headers().pushHeader(QStringLiteral("Vary"), QStringLiteral("Cookie"));
404  }
405 
406  qCDebug(C_CSRFPROTECTION, "Set token \"%s\" to %s.", c->stash(CONTEXT_CSRF_COOKIE).toByteArray().constData(), csrf->d_ptr->useSessions ? "session" : "cookie");
407 }
408 
414 void CSRFProtectionPrivate::reject(Context *c, const QString &logReason, const QString &displayReason)
415 {
416  c->setStash(CONTEXT_CSRF_CHECK_PASSED, false);
417 
418  if (!csrf) {
419  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
420  return;
421  }
422 
423  qCWarning(C_CSRFPROTECTION, "Forbidden: (%s): /%s [%s]", qPrintable(logReason), qPrintable(c->req()->path()), csrf->d_ptr->logFailedIp ? qPrintable(c->req()->addressString()) : "IP logging disabled");
424 
425  c->res()->setStatus(Response::Forbidden);
426  c->setStash(csrf->d_ptr->errorMsgStashKey, displayReason);
427 
428  QString detachToCsrf = c->action()->attribute(QStringLiteral("CSRFDetachTo"));
429  if (detachToCsrf.isEmpty()) {
430  detachToCsrf = csrf->d_ptr->defaultDetachTo;
431  }
432 
433  Action *detachToAction = nullptr;
434 
435  if (!detachToCsrf.isEmpty()) {
436  detachToAction = c->controller()->actionFor(detachToCsrf);
437  if (!detachToAction) {
438  detachToAction = c->dispatcher()->getActionByPath(detachToCsrf);
439  }
440  if (!detachToAction) {
441  qCWarning(C_CSRFPROTECTION, "Can not find action for \"%s\" to detach to.", qPrintable(detachToCsrf));
442  }
443  }
444 
445  if (detachToAction) {
446  c->detach(detachToAction);
447  } else {
448  if (!csrf->d_ptr->genericErrorMessage.isEmpty()) {
449  c->res()->setBody(csrf->d_ptr->genericErrorMessage);
450  c->res()->setContentType(csrf->d_ptr->genericContentType);
451  } else {
452  const QString title = c->translate("Cutelyst::CSRFProtection", "403 Forbidden - CSRF protection check failed");
453  c->res()->setBody(QStringLiteral("<!DOCTYPE html><html><head><meta charset='utf-8'><title>") + title + QStringLiteral("</title></head><body><h1>") + title + QStringLiteral("</h1><p>") + displayReason + QStringLiteral("</p></body></html>"));
454  c->res()->setContentType(QStringLiteral("text/html; charset=utf-8"));
455  }
456  c->detach();
457  }
458 }
459 
460 void CSRFProtectionPrivate::accept(Context *c)
461 {
462  c->setStash(CONTEXT_CSRF_CHECK_PASSED, true);
463  c->setStash(CONTEXT_CSRF_PROCESSING_DONE, true);
464 }
465 
470 bool CSRFProtectionPrivate::compareSaltedTokens(const QByteArray &t1, const QByteArray &t2)
471 {
472  const QByteArray _t1 = CSRFProtectionPrivate::unsaltCipherToken(t1);
473  const QByteArray _t2 = CSRFProtectionPrivate::unsaltCipherToken(t2);
474 
475  // to avoid timing attack
476  int diff = _t1.size() ^ _t2.size();
477  for (int i = 0; i < _t1.size() && i < _t2.size(); i++) {
478  diff |= _t1[i] ^ _t2[i];
479  }
480  return diff == 0;
481 }
482 
487 void CSRFProtectionPrivate::beforeDispatch(Context *c)
488 {
489  if (!csrf) {
490  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRFProtection plugin not registered"), c->translate("Cutelyst::CSRFProtection", "The CSRF protection plugin has not been registered."));
491  return;
492  }
493 
494  const QByteArray csrfToken = CSRFProtectionPrivate::getToken(c);
495  if (!csrfToken.isNull()) {
496  c->setStash(CONTEXT_CSRF_COOKIE, csrfToken);
497  } else {
499  }
500 
501  if (c->stash(CONTEXT_CSRF_PROCESSING_DONE).toBool()) {
502  return;
503  }
504 
505  if (c->action()->attributes().contains(QStringLiteral("CSRFIgnore"))) {
506  qCDebug(C_CSRFPROTECTION, "Action \"%s::%s\" is ignored by the CSRF protection.", qPrintable(c->action()->className()), qPrintable(c->action()->reverse()));
507  return;
508  }
509 
510  if (csrf->d_ptr->ignoredNamespaces.contains(c->action()->ns())) {
511  if (!c->action()->attributes().contains(QStringLiteral("CSRFRequire"))) {
512  qCDebug(C_CSRFPROTECTION, "Namespace \"%s\" is ignored by the CSRF protection.", qPrintable(c->action()->ns()));
513  return;
514  }
515  }
516 
517  // only check the tokens if the method is not secure, e.g. POST
518  // the following methods are secure according to RFC 7231: GET, HEAD, OPTIONS and TRACE
519  if (!CSRFProtectionPrivate::secureMethods.contains(c->req()->method())) {
520 
521  bool ok = true;
522 
523  // Suppose user visits http://example.com/
524  // An active network attacker (man-in-the-middle, MITM) sends a POST form that targets
525  // https://example.com/detonate-bomb/ and submits it via JavaScript.
526  //
527  // The attacker will need to provide a CSRF cookie and token, but that's no problem for a
528  // MITM and the session-independent secret we're using. So the MITM can circumvent the CSRF
529  // protection. This is true for any HTTP connection, but anyone using HTTPS expects better!
530  // For this reason, for https://example.com/ we need additional protection that treats
531  // http://example.com/ as completely untrusted. Under HTTPS, Barth et al. found that the
532  // Referer header is missing for same-domain requests in only about 0.2% of cases or less, so
533  // we can use strict Referer checking.
534  if (c->req()->secure()) {
535  const QString referer = c->req()->headers().referer();
536 
537  if (Q_UNLIKELY(referer.isEmpty())) {
538  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - no Referer."), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - no Referer."));
539  ok = false;
540  } else {
541  const QUrl refererUrl(referer);
542  if (Q_UNLIKELY(!refererUrl.isValid())) {
543  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - Referer is malformed."), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - Referer is malformed."));
544  ok = false;
545  } else {
546  if (Q_UNLIKELY(refererUrl.scheme() != QLatin1String("https"))) {
547  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - Referer is insecure while host is secure."), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - Referer is insecure while host is secure."));
548  ok = false;
549  } else {
550  // If there isn't a CSRF_COOKIE_DOMAIN, require an exact match on host:port.
551  // If not, obey the cookie rules (or those for the session cookie, if we
552  // use sessions
553  const QUrl uri = c->req()->uri();
554  QString goodReferer;
555  if (!csrf->d_ptr->useSessions) {
556  goodReferer = csrf->d_ptr->cookieDomain;
557  }
558  if (goodReferer.isEmpty()) {
559  goodReferer = uri.host();
560  }
561  const int serverPort = uri.port(c->req()->secure() ? 443 : 80);
562  if ((serverPort != 80) && (serverPort != 443)) {
563  goodReferer += QLatin1Char(':') + QString::number(serverPort);
564  }
565 
566  QStringList goodHosts = csrf->d_ptr->trustedOrigins;
567  goodHosts.append(goodReferer);
568 
569  QString refererHost = refererUrl.host();
570  const int refererPort = refererUrl.port(refererUrl.scheme() == QLatin1String("https") ? 443 : 80);
571  if ((refererPort != 80) && (refererPort != 443)) {
572  refererHost += QLatin1Char(':') + QString::number(refererPort);
573  }
574 
575  bool refererCheck = false;
576  for (int i = 0; i < goodHosts.size(); ++i) {
577  const QString host = goodHosts.at(i);
578  if ((host.startsWith(QLatin1Char('.')) && (refererHost.endsWith(host) || (refererHost == host.mid(1)))) || host == refererHost) {
579  refererCheck = true;
580  break;
581  }
582  }
583 
584  if (Q_UNLIKELY(!refererCheck)) {
585  ok = false;
586  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - %1 does not match any trusted origins.").arg(referer), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - %1 does not match any trusted origins.").arg(referer));
587  }
588  }
589  }
590  }
591  }
592 
593  if (Q_LIKELY(ok)) {
594  if (Q_UNLIKELY(csrfToken.isEmpty())) {
595  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRF cookie not set."), c->translate("Cutelyst::CSRFProtection", "CSRF cookie not set."));
596  ok = false;
597  } else {
598 
599  QByteArray requestCsrfToken;
600  // delete does not have body data
601  if (c->req()->method() != QLatin1String("DELETE")) {
602  requestCsrfToken = c->req()->bodyParam(csrf->d_ptr->formInputName).toLatin1();
603  }
604 
605  if (requestCsrfToken.isEmpty()) {
606  requestCsrfToken = c->req()->header(csrf->d_ptr->headerName).toLatin1();
607  if (Q_LIKELY(!requestCsrfToken.isEmpty())) {
608  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from HTTP header %s.", requestCsrfToken.constData(), qPrintable(csrf->d_ptr->headerName));
609  } else {
610  qCDebug(C_CSRFPROTECTION, "Can not get token from HTTP header or form field.");
611  }
612  } else {
613  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from form field %s.", requestCsrfToken.constData(), qPrintable(csrf->d_ptr->formInputName));
614  }
615 
616  requestCsrfToken = CSRFProtectionPrivate::sanitizeToken(requestCsrfToken);
617 
618  if (Q_UNLIKELY(!CSRFProtectionPrivate::compareSaltedTokens(requestCsrfToken, csrfToken))) {
619  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRF token missing or incorrect."), c->translate("Cutelyst::CSRFProtection", "CSRF token missing or incorrect."));
620  ok = false;
621  }
622  }
623  }
624 
625  if (Q_LIKELY(ok)) {
626  CSRFProtectionPrivate::accept(c);
627  }
628  }
629 
630  // Set the CSRF cookie even if it's already set, so we renew
631  // the expiry timer.
632 
633  if (!c->stash(CONTEXT_CSRF_COOKIE_NEEDS_RESET).toBool()) {
634  if (c->stash(CONTEXT_CSRF_COOKIE_SET).toBool()) {
635  return;
636  }
637  }
638 
639  if (!c->stash(CONTEXT_CSRF_COOKIE_USED).toBool()) {
640  return;
641  }
642 
643  CSRFProtectionPrivate::setToken(c);
644  c->setStash(CONTEXT_CSRF_COOKIE_SET, true);
645 }
646 
647 #include "moc_csrfprotection.cpp"
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:363
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:211
void setHeaderName(const QString &headerName)
void setContentType(const QString &type)
Definition: response.h:205
QMap< QString, QString > attributes() const
Definition: action.cpp:81
Engine * engine() const
Dispatcher * dispatcher() const
Definition: context.cpp:152
Response * res() const
Definition: context.cpp:116
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:207
void detach(Action *action=nullptr)
Definition: context.cpp:317
void loadTranslations(const QString &filename, const QString &directory=QString(), const QString &prefix=QString(), const QString &suffix=QString())
Action * actionFor(const QString &name) const
Definition: controller.cpp:48
void setStatus(quint16 status)
Definition: response.cpp:85
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:115
QString ns() const
Definition: action.cpp:131
void setGenericErrorContentTyp(const QString &type)
This class represents a Cutelyst Action.
Definition: action.h:47
void setIgnoredNamespaces(const QStringList &namespaces)
virtual bool setup(Application *app) override
The Cutelyst Context.
Definition: context.h:50
QString addressString() const
Definition: request.cpp:48
QString bodyParam(const QString &key, const QString &defaultValue=QString()) const
Definition: request.h:495
static bool checkPassed(Context *c)
QVariantMap config(const QString &entity) const
user configuration for the application
Definition: engine.cpp:323
Headers & headers()
Definition: response.cpp:290
void setDefaultDetachTo(const QString &actionNameOrPath)
QString header(const QString &key) const
Definition: request.h:519
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:414
QString attribute(const QString &name, const QString &defaultValue=QString()) const
Definition: action.cpp:87
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition: session.cpp:176
void setFormFieldName(const QString &fieldName)
Protect input forms against Cross Site Request Forgery (CSRF/XSRF) attacks.
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
QString reverse() const
Definition: component.cpp:51
void beforeDispatch(Context *c)
void setCookieName(const QString &cookieName)
void setUseSessions(bool useSessions)
void setCookieHttpOnly(bool httpOnly)
void postForked(Application *app)
void setGenericErrorMessage(const QString &message)
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:161
void setErrorMsgStashKey(const QString &keyName)
QString cookie(const QString &name) const
Definition: request.cpp:267
The Cutelyst Application.
Definition: application.h:55
void setBody(QIODevice *body)
Definition: response.cpp:114
Action * getActionByPath(const QString &path) const
Definition: dispatcher.cpp:231
static QString getTokenFormField(Context *c)
static QByteArray getToken(Context *c)
void stash(const QVariantHash &unite)
Definition: context.h:515
QString className() const
Definition: action.cpp:99