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: class Plugin:
def __init__(self, config=None, blueprint=None, permissions={}): def __init__(self, config=None, blueprint=None, permissions=[]):
self.blueprint = blueprint self.blueprint = blueprint
self.permissions = permissions self.permissions = permissions
self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version self.version = pkg_resources.get_distribution(self.__module__.split(".")[0]).version
@ -20,6 +20,7 @@ class Plugin:
def serialize(self): def serialize(self):
return { return {
"version": self.version, "version": self.version,
"permissions": self.permissions
} }

View File

@ -6,7 +6,6 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from werkzeug.exceptions import Forbidden, BadRequest, Unauthorized from werkzeug.exceptions import Forbidden, BadRequest, Unauthorized
from werkzeug.local import LocalProxy
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.modules import Plugin 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.controller import sessionController, userController, messageController
from flaschengeist.system.models.session import Session from flaschengeist.system.models.session import Session
session_controller: sessionController.SessionController = LocalProxy(lambda: sessionController.SessionController())
auth_bp = Blueprint("auth", __name__) auth_bp = Blueprint("auth", __name__)
@ -56,19 +54,19 @@ def _login():
if not user: if not user:
raise Unauthorized raise Unauthorized
logger.debug("user is {{ {} }}".format(user)) 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.debug("token is {{ {} }}".format(session.token))
logger.info("User {{ {} }} success login.".format(userid)) logger.info("User {{ {} }} success login.".format(userid))
# Lets cleanup the DB # Lets cleanup the DB
session_controller.clear_expired() sessionController.clear_expired()
return jsonify({"session": session, "user": user}) return jsonify({"session": session, "user": user})
@auth_bp.route("/auth", methods=["GET"]) @auth_bp.route("/auth", methods=["GET"])
@login_required() @login_required()
def _get_sessions(access_token: Session, **kwargs): 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") a = messageController.Message(access_token._user, "Go", "Bar")
messageController.send_message(a) messageController.send_message(a)
return jsonify(tokens) return jsonify(tokens)
@ -78,14 +76,14 @@ def _get_sessions(access_token: Session, **kwargs):
@login_required() @login_required()
def _delete_session(access_token, token, **kwargs): def _delete_session(access_token, token, **kwargs):
logger.debug("Try to delete access token {{ {} }}".format(token)) 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: if not token:
logger.debug("Token not found in database!") logger.debug("Token not found in database!")
# Return 403 error, so that users can not bruteforce tokens # Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same # Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden raise Forbidden
session_controller.delete_session(token) sessionController.delete_session(token)
session_controller.clear_expired() sessionController.clear_expired()
return jsonify({"ok": "ok"}) return jsonify({"ok": "ok"})
@ -93,7 +91,7 @@ def _delete_session(access_token, token, **kwargs):
@login_required() @login_required()
def _get_session(token, access_token, **kwargs): def _get_session(token, access_token, **kwargs):
logger.debug("get token {{ {} }}".format(token)) 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: if not token:
# Return 403 error, so that users can not bruteforce tokens # Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same # 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() @login_required()
def _get_assocd_user(token, access_token, **kwargs): def _get_assocd_user(token, access_token, **kwargs):
logger.debug("get token {{ {} }}".format(token)) 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: if not token:
# Return 403 error, so that users can not bruteforce tokens # Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same # 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"]) @auth_bp.route("/auth/<token>", methods=["PUT"])
@login_required() @login_required()
def _set_lifetime(token, access_token, **kwargs): 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: if not token:
# Return 403 error, so that users can not bruteforce tokens # Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same # 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: try:
lifetime = request.get_json()["value"] lifetime = request.get_json()["value"]
logger.debug("set lifetime {{ {} }} to access token {{ {} }}".format(lifetime, token)) 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"}) return jsonify({"ok": "ok"})
except (KeyError, TypeError): except (KeyError, TypeError):
raise BadRequest raise BadRequest

View File

