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