From 2fba5747732239b6be7ee5a1e84870fcf31bcd9f Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Mon, 10 Apr 2023 22:13:26 -0400 Subject: [PATCH] sachet/server: auth_required now has more modular permissions there is no more require_admin, because ADMIN is just one of many permissions --- sachet/server/admin/views.py | 8 +++--- sachet/server/files/views.py | 35 +++++++++++++++++++++++++ sachet/server/users/views.py | 4 +-- sachet/server/views_common.py | 49 ++++++++++++++++++++++++++++++++--- 4 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 sachet/server/files/views.py diff --git a/sachet/server/admin/views.py b/sachet/server/admin/views.py index b91fb5e..ba839f6 100644 --- a/sachet/server/admin/views.py +++ b/sachet/server/admin/views.py @@ -1,6 +1,6 @@ from flask import Blueprint, request, jsonify from flask.views import MethodView -from sachet.server.models import ServerSettings +from sachet.server.models import ServerSettings, Permissions from sachet.server import db from sachet.server.views_common import auth_required, ModelAPI @@ -18,17 +18,17 @@ class ServerSettingsAPI(ModelAPI): return settings return rows[-1] - @auth_required(require_admin=True) + @auth_required(required_permissions=(Permissions.ADMIN,)) def get(self, auth_user=None): settings = self.get_settings() return super().get(settings) - @auth_required(require_admin=True) + @auth_required(required_permissions=(Permissions.ADMIN,)) def patch(self, auth_user=None): settings = self.get_settings() return super().patch(settings) - @auth_required(require_admin=True) + @auth_required(required_permissions=(Permissions.ADMIN,)) def put(self, auth_user=None): settings = self.get_settings() return super().put(settings) diff --git a/sachet/server/files/views.py b/sachet/server/files/views.py new file mode 100644 index 0000000..f16d655 --- /dev/null +++ b/sachet/server/files/views.py @@ -0,0 +1,35 @@ +import jwt +from flask import Blueprint, request, jsonify +from flask.views import MethodView +from sachet.server.models import File, Permissions +from sachet.server.views_common import ModelAPI, auth_required + +files_blueprint = Blueprint("files_blueprint", __name__) + + +class FilesAPI(ModelAPI): + """Files metadata API.""" + + @auth_required + def get(self, id, auth_user=None): + pass + + +files_blueprint.add_url_rule( + "/files/", + view_func=FilesAPI.as_view("files_api"), + methods=["POST", "PUT", "PATCH", "GET", "DELETE"], +) + +files_blueprint.add_url_rule( + "/files//content", + view_func=FilesContentAPI.as_view("files_content_api"), + methods=["PUT", "GET"], +) + + +users_blueprint.add_url_rule( + "/users/", + view_func=UserAPI.as_view("user_api"), + methods=["GET", "PATCH", "PUT"], +) diff --git a/sachet/server/users/views.py b/sachet/server/users/views.py index 4ab1609..019e449 100644 --- a/sachet/server/users/views.py +++ b/sachet/server/users/views.py @@ -126,12 +126,12 @@ class UserAPI(ModelAPI): return super().get(info_user) - @auth_required(require_admin=True) + @auth_required(required_permissions=(Permissions.ADMIN,)) def patch(self, username, auth_user=None): patch_user = User.query.filter_by(username=username).first() return super().patch(patch_user) - @auth_required(require_admin=True) + @auth_required(required_permissions=(Permissions.ADMIN,)) def put(self, username, auth_user=None): put_user = User.query.filter_by(username=username).first() return super().put(put_user) diff --git a/sachet/server/views_common.py b/sachet/server/views_common.py index 60142a5..f59ce3f 100644 --- a/sachet/server/views_common.py +++ b/sachet/server/views_common.py @@ -1,16 +1,24 @@ from flask import request, jsonify from flask.views import MethodView from sachet.server.models import Permissions, User, BlacklistToken +from sachet.server import db from functools import wraps from marshmallow import ValidationError +from bitmask import Bitmask import jwt -def auth_required(func=None, *, require_admin=False): +def auth_required(func=None, *, required_permissions=()): """Decorator to require authentication. Passes an argument 'user' to the function, with a User object corresponding to the authenticated session. + + Parameters + ---------- + + required_permissions : tuple of Permissions + Permissions required to access this endpoint. """ # see https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments @@ -44,12 +52,15 @@ def auth_required(func=None, *, require_admin=False): 401, ) - if require_admin and Permissions.ADMIN not in user.permissions: + if ( + Bitmask(AllFlags=Permissions, *required_permissions) + not in user.permissions + ): return ( jsonify( { "status": "fail", - "message": "Administrator permission is required to see this page.", + "message": "Missing permissions to access this page.", } ), 403, @@ -112,6 +123,8 @@ class ModelAPI(MethodView): for k, v in deserialized.items(): setattr(model, k, v) + db.session.commit() + resp = { "status": "success", } @@ -138,7 +151,37 @@ class ModelAPI(MethodView): for k, v in deserialized.items(): setattr(model, k, v) + db.session.commit() + resp = { "status": "success", } return jsonify(resp), 200 + + def delete(self, model): + if not model: + resp = { + "status": "fail", + "message": "This resource does not exist.", + } + return jsonify(resp), 404 + + model.delete() + db.session.commit() + + return jsonify({"status": "success"}) + + def post(self, ModelClass, data): + model_schema = ModelClass.get_schema() + + post_json = request.get_json() + try: + deserialized = model_schema.load(post_json) + except ValidationError as e: + resp = {"status": "fail", "message": f"Invalid data: {str(e)}"} + return jsonify(resp), 400 + + # create new ModelClass instance with all the parameters given in the request + model = ModelClass(**deserialized) + + return jsonify({"status": "success"}), 201