import uuid
from datetime import datetime, timedelta, timezone
from bson.tz_util import utc
from flask.sessions import SessionInterface, SessionMixin
from werkzeug.datastructures import CallbackDict
__all__ = ("MongoEngineSession", "MongoEngineSessionInterface")
[docs]
class MongoEngineSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.sid = sid
self.modified = False
[docs]
class MongoEngineSessionInterface(SessionInterface):
"""SessionInterface for mongoengine"""
def __init__(self, db, collection="session"):
"""
The MongoSessionInterface
:param db: The app's db eg: MongoEngine()
:param collection: The session collection name defaults to "session"
"""
if not isinstance(collection, str):
raise ValueError("Collection argument should be string")
class DBSession(db.Document):
sid = db.StringField(primary_key=True)
data = db.DictField()
expiration = db.DateTimeField()
meta = {
"allow_inheritance": False,
"collection": collection,
"indexes": [
{
"fields": ["expiration"],
"expireAfterSeconds": 60 * 60 * 24 * 7 * 31,
}
],
}
self.cls = DBSession
[docs]
def get_expiration_time(self, app, session) -> timedelta:
if session.permanent:
return app.permanent_session_lifetime
# Fallback to 1 day session ttl, if SESSION_TTL not set.
return timedelta(**app.config.get("SESSION_TTL", {"days": 1}))
[docs]
def open_session(self, app, request):
sid = request.cookies.get(app.config["SESSION_COOKIE_NAME"])
if sid:
stored_session = self.cls.objects(sid=sid).first()
if stored_session:
expiration = stored_session.expiration
if not expiration.tzinfo:
expiration = expiration.replace(tzinfo=utc)
if expiration > datetime.now(tz=timezone.utc).replace(tzinfo=utc):
return MongoEngineSession(
initial=stored_session.data, sid=stored_session.sid
)
return MongoEngineSession(sid=str(uuid.uuid4()))
[docs]
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
httponly = self.get_cookie_httponly(app)
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(app.config["SESSION_COOKIE_NAME"], domain=domain)
return
expiration = datetime.now(timezone.utc).replace(
tzinfo=utc
) + self.get_expiration_time(app, session)
if session.modified:
self.cls(sid=session.sid, data=session, expiration=expiration).save()
response.set_cookie(
app.config["SESSION_COOKIE_NAME"],
session.sid,
expires=expiration,
httponly=httponly,
domain=domain,
)