Merge branch 'main' into bug/refresh-token
# Conflicts: # migrations/versions/07ff3fbef405_.py # migrations/versions/9e14b40371f3_.py # migrations/versions/c98445562ced_.py # src/spiffworkflow_backend/services/authentication_service.py
This commit is contained in:
commit
8e1075406f
|
@ -51,4 +51,4 @@ export IS_GUNICORN="true"
|
|||
export PROCESS_WAITING_MESSAGES="true"
|
||||
|
||||
# THIS MUST BE THE LAST COMMAND!
|
||||
exec poetry run gunicorn ${additional_args} --bind "0.0.0.0:$port" --workers="$workers" --timeout 90 --capture-output --access-logfile '-' --log-level debug wsgi:app
|
||||
exec poetry run gunicorn ${additional_args} --bind "0.0.0.0:$port" --workers="$workers" --limit-request-line 8192 --timeout 90 --capture-output --access-logfile '-' --log-level debug wsgi:app
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function error_handler() {
|
||||
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
|
||||
exit "$2"
|
||||
}
|
||||
trap 'error_handler ${LINENO} $?' ERR
|
||||
set -o errtrace -o errexit -o nounset -o pipefail
|
||||
|
||||
docker ps | grep -Ev '(static|CONTAINER)' | awk '{print $1}' | xargs docker stop
|
||||
docker ps -a | grep -Ev '(static|CONTAINER)' | awk '{print $1}' | xargs docker rm
|
|
@ -26,7 +26,7 @@
|
|||
"oauth2DeviceCodeLifespan": 600,
|
||||
"oauth2DevicePollingInterval": 5,
|
||||
"enabled": true,
|
||||
"sslRequired": "NONE",
|
||||
"sslRequired": "external",
|
||||
"registrationAllowed": false,
|
||||
"registrationEmailAsUsername": false,
|
||||
"rememberMe": false,
|
||||
|
@ -806,6 +806,28 @@
|
|||
"notBefore": 0,
|
||||
"groups": []
|
||||
},
|
||||
{
|
||||
"id": "cecacfd3-2f59-4ce2-87d9-bea91ef13c5b",
|
||||
"createdTimestamp": 1666102618518,
|
||||
"username": "natalia",
|
||||
"enabled": true,
|
||||
"totp": false,
|
||||
"emailVerified": false,
|
||||
"credentials": [
|
||||
{
|
||||
"id": "b6aa9936-39cc-4931-bfeb-60e6753de5ba",
|
||||
"type": "password",
|
||||
"createdDate": 1666102626704,
|
||||
"secretData": "{\"value\":\"kGyQIqZM6n9rjGZkNScJbkFjLvRJ2I+ZzCtjQ80e+zX7QaXtIF3CEeSY6KTXVjE8Z74oyVBWTIibpiTblm5Ztw==\",\"salt\":\"0k+Y+QJiW0YhxuxxYigasg==\",\"additionalParameters\":{}}",
|
||||
"credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
|
||||
}
|
||||
],
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-spiffworkflow"],
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
},
|
||||
{
|
||||
"id": "a15da457-7ebb-49d4-9dcc-6876cb71600d",
|
||||
"createdTimestamp": 1657115919770,
|
||||
|
@ -1255,7 +1277,8 @@
|
|||
"redirectUris": [
|
||||
"http://localhost:7000/*",
|
||||
"http://67.205.133.116:7000/*",
|
||||
"http://167.172.242.138:7000/*"
|
||||
"http://167.172.242.138:7000/*",
|
||||
"https://api.demo.spiffworkflow.org/*"
|
||||
],
|
||||
"webOrigins": [],
|
||||
"notBefore": 0,
|
||||
|
@ -1525,7 +1548,8 @@
|
|||
"redirectUris": [
|
||||
"http://localhost:7001/*",
|
||||
"http://67.205.133.116:7000/*",
|
||||
"http://167.172.242.138:7001/*"
|
||||
"http://167.172.242.138:7001/*",
|
||||
"https://api.demo.spiffworkflow.org/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
|
@ -1594,7 +1618,8 @@
|
|||
"redirectUris": [
|
||||
"http://localhost:7001/*",
|
||||
"http://67.205.133.116:7000/*",
|
||||
"http://167.172.242.138:7001/*"
|
||||
"http://167.172.242.138:7001/*",
|
||||
"https://api.demo.spiffworkflow.org/*"
|
||||
],
|
||||
"webOrigins": [],
|
||||
"notBefore": 0,
|
||||
|
@ -2306,10 +2331,10 @@
|
|||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper"
|
||||
|
@ -2334,14 +2359,14 @@
|
|||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper"
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -2455,7 +2480,7 @@
|
|||
"supportedLocales": [],
|
||||
"authenticationFlows": [
|
||||
{
|
||||
"id": "24ffe820-51bc-402b-b165-7745b6363275",
|
||||
"id": "3ec26fff-71d4-4b11-a747-f06f13423195",
|
||||
"alias": "Account verification options",
|
||||
"description": "Method with which to verity the existing account",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2481,7 +2506,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "a1e19975-9f44-4ddd-ab5a-2315afa028b1",
|
||||
"id": "639c5cc5-30c2-4d3f-a089-fa64cc5e7107",
|
||||
"alias": "Authentication Options",
|
||||
"description": "Authentication options.",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2515,7 +2540,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "88ee8214-27f8-4da3-ba54-cb69053bf593",
|
||||
"id": "32e28313-f365-4ebf-a323-2ea44de185ae",
|
||||
"alias": "Browser - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2541,7 +2566,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "2a720f72-2f6f-4e64-906c-2be5e2fd95fb",
|
||||
"id": "bd58057b-475e-4ac3-891a-1673f732afcb",
|
||||
"alias": "Direct Grant - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2567,7 +2592,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "b6f70fef-da90-4033-9f0e-d1b7f8619e68",
|
||||
"id": "4e042249-48ca-4634-814b-22c8eb85cb7b",
|
||||
"alias": "First broker login - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2593,7 +2618,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "c3869d8d-dda3-4b13-a7f5-55f29195d03a",
|
||||
"id": "862d0cc1-2c80-4e8b-90ac-32988d4ba8b3",
|
||||
"alias": "Handle Existing Account",
|
||||
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2619,7 +2644,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "e2855580-7582-4835-b2af-de34215532fe",
|
||||
"id": "efec0d38-6dfd-4f1a-bddc-56a99e772052",
|
||||
"alias": "Reset - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2645,7 +2670,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "4224394c-485e-42ee-a65a-2bdc6eb092fd",
|
||||
"id": "fc35195a-7cf8-45ed-a6db-66c862ea55e2",
|
||||
"alias": "User creation or linking",
|
||||
"description": "Flow for the existing/non-existing user alternatives",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2672,7 +2697,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "fef8981c-e419-4564-ae91-755e489e6d60",
|
||||
"id": "7be21a14-c03b-45d0-8539-790549d2a620",
|
||||
"alias": "Verify Existing Account by Re-authentication",
|
||||
"description": "Reauthentication of existing account",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2698,7 +2723,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "f214f005-ad6c-4314-86b9-8d973fbaa3d2",
|
||||
"id": "e05cd6b8-cbbb-46ca-a7b7-c3792705da0b",
|
||||
"alias": "browser",
|
||||
"description": "browser based authentication",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2740,7 +2765,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "7a4f7246-66dd-44f6-9c57-917ba6e62197",
|
||||
"id": "c8b4ddcd-fc90-4492-a436-9453765ea05f",
|
||||
"alias": "clients",
|
||||
"description": "Base authentication for clients",
|
||||
"providerId": "client-flow",
|
||||
|
@ -2782,7 +2807,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "2ff421f8-d280-4d56-bd34-25b2a5c3148e",
|
||||
"id": "eb2f7103-73c9-4916-a612-e0aad579e6a7",
|
||||
"alias": "direct grant",
|
||||
"description": "OpenID Connect Resource Owner Grant",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2816,7 +2841,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "ae42aaf0-f2a7-4e38-81be-c9fc06dea76e",
|
||||
"id": "773ea3a2-2401-4147-b64b-001bd1f5f6c5",
|
||||
"alias": "docker auth",
|
||||
"description": "Used by Docker clients to authenticate against the IDP",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2834,7 +2859,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "e5aa743d-c889-422e-ba9f-90fee8c7f5d9",
|
||||
"id": "2f834413-ed70-40f5-82bd-bcea67a1121d",
|
||||
"alias": "first broker login",
|
||||
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2861,7 +2886,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "a54ebefa-6ef6-4e42-a016-2b56af3f8aaa",
|
||||
"id": "593b072d-c66c-41f4-9fe0-37ba45acc6ee",
|
||||
"alias": "forms",
|
||||
"description": "Username, password, otp and other auth forms.",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2887,7 +2912,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "b5d4595a-88b2-4ea9-aeea-d796b0b9085d",
|
||||
"id": "8d932a3a-62cd-4aac-94cc-082196eb5a95",
|
||||
"alias": "http challenge",
|
||||
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2913,7 +2938,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "da2eba73-45d5-4f0f-bfe8-8812481cde93",
|
||||
"id": "2a34b84c-93e7-466a-986a-e5a7a8cad061",
|
||||
"alias": "registration",
|
||||
"description": "registration flow",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -2932,7 +2957,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "6d49fc23-14db-49a2-89b5-58439022e649",
|
||||
"id": "b601070a-b986-482d-8649-9df8feff3bf3",
|
||||
"alias": "registration form",
|
||||
"description": "registration form",
|
||||
"providerId": "form-flow",
|
||||
|
@ -2974,7 +2999,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "a0615de2-cf4a-4812-a9ef-fbc4e38e3d10",
|
||||
"id": "7b1d2327-8429-4584-b6cf-35bfc17bdc8f",
|
||||
"alias": "reset credentials",
|
||||
"description": "Reset credentials for a user if they forgot their password or something",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -3016,7 +3041,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "69f5f241-2b8a-4fe0-a38d-e4abee38add2",
|
||||
"id": "3325ebbb-617c-4917-ab4e-e5f25642536c",
|
||||
"alias": "saml ecp",
|
||||
"description": "SAML ECP Profile Authentication Flow",
|
||||
"providerId": "basic-flow",
|
||||
|
@ -3036,14 +3061,14 @@
|
|||
],
|
||||
"authenticatorConfig": [
|
||||
{
|
||||
"id": "7257ea10-3ff4-4001-8171-edc7a7e5b751",
|
||||
"id": "33b05ac0-d30b-43d8-9ec4-08b79939a561",
|
||||
"alias": "create unique user config",
|
||||
"config": {
|
||||
"require.password.update.after.registration": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "105a6011-5d34-4b70-aaf1-52833e8f62b6",
|
||||
"id": "032891cb-dbd8-4035-a3a9-9c24f644247f",
|
||||
"alias": "review profile config",
|
||||
"config": {
|
||||
"update.profile.on.first.login": "missing"
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
FROM quay.io/keycloak/keycloak:18.0.2 as builder
|
||||
|
||||
ENV KEYCLOAK_LOGLEVEL="ALL"
|
||||
ENV ROOT_LOGLEVEL="ALL"
|
||||
ENV KC_HEALTH_ENABLED="true"
|
||||
# ENV KC_METRICS_ENABLED=true
|
||||
ENV PROXY_ADDRESS_FORWARDING="true"
|
||||
ENV KC_HOSTNAME="keycloak.demo.spiffworkflow.org"
|
||||
ENV KC_HOSTNAME_URL="https://keycloak.demo.spiffworkflow.org"
|
||||
ENV KC_FEATURES="token-exchange,admin-fine-grained-authz"
|
||||
# ENV KC_DB=postgres
|
||||
# Install custom providers
|
||||
# RUN curl -sL https://github.com/aerogear/keycloak-metrics-spi/releases/download/2.5.3/keycloak-metrics-spi-2.5.3.jar -o /opt/keycloak/providers/keycloak-metrics-spi-2.5.3.jar
|
||||
RUN /opt/keycloak/bin/kc.sh build
|
||||
|
||||
FROM quay.io/keycloak/keycloak:18.0.2
|
||||
COPY --from=builder /opt/keycloak/ /opt/keycloak/
|
||||
WORKDIR /opt/keycloak
|
||||
# for demonstration purposes only, please make sure to use proper certificates in production instead
|
||||
# RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore
|
||||
# # change these values to point to a running postgres instance
|
||||
# ENV KC_DB_URL=<DBURL>
|
||||
# ENV KC_DB_USERNAME=<DBUSERNAME>
|
||||
# ENV KC_DB_PASSWORD=<DBPASSWORD>
|
||||
# ENV KC_HOSTNAME=localhost
|
||||
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: c98445562ced
|
||||
Revision ID: f1f17d99d118
|
||||
Revises:
|
||||
Create Date: 2022-10-20 11:29:08.160790
|
||||
Create Date: 2022-10-20 11:52:54.758095
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c98445562ced'
|
||||
revision = 'f1f17d99d118'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -145,8 +145,10 @@ def upgrade():
|
|||
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('value', sa.Text(), nullable=False),
|
||||
sa.Column('creator_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['creator_user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('key')
|
||||
|
@ -174,7 +176,7 @@ def upgrade():
|
|||
sa.Column('task_type', sa.String(length=50), nullable=True),
|
||||
sa.Column('task_status', sa.String(length=50), nullable=True),
|
||||
sa.Column('process_model_display_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('task_data', sa.Text(), nullable=True),
|
||||
sa.Column('task_data', sa.Text(length=4294000000), nullable=True),
|
||||
sa.ForeignKeyConstraint(['assigned_principal_id'], ['principal.id'], ),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
|
@ -639,7 +639,7 @@ werkzeug = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/sartography/flask-bpmn"
|
||||
reference = "main"
|
||||
resolved_reference = "f3fc539423a3522d142146d2a039c0cd49badaf5"
|
||||
resolved_reference = "c8fd01df47518749a074772fec383256c482139f"
|
||||
|
||||
[[package]]
|
||||
name = "Flask-Cors"
|
||||
|
@ -1847,7 +1847,7 @@ test = ["pytest"]
|
|||
|
||||
[[package]]
|
||||
name = "SpiffWorkflow"
|
||||
version = "1.1.7"
|
||||
version = "1.2.1"
|
||||
description = "A workflow framework and BPMN/DMN Processor"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -1858,7 +1858,6 @@ develop = false
|
|||
celery = "*"
|
||||
configparser = "*"
|
||||
dateparser = "*"
|
||||
importlib-metadata = "<5.0"
|
||||
lxml = "*"
|
||||
pytz = "*"
|
||||
|
||||
|
@ -1866,7 +1865,7 @@ pytz = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/sartography/SpiffWorkflow"
|
||||
reference = "main"
|
||||
resolved_reference = "63db3e45947ec66b8d0efc2c74064004f8ff482c"
|
||||
resolved_reference = "a094adad8767f82e9c5fa806a46597e066252a72"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
|
@ -1978,11 +1977,51 @@ python-versions = ">=3.5.3"
|
|||
doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["mypy", "pytest", "typing-extensions"]
|
||||
|
||||
[[package]]
|
||||
name = "types-click"
|
||||
version = "7.1.8"
|
||||
description = "Typing stubs for click"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-Flask"
|
||||
version = "1.1.6"
|
||||
description = "Typing stubs for Flask"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
types-click = "*"
|
||||
types-Jinja2 = "*"
|
||||
types-Werkzeug = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-Jinja2"
|
||||
version = "2.11.9"
|
||||
description = "Typing stubs for Jinja2"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
types-MarkupSafe = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-MarkupSafe"
|
||||
version = "1.1.10"
|
||||
description = "Typing stubs for MarkupSafe"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pytz"
|
||||
version = "2022.4.0.0"
|
||||
description = "Typing stubs for pytz"
|
||||
category = "main"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
|
@ -1990,7 +2029,7 @@ python-versions = "*"
|
|||
name = "types-PyYAML"
|
||||
version = "6.0.12"
|
||||
description = "Typing stubs for PyYAML"
|
||||
category = "main"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
|
@ -1998,7 +2037,7 @@ python-versions = "*"
|
|||
name = "types-requests"
|
||||
version = "2.28.11.1"
|
||||
description = "Typing stubs for requests"
|
||||
category = "main"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
|
@ -2009,7 +2048,15 @@ types-urllib3 = "<1.27"
|
|||
name = "types-urllib3"
|
||||
version = "1.26.25"
|
||||
description = "Typing stubs for urllib3"
|
||||
category = "main"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-Werkzeug"
|
||||
version = "1.0.9"
|
||||
description = "Typing stubs for Werkzeug"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
|
@ -2186,7 +2233,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "7b4eb35239359ebff4c5597052aedc14b47cc7d1880b5617632edbb957511908"
|
||||
content-hash = "cff4bcfd10157833f1a0f0bb806c3543267c3e99cc13f311b328d101c30ac553"
|
||||
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
|
@ -2965,18 +3012,7 @@ py = [
|
|||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pyasn1 = [
|
||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||
{file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
|
||||
{file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
|
||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
|
||||
{file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
|
||||
{file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
|
||||
{file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
|
||||
{file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
|
||||
{file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
|
||||
{file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
|
||||
{file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
|
||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
||||
]
|
||||
pycodestyle = [
|
||||
|
@ -3400,6 +3436,22 @@ typeguard = [
|
|||
{file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"},
|
||||
{file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"},
|
||||
]
|
||||
types-click = [
|
||||
{file = "types-click-7.1.8.tar.gz", hash = "sha256:b6604968be6401dc516311ca50708a0a28baa7a0cb840efd7412f0dbbff4e092"},
|
||||
{file = "types_click-7.1.8-py3-none-any.whl", hash = "sha256:8cb030a669e2e927461be9827375f83c16b8178c365852c060a34e24871e7e81"},
|
||||
]
|
||||
types-Flask = [
|
||||
{file = "types-Flask-1.1.6.tar.gz", hash = "sha256:aac777b3abfff9436e6b01f6d08171cf23ea6e5be71cbf773aaabb1c5763e9cf"},
|
||||
{file = "types_Flask-1.1.6-py3-none-any.whl", hash = "sha256:6ab8a9a5e258b76539d652f6341408867298550b19b81f0e41e916825fc39087"},
|
||||
]
|
||||
types-Jinja2 = [
|
||||
{file = "types-Jinja2-2.11.9.tar.gz", hash = "sha256:dbdc74a40aba7aed520b7e4d89e8f0fe4286518494208b35123bcf084d4b8c81"},
|
||||
{file = "types_Jinja2-2.11.9-py3-none-any.whl", hash = "sha256:60a1e21e8296979db32f9374d8a239af4cb541ff66447bb915d8ad398f9c63b2"},
|
||||
]
|
||||
types-MarkupSafe = [
|
||||
{file = "types-MarkupSafe-1.1.10.tar.gz", hash = "sha256:85b3a872683d02aea3a5ac2a8ef590193c344092032f58457287fbf8e06711b1"},
|
||||
{file = "types_MarkupSafe-1.1.10-py3-none-any.whl", hash = "sha256:ca2bee0f4faafc45250602567ef38d533e877d2ddca13003b319c551ff5b3cc5"},
|
||||
]
|
||||
types-pytz = [
|
||||
{file = "types-pytz-2022.4.0.0.tar.gz", hash = "sha256:17d66e4b16e80ceae0787726f3a22288df7d3f9fdebeb091dc64b92c0e4ea09d"},
|
||||
{file = "types_pytz-2022.4.0.0-py3-none-any.whl", hash = "sha256:950b0f3d64ed5b03a3e29c1e38fe2be8371c933c8e97922d0352345336eb8af4"},
|
||||
|
@ -3416,6 +3468,10 @@ types-urllib3 = [
|
|||
{file = "types-urllib3-1.26.25.tar.gz", hash = "sha256:5aef0e663724eef924afa8b320b62ffef2c1736c1fa6caecfc9bc6c8ae2c3def"},
|
||||
{file = "types_urllib3-1.26.25-py3-none-any.whl", hash = "sha256:c1d78cef7bd581e162e46c20a57b2e1aa6ebecdcf01fd0713bb90978ff3e3427"},
|
||||
]
|
||||
types-Werkzeug = [
|
||||
{file = "types-Werkzeug-1.0.9.tar.gz", hash = "sha256:5cc269604c400133d452a40cee6397655f878fc460e03fde291b9e3a5eaa518c"},
|
||||
{file = "types_Werkzeug-1.0.9-py3-none-any.whl", hash = "sha256:194bd5715a13c598f05c63e8a739328657590943bce941e8a3619a6b5d4a54ec"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
||||
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
||||
|
|
|
@ -47,15 +47,12 @@ marshmallow-enum = "^1.5.1"
|
|||
marshmallow-sqlalchemy = "^0.28.0"
|
||||
PyJWT = "^2.4.0"
|
||||
gunicorn = "^20.1.0"
|
||||
types-pytz = "^2022.1.1"
|
||||
python-keycloak = "^2.5.0"
|
||||
APScheduler = "^3.9.1"
|
||||
types-requests = "^2.28.6"
|
||||
Jinja2 = "^3.1.2"
|
||||
RestrictedPython = "^5.2"
|
||||
Flask-SQLAlchemy = "^3"
|
||||
orjson = "^3.8.0"
|
||||
types-PyYAML = "^6.0.12"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
@ -71,6 +68,11 @@ pre-commit = "^2.20.0"
|
|||
flake8 = "^4.0.1"
|
||||
black = ">=21.10b0"
|
||||
flake8-bandit = "^2.1.2"
|
||||
types-Werkzeug = "^1.0.9"
|
||||
types-PyYAML = "^6.0.12"
|
||||
types-Flask = "^1.1.6"
|
||||
types-requests = "^2.28.6"
|
||||
types-pytz = "^2022.1.1"
|
||||
|
||||
# 1.7.3 broke us. https://github.com/PyCQA/bandit/issues/841
|
||||
bandit = "1.7.2"
|
||||
|
|
|
@ -13,6 +13,7 @@ from flask_bpmn.models.db import db
|
|||
from flask_bpmn.models.db import migrate
|
||||
from flask_cors import CORS # type: ignore
|
||||
from flask_mail import Mail # type: ignore
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
import spiffworkflow_backend.load_database_models # noqa: F401
|
||||
from spiffworkflow_backend.config import setup_config
|
||||
|
@ -131,6 +132,8 @@ def get_hacked_up_app_for_script() -> flask.app.Flask:
|
|||
else:
|
||||
raise Exception(f"Could not find {full_process_model_path}")
|
||||
app = create_app()
|
||||
setup_config(app)
|
||||
configure_sentry(app)
|
||||
return app
|
||||
|
||||
|
||||
|
@ -140,6 +143,15 @@ def configure_sentry(app: flask.app.Flask) -> None:
|
|||
from flask import Flask
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
|
||||
def before_send(event: Any, hint: Any) -> Any:
|
||||
"""Before_send."""
|
||||
if "exc_info" in hint:
|
||||
_exc_type, exc_value, _tb = hint["exc_info"]
|
||||
# NotFound is mostly from web crawlers
|
||||
if isinstance(exc_value, NotFound):
|
||||
return None
|
||||
return event
|
||||
|
||||
sentry_sample_rate = app.config.get("SENTRY_SAMPLE_RATE")
|
||||
if sentry_sample_rate is None:
|
||||
return
|
||||
|
@ -153,6 +165,7 @@ def configure_sentry(app: flask.app.Flask) -> None:
|
|||
# of transactions for performance monitoring.
|
||||
# We recommend adjusting this value in production.
|
||||
traces_sample_rate=float(sentry_sample_rate),
|
||||
before_send=before_send,
|
||||
)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
|
|
@ -937,6 +937,61 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/ServiceTask"
|
||||
|
||||
/authentications:
|
||||
get:
|
||||
tags:
|
||||
- Authentications
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.authentication_list
|
||||
summary: Gets all available authentications from connector proxy
|
||||
responses:
|
||||
"200":
|
||||
description: All authentications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ServiceTask"
|
||||
|
||||
/authentication_callback/{service}/{auth_method}:
|
||||
parameters:
|
||||
- name: service
|
||||
in: path
|
||||
required: true
|
||||
description: The name of the service
|
||||
schema:
|
||||
type: string
|
||||
- name: auth_method
|
||||
in: path
|
||||
required: true
|
||||
description: The method
|
||||
schema:
|
||||
type: string
|
||||
- name: response
|
||||
in: query
|
||||
required: true
|
||||
description: The response
|
||||
schema:
|
||||
type: string
|
||||
- name: token
|
||||
in: query
|
||||
required: true
|
||||
description: The response
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
# disable security so we can get the token from query params instead
|
||||
security: []
|
||||
tags:
|
||||
- Authentications
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.authentication_callback
|
||||
summary: Callback to backend
|
||||
responses:
|
||||
"200":
|
||||
description: All authentications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ServiceTask"
|
||||
|
||||
/tasks/{process_instance_id}/{task_id}:
|
||||
parameters:
|
||||
- name: task_id
|
||||
|
@ -1443,10 +1498,6 @@ components:
|
|||
category_id:
|
||||
type: string
|
||||
nullable: true
|
||||
standalone:
|
||||
type: boolean
|
||||
example: false
|
||||
default: false
|
||||
workflow_spec_category:
|
||||
$ref: "#/components/schemas/ProcessModelCategory"
|
||||
is_status:
|
||||
|
|
|
@ -22,6 +22,10 @@ SPIFFWORKFLOW_FRONTEND_URL = environ.get(
|
|||
SPIFFWORKFLOW_BACKEND_URL = environ.get(
|
||||
"SPIFFWORKFLOW_BACKEND_URL", default="http://localhost:7000"
|
||||
)
|
||||
# service task connector proxy
|
||||
CONNECTOR_PROXY_URL = environ.get(
|
||||
"CONNECTOR_PROXY_URL", default="http://localhost:7004"
|
||||
)
|
||||
|
||||
GIT_COMMIT_ON_SAVE = environ.get("GIT_COMMIT_ON_SAVE", default="false") == "true"
|
||||
|
||||
|
@ -37,11 +41,6 @@ SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = (
|
|||
environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="false") == "true"
|
||||
)
|
||||
|
||||
# service task connector proxy
|
||||
CONNECTOR_PROXY_URL = environ.get(
|
||||
"CONNECTOR_PROXY_URL", default="http://localhost:7004"
|
||||
)
|
||||
|
||||
SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get(
|
||||
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
groups:
|
||||
admin:
|
||||
users: [jakub, kb, alex, dan, mike, jason, amir, jarrad, elizabeth, jon]
|
||||
users:
|
||||
[jakub, kb, alex, dan, mike, jason, amir, jarrad, elizabeth, jon, natalia]
|
||||
|
||||
finance:
|
||||
users: [harmeet, sasha]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
groups:
|
||||
admin:
|
||||
users: [jakub, kb, alex, dan, mike, jason, amir, jarrad, elizabeth, jon]
|
||||
users:
|
||||
[jakub, kb, alex, dan, mike, jason, amir, jarrad, elizabeth, jon, natalia]
|
||||
|
||||
finance:
|
||||
users: [harmeet, sasha]
|
||||
|
|
|
@ -46,7 +46,7 @@ class ActiveTaskModel(SpiffworkflowBaseDBModel):
|
|||
task_type = db.Column(db.String(50))
|
||||
task_status = db.Column(db.String(50))
|
||||
process_model_display_name = db.Column(db.String(255))
|
||||
task_data: str = db.Column(db.Text)
|
||||
task_data: str = db.Column(db.Text(4294000000))
|
||||
|
||||
@classmethod
|
||||
def to_task(cls, task: ActiveTaskModel) -> Task:
|
||||
|
|
|
@ -31,12 +31,8 @@ class ProcessModelInfo:
|
|||
description: str
|
||||
process_group_id: str = ""
|
||||
process_group: Any | None = None
|
||||
is_master_spec: bool | None = False
|
||||
standalone: bool | None = False
|
||||
library: bool | None = False
|
||||
primary_file_name: str | None = None
|
||||
primary_process_id: str | None = None
|
||||
libraries: list[str] = field(default_factory=list)
|
||||
display_order: int | None = 0
|
||||
is_review: bool = False
|
||||
files: list[File] | None = field(default_factory=list[File])
|
||||
|
@ -67,15 +63,11 @@ class ProcessModelInfoSchema(Schema):
|
|||
id = marshmallow.fields.String(required=True)
|
||||
display_name = marshmallow.fields.String(required=True)
|
||||
description = marshmallow.fields.String()
|
||||
is_master_spec = marshmallow.fields.Boolean(required=True)
|
||||
standalone = marshmallow.fields.Boolean(required=True)
|
||||
library = marshmallow.fields.Boolean(required=True)
|
||||
display_order = marshmallow.fields.Integer(allow_none=True)
|
||||
primary_file_name = marshmallow.fields.String(allow_none=True)
|
||||
primary_process_id = marshmallow.fields.String(allow_none=True)
|
||||
is_review = marshmallow.fields.Boolean(allow_none=True)
|
||||
process_group_id = marshmallow.fields.String(allow_none=True)
|
||||
libraries = marshmallow.fields.List(marshmallow.fields.String(), allow_none=True)
|
||||
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
|
||||
fault_or_suspend_on_exception = marshmallow.fields.String()
|
||||
exception_notification_addresses = marshmallow.fields.List(
|
||||
|
|
|
@ -16,8 +16,10 @@ class SecretModel(SpiffworkflowBaseDBModel):
|
|||
__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)
|
||||
value: str = db.Column(db.Text(), nullable=False)
|
||||
creator_user_id: int = db.Column(ForeignKey(UserModel.id), nullable=False)
|
||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||
created_at_in_seconds: int = db.Column(db.Integer)
|
||||
|
||||
|
||||
class SecretModelSchema(Schema):
|
||||
|
|
|
@ -7,7 +7,7 @@ from flask import redirect
|
|||
from flask import render_template
|
||||
from flask import request
|
||||
from flask import url_for
|
||||
from werkzeug.wrappers.response import Response
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
|
|
|
@ -13,11 +13,13 @@ from typing import Union
|
|||
import connexion # type: ignore
|
||||
import flask.wrappers
|
||||
import jinja2
|
||||
import werkzeug
|
||||
from flask import Blueprint
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask.wrappers import Response
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
|
@ -52,6 +54,7 @@ from spiffworkflow_backend.models.secret_model import SecretModel
|
|||
from spiffworkflow_backend.models.secret_model import SecretModelSchema
|
||||
from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.routes.user import verify_token
|
||||
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
|
||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||
from spiffworkflow_backend.services.git_service import GitService
|
||||
|
@ -774,6 +777,33 @@ def service_tasks_show() -> flask.wrappers.Response:
|
|||
)
|
||||
|
||||
|
||||
def authentication_list() -> flask.wrappers.Response:
|
||||
"""Authentication_list."""
|
||||
available_authentications = ServiceTaskService.authentication_list()
|
||||
response_json = {
|
||||
"results": available_authentications,
|
||||
"connector_proxy_base_url": current_app.config["CONNECTOR_PROXY_URL"],
|
||||
"redirect_url": f"{current_app.config['SPIFFWORKFLOW_BACKEND_URL']}/v1.0/authentication_callback",
|
||||
}
|
||||
|
||||
return Response(json.dumps(response_json), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
def authentication_callback(
|
||||
service: str,
|
||||
auth_method: str,
|
||||
) -> werkzeug.wrappers.Response:
|
||||
"""Authentication_callback."""
|
||||
verify_token(request.args.get("token"))
|
||||
response = request.args["response"]
|
||||
SecretService().update_secret(
|
||||
f"{service}/{auth_method}", response, g.user.id, create_if_not_exists=True
|
||||
)
|
||||
return redirect(
|
||||
f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/authentications"
|
||||
)
|
||||
|
||||
|
||||
def process_instance_report_show(
|
||||
process_group_id: str,
|
||||
process_model_id: str,
|
||||
|
|
|
@ -11,7 +11,7 @@ from flask import current_app
|
|||
from flask import g
|
||||
from flask import redirect
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
from werkzeug.wrappers.response import Response
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.authentication_service import (
|
||||
|
|
|
@ -11,8 +11,7 @@ from flask import current_app
|
|||
from flask import redirect
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
from flask_bpmn.models.db import db
|
||||
from werkzeug.wrappers.response import Response
|
||||
|
||||
from werkzeug.wrappers import Response
|
||||
from spiffworkflow_backend.models.refresh_token import RefreshTokenModel
|
||||
|
||||
|
||||
|
|
|
@ -20,11 +20,6 @@ class FileSystemService:
|
|||
""" Simple Service meant for extension that provides some useful
|
||||
methods for dealing with the File system.
|
||||
"""
|
||||
LIBRARY_SPECS = "Library Specs"
|
||||
STAND_ALONE_SPECS = "Stand Alone"
|
||||
MASTER_SPECIFICATION = "Master Specification"
|
||||
REFERENCE_FILES = "Reference Files"
|
||||
SPECIAL_FOLDERS = [LIBRARY_SPECS, MASTER_SPECIFICATION, REFERENCE_FILES]
|
||||
CAT_JSON_FILE = "process_group.json"
|
||||
WF_JSON_FILE = "workflow.json"
|
||||
|
||||
|
@ -41,13 +36,6 @@ class FileSystemService:
|
|||
"""Category_path."""
|
||||
return os.path.join(FileSystemService.root_path(), name)
|
||||
|
||||
@staticmethod
|
||||
def library_path(name: str) -> str:
|
||||
"""Library_path."""
|
||||
return os.path.join(
|
||||
FileSystemService.root_path(), FileSystemService.LIBRARY_SPECS, name
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def full_path_from_relative_path(relative_path: str) -> str:
|
||||
"""Full_path_from_relative_path."""
|
||||
|
@ -66,32 +54,13 @@ class FileSystemService:
|
|||
@staticmethod
|
||||
def process_group_path_for_spec(spec: ProcessModelInfo) -> str:
|
||||
"""Category_path_for_spec."""
|
||||
if spec.is_master_spec:
|
||||
return os.path.join(FileSystemService.root_path())
|
||||
elif spec.library:
|
||||
process_group_path = FileSystemService.process_group_path(
|
||||
FileSystemService.LIBRARY_SPECS
|
||||
)
|
||||
elif spec.standalone:
|
||||
process_group_path = FileSystemService.process_group_path(
|
||||
FileSystemService.STAND_ALONE_SPECS
|
||||
)
|
||||
else:
|
||||
process_group_path = FileSystemService.process_group_path(
|
||||
spec.process_group_id
|
||||
)
|
||||
return process_group_path
|
||||
return FileSystemService.process_group_path(spec.process_group_id)
|
||||
|
||||
@staticmethod
|
||||
def workflow_path(spec: ProcessModelInfo) -> str:
|
||||
"""Workflow_path."""
|
||||
if spec.is_master_spec:
|
||||
return os.path.join(
|
||||
FileSystemService.root_path(), FileSystemService.MASTER_SPECIFICATION
|
||||
)
|
||||
else:
|
||||
process_group_path = FileSystemService.process_group_path_for_spec(spec)
|
||||
return os.path.join(process_group_path, spec.id)
|
||||
process_group_path = FileSystemService.process_group_path_for_spec(spec)
|
||||
return os.path.join(process_group_path, spec.id)
|
||||
|
||||
@staticmethod
|
||||
def full_path_to_process_model_file(spec: ProcessModelInfo, file_name: str) -> str:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Process_instance_processor."""
|
||||
import _strptime # type: ignore
|
||||
import decimal
|
||||
import json
|
||||
import logging
|
||||
|
@ -95,6 +96,7 @@ DEFAULT_GLOBALS.update(
|
|||
"datetime": datetime,
|
||||
"time": time,
|
||||
"decimal": decimal,
|
||||
"_strptime": _strptime,
|
||||
}
|
||||
)
|
||||
# This will overwrite the standard builtins
|
||||
|
@ -343,9 +345,7 @@ class ProcessInstanceProcessor:
|
|||
f"The given process model was not found: {process_group_identifier}/{process_model_identifier}.",
|
||||
)
|
||||
)
|
||||
spec_files = SpecFileService.get_files(
|
||||
process_model_info, include_libraries=True
|
||||
)
|
||||
spec_files = SpecFileService.get_files(process_model_info)
|
||||
return cls.get_spec(spec_files, process_model_info)
|
||||
|
||||
@classmethod
|
||||
|
@ -895,6 +895,10 @@ class ProcessInstanceProcessor:
|
|||
except WorkflowTaskExecException as we:
|
||||
raise ApiError.from_workflow_exception("task_error", str(we), we) from we
|
||||
|
||||
finally:
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
def cancel_notify(self) -> None:
|
||||
"""Cancel_notify."""
|
||||
self.__cancel_notify(self.bpmn_process_instance)
|
||||
|
|
|
@ -217,9 +217,6 @@ class ProcessInstanceService:
|
|||
message="A process instance must have a user_id.",
|
||||
task=spiff_task,
|
||||
)
|
||||
# # Standalone workflow - we only care about the current user
|
||||
# elif processor.workflow_model.study_id is None and processor.workflow_model.user_id is not None:
|
||||
# return [processor.workflow_model.user_id]
|
||||
|
||||
# Workflow associated with a study - get all the users
|
||||
else:
|
||||
|
|
|
@ -63,12 +63,6 @@ class ProcessModelService(FileSystemService):
|
|||
def save_process_model(self, process_model: ProcessModelInfo) -> None:
|
||||
"""Save_process_model."""
|
||||
spec_path = self.workflow_path(process_model)
|
||||
if (
|
||||
process_model.is_master_spec
|
||||
or process_model.library
|
||||
or process_model.standalone
|
||||
):
|
||||
process_model.process_group_id = ""
|
||||
os.makedirs(spec_path, exist_ok=True)
|
||||
json_path = os.path.join(spec_path, self.WF_JSON_FILE)
|
||||
with open(json_path, "w") as wf_json:
|
||||
|
@ -90,20 +84,6 @@ class ProcessModelService(FileSystemService):
|
|||
path = self.workflow_path(process_model)
|
||||
shutil.rmtree(path)
|
||||
|
||||
@property
|
||||
def master_spec(self) -> Optional[ProcessModelInfo]:
|
||||
"""Master_spec."""
|
||||
return self.get_master_spec()
|
||||
|
||||
def get_master_spec(self) -> Optional[ProcessModelInfo]:
|
||||
"""Get_master_spec."""
|
||||
path = os.path.join(
|
||||
FileSystemService.root_path(), FileSystemService.MASTER_SPECIFICATION
|
||||
)
|
||||
if os.path.exists(path):
|
||||
return self.__scan_spec(path, FileSystemService.MASTER_SPECIFICATION)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_process_model_from_relative_path(
|
||||
cls, relative_path: str
|
||||
|
@ -121,9 +101,6 @@ class ProcessModelService(FileSystemService):
|
|||
if not os.path.exists(FileSystemService.root_path()):
|
||||
raise ProcessEntityNotFoundError("process_model_not_found")
|
||||
|
||||
master_spec = self.get_master_spec()
|
||||
if master_spec and master_spec.id == process_model_id:
|
||||
return master_spec
|
||||
if group_id is not None:
|
||||
process_group = self.get_process_group(group_id)
|
||||
if process_group is not None:
|
||||
|
@ -168,20 +145,6 @@ class ProcessModelService(FileSystemService):
|
|||
process_groups.sort()
|
||||
return process_groups
|
||||
|
||||
def get_libraries(self) -> List[ProcessModelInfo]:
|
||||
"""Get_libraries."""
|
||||
process_group = self.get_process_group(self.LIBRARY_SPECS)
|
||||
if not process_group:
|
||||
return []
|
||||
return process_group.process_models
|
||||
|
||||
def get_standalones(self) -> List[ProcessModelInfo]:
|
||||
"""Get_standalones."""
|
||||
process_group = self.get_process_group(self.STAND_ALONE_SPECS)
|
||||
if not process_group:
|
||||
return []
|
||||
return process_group.process_models
|
||||
|
||||
def get_process_group(self, process_group_id: str) -> ProcessGroup:
|
||||
"""Look for a given process_group, and return it."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
|
@ -240,14 +203,6 @@ class ProcessModelService(FileSystemService):
|
|||
process_groups = []
|
||||
for item in directory_items:
|
||||
if item.is_dir() and not item.name[0] == ".":
|
||||
# if item.name == self.REFERENCE_FILES:
|
||||
# continue
|
||||
# elif item.name == self.MASTER_SPECIFICATION:
|
||||
# continue
|
||||
# elif item.name == self.LIBRARY_SPECS:
|
||||
# continue
|
||||
# elif item.name == self.STAND_ALONE_SPECS:
|
||||
# continue
|
||||
process_groups.append(self.__scan_process_group(item))
|
||||
return process_groups
|
||||
|
||||
|
@ -292,7 +247,6 @@ class ProcessModelService(FileSystemService):
|
|||
) -> ProcessModelInfo:
|
||||
"""__scan_spec."""
|
||||
spec_path = os.path.join(path, self.WF_JSON_FILE)
|
||||
is_master = FileSystemService.MASTER_SPECIFICATION in spec_path
|
||||
|
||||
if os.path.exists(spec_path):
|
||||
with open(spec_path) as wf_json:
|
||||
|
@ -312,14 +266,10 @@ class ProcessModelService(FileSystemService):
|
|||
|
||||
spec = ProcessModelInfo(
|
||||
id=name,
|
||||
library=False,
|
||||
standalone=False,
|
||||
is_master_spec=is_master,
|
||||
display_name=name,
|
||||
description="",
|
||||
display_order=0,
|
||||
is_review=False,
|
||||
libraries=[],
|
||||
)
|
||||
with open(spec_path, "w") as wf_json:
|
||||
json.dump(self.WF_SCHEMA.dump(spec), wf_json, indent=4)
|
||||
|
|
|
@ -69,7 +69,8 @@ class SecretService:
|
|||
def update_secret(
|
||||
key: str,
|
||||
value: str,
|
||||
creator_user_id: Optional[int] = None,
|
||||
creator_user_id: int,
|
||||
create_if_not_exists: Optional[bool] = False,
|
||||
) -> None:
|
||||
"""Does this pass pre commit?"""
|
||||
secret_model = SecretModel.query.filter(SecretModel.key == key).first()
|
||||
|
@ -80,16 +81,18 @@ class SecretService:
|
|||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
raise ApiError(
|
||||
error_code="update_secret_error",
|
||||
message=f"There was an error updating the secret with key: {key}, and value: {value}",
|
||||
) from e
|
||||
db.session.rollback()
|
||||
raise e
|
||||
else:
|
||||
raise ApiError(
|
||||
error_code="update_secret_error",
|
||||
message=f"User: {creator_user_id} cannot update the secret with key : {key}",
|
||||
status_code=401,
|
||||
)
|
||||
elif create_if_not_exists:
|
||||
SecretService.add_secret(
|
||||
key=key, value=value, creator_user_id=creator_user_id
|
||||
)
|
||||
else:
|
||||
raise ApiError(
|
||||
error_code="update_secret_error",
|
||||
|
|
|
@ -9,6 +9,10 @@ from spiffworkflow_backend.services.file_system_service import FileSystemService
|
|||
from spiffworkflow_backend.services.secret_service import SecretService
|
||||
|
||||
|
||||
class ConnectorProxyError(Exception):
|
||||
"""ConnectorProxyError."""
|
||||
|
||||
|
||||
def connector_proxy_url() -> Any:
|
||||
"""Returns the connector proxy url."""
|
||||
return current_app.config["CONNECTOR_PROXY_URL"]
|
||||
|
@ -23,19 +27,20 @@ class ServiceTaskDelegate:
|
|||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
|
||||
secret_prefix = "secret:" # noqa: S105
|
||||
if value.startswith(secret_prefix):
|
||||
key = value.removeprefix(secret_prefix)
|
||||
secret = SecretService().get_secret(key)
|
||||
assert secret # noqa: S101
|
||||
return secret.value
|
||||
if isinstance(value, str):
|
||||
secret_prefix = "secret:" # noqa: S105
|
||||
if value.startswith(secret_prefix):
|
||||
key = value.removeprefix(secret_prefix)
|
||||
secret = SecretService().get_secret(key)
|
||||
assert secret # noqa: S101
|
||||
return secret.value
|
||||
|
||||
file_prefix = "file:"
|
||||
if value.startswith(file_prefix):
|
||||
file_name = value.removeprefix(file_prefix)
|
||||
full_path = FileSystemService.full_path_from_relative_path(file_name)
|
||||
with open(full_path) as f:
|
||||
return f.read()
|
||||
file_prefix = "file:"
|
||||
if value.startswith(file_prefix):
|
||||
file_name = value.removeprefix(file_prefix)
|
||||
full_path = FileSystemService.full_path_from_relative_path(file_name)
|
||||
with open(full_path) as f:
|
||||
return f.read()
|
||||
|
||||
return value
|
||||
|
||||
|
@ -48,7 +53,9 @@ class ServiceTaskDelegate:
|
|||
}
|
||||
params["spiff__task_data"] = json.dumps(task_data)
|
||||
|
||||
proxied_response = requests.get(f"{connector_proxy_url()}/v1/do/{name}", params)
|
||||
proxied_response = requests.post(
|
||||
f"{connector_proxy_url()}/v1/do/{name}", params
|
||||
)
|
||||
|
||||
if proxied_response.status_code != 200:
|
||||
print("got error from connector proxy")
|
||||
|
@ -73,3 +80,17 @@ class ServiceTaskService:
|
|||
except Exception as e:
|
||||
print(e)
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def authentication_list() -> Any:
|
||||
"""Returns a list of available authentications."""
|
||||
try:
|
||||
response = requests.get(f"{connector_proxy_url()}/v1/auths")
|
||||
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
|
||||
parsed_response = json.loads(response.text)
|
||||
return parsed_response
|
||||
except Exception as exception:
|
||||
raise ConnectorProxyError(exception.__class__.__name__) from exception
|
||||
|
|
|
@ -39,22 +39,15 @@ class SpecFileService(FileSystemService):
|
|||
def get_files(
|
||||
process_model_info: ProcessModelInfo,
|
||||
file_name: Optional[str] = None,
|
||||
include_libraries: bool = False,
|
||||
extension_filter: str = "",
|
||||
) -> List[File]:
|
||||
"""Return all files associated with a workflow specification."""
|
||||
path = SpecFileService.workflow_path(process_model_info)
|
||||
files = SpecFileService._get_files(path, file_name)
|
||||
if include_libraries:
|
||||
for lib_name in process_model_info.libraries:
|
||||
lib_path = SpecFileService.library_path(lib_name)
|
||||
files.extend(SpecFileService._get_files(lib_path, file_name))
|
||||
|
||||
if extension_filter != "":
|
||||
files = list(
|
||||
filter(lambda file: file.name.endswith(extension_filter), files)
|
||||
)
|
||||
|
||||
return files
|
||||
|
||||
@staticmethod
|
||||
|
@ -96,13 +89,6 @@ class SpecFileService(FileSystemService):
|
|||
def get_data(process_model_info: ProcessModelInfo, file_name: str) -> bytes:
|
||||
"""Get_data."""
|
||||
file_path = SpecFileService.file_path(process_model_info, file_name)
|
||||
if not os.path.exists(file_path):
|
||||
# If the file isn't here, it may be in a library
|
||||
for lib in process_model_info.libraries:
|
||||
file_path = SpecFileService.library_path(lib)
|
||||
file_path = os.path.join(file_path, file_name)
|
||||
if os.path.exists(file_path):
|
||||
break
|
||||
if not os.path.exists(file_path):
|
||||
raise ApiError(
|
||||
"unknown_file",
|
||||
|
|
|
@ -13,7 +13,7 @@ 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 werkzeug.test import TestResponse
|
||||
from werkzeug.test import TestResponse # type: ignore
|
||||
|
||||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_group import ProcessGroupSchema
|
||||
|
@ -115,11 +115,7 @@ class BaseTest:
|
|||
display_name=process_model_display_name,
|
||||
description=process_model_description,
|
||||
process_group_id=process_group.id,
|
||||
standalone=False,
|
||||
is_review=False,
|
||||
is_master_spec=False,
|
||||
libraries=[],
|
||||
library=False,
|
||||
primary_process_id=primary_process_id,
|
||||
primary_file_name=primary_file_name,
|
||||
fault_or_suspend_on_exception=fault_or_suspend_on_exception,
|
||||
|
|
|
@ -18,12 +18,9 @@ class ExampleDataLoader:
|
|||
process_model_id: str,
|
||||
display_name: str = "",
|
||||
description: str = "",
|
||||
master_spec: bool = False,
|
||||
process_group_id: str = "",
|
||||
display_order: int = 0,
|
||||
from_tests: bool = False,
|
||||
standalone: bool = False,
|
||||
library: bool = False,
|
||||
bpmn_file_name: Optional[str] = None,
|
||||
process_model_source_directory: Optional[str] = None,
|
||||
) -> ProcessModelInfo:
|
||||
|
@ -38,11 +35,7 @@ class ExampleDataLoader:
|
|||
description=description,
|
||||
process_group_id=process_group_id,
|
||||
display_order=display_order,
|
||||
is_master_spec=master_spec,
|
||||
standalone=standalone,
|
||||
library=library,
|
||||
is_review=False,
|
||||
libraries=[],
|
||||
)
|
||||
workflow_spec_service = ProcessModelService()
|
||||
workflow_spec_service.add_spec(spec)
|
||||
|
|
|
@ -37,9 +37,7 @@ def assure_process_group_exists(process_group_id: Optional[str] = None) -> Proce
|
|||
|
||||
def load_test_spec(
|
||||
process_model_id: str,
|
||||
master_spec: bool = False,
|
||||
process_group_id: Optional[str] = None,
|
||||
library: bool = False,
|
||||
bpmn_file_name: Optional[str] = None,
|
||||
process_model_source_directory: Optional[str] = None,
|
||||
) -> ProcessModelInfo:
|
||||
|
@ -48,9 +46,8 @@ def load_test_spec(
|
|||
process_model_service = ProcessModelService()
|
||||
if process_group_id is None:
|
||||
process_group_id = "test_process_group_id"
|
||||
if not master_spec and not library:
|
||||
process_group = assure_process_group_exists(process_group_id)
|
||||
process_group_id = process_group.id
|
||||
process_group = assure_process_group_exists(process_group_id)
|
||||
process_group_id = process_group.id
|
||||
|
||||
try:
|
||||
return process_model_service.get_process_model(
|
||||
|
@ -59,11 +56,9 @@ def load_test_spec(
|
|||
except ProcessEntityNotFoundError:
|
||||
spec = ExampleDataLoader().create_spec(
|
||||
process_model_id=process_model_id,
|
||||
master_spec=master_spec,
|
||||
from_tests=True,
|
||||
display_name=process_model_id,
|
||||
process_group_id=process_group_id,
|
||||
library=library,
|
||||
bpmn_file_name=bpmn_file_name,
|
||||
process_model_source_directory=process_model_source_directory,
|
||||
)
|
||||
|
|
|
@ -1560,6 +1560,16 @@ class TestProcessApi(BaseTest):
|
|||
assert response.json is not None
|
||||
assert len(response.json["results"]) == 2
|
||||
|
||||
# TODO: test the auth callback endpoint
|
||||
# def test_can_store_authentication_secret(
|
||||
# self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None
|
||||
# ) -> None:
|
||||
# """Test_can_store_authentication_secret."""
|
||||
# response = client.get(
|
||||
# "/v1.0/authentication_callback",
|
||||
# headers=self.logged_in_headers(user),
|
||||
# )
|
||||
|
||||
# def test_get_process_model(self):
|
||||
#
|
||||
# load_test_spec('random_fact')
|
||||
|
|
|
@ -7,7 +7,7 @@ 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 werkzeug.test import TestResponse # type: ignore
|
||||
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.secret_model import SecretModel
|
||||
|
|
|
@ -20,7 +20,6 @@ class TestProcessModel(BaseTest):
|
|||
id="model_one", display_name="Model One"
|
||||
)
|
||||
assert process_model_one.files == []
|
||||
assert process_model_one.libraries == []
|
||||
|
||||
def test_can_run_process_model_with_call_activities_when_in_same_process_model_directory(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
"""Test_various_bpmn_constructs."""
|
||||
from flask.app import Flask
|
||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||
|
||||
from spiffworkflow_backend.services.secret_service import SecretService
|
||||
from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate
|
||||
|
||||
|
||||
class TestServiceTaskDelegate(BaseTest):
|
||||
"""TestServiceTaskDelegate."""
|
||||
|
||||
def test_normalize_value_without_secret(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
"""Test_normalize_value_without_secret."""
|
||||
result = ServiceTaskDelegate.normalize_value("hey")
|
||||
assert result == "hey"
|
||||
|
||||
def test_normalize_value_with_int(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
"""Test_normalize_value_with_int."""
|
||||
result = ServiceTaskDelegate.normalize_value(1)
|
||||
assert result == 1
|
||||
|
||||
def test_normalize_value_with_secret(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
"""Test_normalize_value_with_secret."""
|
||||
user = self.find_or_create_user("test_user")
|
||||
SecretService().add_secret("hot_secret", "my_secret_value", user.id)
|
||||
result = ServiceTaskDelegate.normalize_value("secret:hot_secret")
|
||||
assert result == "my_secret_value"
|
Loading…
Reference in New Issue