Compare commits

...

2 Commits

Author SHA1 Message Date
Ferdinand Thiessen 4248825af0 Revert future imports for annotations, PEP563 is still defered
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-08-25 12:06:59 +02:00
Ferdinand Thiessen e22e38b304 Implement custom UA parsing, allowing to update Flask and Werkzeug
Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
2022-08-22 17:18:03 +02:00
9 changed files with 64 additions and 17 deletions

View File

@ -25,6 +25,7 @@ def get_enabled_plugins():
class PluginStub: class PluginStub:
def __init__(self, name) -> None: def __init__(self, name) -> None:
self.name = name self.name = name
self.version = "?"
logger.error("Could not connect to database or database not initialized! No plugins enabled!") logger.error("Could not connect to database or database not initialized! No plugins enabled!")
logger.debug("Can not query enabled plugins", exc_info=True) logger.debug("Can not query enabled plugins", exc_info=True)

View File

@ -11,7 +11,36 @@ from ..database import db
lifetime = 1800 lifetime = 1800
def validate_token(token, user_agent, permission): def __get_user_agent_platform(ua: str):
if "Win" in ua:
return "Windows"
if "Mac" in ua:
return "Macintosh"
if "Linux" in ua:
return "Linux"
if "Android" in ua:
return "Android"
if "like Mac" in ua:
return "iOS"
return "unknown"
def __get_user_agent_browser(ua: str):
ua_str = ua.lower()
if "firefox" in ua_str or "fxios" in ua_str:
return "firefox"
if "safari" in ua_str:
return "safari"
if "opr/" in ua_str:
return "opera"
if "edg" in ua_str:
return "edge"
if "chrom" in ua_str or "crios" in ua_str:
return "chrome"
return "unknown"
def validate_token(token, request_headers, permission):
"""Verify session """Verify session
Verify a Session and Roles so if the User has permission or not. Verify a Session and Roles so if the User has permission or not.
@ -19,7 +48,7 @@ def validate_token(token, user_agent, permission):
Args: Args:
token: Token to verify. token: Token to verify.
user_agent: User agent of browser to check request_headers: Headers to validate user agent of browser
permission: Permission needed to access restricted routes permission: Permission needed to access restricted routes
Returns: Returns:
A Session for this given Token A Session for this given Token
@ -31,8 +60,16 @@ def validate_token(token, user_agent, permission):
session = Session.query.filter_by(token=token).one_or_none() session = Session.query.filter_by(token=token).one_or_none()
if session: if session:
logger.debug("token found, check if expired or invalid user agent differs") logger.debug("token found, check if expired or invalid user agent differs")
platform = request_headers.get("Sec-CH-UA-Platform", None) or __get_user_agent_platform(
request_headers.get("User-Agent", "")
)
browser = request_headers.get("Sec-CH-UA", None) or __get_user_agent_browser(
request_headers.get("User-Agent", "")
)
if session.expires >= datetime.now(timezone.utc) and ( if session.expires >= datetime.now(timezone.utc) and (
session.browser == user_agent.browser and session.platform == user_agent.platform session.browser == browser and session.platform == platform
): ):
if not permission or session.user_.has_permission(permission): if not permission or session.user_.has_permission(permission):
session.refresh() session.refresh()

View File

@ -1,3 +1,5 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from sqlalchemy import event from sqlalchemy import event
from pathlib import Path from pathlib import Path

View File

@ -1,3 +1,5 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any
@ -14,8 +16,8 @@ class Notification(db.Model, ModelSerializeMixin):
user_id_: int = db.Column("user", Serial, db.ForeignKey("user.id"), nullable=False) user_id_: int = db.Column("user", Serial, db.ForeignKey("user.id"), nullable=False)
plugin_id_: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id"), nullable=False) plugin_id_: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id"), nullable=False)
user_: "User" = db.relationship("User") user_: User = db.relationship("User")
plugin_: "Plugin" = db.relationship( plugin_: Plugin = db.relationship(
"Plugin", backref=db.backref("notifications_", cascade="all, delete, delete-orphan") "Plugin", backref=db.backref("notifications_", cascade="all, delete, delete-orphan")
) )

