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 import Blueprint, request, jsonify
from flask.views import MethodView 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 import db
from sachet.server.views_common import auth_required, ModelAPI from sachet.server.views_common import auth_required, ModelAPI
@ -18,17 +18,17 @@ class ServerSettingsAPI(ModelAPI):
return settings return settings
return rows[-1] return rows[-1]
@auth_required(require_admin=True) @auth_required(required_permissions=(Permissions.ADMIN,))
def get(self, auth_user=None): def get(self, auth_user=None):
settings = self.get_settings() settings = self.get_settings()
return super().get(settings) return super().get(settings)
@auth_required(require_admin=True) @auth_required(required_permissions=(Permissions.ADMIN,))
def patch(self, auth_user=None): def patch(self, auth_user=None):
settings = self.get_settings() settings = self.get_settings()
return super().patch(settings) return super().patch(settings)
@auth_required(require_admin=True) @auth_required(required_permissions=(Permissions.ADMIN,))
def put(self, auth_user=None): def put(self, auth_user=None):
settings = self.get_settings() settings = self.get_settings()
return super().put(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) return super().get(info_user)
@auth_required(require_admin=True) @auth_required(required_permissions=(Permissions.ADMIN,))
def patch(self, username, auth_user=None): def patch(self, username, auth_user=None):
patch_user = User.query.filter_by(username=username).first() patch_user = User.query.filter_by(username=username).first()
return super().patch(patch_user) return super().patch(patch_user)
@auth_required(require_admin=True) @auth_required(required_permissions=(Permissions.ADMIN,))
def put(self, username, auth_user=None): def put(self, username, auth_user=None):
put_user = User.query.filter_by(username=username).first() put_user = User.query.filter_by(username=username).first()
return super().put(put_user) return super().put(put_user)

View File

@ -1,16 +1,24 @@
from flask import request, jsonify from flask import request, jsonify
from flask.views import MethodView from flask.views import MethodView
from sachet.server.models import Permissions, User, BlacklistToken from sachet.server.models import Permissions, User, BlacklistToken
from sachet.server import db
from functools import wraps from functools import wraps
from marshmallow import ValidationError from marshmallow import ValidationError
from bitmask import Bitmask
import jwt import jwt
def auth_required(func=None, *, require_admin=False): def auth_required(func=None, *, required_permissions=()):
"""Decorator to require authentication. """Decorator to require authentication.
Passes an argument 'user' to the function, with a User object corresponding Passes an argument 'user' to the function, with a User object corresponding
to the authenticated session. 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 # see https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments
@ -44,12 +52,15 @@ def auth_required(func=None, *, require_admin=False):
401, 401,
) )
if require_admin and Permissions.ADMIN not in user.permissions: if (
Bitmask(AllFlags=Permissions, *required_permissions)
not in user.permissions
):
return ( return (
jsonify( jsonify(
{ {
"status": "fail", "status": "fail",
"message": "Administrator permission is required to see this page.", "message": "Missing permissions to access this page.",
} }
), ),
403, 403,
@ -112,6 +123,8 @@ class ModelAPI(MethodView):
for k, v in deserialized.items(): for k, v in deserialized.items():
setattr(model, k, v) setattr(model, k, v)
db.session.commit()
resp = { resp = {
"status": "success", "status": "success",
} }
@ -138,7 +151,37 @@ class ModelAPI(MethodView):
for k, v in deserialized.items(): for k, v in deserialized.items():
setattr(model, k, v) setattr(model, k, v)
db.session.commit()
resp = { resp = {
"status": "success", "status": "success",
} }
return jsonify(resp), 200 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