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 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") 34 static 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");
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();
263 QByteArray SessionPrivate::generateSessionId()
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;
292 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
296 if (!property.isNull()) {
297 ret =
property.toByteArray();
303 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
318 ret = createSessionId(session, c, expires);
326 const auto sid = generateSessionId();
328 qCDebug(C_SESSION) <<
"Created session" << sid;
331 resetSessionExpires(session, c, sid);
332 setSessionId(session, c, sid);
337 void 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);
366 qCDebug(C_SESSION) <<
"Deleting session" << reason;
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);
383 c->
setStash(SESSION_DELETE_REASON, reason);
388 c->
setStash(SESSION_DELETED_ID,
true);
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();
451 auto it =
id.begin();
455 if ((c >=
'a' && c <=
'f') || (c >=
'0' && c <=
'9')) {
465 qint64 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);
489 qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
494 session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
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());
516 void SessionPrivate::saveSessionExpires(
Context *c)
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);
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"));
561 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
564 const qint64 expires = qint64(session->d_ptr->sessionExpires);
568 qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
572 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
573 const qint64 initial = initialSessionExpires(session, c);
574 return qMax(initial, stored);
580 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
586 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
587 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
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);
613 void SessionPrivate::extendSessionId(
Session *session,
618 updateSessionCookie(c,
632 return loadedConfig.
value(key, defaultConfig.value(key, defaultValue));
640 #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
static quint64 expires(Context *c)
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 deleteSession(Context *c, const QString &reason=QString())
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)
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)
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)
QByteArray cookie(QByteArrayView name) const
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)
QByteArray toRfc4122() const const
QDateTime currentDateTimeUtc()
static void changeExpires(Context *c, quint64 expires)
QString applicationName()