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