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