fixed submitting and getting user tasks w/ burnettk
This commit is contained in:
parent
c9b086d969
commit
57248eab76
|
@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|||
from flask_bpmn.models.db import db
|
||||
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.schema import CheckConstraint
|
||||
|
||||
from spiffworkflow_backend.models.group import GroupModel
|
||||
|
@ -28,3 +29,6 @@ class PrincipalModel(SpiffworkflowBaseDBModel):
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(ForeignKey(UserModel.id), nullable=True, unique=True)
|
||||
group_id = db.Column(ForeignKey(GroupModel.id), nullable=True, unique=True)
|
||||
|
||||
user = relationship("UserModel", viewonly=True)
|
||||
group = relationship("GroupModel", viewonly=True)
|
||||
|
|
|
@ -28,12 +28,14 @@ from lxml import etree # type: ignore
|
|||
from lxml.builder import ElementMaker # type: ignore
|
||||
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
ProcessEntityNotFoundError,
|
||||
)
|
||||
from spiffworkflow_backend.models.active_task import ActiveTaskModel
|
||||
from spiffworkflow_backend.models.active_task_user import ActiveTaskUserModel
|
||||
from spiffworkflow_backend.models.file import FileSchema
|
||||
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
||||
from spiffworkflow_backend.models.message_model import MessageModel
|
||||
|
@ -918,11 +920,11 @@ def process_instance_report_show(
|
|||
def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||
"""Task_list_my_tasks."""
|
||||
principal = find_principal_or_raise()
|
||||
# TODO: use join table
|
||||
active_tasks = (
|
||||
ActiveTaskModel.query.filter_by(assigned_principal_id=principal.id)
|
||||
.order_by(desc(ActiveTaskModel.id)) # type: ignore
|
||||
ActiveTaskModel.query.order_by(desc(ActiveTaskModel.id)) # type: ignore
|
||||
.join(ProcessInstanceModel)
|
||||
.join(ActiveTaskUserModel)
|
||||
.filter_by(user_id=principal.user_id)
|
||||
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
||||
.add_columns(
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
|
@ -1085,18 +1087,15 @@ def task_submit(
|
|||
) -> flask.wrappers.Response:
|
||||
"""Task_submit_user_data."""
|
||||
principal = find_principal_or_raise()
|
||||
active_task_assigned_to_me = find_active_task_by_id_or_raise(
|
||||
process_instance_id, task_id, principal.id
|
||||
)
|
||||
|
||||
process_instance = find_process_instance_by_id_or_raise(
|
||||
active_task_assigned_to_me.process_instance_id
|
||||
)
|
||||
process_instance = find_process_instance_by_id_or_raise(process_instance_id)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
spiff_task = get_spiff_task_from_process_instance(
|
||||
task_id, process_instance, processor=processor
|
||||
)
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, principal.user
|
||||
)
|
||||
|
||||
if spiff_task.state != TaskState.READY:
|
||||
raise (
|
||||
|
@ -1110,10 +1109,6 @@ def task_submit(
|
|||
if terminate_loop and spiff_task.is_looping():
|
||||
spiff_task.terminate_loop()
|
||||
|
||||
# TODO: support repeating fields
|
||||
# Extract the details specific to the form submitted
|
||||
# form_data = WorkflowService().extract_form_data(body, spiff_task)
|
||||
|
||||
ProcessInstanceService.complete_form_task(processor, spiff_task, body, g.user)
|
||||
|
||||
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
||||
|
@ -1128,10 +1123,13 @@ def task_submit(
|
|||
|
||||
ProcessInstanceService.update_task_assignments(processor)
|
||||
|
||||
# TODO: update
|
||||
next_active_task_assigned_to_me = ActiveTaskModel.query.filter_by(
|
||||
assigned_principal_id=principal.id, process_instance_id=process_instance.id
|
||||
).first()
|
||||
next_active_task_assigned_to_me = (
|
||||
ActiveTaskModel.query.filter_by(process_instance_id=process_instance_id)
|
||||
.order_by(asc(ActiveTaskModel.id)) # type: ignore
|
||||
.join(ActiveTaskUserModel)
|
||||
.filter_by(user_id=principal.user_id)
|
||||
.first()
|
||||
)
|
||||
if next_active_task_assigned_to_me:
|
||||
return make_response(
|
||||
jsonify(ActiveTaskModel.to_task(next_active_task_assigned_to_me)), 200
|
||||
|
@ -1294,31 +1292,6 @@ def find_principal_or_raise() -> PrincipalModel:
|
|||
return principal # type: ignore
|
||||
|
||||
|
||||
def find_active_task_by_id_or_raise(
|
||||
process_instance_id: int, task_id: str, principal_id: PrincipalModel
|
||||
) -> ActiveTaskModel:
|
||||
"""Find_active_task_by_id_or_raise."""
|
||||
# TODO: update
|
||||
active_task_assigned_to_me = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=process_instance_id,
|
||||
task_id=task_id,
|
||||
# assigned_principal_id=principal_id,
|
||||
).first()
|
||||
if active_task_assigned_to_me is None:
|
||||
message = (
|
||||
f"Task not found for principal user {principal_id} "
|
||||
f"process_instance_id: {process_instance_id}, task_id: {task_id}"
|
||||
)
|
||||
raise (
|
||||
ApiError(
|
||||
error_code="task_not_found",
|
||||
message=message,
|
||||
status_code=400,
|
||||
)
|
||||
)
|
||||
return active_task_assigned_to_me # type: ignore
|
||||
|
||||
|
||||
def find_process_instance_by_id_or_raise(
|
||||
process_instance_id: int,
|
||||
) -> ProcessInstanceModel:
|
||||
|
|
|
@ -10,8 +10,10 @@ from flask import g
|
|||
from flask import request
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
from flask_bpmn.models.db import db
|
||||
from SpiffWorkflow.task import Task as SpiffTask
|
||||
from sqlalchemy import text
|
||||
|
||||
from spiffworkflow_backend.models.active_task import ActiveTaskModel # type: ignore
|
||||
from spiffworkflow_backend.models.group import GroupModel
|
||||
from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel
|
||||
from spiffworkflow_backend.models.permission_target import PermissionTargetModel
|
||||
|
@ -20,6 +22,9 @@ from spiffworkflow_backend.models.principal import PrincipalModel
|
|||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.models.user import UserNotFoundError
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
|
||||
|
@ -27,6 +32,14 @@ class PermissionsFileNotSetError(Exception):
|
|||
"""PermissionsFileNotSetError."""
|
||||
|
||||
|
||||
class ActiveTaskNotFoundError(Exception):
|
||||
"""ActiveTaskNotFoundError."""
|
||||
|
||||
|
||||
class UserDoesNotHaveAccessToTaskError(Exception):
|
||||
"""UserDoesNotHaveAccessToTaskError."""
|
||||
|
||||
|
||||
class AuthorizationService:
|
||||
"""Determine whether a user has permission to perform their request."""
|
||||
|
||||
|
@ -364,196 +377,29 @@ class AuthorizationService:
|
|||
"The Authentication token you provided is invalid. You need a new token. ",
|
||||
) from exception
|
||||
|
||||
# def get_bearer_token_from_internal_token(self, internal_token):
|
||||
# """Get_bearer_token_from_internal_token."""
|
||||
# self.decode_auth_token(internal_token)
|
||||
# print(f"get_user_by_internal_token: {internal_token}")
|
||||
@staticmethod
|
||||
def assert_user_can_complete_spiff_task(
|
||||
processor: ProcessInstanceProcessor,
|
||||
spiff_task: SpiffTask,
|
||||
user: UserModel,
|
||||
) -> bool:
|
||||
"""Assert_user_can_complete_spiff_task."""
|
||||
active_task = ActiveTaskModel.query.filter_by(
|
||||
task_name=spiff_task.task_spec.name,
|
||||
process_instance_id=processor.process_instance_model.id,
|
||||
).first()
|
||||
if active_task is None:
|
||||
raise ActiveTaskNotFoundError(
|
||||
f"Could find an active task with task name '{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
)
|
||||
|
||||
# def introspect_token(self, basic_token: str) -> dict:
|
||||
# """Introspect_token."""
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
#
|
||||
# bearer_token = AuthorizationService().get_bearer_token(basic_token)
|
||||
# auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
#
|
||||
# headers = {
|
||||
# "Content-Type": "application/x-www-form-urlencoded",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# data = {
|
||||
# "client_id": open_id_client_id,
|
||||
# "client_secret": open_id_client_secret_key,
|
||||
# "token": basic_token,
|
||||
# }
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token/introspect"
|
||||
#
|
||||
# introspect_response = requests.post(request_url, headers=headers, data=data)
|
||||
# introspection = json.loads(introspect_response.text)
|
||||
#
|
||||
# return introspection
|
||||
|
||||
# def get_permission_by_basic_token(self, basic_token: dict) -> list:
|
||||
# """Get_permission_by_basic_token."""
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
#
|
||||
# # basic_token = AuthorizationService().refresh_token(basic_token)
|
||||
# # bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token'])
|
||||
# bearer_token = AuthorizationService().get_bearer_token(basic_token)
|
||||
# # auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
# auth_bearer_string = f"Bearer {bearer_token}"
|
||||
#
|
||||
# headers = {
|
||||
# "Content-Type": "application/x-www-form-urlencoded",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# data = {
|
||||
# "client_id": open_id_client_id,
|
||||
# "client_secret": open_id_client_secret_key,
|
||||
# "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
|
||||
# "response_mode": "permissions",
|
||||
# "audience": open_id_client_id,
|
||||
# "response_include_resource_name": True,
|
||||
# }
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
||||
# permission_response = requests.post(request_url, headers=headers, data=data)
|
||||
# permission = json.loads(permission_response.text)
|
||||
# return permission
|
||||
|
||||
# def get_auth_status_for_resource_and_scope_by_token(
|
||||
# self, basic_token: dict, resource: str, scope: str
|
||||
# ) -> str:
|
||||
# """Get_auth_status_for_resource_and_scope_by_token."""
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
#
|
||||
# # basic_token = AuthorizationService().refresh_token(basic_token)
|
||||
# bearer_token = AuthorizationService().get_bearer_token(basic_token)
|
||||
# auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
#
|
||||
# headers = {
|
||||
# "Content-Type": "application/x-www-form-urlencoded",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# data = {
|
||||
# "client_id": open_id_client_id,
|
||||
# "client_secret": open_id_client_secret_key,
|
||||
# "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
|
||||
# "permission": f"{resource}#{scope}",
|
||||
# "response_mode": "permissions",
|
||||
# "audience": open_id_client_id,
|
||||
# }
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
||||
# auth_response = requests.post(request_url, headers=headers, data=data)
|
||||
#
|
||||
# print("get_auth_status_for_resource_and_scope_by_token")
|
||||
# auth_status: str = json.loads(auth_response.text)
|
||||
# return auth_status
|
||||
|
||||
# def get_permissions_by_token_for_resource_and_scope(
|
||||
# self, basic_token: str, resource: str|None=None, scope: str|None=None
|
||||
# ) -> str:
|
||||
# """Get_permissions_by_token_for_resource_and_scope."""
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
#
|
||||
# # basic_token = AuthorizationService().refresh_token(basic_token)
|
||||
# # bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token'])
|
||||
# bearer_token = AuthorizationService().get_bearer_token(basic_token)
|
||||
# auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
#
|
||||
# headers = {
|
||||
# "Content-Type": "application/x-www-form-urlencoded",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# permision = ""
|
||||
# if resource is not None and resource != '':
|
||||
# permision += resource
|
||||
# if scope is not None and scope != '':
|
||||
# permision += "#" + scope
|
||||
# data = {
|
||||
# "client_id": open_id_client_id,
|
||||
# "client_secret": open_id_client_secret_key,
|
||||
# "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
|
||||
# "response_mode": "permissions",
|
||||
# "permission": permision,
|
||||
# "audience": open_id_client_id,
|
||||
# "response_include_resource_name": True,
|
||||
# }
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
||||
# permission_response = requests.post(request_url, headers=headers, data=data)
|
||||
# permission: str = json.loads(permission_response.text)
|
||||
# return permission
|
||||
|
||||
# def get_resource_set(self, public_access_token, uri):
|
||||
# """Get_resource_set."""
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
# bearer_token = AuthorizationService().get_bearer_token(public_access_token)
|
||||
# auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
# headers = {
|
||||
# "Content-Type": "application/json",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# data = {
|
||||
# "matchingUri": "true",
|
||||
# "deep": "true",
|
||||
# "max": "-1",
|
||||
# "exactName": "false",
|
||||
# "uri": uri,
|
||||
# }
|
||||
#
|
||||
# # f"matchingUri=true&deep=true&max=-1&exactName=false&uri={URI_TO_TEST_AGAINST}"
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/authz/protection/resource_set"
|
||||
# response = requests.get(request_url, headers=headers, data=data)
|
||||
#
|
||||
# print("get_resource_set")
|
||||
|
||||
# def get_permission_by_token(self, public_access_token: str) -> dict:
|
||||
# """Get_permission_by_token."""
|
||||
# # TODO: Write a test for this
|
||||
# (
|
||||
# open_id_server_url,
|
||||
# open_id_client_id,
|
||||
# open_id_realm_name,
|
||||
# open_id_client_secret_key,
|
||||
# ) = AuthorizationService.get_open_id_args()
|
||||
# bearer_token = AuthorizationService().get_bearer_token(public_access_token)
|
||||
# auth_bearer_string = f"Bearer {bearer_token['access_token']}"
|
||||
# headers = {
|
||||
# "Content-Type": "application/x-www-form-urlencoded",
|
||||
# "Authorization": auth_bearer_string,
|
||||
# }
|
||||
# data = {
|
||||
# "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
|
||||
# "audience": open_id_client_id,
|
||||
# }
|
||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
||||
# permission_response = requests.post(request_url, headers=headers, data=data)
|
||||
# permission: dict = json.loads(permission_response.text)
|
||||
#
|
||||
# return permission
|
||||
if user not in active_task.potential_owners:
|
||||
raise UserDoesNotHaveAccessToTaskError(
|
||||
f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class KeycloakAuthorization:
|
||||
|
|
|
@ -11,7 +11,6 @@ from flask_bpmn.models.db import db
|
|||
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
|
||||
from spiffworkflow_backend.models.active_task import ActiveTaskModel # type: ignore
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
|
@ -20,6 +19,7 @@ from spiffworkflow_backend.models.task import Task
|
|||
from spiffworkflow_backend.models.task_event import TaskAction
|
||||
from spiffworkflow_backend.models.task_event import TaskEventModel
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.git_service import GitService
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
|
@ -27,14 +27,6 @@ from spiffworkflow_backend.services.process_instance_processor import (
|
|||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
class ActiveTaskNotFoundError(Exception):
|
||||
"""ActiveTaskNotFoundError."""
|
||||
|
||||
|
||||
class UserDoesNotHaveAccessToTaskError(Exception):
|
||||
"""UserDoesNotHaveAccessToTaskError."""
|
||||
|
||||
|
||||
class ProcessInstanceService:
|
||||
"""ProcessInstanceService."""
|
||||
|
||||
|
@ -279,21 +271,9 @@ class ProcessInstanceService:
|
|||
Abstracted here because we need to do it multiple times when completing all tasks in
|
||||
a multi-instance task.
|
||||
"""
|
||||
active_task = ActiveTaskModel.query.filter_by(
|
||||
task_name=spiff_task.task_spec.name,
|
||||
process_instance_id=processor.process_instance_model.id,
|
||||
).first()
|
||||
if active_task is None:
|
||||
raise ActiveTaskNotFoundError(
|
||||
f"Could find an active task with task name '{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
)
|
||||
|
||||
if user not in active_task.potential_owners:
|
||||
raise UserDoesNotHaveAccessToTaskError(
|
||||
f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
)
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, user
|
||||
)
|
||||
|
||||
dot_dct = ProcessInstanceService.create_dot_dict(data)
|
||||
spiff_task.update_data(dot_dct)
|
||||
|
|
|
@ -7,15 +7,15 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
|||
from spiffworkflow_backend.models.group import GroupModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.authorization_service import (
|
||||
UserDoesNotHaveAccessToTaskError,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_instance_service import (
|
||||
ProcessInstanceService,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_instance_service import (
|
||||
UserDoesNotHaveAccessToTaskError,
|
||||
)
|
||||
|
||||
|
||||
class TestProcessInstanceProcessor(BaseTest):
|
||||
|
|
Loading…
Reference in New Issue