diff --git a/src/spiffworkflow_backend/__init__.py b/src/spiffworkflow_backend/__init__.py index f2ef34f9..fa348657 100644 --- a/src/spiffworkflow_backend/__init__.py +++ b/src/spiffworkflow_backend/__init__.py @@ -17,7 +17,9 @@ from flask_mail import Mail # type: ignore import spiffworkflow_backend.load_database_models # noqa: F401 from spiffworkflow_backend.config import setup_config from spiffworkflow_backend.routes.admin_blueprint.admin_blueprint import admin_blueprint +from spiffworkflow_backend.routes.process_api_blueprint import check_for_permission from spiffworkflow_backend.routes.process_api_blueprint import process_api_blueprint +from spiffworkflow_backend.routes.user import verify_token from spiffworkflow_backend.routes.user_blueprint import user_blueprint from spiffworkflow_backend.services.background_processing_service import ( BackgroundProcessingService, @@ -113,6 +115,9 @@ def create_app() -> flask.app.Flask: configure_sentry(app) + app.before_request(verify_token) + app.before_request(check_for_permission) + return app # type: ignore diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index 1ea29d20..e9d5fd79 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -6,8 +6,8 @@ info: name: MIT servers: - url: http://localhost:5000/v1.0 -security: - - jwt: ["secret"] +security: [] +# - jwt: ["secret"] # - oAuth2AuthCode: # - read_email # - uid @@ -378,7 +378,6 @@ paths: application/json: schema: $ref: "#/components/schemas/OkTrue" - # process model update put: operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_update summary: Modifies an existing process mosel with the given parameters. @@ -827,7 +826,6 @@ paths: application/json: schema: $ref: "#/components/schemas/File" - # process_model_file_update put: operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_file_update summary: save the contents to the given file diff --git a/src/spiffworkflow_backend/models/permission_assignment.py b/src/spiffworkflow_backend/models/permission_assignment.py index 5fc7ae31..b5bee4aa 100644 --- a/src/spiffworkflow_backend/models/permission_assignment.py +++ b/src/spiffworkflow_backend/models/permission_assignment.py @@ -31,7 +31,13 @@ class Permission(enum.Enum): read = "read" update = "update" delete = "delete" + + # maybe read to GET process_model/process-instances instead? list = "list" + + # maybe use create instead on + # POST http://localhost:7000/v1.0/process-models/category_number_one/call-activity/process-instances/* + # POST http://localhost:7000/v1.0/process-models/category_number_one/call-activity/process-instances/332/run instantiate = "instantiate" # this is something you do to a process model diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index 95e4805f..664217a8 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -4,10 +4,14 @@ import os import random import string import uuid +from functools import wraps from typing import Any +from typing import Callable +from typing import cast from typing import Dict from typing import Optional from typing import TypedDict +from typing import TypeVar from typing import Union import connexion # type: ignore @@ -53,6 +57,7 @@ from spiffworkflow_backend.models.secret_model import SecretModel from spiffworkflow_backend.models.secret_model import SecretModelSchema from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.git_service import GitService @@ -87,6 +92,51 @@ class ReactJsonSchemaSelectOption(TypedDict): process_api_blueprint = Blueprint("process_api", __name__) +authorization_exclusion_list = ['status'] + + +def check_for_permission() -> None: + """Check_for_permission.""" + if request.method == 'OPTIONS': + return None + + if not request.endpoint: + raise ApiError( + error_code="request_endpoint_not_found", + message="Could not find the endpong from the rquest.", + status_code=500, + ) + + api_view_function = current_app.view_functions[request.endpoint] + if api_view_function and api_view_function.__name__ not in authorization_exclusion_list: + permission_string = get_permission_from_request_method() + if permission_string: + has_permission = AuthorizationService.user_has_permission( + user=g.user, + permission=permission_string, + target_uri=request.path, + ) + if has_permission: + return None + + raise ApiError( + error_code="unauthorized", + message="User is not authorized to perform requested action.", + status_code=403, + ) + + +def get_permission_from_request_method() -> Optional[str]: + request_method_mapper = { + "POST": "create", + "GET": "read", + "PUT": "update", + "DELETE": "delete" + } + if request.method in request_method_mapper: + return request_method_mapper[request.method] + + return None def status() -> flask.wrappers.Response: diff --git a/src/spiffworkflow_backend/routes/user.py b/src/spiffworkflow_backend/routes/user.py index a87f7b72..c028ed46 100644 --- a/src/spiffworkflow_backend/routes/user.py +++ b/src/spiffworkflow_backend/routes/user.py @@ -10,6 +10,7 @@ import jwt from flask import current_app from flask import g from flask import redirect +from flask import request from flask_bpmn.api.api_error import ApiError from werkzeug.wrappers.response import Response @@ -26,6 +27,7 @@ from spiffworkflow_backend.services.user_service import UserService """ +# authorization_exclusion_list = ['status'] def verify_token(token: Optional[str] = None) -> Dict[str, Optional[Union[str, int]]]: """Verify the token for the user (if provided). @@ -41,6 +43,17 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[Union[str, i ApiError: If not on production and token is not valid, returns an 'invalid_token' 403 error. If on production and user is not authenticated, returns a 'no_user' 403 error. """ + + if request.method == 'OPTIONS': + return None + + api_view_function = current_app.view_functions[request.endpoint] + if api_view_function and api_view_function.__name__.startswith('login'): + return None + + if not token and 'Authorization' in request.headers: + token = request.headers['Authorization'].removeprefix('Bearer ') + if token: user_model = None decoded_token = get_decoded_token(token) diff --git a/src/spiffworkflow_backend/services/authorization_service.py b/src/spiffworkflow_backend/services/authorization_service.py index f1272ef0..5a881ef9 100644 --- a/src/spiffworkflow_backend/services/authorization_service.py +++ b/src/spiffworkflow_backend/services/authorization_service.py @@ -21,6 +21,9 @@ from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignme from spiffworkflow_backend.services.user_service import UserService +class PermissionsFileNotSetError(Exception): + pass + class AuthorizationService: """Determine whether a user has permission to perform their request.""" @@ -77,6 +80,9 @@ class AuthorizationService: cls, raise_if_missing_user: bool = False ) -> None: """Import_permissions_from_yaml_file.""" + if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None: + raise(PermissionsFileNotSetError("SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions")) + permission_configs = None with open(current_app.config["PERMISSIONS_FILE_FULLPATH"]) as file: permission_configs = yaml.safe_load(file)