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