sachet/server: auth_required now has more modular permissions

there is no more require_admin, because ADMIN is just one of many
permissions
This commit is contained in:
dogeystamp 2023-04-10 22:13:26 -04:00
parent 6749391b3c
commit 2fba574773
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
4 changed files with 87 additions and 9 deletions

View File

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

View File

@ -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/<id>",
view_func=FilesAPI.as_view("files_api"),
methods=["POST", "PUT", "PATCH", "GET", "DELETE"],
)
files_blueprint.add_url_rule(
"/files/<id>/content",
view_func=FilesContentAPI.as_view("files_content_api"),
methods=["PUT", "GET"],
)
users_blueprint.add_url_rule(
"/users/<username>",
view_func=UserAPI.as_view("user_api"),
methods=["GET", "PATCH", "PUT"],
)

View File

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

View File

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