@ -6,11 +6,12 @@ from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import roleController from flaschengeist.system.controller import roleController
roles_bp = Blueprint("roles", __name__) roles_bp = Blueprint("roles", __name__)
roles_permission = "roles_edit"
class RolesPlugin(Plugin): class RolesPlugin(Plugin):
def __init__(self, config): 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"]) @roles_bp.route("/roles", methods=["POST"])
@login_required() @login_required(permissions=[roles_permission])
def add_role(self): def add_role(**kwargs):
data = request.get_json() data = request.get_json()
if not data or "name" not in data: if not data or "name" not in data:
raise BadRequest raise BadRequest
@ -61,7 +62,7 @@ def __get_role(rid, **kwargs):
@roles_bp.route("/roles/<rid>", methods=["PUT"]) @roles_bp.route("/roles/<rid>", methods=["PUT"])
@login_required() @login_required(permissions=[roles_permission])
def __edit_role(rid, **kwargs): def __edit_role(rid, **kwargs):
role = roleController.get_role(rid) role = roleController.get_role(rid)
if not role: if not role:
@ -77,7 +78,7 @@ def __edit_role(rid, **kwargs):
@roles_bp.route("/roles/<rid>", methods=["DELETE"]) @roles_bp.route("/roles/<rid>", methods=["DELETE"])
@login_required() @login_required(permissions=[roles_permission])
def __delete_role(rid, **kwargs): def __delete_role(rid, **kwargs):
if not roleController.delete_role(rid): if not roleController.delete_role(rid):
raise NotFound raise NotFound

View File

