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>
21 Q_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")
33 static 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();
262 QByteArray SessionPrivate::generateSessionId()
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);
336 void 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')) {
464 qint64 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);
488 qint64 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());
515 void 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"));
560 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
563 const qint64 expires = qint64(session->d_ptr->sessionExpires);
567 qint64 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);
612 void 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_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