diff --git a/app/api/__init__.py b/app/api/__init__.py index 0b077dc..e69de29 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1,106 +0,0 @@ -import sys -from flask import Blueprint, request, g -from flask_restful import Api, Resource, abort -from functools import wraps -from marshmallow import ValidationError -from app.accounts.api import validate_token - - -api_bp = Blueprint('api', __name__) -api = Api(api_bp) - - -def protected(func): - @wraps(func) - def protected_function(*args, **kwargs): - try: - token = request.headers['Authorization'] or None - - if not token: - abort(401, message='Unauthorized', status='error') - - g.current_account = validate_token(token.replace("Bearer ", "")) - if not g.current_account: - abort(401, message='Unauthorized', status='error') - except Exception: - error_type, error_instance, traceback = sys.exc_info() - print(str(error_type)) - print(str(error_instance)) - abort(401, message='Unauthorized', status='error') - - return func(*args, **kwargs) - - return protected_function - - -def requires_permission(permission, action_name='Action'): - def requires_permission_decorator(func): - @wraps(func) - def permission_protected_function(*args, **kwargs): - if permission not in g.current_account.role.permissions: - abort(403, - message=(action_name+' is not allowed'), - status='error') - - return func(*args, **kwargs) - - return permission_protected_function - - return requires_permission_decorator - - -class ProtectedResource(Resource): - method_decorators = [protected] - - -def add_resources(): - from .resources.account import (AccountResource, - AccountListResource, - AccountRoleResource, - RoleResource, - RolesResource) - from .resources.token import TokenResource, ValidateTokenResource - from .resources.device import (DeviceResource, - DeviceRecordingResource, - DeviceListResource, - DeviceTypeResource, - DeviceTypeListResource, - DeviceConfigurationResource) - from .resources.dashboard import DashboardResource, DashboardListResource - - api.add_resource(AccountResource, '/v1/accounts/') - api.add_resource(AccountListResource, '/v1/accounts') - api.add_resource(AccountRoleResource, '/v1/accounts//role') - api.add_resource(RoleResource, '/v1/roles/') - api.add_resource(RolesResource, '/v1/roles') - api.add_resource(TokenResource, '/v1/token') - api.add_resource(ValidateTokenResource, '/v1/token/validate') - api.add_resource(DeviceResource, '/v1/devices/') - api.add_resource(DeviceRecordingResource, - '/v1/devices//recordings') - api.add_resource(DeviceListResource, '/v1/devices') - api.add_resource(DeviceTypeResource, - '/v1/devices/types/') - api.add_resource(DeviceTypeListResource, '/v1/devices/types') - api.add_resource(DeviceConfigurationResource, - '/v1/devices//configuration') - api.add_resource(DashboardListResource, '/v1/dashboards') - api.add_resource(DashboardResource, - '/v1/dashboards/') - - -add_resources() - - -@api_bp.errorhandler(ValidationError) -@api_bp.errorhandler(422) -def handle_validation_error(e): - return {'status': 'error', 'message': str(e)}, 422 - - -@api_bp.errorhandler(Exception) -def handle_unknown_errors(e): - return ({ - 'status': 'failed', - 'message': 'Unknown error has occurred! ({0})'.format(str(e)) - }, 500) diff --git a/app/api/auth_protection.py b/app/api/auth_protection.py new file mode 100644 index 0000000..48459d2 --- /dev/null +++ b/app/api/auth_protection.py @@ -0,0 +1,32 @@ +import sys +from functools import wraps +from flask import request, g +from flask_restful import Resource, abort +from app.accounts.api import validate_token + + +def protected(func): + @wraps(func) + def protected_function(*args, **kwargs): + try: + token = request.headers['Authorization'] or None + + if not token: + abort(401, message='Unauthorized', status='error') + + g.current_account = validate_token(token.replace("Bearer ", "")) + if not g.current_account: + abort(401, message='Unauthorized', status='error') + except Exception: + error_type, error_instance, traceback = sys.exc_info() + print(str(error_type)) + print(str(error_instance)) + abort(401, message='Unauthorized', status='error') + + return func(*args, **kwargs) + + return protected_function + + +class ProtectedResource(Resource): + method_decorators = [protected] diff --git a/app/api/blueprint.py b/app/api/blueprint.py new file mode 100644 index 0000000..969abc5 --- /dev/null +++ b/app/api/blueprint.py @@ -0,0 +1,60 @@ +from flask_restful import Api +from marshmallow import ValidationError +from flask import Blueprint + + +api_bp = Blueprint('api', __name__) +api = Api(api_bp) + + +def add_resources(): + from .resources.account import (AccountResource, + AccountListResource, + AccountRoleResource, + RoleResource, + RolesResource) + from .resources.token import TokenResource, ValidateTokenResource + from .resources.device import (DeviceResource, + DeviceRecordingResource, + DeviceListResource, + DeviceTypeResource, + DeviceTypeListResource, + DeviceConfigurationResource) + from .resources.dashboard import DashboardResource, DashboardListResource + + api.add_resource(AccountResource, '/v1/accounts/') + api.add_resource(AccountListResource, '/v1/accounts') + api.add_resource(AccountRoleResource, '/v1/accounts//role') + api.add_resource(RoleResource, '/v1/roles/') + api.add_resource(RolesResource, '/v1/roles') + api.add_resource(TokenResource, '/v1/token') + api.add_resource(ValidateTokenResource, '/v1/token/validate') + api.add_resource(DeviceResource, '/v1/devices/') + api.add_resource(DeviceRecordingResource, + '/v1/devices//recordings') + api.add_resource(DeviceListResource, '/v1/devices') + api.add_resource(DeviceTypeResource, + '/v1/devices/types/') + api.add_resource(DeviceTypeListResource, '/v1/devices/types') + api.add_resource(DeviceConfigurationResource, + '/v1/devices//configuration') + api.add_resource(DashboardListResource, '/v1/dashboards') + api.add_resource(DashboardResource, + '/v1/dashboards/') + + +add_resources() + + +@api_bp.errorhandler(ValidationError) +@api_bp.errorhandler(422) +def handle_validation_error(e): + return {'status': 'error', 'message': str(e)}, 422 + + +@api_bp.errorhandler(Exception) +def handle_unknown_errors(e): + return ({ + 'status': 'failed', + 'message': 'Unknown error has occurred! ({0})'.format(str(e)) + }, 500) diff --git a/app/api/permission_protection.py b/app/api/permission_protection.py new file mode 100644 index 0000000..66d3207 --- /dev/null +++ b/app/api/permission_protection.py @@ -0,0 +1,19 @@ +from flask import g +from flask_restful import abort +from functools import wraps + + +def requires_permission(permission, action_name='Action'): + def requires_permission_decorator(func): + @wraps(func) + def permission_protected_function(*args, **kwargs): + if permission not in g.current_account.role.permissions: + abort(403, + message=(action_name+' is not allowed'), + status='error') + + return func(*args, **kwargs) + + return permission_protected_function + + return requires_permission_decorator diff --git a/app/api/resources/account.py b/app/api/resources/account.py index 8fbe961..828774e 100644 --- a/app/api/resources/account.py +++ b/app/api/resources/account.py @@ -4,7 +4,8 @@ from marshmallow import Schema, fields from webargs.flaskparser import use_args from flasgger import swag_from import app.accounts.api as accounts -from app.api import ProtectedResource, requires_permission +from app.api.auth_protection import ProtectedResource +from app.api.permission_protection import requires_permission class UserSchema(Schema): diff --git a/app/api/resources/dashboard.py b/app/api/resources/dashboard.py index a046fd2..ae3652f 100644 --- a/app/api/resources/dashboard.py +++ b/app/api/resources/dashboard.py @@ -4,7 +4,7 @@ from marshmallow import Schema, fields from webargs.flaskparser import use_args from flasgger import swag_from import app.dashboards.api as dashboard -from app.api import ProtectedResource +from app.api.auth_protection import ProtectedResource class DashboardSchema(Schema): diff --git a/app/api/resources/device.py b/app/api/resources/device.py index 30a3fbe..1c6d8e1 100644 --- a/app/api/resources/device.py +++ b/app/api/resources/device.py @@ -4,7 +4,7 @@ from webargs.flaskparser import use_args from flasgger import swag_from from flask import g, request import app.devices.api as devices -from app.api import ProtectedResource +from app.api.auth_protection import ProtectedResource class DeviceTypeSchema(Schema): diff --git a/app/api/resources/token.py b/app/api/resources/token.py index 26d80bf..16d3b2e 100644 --- a/app/api/resources/token.py +++ b/app/api/resources/token.py @@ -2,7 +2,7 @@ from flask_restful import Resource, abort from webargs import fields from webargs.flaskparser import use_args from flasgger import swag_from -from app.api import ProtectedResource +from app.api.auth_protection import ProtectedResource import app.accounts.api as accounts diff --git a/app/core.py b/app/core.py index 03a41bb..eaa411e 100644 --- a/app/core.py +++ b/app/core.py @@ -30,7 +30,7 @@ def setup_blueprints(app): from .accounts.blueprint import accounts_bp from .devices.blueprint import devices_bp from .dashboards.blueprint import dashboard_bp - from .api import api_bp + from .api.blueprint import api_bp from .mqtt.blueprint import mqtt_bp app.register_blueprint(accounts_bp)