@ -10,11 +10,13 @@ from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import eventController from flaschengeist.system.controller import eventController
schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule") schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule")
schedule_perms = {"EDIT_EVENT": "schedule_edit_event",
"NEW_EVENT": "schedule_create_event"}
class SchedulePlugin(Plugin): class SchedulePlugin(Plugin):
def __init__(self, config): 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"]) @schedule_bp.route("/events/<int:id>", methods=["GET"])
@login_required() # roles=['schedule_read']) @login_required()
def __get_event(self, id, **kwargs): def __get_event(id, **kwargs):
event = eventController.get_event(id) event = eventController.get_event(id)
if not event: if not event:
raise NotFound raise NotFound
@ -51,7 +53,7 @@ def __get_event(self, id, **kwargs):
@schedule_bp.route("/events", methods=["GET"]) @schedule_bp.route("/events", methods=["GET"])
@schedule_bp.route("/events/<int:year>/<int:month>", methods=["GET"]) @schedule_bp.route("/events/<int:year>/<int:month>", methods=["GET"])
@schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", 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): def __get_events(year=datetime.now().year, month=datetime.now().month, day=None, **kwargs):
"""Get Event objects for specified date (or month or year), """Get Event objects for specified date (or month or year),
if nothing set then events for current month are returned 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"]) @schedule_bp.route("/events", methods=["POST"])
@login_required() @login_required(permissions=[schedule_perms["NEW_EVENT"]])
def __new_event(**kwargs): def __new_event(**kwargs):
data = request.get_json() data = request.get_json()
event = eventController.create_event( event = eventController.create_event(
@ -117,7 +119,7 @@ def __new_event(**kwargs):
@schedule_bp.route("/events/<int:event_id>", methods=["DELETE"]) @schedule_bp.route("/events/<int:event_id>", methods=["DELETE"])
@login_required() @login_required(permissions=[schedule_perms["EDIT_EVENT"]])
def __delete_event(event_id, **kwargs): def __delete_event(event_id, **kwargs):
if not eventController.delete_event(event_id): if not eventController.delete_event(event_id):
raise NotFound raise NotFound

View File

@ -7,12 +7,12 @@ from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import userController from flaschengeist.system.controller import userController
users_bp = Blueprint("users", __name__) users_bp = Blueprint("users", __name__)
permissions = {"EDIT_USER": "edit_user"} users_perm = "users_edit_other"
class UsersPlugin(Plugin): class UsersPlugin(Plugin):
def __init__(self, config): def __init__(self, config):
super().__init__(blueprint=users_bp, permissions=permissions) super().__init__(blueprint=users_bp, permissions=[users_perm])
################################################# #################################################
# Routes # # Routes #
@ -57,7 +57,7 @@ def __edit_user(uid, **kwargs):
if not user: if not user:
raise NotFound 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 return Forbidden
data = request.get_json() 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 sqlalchemy.exc import IntegrityError
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest

View File

@ -4,22 +4,11 @@ from flaschengeist.system.database import db
from flaschengeist import logger from flaschengeist import logger
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from datetime import datetime, timezone from datetime import datetime, timezone
from . import Singleton
lifetime = 1800
class SessionController(metaclass=Singleton): def validate_token(token, user_agent, permissions):
"""Control all created Sessions
This Class create, delete, find and manage Sessions.
Attributes:
lifetime: Variable for the Lifetime of a Session in seconds.
"""
def __init__(self, lifetime=1800):
self.lifetime = lifetime
def validate_token(self, token, user_agent, permissions):
"""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.
@ -45,11 +34,12 @@ class SessionController(metaclass=Singleton):
return access_token return access_token
else: else:
logger.debug("access token is out of date or invalid client used") logger.debug("access token is out of date or invalid client used")
self.delete_session(access_token) delete_session(access_token)
logger.debug("no valid access token with token: {{ {} }} and permissions: {{ {} }}".format(token, permissions)) logger.debug("no valid access token with token: {{ {} }} and permissions: {{ {} }}".format(token, permissions))
return False return False
def create(self, user, user_agent=None) -> Session:
def create(user, user_agent=None) -> Session:
"""Create a Session """Create a Session
Args: Args:
@ -64,7 +54,7 @@ class SessionController(metaclass=Singleton):
session = Session( session = Session(
token=token_str, token=token_str,
_user=user, _user=user,
lifetime=self.lifetime, lifetime=lifetime,
browser=user_agent.browser, browser=user_agent.browser,
platform=user_agent.platform, platform=user_agent.platform,
) )
@ -74,7 +64,8 @@ class SessionController(metaclass=Singleton):
logger.debug("access token is {{ {} }}".format(session.token)) logger.debug("access token is {{ {} }}".format(session.token))
return session return session
def get_session(self, token, owner=None):
def get_session(token, owner=None):
"""Retrieves Session from token string """Retrieves Session from token string
Args: Args:
@ -91,10 +82,12 @@ class SessionController(metaclass=Singleton):
raise Forbidden raise Forbidden
return session return session
def get_users_sessions(self, user):
def get_users_sessions(user):
return Session.query.filter(Session._user == user) return Session.query.filter(Session._user == user)
def delete_session(self, token: Session):
def delete_session(token: Session):
"""Deletes given Session """Deletes given Session
Args: Args:
@ -103,15 +96,18 @@ class SessionController(metaclass=Singleton):
db.session.delete(token) db.session.delete(token)
db.session.commit() db.session.commit()
def update_session(self, session):
def update_session(session):
session.refresh() session.refresh()
db.session.commit() db.session.commit()
def set_lifetime(self, session, lifetime):
session.lifetime = lifetime
self.update_session(session)
def clear_expired(self): def set_lifetime(session, lifetime):
session.lifetime = lifetime
update_session(session)
def clear_expired():
"""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.now(timezone.utc)).delete() deleted = Session.query.filter(Session.expires < datetime.now(timezone.utc)).delete()

View File

@ -3,21 +3,15 @@ from flask import request
from werkzeug.exceptions import Unauthorized from werkzeug.exceptions import Unauthorized
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.system.controller import sessionController
def login_required(**kwargs): def login_required(permissions=None):
from .controller.sessionController import SessionController def wrap(func):
ac_controller = SessionController()
permissions = None
if "permissions" in kwargs:
permissions = kwargs["roles"]
def real_decorator(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapped_f(*args, **kwargs):
token = request.headers.get("Authorization").split(" ")[-1] token = list(filter(None, request.headers.get("Authorization").split(" ")))[-1]
access_token = ac_controller.validate_token(token, request.user_agent, permissions) access_token = sessionController.validate_token(token, request.user_agent, permissions)
if access_token: if access_token:
kwargs["access_token"] = access_token kwargs["access_token"] = access_token
logger.debug("token {{ {} }} is valid".format(token)) logger.debug("token {{ {} }} is valid".format(token))
@ -25,7 +19,5 @@ def login_required(**kwargs):
else: else:
logger.info("token {{ {} }} is not valid".format(token)) logger.info("token {{ {} }} is not valid".format(token))
raise Unauthorized raise Unauthorized
return wrapped_f
return wrapper return wrap
return real_decorator