added permission checks to user and recorder API
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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, '/')
|
||||||
|
|||||||
@@ -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.
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user