added permission checks to user and recorder API

This commit is contained in:
Tobias Kurze
2020-08-06 15:23:14 +02:00
parent 82b3e78488
commit 437cec38e0
6 changed files with 59 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ Backend base module
""" """
import logging import logging
import os import os
from functools import wraps
from io import StringIO from io import StringIO
from logging.config import dictConfig from logging.config import dictConfig
from logging.handlers import MemoryHandler from logging.handlers import MemoryHandler
@@ -14,7 +15,7 @@ import jwt
import requests import requests
from flask import Flask, jsonify, abort from flask import Flask, jsonify, abort
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
from flask_jwt_extended import JWTManager, decode_token from flask_jwt_extended import JWTManager, decode_token, get_jwt_identity
from flask_login import LoginManager from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS from flask_cors import CORS
@@ -179,6 +180,7 @@ CORS(auth_api_bp)
logging.getLogger('flask_cors').level = logging.DEBUG logging.getLogger('flask_cors').level = logging.DEBUG
# Fix jwt_extended by 'duck typing' error handlers # Fix jwt_extended by 'duck typing' error handlers
# jwt_extended._set_error_handler_callbacks(api_v1) # removed for the moment, might raise new (old) problems # jwt_extended._set_error_handler_callbacks(api_v1) # removed for the moment, might raise new (old) problems

View File

@@ -9,9 +9,10 @@ from pprint import pprint
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_restx import fields, Resource, inputs from flask_restx import fields, Resource, inputs
from backend import db, app, LrcException from backend import db, app, LrcException, Config
from backend.api import api_recorder from backend.api import api_recorder
from backend.api.models import recorder_model, recorder_model_model, recorder_command_model from backend.api.models import recorder_model, recorder_model_model, recorder_command_model
from backend.auth.utils import requires_permission_level
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room from backend.models.room_model import Room
import backend.recorder_adapters as r_a import backend.recorder_adapters as r_a
@@ -25,6 +26,7 @@ logger = logging.getLogger("lrc.api.recorder")
@api_recorder.param('id', 'The recorder identifier') @api_recorder.param('id', 'The recorder identifier')
class RecorderResource(Resource): class RecorderResource(Resource):
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECODER_SHOW)
@api_recorder.doc('get_recorder') @api_recorder.doc('get_recorder')
@api_recorder.marshal_with(recorder_model, skip_none=False) @api_recorder.marshal_with(recorder_model, skip_none=False)
def get(self, id): def get(self, id):
@@ -35,6 +37,7 @@ class RecorderResource(Resource):
api_recorder.abort(404) api_recorder.abort(404)
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDER_DELETE)
@api_recorder.doc('delete_todo') @api_recorder.doc('delete_todo')
@api_recorder.response(204, 'Todo deleted') @api_recorder.response(204, 'Todo deleted')
def delete(self, id): def delete(self, id):
@@ -65,6 +68,7 @@ class RecorderResource(Resource):
required=False, store_missing=False) required=False, store_missing=False)
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDER_EDIT)
@api_recorder.doc('update_recorder') @api_recorder.doc('update_recorder')
@api_recorder.expect(recorder_model) @api_recorder.expect(recorder_model)
def put(self, id): def put(self, id):
@@ -85,6 +89,7 @@ class RecorderResource(Resource):
@api_recorder.route('') @api_recorder.route('')
class RecorderList(Resource): class RecorderList(Resource):
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDERS_LIST)
@api_recorder.doc('recorders') @api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model, skip_none=False) @api_recorder.marshal_list_with(recorder_model, skip_none=False)
def get(self): def get(self):
@@ -95,6 +100,7 @@ class RecorderList(Resource):
return Recorder.get_all() return Recorder.get_all()
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECODER_NEW)
@api_recorder.doc('create_recorder') @api_recorder.doc('create_recorder')
@api_recorder.expect(recorder_model) @api_recorder.expect(recorder_model)
@api_recorder.marshal_with(recorder_model, skip_none=False, code=201) @api_recorder.marshal_with(recorder_model, skip_none=False, code=201)
@@ -161,6 +167,7 @@ class RecorderModelResource(Resource):
@api_recorder.route('/model') @api_recorder.route('/model')
class RecorderModelList(Resource): class RecorderModelList(Resource):
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECODER_MODELS_LIST)
@api_recorder.doc('recorders') @api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model_model) @api_recorder.marshal_list_with(recorder_model_model)
def get(self): def get(self):
@@ -172,6 +179,7 @@ class RecorderModelList(Resource):
@api_recorder.param('id', 'The recorder command identifier') @api_recorder.param('id', 'The recorder command identifier')
class RecorderCommandResource(Resource): class RecorderCommandResource(Resource):
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMAND_SHOW)
@api_recorder.doc('get_recorder_command') @api_recorder.doc('get_recorder_command')
@api_recorder.marshal_with(recorder_command_model) @api_recorder.marshal_with(recorder_command_model)
def get(self, id): def get(self, id):
@@ -186,6 +194,7 @@ class RecorderCommandResource(Resource):
recorder_command_model_parser.add_argument('alternative_name', type=str, required=False) recorder_command_model_parser.add_argument('alternative_name', type=str, required=False)
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMAND_EDIT)
@api_recorder.doc('update_recorder_command') @api_recorder.doc('update_recorder_command')
@api_recorder.expect(recorder_command_model_parser) @api_recorder.expect(recorder_command_model_parser)
@api_recorder.marshal_with(recorder_command_model) @api_recorder.marshal_with(recorder_command_model)
@@ -201,6 +210,7 @@ class RecorderCommandResource(Resource):
@api_recorder.route('/command') @api_recorder.route('/command')
class RecorderCommandList(Resource): class RecorderCommandList(Resource):
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMANDS_LIST)
@api_recorder.doc('recorder_commands') @api_recorder.doc('recorder_commands')
@api_recorder.marshal_list_with(recorder_command_model) @api_recorder.marshal_list_with(recorder_command_model)
def get(self): def get(self):