View File

@ -1,3 +1,5 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from typing import Any from typing import Any
from ..database import db from ..database import db

View File

@ -1,11 +1,12 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from secrets import compare_digest from secrets import compare_digest
from .. import logger
from ..database import db from ..database import db
from ..database.types import ModelSerializeMixin, UtcDateTime, Serial from ..database.types import ModelSerializeMixin, UtcDateTime, Serial
from flaschengeist import logger
class Session(db.Model, ModelSerializeMixin): class Session(db.Model, ModelSerializeMixin):
"""Model for a Session """Model for a Session
@ -20,13 +21,13 @@ class Session(db.Model, ModelSerializeMixin):
expires: datetime = db.Column(UtcDateTime) 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(127))
platform: str = db.Column(db.String(30)) platform: str = db.Column(db.String(64))
userid: str = "" userid: str = ""
_id = db.Column("id", Serial, primary_key=True) _id = db.Column("id", Serial, primary_key=True)
_user_id = db.Column("user_id", Serial, db.ForeignKey("user.id")) _user_id = db.Column("user_id", Serial, db.ForeignKey("user.id"))
user_: "User" = db.relationship("User", back_populates="sessions_") user_: User = db.relationship("User", back_populates="sessions_")
@property @property
def userid(self): def userid(self):

View File

@ -1,3 +1,5 @@
from __future__ import annotations # TODO: Remove if python requirement is >= 3.12 (? PEP 563 is defered)
from typing import Optional from typing import Optional
from datetime import date, datetime from datetime import date, datetime
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
@ -62,10 +64,10 @@ class User(db.Model, ModelSerializeMixin):
# Protected stuff for backend use only # Protected stuff for backend use only
id_ = db.Column("id", Serial, primary_key=True) id_ = db.Column("id", Serial, primary_key=True)
roles_: list[Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge") roles_: list[Role] = db.relationship("Role", secondary=association_table, cascade="save-update, merge")
sessions_: list["Session"] = db.relationship( sessions_: list[Session] = db.relationship(
"Session", back_populates="user_", cascade="all, delete, delete-orphan" "Session", back_populates="user_", cascade="all, delete, delete-orphan"
) )
avatar_: Optional["Image"] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True) avatar_: Optional[Image] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True)
reset_requests_: list["_PasswordReset"] = db.relationship("_PasswordReset", cascade="all, delete, delete-orphan") reset_requests_: list["_PasswordReset"] = db.relationship("_PasswordReset", cascade="all, delete, delete-orphan")
# Private stuff for internal use # Private stuff for internal use

View File

@ -14,7 +14,7 @@ def extract_session(permission=None):
logger.debug("Missing Authorization header or ill-formed") logger.debug("Missing Authorization header or ill-formed")
raise Unauthorized raise Unauthorized
session = sessionController.validate_token(token, request.user_agent, permission) session = sessionController.validate_token(token, request.headers, permission)
return session return session

View File

@ -22,14 +22,14 @@ include_package_data = True
python_requires = >=3.10 python_requires = >=3.10
packages = find: packages = find:
install_requires = install_requires =
Flask==2.0.3 Flask>=2.2.2
Pillow>=9.0 Pillow>=9.2
flask_cors flask_cors
flask_migrate>=3.1.0 flask_migrate>=3.1.0
flask_sqlalchemy>=2.5.1 flask_sqlalchemy>=2.5.1
sqlalchemy>=1.4.39 sqlalchemy>=1.4.40
toml toml
werkzeug==2.0.3 werkzeug>=2.2.2
[options.extras_require] [options.extras_require]
argon = argon2-cffi argon = argon2-cffi