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 QStringLiteral("_c_session_values")
25#define SESSION_EXPIRES QStringLiteral("_c_session_expires")
26#define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
27#define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
28#define SESSION_UPDATED QStringLiteral("_c_session_updated")
29#define SESSION_ID QStringLiteral("_c_session_id")
30#define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
31#define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
32#define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
34static thread_local Session *m_instance =
nullptr;
38 , d_ptr(new SessionPrivate(this))
44 , d_ptr(new SessionPrivate(this))
46 d_ptr->defaultConfig = defaultConfig;
59 d->loadedConfig = app->
engine()->
config(u
"Cutelyst_Session_Plugin"_s);
60 d->sessionExpires = std::chrono::duration_cast<std::chrono::seconds>(
63 d->expiryThreshold = d->config(u
"expiry_threshold"_s, 0).toLongLong();
64 d->verifyAddress = d->config(u
"verify_address"_s,
false).toBool();
65 d->verifyUserAgent = d->config(u
"verify_user_agent"_s,
false).toBool();
66 d->cookieHttpOnly = d->config(u
"cookie_http_only"_s,
true).toBool();
67 d->cookieSecure = d->config(u
"cookie_secure"_s,
false).toBool();
69 const QString _sameSite = d->config(u
"cookie_same_site"_s, u
"strict"_s).toString();
71 d->cookieSameSite = QNetworkCookie::SameSite::Default;
73 d->cookieSameSite = QNetworkCookie::SameSite::None;
75 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
77 d->cookieSameSite = QNetworkCookie::SameSite::Strict;
84 d->store = std::make_unique<SessionStoreFile>(
this);
93 Q_ASSERT_X(d->store,
"Cutelyst::Session::setStorage",
"Session Storage is alread defined");
94 store->setParent(
this);
95 d->store = std::move(store);
101 return d->store.get();
109 if (Q_UNLIKELY(!m_instance)) {
110 qCCritical(C_SESSION) <<
"Session plugin not registered";
114 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
129 if (Q_UNLIKELY(!m_instance)) {
130 qCCritical(C_SESSION) <<
"Session plugin not registered";
134 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
136 return quint64(SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong()));
147 if (Q_UNLIKELY(!m_instance)) {
148 qCCritical(C_SESSION) <<
"Session plugin not registered";
152 m_instance->d_ptr->store->storeSessionData(c, sid, u
"expires"_s, timeExp);
157 if (Q_UNLIKELY(!m_instance)) {
158 qCCritical(C_SESSION) <<
"Session plugin not registered";
161 SessionPrivate::deleteSession(m_instance, c, reason);
166 return c->
stash(SESSION_DELETE_REASON).toString();
174 session = SessionPrivate::loadSession(c);
178 ret = session.
toHash().value(key, defaultValue);
188 session = SessionPrivate::loadSession(c);
190 if (Q_UNLIKELY(!m_instance)) {
191 qCCritical(C_SESSION) <<
"Session plugin not registered";
195 SessionPrivate::createSessionIdIfNeeded(
196 m_instance, c, m_instance->d_ptr->sessionExpires);
197 session = SessionPrivate::initializeSessionData(m_instance, c);
201 QVariantHash data = session.
toHash();
202 data.insert(key,
value);
212 session = SessionPrivate::loadSession(c);
214 if (Q_UNLIKELY(!m_instance)) {
215 qCCritical(C_SESSION) <<
"Session plugin not registered";
219 SessionPrivate::createSessionIdIfNeeded(
220 m_instance, c, m_instance->d_ptr->sessionExpires);
221 session = SessionPrivate::initializeSessionData(m_instance, c);
225 QVariantHash data = session.
toHash();
236 session = SessionPrivate::loadSession(c);
238 if (Q_UNLIKELY(!m_instance)) {
239 qCCritical(C_SESSION) <<
"Session plugin not registered";
243 SessionPrivate::createSessionIdIfNeeded(
244 m_instance, c, m_instance->d_ptr->sessionExpires);
245 session = SessionPrivate::initializeSessionData(m_instance, c);
249 QVariantHash data = session.
toHash();
250 for (
const QString &key : keys) {
260 return !SessionPrivate::loadSession(c).isNull();
271 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
274 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
276 const QByteArray sid = getSessionId(c, sessionName);
278 if (!validateSessionId(sid)) {
279 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
289QByteArray SessionPrivate::getSessionId(
Context *c,
const QByteArray &sessionName)
292 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
295 const QVariant
property = c->
stash(SESSION_ID);
296 if (!property.isNull()) {
297 ret =
property.toByteArray();
303 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
311QByteArray SessionPrivate::createSessionIdIfNeeded(
Session *session,
Context *c, qint64 expires)
314 const QVariant sid = c->
stash(SESSION_ID);
318 ret = createSessionId(session, c, expires);
323QByteArray SessionPrivate::createSessionId(
Session *session,
Context *c, qint64 expires)
326 const auto sid = generateSessionId();
328 qCDebug(C_SESSION) <<
"Created session" << sid;
331 resetSessionExpires(session, c, sid);
332 setSessionId(session, c, sid);
337void SessionPrivate::_q_saveSession(
Context *c)
340 saveSessionExpires(c);
348 if (Q_UNLIKELY(!m_instance)) {
349 qCCritical(C_SESSION) <<
"Session plugin not registered";
352 saveSessionExpires(c);
354 if (!c->
stash(SESSION_UPDATED).toBool()) {
357 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
360 const auto sid = c->
stash(SESSION_ID).toByteArray();
361 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"session"), sessionData);
364void SessionPrivate::deleteSession(
Session *session,
Context *c,
const QString &reason)
366 qCDebug(C_SESSION) <<
"Deleting session" << reason;
368 const QVariant sidVar = c->
stash(SESSION_ID).toString();
371 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
372 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
373 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
375 deleteSessionId(session, c, sid);
379 c->
setStash(SESSION_VALUES, QVariant());
380 c->
setStash(SESSION_ID, QVariant());
381 c->
setStash(SESSION_EXPIRES, QVariant());
383 c->
setStash(SESSION_DELETE_REASON, reason);
386void SessionPrivate::deleteSessionId(
Session *session,
Context *c,
const QByteArray &sid)
388 c->
setStash(SESSION_DELETED_ID,
true);
393QVariant SessionPrivate::loadSession(
Context *c)
396 const QVariant
property = c->
stash(SESSION_VALUES);
397 if (!property.isNull()) {
402 if (Q_UNLIKELY(!m_instance)) {
403 qCCritical(C_SESSION) <<
"Session plugin not registered";
408 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
409 if (SessionPrivate::validateSessionId(sid)) {
411 const QVariantHash sessionData =
412 m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session"))
414 c->
setStash(SESSION_VALUES, sessionData);
416 if (m_instance->d_ptr->verifyAddress) {
417 auto it = sessionData.constFind(u
"__address"_s);
418 if (it != sessionData.constEnd() &&
421 <<
"Deleting session" << sid <<
"due to address mismatch:" << *it
423 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
428 if (m_instance->d_ptr->verifyUserAgent) {
429 auto it = sessionData.constFind(u
"__user_agent"_s);
430 if (it != sessionData.constEnd() &&
431 it->toByteArray() != c->
request()->userAgent()) {
433 <<
"Deleting session" << sid <<
"due to user agent mismatch:" << *it
434 <<
"!=" << c->
request()->userAgent();
435 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
440 qCDebug(C_SESSION) <<
"Restored session" << sid <<
"keys" << sessionData.
size();
449bool SessionPrivate::validateSessionId(QByteArrayView
id)
451 auto it =
id.begin();
455 if ((c >=
'a' && c <=
'f') || (c >=
'0' && c <=
'9')) {
465qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
467 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
471 const qint64 current = getStoredSessionExpires(session, c, sid);
472 const qint64 cutoff = current - threshold;
475 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
476 qint64 updated = calculateInitialSessionExpires(session, c, sid);
477 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
478 extendSessionId(session, c, sid, updated);
489qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
491 const QByteArray &sessionid)
493 const QVariant expires =
494 session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
498QVariant SessionPrivate::initializeSessionData(
Session *session,
Context *c)
502 ret.insert(QStringLiteral(
"__created"), now);
503 ret.insert(QStringLiteral(
"__updated"), now);
505 if (session->d_ptr->verifyAddress) {
509 if (session->d_ptr->verifyUserAgent) {
510 ret.insert(QStringLiteral(
"__user_agent"), c->
request()->userAgent());
516void SessionPrivate::saveSessionExpires(
Context *c)
518 const QVariant expires = c->
stash(SESSION_EXPIRES);
522 if (Q_UNLIKELY(!m_instance)) {
523 qCCritical(C_SESSION) <<
"Session plugin not registered";
527 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
529 if (extended > current) {
530 m_instance->d_ptr->store->storeSessionData(
531 c, sid, QStringLiteral(
"expires"), extended);
538 SessionPrivate::loadSessionExpires(
Session *session,
Context *c,
const QByteArray &sessionId)
541 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
542 ret = c->
stash(SESSION_EXPIRES);
545 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
548 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
551 c->
setStash(SESSION_EXPIRES, expires);
554 deleteSession(session, c, QStringLiteral(
"session expired"));
561qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
564 const qint64 expires = qint64(session->d_ptr->sessionExpires);
568qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
570 const QByteArray &sessionId)
572 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
573 const qint64 initial = initialSessionExpires(session, c);
574 return qMax(initial, stored);
578 SessionPrivate::resetSessionExpires(
Session *session,
Context *c,
const QByteArray &sessionId)
580 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
586 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
587 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
592void SessionPrivate::updateSessionCookie(
Context *c,
const QNetworkCookie &updated)
597QNetworkCookie SessionPrivate::makeSessionCookie(
Session *session,
599 const QByteArray &sid,
600 const QDateTime &expires)
603 QNetworkCookie cookie(session->d_ptr->sessionName, sid);
604 cookie.setPath(u
"/"_s);
605 cookie.setExpirationDate(expires);
606 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
607 cookie.setSecure(session->d_ptr->cookieSecure);
608 cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
613void SessionPrivate::extendSessionId(
Session *session,
615 const QByteArray &sid,
618 updateSessionCookie(c,
622void SessionPrivate::setSessionId(
Session *session,
Context *c,
const QByteArray &sid)
630QVariant SessionPrivate::config(
const QString &key,
const QVariant &defaultValue)
const
632 return loadedConfig.value(key, defaultConfig.value(key, defaultValue));
640#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(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_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