Merge remote-tracking branch 'origin/main' into feature/process_instance_search

This commit is contained in:
burnettk 2022-09-22 18:02:18 -04:00
commit 4736e2878e
13 changed files with 1237 additions and 179 deletions

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: e9763012b98b
Revision ID: 86bdc3330645
Revises:
Create Date: 2022-09-22 16:15:16.490215
Create Date: 2022-09-22 18:01:10.013335
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e9763012b98b'
revision = '86bdc3330645'
down_revision = None
branch_labels = None
depends_on = None
@ -126,6 +126,15 @@ def upgrade():
op.create_index(op.f('ix_process_instance_report_identifier'), 'process_instance_report', ['identifier'], unique=False)
op.create_index(op.f('ix_process_instance_report_process_group_identifier'), 'process_instance_report', ['process_group_identifier'], unique=False)
op.create_index(op.f('ix_process_instance_report_process_model_identifier'), 'process_instance_report', ['process_model_identifier'], unique=False)
op.create_table('secret',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(length=50), nullable=False),
sa.Column('value', sa.String(length=255), nullable=False),
sa.Column('creator_user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['creator_user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('key')
)
op.create_table('user_group_assignment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
@ -205,6 +214,14 @@ def upgrade():
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('secret_allowed_process',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('secret_id', sa.Integer(), nullable=False),
sa.Column('allowed_relative_path', sa.String(length=500), nullable=False),
sa.ForeignKeyConstraint(['secret_id'], ['secret.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('secret_id', 'allowed_relative_path', name='unique_secret_path')
)
op.create_table('spiff_logging',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('process_instance_id', sa.Integer(), nullable=False),
@ -277,6 +294,7 @@ def downgrade():
op.drop_table('data_store')
op.drop_table('task_event')
op.drop_table('spiff_logging')
op.drop_table('secret_allowed_process')
op.drop_table('message_instance')
op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation')
op.drop_index(op.f('ix_message_correlation_process_instance_id'), table_name='message_correlation')
@ -286,6 +304,7 @@ def downgrade():
op.drop_table('file')
op.drop_table('active_task')
op.drop_table('user_group_assignment')
op.drop_table('secret')
op.drop_index(op.f('ix_process_instance_report_process_model_identifier'), table_name='process_instance_report')
op.drop_index(op.f('ix_process_instance_report_process_group_identifier'), table_name='process_instance_report')
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')

View File

@ -8,6 +8,8 @@ servers:
- url: http://localhost:5000/v1.0
security:
- jwt: ["secret"]
# - oAuth2AuthCode:
# - read_email
paths:
/login:
@ -83,35 +85,47 @@ paths:
"200":
description: Logout Authenticated User
/login_swagger:
parameters:
- name: uid
in: query
required: true
description: The user we are authenticating
schema:
type: string
- name: password
in: query
required: true
description: The password for the user
schema:
type: string
format: password
- name: redirect_url
in: query
required: false
schema:
type: string
get:
security: []
operationId: spiffworkflow_backend.routes.user.api_login
summary: Authenticate user for API access
tags:
- Authentication
responses:
"304":
description: Redirection to the hosted frontend with an auth_token header.
# /login_api:
# parameters:
# - name: redirect_url
# in: query
# required: false
# schema:
# type: string
# get:
# security: []
# operationId: spiffworkflow_backend.routes.user.login_api
# summary: Authenticate user for API access
# tags:
# - Authentication
# responses:
# "304":
# description: Redirection to the hosted frontend with an auth_token header.
# /login_api_return:
# parameters:
# - name: code
# in: query
# required: true
# schema:
# type: string
# - name: state
# in: query
# required: true
# schema:
# type: string
# - name: session_state
# in: query
# required: false
# schema:
# type: string
# get:
# security: []
# operationId: spiffworkflow_backend.routes.user.login_api_return
# tags:
# - Authentication
# responses:
# "200":
# description: Test Return Response
/status:
get:
@ -127,7 +141,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/process-groups:
parameters:
@ -208,7 +222,7 @@ paths:
description: The process group was deleted.
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_update
summary: Upates a single process group
summary: Updates a single process group
tags:
- Process Groups
requestBody:
@ -313,7 +327,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/File"
$ref: "#/components/schemas/File"
# get:
# operationId: spiffworkflow_backend.api.process_api_blueprint.get_files
# summary: Provide a list of workflow spec files for the given workflow_spec_id. IMPORTANT, only includes metadata, not the file content.
@ -368,7 +382,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
# process model update
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_update
@ -531,7 +545,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run:
parameters:
@ -604,7 +618,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/reports:
parameters:
@ -657,7 +671,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/process-instances/reports/{report_identifier}:
parameters:
@ -716,7 +730,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_report_delete
summary: Delete a process instance report
@ -728,7 +742,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/process-models/{process_group_id}/{process_model_id}/files/{file_name}:
parameters:
@ -762,7 +776,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/File"
$ref: "#/components/schemas/File"
# process_model_file_update
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_file_update
@ -786,7 +800,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_model_file_delete
summary: Removes an existing process model file
@ -798,7 +812,7 @@ paths:
content:
application/json:
schema:
$ref: "#components/schemas/OkTrue"
$ref: "#/components/schemas/OkTrue"
/tasks:
parameters:
@ -954,11 +968,6 @@ paths:
- Messages
operationId: spiffworkflow_backend.routes.process_api_blueprint.message_instance_list
summary: Get a list of message instances
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Workflow"
responses:
"200":
description: One task
@ -1036,7 +1045,115 @@ paths:
content:
application/json:
schema:
type: array
$ref: "#/components/schemas/ProcessInstanceLog"
/secrets:
post:
operationId: spiffworkflow_backend.routes.process_api_blueprint.add_secret
summary: Create a secret for a key and value
tags:
- Secrets
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Secret"
responses:
"201":
description: Secret created successfully
content:
application/json:
schema:
type: number
/secrets/{key}:
parameters:
- name: key
in: path
required: true
description: The key we are using
schema:
type: string
get:
operationId: spiffworkflow_backend.routes.process_api_blueprint.get_secret
summary: Return a secret value for a key
tags:
- Secrets
responses:
"200":
description: We return a secret
content:
application/json:
schema:
type: string
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.delete_secret
summary: Delete an existing secret
tags:
- Secrets
responses:
"204":
description: The secret is deleted
"401":
description: Unauthorized to delete secret
"404":
description: Secret does not exist
put:
operationId: spiffworkflow_backend.routes.process_api_blueprint.update_secret
summary: Modify an existing secret
tags:
- Secrets
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Secret"
responses:
"200":
description: Secret updated successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Secret"
"401":
description: Unauthorized to update secret
"404":
description: Secret does not exist
/secrets/allowed_process_paths:
post:
operationId: spiffworkflow_backend.routes.process_api_blueprint.add_allowed_process_path
summary: Create an allowed process to a secret
tags:
- Secrets
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SecretAllowedProcessPath"
responses:
"201":
description: Allowed process created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/SecretAllowedProcessPath"
/secrets/allowed_process_paths/{allowed_process_path_id}:
parameters:
- name: allowed_process_path_id
in: path
required: true
description: The id of the allowed process path to delete
schema:
type: integer
delete:
operationId: spiffworkflow_backend.routes.process_api_blueprint.delete_allowed_process_path
summary: Delete an existing allowed process for a secret
tags:
- Secrets
responses:
"204":
description: The allowed process is deleted.
components:
securitySchemes:
@ -1046,6 +1163,18 @@ components:
bearerFormat: JWT
x-bearerInfoFunc: spiffworkflow_backend.routes.user.verify_token
x-scopeValidateFunc: spiffworkflow_backend.routes.user.validate_scope
oAuth2AuthCode:
type: oauth2
description: authenticate with openid server
flows:
authorizationCode:
authorizationUrl: /v1.0/login_api
tokenUrl: /v1.0/login_return
scopes:
read_email: read email
x-tokenInfoFunc: spiffworkflow_backend.routes.user.get_scope
schemas:
OkTrue:
properties:
@ -1764,3 +1893,72 @@ components:
untracked:
type: array
example: ["a_file.txt", "b_file.txt"]
Secret:
properties:
key:
description: The key of the secret we want to use
type: string
example: my_secret_key
nullable: false
value:
description: The value associated with the key
type: string
example: my_super_secret_value
nullable: false
creator_user_id:
description: The id of the logged in user that created this secret
type: number
example: 1
nullable: false
allowed_processes:
description: The processes allowed to access this secret
type: array
items:
$ref: "#/components/schemas/SecretAllowedProcessPath"
nullable: true
ProcessInstanceLog:
properties:
id:
description: The id of the log
type: number
example: 1
nullable: false
process_instance_id:
description: The id of the associated process instance
type: number
example: 2
nullable: false
bpmn_process_identifier:
description: The id of the bpmn process element
type: string
example: Process_SimpleProcess
nullable: false
task:
description: The task identifier
type: number
example: 1234567890
nullable: false
message:
description: The msg returned in the log
type: string
example: Some message returned in the log
nullable: true
timestamp:
description: The timestamp returned in the log
type: number
example: 123456789.12345
SecretAllowedProcessPath:
properties:
id:
description: The id of the allowed process path
type: number
example: 1
nullable: true
secret_id:
description: The id of the secret associated with this allowed process path
type: number
example: 2
allowed_relative_path:
description: The allowed process path
type: string
example: /group_one/group_two/model_a

View File

@ -27,9 +27,11 @@ from spiffworkflow_backend.models.process_instance import (
from spiffworkflow_backend.models.process_instance_report import (
ProcessInstanceReportModel,
) # noqa: F401
from spiffworkflow_backend.models.spiff_logging import (
SpiffLoggingModel,
from spiffworkflow_backend.models.secret_model import (
SecretAllowedProcessPathModel,
) # noqa: F401
from spiffworkflow_backend.models.secret_model import SecretModel # noqa: F401
from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel # noqa: F401
from spiffworkflow_backend.models.task_event import TaskEventModel # noqa: F401
from spiffworkflow_backend.models.user import UserModel # noqa: F401
from spiffworkflow_backend.models.user_group_assignment import (

View File

@ -0,0 +1,58 @@
"""Secret_model."""
from flask_bpmn.models.db import db
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
from marshmallow import Schema
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from spiffworkflow_backend.models.user import UserModel
class SecretModel(SpiffworkflowBaseDBModel):
"""SecretModel."""
__tablename__ = "secret"
id: int = db.Column(db.Integer, primary_key=True)
key: str = db.Column(db.String(50), unique=True, nullable=False)
value: str = db.Column(db.String(255), nullable=False)
creator_user_id: int = db.Column(ForeignKey(UserModel.id), nullable=False)
allowed_processes = relationship("SecretAllowedProcessPathModel", cascade="delete")
class SecretModelSchema(Schema):
"""SecretModelSchema."""
class Meta:
"""Meta."""
model = SecretModel
fields = ["key", "value", "creator_user_id"]
class SecretAllowedProcessPathModel(SpiffworkflowBaseDBModel):
"""Allowed processes can be Process Groups or Process Models.
We store the path in either case.
"""
__tablename__ = "secret_allowed_process"
__table_args__ = (
db.UniqueConstraint(
"secret_id", "allowed_relative_path", name="unique_secret_path"
),
)
id: int = db.Column(db.Integer, primary_key=True)
secret_id: int = db.Column(ForeignKey(SecretModel.id), nullable=False) # type: ignore
allowed_relative_path: str = db.Column(db.String(500), nullable=False)
class SecretAllowedProcessSchema(Schema):
"""SecretAllowedProcessSchema."""
class Meta:
"""Meta."""
model = SecretAllowedProcessPathModel
fields = ["secret_id", "allowed_relative_path"]

View File

@ -43,6 +43,8 @@ from spiffworkflow_backend.models.process_instance_report import (
)
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
from spiffworkflow_backend.models.secret_model import SecretAllowedProcessSchema
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.error_handling_service import ErrorHandlingService
@ -55,8 +57,10 @@ from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService,
)
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.secret_service import SecretService
from spiffworkflow_backend.services.service_task_service import ServiceTaskService
from spiffworkflow_backend.services.spec_file_service import SpecFileService
from spiffworkflow_backend.services.user_service import UserService
process_api_blueprint = Blueprint("process_api", __name__)
@ -283,7 +287,6 @@ def process_model_file_delete(
def add_file(process_group_id: str, process_model_id: str) -> flask.wrappers.Response:
"""Add_file."""
ProcessModelService()
process_model = get_process_model(process_model_id, process_group_id)
request_file = get_file_from_request()
if not request_file.filename:
@ -1096,3 +1099,52 @@ def get_spiff_task_from_process_instance(
)
)
return spiff_task
#
# Methods for secrets CRUD - maybe move somewhere else:
#
def get_secret(key: str) -> str | None:
"""Get_secret."""
return SecretService.get_secret(key)
def add_secret(body: Dict) -> Response:
"""Add secret."""
secret_model = SecretService().add_secret(
body["key"], body["value"], body["creator_user_id"]
)
assert secret_model # noqa: S101
return Response(
json.dumps(SecretModelSchema().dump(secret_model)),
status=201,
mimetype="application/json",
)
def update_secret(key: str, body: dict) -> None:
"""Update secret."""
SecretService().update_secret(key, body["value"], body["creator_user_id"])
def delete_secret(key: str) -> None:
"""Delete secret."""
current_user = UserService.current_user()
SecretService.delete_secret(key, current_user.id)
def add_allowed_process_path(body: dict) -> Any:
"""Get allowed process paths."""
allowed_process_path = SecretService.add_allowed_process(
body["secret_id"], g.user.id, body["allowed_relative_path"]
)
return Response(
json.dumps(SecretAllowedProcessSchema().dump(allowed_process_path)),
status=201,
mimetype="application/json",
)
def delete_allowed_process_path(allowed_process_path_id: int) -> Any:
"""Get allowed process paths."""
SecretService().delete_allowed_process(allowed_process_path_id, g.user.id)

View File

@ -151,22 +151,41 @@ def validate_scope(token: Any) -> bool:
return True
def api_login(uid: str, password: str, redirect_url: Optional[str] = None) -> dict:
"""Api_login."""
# TODO: Fix this! mac 20220801
# token:dict = PublicAuthenticationService().get_public_access_token(uid, password)
# g.token = token
#
# return token
return {}
# def login_api(redirect_url: str = "/v1.0/ui") -> Response:
# """Api_login."""
# # TODO: Fix this! mac 20220801
# # token:dict = PublicAuthenticationService().get_public_access_token(uid, password)
# #
# # return token
# # if uid:
# # sub = f"service:internal::service_id:{uid}"
# # token = encode_auth_token(sub)
# # user_model = UserModel(username=uid,
# # uid=uid,
# # service='internal',
# # name="API User")
# # g.user = user_model
# #
# # g.token = token
# # scope = get_scope(token)
# # return token
# # return {"uid": uid, "sub": uid, "scope": scope}
# return login(redirect_url)
def encode_auth_token(uid: str) -> str:
# def login_api_return(code: str, state: str, session_state: str) -> Optional[Response]:
# print("login_api_return")
def encode_auth_token(sub: str, token_type: Optional[str] = None) -> str:
"""Generates the Auth Token.
:return: string
"""
payload = {"sub": uid}
payload = {"sub": sub}
if token_type is None:
token_type = "internal" # noqa: S105
payload["token_type"] = token_type
if "SECRET_KEY" in current_app.config:
secret_key = current_app.config.get("SECRET_KEY")
else:
@ -304,4 +323,15 @@ def get_user_from_decoded_internal_token(decoded_token: dict) -> Optional[UserMo
.filter(UserModel.service_id == service_id)
.first()
)
# user: UserModel = UserModel.query.filter()
if user:
return user
user = UserModel(
username=service_id,
uid=service_id,
service=service,
service_id=service_id,
name="API User",
)
return user

View File

@ -132,7 +132,9 @@ def setup_logger(app: Flask) -> None:
spiff_logger_filehandler = None
if app.config["SPIFFWORKFLOW_BACKEND_LOG_TO_FILE"]:
spiff_logger_filehandler = logging.FileHandler(f"log/{app.env}.log")
spiff_logger_filehandler = logging.FileHandler(
f"{app.root_path}/../../log/{app.env}.log"
)
spiff_logger_filehandler.setLevel(spiff_log_level)
spiff_logger_filehandler.setFormatter(log_formatter)

View File

@ -0,0 +1,204 @@
"""Secret_service."""
from typing import Optional
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from sqlalchemy.exc import IntegrityError
from spiffworkflow_backend.models.secret_model import SecretAllowedProcessPathModel
from spiffworkflow_backend.models.secret_model import SecretModel
# from cryptography.fernet import Fernet
#
#
# class EncryptionService:
# key = Fernet.generate_key() # this is your "password"
# cipher_suite = Fernet(key)
# encoded_text = cipher_suite.encrypt(b"Hello stackoverflow!")
# decoded_text = cipher_suite.decrypt(encoded_text)
class SecretService:
"""SecretService."""
def encrypt_key(self, plain_key: str) -> str:
"""Encrypt_key."""
# flask_secret = current_app.secret_key
# print("encrypt_key")
...
def decrypt_key(self, encrypted_key: str) -> str:
"""Decrypt key."""
...
@staticmethod
def add_secret(
key: str,
value: str,
creator_user_id: int,
) -> SecretModel:
"""Add_secret."""
# encrypted_key = self.encrypt_key(key)
secret_model = SecretModel(
key=key, value=value, creator_user_id=creator_user_id
)
db.session.add(secret_model)
try:
db.session.commit()
except Exception as e:
raise ApiError(
code="create_secret_error",
message=f"There was an error creating a secret with key: {key} and value ending with: {value[:-4]}. "
f"Original error is {e}",
) from e
return secret_model
@staticmethod
def get_secret(key: str) -> str | None:
"""Get_secret."""
secret: SecretModel = (
db.session.query(SecretModel).filter(SecretModel.key == key).first()
)
if secret is not None:
return secret.value
@staticmethod
def update_secret(
key: str,
value: str,
creator_user_id: Optional[int] = None,
) -> None:
"""Does this pass pre commit?"""
secret_model = SecretModel.query.filter(SecretModel.key == key).first()
if secret_model:
if secret_model.creator_user_id == creator_user_id:
secret_model.value = value
db.session.add(secret_model)
try:
db.session.commit()
except Exception as e:
raise ApiError(
code="update_secret_error",
message=f"There was an error updating the secret with key: {key}, and value: {value}",
) from e
else:
raise ApiError(
code="update_secret_error",
message=f"User: {creator_user_id} cannot update the secret with key : {key}",
status_code=401,
)
else:
raise ApiError(
code="update_secret_error",
message=f"Cannot update secret with key: {key}. Resource does not exist.",
status_code=404,
)
@staticmethod
def delete_secret(key: str, user_id: int) -> None:
"""Delete secret."""
secret_model = SecretModel.query.filter(SecretModel.key == key).first()
if secret_model:
if secret_model.creator_user_id == user_id:
db.session.delete(secret_model)
try:
db.session.commit()
except Exception as e:
raise ApiError(
code="delete_secret_error",
message=f"Could not delete secret with key: {key}. Original error is: {e}",
) from e
else:
raise ApiError(
code="delete_secret_error",
message=f"User: {user_id} cannot delete the secret with key : {key}",
status_code=401,
)
else:
raise ApiError(
code="delete_secret_error",
message=f"Cannot delete secret with key: {key}. Resource does not exist.",
status_code=404,
)
@staticmethod
def add_allowed_process(
secret_id: int, user_id: str, allowed_relative_path: str
) -> SecretAllowedProcessPathModel:
"""Add_allowed_process."""
secret_model = SecretModel.query.filter(SecretModel.id == secret_id).first()
if secret_model:
if secret_model.creator_user_id == user_id:
secret_process_model = SecretAllowedProcessPathModel(
secret_id=secret_model.id,
allowed_relative_path=allowed_relative_path,
)
assert secret_process_model # noqa: S101
db.session.add(secret_process_model)
try:
db.session.commit()
except IntegrityError as ie:
db.session.rollback()
raise ApiError(
code="add_allowed_process_error",
message=f"Error adding allowed_process with secret {secret_model.id}, "
f"and path: {allowed_relative_path}. Resource already exists. "
f"Original error is {ie}",
status_code=409,
) from ie
except Exception as e:
# TODO: should we call db.session.rollback() here?
# db.session.rollback()
raise ApiError(
code="add_allowed_process_error",
message=f"Could not create an allowed process for secret with key: {secret_model.key} "
f"with path: {allowed_relative_path}. "
f"Original error is {e}",
) from e
return secret_process_model
else:
raise ApiError(
code="add_allowed_process_error",
message=f"User: {user_id} cannot modify the secret with key : {secret_model.key}",
status_code=401,
)
else:
raise ApiError(
code="add_allowed_process_error",
message=f"Cannot add allowed process to secret with key: {secret_id}. Resource does not exist.",
status_code=404,
)
@staticmethod
def delete_allowed_process(allowed_process_id: int, user_id: int) -> None:
"""Delete_allowed_process."""
allowed_process = SecretAllowedProcessPathModel.query.filter(
SecretAllowedProcessPathModel.id == allowed_process_id
).first()
if allowed_process:
secret = SecretModel.query.filter(
SecretModel.id == allowed_process.secret_id
).first()
assert secret # noqa: S101
if secret.creator_user_id == user_id:
db.session.delete(allowed_process)
try:
db.session.commit()
except Exception as e:
raise ApiError(
code="delete_allowed_process_error",
message=f"There was an exception deleting allowed_process: {allowed_process_id}. "
f"Original error is: {e}",
) from e
else:
raise ApiError(
code="delete_allowed_process_error",
message=f"User: {user_id} cannot delete the allowed_process with id : {allowed_process_id}",
status_code=401,
)
else:
raise ApiError(
code="delete_allowed_process_error",
message=f"Cannot delete allowed_process: {allowed_process_id}. Resource does not exist.",
status_code=404,
)

View File

@ -13,7 +13,6 @@ from flask.testing import FlaskClient
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from tests.spiffworkflow_backend.helpers.test_data import logged_in_headers
from werkzeug.test import TestResponse
from spiffworkflow_backend.models.process_group import ProcessGroup
@ -26,12 +25,14 @@ from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.user_service import UserService
# from tests.spiffworkflow_backend.helpers.test_data import logged_in_headers
class BaseTest:
"""BaseTest."""
@staticmethod
def find_or_create_user(username: str = "test_user1") -> UserModel:
def find_or_create_user(username: str = "test_user_1") -> UserModel:
"""Find_or_create_user."""
user = UserModel.query.filter_by(username=username).first()
if isinstance(user, UserModel):
@ -39,7 +40,6 @@ class BaseTest:
user = UserService().create_user("internal", username, username=username)
if isinstance(user, UserModel):
UserService().create_principal(user_id=user.id)
return user
raise ApiError(
@ -128,7 +128,7 @@ class BaseTest:
"/v1.0/process-models",
content_type="application/json",
data=json.dumps(ProcessModelInfoSchema().dump(model)),
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 201
return response
@ -154,7 +154,7 @@ class BaseTest:
data=data,
follow_redirects=True,
content_type="multipart/form-data",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 201
assert response.get_data() is not None
@ -164,7 +164,7 @@ class BaseTest:
response = client.get(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/{file_name}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
file2 = json.loads(response.get_data(as_text=True))
@ -184,7 +184,7 @@ class BaseTest:
)
response = client.post(
"/v1.0/process-groups",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group)),
)
@ -220,6 +220,32 @@ class BaseTest:
db.session.commit()
return process_instance
@staticmethod
def logged_in_headers(
user: UserModel, _redirect_url: str = "http://some/frontend/url"
) -> Dict[str, str]:
"""Logged_in_headers."""
# if user is None:
# uid = 'test_user'
# user_info = {'uid': 'test_user'}
# else:
# uid = user.uid
# user_info = {'uid': user.uid}
# query_string = user_info_to_query_string(user_info, redirect_url)
# rv = self.app.get("/v1.0/login%s" % query_string, follow_redirects=False)
# self.assertTrue(rv.status_code == 302)
# self.assertTrue(str.startswith(rv.location, redirect_url))
#
# user_model = session.query(UserModel).filter_by(uid=uid).first()
# self.assertIsNotNone(user_model.ldap_info.display_name)
# self.assertEqual(user_model.uid, uid)
# self.assertTrue('user' in g, 'User should be in Flask globals')
# user = UserService.current_user(allow_admin_impersonate=True)
# self.assertEqual(uid, user.uid, 'Logged in user should match given user uid')
return dict(Authorization="Bearer " + user.encode_auth_token())
def get_test_data_file_contents(
self, file_name: str, process_model_test_data_dir: str
) -> bytes:

View File

@ -1,5 +1,4 @@
"""User."""
from typing import Dict
from typing import Optional
from tests.spiffworkflow_backend.helpers.example_data import ExampleDataLoader
@ -9,7 +8,6 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
)
from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
@ -81,29 +79,3 @@ def load_test_spec(
# query_string_list.append('redirect_url=%s' % redirect_url)
#
# return '?%s' % '&'.join(query_string_list)
def logged_in_headers(
user: UserModel, _redirect_url: str = "http://some/frontend/url"
) -> Dict[str, str]:
"""Logged_in_headers."""
# if user is None:
# uid = 'test_user'
# user_info = {'uid': 'test_user'}
# else:
# uid = user.uid
# user_info = {'uid': user.uid}
# query_string = user_info_to_query_string(user_info, redirect_url)
# rv = self.app.get("/v1.0/login%s" % query_string, follow_redirects=False)
# self.assertTrue(rv.status_code == 302)
# self.assertTrue(str.startswith(rv.location, redirect_url))
#
# user_model = session.query(UserModel).filter_by(uid=uid).first()
# self.assertIsNotNone(user_model.ldap_info.display_name)
# self.assertEqual(user_model.uid, uid)
# self.assertTrue('user' in g, 'User should be in Flask globals')
# user = UserService.current_user(allow_admin_impersonate=True)
# self.assertEqual(uid, user.uid, 'Logged in user should match given user uid')
return dict(Authorization="Bearer " + user.encode_auth_token())

View File

@ -2,7 +2,6 @@
from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import logged_in_headers
class TestLoggingService(BaseTest):
@ -15,7 +14,7 @@ class TestLoggingService(BaseTest):
process_group_id = "test_logging_spiff_logger"
process_model_id = "simple_script"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
)
@ -23,13 +22,13 @@ class TestLoggingService(BaseTest):
process_instance_id = response.json["id"]
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
log_response = client.get(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/logs",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert log_response.status_code == 200
assert log_response.json

View File

@ -10,7 +10,6 @@ from flask.testing import FlaskClient
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from tests.spiffworkflow_backend.helpers.test_data import logged_in_headers
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
ProcessEntityNotFoundError,
@ -101,7 +100,7 @@ class TestProcessApi(BaseTest):
data=data,
follow_redirects=True,
content_type="multipart/form-data",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
process_model = ProcessModelService().get_process_model(
@ -125,7 +124,7 @@ class TestProcessApi(BaseTest):
user = self.find_or_create_user()
response = client.delete(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -142,7 +141,7 @@ class TestProcessApi(BaseTest):
test_process_group_id = "runs_without_input"
test_process_model_id = "sample"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
# create an instance from a model
response = self.create_process_instance(
client, test_process_group_id, test_process_model_id, headers
@ -155,7 +154,7 @@ class TestProcessApi(BaseTest):
# try to delete the model
response = client.delete(
f"/v1.0/process-models/{test_process_group_id}/{test_process_model_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
# make sure we get an error in the response
@ -183,7 +182,7 @@ class TestProcessApi(BaseTest):
user = self.find_or_create_user()
response = client.put(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(ProcessModelInfoSchema().dump(process_model)),
)
@ -217,7 +216,7 @@ class TestProcessApi(BaseTest):
# get all models
response = client.get(
f"/v1.0/process-models?process_group_identifier={group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 5
@ -228,7 +227,7 @@ class TestProcessApi(BaseTest):
# get first page, 1 per page
response = client.get(
f"/v1.0/process-models?page=1&per_page=1&process_group_identifier={group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 1
@ -240,7 +239,7 @@ class TestProcessApi(BaseTest):
# get second page, 1 per page
response = client.get(
f"/v1.0/process-models?page=2&per_page=1&process_group_identifier={group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 1
@ -252,7 +251,7 @@ class TestProcessApi(BaseTest):
# get first page, 3 per page
response = client.get(
f"/v1.0/process-models?page=1&per_page=3&process_group_identifier={group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 3
@ -264,7 +263,7 @@ class TestProcessApi(BaseTest):
# get second page, 3 per page
response = client.get(
f"/v1.0/process-models?page=2&per_page=3&process_group_identifier={group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
# there should only be 2 left
assert response.json is not None
@ -287,7 +286,7 @@ class TestProcessApi(BaseTest):
user = self.find_or_create_user()
response = client.post(
"/v1.0/process-groups",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group)),
)
@ -320,7 +319,8 @@ class TestProcessApi(BaseTest):
assert persisted.id == process_group_id
client.delete(
f"/v1.0/process-groups/{process_group_id}", headers=logged_in_headers(user)
f"/v1.0/process-groups/{process_group_id}",
headers=self.logged_in_headers(user),
)
with pytest.raises(ProcessEntityNotFoundError):
@ -345,7 +345,7 @@ class TestProcessApi(BaseTest):
response = client.put(
f"/v1.0/process-groups/{group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(ProcessGroupSchema().dump(process_group)),
)
@ -370,7 +370,7 @@ class TestProcessApi(BaseTest):
# get all groups
response = client.get(
"/v1.0/process-groups",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 5
@ -381,7 +381,7 @@ class TestProcessApi(BaseTest):
# get first page, one per page
response = client.get(
"/v1.0/process-groups?page=1&per_page=1",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 1
@ -393,7 +393,7 @@ class TestProcessApi(BaseTest):
# get second page, one per page
response = client.get(
"/v1.0/process-groups?page=2&per_page=1",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 1
@ -405,7 +405,7 @@ class TestProcessApi(BaseTest):
# get first page, 3 per page
response = client.get(
"/v1.0/process-groups?page=1&per_page=3",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
assert len(response.json["results"]) == 3
@ -419,7 +419,7 @@ class TestProcessApi(BaseTest):
# get second page, 3 per page
response = client.get(
"/v1.0/process-groups?page=2&per_page=3",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
# there should only be 2 left
assert response.json is not None
@ -444,7 +444,7 @@ class TestProcessApi(BaseTest):
data=data,
follow_redirects=True,
content_type="multipart/form-data",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -465,7 +465,7 @@ class TestProcessApi(BaseTest):
data=data,
follow_redirects=True,
content_type="multipart/form-data",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -487,7 +487,7 @@ class TestProcessApi(BaseTest):
data=data,
follow_redirects=True,
content_type="multipart/form-data",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
@ -496,7 +496,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/random_fact.svg",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
updated_file = json.loads(response.get_data(as_text=True))
@ -514,7 +514,7 @@ class TestProcessApi(BaseTest):
response = client.delete(
f"/v1.0/process-models/INCORRECT-NON-EXISTENT-GROUP/{process_model.id}/files/random_fact.svg",
follow_redirects=True,
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -532,7 +532,7 @@ class TestProcessApi(BaseTest):
response = client.delete(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/random_fact_DOES_NOT_EXIST.svg",
follow_redirects=True,
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -550,7 +550,7 @@ class TestProcessApi(BaseTest):
response = client.delete(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/random_fact.svg",
follow_redirects=True,
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
@ -559,7 +559,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/files/random_fact.svg",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 404
@ -573,7 +573,7 @@ class TestProcessApi(BaseTest):
load_test_spec(process_model_dir_name, process_group_id=test_process_group_id)
response = client.get(
f"/v1.0/process-models/{test_process_group_id}/{process_model_dir_name}/files/hello_world.bpmn",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -589,7 +589,7 @@ class TestProcessApi(BaseTest):
process_model = load_test_spec("hello_world")
response = client.post(
f"/v1.0/process-models/{process_model.process_group_id}/{process_model.id}/process-instances",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 201
assert response.json is not None
@ -601,7 +601,9 @@ class TestProcessApi(BaseTest):
) -> None:
"""Test_get_process_groups_when_none."""
user = self.find_or_create_user()
response = client.get("/v1.0/process-groups", headers=logged_in_headers(user))
response = client.get(
"/v1.0/process-groups", headers=self.logged_in_headers(user)
)
assert response.status_code == 200
assert response.json is not None
assert response.json["results"] == []
@ -612,7 +614,9 @@ class TestProcessApi(BaseTest):
"""Test_get_process_groups_when_there_are_some."""
user = self.find_or_create_user()
load_test_spec("hello_world")
response = client.get("/v1.0/process-groups", headers=logged_in_headers(user))
response = client.get(
"/v1.0/process-groups", headers=self.logged_in_headers(user)
)
assert response.status_code == 200
assert response.json is not None
assert len(response.json["results"]) == 1
@ -630,7 +634,7 @@ class TestProcessApi(BaseTest):
load_test_spec(process_model_dir_name, process_group_id=test_process_group_id)
response = client.get(
f"/v1.0/process-groups/{test_process_group_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -647,7 +651,7 @@ class TestProcessApi(BaseTest):
load_test_spec(process_model_dir_name, process_group_id=test_process_group_id)
response = client.get(
f"/v1.0/process-models/{test_process_group_id}/{process_model_dir_name}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -664,7 +668,7 @@ class TestProcessApi(BaseTest):
group_id = self.create_process_group(client, user, "my_group")
response = client.get(
f"/v1.0/process-models/{group_id}/{process_model_dir_name}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
assert response.json is not None
@ -677,7 +681,7 @@ class TestProcessApi(BaseTest):
test_process_group_id = "runs_without_input"
test_process_model_id = "sample"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, test_process_group_id, test_process_model_id, headers
)
@ -693,7 +697,7 @@ class TestProcessApi(BaseTest):
process_group_id = "runs_without_input"
process_model_id = "sample"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
)
@ -701,7 +705,7 @@ class TestProcessApi(BaseTest):
process_instance_id = response.json["id"]
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
@ -709,7 +713,7 @@ class TestProcessApi(BaseTest):
assert response.json["updated_at_in_seconds"] > 0
assert response.json["status"] == "complete"
assert response.json["process_model_identifier"] == process_model_id
assert response.json["data"]["current_user"]["username"] == "test_user1"
assert response.json["data"]["current_user"]["username"] == user.username
assert response.json["data"]["Mike"] == "Awesome"
assert response.json["data"]["person"] == "Kevin"
@ -733,7 +737,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/messages/{message_model_identifier}",
content_type="application/json",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
data=json.dumps({"payload": payload}),
)
assert response.status_code == 200
@ -773,7 +777,7 @@ class TestProcessApi(BaseTest):
client,
process_model.process_group_id,
process_model.id,
logged_in_headers(user),
self.logged_in_headers(user),
)
assert response.json is not None
process_instance_id = response.json["id"]
@ -781,7 +785,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_model.process_group_id}/"
f"{process_model.id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
@ -789,7 +793,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/messages/{message_model_identifier}",
content_type="application/json",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
data=json.dumps(
{"payload": payload, "process_instance_id": process_instance_id}
),
@ -824,7 +828,7 @@ class TestProcessApi(BaseTest):
client,
process_model.process_group_id,
process_model.id,
logged_in_headers(user),
self.logged_in_headers(user),
)
assert response.json is not None
process_instance_id = response.json["id"]
@ -832,7 +836,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_model.process_group_id}/"
f"{process_model.id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -840,7 +844,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_model.process_group_id}/"
f"{process_model.id}/process-instances/{process_instance_id}/terminate",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -859,7 +863,7 @@ class TestProcessApi(BaseTest):
process_model_id = "user_task"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
)
@ -868,7 +872,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
@ -883,7 +887,7 @@ class TestProcessApi(BaseTest):
delete_response = client.delete(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert delete_response.status_code == 200
@ -895,7 +899,7 @@ class TestProcessApi(BaseTest):
process_model_id = "user_task"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
)
@ -904,7 +908,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
@ -925,14 +929,14 @@ class TestProcessApi(BaseTest):
test_process_group_id = "runs_without_input"
process_model_dir_name = "sample"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
self.create_process_instance(
client, test_process_group_id, process_model_dir_name, headers
)
response = client.get(
"/v1.0/process-instances",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -961,7 +965,7 @@ class TestProcessApi(BaseTest):
test_process_group_id = "runs_without_input"
process_model_dir_name = "sample"
user = self.find_or_create_user()
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
self.create_process_instance(
client, test_process_group_id, process_model_dir_name, headers
)
@ -980,7 +984,7 @@ class TestProcessApi(BaseTest):
response = client.get(
"/v1.0/process-instances?per_page=2&page=3",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -991,7 +995,7 @@ class TestProcessApi(BaseTest):
response = client.get(
"/v1.0/process-instances?per_page=2&page=1",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1028,7 +1032,7 @@ class TestProcessApi(BaseTest):
# Without filtering we should get all 5 instances
response = client.get(
f"/v1.0/process-instances?process_group_identifier={test_process_group_id}&process_model_identifier={test_process_model_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1039,7 +1043,7 @@ class TestProcessApi(BaseTest):
for i in range(5):
response = client.get(
f"/v1.0/process-instances?process_status={ProcessInstanceStatus[statuses[i]].value}&process_group_identifier={test_process_group_id}&process_model_identifier={test_process_model_id}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1050,7 +1054,7 @@ class TestProcessApi(BaseTest):
# start > 1000 - this should eliminate the first
response = client.get(
"/v1.0/process-instances?start_from=1001",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1061,7 +1065,7 @@ class TestProcessApi(BaseTest):
# start > 2000, end < 5000 - this should eliminate the first 2 and the last
response = client.get(
"/v1.0/process-instances?start_from=2001&end_till=5999",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1072,7 +1076,7 @@ class TestProcessApi(BaseTest):
# start > 1000, start < 4000 - this should eliminate the first and the last 2
response = client.get(
"/v1.0/process-instances?start_from=1001&start_till=3999",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1083,7 +1087,7 @@ class TestProcessApi(BaseTest):
# end > 2000, end < 6000 - this should eliminate the first and the last
response = client.get(
"/v1.0/process-instances?end_from=2001&end_till=5999",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.json is not None
results = response.json["results"]
@ -1098,7 +1102,7 @@ class TestProcessApi(BaseTest):
process_group_identifier = "runs_without_input"
process_model_identifier = "sample"
user = self.find_or_create_user()
logged_in_headers(user)
self.logged_in_headers(user)
load_test_spec(
process_model_identifier, process_group_id=process_group_identifier
)
@ -1113,7 +1117,7 @@ class TestProcessApi(BaseTest):
)
response = client.get(
f"/v1.0/process-models/{process_group_identifier}/{process_model_identifier}/process-instances/reports",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1162,7 +1166,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/process-models/{test_process_group_id}/{process_model_dir_name}/process-instances/reports/sure",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1215,7 +1219,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/process-models/{test_process_group_id}/{process_model_dir_name}/process-instances/reports/sure?grade_level=1",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1235,7 +1239,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/process-models/{test_process_group_id}/{process_model_dir_name}/process-instances/reports/sure?grade_level=1",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 404
data = json.loads(response.get_data(as_text=True))
@ -1249,7 +1253,7 @@ class TestProcessApi(BaseTest):
user: UserModel,
) -> Any:
"""Setup_testing_instance."""
headers = logged_in_headers(user)
headers = self.logged_in_headers(user)
response = self.create_process_instance(
client, process_group_id, process_model_id, headers
)
@ -1279,7 +1283,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -1325,7 +1329,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
@ -1360,7 +1364,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/process-models/{process_group_id}/{process_model_id}/process-instances/{process_instance_id}/run",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 400
assert len(outbox) == 1
@ -1419,7 +1423,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/messages/{message_model_identifier}",
content_type="application/json",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
data=json.dumps({"payload": payload}),
)
assert response.status_code == 200
@ -1429,7 +1433,7 @@ class TestProcessApi(BaseTest):
response = client.post(
f"/v1.0/messages/{message_model_identifier}",
content_type="application/json",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
data=json.dumps({"payload": payload}),
)
assert response.status_code == 200
@ -1438,7 +1442,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/messages?process_instance_id={process_instance_id_one}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1450,7 +1454,7 @@ class TestProcessApi(BaseTest):
response = client.get(
f"/v1.0/messages?process_instance_id={process_instance_id_two}",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1462,7 +1466,7 @@ class TestProcessApi(BaseTest):
response = client.get(
"/v1.0/messages",
headers=logged_in_headers(user),
headers=self.logged_in_headers(user),
)
assert response.status_code == 200
assert response.json is not None
@ -1471,7 +1475,7 @@ class TestProcessApi(BaseTest):
# def test_get_process_model(self):
#
# load_test_spec('random_fact')
# response = client.get('/v1.0/workflow-specification/random_fact', headers=logged_in_headers())
# response = client.get('/v1.0/workflow-specification/random_fact', headers=self.logged_in_headers())
# assert_success(response)
# json_data = json.loads(response.get_data(as_text=True))
# api_spec = WorkflowSpecInfoSchema().load(json_data)

View File

@ -0,0 +1,492 @@
"""Test_secret_service."""
import json
import pytest
from flask.app import Flask
from flask.testing import FlaskClient
from flask_bpmn.api.api_error import ApiError
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from werkzeug.test import TestResponse
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.secret_model import SecretAllowedProcessPathModel
from spiffworkflow_backend.models.secret_model import SecretModel
from spiffworkflow_backend.models.secret_model import SecretModelSchema
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.secret_service import SecretService
class SecretServiceTestHelpers(BaseTest):
"""SecretServiceTestHelpers."""
test_key = "test_key"
test_value = "test_value"
test_process_group_id = "test"
test_process_group_display_name = "My Test Process Group"
test_process_model_id = "make_cookies"
test_process_model_display_name = "Cooooookies"
test_process_model_description = "Om nom nom delicious cookies"
def add_test_secret(self, user: UserModel) -> SecretModel:
"""Add_test_secret."""
return SecretService().add_secret(self.test_key, self.test_value, user.id)
def add_test_process(
self, client: FlaskClient, user: UserModel
) -> ProcessModelInfo:
"""Add_test_process."""
self.create_process_group(
client,
user,
self.test_process_group_id,
display_name=self.test_process_group_display_name,
)
self.create_process_model_with_api(
client,
process_group_id=self.test_process_group_id,
process_model_id=self.test_process_model_id,
process_model_display_name=self.test_process_model_display_name,
process_model_description=self.test_process_model_description,
)
process_model_info = ProcessModelService().get_process_model(
self.test_process_model_id, self.test_process_group_id
)
return process_model_info
def add_test_secret_allowed_process(
self, client: FlaskClient, user: UserModel
) -> SecretAllowedProcessPathModel:
"""Add_test_secret_allowed_process."""
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
test_secret = self.add_test_secret(user)
allowed_process_model = SecretService().add_allowed_process(
secret_id=test_secret.id,
user_id=user.id,
allowed_relative_path=process_model_relative_path,
)
return allowed_process_model
class TestSecretService(SecretServiceTestHelpers):
"""TestSecretService."""
def test_add_secret(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_add_secret."""
user = self.find_or_create_user()
test_secret = self.add_test_secret(user)
assert test_secret is not None
assert test_secret.key == self.test_key
assert test_secret.value == self.test_value
assert test_secret.creator_user_id == user.id
def test_add_secret_duplicate_key_fails(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_add_secret_duplicate_key_fails."""
user = self.find_or_create_user()
self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
self.add_test_secret(user)
assert "Duplicate entry" in ae.value.message
def test_get_secret(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_get_secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secret = SecretService().get_secret(self.test_key)
assert secret is not None
assert secret == self.test_value
def test_get_secret_bad_key_fails(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_get_secret_bad_service."""
user = self.find_or_create_user()
self.add_test_secret(user)
bad_secret = SecretService().get_secret("bad_key")
assert bad_secret is None
def test_update_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test update secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secret = SecretService.get_secret(self.test_key)
assert secret == self.test_value
SecretService.update_secret(self.test_key, "new_secret_value", user.id)
new_secret = SecretService.get_secret(self.test_key)
assert new_secret == "new_secret_value" # noqa: S105
def test_update_secret_bad_user_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_update_secret_bad_user."""
user = self.find_or_create_user()
self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService.update_secret(
self.test_key, "new_secret_value", user.id + 1
) # noqa: S105
assert (
ae.value.message
== f"User: {user.id+1} cannot update the secret with key : test_key"
)
def test_update_secret_bad_secret_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_update_secret_bad_secret_fails."""
user = self.find_or_create_user()
secret = self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService.update_secret(secret.key + "x", "some_new_value", user.id)
assert "Resource does not exist" in ae.value.message
def test_delete_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test delete secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secrets = SecretModel.query.all()
assert len(secrets) == 1
assert secrets[0].creator_user_id == user.id
SecretService.delete_secret(self.test_key, user.id)
secrets = SecretModel.query.all()
assert len(secrets) == 0
def test_delete_secret_bad_user_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_delete_secret_bad_user."""
user = self.find_or_create_user()
self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService.delete_secret(self.test_key, user.id + 1)
assert (
f"User: {user.id+1} cannot delete the secret with key" in ae.value.message
)
def test_delete_secret_bad_secret_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_delete_secret_bad_secret_fails."""
user = self.find_or_create_user()
self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService.delete_secret(self.test_key + "x", user.id)
assert "Resource does not exist" in ae.value.message
def test_secret_add_allowed_process(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_add_allowed_process."""
user = self.find_or_create_user()
test_secret = self.add_test_secret(user)
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
allowed_process_model = SecretService().add_allowed_process(
secret_id=test_secret.id,
user_id=user.id,
allowed_relative_path=process_model_relative_path,
)
assert allowed_process_model is not None
assert isinstance(allowed_process_model, SecretAllowedProcessPathModel)
assert allowed_process_model.secret_id == test_secret.id
assert (
allowed_process_model.allowed_relative_path == process_model_relative_path
)
assert len(test_secret.allowed_processes) == 1
assert test_secret.allowed_processes[0] == allowed_process_model
def test_secret_add_allowed_process_same_process_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Do not allow duplicate entries for secret_id/allowed_relative_path pairs.
We actually take care of this in the db model with a unique constraint
on the 2 columns.
"""
user = self.find_or_create_user()
test_secret = self.add_test_secret(user)
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
SecretService().add_allowed_process(
secret_id=test_secret.id,
user_id=user.id,
allowed_relative_path=process_model_relative_path,
)
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 1
with pytest.raises(ApiError) as ae:
SecretService().add_allowed_process(
secret_id=test_secret.id,
user_id=user.id,
allowed_relative_path=process_model_relative_path,
)
assert "Resource already exists" in ae.value.message
assert "IntegrityError" in ae.value.message
assert "Duplicate entry" in ae.value.message
def test_secret_add_allowed_process_bad_user_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_add_allowed_process_bad_user."""
user = self.find_or_create_user()
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
test_secret = self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService().add_allowed_process(
secret_id=test_secret.id,
user_id=user.id + 1,
allowed_relative_path=process_model_relative_path,
)
assert (
ae.value.message
== f"User: {user.id+1} cannot modify the secret with key : {self.test_key}"
)
def test_secret_add_allowed_process_bad_secret_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_add_allowed_process_bad_secret_fails."""
user = self.find_or_create_user()
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
test_secret = self.add_test_secret(user)
with pytest.raises(ApiError) as ae:
SecretService().add_allowed_process(
secret_id=test_secret.id + 1,
user_id=user.id,
allowed_relative_path=process_model_relative_path,
)
assert "Resource does not exist" in ae.value.message
print("test_secret_add_allowed_process_bad_secret")
def test_secret_delete_allowed_process(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_delete_allowed_process."""
user = self.find_or_create_user()
allowed_process_model = self.add_test_secret_allowed_process(client, user)
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 1
SecretService().delete_allowed_process(allowed_process_model.id, user.id)
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 0
def test_secret_delete_allowed_process_bad_user_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_delete_allowed_process_bad_user_fails."""
user = self.find_or_create_user()
allowed_process_model = self.add_test_secret_allowed_process(client, user)
with pytest.raises(ApiError) as ae:
SecretService().delete_allowed_process(
allowed_process_model.id, user.id + 1
)
message = ae.value.message
assert (
f"User: {user.id+1} cannot delete the allowed_process with id : {allowed_process_model.id}"
in message
)
def test_secret_delete_allowed_process_bad_allowed_process_fails(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_secret_delete_allowed_process_bad_allowed_process_fails."""
user = self.find_or_create_user()
allowed_process_model = self.add_test_secret_allowed_process(client, user)
with pytest.raises(ApiError) as ae:
SecretService().delete_allowed_process(
allowed_process_model.id + 1, user.id
)
assert "Resource does not exist" in ae.value.message
class TestSecretServiceApi(SecretServiceTestHelpers):
"""TestSecretServiceApi."""
def test_add_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_add_secret."""
user = self.find_or_create_user()
secret_model = SecretModel(
key=self.test_key,
value=self.test_value,
creator_user_id=user.id,
)
data = json.dumps(SecretModelSchema().dump(secret_model))
response: TestResponse = client.post(
"/v1.0/secrets",
headers=self.logged_in_headers(user),
content_type="application/json",
data=data,
)
assert response.json
secret: dict = response.json
for key in ["key", "value", "creator_user_id"]:
assert key in secret.keys()
assert secret["key"] == self.test_key
assert secret["value"] == self.test_value
assert secret["creator_user_id"] == user.id
def test_get_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test get secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secret_response = client.get(
f"/v1.0/secrets/{self.test_key}",
headers=self.logged_in_headers(user),
)
assert secret_response
assert secret_response.status_code == 200
assert secret_response.json == self.test_value
def test_update_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_update_secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secret = SecretService.get_secret(self.test_key)
assert secret == self.test_value
secret_model = SecretModel(
key=self.test_key, value="new_secret_value", creator_user_id=user.id
)
response = client.put(
f"/v1.0/secrets/{self.test_key}",
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(SecretModelSchema().dump(secret_model)),
)
assert response.status_code == 204
secret_model = SecretModel.query.filter(
SecretModel.key == self.test_key
).first()
assert secret_model.value == "new_secret_value"
def test_delete_secret(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test delete secret."""
user = self.find_or_create_user()
self.add_test_secret(user)
secret = SecretService.get_secret(self.test_key)
assert secret
assert secret == self.test_value
secret_response = client.delete(
f"/v1.0/secrets/{self.test_key}",
headers=self.logged_in_headers(user),
)
assert secret_response.status_code == 204
secret = SecretService.get_secret(self.test_key)
assert secret is None
def test_delete_secret_bad_user(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_delete_secret_bad_user."""
user_1 = self.find_or_create_user()
user_2 = self.find_or_create_user("test_user_2")
self.add_test_secret(user_1)
secret_response = client.delete(
f"/v1.0/secrets/{self.test_key}",
headers=self.logged_in_headers(user_2),
)
assert secret_response.status_code == 401
def test_delete_secret_bad_key(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test delete secret."""
user = self.find_or_create_user()
secret_response = client.delete(
"/v1.0/secrets/bad_secret_key",
headers=self.logged_in_headers(user),
)
assert secret_response.status_code == 404
print("test_delete_secret_bad_key")
def test_add_secret_allowed_process(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test add secret allowed process."""
user = self.find_or_create_user()
test_secret = self.add_test_secret(user)
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
data = {
"secret_id": test_secret.id,
"allowed_relative_path": process_model_relative_path,
}
response: TestResponse = client.post(
"/v1.0/secrets/allowed_process_paths",
headers=self.logged_in_headers(user),
content_type="application/json",
data=json.dumps(data),
)
assert response.status_code == 201
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 1
assert allowed_processes[0].allowed_relative_path == process_model_relative_path
assert allowed_processes[0].secret_id == test_secret.id
def test_delete_secret_allowed_process(
self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test delete secret allowed process."""
user = self.find_or_create_user()
test_secret = self.add_test_secret(user)
process_model_info = self.add_test_process(client, user)
process_model_relative_path = FileSystemService.process_model_relative_path(
process_model_info
)
allowed_process = SecretService.add_allowed_process(
test_secret.id, user.id, process_model_relative_path
)
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 1
assert allowed_processes[0].secret_id == test_secret.id
assert allowed_processes[0].allowed_relative_path == process_model_relative_path
response = client.delete(
f"/v1.0/secrets/allowed_process_paths/{allowed_process.id}",
headers=self.logged_in_headers(user),
)
assert response.status_code == 204
allowed_processes = SecretAllowedProcessPathModel.query.all()
assert len(allowed_processes) == 0