Made database datetime timezone aware
This commit is contained in:
parent
addfb7c7c4
commit
db96d4b178
|
@ -21,6 +21,7 @@ class CustomJSONEncoder(JSONEncoder):
|
||||||
|
|
||||||
if isinstance(o, datetime):
|
if isinstance(o, datetime):
|
||||||
return o.isoformat()
|
return o.isoformat()
|
||||||
|
|
||||||
# Check if iterable
|
# Check if iterable
|
||||||
try:
|
try:
|
||||||
iterable = iter(o)
|
iterable = iter(o)
|
||||||
|
|
|
@ -14,7 +14,7 @@ from flaschengeist.system.decorator import login_required
|
||||||
from flaschengeist.system.controller import sessionController, userController, messageController
|
from flaschengeist.system.controller import sessionController, userController, messageController
|
||||||
from flaschengeist.system.models.session import Session
|
from flaschengeist.system.models.session import Session
|
||||||
|
|
||||||
session_controller = LocalProxy(lambda: sessionController.SessionController())
|
session_controller: sessionController.SessionController = LocalProxy(lambda: sessionController.SessionController())
|
||||||
auth_bp = Blueprint("auth", __name__)
|
auth_bp = Blueprint("auth", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SessionController(metaclass=Singleton):
|
||||||
access_token = Session.query.filter_by(token=token).one_or_none()
|
access_token = Session.query.filter_by(token=token).one_or_none()
|
||||||
if access_token:
|
if access_token:
|
||||||
logger.debug("token found, check if expired or invalid user agent differs")
|
logger.debug("token found, check if expired or invalid user agent differs")
|
||||||
if access_token.expires >= datetime.utcnow() and (
|
if access_token.expires >= datetime.now(timezone.utc) and (
|
||||||
access_token.browser == user_agent.browser and access_token.platform == user_agent.platform
|
access_token.browser == user_agent.browser and access_token.platform == user_agent.platform
|
||||||
):
|
):
|
||||||
if not permissions or access_token.user.has_permissions(permissions):
|
if not permissions or access_token.user.has_permissions(permissions):
|
||||||
|
@ -71,7 +71,6 @@ class SessionController(metaclass=Singleton):
|
||||||
session.refresh()
|
session.refresh()
|
||||||
db.session.add(session)
|
db.session.add(session)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
logger.debug("access token is {{ {} }}".format(session.token))
|
logger.debug("access token is {{ {} }}".format(session.token))
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
@ -95,8 +94,7 @@ class SessionController(metaclass=Singleton):
|
||||||
def get_users_sessions(self, user):
|
def get_users_sessions(self, user):
|
||||||
return Session.query.filter(Session._user == user)
|
return Session.query.filter(Session._user == user)
|
||||||
|
|
||||||
@staticmethod
|
def delete_session(self, token: Session):
|
||||||
def delete_session(token: Session):
|
|
||||||
"""Deletes given Session
|
"""Deletes given Session
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -105,8 +103,7 @@ class SessionController(metaclass=Singleton):
|
||||||
db.session.delete(token)
|
db.session.delete(token)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@staticmethod
|
def update_session(self, session):
|
||||||
def update_session(session):
|
|
||||||
session.refresh()
|
session.refresh()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -117,6 +114,6 @@ class SessionController(metaclass=Singleton):
|
||||||
def clear_expired(self):
|
def clear_expired(self):
|
||||||
"""Remove expired tokens from database"""
|
"""Remove expired tokens from database"""
|
||||||
logger.debug("Clear expired Sessions")
|
logger.debug("Clear expired Sessions")
|
||||||
deleted = Session.query.filter(Session.expires < datetime.utcnow()).delete()
|
deleted = Session.query.filter(Session.expires < datetime.now(timezone.utc)).delete()
|
||||||
logger.debug("{} sessions have been removed".format(deleted))
|
logger.debug("{} sessions have been removed".format(deleted))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import datetime
|
||||||
|
from sqlalchemy.types import DateTime, TypeDecorator
|
||||||
|
|
||||||
|
|
||||||
class ModelSerializeMixin:
|
class ModelSerializeMixin:
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
d = {param: getattr(self, param) for param in self.__class__.__annotations__ if not param.startswith("_")}
|
d = {param: getattr(self, param) for param in self.__class__.__annotations__ if not param.startswith("_")}
|
||||||
|
@ -5,3 +9,34 @@ class ModelSerializeMixin:
|
||||||
key, value = d.popitem()
|
key, value = d.popitem()
|
||||||
return value
|
return value
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class UtcDateTime(TypeDecorator):
|
||||||
|
"""Almost equivalent to :class:`~sqlalchemy.types.DateTime` with
|
||||||
|
``timezone=True`` option, but it differs from that by:
|
||||||
|
- Never silently take naive :class:`~datetime.datetime`, instead it
|
||||||
|
always raise :exc:`ValueError` unless time zone aware value.
|
||||||
|
- :class:`~datetime.datetime` value's :attr:`~datetime.datetime.tzinfo`
|
||||||
|
is always converted to UTC.
|
||||||
|
- Unlike SQLAlchemy's built-in :class:`~sqlalchemy.types.DateTime`,
|
||||||
|
it never return naive :class:`~datetime.datetime`, but time zone
|
||||||
|
aware value, even with SQLite or MySQL.
|
||||||
|
"""
|
||||||
|
|
||||||
|
impl = DateTime(timezone=True)
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
if value is not None:
|
||||||
|
if not isinstance(value, datetime.datetime):
|
||||||
|
raise TypeError('expected datetime.datetime, not ' +
|
||||||
|
repr(value))
|
||||||
|
elif value.tzinfo is None:
|
||||||
|
raise ValueError('naive datetime is disallowed')
|
||||||
|
return value.astimezone(datetime.timezone.utc)
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
if value is not None:
|
||||||
|
if value.tzinfo is not None:
|
||||||
|
value = value.astimezone(datetime.timezone.utc)
|
||||||
|
value = value.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
return value
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from . import ModelSerializeMixin
|
from . import ModelSerializeMixin, UtcDateTime
|
||||||
from .user import User
|
from .user import User
|
||||||
from ..database import db
|
from ..database import db
|
||||||
from secrets import compare_digest
|
from secrets import compare_digest
|
||||||
|
@ -17,7 +17,7 @@ class Session(db.Model, ModelSerializeMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "session"
|
__tablename__ = "session"
|
||||||
expires: datetime = db.Column(db.DateTime)
|
expires: datetime = db.Column(UtcDateTime)
|
||||||
token: str = db.Column(db.String(32), unique=True)
|
token: str = db.Column(db.String(32), unique=True)
|
||||||
lifetime: int = db.Column(db.Integer)
|
lifetime: int = db.Column(db.Integer)
|
||||||
browser: str = db.Column(db.String(30))
|
browser: str = db.Column(db.String(30))
|
||||||
|
@ -33,8 +33,7 @@ class Session(db.Model, ModelSerializeMixin):
|
||||||
Update the Timestamp to the current Time.
|
Update the Timestamp to the current Time.
|
||||||
"""
|
"""
|
||||||
logger.debug("update timestamp from session with token {{ {} }}".format(self))
|
logger.debug("update timestamp from session with token {{ {} }}".format(self))
|
||||||
self.expires = datetime.utcnow() + timedelta(seconds=self.lifetime)
|
self.expires = datetime.now(timezone.utc) + timedelta(seconds=self.lifetime)
|
||||||
self.expires.replace(tzinfo=timezone.utc)
|
|
||||||
|
|
||||||
def __eq__(self, token):
|
def __eq__(self, token):
|
||||||
return compare_digest(self.token, token)
|
return compare_digest(self.token, token)
|
||||||
|
|
Loading…
Reference in New Issue