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()
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");
105 if (Q_UNLIKELY(!m_instance)) {
106 qCCritical(C_SESSION) <<
"Session plugin not registered";
110 ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
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()));
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();
170 session = SessionPrivate::loadSession(c);
174 ret = session.
toHash().value(key, defaultValue);
184 session = SessionPrivate::loadSession(c);
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);
207 session = SessionPrivate::loadSession(c);
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();
230 session = SessionPrivate::loadSession(c);
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()
264 if (!c->
stash(SESSION_TRIED_LOADING_ID).isNull()) {
267 c->
setStash(SESSION_TRIED_LOADING_ID,
true);
269 const QString sid = getSessionId(c, sessionName);
271 if (!validateSessionId(sid)) {
272 qCCritical(C_SESSION) <<
"Tried to set invalid session ID" << sid;
285 bool deleted = !c->
stash(SESSION_DELETED_ID).isNull();
289 if (!property.isNull()) {
290 ret =
property.toString();
296 qCDebug(C_SESSION) <<
"Found sessionid" << cookie <<
"in cookie";
311 ret = createSessionId(session, c, expires);
319 const QString sid = generateSessionId();
321 qCDebug(C_SESSION) <<
"Created session" << sid;
324 resetSessionExpires(session, c, sid);
325 setSessionId(session, c, sid);
330 void SessionPrivate::_q_saveSession(
Context *c)
333 saveSessionExpires(c);
341 if (Q_UNLIKELY(!m_instance)) {
342 qCCritical(C_SESSION) <<
"Session plugin not registered";
345 saveSessionExpires(c);
347 if (!c->
stash(SESSION_UPDATED).toBool()) {
351 QVariantHash sessionData = c->
stash(SESSION_VALUES).toHash();
360 qCDebug(C_SESSION) <<
"Deleting session" << reason;
365 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"session"));
366 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"expires"));
367 session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral(
"flash"));
369 deleteSessionId(session, c, sid);
377 c->
setStash(SESSION_DELETE_REASON, reason);
382 c->
setStash(SESSION_DELETED_ID,
true);
391 if (!property.isNull()) {
396 if (Q_UNLIKELY(!m_instance)) {
397 qCCritical(C_SESSION) <<
"Session plugin not registered";
402 if (!loadSessionExpires(m_instance, c, sid).isNull()) {
403 if (SessionPrivate::validateSessionId(sid)) {
405 const QVariantHash sessionData = m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral(
"session")).toHash();
406 c->
setStash(SESSION_VALUES, sessionData);
408 if (m_instance->d_ptr->verifyAddress &&
409 sessionData.contains(QStringLiteral(
"__address")) &&
410 sessionData.
value(QStringLiteral(
"__address")).toString() != c->request()->
address().
toString()) {
411 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to address mismatch:"
412 << sessionData.value(QStringLiteral(
"__address")).toString()
415 deleteSession(m_instance, c, QStringLiteral(
"address mismatch"));
419 if (m_instance->d_ptr->verifyUserAgent &&
420 sessionData.contains(QStringLiteral(
"__user_agent")) &&
421 sessionData.
value(QStringLiteral(
"__user_agent")).toString() != c->request()->userAgent()) {
422 qCWarning(C_SESSION) <<
"Deleting session" << sid <<
"due to user agent mismatch:"
423 << sessionData.value(QStringLiteral(
"__user_agent")).toString()
425 << c->request()->userAgent();
426 deleteSession(m_instance, c, QStringLiteral(
"user agent mismatch"));
430 qCDebug(C_SESSION) <<
"Restored session" << sid;
439 bool SessionPrivate::validateSessionId(
const QString &
id)
441 auto it =
id.constBegin();
442 auto end =
id.constEnd();
455 qint64 SessionPrivate::extendSessionExpires(
Session *session,
Context *c, qint64 expires)
457 const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
461 const qint64 current = getStoredSessionExpires(session, c, sid);
462 const qint64 cutoff = current - threshold;
465 if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
466 qint64 updated = calculateInitialSessionExpires(session, c, sid);
467 c->
setStash(SESSION_EXTENDED_EXPIRES, updated);
468 extendSessionId(session, c, sid, updated);
479 qint64 SessionPrivate::getStoredSessionExpires(
Session *session,
Context *c,
const QString &sessionid)
481 const QVariant expires = session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral(
"expires"), 0);
489 ret.insert(QStringLiteral(
"__created"), now);
490 ret.insert(QStringLiteral(
"__updated"), now);
492 if (session->d_ptr->verifyAddress) {
493 ret.insert(QStringLiteral(
"__address"), c->request()->
address().
toString());
496 if (session->d_ptr->verifyUserAgent) {
497 ret.insert(QStringLiteral(
"__user_agent"), c->request()->userAgent());
503 void SessionPrivate::saveSessionExpires(
Context *c)
509 if (Q_UNLIKELY(!m_instance)) {
510 qCCritical(C_SESSION) <<
"Session plugin not registered";
514 const qint64 current = getStoredSessionExpires(m_instance, c, sid);
516 if (extended > current) {
517 m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral(
"expires"), extended);
526 if (c->
stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
527 ret = c->
stash(SESSION_EXPIRES);
530 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
533 const qint64 expires = getStoredSessionExpires(session, c, sessionId);
536 c->
setStash(SESSION_EXPIRES, expires);
539 deleteSession(session, c, QStringLiteral(
"session expired"));
546 qint64 SessionPrivate::initialSessionExpires(
Session *session,
Context *c)
549 const qint64 expires = qint64(session->d_ptr->sessionExpires);
553 qint64 SessionPrivate::calculateInitialSessionExpires(
Session *session,
Context *c,
const QString &sessionId)
555 const qint64 stored = getStoredSessionExpires(session, c, sessionId);
556 const qint64 initial = initialSessionExpires(session, c);
557 return qMax(initial , stored);
562 const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
568 c->
setStash(SESSION_TRIED_LOADING_EXPIRES,
true);
569 c->
setStash(SESSION_EXTENDED_EXPIRES, exp);
583 cookie.setPath(QStringLiteral(
"/"));
584 cookie.setExpirationDate(expires);
585 cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
586 cookie.setSecure(session->d_ptr->cookieSecure);
591 void SessionPrivate::extendSessionId(
Session *session,
Context *c,
const QString &sid, qint64 expires)
598 updateSessionCookie(c, makeSessionCookie(session, c, sid,
607 #include "moc_session.cpp"
The Cutelyst Application.
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
QVariantMap config(const QString &entity) const
user configuration for the application
QHostAddress address() const
QString cookie(const QString &name) const
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.
QDateTime currentDateTimeUtc()
qint64 currentMSecsSinceEpoch()
QDateTime fromMSecsSinceEpoch(qint64 msecs)
QString toString() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setParent(QObject *parent)
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
QByteArray toLatin1() const const
bool isNull() const const
QHash< QString, QVariant > toHash() const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const