View File

@@ -14,7 +14,8 @@ from flask_restx import Resource, fields, inputs, abort
from backend import db, app, jwt_auth from backend import db, app, jwt_auth
from backend.api import api_user from backend.api import api_user
from backend.api.models import user_model, recorder_model, generic_id_parser from backend.api.models import user_model, recorder_model, generic_id_parser
from backend.models import Recorder from backend.auth.utils import requires_permission_level
from backend.models import Recorder, Config
from backend.models.user_model import User, Group from backend.models.user_model import User, Group
@@ -90,18 +91,20 @@ class UserList(Resource):
# @jwt_auth.login_required # @jwt_auth.login_required
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.USERS_LIST)
@api_user.doc('users') @api_user.doc('users')
@api_user.marshal_list_with(user_model) @api_user.marshal_list_with(user_model)
def get(self): def get(self):
""" """
just a test! returns all users
:return: Hello: World :return: all users
""" """
current_user = get_jwt_identity() current_user = get_jwt_identity()
app.logger.info(current_user) app.logger.info(current_user)
return User.get_all() return User.get_all()
@jwt_required @jwt_required
@requires_permission_level(Config.Permissions.USER_CREATE)
@api_user.doc('create_group') @api_user.doc('create_group')
@api_user.expect(user_model) @api_user.expect(user_model)
@api_user.marshal_with(user_model, code=201) @api_user.marshal_with(user_model, code=201)
@@ -117,6 +120,7 @@ class UserList(Resource):
@api_user.response(404, 'User not found') @api_user.response(404, 'User not found')
class UserResource(Resource): class UserResource(Resource):
@jwt_auth.login_required @jwt_auth.login_required
@requires_permission_level(Config.Permissions.USER_SHOW)
@api_user.doc('get_user') @api_user.doc('get_user')
@api_user.marshal_with(user_model) @api_user.marshal_with(user_model)
def get(self, id): def get(self, id):
@@ -126,4 +130,16 @@ class UserResource(Resource):
return user return user
api_user.abort(404) api_user.abort(404)
@jwt_auth.login_required
@requires_permission_level(Config.Permissions.USER_DELETE)
@api_user.doc('delete_user')
def delete(self, id):
"""Fetch a user given its identifier"""
user = User.get_by_id(id)
if user is not None:
db.session.delete(user)
db.session.commit()
return "ok"
api_user.abort(404)
# api_user.add_resource(UserResource, '/') # api_user.add_resource(UserResource, '/')

View File

@@ -2,6 +2,8 @@ import flask_jwt_extended
from flask_jwt_extended import jwt_optional, get_jwt_identity from flask_jwt_extended import jwt_optional, get_jwt_identity
from functools import wraps from functools import wraps
from flask_restx import abort
from backend import jwt_auth from backend import jwt_auth
from backend.models.user_model import User from backend.models.user_model import User
@@ -10,26 +12,16 @@ def requires_permission_level(permission_level):
def decorator(f): def decorator(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if flask_jwt_extended.verify_jwt_in_request(): # if flask_jwt_extended.verify_jwt_in_request():
current_user_id = get_jwt_identity() current_user_id = get_jwt_identity()
user = User.get_by_identifier(current_user_id) user = User.get_by_identifier(current_user_id)
if user is not None: if user is not None:
if user.has_permission(permission_level): if not user.has_permission(permission_level):
#for g in user.groups: abort(401, f"You are missing the permission: {permission_level}")
# if g.permissions
#TODO
pass
else:
pass
# return FALSE
#if not session.get('email'):
# return redirect(url_for('users.login'))
#user = User.find_by_email(session['email'])
#elif not user.allowed(access_level):
# return redirect(url_for('users.profile', message="You do not have access to that page. Sorry!"))
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
return decorator return decorator
@@ -38,5 +30,7 @@ def require_jwt():
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
return jwt_auth.login_required(jwt_optional(f(*args, **kwargs))) return jwt_auth.login_required(jwt_optional(f(*args, **kwargs)))
return decorated_function return decorated_function
return decorator return decorator

Binary file not shown.

View File

@@ -3,6 +3,7 @@
Example user model and related models Example user model and related models
""" """
import json import json
from enum import Enum
import sqlalchemy import sqlalchemy
from sqlalchemy.orm import relation, validates from sqlalchemy.orm import relation, validates
@@ -253,12 +254,24 @@ class User(UserMixin, db.Model):
@property @property
def effective_permissions(self): def effective_permissions(self):
permissions = Config.ROLE_PERMISSION_MAPPINGS.get(self.role, set()) role_permissions = Config.ROLE_PERMISSION_MAPPINGS.get(self.role, set())
permissions = set(Permission.query.filter(Permission.name.in_(role_permissions)).all())
for g in self.groups: for g in self.groups:
for p in g.permissions: for p in g.permissions:
permissions.add(p) permissions.add(p)
return permissions return permissions
def has_permission(self, permission):
user_permissions = self.effective_permissions
if isinstance(permission, str):
return any([user_permission.name == permission for user_permission in user_permissions])
if isinstance(permission, Permission):
return any([user_permission.id == permission.id for user_permission in user_permissions])
if isinstance(permission, Enum):
return any([user_permission.name == str(permission.value) for user_permission in user_permissions])
return False
@staticmethod @staticmethod
def decode_auth_token(auth_token): def decode_auth_token(auth_token):
""" """