7 #include "sessionstorefile.h"
9 #include <Cutelyst/Application>
10 #include <Cutelyst/Context>
11 #include <Cutelyst/Response>
12 #include <Cutelyst/Engine>
15 #include <QHostAddress>
16 #include <QLoggingCategory>
17 #include <QCoreApplication>
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;
36 , d_ptr(new SessionPrivate(this))
41 Cutelyst::Session::~Session()
49 d->sessionName = QCoreApplication::applicationName() + QLatin1String(
"_session");
51 const QVariantMap config = app->
engine()->
config(QLatin1String(
"Cutelyst_Session_Plugin"));
52 d->sessionExpires = config.value(QLatin1String(
"expires"), 7200).toLongLong();
53 d->expiryThreshold = config.value(QLatin1String(
"expiry_threshold"), 0).toLongLong();
54 d->verifyAddress = config.value(QLatin1String(
"verify_address"),
false).toBool();
55 d->verifyUserAgent = config.value(QLatin1String(
"verify_user_agent"),
false).toBool();
56 d->cookieHttpOnly = config.value(QLatin1String(
"cookie_http_only"),
true).toBool();
57 d->cookieSecure = config.value(QLatin1String(
"cookie_secure"),
false).toBool();
74 Q_ASSERT_X(d->store,
"Cutelyst::Session::setStorage",
"Session Storage is alread defined");
75 store->setParent(
this);
88 const QVariant sid = c->
stash(SESSION_ID);
90 if (Q_UNLIKELY(!m_instance)) {
91 qCCritical(C_SESSION) <<
"Session plugin not registered";
95 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
110 if (Q_UNLIKELY(!m_instance)) {
111 qCCritical(C_SESSION) <<
"Session plugin not registered";
115 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
117 return quint64(SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong()));
126 const qint64 timeExp = QDateTime::currentMSecsSinceEpoch() / 1000 + qint64(
expires);
128 if (Q_UNLIKELY(!m_instance)) {
129 qCCritical(C_SESSION) <<
"Session plugin not registered";
133 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), timeExp);
138 if (Q_UNLIKELY(!m_instance)) {
139 qCCritical(C_SESSION) <<
"Session plugin not registered";
142 SessionPrivate::deleteSession(m_instance, c, reason);
147 return c->
stash(SESSION_DELETE_REASON).toString();
152 QVariant ret = defaultValue;
153 QVariant session = c->
stash(SESSION_VALUES);
154 if (session.isNull()) {
155 session = SessionPrivate::loadSession(c);
158 if (!session.isNull()) {
159 ret = session.toHash().value(key, defaultValue);
167 QVariant session = c->
stash(SESSION_VALUES);
168 if (session.isNull()) {
169 session = SessionPrivate::loadSession(c);
170 if (session.isNull()) {
171 if (Q_UNLIKELY(!m_instance)) {
172 qCCritical(C_SESSION) <<
"Session plugin not registered";
176 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
177 session = SessionPrivate::initializeSessionData(m_instance, c);
181 QVariantHash data = session.toHash();
182 data.insert(key,
value);
190 QVariant session = c->
stash(SESSION_VALUES);
191 if (session.isNull()) {
192 session = SessionPrivate::loadSession(c);
193 if (session.isNull()) {
194 if (Q_UNLIKELY(!m_instance)) {
195 qCCritical(C_SESSION) <<
"Session plugin not registered";
199 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
200 session = SessionPrivate::initializeSessionData(m_instance, c);
204 QVariantHash data = session.toHash();
213 QVariant session = c->
stash(SESSION_VALUES);
214 if (session.isNull()) {
215 session = SessionPrivate::loadSession(c);
216 if (session.isNull()) {
217 if (Q_UNLIKELY(!m_instance)) {
218 qCCritical(C_SESSION) <<
"Session plugin not registered";
222 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
223 session = SessionPrivate::initializeSessionData(m_instance, c);
227 QVariantHash data = session.toHash();
228 for (
const QString &key : keys) {
238 return !SessionPrivate::loadSession(c).isNull();
241 QString SessionPrivate::generateSessionId()
243 return QString::fromLatin1(QUuid::createUuid().toRfc4122().toHex());
246 QString SessionPrivate::loadSessionId(
Context *c,
const QString &sessionName)
249 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
252 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
254 const QString sid = getSessionId(c, sessionName);
255 if (!sid.isEmpty()) {
256 if (!validateSessionId(sid)) {
257 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
267 QString SessionPrivate::getSessionId(
Context *c,
const QString &sessionName)
270 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
273 const QVariant
property = c->
stash(SESSION_ID);
274 if (!property.isNull()) {
275 ret =
property.toString();
279 const QString cookie = c->request()->
cookie(sessionName);
280 if (!cookie.isEmpty()) {
281 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
289 QString SessionPrivate::createSessionIdIfNeeded(
Session *session,
Context *c, qint64 expires)
292 const QVariant sid = c->
stash(SESSION_ID);
294 ret = sid.toString();
296 ret = createSessionId(session, c, expires);
301 QString SessionPrivate::createSessionId(
Session *session,
Context *c, qint64 expires)
304 const QString sid = generateSessionId();
306 qCDebug(C_SESSION) <<
"Created session" << sid;
309 resetSessionExpires(session, c, sid);
310 setSessionId(session, c, sid);
315 void SessionPrivate::_q_saveSession(
Context *c)
318 saveSessionExpires(c);
326 if (Q_UNLIKELY(!m_instance)) {
327 qCCritical(C_SESSION) <<
"Session plugin not registered";
330 saveSessionExpires(c);
332 if (!c->
stash(SESSION_UPDATED).toBool()) {
336 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
337 sessionData.insert(QStringLiteral(
"__updated"), QDateTime::currentMSecsSinceEpoch() / 1000);
339 const QString sid = c->
stash(SESSION_ID).toString();
343 void SessionPrivate::deleteSession(
Session *session,
Context *c,
const QString &reason)
345 qCDebug(C_SESSION) <<
"Deleting session" << reason;
347 const QVariant sidVar = c->
stash(SESSION_ID).toString();
348 if (!sidVar.isNull()) {
349 const QString sid = sidVar.toString();
350 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
351 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
352 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
354 deleteSessionId(session, c, sid);
358 c->
setStash(SESSION_VALUES, QVariant());
359 c->
setStash(SESSION_ID, QVariant());
360 c->
setStash(SESSION_EXPIRES, QVariant());
362 c->
setStash(SESSION_DELETE_REASON, reason);
365 void SessionPrivate::deleteSessionId(
Session *session,
Context *c,
const QString &sid)
367 c->
setStash(SESSION_DELETED_ID,
true);
369 updateSessionCookie(c, makeSessionCookie(session, c, sid, QDateTime::currentDateTimeUtc()));
372 QVariant SessionPrivate::loadSession(
Context *c)
375 const QVariant
property = c->
stash(SESSION_VALUES);
376 if (!property.isNull()) {
377 ret =
property.toHash();
381 if (Q_UNLIKELY(!m_instance)) {
382 qCCritical(C_SESSION) <<
"Session plugin not registered";
387 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
388 if (SessionPrivate::validateSessionId(sid)) {
390 const QVariantHash sessionData = m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session")).toHash();
391 c->
setStash(SESSION_VALUES, sessionData);
393 if (m_instance->d_ptr->verifyAddress &&
394 sessionData.contains(QStringLiteral(
"__address")) &&
395 sessionData.
value(QStringLiteral(
"__address")).toString() != c->request()->
address().toString()) {
396 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to address mismatch:"
397 << sessionData.value(QStringLiteral(
"__address")).toString()
399 << c->request()->
address().toString();
400 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
404 if (m_instance->d_ptr->verifyUserAgent &&
405 sessionData.contains(QStringLiteral(
"__user_agent")) &&
406 sessionData.
value(QStringLiteral(
"__user_agent")).toString() != c->request()->userAgent()) {
407 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to user agent mismatch:"
408 << sessionData.value(QStringLiteral(
"__user_agent")).toString()
410 << c->request()->userAgent();
411 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
415 qCDebug(C_SESSION) <<
"Restored session" << sid;
424 bool SessionPrivate::validateSessionId(
const QString &
id)
426 auto it =
id.constBegin();
427 auto end =
id.constEnd();
430 if ((c >= u
'a' && c <= u
'f') || (c >= u
'0' && c <= u
'9')) {
440 qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
442 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
445 if (!sid.isEmpty()) {
446 const qint64 current = getStoredSessionExpires(session, c, sid);
447 const qint64 cutoff = current - threshold;
448 const qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000;
450 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
451 qint64 updated = calculateInitialSessionExpires(session, c, sid);
452 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
453 extendSessionId(session, c, sid, updated);
464 qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
Context *c,
const QString &sessionid)
466 const QVariant expires = session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
467 return expires.toLongLong();
470 QVariant SessionPrivate::initializeSessionData(
Session *session,
Context *c)
473 const qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000;
474 ret.insert(QStringLiteral(
"__created"), now);
475 ret.insert(QStringLiteral(
"__updated"), now);
477 if (session->d_ptr->verifyAddress) {
478 ret.insert(QStringLiteral(
"__address"), c->request()->
address().toString());
481 if (session->d_ptr->verifyUserAgent) {
482 ret.insert(QStringLiteral(
"__user_agent"), c->request()->userAgent());
488 void SessionPrivate::saveSessionExpires(
Context *c)
490 const QVariant expires = c->
stash(SESSION_EXPIRES);
491 if (!expires.isNull()) {
493 if (!sid.isEmpty()) {
494 if (Q_UNLIKELY(!m_instance)) {
495 qCCritical(C_SESSION) <<
"Session plugin not registered";
499 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
501 if (extended > current) {
502 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), extended);
508 QVariant SessionPrivate::loadSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
511 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
512 ret = c->
stash(SESSION_EXPIRES);
515 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
517 if (!sessionId.isEmpty()) {
518 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
520 if (expires >= QDateTime::currentMSecsSinceEpoch() / 1000) {
521 c->
setStash(SESSION_EXPIRES, expires);
524 deleteSession(session, c, QStringLiteral(
"session expired"));
531 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
534 const qint64 expires = qint64(session->d_ptr->sessionExpires);
535 return QDateTime::currentMSecsSinceEpoch() / 1000 + expires;
538 qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
540 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
541 const qint64 initial = initialSessionExpires(session, c);
542 return qMax(initial , stored);
545 qint64 SessionPrivate::resetSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
547 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
553 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
554 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
559 void SessionPrivate::updateSessionCookie(
Context *c,
const QNetworkCookie &updated)
564 QNetworkCookie SessionPrivate::makeSessionCookie(
Session *session,
Context *c,
const QString &sid,
const QDateTime &expires)
567 QNetworkCookie cookie(session->d_ptr->sessionName.toLatin1(), sid.toLatin1());
568 cookie.setPath(QStringLiteral(
"/"));
569 cookie.setExpirationDate(expires);
570 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
571 cookie.setSecure(session->d_ptr->cookieSecure);
576 void SessionPrivate::extendSessionId(
Session *session,
Context *c,
const QString &sid, qint64 expires)
578 updateSessionCookie(c, makeSessionCookie(session, c, sid, QDateTime::fromMSecsSinceEpoch(expires * 1000)));
581 void SessionPrivate::setSessionId(
Session *session,
Context *c,
const QString &sid)
583 updateSessionCookie(c, makeSessionCookie(session, c, sid,
584 QDateTime::fromMSecsSinceEpoch(initialSessionExpires(session, c) * 1000)));
592 #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
user configuration for the application
QString cookie(const QString &name) const
QHostAddress address() const noexcept
void setCookie(const QNetworkCookie &cookie)
SessionStore(QObject *parent=nullptr)
virtual bool storeSessionData(Context *c, const QString &sid, const QString &key, const QVariant &value)=0
static void deleteSession(Context *c, const QString &reason=QString())
static QString deleteReason(Context *c)
virtual bool setup(Application *app) final
Session(Application *parent)
static QString id(Context *c)
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)
static void changeExpires(Context *c, quint64 expires)
SessionStore * storage() const
void setStorage(SessionStore *store)
static void deleteValue(Context *c, const QString &key)
static quint64 expires(Context *c)
static void deleteValues(Context *c, const QStringList &keys)
The Cutelyst namespace holds all public Cutelyst API.