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> 22 Q_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 35 thread_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");
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();
265 QByteArray SessionPrivate::generateSessionId()
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);
339 void 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');
457 qint64 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);
481 qint64 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());
507 void 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);
551 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
554 const qint64 expires = session->d_ptr->sessionExpires;
558 qint64 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);
603 void SessionPrivate::extendSessionId(
Session *session,
608 updateSessionCookie(c,
622 return loadedConfig.
value(key, defaultConfig.value(key, defaultValue));
630 #include "moc_session.cpp" qlonglong toLongLong(bool *ok) const const
QByteArray toByteArray() const const
void setCookie(const QNetworkCookie &cookie)
QHash< QString, QVariant > toHash() const const
void postForked(Cutelyst::Application *app)
QByteArray toHex(char separator) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isEmpty() const const
void setStash(const QString &key, const QVariant &value)
QString toString() const const
QHostAddress address() const noexcept
bool isNull() const const
void stash(const QVariantHash &unite)
void setStorage(std::unique_ptr< SessionStore > store)
QVariantMap config(const QString &entity) const
static bool isValid(Context *c)
static void setValue(Context *c, const QString &key, const QVariant &value)
The Cutelyst namespace holds all public Cutelyst API.
static QByteArray id(Context *c)
Session(Application *parent)
void setParent(QObject *parent)
static void changeExpires(Context *c, qint64 expires)
SessionStore * storage() const
static QString deleteReason(Context *c)
static void deleteValue(Context *c, const QString &key)
void afterDispatch(Cutelyst::Context *c)
Abstract class to create a session store.
QByteArray toLatin1() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QByteArray cookie(QAnyStringView name) const
static void deleteValues(Context *c, const QStringList &keys)
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
virtual bool setup(Application *app) final
Base class for Cutelyst Plugins.
The Cutelyst application.
SessionStore(QObject *parent=nullptr)
Plugin providing methods for session management.
Engine * engine() const noexcept
qint64 currentSecsSinceEpoch()
qsizetype size() const const
Response * response() const noexcept
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
static void deleteSession(Context *c, const QString &reason={})
QByteArray toRfc4122() const const
QDateTime currentDateTimeUtc()
QString applicationName()
static qint64 expires(Context *c)