Improved some permission related stuff, rewrote session controller

This commit is contained in:
Ferdinand Thiessen 2020-10-20 18:52:02 +02:00
parent db96d4b178
commit 854a1f6156
9 changed files with 125 additions and 143 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -1,4 +1,3 @@
from flask import current_app
from sqlalchemy.exc import IntegrityError
from werkzeug.exceptions import BadRequest

View File

@ -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()

View File

@ -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