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 u"_c_session_values"_s
25#define SESSION_EXPIRES u"_c_session_expires"_s
26#define SESSION_TRIED_LOADING_EXPIRES u"_c_session_tried_loading_expires"_s
27#define SESSION_EXTENDED_EXPIRES u"_c_session_extended_expires"_s
28#define SESSION_UPDATED u"_c_session_updated"_s
29#define SESSION_ID u"_c_session_id"_s
30#define SESSION_TRIED_LOADING_ID u"_c_session_tried_loading_id"_s
31#define SESSION_DELETED_ID u"_c_session_deleted_id"_s
32#define SESSION_DELETE_REASON u"_c_session_delete_reason"_s
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;
291QByteArray SessionPrivate::getSessionId(
const Context *c,
const QByteArray &sessionName)
294 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
297 const QVariant
property = c->
stash(SESSION_ID);
298 if (!property.isNull()) {
299 ret =
property.toByteArray();
305 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
313QByteArray SessionPrivate::createSessionIdIfNeeded(
Session *session,
Context *c, qint64 expires)
316 const QVariant sid = c->
stash(SESSION_ID);
320 ret = createSessionId(session, c, expires);
325QByteArray SessionPrivate::createSessionId(
Session *session,
Context *c, qint64 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, u
"session"_s, sessionData);
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();
373 session->d_ptr->store->deleteSessionData(c, sid, u
"session"_s);
374 session->d_ptr->store->deleteSessionData(c, sid, u
"expires"_s);
375 session->d_ptr->store->deleteSessionData(c, sid, u
"flash"_s);
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 QByteArray &sid)
390 c->
setStash(SESSION_DELETED_ID,
true);
395QVariant SessionPrivate::loadSession(
Context *c)
398 const QVariant
property = c->
stash(SESSION_VALUES);
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, u
"session"_s).toHash();
415 c->
setStash(SESSION_VALUES, sessionData);
417 if (m_instance->d_ptr->verifyAddress) {
418 auto it = sessionData.constFind(u
"__address"_s);
419 if (it != sessionData.constEnd() &&
422 <<
"Deleting session" << sid <<
"due to address mismatch:" << *it
424 deleteSession(m_instance, c, u
"address mismatch"_s);
429 if (m_instance->d_ptr->verifyUserAgent) {
430 auto it = sessionData.constFind(u
"__user_agent"_s);
431 if (it != sessionData.constEnd() &&
432 it->toByteArray() != c->
request()->userAgent()) {
434 <<
"Deleting session" << sid <<
"due to user agent mismatch:" << *it
435 <<
"!=" << c->
request()->userAgent();
436 deleteSession(m_instance, c, u
"user agent mismatch"_s);
441 qCDebug(C_SESSION) <<
"Restored session" << sid <<
"keys" << sessionData.
size();
450bool SessionPrivate::validateSessionId(QByteArrayView
id)
452 return !
id.empty() && std::ranges::all_of(
id, [](
char c) {
453 return (c >=
'a' && c <=
'f') || (c >=
'0' && c <=
'9');
457qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
459 const qint64 threshold = session->d_ptr->expiryThreshold;
463 const qint64 current = getStoredSessionExpires(session, c, sid);
464 const qint64 cutoff = current - threshold;
467 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
468 qint64 updated = calculateInitialSessionExpires(session, c, sid);
469 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
470 extendSessionId(session, c, sid, updated);
481qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
483 const QByteArray &sessionid)
485 const QVariant expires = session->d_ptr->store->getSessionData(c, sessionid, u
"expires"_s, 0);
489QVariant SessionPrivate::initializeSessionData(
const Session *session,
const Context *c)
493 ret.insert(u
"__created"_s, now);
494 ret.insert(u
"__updated"_s, now);
496 if (session->d_ptr->verifyAddress) {
500 if (session->d_ptr->verifyUserAgent) {
501 ret.insert(u
"__user_agent"_s, c->
request()->userAgent());
507void SessionPrivate::saveSessionExpires(
Context *c)
509 const QVariant expires = c->
stash(SESSION_EXPIRES);
513 if (Q_UNLIKELY(!m_instance)) {
514 qCCritical(C_SESSION) <<
"Session plugin not registered";
518 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
520 if (extended > current) {
521 m_instance->d_ptr->store->storeSessionData(c, sid, u
"expires"_s, extended);
528 SessionPrivate::loadSessionExpires(
Session *session,
Context *c,
const QByteArray &sessionId)
531 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
532 ret = c->
stash(SESSION_EXPIRES);
535 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
538 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
541 c->
setStash(SESSION_EXPIRES, expires);
544 deleteSession(session, c, u
"session expired"_s);
551qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
554 const qint64 expires = session->d_ptr->sessionExpires;
558qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
560 const QByteArray &sessionId)
562 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
563 const qint64 initial = initialSessionExpires(session, c);
564 return qMax(initial, stored);
568 SessionPrivate::resetSessionExpires(
Session *session,
Context *c,
const QByteArray &sessionId)
570 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
576 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
577 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
582void SessionPrivate::updateSessionCookie(
const Context *c,
const QNetworkCookie &updated)
587QNetworkCookie SessionPrivate::makeSessionCookie(
Session *session,
589 const QByteArray &sid,
590 const QDateTime &expires)
593 QNetworkCookie cookie(session->d_ptr->sessionName, sid);
594 cookie.setPath(u
"/"_s);
595 cookie.setExpirationDate(expires);
596 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
597 cookie.setSecure(session->d_ptr->cookieSecure);
598 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
603void SessionPrivate::extendSessionId(
Session *session,
605 const QByteArray &sid,
608 updateSessionCookie(c,
612void SessionPrivate::setSessionId(
Session *session,
Context *c,
const QByteArray &sid)
620QVariant SessionPrivate::config(
const QString &key,
const QVariant &defaultValue)
const
622 return loadedConfig.value(key, defaultConfig.value(key, defaultValue));
630#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
Plugin(Application *parent)
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)
QObject * parent() const const
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