6#include "sessionstorefile.h"
8#include <Cutelyst/Application>
9#include <Cutelyst/Context>
10#include <Cutelyst/Engine>
11#include <Cutelyst/Response>
13#include <QCoreApplication>
14#include <QHostAddress>
15#include <QLoggingCategory>
20Q_LOGGING_CATEGORY(C_SESSION,
"cutelyst.plugin.session", QtWarningMsg)
22#define SESSION_VALUES QStringLiteral("_c_session_values")
23#define SESSION_EXPIRES QStringLiteral("_c_session_expires")
24#define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
25#define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
26#define SESSION_UPDATED QStringLiteral("_c_session_updated")
27#define SESSION_ID QStringLiteral("_c_session_id")
28#define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
29#define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
30#define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
32static thread_local Session *m_instance =
nullptr;
36 , d_ptr(new SessionPrivate(this))
40Cutelyst::Session::~Session()
51 d->sessionExpires = config.value(
QLatin1String(
"expires"), 7200).toLongLong();
52 d->expiryThreshold = config.value(
QLatin1String(
"expiry_threshold"), 0).toLongLong();
53 d->verifyAddress = config.value(
QLatin1String(
"verify_address"),
false).toBool();
54 d->verifyUserAgent = config.value(
QLatin1String(
"verify_user_agent"),
false).toBool();
55 d->cookieHttpOnly = config.value(
QLatin1String(
"cookie_http_only"),
true).toBool();
56 d->cookieSecure = config.value(
QLatin1String(
"cookie_secure"),
false).toBool();
58 config.value(
QLatin1String(
"cookie_same_site"), QStringLiteral(
"strict")).toString();
59#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
61 d->cookieSameSite = QNetworkCookie::SameSite::Default;
63 d->cookieSameSite = QNetworkCookie::SameSite::None;
65 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
67 d->cookieSameSite = QNetworkCookie::SameSite::Strict;
94 Q_ASSERT_X(d->store,
"Cutelyst::Session::setStorage",
"Session Storage is alread defined");
110 if (Q_UNLIKELY(!m_instance)) {
111 qCCritical(C_SESSION) <<
"Session plugin not registered";
115 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
130 if (Q_UNLIKELY(!m_instance)) {
131 qCCritical(C_SESSION) <<
"Session plugin not registered";
135 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
137 return quint64(SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong()));
148 if (Q_UNLIKELY(!m_instance)) {
149 qCCritical(C_SESSION) <<
"Session plugin not registered";
153 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), timeExp);
158 if (Q_UNLIKELY(!m_instance)) {
159 qCCritical(C_SESSION) <<
"Session plugin not registered";
162 SessionPrivate::deleteSession(m_instance, c, reason);
167 return c->
stash(SESSION_DELETE_REASON).toString();
175 session = SessionPrivate::loadSession(c);
179 ret = session.
toHash().value(key, defaultValue);
189 session = SessionPrivate::loadSession(c);
191 if (Q_UNLIKELY(!m_instance)) {
192 qCCritical(C_SESSION) <<
"Session plugin not registered";
196 SessionPrivate::createSessionIdIfNeeded(
197 m_instance, c, m_instance->d_ptr->sessionExpires);
198 session = SessionPrivate::initializeSessionData(m_instance, c);
202 QVariantHash data = session.
toHash();
203 data.insert(key,
value);
213 session = SessionPrivate::loadSession(c);
215 if (Q_UNLIKELY(!m_instance)) {
216 qCCritical(C_SESSION) <<
"Session plugin not registered";
220 SessionPrivate::createSessionIdIfNeeded(
221 m_instance, c, m_instance->d_ptr->sessionExpires);
222 session = SessionPrivate::initializeSessionData(m_instance, c);
226 QVariantHash data = session.
toHash();
237 session = SessionPrivate::loadSession(c);
239 if (Q_UNLIKELY(!m_instance)) {
240 qCCritical(C_SESSION) <<
"Session plugin not registered";
244 SessionPrivate::createSessionIdIfNeeded(
245 m_instance, c, m_instance->d_ptr->sessionExpires);
246 session = SessionPrivate::initializeSessionData(m_instance, c);
250 QVariantHash data = session.
toHash();
251 for (
const QString &key : keys) {
261 return !SessionPrivate::loadSession(c).isNull();
264QString SessionPrivate::generateSessionId()
272 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
275 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
277 const QString sid = getSessionId(c, sessionName);
279 if (!validateSessionId(sid)) {
280 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
290QString SessionPrivate::getSessionId(
Context *c,
const QString &sessionName)
293 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
296 const QVariant
property = c->
stash(SESSION_ID);
297 if (!property.isNull()) {
298 ret =
property.toString();
302 const QString cookie = c->request()->
cookie(sessionName);
304 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
312QString SessionPrivate::createSessionIdIfNeeded(
Session *session,
Context *c, qint64 expires)
315 const QVariant sid = c->
stash(SESSION_ID);
319 ret = createSessionId(session, c, expires);
324QString SessionPrivate::createSessionId(
Session *session,
Context *c, qint64 expires)
327 const QString sid = generateSessionId();
329 qCDebug(C_SESSION) <<
"Created session" << sid;
332 resetSessionExpires(session, c, sid);
333 setSessionId(session, c, sid);
338void SessionPrivate::_q_saveSession(
Context *c)
341 saveSessionExpires(c);
349 if (Q_UNLIKELY(!m_instance)) {
350 qCCritical(C_SESSION) <<
"Session plugin not registered";
353 saveSessionExpires(c);
355 if (!c->
stash(SESSION_UPDATED).toBool()) {
359 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
362 const QString sid = c->
stash(SESSION_ID).toString();
366void SessionPrivate::deleteSession(
Session *session,
Context *c,
const QString &reason)
368 qCDebug(C_SESSION) <<
"Deleting session" << reason;
370 const QVariant sidVar = c->
stash(SESSION_ID).toString();
372 const QString sid = sidVar.
toString();
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);
381 c->
setStash(SESSION_VALUES, QVariant());
382 c->
setStash(SESSION_ID, QVariant());
383 c->
setStash(SESSION_EXPIRES, QVariant());
385 c->
setStash(SESSION_DELETE_REASON, reason);
388void SessionPrivate::deleteSessionId(
Session *session,
Context *c,
const QString &sid)
390 c->
setStash(SESSION_DELETED_ID,
true);
392#if (QT_VERSION >= QT_VERSION_CHECK(6, 1, 0))
395 updateSessionCuteCookie(
400QVariant SessionPrivate::loadSession(
Context *c)
403 const QVariant
property = c->
stash(SESSION_VALUES);
404 if (!property.isNull()) {
409 if (Q_UNLIKELY(!m_instance)) {
410 qCCritical(C_SESSION) <<
"Session plugin not registered";
415 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
416 if (SessionPrivate::validateSessionId(sid)) {
418 const QVariantHash sessionData =
419 m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session"))
421 c->
setStash(SESSION_VALUES, sessionData);
423 if (m_instance->d_ptr->verifyAddress &&
424 sessionData.contains(QStringLiteral(
"__address")) &&
425 sessionData.
value(QStringLiteral(
"__address")).toString() !=
427 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to address mismatch:"
428 << sessionData.value(QStringLiteral(
"__address")).toString()
430 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
434 if (m_instance->d_ptr->verifyUserAgent &&
435 sessionData.contains(QStringLiteral(
"__user_agent")) &&
436 sessionData.
value(QStringLiteral(
"__user_agent")).toString() !=
437 c->request()->userAgent()) {
438 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to user agent mismatch:"
439 << sessionData.value(QStringLiteral(
"__user_agent")).toString()
440 <<
"!=" << c->request()->userAgent();
441 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
445 qCDebug(C_SESSION) <<
"Restored session" << sid;
454bool SessionPrivate::validateSessionId(
const QString &
id)
456 auto it =
id.constBegin();
457 auto end =
id.constEnd();
460 if ((c >= u
'a' && c <= u
'f') || (c >= u
'0' && c <= u
'9')) {
470qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
472 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
476 const qint64 current = getStoredSessionExpires(session, c, sid);
477 const qint64 cutoff = current - threshold;
480 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
481 qint64 updated = calculateInitialSessionExpires(session, c, sid);
482 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
483 extendSessionId(session, c, sid, updated);
495 SessionPrivate::getStoredSessionExpires(
Session *session,
Context *c,
const QString &sessionid)
497 const QVariant expires =
498 session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
502QVariant SessionPrivate::initializeSessionData(
Session *session,
Context *c)
506 ret.insert(QStringLiteral(
"__created"), now);
507 ret.insert(QStringLiteral(
"__updated"), now);
509 if (session->d_ptr->verifyAddress) {
510 ret.insert(QStringLiteral(
"__address"), c->request()->
address().
toString());
513 if (session->d_ptr->verifyUserAgent) {
514 ret.insert(QStringLiteral(
"__user_agent"), c->request()->userAgent());
520void SessionPrivate::saveSessionExpires(
Context *c)
522 const QVariant expires = c->
stash(SESSION_EXPIRES);
526 if (Q_UNLIKELY(!m_instance)) {
527 qCCritical(C_SESSION) <<
"Session plugin not registered";
531 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
533 if (extended > current) {
534 m_instance->d_ptr->store->storeSessionData(
535 c, sid, QStringLiteral(
"expires"), extended);
541QVariant SessionPrivate::loadSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
544 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
545 ret = c->
stash(SESSION_EXPIRES);
548 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
551 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
554 c->
setStash(SESSION_EXPIRES, expires);
557 deleteSession(session, c, QStringLiteral(
"session expired"));
564qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
567 const qint64 expires = qint64(session->d_ptr->sessionExpires);
571qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
573 const QString &sessionId)
575 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
576 const qint64 initial = initialSessionExpires(session, c);
577 return qMax(initial, stored);
580qint64 SessionPrivate::resetSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
582 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
588 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
589 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
594void SessionPrivate::updateSessionCookie(
Context *c,
const QNetworkCookie &updated)
599#if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
600void SessionPrivate::updateSessionCuteCookie(
Context *c,
const Cookie &updated)
602 c->
response()->setCuteCookie(updated);
606QNetworkCookie SessionPrivate::makeSessionCookie(
Session *session,
609 const QDateTime &expires)
612 QNetworkCookie cookie(session->d_ptr->sessionName.toLatin1(), sid.
toLatin1());
613 cookie.setPath(QStringLiteral(
"/"));
614 cookie.setExpirationDate(expires);
615 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
616 cookie.setSecure(session->d_ptr->cookieSecure);
617#if (QT_VERSION >= QT_VERSION_CHECK(6, 1, 0))
618 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
624#if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
625Cookie SessionPrivate::makeSessionCuteCookie(
Session *session,
628 const QDateTime &expires)
631 Cookie cookie(session->d_ptr->sessionName.toLatin1(), sid.
toLatin1());
632 cookie.setPath(QStringLiteral(
"/"));
633 cookie.setExpirationDate(expires);
634 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
635 cookie.setSecure(session->d_ptr->cookieSecure);
636 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
642void SessionPrivate::extendSessionId(
Session *session,
647#if (QT_VERSION >= QT_VERSION_CHECK(6, 1, 0))
651 updateSessionCuteCookie(
656void SessionPrivate::setSessionId(
Session *session,
Context *c,
const QString &sid)
658#if (QT_VERSION >= QT_VERSION_CHECK(6, 1, 0))
659 updateSessionCookie(c,
660 makeSessionCookie(session,
664 initialSessionExpires(session, c) * 1000)));
666 updateSessionCuteCookie(c,
667 makeSessionCuteCookie(session,
671 initialSessionExpires(session, c) * 1000)));
680#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
user configuration for the application
Plugin(Application *parent)
QString cookie(const QString &name) const
QHostAddress address() const noexcept
void setCookie(const QNetworkCookie &cookie)
SessionStore(QObject *parent=nullptr)
virtual bool storeSessionData(Context *c, const QString &sid, const QString &key, const QVariant &value)=0
static void deleteSession(Context *c, const QString &reason=QString())
static QString deleteReason(Context *c)
virtual bool setup(Application *app) final
Session(Application *parent)
static QString id(Context *c)
static bool isValid(Context *c)
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
static void setValue(Context *c, const QString &key, const QVariant &value)
static void changeExpires(Context *c, quint64 expires)
SessionStore * storage() const
void setStorage(SessionStore *store)
static void deleteValue(Context *c, const QString &key)
static quint64 expires(Context *c)
static void deleteValues(Context *c, const QStringList &keys)
The Cutelyst namespace holds all public Cutelyst API.
QDateTime currentDateTimeUtc()
qint64 currentMSecsSinceEpoch()
QDateTime fromMSecsSinceEpoch(qint64 msecs)
QString toString() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
void setParent(QObject *parent)
int compare(const QString &other, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
QByteArray toLatin1() const const
bool isNull() const const
QHash< QString, QVariant > toHash() const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const