18 #include "session_p.h"
20 #include "sessionstorefile.h"
22 #include <Cutelyst/Application>
23 #include <Cutelyst/Context>
24 #include <Cutelyst/Response>
25 #include <Cutelyst/Engine>
28 #include <QHostAddress>
29 #include <QLoggingCategory>
30 #include <QCoreApplication>
34 Q_LOGGING_CATEGORY(C_SESSION,
"cutelyst.plugin.session", QtWarningMsg)
36 #define SESSION_VALUES QStringLiteral("_c_session_values")
37 #define SESSION_EXPIRES QStringLiteral("_c_session_expires")
38 #define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
39 #define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
40 #define SESSION_UPDATED QStringLiteral("_c_session_updated")
41 #define SESSION_ID QStringLiteral("_c_session_id")
42 #define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
43 #define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
44 #define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
46 static thread_local
Session *m_instance =
nullptr;
49 , d_ptr(new SessionPrivate(this))
54 Cutelyst::Session::~Session()
62 d->sessionName = QCoreApplication::applicationName() + QLatin1String(
"_session");
64 const QVariantMap config = app->
engine()->
config(QLatin1String(
"Cutelyst_Session_Plugin"));
65 d->sessionExpires = config.value(QLatin1String(
"expires"), 7200).toLongLong();
66 d->expiryThreshold = config.value(QLatin1String(
"expiry_threshold"), 0).toLongLong();
67 d->verifyAddress = config.value(QLatin1String(
"verify_address"),
false).toBool();
68 d->verifyUserAgent = config.value(QLatin1String(
"verify_user_agent"),
false).toBool();
69 d->cookieHttpOnly = config.value(QLatin1String(
"cookie_http_only"),
true).toBool();
70 d->cookieSecure = config.value(QLatin1String(
"cookie_secure"),
false).toBool();
88 qFatal(
"Session Storage is alread defined");
90 store->setParent(
this);
103 const QVariant sid = c->
stash(SESSION_ID);
105 if (Q_UNLIKELY(!m_instance)) {
106 qCCritical(C_SESSION) <<
"Session plugin not registered";
110 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
112 ret = sid.toString();
125 if (Q_UNLIKELY(!m_instance)) {
126 qCCritical(C_SESSION) <<
"Session plugin not registered";
130 expires = SessionPrivate::loadSessionExpires(m_instance, c,
id(c));
132 return quint64(SessionPrivate::extendSessionExpires(m_instance, c,
expires.toLongLong()));
141 const qint64 timeExp = QDateTime::currentMSecsSinceEpoch() / 1000 + qint64(
expires);
143 if (Q_UNLIKELY(!m_instance)) {
144 qCCritical(C_SESSION) <<
"Session plugin not registered";
148 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), timeExp);
153 if (Q_UNLIKELY(!m_instance)) {
154 qCCritical(C_SESSION) <<
"Session plugin not registered";
157 SessionPrivate::deleteSession(m_instance, c, reason);
162 return c->
stash(SESSION_DELETE_REASON).toString();
167 QVariant ret = defaultValue;
168 QVariant session = c->
stash(SESSION_VALUES);
169 if (session.isNull()) {
170 session = SessionPrivate::loadSession(c);
173 if (!session.isNull()) {
174 ret = session.toHash().value(key, defaultValue);
182 QVariant session = c->
stash(SESSION_VALUES);
183 if (session.isNull()) {
184 session = SessionPrivate::loadSession(c);
185 if (session.isNull()) {
186 if (Q_UNLIKELY(!m_instance)) {
187 qCCritical(C_SESSION) <<
"Session plugin not registered";
191 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
192 session = SessionPrivate::initializeSessionData(m_instance, c);
196 QVariantHash data = session.toHash();
197 data.insert(key,
value);
205 QVariant session = c->
stash(SESSION_VALUES);
206 if (session.isNull()) {
207 session = SessionPrivate::loadSession(c);
208 if (session.isNull()) {
209 if (Q_UNLIKELY(!m_instance)) {
210 qCCritical(C_SESSION) <<
"Session plugin not registered";
214 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
215 session = SessionPrivate::initializeSessionData(m_instance, c);
219 QVariantHash data = session.toHash();
228 QVariant session = c->
stash(SESSION_VALUES);
229 if (session.isNull()) {
230 session = SessionPrivate::loadSession(c);
231 if (session.isNull()) {
232 if (Q_UNLIKELY(!m_instance)) {
233 qCCritical(C_SESSION) <<
"Session plugin not registered";
237 SessionPrivate::createSessionIdIfNeeded(m_instance, c, m_instance->d_ptr->sessionExpires);
238 session = SessionPrivate::initializeSessionData(m_instance, c);
242 QVariantHash data = session.toHash();
243 for (
const QString &key : keys) {
253 return !SessionPrivate::loadSession(c).isNull();
256 QString SessionPrivate::generateSessionId()
258 return QString::fromLatin1(QUuid::createUuid().toRfc4122().toHex());
261 QString SessionPrivate::loadSessionId(
Context *c,
const QString &sessionName)
264 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
267 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
269 const QString sid = getSessionId(c, sessionName);
270 if (!sid.isEmpty() && !validateSessionId(sid)) {
271 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
280 QString SessionPrivate::getSessionId(
Context *c,
const QString &sessionName)
283 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
286 const QVariant
property = c->
stash(SESSION_ID);
287 if (!property.isNull()) {
288 ret =
property.toString();
292 const QString cookie = c->request()->
cookie(sessionName);
293 if (!cookie.isEmpty()) {
294 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
302 QString SessionPrivate::createSessionIdIfNeeded(
Session *session,
Context *c, qint64 expires)
305 const QVariant sid = c->
stash(SESSION_ID);
307 ret = sid.toString();
309 ret = createSessionId(session, c, expires);
314 QString SessionPrivate::createSessionId(
Session *session,
Context *c, qint64 expires)
317 const QString sid = generateSessionId();
319 qCDebug(C_SESSION) <<
"Created session" << sid;
322 resetSessionExpires(session, c, sid);
323 setSessionId(session, c, sid);
328 void SessionPrivate::_q_saveSession(
Context *c)
331 saveSessionExpires(c);
339 if (Q_UNLIKELY(!m_instance)) {
340 qCCritical(C_SESSION) <<
"Session plugin not registered";
343 saveSessionExpires(c);
345 if (!c->
stash(SESSION_UPDATED).toBool()) {
349 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
350 sessionData.insert(QStringLiteral(
"__updated"), QDateTime::currentMSecsSinceEpoch() / 1000);
352 const QString sid = c->
stash(SESSION_ID).toString();
356 void SessionPrivate::deleteSession(
Session *session,
Context *c,
const QString &reason)
358 qCDebug(C_SESSION) <<
"Deleting session" << reason;
360 const QVariant sidVar = c->
stash(SESSION_ID).toString();
361 if (!sidVar.isNull()) {
362 const QString sid = sidVar.toString();
363 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
364 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
365 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
367 deleteSessionId(session, c, sid);
371 c->
setStash(SESSION_VALUES, QVariant());
372 c->
setStash(SESSION_ID, QVariant());
373 c->
setStash(SESSION_EXPIRES, QVariant());
375 c->
setStash(SESSION_DELETE_REASON, reason);
378 void SessionPrivate::deleteSessionId(
Session *session,
Context *c,
const QString &sid)
380 c->
setStash(SESSION_DELETED_ID,
true);
382 updateSessionCookie(c, makeSessionCookie(session, c, sid, QDateTime::currentDateTimeUtc()));
385 QVariant SessionPrivate::loadSession(
Context *c)
388 const QVariant
property = c->
stash(SESSION_VALUES);
389 if (!property.isNull()) {
390 ret =
property.toHash();
394 if (Q_UNLIKELY(!m_instance)) {
395 qCCritical(C_SESSION) <<
"Session plugin not registered";
400 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
401 if (SessionPrivate::validateSessionId(sid)) {
403 const QVariantHash sessionData = m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session")).toHash();
404 c->
setStash(SESSION_VALUES, sessionData);
406 if (m_instance->d_ptr->verifyAddress &&
407 sessionData.contains(QStringLiteral(
"__address")) &&
408 sessionData.
value(QStringLiteral(
"__address")).toString() != c->request()->
address().toString()) {
409 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to address mismatch:"
410 << sessionData.value(QStringLiteral(
"__address")).toString()
412 << c->request()->
address().toString();
413 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
417 if (m_instance->d_ptr->verifyUserAgent &&
418 sessionData.contains(QStringLiteral(
"__user_agent")) &&
419 sessionData.
value(QStringLiteral(
"__user_agent")).toString() != c->request()->userAgent()) {
420 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to user agent mismatch:"
421 << sessionData.value(QStringLiteral(
"__user_agent")).toString()
423 << c->request()->userAgent();
424 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
428 qCDebug(C_SESSION) <<
"Restored session" << sid;
437 bool SessionPrivate::validateSessionId(
const QString &
id)
439 auto it =
id.constBegin();
440 auto end =
id.constEnd();
443 if ((c >= QLatin1Char(
'a') && c <= QLatin1Char(
'f')) || (c >= QLatin1Char(
'0') && c <= QLatin1Char(
'9'))) {
453 qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
455 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
458 if (!sid.isEmpty()) {
459 const qint64 current = getStoredSessionExpires(session, c, sid);
460 const qint64 cutoff = current - threshold;
461 const qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000;
463 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
464 qint64 updated = calculateInitialSessionExpires(session, c, sid);
465 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
466 extendSessionId(session, c, sid, updated);
477 qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
Context *c,
const QString &sessionid)
479 const QVariant expires = session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
480 return expires.toLongLong();
483 QVariant SessionPrivate::initializeSessionData(
Session *session,
Context *c)
486 const qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000;
487 ret.insert(QStringLiteral(
"__created"), now);
488 ret.insert(QStringLiteral(
"__updated"), now);
490 if (session->d_ptr->verifyAddress) {
491 ret.insert(QStringLiteral(
"__address"), c->request()->
address().toString());
494 if (session->d_ptr->verifyUserAgent) {
495 ret.insert(QStringLiteral(
"__user_agent"), c->request()->userAgent());
501 void SessionPrivate::saveSessionExpires(
Context *c)
503 const QVariant expires = c->
stash(SESSION_EXPIRES);
504 if (!expires.isNull()) {
506 if (!sid.isEmpty()) {
507 if (Q_UNLIKELY(!m_instance)) {
508 qCCritical(C_SESSION) <<
"Session plugin not registered";
512 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
514 if (extended > current) {
515 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), extended);
521 QVariant SessionPrivate::loadSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
524 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
525 ret = c->
stash(SESSION_EXPIRES);
528 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
530 if (!sessionId.isEmpty()) {
531 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
533 if (expires >= QDateTime::currentMSecsSinceEpoch() / 1000) {
534 c->
setStash(SESSION_EXPIRES, expires);
537 deleteSession(session, c, QStringLiteral(
"session expired"));
544 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
547 const qint64 expires = qint64(session->d_ptr->sessionExpires);
548 return QDateTime::currentMSecsSinceEpoch() / 1000 + expires;
551 qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
553 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
554 const qint64 initial = initialSessionExpires(session, c);
555 return qMax(initial , stored);
558 qint64 SessionPrivate::resetSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
560 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
566 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
567 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
572 void SessionPrivate::updateSessionCookie(
Context *c,
const QNetworkCookie &updated)
577 QNetworkCookie SessionPrivate::makeSessionCookie(
Session *session,
Context *c,
const QString &sid,
const QDateTime &expires)
580 QNetworkCookie cookie(session->d_ptr->sessionName.toLatin1(), sid.toLatin1());
581 cookie.setPath(QStringLiteral(
"/"));
582 cookie.setExpirationDate(expires);
583 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
584 cookie.setSecure(session->d_ptr->cookieSecure);
589 void SessionPrivate::extendSessionId(
Session *session,
Context *c,
const QString &sid, qint64 expires)
591 updateSessionCookie(c, makeSessionCookie(session, c, sid, QDateTime::fromMSecsSinceEpoch(expires * 1000)));
594 void SessionPrivate::setSessionId(
Session *session,
Context *c,
const QString &sid)
596 updateSessionCookie(c, makeSessionCookie(session, c, sid,
597 QDateTime::fromMSecsSinceEpoch(initialSessionExpires(session, c) * 1000)));
605 #include "moc_session.cpp"