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;
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, u
"session"_s, sessionData);
368 qCDebug(C_SESSION) <<
"Deleting session" << reason;
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);
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, 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();
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,
485 const QVariant expires = session->d_ptr->store->getSessionData(c, sessionid, u
"expires"_s, 0);
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)
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);
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,
562 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
563 const qint64 initial = initialSessionExpires(session, c);
564 return qMax(initial, stored);
570 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
576 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
577 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
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,
608 updateSessionCookie(c,
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
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