Improved some permission related stuff, rewrote session controller
This commit is contained in:
parent
db96d4b178
commit
854a1f6156
|
@ -6,7 +6,7 @@ send_message_hook = HookCall("send_message")
|
|||
|
||||
|
||||
class Plugin:
|
||||
def __init__(self, config=None, blueprint=None, permissions={}):
|
||||
def __init__(self, config=None, blueprint=None, permissions=[]):
|
||||
self.blueprint = blueprint
|
||||
self.permissions = permissions
|
||||
self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version
|
||||
|
@ -20,6 +20,7 @@ class Plugin:
|
|||
def serialize(self):
|
||||
return {
|
||||
"version": self.version,
|
||||
"permissions": self.permissions
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
from flask import Blueprint, request, jsonify
|
||||
from werkzeug.exceptions import Forbidden, BadRequest, Unauthorized
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.modules import Plugin
|
||||
|
@ -14,7 +13,6 @@ from flaschengeist.system.decorator import login_required
|
|||
from flaschengeist.system.controller import sessionController, userController, messageController
|
||||
from flaschengeist.system.models.session import Session
|
||||
|
||||
session_controller: sessionController.SessionController = LocalProxy(lambda: sessionController.SessionController())
|
||||
auth_bp = Blueprint("auth", __name__)
|
||||
|
||||
|
||||
|
@ -56,19 +54,19 @@ def _login():
|
|||
if not user:
|
||||
raise Unauthorized
|
||||
logger.debug("user is {{ {} }}".format(user))
|
||||
session = session_controller.create(user, user_agent=request.user_agent)
|
||||
session = sessionController.create(user, user_agent=request.user_agent)
|
||||
logger.debug("token is {{ {} }}".format(session.token))
|
||||
logger.info("User {{ {} }} success login.".format(userid))
|
||||
|
||||
# Lets cleanup the DB
|
||||
session_controller.clear_expired()
|
||||
sessionController.clear_expired()
|
||||
return jsonify({"session": session, "user": user})
|
||||
|
||||
|
||||
@auth_bp.route("/auth", methods=["GET"])
|
||||
@login_required()
|
||||
def _get_sessions(access_token: Session, **kwargs):
|
||||
tokens = session_controller.get_users_sessions(access_token._user)
|
||||
tokens = sessionController.get_users_sessions(access_token._user)
|
||||
a = messageController.Message(access_token._user, "Go", "Bar")
|
||||
messageController.send_message(a)
|
||||
return jsonify(tokens)
|
||||
|
@ -78,14 +76,14 @@ def _get_sessions(access_token: Session, **kwargs):
|
|||
@login_required()
|
||||
def _delete_session(access_token, token, **kwargs):
|
||||
logger.debug("Try to delete access token {{ {} }}".format(token))
|
||||
token = session_controller.get_session(token, access_token.user)
|
||||
token = sessionController.get_session(token, access_token.user)
|
||||
if not token:
|
||||
logger.debug("Token not found in database!")
|
||||
# Return 403 error, so that users can not bruteforce tokens
|
||||
# Valid tokens from other users and invalid tokens now are looking the same
|
||||
raise Forbidden
|
||||
session_controller.delete_session(token)
|
||||
session_controller.clear_expired()
|
||||
sessionController.delete_session(token)
|
||||
sessionController.clear_expired()
|
||||
return jsonify({"ok": "ok"})
|
||||
|
||||
|
||||
|
@ -93,7 +91,7 @@ def _delete_session(access_token, token, **kwargs):
|
|||
@login_required()
|
||||
def _get_session(token, access_token, **kwargs):
|
||||
logger.debug("get token {{ {} }}".format(token))
|
||||
session = session_controller.get_session(token, access_token.user)
|
||||
session = sessionController.get_session(token, access_token.user)
|
||||
if not token:
|
||||
# Return 403 error, so that users can not bruteforce tokens
|
||||
# Valid tokens from other users and invalid tokens now are looking the same
|
||||
|
@ -105,7 +103,7 @@ def _get_session(token, access_token, **kwargs):
|
|||
@login_required()
|
||||
def _get_assocd_user(token, access_token, **kwargs):
|
||||
logger.debug("get token {{ {} }}".format(token))
|
||||
session = session_controller.get_session(token, access_token.user)
|
||||
session = sessionController.get_session(token, access_token.user)
|
||||
if not token:
|
||||
# Return 403 error, so that users can not bruteforce tokens
|
||||
# Valid tokens from other users and invalid tokens now are looking the same
|
||||
|
@ -116,7 +114,7 @@ def _get_assocd_user(token, access_token, **kwargs):
|
|||
@auth_bp.route("/auth/<token>", methods=["PUT"])
|
||||
@login_required()
|
||||
def _set_lifetime(token, access_token, **kwargs):
|
||||
token = session_controller.get_token(token, access_token.user)
|
||||
token = sessionController.get_token(token, access_token.user)
|
||||
if not token:
|
||||
# Return 403 error, so that users can not bruteforce tokens
|
||||
# Valid tokens from other users and invalid tokens now are looking the same
|
||||
|
@ -124,7 +122,7 @@ def _set_lifetime(token, access_token, **kwargs):
|
|||
try:
|
||||
lifetime = request.get_json()["value"]
|
||||
logger.debug("set lifetime {{ {} }} to access token {{ {} }}".format(lifetime, token))
|
||||
session_controller.set_lifetime(token, lifetime)
|
||||
sessionController.set_lifetime(token, lifetime)
|
||||
return jsonify({"ok": "ok"})
|
||||
except (KeyError, TypeError):
|
||||
raise BadRequest
|
||||
|
|
|
@ -6,11 +6,12 @@ from flaschengeist.system.decorator import login_required
|
|||
from flaschengeist.system.controller import roleController
|
||||
|
||||
roles_bp = Blueprint("roles", __name__)
|
||||
roles_permission = "roles_edit"
|
||||
|
||||
|
||||
class RolesPlugin(Plugin):
|
||||
def __init__(self, config):
|
||||
super().__init__(config, roles_bp)
|
||||
super().__init__(config, roles_bp, permissions=[roles_permission])
|
||||
|
||||
|
||||
######################################################
|
||||
|
@ -26,8 +27,8 @@ class RolesPlugin(Plugin):
|
|||
|
||||
|
||||
@roles_bp.route("/roles", methods=["POST"])
|
||||
@login_required()
|
||||
def add_role(self):
|
||||
@login_required(permissions=[roles_permission])
|
||||
def add_role(**kwargs):
|
||||
data = request.get_json()
|
||||
if not data or "name" not in data:
|
||||
raise BadRequest
|
||||
|
@ -61,7 +62,7 @@ def __get_role(rid, **kwargs):
|
|||
|
||||
|
||||
@roles_bp.route("/roles/<rid>", methods=["PUT"])
|
||||
@login_required()
|
||||
@login_required(permissions=[roles_permission])
|
||||
def __edit_role(rid, **kwargs):
|
||||
role = roleController.get_role(rid)
|
||||
if not role:
|
||||
|
@ -77,7 +78,7 @@ def __edit_role(rid, **kwargs):
|
|||
|
||||
|
||||
@roles_bp.route("/roles/<rid>", methods=["DELETE"])
|
||||
@login_required()
|
||||
@login_required(permissions=[roles_permission])
|
||||
def __delete_role(rid, **kwargs):
|
||||
if not roleController.delete_role(rid):
|
||||
raise NotFound
|
||||
|
|
|
@ -10,11 +10,13 @@ from flaschengeist.system.decorator import login_required
|
|||
from flaschengeist.system.controller import eventController
|
||||
|
||||
schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule")
|
||||
schedule_perms = {"EDIT_EVENT": "schedule_edit_event",
|
||||
"NEW_EVENT": "schedule_create_event"}
|
||||
|
||||
|
||||
class SchedulePlugin(Plugin):
|
||||
def __init__(self, config):
|
||||
super().__init__(blueprint=schedule_bp)
|
||||
super().__init__(blueprint=schedule_bp, permissions=schedule_perms.values())
|
||||
|
||||
|
||||
####################################################################################
|
||||
|
@ -40,8 +42,8 @@ class SchedulePlugin(Plugin):
|
|||
|
||||
|
||||
@schedule_bp.route("/events/<int:id>", methods=["GET"])
|
||||
@login_required() # roles=['schedule_read'])
|
||||
def __get_event(self, id, **kwargs):
|
||||
@login_required()
|
||||
def __get_event(id, **kwargs):
|
||||
event = eventController.get_event(id)
|
||||
if not event:
|
||||
raise NotFound
|
||||
|
@ -51,7 +53,7 @@ def __get_event(self, id, **kwargs):
|
|||
@schedule_bp.route("/events", methods=["GET"])
|
||||
@schedule_bp.route("/events/<int:year>/<int:month>", methods=["GET"])
|
||||
@schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", methods=["GET"])
|
||||
@login_required() # roles=['schedule_read'])
|
||||
@login_required()
|
||||
def __get_events(year=datetime.now().year, month=datetime.now().month, day=None, **kwargs):
|
||||
"""Get Event objects for specified date (or month or year),
|
||||
if nothing set then events for current month are returned
|
||||
|
@ -104,7 +106,7 @@ def __new_slot_kind(**kwargs):
|
|||
|
||||
|
||||
@schedule_bp.route("/events", methods=["POST"])
|
||||
@login_required()
|
||||
@login_required(permissions=[schedule_perms["NEW_EVENT"]])
|
||||
def __new_event(**kwargs):
|
||||
data = request.get_json()
|
||||
event = eventController.create_event(
|
||||
|
@ -117,7 +119,7 @@ def __new_event(**kwargs):
|
|||
|
||||
|
||||
@schedule_bp.route("/events/<int:event_id>", methods=["DELETE"])
|
||||
@login_required()
|
||||
@login_required(permissions=[schedule_perms["EDIT_EVENT"]])
|
||||
def __delete_event(event_id, **kwargs):
|
||||
if not eventController.delete_event(event_id):
|
||||
raise NotFound
|
||||
|
|
|
@ -7,12 +7,12 @@ from flaschengeist.system.decorator import login_required
|
|||
from flaschengeist.system.controller import userController
|
||||
|
||||
users_bp = Blueprint("users", __name__)
|
||||
permissions = {"EDIT_USER": "edit_user"}
|
||||
users_perm = "users_edit_other"
|
||||
|
||||
|
||||
class UsersPlugin(Plugin):
|
||||
def __init__(self, config):
|
||||
super().__init__(blueprint=users_bp, permissions=permissions)
|
||||
super().__init__(blueprint=users_bp, permissions=[users_perm])
|
||||
|
||||
#################################################
|
||||
# Routes #
|
||||
|
@ -57,7 +57,7 @@ def __edit_user(uid, **kwargs):
|
|||
if not user:
|
||||
raise NotFound
|
||||
|
||||
if uid != kwargs["access_token"].user.userid and user.has_permissions(permissions["EDIT_USER"]):
|
||||
if uid != kwargs["access_token"].user.userid and user.has_permissions([users_perm]):
|
||||
return Forbidden
|
||||
|
||||
data = request.get_json()
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
|
@ -1,4 +1,3 @@
|
|||
from flask import current_app
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
|
|
@ -4,116 +4,112 @@ from flaschengeist.system.database import db
|
|||
from flaschengeist import logger
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from datetime import datetime, timezone
|
||||
from . import Singleton
|
||||
|
||||
lifetime = 1800
|
||||
|
||||
|
||||
class SessionController(metaclass=Singleton):
|
||||
"""Control all created Sessions
|
||||
def validate_token(token, user_agent, permissions):
|
||||
"""Verify session
|
||||
|
||||
This Class create, delete, find and manage Sessions.
|
||||
Verify a Session and Roles so if the User has permission or not.
|
||||
Retrieves the access token if valid else retrieves False
|
||||
|
||||
Attributes:
|
||||
lifetime: Variable for the Lifetime of a Session in seconds.
|
||||
Args:
|
||||
token: Token to verify.
|
||||
user_agent: User agent of browser to check
|
||||
permissions: Permissions needed to access restricted routes
|
||||
Returns:
|
||||
A Session for this given Token or False.
|
||||
"""
|
||||
logger.debug("check token {{ {} }} is valid".format(token))
|
||||
access_token = Session.query.filter_by(token=token).one_or_none()
|
||||
if access_token:
|
||||
logger.debug("token found, check if expired or invalid user agent differs")
|
||||
if access_token.expires >= datetime.now(timezone.utc) and (
|
||||
access_token.browser == user_agent.browser and access_token.platform == user_agent.platform
|
||||
):
|
||||
if not permissions or access_token.user.has_permissions(permissions):
|
||||
access_token.refresh()
|
||||
db.session.commit()
|
||||
return access_token
|
||||
else:
|
||||
logger.debug("access token is out of date or invalid client used")
|
||||
delete_session(access_token)
|
||||
logger.debug("no valid access token with token: {{ {} }} and permissions: {{ {} }}".format(token, permissions))
|
||||
return False
|
||||
|
||||
def __init__(self, lifetime=1800):
|
||||
self.lifetime = lifetime
|
||||
|
||||
def validate_token(self, token, user_agent, permissions):
|
||||
"""Verify session
|
||||
def create(user, user_agent=None) -> Session:
|
||||
"""Create a Session
|
||||
|
||||
Verify a Session and Roles so if the User has permission or not.
|
||||
Retrieves the access token if valid else retrieves False
|
||||
Args:
|
||||
user: For which User is to create a Session
|
||||
user_agent: User agent to identify session
|
||||
|
||||
Args:
|
||||
token: Token to verify.
|
||||
user_agent: User agent of browser to check
|
||||
permissions: Permissions needed to access restricted routes
|
||||
Returns:
|
||||
A Session for this given Token or False.
|
||||
"""
|
||||
logger.debug("check token {{ {} }} is valid".format(token))
|
||||
access_token = Session.query.filter_by(token=token).one_or_none()
|
||||
if access_token:
|
||||
logger.debug("token found, check if expired or invalid user agent differs")
|
||||
if access_token.expires >= datetime.now(timezone.utc) and (
|
||||
access_token.browser == user_agent.browser and access_token.platform == user_agent.platform
|
||||
):
|
||||
if not permissions or access_token.user.has_permissions(permissions):
|
||||
access_token.refresh()
|
||||
db.session.commit()
|
||||
return access_token
|
||||
else:
|
||||
logger.debug("access token is out of date or invalid client used")
|
||||
self.delete_session(access_token)
|
||||
logger.debug("no valid access token with token: {{ {} }} and permissions: {{ {} }}".format(token, permissions))
|
||||
return False
|
||||
Returns:
|
||||
Session: A created Token for User
|
||||
"""
|
||||
logger.debug("create access token")
|
||||
token_str = secrets.token_hex(16)
|
||||
session = Session(
|
||||
token=token_str,
|
||||
_user=user,
|
||||
lifetime=lifetime,
|
||||
browser=user_agent.browser,
|
||||
platform=user_agent.platform,
|
||||
)
|
||||
session.refresh()
|
||||
db.session.add(session)
|
||||
db.session.commit()
|
||||
logger.debug("access token is {{ {} }}".format(session.token))
|
||||
return session
|
||||
|
||||
def create(self, user, user_agent=None) -> Session:
|
||||
"""Create a Session
|
||||
|
||||
Args:
|
||||
user: For which User is to create a Session
|
||||
user_agent: User agent to identify session
|
||||
def get_session(token, owner=None):
|
||||
"""Retrieves Session from token string
|
||||
|
||||
Returns:
|
||||
Session: A created Token for User
|
||||
"""
|
||||
logger.debug("create access token")
|
||||
token_str = secrets.token_hex(16)
|
||||
session = Session(
|
||||
token=token_str,
|
||||
_user=user,
|
||||
lifetime=self.lifetime,
|
||||
browser=user_agent.browser,
|
||||
platform=user_agent.platform,
|
||||
)
|
||||
session.refresh()
|
||||
db.session.add(session)
|
||||
db.session.commit()
|
||||
logger.debug("access token is {{ {} }}".format(session.token))
|
||||
return session
|
||||
Args:
|
||||
token (str): Token string
|
||||
owner (User, optional): User owning the token
|
||||
|
||||
def get_session(self, token, owner=None):
|
||||
"""Retrieves Session from token string
|
||||
Raises:
|
||||
Forbidden: Raised if owner is set but does not match
|
||||
Returns:
|
||||
Session: Token object identified by given token string
|
||||
"""
|
||||
session = Session.query.filter(Session.token == token).one_or_none()
|
||||
if session and (owner and owner != session.user):
|
||||
raise Forbidden
|
||||
return session
|
||||
|
||||
Args:
|
||||
token (str): Token string
|
||||
owner (User, optional): User owning the token
|
||||
|
||||
Raises:
|
||||
Forbidden: Raised if owner is set but does not match
|
||||
Returns:
|
||||
Session: Token object identified by given token string
|
||||
"""
|
||||
session = Session.query.filter(Session.token == token).one_or_none()
|
||||
if session and (owner and owner != session.user):
|
||||
raise Forbidden
|
||||
return session
|
||||
def get_users_sessions(user):
|
||||
return Session.query.filter(Session._user == user)
|
||||
|
||||
def get_users_sessions(self, user):
|
||||
return Session.query.filter(Session._user == user)
|
||||
|
||||
def delete_session(self, token: Session):
|
||||
"""Deletes given Session
|
||||
def delete_session(token: Session):
|
||||
"""Deletes given Session
|
||||
|
||||
Args:
|
||||
token (Session): Token to delete
|
||||
"""
|
||||
db.session.delete(token)
|
||||
db.session.commit()
|
||||
Args:
|
||||
token (Session): Token to delete
|
||||
"""
|
||||
db.session.delete(token)
|
||||
db.session.commit()
|
||||
|
||||
def update_session(self, session):
|
||||
session.refresh()
|
||||
db.session.commit()
|
||||
|
||||
def set_lifetime(self, session, lifetime):
|
||||
session.lifetime = lifetime
|
||||
self.update_session(session)
|
||||
def update_session(session):
|
||||
session.refresh()
|
||||
db.session.commit()
|
||||
|
||||
def clear_expired(self):
|
||||
"""Remove expired tokens from database"""
|
||||
logger.debug("Clear expired Sessions")
|
||||
deleted = Session.query.filter(Session.expires < datetime.now(timezone.utc)).delete()
|
||||
logger.debug("{} sessions have been removed".format(deleted))
|
||||
db.session.commit()
|
||||
|
||||
def set_lifetime(session, lifetime):
|
||||
session.lifetime = lifetime
|
||||
update_session(session)
|
||||
|
||||
|
||||
def clear_expired():
|
||||
"""Remove expired tokens from database"""
|
||||
logger.debug("Clear expired Sessions")
|
||||
deleted = Session.query.filter(Session.expires < datetime.now(timezone.utc)).delete()
|
||||
logger.debug("{} sessions have been removed".format(deleted))
|
||||
db.session.commit()
|
||||
|
|
|
@ -3,21 +3,15 @@ from flask import request
|
|||
from werkzeug.exceptions import Unauthorized
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.system.controller import sessionController
|
||||
|
||||
|
||||
def login_required(**kwargs):
|
||||
from .controller.sessionController import SessionController
|
||||
|
||||
ac_controller = SessionController()
|
||||
permissions = None
|
||||
if "permissions" in kwargs:
|
||||
permissions = kwargs["roles"]
|
||||
|
||||
def real_decorator(func):
|
||||
def login_required(permissions=None):
|
||||
def wrap(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
token = request.headers.get("Authorization").split(" ")[-1]
|
||||
access_token = ac_controller.validate_token(token, request.user_agent, permissions)
|
||||
def wrapped_f(*args, **kwargs):
|
||||
token = list(filter(None, request.headers.get("Authorization").split(" ")))[-1]
|
||||
access_token = sessionController.validate_token(token, request.user_agent, permissions)
|
||||
if access_token:
|
||||
kwargs["access_token"] = access_token
|
||||
logger.debug("token {{ {} }} is valid".format(token))
|
||||
|
@ -25,7 +19,5 @@ def login_required(**kwargs):
|
|||
else:
|
||||
logger.info("token {{ {} }} is not valid".format(token))
|
||||
raise Unauthorized
|
||||
|
||||
return wrapper
|
||||
|
||||
return real_decorator
|
||||
return wrapped_f
|
||||
return wrap
|
||||
|
|
Loading…
Reference in New Issue