6#include "sessionstorefile.h"
9#include <Cutelyst/Application>
10#include <Cutelyst/Context>
11#include <Cutelyst/Engine>
12#include <Cutelyst/Response>
14#include <QCoreApplication>
15#include <QHostAddress>
16#include <QLoggingCategory>
22Q_LOGGING_CATEGORY(C_SESSION,
"cutelyst.plugin.session", QtWarningMsg)
24#define SESSION_VALUES QStringLiteral("_c_session_values")
25#define SESSION_EXPIRES QStringLiteral("_c_session_expires")
26#define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
27#define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
28#define SESSION_UPDATED QStringLiteral("_c_session_updated")
29#define SESSION_ID QStringLiteral("_c_session_id")
30#define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
31#define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
32#define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
35thread_local Session *m_instance =
nullptr;
40 , d_ptr(new SessionPrivate(this))
46 , d_ptr(new SessionPrivate(this))
48 d_ptr->defaultConfig = defaultConfig;
61 d->loadedConfig = app->
engine()->
config(u
"Cutelyst_Session_Plugin"_s);
62 d->sessionExpires = std::chrono::duration_cast<std::chrono::seconds>(
65 d->expiryThreshold = d->config(u
"expiry_threshold"_s, 0).toLongLong();
66 d->verifyAddress = d->config(u
"verify_address"_s,
false).toBool();
67 d->verifyUserAgent = d->config(u
"verify_user_agent"_s,
false).toBool();
68 d->cookieHttpOnly = d->config(u
"cookie_http_only"_s,
true).toBool();
69 d->cookieSecure = d->config(u
"cookie_secure"_s,
false).toBool();
71 const QString _sameSite = d->config(u
"cookie_same_site"_s, u
"strict"_s).toString();
73 d->cookieSameSite = QNetworkCookie::SameSite::Default;
75 d->cookieSameSite = QNetworkCookie::SameSite::None;
77 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
79 d->cookieSameSite = QNetworkCookie::SameSite::Strict;
86 d->store = std::make_unique<SessionStoreFile>(
this);
95 Q_ASSERT_X(d->store,
"Cutelyst::Session::setStorage",
"Session Storage is alread defined");
96 store->setParent(
this);
97 d->store = std::move(store);
103 return d->store.get();
111 if (Q_UNLIKELY(!m_instance)) {
112 qCCritical(C_SESSION) <<
"Session plugin not registered";
116 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
131 if (Q_UNLIKELY(!m_instance)) {
132 qCCritical(C_SESSION) <<
"Session plugin not registered";
136 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
138 return SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong());
149 if (Q_UNLIKELY(!m_instance)) {
150 qCCritical(C_SESSION) <<
"Session plugin not registered";
154 m_instance->d_ptr->store->storeSessionData(c, sid, u
"expires"_s, timeExp);
159 if (Q_UNLIKELY(!m_instance)) {
160 qCCritical(C_SESSION) <<
"Session plugin not registered";
163 SessionPrivate::deleteSession(m_instance, c, reason);
168 return c->
stash(SESSION_DELETE_REASON).toString();
176 session = SessionPrivate::loadSession(c);
180 ret = session.
toHash().value(key, defaultValue);
190 session = SessionPrivate::loadSession(c);
192 if (Q_UNLIKELY(!m_instance)) {
193 qCCritical(C_SESSION) <<
"Session plugin not registered";
197 SessionPrivate::createSessionIdIfNeeded(
198 m_instance, c, m_instance->d_ptr->sessionExpires);
199 session = SessionPrivate::initializeSessionData(m_instance, c);
203 QVariantHash data = session.
toHash();
204 data.insert(key,
value);
214 session = SessionPrivate::loadSession(c);
216 if (Q_UNLIKELY(!m_instance)) {
217 qCCritical(C_SESSION) <<
"Session plugin not registered";
221 SessionPrivate::createSessionIdIfNeeded(
222 m_instance, c, m_instance->d_ptr->sessionExpires);
223 session = SessionPrivate::initializeSessionData(m_instance, c);
227 QVariantHash data = session.
toHash();
238 session = SessionPrivate::loadSession(c);
240 if (Q_UNLIKELY(!m_instance)) {
241 qCCritical(C_SESSION) <<
"Session plugin not registered";
245 SessionPrivate::createSessionIdIfNeeded(
246 m_instance, c, m_instance->d_ptr->sessionExpires);
247 session = SessionPrivate::initializeSessionData(m_instance, c);
251 QVariantHash data = session.
toHash();
252 for (
const QString &key : keys) {
262 return !SessionPrivate::loadSession(c).isNull();
273 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
276 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
278 const QByteArray sid = getSessionId(c, sessionName);
280 if (!validateSessionId(sid)) {
281 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
294 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
298 if (!property.isNull()) {
299 ret =
property.toByteArray();
305 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
320 ret = createSessionId(session, c, expires);
328 const auto sid = generateSessionId();
330 qCDebug(C_SESSION) <<
"Created session" << sid;
333 resetSessionExpires(session, c, sid);
334 setSessionId(session, c, sid);
339void SessionPrivate::_q_saveSession(
Context *c)
342 saveSessionExpires(c);
350 if (Q_UNLIKELY(!m_instance)) {
351 qCCritical(C_SESSION) <<
"Session plugin not registered";
354 saveSessionExpires(c);
356 if (!c->
stash(SESSION_UPDATED).toBool()) {
359 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
362 const auto sid = c->
stash(SESSION_ID).toByteArray();
363 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"session"), sessionData);
368 qCDebug(C_SESSION) <<
"Deleting session" << reason;
373 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
374 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
375 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
377 deleteSessionId(session, c, sid);
385 c->
setStash(SESSION_DELETE_REASON, reason);
390 c->
setStash(SESSION_DELETED_ID,
true);
399 if (!property.isNull()) {
404 if (Q_UNLIKELY(!m_instance)) {
405 qCCritical(C_SESSION) <<
"Session plugin not registered";
410 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
411 if (SessionPrivate::validateSessionId(sid)) {
413 const QVariantHash sessionData =
414 m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session"))
416 c->
setStash(SESSION_VALUES, sessionData);
418 if (m_instance->d_ptr->verifyAddress) {
419 auto it = sessionData.constFind(u
"__address"_s);
420 if (it != sessionData.constEnd() &&
423 <<
"Deleting session" << sid <<
"due to address mismatch:" << *it
425 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
430 if (m_instance->d_ptr->verifyUserAgent) {
431 auto it = sessionData.constFind(u
"__user_agent"_s);
432 if (it != sessionData.constEnd() &&
433 it->toByteArray() != c->
request()->userAgent()) {
435 <<
"Deleting session" << sid <<
"due to user agent mismatch:" << *it
436 <<
"!=" << c->
request()->userAgent();
437 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
442 qCDebug(C_SESSION) <<
"Restored session" << sid <<
"keys" << sessionData.
size();
454 if ((c >=
'a' && c <=
'f') || (c >=
'0' && c <=
'9')) {
463qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
465 const qint64 threshold = session->d_ptr->expiryThreshold;
469 const qint64 current = getStoredSessionExpires(session, c, sid);
470 const qint64 cutoff = current - threshold;
473 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
474 qint64 updated = calculateInitialSessionExpires(session, c, sid);
475 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
476 extendSessionId(session, c, sid, updated);
487qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
492 session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
500 ret.insert(QStringLiteral(
"__created"), now);
501 ret.insert(QStringLiteral(
"__updated"), now);
503 if (session->d_ptr->verifyAddress) {
507 if (session->d_ptr->verifyUserAgent) {
508 ret.insert(QStringLiteral(
"__user_agent"), c->
request()->userAgent());
514void SessionPrivate::saveSessionExpires(
Context *c)
520 if (Q_UNLIKELY(!m_instance)) {
521 qCCritical(C_SESSION) <<
"Session plugin not registered";
525 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
527 if (extended > current) {
528 m_instance->d_ptr->store->storeSessionData(
529 c, sid, QStringLiteral(
"expires"), extended);
539 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
540 ret = c->
stash(SESSION_EXPIRES);
543 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
546 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
549 c->
setStash(SESSION_EXPIRES, expires);
552 deleteSession(session, c, QStringLiteral(
"session expired"));
559qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
562 const qint64 expires = session->d_ptr->sessionExpires;
566qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
570 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
571 const qint64 initial = initialSessionExpires(session, c);
572 return qMax(initial, stored);
578 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
584 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
585 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
602 cookie.setPath(u
"/"_s);
603 cookie.setExpirationDate(expires);
604 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
605 cookie.setSecure(session->d_ptr->cookieSecure);
606 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
611void SessionPrivate::extendSessionId(
Session *session,
616 updateSessionCookie(c,
630 return loadedConfig.
value(key, defaultConfig.value(key, defaultValue));
638#include "moc_session.cpp"
The Cutelyst application.
Engine * engine() const noexcept
void afterDispatch(Cutelyst::Context *c)
void postForked(Cutelyst::Application *app)
void stash(const QVariantHash &unite)
void setStash(const QString &key, const QVariant &value)
Response * response() const noexcept
QVariantMap config(const QString &entity) const
Base class for Cutelyst Plugins.
QByteArray cookie(QAnyStringView name) const
QHostAddress address() const noexcept
void setCookie(const QNetworkCookie &cookie)
Abstract class to create a session store.
SessionStore(QObject *parent=nullptr)
Plugin providing methods for session management.
static qint64 expires(Context *c)
static QString deleteReason(Context *c)
virtual bool setup(Application *app) final
Session(Application *parent)
static bool isValid(Context *c)
static void deleteSession(Context *c, const QString &reason={})
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
static void setValue(Context *c, const QString &key, const QVariant &value)
void setStorage(std::unique_ptr< SessionStore > store)
static QByteArray id(Context *c)
SessionStore * storage() const
static void deleteValue(Context *c, const QString &key)
static void deleteValues(Context *c, const QStringList &keys)
static void changeExpires(Context *c, qint64 expires)
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
The Cutelyst namespace holds all public Cutelyst API.
bool isEmpty() const const
qsizetype size() const const
QByteArray toHex(char separator) const const
QDateTime currentDateTimeUtc()
qint64 currentSecsSinceEpoch()
QDateTime fromSecsSinceEpoch(qint64 secs)
QString toString() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QByteArray toRfc4122() const const
bool isNull() const const
QByteArray toByteArray() const const
QHash< QString, QVariant > toHash() const const
qlonglong toLongLong(bool *ok) const const