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>
21Q_LOGGING_CATEGORY(C_SESSION,
"cutelyst.plugin.session", QtWarningMsg)
23#define SESSION_VALUES QStringLiteral("_c_session_values")
24#define SESSION_EXPIRES QStringLiteral("_c_session_expires")
25#define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
26#define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
27#define SESSION_UPDATED QStringLiteral("_c_session_updated")
28#define SESSION_ID QStringLiteral("_c_session_id")
29#define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
30#define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
31#define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
33static thread_local Session *m_instance =
nullptr;
37 , d_ptr(new SessionPrivate(this))
43 , d_ptr(new SessionPrivate(this))
45 d_ptr->defaultConfig = defaultConfig;
58 d->loadedConfig = app->
engine()->
config(u
"Cutelyst_Session_Plugin"_qs);
59 d->sessionExpires = std::chrono::duration_cast<std::chrono::seconds>(
62 d->expiryThreshold = d->config(u
"expiry_threshold"_qs, 0).toLongLong();
63 d->verifyAddress = d->config(u
"verify_address"_qs,
false).toBool();
64 d->verifyUserAgent = d->config(u
"verify_user_agent"_qs,
false).toBool();
65 d->cookieHttpOnly = d->config(u
"cookie_http_only"_qs,
true).toBool();
66 d->cookieSecure = d->config(u
"cookie_secure"_qs,
false).toBool();
68 const QString _sameSite = d->config(u
"cookie_same_site"_qs, u
"strict"_qs).toString();
70 d->cookieSameSite = QNetworkCookie::SameSite::Default;
72 d->cookieSameSite = QNetworkCookie::SameSite::None;
74 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
76 d->cookieSameSite = QNetworkCookie::SameSite::Strict;
83 d->store = std::make_unique<SessionStoreFile>(
this);
92 Q_ASSERT_X(d->store,
"Cutelyst::Session::setStorage",
"Session Storage is alread defined");
93 store->setParent(
this);
94 d->store = std::move(store);
100 return d->store.get();
108 if (Q_UNLIKELY(!m_instance)) {
109 qCCritical(C_SESSION) <<
"Session plugin not registered";
113 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
128 if (Q_UNLIKELY(!m_instance)) {
129 qCCritical(C_SESSION) <<
"Session plugin not registered";
133 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
135 return quint64(SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong()));
146 if (Q_UNLIKELY(!m_instance)) {
147 qCCritical(C_SESSION) <<
"Session plugin not registered";
151 m_instance->d_ptr->store->storeSessionData(c, sid, u
"expires"_qs, timeExp);
156 if (Q_UNLIKELY(!m_instance)) {
157 qCCritical(C_SESSION) <<
"Session plugin not registered";
160 SessionPrivate::deleteSession(m_instance, c, reason);
165 return c->
stash(SESSION_DELETE_REASON).toString();
173 session = SessionPrivate::loadSession(c);
177 ret = session.
toHash().value(key, defaultValue);
187 session = SessionPrivate::loadSession(c);
189 if (Q_UNLIKELY(!m_instance)) {
190 qCCritical(C_SESSION) <<
"Session plugin not registered";
194 SessionPrivate::createSessionIdIfNeeded(
195 m_instance, c, m_instance->d_ptr->sessionExpires);
196 session = SessionPrivate::initializeSessionData(m_instance, c);
200 QVariantHash data = session.
toHash();
201 data.insert(key,
value);
211 session = SessionPrivate::loadSession(c);
213 if (Q_UNLIKELY(!m_instance)) {
214 qCCritical(C_SESSION) <<
"Session plugin not registered";
218 SessionPrivate::createSessionIdIfNeeded(
219 m_instance, c, m_instance->d_ptr->sessionExpires);
220 session = SessionPrivate::initializeSessionData(m_instance, c);
224 QVariantHash data = session.
toHash();
235 session = SessionPrivate::loadSession(c);
237 if (Q_UNLIKELY(!m_instance)) {
238 qCCritical(C_SESSION) <<
"Session plugin not registered";
242 SessionPrivate::createSessionIdIfNeeded(
243 m_instance, c, m_instance->d_ptr->sessionExpires);
244 session = SessionPrivate::initializeSessionData(m_instance, c);
248 QVariantHash data = session.
toHash();
249 for (
const QString &key : keys) {
259 return !SessionPrivate::loadSession(c).isNull();
270 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
273 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
275 const QByteArray sid = getSessionId(c, sessionName);
277 if (!validateSessionId(sid)) {
278 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
291 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
295 if (!property.isNull()) {
296 ret =
property.toByteArray();
302 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
317 ret = createSessionId(session, c, expires);
325 const auto sid = generateSessionId();
327 qCDebug(C_SESSION) <<
"Created session" << sid;
330 resetSessionExpires(session, c, sid);
331 setSessionId(session, c, sid);
336void SessionPrivate::_q_saveSession(
Context *c)
339 saveSessionExpires(c);
347 if (Q_UNLIKELY(!m_instance)) {
348 qCCritical(C_SESSION) <<
"Session plugin not registered";
351 saveSessionExpires(c);
353 if (!c->
stash(SESSION_UPDATED).toBool()) {
356 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
359 const auto sid = c->
stash(SESSION_ID).toByteArray();
360 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"session"), sessionData);
365 qCDebug(C_SESSION) <<
"Deleting session" << reason;
370 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
371 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
372 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
374 deleteSessionId(session, c, sid);
382 c->
setStash(SESSION_DELETE_REASON, reason);
387 c->
setStash(SESSION_DELETED_ID,
true);
396 if (!property.isNull()) {
401 if (Q_UNLIKELY(!m_instance)) {
402 qCCritical(C_SESSION) <<
"Session plugin not registered";
407 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
408 if (SessionPrivate::validateSessionId(sid)) {
410 const QVariantHash sessionData =
411 m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session"))
413 c->
setStash(SESSION_VALUES, sessionData);
415 if (m_instance->d_ptr->verifyAddress) {
416 auto it = sessionData.constFind(u
"__address"_qs);
417 if (it != sessionData.constEnd() &&
420 <<
"Deleting session" << sid <<
"due to address mismatch:" << *it
422 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
427 if (m_instance->d_ptr->verifyUserAgent) {
428 auto it = sessionData.constFind(u
"__user_agent"_qs);
429 if (it != sessionData.constEnd() &&
430 it->toByteArray() != c->
request()->userAgent()) {
432 <<
"Deleting session" << sid <<
"due to user agent mismatch:" << *it
433 <<
"!=" << c->
request()->userAgent();
434 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
439 qCDebug(C_SESSION) <<
"Restored session" << sid <<
"keys" << sessionData.
size();
450 auto it =
id.begin();
454 if ((c >=
'a' && c <=
'f') || (c >=
'0' && c <=
'9')) {
464qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
466 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
470 const qint64 current = getStoredSessionExpires(session, c, sid);
471 const qint64 cutoff = current - threshold;
474 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
475 qint64 updated = calculateInitialSessionExpires(session, c, sid);
476 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
477 extendSessionId(session, c, sid, updated);
488qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
493 session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
501 ret.insert(QStringLiteral(
"__created"), now);
502 ret.insert(QStringLiteral(
"__updated"), now);
504 if (session->d_ptr->verifyAddress) {
508 if (session->d_ptr->verifyUserAgent) {
509 ret.insert(QStringLiteral(
"__user_agent"), c->
request()->userAgent());
515void SessionPrivate::saveSessionExpires(
Context *c)
521 if (Q_UNLIKELY(!m_instance)) {
522 qCCritical(C_SESSION) <<
"Session plugin not registered";
526 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
528 if (extended > current) {
529 m_instance->d_ptr->store->storeSessionData(
530 c, sid, QStringLiteral(
"expires"), extended);
540 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
541 ret = c->
stash(SESSION_EXPIRES);
544 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
547 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
550 c->
setStash(SESSION_EXPIRES, expires);
553 deleteSession(session, c, QStringLiteral(
"session expired"));
560qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
563 const qint64 expires = qint64(session->d_ptr->sessionExpires);
567qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
571 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
572 const qint64 initial = initialSessionExpires(session, c);
573 return qMax(initial, stored);
579 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
585 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
586 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
603 cookie.setPath(u
"/"_qs);
604 cookie.setExpirationDate(expires);
605 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
606 cookie.setSecure(session->d_ptr->cookieSecure);
607 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
612void SessionPrivate::extendSessionId(
Session *session,
617 updateSessionCookie(c,
631 return loadedConfig.
value(key, defaultConfig.value(key, defaultValue));
639#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(QByteArrayView 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 void deleteSession(Context *c, const QString &reason=QString())
static QString deleteReason(Context *c)
virtual bool setup(Application *app) final
Session(Application *parent)
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)
void setStorage(std::unique_ptr< SessionStore > store)
static void changeExpires(Context *c, quint64 expires)
static QByteArray id(Context *c)
SessionStore * storage() const
static void deleteValue(Context *c, const QString &key)
static quint64 expires(Context *c)
static void deleteValues(Context *c, const QStringList &keys)
CUTELYST_LIBRARY 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, Qt::TimeSpec spec, int offsetSeconds)
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