Cutelyst  3.5.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 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
87  d->trustedOrigins = config.value(QStringLiteral("trusted_origins")).toString().split(QLatin1Char(','), Qt::SkipEmptyParts);
88 #else
89  d->trustedOrigins = config.value(QStringLiteral("trusted_origins")).toString().split(QLatin1Char(','), QString::SkipEmptyParts);
90 #endif
91  if (d->formInputName.isEmpty()) {
92  d->formInputName = QStringLiteral(DEFAULT_FORM_INPUT_NAME);
93  }
94  d->logFailedIp = config.value(QStringLiteral("log_failed_ip"), false).toBool();
95  if (d->errorMsgStashKey.isEmpty()) {
96  d->errorMsgStashKey = QStringLiteral("error_msg");
97  }
98 
99  connect(app, &Application::postForked, this, [](Application *app){
100  csrf = app->plugin<CSRFProtection *>();
101  });
102 
103  connect(app, &Application::beforeDispatch, this, [d](Context *c) {
104  d->beforeDispatch(c);
105  });
106 
107  return true;
108 }
109 
110 void CSRFProtection::setDefaultDetachTo(const QString &actionNameOrPath)
111 {
112  Q_D(CSRFProtection);
113  d->defaultDetachTo = actionNameOrPath;
114 }
115 
117 {
118  Q_D(CSRFProtection);
119  if (!fieldName.isEmpty()) {
120  d->formInputName = fieldName;
121  } else {
122  d->formInputName = QStringLiteral(DEFAULT_FORM_INPUT_NAME);
123  }
124 }
125 
127 {
128  Q_D(CSRFProtection);
129  if (!keyName.isEmpty()) {
130  d->errorMsgStashKey = keyName;
131  } else {
132  d->errorMsgStashKey = QStringLiteral("error_msg");
133  }
134 }
135 
137 {
138  Q_D(CSRFProtection);
139  d->ignoredNamespaces = namespaces;
140 }
141 
142 void CSRFProtection::setUseSessions(bool useSessions)
143 {
144  Q_D(CSRFProtection);
145  d->useSessions = useSessions;
146 }
147 
149 {
150  Q_D(CSRFProtection);
151  d->cookieHttpOnly = httpOnly;
152 }
153 
154 void CSRFProtection::setCookieName(const QString &cookieName)
155 {
156  Q_D(CSRFProtection);
157  d->cookieName = cookieName;
158 }
159 
160 void CSRFProtection::setHeaderName(const QString &headerName)
161 {
162  Q_D(CSRFProtection);
163  d->headerName = headerName;
164 }
165 
167 {
168  Q_D(CSRFProtection);
169  d->genericErrorMessage = message;
170 }
171 
173 {
174  Q_D(CSRFProtection);
175  d->genericContentType = type;
176 }
177 
179 {
180  QByteArray token;
181 
182  const QByteArray contextCookie = c->stash(CONTEXT_CSRF_COOKIE).toByteArray();
183  QByteArray secret;
184  if (contextCookie.isEmpty()) {
185  secret = CSRFProtectionPrivate::getNewCsrfString();
186  token = CSRFProtectionPrivate::saltCipherSecret(secret);
187  c->setStash(CONTEXT_CSRF_COOKIE, token);
188  } else {
189  secret = CSRFProtectionPrivate::unsaltCipherToken(contextCookie);
190  token = CSRFProtectionPrivate::saltCipherSecret(secret);
191  }
192 
193  c->setStash(CONTEXT_CSRF_COOKIE_USED, true);
194 
195  return token;
196 }
197 
199 {
200  QString form;
201 
202  if (!csrf) {
203  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
204  return form;
205  }
206 
207  form = QStringLiteral("<input type=\"hidden\" name=\"%1\" value=\"%2\" />").arg(csrf->d_ptr->formInputName, QString::fromLatin1(CSRFProtection::getToken(c)));
208 
209  return form;
210 }
211 
213 {
214  if (CSRFProtectionPrivate::secureMethods.contains(c->req()->method())) {
215  return true;
216  } else {
217  return c->stash(CONTEXT_CSRF_CHECK_PASSED).toBool();
218  }
219 }
220 
221 //void CSRFProtection::rotateToken(Context *c)
222 //{
223 // c->setStash(CONTEXT_CSRF_COOKIE_USED, true);
224 // c->setStash(CONTEXT_CSRF_COOKIE, CSRFProtectionPrivate::getNewCsrfToken());
225 // c->setStash(CONTEXT_CSRF_COOKIE_NEEDS_RESET, true);
226 //}
227 
232 QByteArray CSRFProtectionPrivate::getNewCsrfString()
233 {
234  QByteArray csrfString;
235 
236  while (csrfString.size() < CSRF_SECRET_LENGTH) {
237  csrfString.append(QUuid::createUuid().toRfc4122().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
238  }
239 
240  csrfString.resize(CSRF_SECRET_LENGTH);
241 
242  return csrfString;
243 }
244 
250 QByteArray CSRFProtectionPrivate::saltCipherSecret(const QByteArray &secret)
251 {
252  QByteArray salted;
253  salted.reserve(CSRF_TOKEN_LENGTH);
254 
255  const QByteArray salt = CSRFProtectionPrivate::getNewCsrfString();
256  const QByteArray chars = QByteArrayLiteral(CSRF_ALLOWED_CHARS);
257  std::vector<std::pair<int,int>> pairs;
258  pairs.reserve(std::min(secret.size(), salt.size()));
259  for (int i = 0; i < std::min(secret.size(), salt.size()); ++i) {
260  pairs.push_back(std::make_pair(chars.indexOf(secret.at(i)), chars.indexOf(salt.at(i))));
261  }
262 
263  QByteArray cipher;
264  cipher.reserve(CSRF_SECRET_LENGTH);
265  for (std::size_t i = 0; i < pairs.size(); ++i) {
266  const std::pair<int,int> p = pairs.at(i);
267  cipher.append(chars[(p.first + p.second) % chars.size()]);
268  }
269 
270  salted = salt + cipher;
271 
272  return salted;
273 }
274 
281 QByteArray CSRFProtectionPrivate::unsaltCipherToken(const QByteArray &token)
282 {
283  QByteArray secret;
284  secret.reserve(CSRF_SECRET_LENGTH);
285 
286  const QByteArray salt = token.left(CSRF_SECRET_LENGTH);
287  const QByteArray _token = token.mid(CSRF_SECRET_LENGTH);
288 
289  const QByteArray chars = QByteArrayLiteral(CSRF_ALLOWED_CHARS);
290  std::vector<std::pair<int,int>> pairs;
291  pairs.reserve(std::min(salt.size(), _token.size()));
292  for (int i = 0; i < std::min(salt.size(), _token.size()); ++i) {
293  pairs.push_back(std::make_pair(chars.indexOf(_token.at(i)), chars.indexOf(salt.at(i))));
294  }
295 
296 
297  for (std::size_t i = 0; i < pairs.size(); ++i) {
298  const std::pair<int,int> p = pairs.at(i);
299  int idx = p.first - p.second;
300  if (idx < 0) {
301  idx = chars.size() + idx;
302  }
303  secret.append(chars.at(idx));
304  }
305 
306  return secret;
307 }
308 
314 QByteArray CSRFProtectionPrivate::getNewCsrfToken()
315 {
316  return CSRFProtectionPrivate::saltCipherSecret(CSRFProtectionPrivate::getNewCsrfString());
317 }
318 
324 QByteArray CSRFProtectionPrivate::sanitizeToken(const QByteArray &token)
325 {
326  QByteArray sanitized;
327 
328  const QString tokenString = QString::fromLatin1(token);
329  if (tokenString.contains(CSRFProtectionPrivate::sanitizeRe)) {
330  sanitized = CSRFProtectionPrivate::getNewCsrfToken();
331  } else if (token.size() != CSRF_TOKEN_LENGTH) {
332  sanitized = CSRFProtectionPrivate::getNewCsrfToken();
333  } else {
334  sanitized = token;
335  }
336 
337  return sanitized;
338 }
339 
344 QByteArray CSRFProtectionPrivate::getToken(Context *c)
345 {
346  QByteArray token;
347 
348  if (!csrf) {
349  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
350  return token;
351  }
352 
353  if (csrf->d_ptr->useSessions) {
354  token = Session::value(c, QStringLiteral(CSRF_SESSION_KEY)).toByteArray();
355  } else {
356  QByteArray cookieToken = c->req()->cookie(csrf->d_ptr->cookieName).toLatin1();
357 
358  if (cookieToken.isEmpty()) {
359  return token;
360  }
361 
362  token = CSRFProtectionPrivate::sanitizeToken(cookieToken);
363  if (token != cookieToken) {
364  c->setStash(CONTEXT_CSRF_COOKIE_NEEDS_RESET, true);
365  }
366  }
367 
368  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from %s.", token.constData(), csrf->d_ptr->useSessions ? "session" : "cookie");
369 
370  return token;
371 }
372 
377 void CSRFProtectionPrivate::setToken(Context *c)
378 {
379  if (!csrf) {
380  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
381  return;
382  }
383 
384  if (csrf->d_ptr->useSessions) {
385  Session::setValue(c, QStringLiteral(CSRF_SESSION_KEY), c->stash(CONTEXT_CSRF_COOKIE).toByteArray());
386  } else {
387  QNetworkCookie cookie(csrf->d_ptr->cookieName.toLatin1(), c->stash(CONTEXT_CSRF_COOKIE).toByteArray());
388  if (!csrf->d_ptr->cookieDomain.isEmpty()) {
389  cookie.setDomain(csrf->d_ptr->cookieDomain);
390  }
391  cookie.setExpirationDate(QDateTime::currentDateTime().addSecs(csrf->d_ptr->cookieAge));
392  cookie.setHttpOnly(csrf->d_ptr->cookieHttpOnly);
393  cookie.setPath(csrf->d_ptr->cookiePath);
394  cookie.setSecure(csrf->d_ptr->cookieSecure);
395  c->res()->setCookie(cookie);
396  c->res()->headers().pushHeader(QStringLiteral("Vary"), QStringLiteral("Cookie"));
397  }
398 
399  qCDebug(C_CSRFPROTECTION, "Set token \"%s\" to %s.", c->stash(CONTEXT_CSRF_COOKIE).toByteArray().constData(), csrf->d_ptr->useSessions ? "session" : "cookie");
400 }
401 
407 void CSRFProtectionPrivate::reject(Context *c, const QString &logReason, const QString &displayReason)
408 {
409  c->setStash(CONTEXT_CSRF_CHECK_PASSED, false);
410 
411  if (!csrf) {
412  qCCritical(C_CSRFPROTECTION) << "CSRFProtection plugin not registered";
413  return;
414  }
415 
416  qCWarning(C_CSRFPROTECTION, "Forbidden: (%s): /%s [%s]", qPrintable(logReason), qPrintable(c->req()->path()), csrf->d_ptr->logFailedIp ? qPrintable(c->req()->addressString()) : "IP logging disabled");
417 
418  c->res()->setStatus(Response::Forbidden);
419  c->setStash(csrf->d_ptr->errorMsgStashKey, displayReason);
420 
421  QString detachToCsrf = c->action()->attribute(QStringLiteral("CSRFDetachTo"));
422  if (detachToCsrf.isEmpty()) {
423  detachToCsrf = csrf->d_ptr->defaultDetachTo;
424  }
425 
426  Action *detachToAction = nullptr;
427 
428  if (!detachToCsrf.isEmpty()) {
429  detachToAction = c->controller()->actionFor(detachToCsrf);
430  if (!detachToAction) {
431  detachToAction = c->dispatcher()->getActionByPath(detachToCsrf);
432  }
433  if (!detachToAction) {
434  qCWarning(C_CSRFPROTECTION, "Can not find action for \"%s\" to detach to.", qPrintable(detachToCsrf));
435  }
436  }
437 
438  if (detachToAction) {
439  c->detach(detachToAction);
440  } else {
441  c->res()->setStatus(403);
442  if (!csrf->d_ptr->genericErrorMessage.isEmpty()) {
443  c->res()->setBody(csrf->d_ptr->genericErrorMessage);
444  c->res()->setContentType(csrf->d_ptr->genericContentType);
445  } else {
446  const QString title = c->translate("Cutelyst::CSRFProtection", "403 Forbidden - CSRF protection check failed");
447  c->res()->setBody(QStringLiteral("<!DOCTYPE html>\n"
448  "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
449  " <head>\n"
450  " <title>") + title +
451  QStringLiteral("</title>\n"
452  " </head>\n"
453  " <body>\n"
454  " <h1>") + title +
455  QStringLiteral("</h1>\n"
456  " <p>") + displayReason +
457  QStringLiteral("</p>\n"
458  " </body>\n"
459  "</html>\n"));
460  c->res()->setContentType(QStringLiteral("text/html; charset=utf-8"));
461  }
462  c->detach();
463  }
464 }
465 
466 void CSRFProtectionPrivate::accept(Context *c)
467 {
468  c->setStash(CONTEXT_CSRF_CHECK_PASSED, true);
469  c->setStash(CONTEXT_CSRF_PROCESSING_DONE, true);
470 }
471 
476 bool CSRFProtectionPrivate::compareSaltedTokens(const QByteArray &t1, const QByteArray &t2)
477 {
478  const QByteArray _t1 = CSRFProtectionPrivate::unsaltCipherToken(t1);
479  const QByteArray _t2 = CSRFProtectionPrivate::unsaltCipherToken(t2);
480 
481  // to avoid timing attack
482  int diff = _t1.size() ^ _t2.size();
483  for (int i = 0; i < _t1.size() && i < _t2.size(); i++) {
484  diff |= _t1[i] ^ _t2[i];
485  }
486  return diff == 0;
487 }
488 
493 void CSRFProtectionPrivate::beforeDispatch(Context *c)
494 {
495  if (!csrf) {
496  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRFProtection plugin not registered"), c->translate("Cutelyst::CSRFProtection", "The CSRF protection plugin has not been registered."));
497  return;
498  }
499 
500  const QByteArray csrfToken = CSRFProtectionPrivate::getToken(c);
501  if (!csrfToken.isNull()) {
502  c->setStash(CONTEXT_CSRF_COOKIE, csrfToken);
503  } else {
505  }
506 
507  if (c->stash(CONTEXT_CSRF_PROCESSING_DONE).toBool()) {
508  return;
509  }
510 
511  if (c->action()->attributes().contains(QStringLiteral("CSRFIgnore"))) {
512  qCDebug(C_CSRFPROTECTION, "Action \"%s::%s\" is ignored by the CSRF protection.", qPrintable(c->action()->className()), qPrintable(c->action()->reverse()));
513  return;
514  }
515 
516  if (csrf->d_ptr->ignoredNamespaces.contains(c->action()->ns())) {
517  if (!c->action()->attributes().contains(QStringLiteral("CSRFRequire"))) {
518  qCDebug(C_CSRFPROTECTION, "Namespace \"%s\" is ignored by the CSRF protection.", qPrintable(c->action()->ns()));
519  return;
520  }
521  }
522 
523  // only check the tokens if the method is not secure, e.g. POST
524  // the following methods are secure according to RFC 7231: GET, HEAD, OPTIONS and TRACE
525  if (!CSRFProtectionPrivate::secureMethods.contains(c->req()->method())) {
526 
527  bool ok = true;
528 
529  // Suppose user visits http://example.com/
530  // An active network attacker (man-in-the-middle, MITM) sends a POST form that targets
531  // https://example.com/detonate-bomb/ and submits it via JavaScript.
532  //
533  // The attacker will need to provide a CSRF cookie and token, but that's no problem for a
534  // MITM and the session-independent secret we're using. So the MITM can circumvent the CSRF
535  // protection. This is true for any HTTP connection, but anyone using HTTPS expects better!
536  // For this reason, for https://example.com/ we need additional protection that treats
537  // http://example.com/ as completely untrusted. Under HTTPS, Barth et al. found that the
538  // Referer header is missing for same-domain requests in only about 0.2% of cases or less, so
539  // we can use strict Referer checking.
540  if (c->req()->secure()) {
541  const QString referer = c->req()->headers().referer();
542 
543  if (Q_UNLIKELY(referer.isEmpty())) {
544  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - no Referer"), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - no Referer."));
545  ok = false;
546  } else {
547  const QUrl refererUrl(referer);
548  if (Q_UNLIKELY(!refererUrl.isValid())) {
549  CSRFProtectionPrivate::reject(c, QStringLiteral("Referer checking failed - Referer is malformed"), c->translate("Cutelyst::CSRFProtection", "Referer checking failed - Referer is malformed."));
550  ok = false;
551  } else {
552  if (Q_UNLIKELY(refererUrl.scheme() != QLatin1String("https"))) {
553  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."));
554  ok = false;
555  } else {
556  // If there isn't a CSRF_COOKIE_DOMAIN, require an exact match on host:port.
557  // If not, obey the cookie rules (or those for the session cookie, if we
558  // use sessions
559  const QUrl uri = c->req()->uri();
560  QString goodReferer;
561  if (!csrf->d_ptr->useSessions) {
562  goodReferer = csrf->d_ptr->cookieDomain;
563  }
564  if (goodReferer.isEmpty()) {
565  goodReferer = uri.host();
566  }
567  const int serverPort = uri.port(c->req()->secure() ? 443 : 80);
568  if ((serverPort != 80) && (serverPort != 443)) {
569  goodReferer += QLatin1Char(':') + QString::number(serverPort);
570  }
571 
572  QStringList goodHosts = csrf->d_ptr->trustedOrigins;
573  goodHosts.append(goodReferer);
574 
575  QString refererHost = refererUrl.host();
576  const int refererPort = refererUrl.port(refererUrl.scheme().compare(u"https") == 0 ? 443 : 80);
577  if ((refererPort != 80) && (refererPort != 443)) {
578  refererHost += QLatin1Char(':') + QString::number(refererPort);
579  }
580 
581  bool refererCheck = false;
582  for (int i = 0; i < goodHosts.size(); ++i) {
583  const QString host = goodHosts.at(i);
584  if ((host.startsWith(QLatin1Char('.')) && (refererHost.endsWith(host) || (refererHost == host.mid(1)))) || host == refererHost) {
585  refererCheck = true;
586  break;
587  }
588  }
589 
590  if (Q_UNLIKELY(!refererCheck)) {
591  ok = false;
592  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));
593  }
594  }
595  }
596  }
597  }
598 
599  if (Q_LIKELY(ok)) {
600  if (Q_UNLIKELY(csrfToken.isEmpty())) {
601  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRF cookie not set"), c->translate("Cutelyst::CSRFProtection", "CSRF cookie not set."));
602  ok = false;
603  } else {
604 
605  QByteArray requestCsrfToken;
606  // delete does not have body data
607  if (!c->req()->isDelete()) {
608  if (c->req()->contentType().compare(u"multipart/form-data") == 0) {
609  // everything is an upload, even our token
610  Upload *upload = c->req()->upload(csrf->d_ptr->formInputName);
611  if (upload && upload->size() < 1024 /*FIXME*/) {
612  requestCsrfToken = upload->readAll();
613  }
614  } else
615  requestCsrfToken = c->req()->bodyParam(csrf->d_ptr->formInputName).toLatin1();
616  }
617 
618  if (requestCsrfToken.isEmpty()) {
619  requestCsrfToken = c->req()->header(csrf->d_ptr->headerName).toLatin1();
620  if (Q_LIKELY(!requestCsrfToken.isEmpty())) {
621  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from HTTP header %s.", requestCsrfToken.constData(), qPrintable(csrf->d_ptr->headerName));
622  } else {
623  qCDebug(C_CSRFPROTECTION, "Can not get token from HTTP header or form field.");
624  }
625  } else {
626  qCDebug(C_CSRFPROTECTION, "Got token \"%s\" from form field %s.", requestCsrfToken.constData(), qPrintable(csrf->d_ptr->formInputName));
627  }
628 
629  requestCsrfToken = CSRFProtectionPrivate::sanitizeToken(requestCsrfToken);
630 
631  if (Q_UNLIKELY(!CSRFProtectionPrivate::compareSaltedTokens(requestCsrfToken, csrfToken))) {
632  CSRFProtectionPrivate::reject(c, QStringLiteral("CSRF token missing or incorrect"), c->translate("Cutelyst::CSRFProtection", "CSRF token missing or incorrect."));
633  ok = false;
634  }
635  }
636  }
637 
638  if (Q_LIKELY(ok)) {
639  CSRFProtectionPrivate::accept(c);
640  }
641  }
642 
643  // Set the CSRF cookie even if it's already set, so we renew
644  // the expiry timer.
645 
646  if (!c->stash(CONTEXT_CSRF_COOKIE_NEEDS_RESET).toBool()) {
647  if (c->stash(CONTEXT_CSRF_COOKIE_SET).toBool()) {
648  return;
649  }
650  }
651 
652  if (!c->stash(CONTEXT_CSRF_COOKIE_USED).toBool()) {
653  return;
654  }
655 
656  CSRFProtectionPrivate::setToken(c);
657  c->setStash(CONTEXT_CSRF_COOKIE_SET, true);
658 }
659 
660 #include "moc_csrfprotection.cpp"
ParamsMultiMap attributes() const noexcept
Definition: action.cpp:66
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:410
QByteArray toByteArray() const const
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:215
void setHeaderName(const QString &headerName)
void postForked(Cutelyst::Application *app)
void setContentType(const QString &type)
Definition: response.h:205
void reserve(int size)
char at(int i) const const
Headers & headers() noexcept
bool isDelete() const noexcept
Definition: request.cpp:350
Response * res() const noexcept
Definition: context.cpp:103
const T & at(int i) const const
bool isNull() const const
QString host(QUrl::ComponentFormattingOptions options) const const
bool isEmpty() const const
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
void detach(Action *action=nullptr)
Definition: context.cpp:333
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:37
int port(int defaultPort) const const
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:102
void setGenericErrorContentTyp(const QString &type)
int size() const const
This class represents a Cutelyst Action.
Definition: action.h:34
void setIgnoredNamespaces(const QStringList &namespaces)
Cutelyst Upload handles file upload request
Definition: upload.h:22
void resize(int size)
virtual bool setup(Application *app) override
The Cutelyst Context.
Definition: context.h:38
int indexOf(char ch, int from) const const
QString number(int n, int base)
virtual ~CSRFProtection() override
void append(const T &value)
void setDomain(const QString &domain)
QString addressString() const
Definition: request.cpp:39
void stash(const QVariantHash &unite)
Definition: context.cpp:540
Upload * upload(const QString &name) const
Definition: request.h:563
Headers headers() const noexcept
Definition: request.cpp:308
static bool checkPassed(Context *c)
QVariantMap config(const QString &entity) const
user configuration for the application
Definition: engine.cpp:307
bool isEmpty() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray readAll()
void setDefaultDetachTo(const QString &actionNameOrPath)
QString header(const QString &key) const
Definition: request.h:554
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:471
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition: session.cpp:165
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:43
QByteArray mid(int pos, int len) const const
void setCookieName(const QString &cookieName)
void setUseSessions(bool useSessions)
QByteArray & append(char ch)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString ns() const noexcept
Definition: action.cpp:116
void beforeDispatch(Cutelyst::Context *c)
void setCookieHttpOnly(bool httpOnly)
bool contains(const Key &key, const T &value) const const
void setGenericErrorMessage(const QString &message)
QByteArray left(int len) const const
QDateTime currentDateTime()
QByteArray toLatin1() const const
QString mid(int position, int n) const const
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:150
void setErrorMsgStashKey(const QString &keyName)
QString fromLatin1(const char *str, int size)
QString cookie(const QString &name) const
Definition: request.cpp:272
The Cutelyst Application.
Definition: application.h:42
Engine * engine() const noexcept
CSRFProtection(Application *parent)
QString bodyParam(const QString &key, const QString &defaultValue={}) const
Definition: request.h:530
void setBody(QIODevice *body)
Definition: response.cpp:101
QString attribute(const QString &name, const QString &defaultValue={}) const
Definition: action.cpp:72
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Action * getActionByPath(const QString &path) const
Definition: dispatcher.cpp:220
int compare(const QString &other, Qt::CaseSensitivity cs) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
static QString getTokenFormField(Context *c)
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
static QByteArray getToken(Context *c)
QUuid createUuid()
virtual qint64 size() const override
Definition: upload.cpp:140
QString referer() const
Definition: headers.cpp:310
QString className() const
Definition: action.cpp:84
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:139