Merge branch 'main' into save_step_data

This commit is contained in:
Jon Herron 2023-02-08 10:14:07 -05:00
commit fb1e2abb0d
42 changed files with 955 additions and 498 deletions

View File

@ -32,7 +32,7 @@ per-file-ignores =
# the exclude=./migrations option doesn't seem to work with pre-commit
# migrations are autogenerated from "flask db migration" so ignore them
spiffworkflow-backend/migrations/*:D
spiffworkflow-backend/src/spiffworkflow_backend/config/testing.py:S105
spiffworkflow-backend/src/spiffworkflow_backend/config/unit_testing.py:S105
spiffworkflow-backend/src/spiffworkflow_backend/load_database_models.py:F401
# this file overwrites methods from the logging library so we can't change them

View File

@ -14,7 +14,6 @@ services:
spiffworkflow-backend:
container_name: spiffworkflow-backend
build: ./spiffworkflow-backend/.
image: ghcr.io/sartography/spiffworkflow-backend:latest
depends_on:
spiffworkflow-db:

View File

@ -32,7 +32,7 @@ per-file-ignores =
# the exclude=./migrations option doesn't seem to work with pre-commit
# migrations are autogenerated from "flask db migration" so ignore them
migrations/*:D
src/spiffworkflow_backend/config/testing.py:S105
src/spiffworkflow_backend/config/unit_testing.py:S105
src/spiffworkflow_backend/load_database_models.py:F401
# this file overwrites methods from the logging library so we can't change them

View File

@ -25,6 +25,14 @@ if [[ "${SPIFFWORKFLOW_BACKEND_UPGRADE_DB:-}" == "true" ]]; then
poetry run flask db upgrade
fi
if [[ -z "${GUNICORN_LOG_LEVEL:-}" ]]; then
GUNICORN_LOG_LEVEL=debug
fi
if [[ -z "${GUNICORN_TIMEOUT_SECONDS:-}" ]]; then
GUNICORN_TIMEOUT_SECONDS=90
fi
port="${SPIFFWORKFLOW_BACKEND_PORT:-}"
if [[ -z "$port" ]]; then
port=7000
@ -53,4 +61,11 @@ git config --global --add safe.directory "${BPMN_SPEC_ABSOLUTE_DIR}"
export IS_GUNICORN="true"
# THIS MUST BE THE LAST COMMAND!
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
exec poetry run gunicorn ${additional_args} \
--bind "0.0.0.0:$port" \
--workers="$workers" \
--limit-request-line 8192 \
--timeout "$GUNICORN_TIMEOUT_SECONDS" \
--capture-output \
--access-logfile '-' \
--log-level "$GUNICORN_LOG_LEVEL" wsgi:app

View File

@ -8,7 +8,7 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
def main(process_instance_id: str):
"""Main."""
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "development"
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "local_development"
if os.environ.get("BPMN_SPEC_ABSOLUTE_DIR") is None:
os.environ["BPMN_SPEC_ABSOLUTE_DIR"] = "hey"
flask_env_key = "FLASK_SESSION_SECRET_KEY"

View File

@ -22,8 +22,8 @@ set -o errtrace -o errexit -o nounset -o pipefail
# KEYCLOAK_BASE_URL=http://localhost:7002
KEYCLOAK_BASE_URL=https://keycloak.dev.spiffworkflow.org
BACKEND_BASE_URL=http://localhost:7000
# BACKEND_BASE_URL=https://api.dev.spiffworkflow.org
# BACKEND_BASE_URL=http://localhost:7000
BACKEND_BASE_URL=https://api.dev.spiffworkflow.org
REALM_NAME=spiffworkflow
USERNAME=${1-fin}
PASSWORD=${2-fin}
@ -61,6 +61,7 @@ result=$(curl -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
-d "client_id=$BACKEND_CLIENT_ID" \
)
backend_token=$(jq -r '.access_token' <<< "$result")
echo "testing hitting backend with token: $backend_token"
curl --fail -v "${BACKEND_BASE_URL}/v1.0/process-groups?per_page=1" -H "Authorization: Bearer $backend_token"

View File

@ -35,8 +35,8 @@ if [[ "${1:-}" == "clean" ]]; then
fi
rm -f ./src/instance/*.sqlite3
mysql -uroot -e "DROP DATABASE IF EXISTS spiffworkflow_backend_development"
mysql -uroot -e "DROP DATABASE IF EXISTS spiffworkflow_backend_testing"
mysql -uroot -e "DROP DATABASE IF EXISTS spiffworkflow_backend_local_development"
mysql -uroot -e "DROP DATABASE IF EXISTS spiffworkflow_backend_unit_testing"
# TODO: check to see if the db already exists and we can connect to it. also actually clean it up.
# start postgres in background with one db
@ -45,23 +45,23 @@ if [[ "${1:-}" == "clean" ]]; then
docker run --name postgres-spiff -p 5432:5432 -e POSTGRES_PASSWORD=spiffworkflow_backend -e POSTGRES_USER=spiffworkflow_backend -e POSTGRES_DB=spiffworkflow_backend_testing -d postgres
sleep 4 # classy
fi
if ! docker exec -it postgres-spiff psql -U spiffworkflow_backend spiffworkflow_backend_development -c "select 1"; then
if ! docker exec -it postgres-spiff psql -U spiffworkflow_backend spiffworkflow_backend_local_development -c "select 1"; then
# create other db. spiffworkflow_backend_testing came with the docker run.
docker exec -it postgres-spiff psql -U spiffworkflow_backend spiffworkflow_backend_testing -c "create database spiffworkflow_backend_development;"
docker exec -it postgres-spiff psql -U spiffworkflow_backend spiffworkflow_backend_testing -c "create database spiffworkflow_backend_local_development;"
fi
fi
fi
tasks="$tasks upgrade"
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS spiffworkflow_backend_development"
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS spiffworkflow_backend_testing"
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS spiffworkflow_backend_local_development"
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS spiffworkflow_backend_unit_testing"
for task in $tasks; do
SPIFFWORKFLOW_BACKEND_ENV=development FLASK_APP=src/spiffworkflow_backend poetry run flask db "$task"
SPIFFWORKFLOW_BACKEND_ENV=local_development FLASK_APP=src/spiffworkflow_backend poetry run flask db "$task"
done
SPIFFWORKFLOW_BACKEND_ENV=testing FLASK_APP=src/spiffworkflow_backend poetry run flask db upgrade
if [[ -n "${SPIFFWORKFLOW_BACKEND_ENV:-}" ]] && ! grep -Eq '^(development|testing)$' <<< "$SPIFFWORKFLOW_BACKEND_ENV"; then
SPIFFWORKFLOW_BACKEND_ENV=unit_testing FLASK_APP=src/spiffworkflow_backend poetry run flask db upgrade
if [[ -n "${SPIFFWORKFLOW_BACKEND_ENV:-}" ]] && ! grep -Eq '^(local_development|unit_testing)$' <<< "$SPIFFWORKFLOW_BACKEND_ENV"; then
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS spiffworkflow_backend_$SPIFFWORKFLOW_BACKEND_ENV"
FLASK_APP=src/spiffworkflow_backend poetry run flask db upgrade
fi

View File

@ -14,7 +14,7 @@ if [[ "$arg" == "acceptance" ]]; then
fi
if [[ -z "${SPIFFWORKFLOW_BACKEND_ENV:-}" ]]; then
export SPIFFWORKFLOW_BACKEND_ENV=development
export SPIFFWORKFLOW_BACKEND_ENV=local_development
fi
BPMN_SPEC_ABSOLUTE_DIR=$(./bin/find_sample_process_models)

View File

@ -10,7 +10,7 @@ from spiffworkflow_backend.services.secret_service import SecretService
def main(env_file: str):
"""Main."""
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "development"
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "local_development"
if os.environ.get("BPMN_SPEC_ABSOLUTE_DIR") is None:
os.environ["BPMN_SPEC_ABSOLUTE_DIR"] = "hey"
flask_env_key = "FLASK_SESSION_SECRET_KEY"

View File

@ -37,7 +37,7 @@ from spiffworkflow_backend import create_app # noqa: E402
@pytest.fixture(scope="session")
def app() -> Flask:
"""App."""
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "testing"
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "unit_testing"
os.environ["FLASK_SESSION_SECRET_KEY"] = "super_secret_key"
app = create_app()

View File

@ -51,7 +51,7 @@ services:
context: .
environment:
- APPLICATION_ROOT=/
- SPIFFWORKFLOW_BACKEND_ENV=${SPIFFWORKFLOW_BACKEND_ENV:-development}
- SPIFFWORKFLOW_BACKEND_ENV=${SPIFFWORKFLOW_BACKEND_ENV:-local_development}
- FLASK_DEBUG=0
- FLASK_SESSION_SECRET_KEY=${FLASK_SESSION_SECRET_KEY:-super_secret_key}
- OPEN_ID_SERVER_URL=${OPEN_ID_SERVER_URL:-http://localhost:7002/realms/spiffworkflow}

View File

@ -44,14 +44,66 @@ result=$(curl --fail -s -X POST "$KEYCLOAK_URL" "$INSECURE" \
)
backend_token=$(jq -r '.access_token' <<< "$result")
while read -r user_email; do
if [[ -n "$user_email" ]]; then
username=$(awk -F '@' '{print $1}' <<<"$user_email")
credentials='{"type":"password","value":"'"${username}"'","temporary":false}'
function add_user() {
local user_email=$1
local username=$2
local user_attribute_one=$3
curl --fail --location --request POST "http://localhost:7002/admin/realms/${keycloak_realm}/users" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $backend_token" \
--data-raw '{"email":"'"${user_email}"'", "enabled":"true", "username":"'"${username}"'", "credentials":['"${credentials}"']}'
local credentials='{"type":"password","value":"'"${username}"'","temporary":false}'
local data='{"email":"'"${user_email}"'", "enabled":"true", "username":"'"${username}"'", "credentials":['"${credentials}"']'
if [[ -n "$user_attribute_one" ]]; then
data=''${data}', "attributes": {"'${custom_attribute_one}'": [ "'$user_attribute_one'" ]}'
fi
data="${data}}"
local http_code
http_code=$(curl --silent -o /dev/null -w '%{http_code}' --location --request POST "http://localhost:7002/admin/realms/${keycloak_realm}/users" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $backend_token" \
--data-raw "$data")
echo "$http_code"
}
first_line_processed="false"
custom_attribute_one=''
while read -r input_line; do
if ! grep -qE '^#' <<<"$input_line" ; then
if [[ "$first_line_processed" == "false" ]]; then
email_header=$(awk -F ',' '{print $1}' <<<"$input_line")
if [[ "$email_header" != "email" ]]; then
>&2 echo "ERROR: the first column in the first row must be email."
exit 1
fi
custom_attribute_one=$(awk -F ',' '{print $2}' <<<"$input_line")
first_line_processed="true"
elif [[ -n "$input_line" ]]; then
user_email=$(awk -F ',' '{print $1}' <<<"$input_line")
username=$(awk -F '@' '{print $1}' <<<"$user_email")
user_attribute_one=$(awk -F ',' '{print $2}' <<<"$input_line")
http_code=$(add_user "$user_email" "$username" "$user_attribute_one")
if [[ "$http_code" == "409" ]]; then
user_info=$(curl --fail --silent --location --request GET "http://localhost:7002/admin/realms/${keycloak_realm}/users?username=${username}&exact=true" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $backend_token")
user_id=$(jq -r '.[0] | .id' <<<"$user_info")
if [[ -z "$user_id" ]]; then
>&2 echo "ERROR: Could not find user_id for user: ${user_email}"
exit 1
fi
curl --fail --location --silent --request DELETE "http://localhost:7002/admin/realms/${keycloak_realm}/users/${user_id}" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $backend_token"
http_code=$(add_user "$user_email" "$username" "$user_attribute_one")
if [[ "$http_code" != "201" ]]; then
>&2 echo "ERROR: Failed to recreate user: ${user_email} with http_code: ${http_code}"
exit 1
fi
fi
fi
fi
done <"$user_file_with_one_email_per_line"

View File

@ -1,6 +1,7 @@
email,spiffworkflow-employeeid
admin@spiffworkflow.org
alex@sartography.com
dan@sartography.com
alex@sartography.com,111
dan@sartography.com,115
daniel@sartography.com
elizabeth@sartography.com
j@sartography.com

View File

@ -1,50 +1,57 @@
email,spiffworkflow-employeeid
admin@spiffworkflow.org
amir@status.im
app.program.lead@status.im
core@status.im
core@status.im,113
dao.project.lead@status.im
desktop.program.lead@status.im
desktop.project.lead@status.im
fin1@status.im
fin@status.im
finance.lead@status.im
finance.sme@status.im
fin@status.im,118
finance.lead@status.im,1182
finance.lead@status.im,1289
finance_user1@status.im
harmeet@status.im
harmeet@status.im,109
infra.program-lead@status.im
infra.project-lead@status.im
infra.sme@status.im
infra.sme@status.im,1202
infra1.sme@status.im
infra2.sme@status.im
jakub@status.im
jarrad@status.im
lead1@status.im
lead@status.im
legal.lead@status.im
lead@status.im,1140
legal.lead@status.im,1243
legal.program-lead.sme@status.im
legal.program-lead@status.im
legal.project-lead.sme@status.im
legal.project-lead@status.im
legal.sme@status.im
legal.sme1@status.im,1345
legal.sme@status.im,1253
legal1.sme@status.im
manuchehr@status.im
manuchehr@status.im,110
peopleops.partner.program-lead@status.im,146
peopleops.partner.project-lead@status.im,147
peopleops.partner.sme@status.im,148
peopleops.partner1.sme@status.im,149
peopleops.partner@status.im
peopleops.partner@status.im,150
peopleops.talent.program-lead@status.im
peopleops.talent.project-lead@status.im
peopleops.talent.sme@status.im
peopleops.talent1.sme@status.im
peopleops.talent@status.im
peopleops.talent@status.im,141
ppg.ba.program-lead@status.im
ppg.ba.project-lead@status.im
ppg.ba.sme1@status.im
ppg.ba.sme@status.im
ppg.ba@status.im
program.lead@status.im
sasha@status.im
ppg.ba.sme1@status.im,1398
ppg.ba.sme@status.im,1387
ppg.ba@status.im,1276
program.lead@status.im,1211
sasha@status.im,112
security.program-lead.sme@status.im
security.program-lead@status.im
security.project-lead.sme@status.im
security.project-lead@status.im
security.sme@status.im
security.sme@status.im,1230
security1.sme@status.im
services.lead@status.im

View File

@ -0,0 +1,32 @@
"""empty message
Revision ID: ca9b79dde5cc
Revises: 2ec4222f0012
Create Date: 2023-02-03 21:06:56.396816
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ca9b79dde5cc'
down_revision = '2ec4222f0012'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('tenant_specific_field_1', sa.String(length=255), nullable=True))
op.add_column('user', sa.Column('tenant_specific_field_2', sa.String(length=255), nullable=True))
op.add_column('user', sa.Column('tenant_specific_field_3', sa.String(length=255), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'tenant_specific_field_3')
op.drop_column('user', 'tenant_specific_field_2')
op.drop_column('user', 'tenant_specific_field_1')
# ### end Alembic commands ###

View File

@ -42,7 +42,7 @@ def setup_database(session: Session) -> None:
flask_env_key = "FLASK_SESSION_SECRET_KEY"
session.env[flask_env_key] = "super_secret_key"
session.env["FLASK_APP"] = "src/spiffworkflow_backend"
session.env["SPIFFWORKFLOW_BACKEND_ENV"] = "testing"
session.env["SPIFFWORKFLOW_BACKEND_ENV"] = "unit_testing"
session.run("flask", "db", "upgrade")

View File

@ -1,5 +1,6 @@
"""__init__."""
import os
import sys
from typing import Any
import connexion # type: ignore
@ -141,7 +142,7 @@ def create_app() -> flask.app.Flask:
def get_hacked_up_app_for_script() -> flask.app.Flask:
"""Get_hacked_up_app_for_script."""
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "development"
os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "local_development"
flask_env_key = "FLASK_SESSION_SECRET_KEY"
os.environ[flask_env_key] = "whatevs"
if "BPMN_SPEC_ABSOLUTE_DIR" not in os.environ:
@ -203,6 +204,9 @@ def configure_sentry(app: flask.app.Flask) -> None:
if sentry_traces_sample_rate is None:
raise Exception("SENTRY_TRACES_SAMPLE_RATE is not set somehow")
# profiling doesn't work on windows, because of an issue like https://github.com/nvdv/vprof/issues/62
profiles_sample_rate = 0 if sys.platform.startswith("win") else 1
sentry_sdk.init(
dsn=app.config.get("SENTRY_DSN"),
integrations=[
@ -218,8 +222,6 @@ def configure_sentry(app: flask.app.Flask) -> None:
traces_sample_rate=float(sentry_traces_sample_rate),
traces_sampler=traces_sampler,
# The profiles_sample_rate setting is relative to the traces_sample_rate setting.
_experiments={
"profiles_sample_rate": 1,
},
_experiments={"profiles_sample_rate": profiles_sample_rate},
before_send=before_send,
)

View File

@ -51,6 +51,19 @@ def load_config_file(app: Flask, env_config_module: str) -> None:
) from exception
def _set_up_tenant_specific_fields_as_list_of_strings(app: Flask) -> None:
tenant_specific_fields = app.config.get("TENANT_SPECIFIC_FIELDS")
if tenant_specific_fields is None or tenant_specific_fields == "":
app.config["TENANT_SPECIFIC_FIELDS"] = []
else:
app.config["TENANT_SPECIFIC_FIELDS"] = tenant_specific_fields.split(",")
if len(app.config["TENANT_SPECIFIC_FIELDS"]) > 3:
raise ConfigurationError(
"TENANT_SPECIFIC_FIELDS can have a maximum of 3 fields"
)
def setup_config(app: Flask) -> None:
"""Setup_config."""
# ensure the instance folder exists
@ -108,3 +121,4 @@ def setup_config(app: Flask) -> None:
thread_local_data = threading.local()
app.config["THREAD_LOCAL_DATA"] = thread_local_data
_set_up_tenant_specific_fields_as_list_of_strings(app)

View File

@ -72,7 +72,7 @@ GIT_SSH_PRIVATE_KEY = environ.get("GIT_SSH_PRIVATE_KEY")
GIT_USERNAME = environ.get("GIT_USERNAME")
GIT_USER_EMAIL = environ.get("GIT_USER_EMAIL")
# Datbase Configuration
# Database Configuration
SPIFF_DATABASE_TYPE = environ.get(
"SPIFF_DATABASE_TYPE", default="mysql"
) # can also be sqlite, postgres
@ -88,3 +88,8 @@ SYSTEM_NOTIFICATION_PROCESS_MODEL_MESSAGE_ID = environ.get(
ALLOW_CONFISCATING_LOCK_AFTER_SECONDS = int(
environ.get("ALLOW_CONFISCATING_LOCK_AFTER_SECONDS", default="600")
)
# Tenant specific fields is a comma separated list of field names that we will convert to list of strings
# and store in the user table's tenant_specific_field_n columns. You can have up to three items in this
# comma-separated list.
TENANT_SPECIFIC_FIELDS = environ.get("TENANT_SPECIFIC_FIELDS")

View File

@ -2,7 +2,7 @@
from os import environ
SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get(
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME", default="development.yml"
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME", default="local_development.yml"
)
SPIFFWORKFLOW_BACKEND_LOG_LEVEL = environ.get(

View File

@ -0,0 +1,10 @@
"""Qa2."""
from os import environ
SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get(
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME", default="qa1.yml"
)
SPIFFWORKFLOW_FRONTEND_URL = "https://qa2.spiffworkflow.org"
OPEN_ID_SERVER_URL = "https://qa2.spiffworkflow.org/keycloak/realms/spiffworkflow"
SPIFFWORKFLOW_BACKEND_URL = "https://qa2.spiffworkflow.org/api"
CONNECTOR_PROXY_URL = "https://qa2.spiffworkflow.org/connector-proxy"

View File

@ -9,7 +9,7 @@ SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = (
)
SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get(
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME", default="testing.yml"
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME", default="unit_testing.yml"
)
SPIFFWORKFLOW_BACKEND_LOG_LEVEL = environ.get(

View File

@ -34,6 +34,9 @@ class UserModel(SpiffworkflowBaseDBModel):
service_id = db.Column(db.String(255), nullable=False, unique=False)
display_name = db.Column(db.String(255))
email = db.Column(db.String(255))
tenant_specific_field_1: str | None = db.Column(db.String(255))
tenant_specific_field_2: str | None = db.Column(db.String(255))
tenant_specific_field_3: str | None = db.Column(db.String(255))
updated_at_in_seconds: int = db.Column(db.Integer)
created_at_in_seconds: int = db.Column(db.Integer)

View File

@ -2,6 +2,7 @@
import json
import os
import uuid
from sys import exc_info
from typing import Any
from typing import Dict
from typing import Optional
@ -559,9 +560,21 @@ def _render_jinja_template(unprocessed_template: str, spiff_task: SpiffTask) ->
template_error.lineno - 1
]
wfe.add_note(
"Jinja2 template errors can happen when trying to displaying task data"
"Jinja2 template errors can happen when trying to display task data"
)
raise wfe from template_error
except Exception as error:
type, value, tb = exc_info()
wfe = WorkflowTaskException(str(error), task=spiff_task, exception=error)
while tb:
if tb.tb_frame.f_code.co_filename == "<template>":
wfe.line_number = tb.tb_lineno
wfe.error_line = unprocessed_template.split("\n")[tb.tb_lineno - 1]
tb = tb.tb_next
wfe.add_note(
"Jinja2 template errors can happen when trying to displaying task data"
)
raise wfe from error
def _get_spiff_task_from_process_instance(

View File

@ -1,6 +1,7 @@
"""Get_env."""
"""Get current user."""
from typing import Any
from flask import current_app
from flask import g
from spiffworkflow_backend.models.script_attributes_context import (
@ -10,8 +11,6 @@ from spiffworkflow_backend.scripts.script import Script
class GetCurrentUser(Script):
"""GetCurrentUser."""
@staticmethod
def requires_privileged_permissions() -> bool:
"""We have deemed this function safe to run without elevated permissions."""
@ -28,4 +27,7 @@ class GetCurrentUser(Script):
**kwargs: Any
) -> Any:
"""Run."""
return g.user.username
# dump the user using our json encoder and then load it back up as a dict
# to remove unwanted field types
user_as_json_string = current_app.json.dumps(g.user)
return current_app.json.loads(user_as_json_string)

View File

@ -485,38 +485,42 @@ class AuthorizationService:
.filter(UserModel.service_id == user_info["sub"])
.first()
)
email = display_name = username = ""
user_attributes = {}
if "email" in user_info:
username = user_info["email"]
email = user_info["email"]
user_attributes["username"] = user_info["email"]
user_attributes["email"] = user_info["email"]
else: # we fall back to the sub, which may be very ugly.
username = user_info["sub"] + "@" + user_info["iss"]
fallback_username = user_info["sub"] + "@" + user_info["iss"]
user_attributes["username"] = fallback_username
if "preferred_username" in user_info:
display_name = user_info["preferred_username"]
user_attributes["display_name"] = user_info["preferred_username"]
elif "nickname" in user_info:
display_name = user_info["nickname"]
user_attributes["display_name"] = user_info["nickname"]
elif "name" in user_info:
display_name = user_info["name"]
user_attributes["display_name"] = user_info["name"]
user_attributes["service"] = user_info["iss"]
user_attributes["service_id"] = user_info["sub"]
for field_index, tenant_specific_field in enumerate(
current_app.config["TENANT_SPECIFIC_FIELDS"]
):
if tenant_specific_field in user_info:
field_number = field_index + 1
user_attributes[f"tenant_specific_field_{field_number}"] = user_info[
tenant_specific_field
]
if user_model is None:
current_app.logger.debug("create_user in login_return")
is_new_user = True
user_model = UserService().create_user(
username=username,
service=user_info["iss"],
service_id=user_info["sub"],
email=email,
display_name=display_name,
)
user_model = UserService().create_user(**user_attributes)
else:
# Update with the latest information
user_model.username = username
user_model.email = email
user_model.display_name = display_name
user_model.service = user_info["iss"]
user_model.service_id = user_info["sub"]
for key, value in user_attributes.items():
setattr(user_model, key, value)
# this may eventually get too slow.
# when it does, be careful about backgrounding, because

View File

@ -136,7 +136,7 @@ def setup_logger(app: Flask) -> None:
# the json formatter is nice for real environments but makes
# debugging locally a little more difficult
if app.config["ENV_IDENTIFIER"] != "development":
if app.config["ENV_IDENTIFIER"] != "local_development":
json_formatter = JsonFormatter(
{
"level": "levelname",

View File

@ -1311,7 +1311,7 @@ class ProcessInstanceProcessor:
db.session.commit()
if result.rowcount < 1:
raise ProcessInstanceIsAlreadyLockedError(
f"Cannot lock process instance {self.process_instance_model.id}."
f"Cannot lock process instance {self.process_instance_model.id}. "
"It has already been locked."
)

View File

@ -29,6 +29,9 @@ class UserService:
service_id: str,
email: Optional[str] = "",
display_name: Optional[str] = "",
tenant_specific_field_1: Optional[str] = None,
tenant_specific_field_2: Optional[str] = None,
tenant_specific_field_3: Optional[str] = None,
) -> UserModel:
"""Create_user."""
user_model: Optional[UserModel] = (
@ -46,6 +49,9 @@ class UserService:
service_id=service_id,
email=email,
display_name=display_name,
tenant_specific_field_1=tenant_specific_field_1,
tenant_specific_field_2=tenant_specific_field_2,
tenant_specific_field_3=tenant_specific_field_3,
)
db.session.add(user_model)

View File

@ -82,7 +82,8 @@
<bpmn:incoming>Flow_0bgkfue</bpmn:incoming>
<bpmn:outgoing>Flow_1ivhu7x</bpmn:outgoing>
<bpmn:script>approver = get_current_user()
lane_owners["Finance Team"].remove(approver)</bpmn:script>
username = approver['username']
lane_owners["Finance Team"].remove(username)</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">

View File

@ -0,0 +1,46 @@
"""Test_get_localtime."""
import json
from flask import g
from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext,
)
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.scripts.get_current_user import GetCurrentUser
class TestGetCurrentUser(BaseTest):
def test_get_current_user(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
"""Test_can_get_members_of_a_group."""
testuser1 = self.find_or_create_user("testuser1")
testuser1.tenant_specific_field_1 = "456"
db.session.add(testuser1)
db.session.commit()
testuser1 = self.find_or_create_user("testuser1")
g.user = testuser1
process_model_identifier = "test_process_model"
process_instance_id = 1
script_attributes_context = ScriptAttributesContext(
task=None,
environment_identifier="testing",
process_instance_id=process_instance_id,
process_model_identifier=process_model_identifier,
)
result = GetCurrentUser().run(
script_attributes_context,
)
assert result["username"] == "testuser1"
assert result["tenant_specific_field_1"] == "456"
json.dumps(result)

View File

@ -20,4 +20,4 @@ class TestEnvironmentVarScript(BaseTest):
with app.app_context():
script_engine = ProcessInstanceProcessor._script_engine
result = script_engine._evaluate("get_env()", {})
assert result == "testing"
assert result == "unit_testing"

View File

@ -63,6 +63,17 @@ Fix lint in code.
Probably just stick with lint:fix which also runs prettier.
## Runtime configuration options
The frontend docker image respects the following environment variables.
SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_APP_ROUTING_STRATEGY=subdomain_based
SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_APP_ROUTING_STRATEGY=path_based
subdomain_based example: api.spiffworkflow.org goes to backend
path_based example: spiffworkflow.org/api goes to backend
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).

View File

@ -7,4 +7,41 @@ function error_handler() {
trap 'error_handler ${LINENO} $?' ERR
set -o errtrace -o errexit -o nounset -o pipefail
# sort of like https://lithic.tech/blog/2020-05/react-dynamic-config, but without golang
react_configs=$(env | grep -E "^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_" || echo '')
if [[ -n "$react_configs" ]]; then
index_html_file="./build/index.html"
if [[ ! -f "$index_html_file" ]]; then
>&2 echo "ERROR: Could not find '${index_html_file}'. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1
fi
if ! command -v sed >/dev/null ; then
>&2 echo "ERROR: sed command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1
fi
if ! command -v perl >/dev/null ; then
>&2 echo "ERROR: perl command not found. Cannot use SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG values without it."
exit 1
fi
for react_config in $react_configs; do
react_config_without_prefix=$(sed -E 's/^SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_([^=]*)=(.*)/\1=\\"\2\\"/' <<<"${react_config}")
if [[ -z "$react_config_without_prefix" ]]; then
>&2 echo "ERROR: Could not parse react config line: '${react_config}'."
exit 1
fi
# actually do the search and replace to add the js config to the html page
perl -pi -e "s/(window._jsenv=\{\})/\1;window._jsenv.${react_config_without_prefix}/" "$index_html_file"
if ! grep -Eq "${react_config_without_prefix}" "$index_html_file"; then
>&2 echo "ERROR: Could not find '${react_config_without_prefix}' in '${index_html_file}' after search and replace. It is likely that the assumptions in boot_server_in_docker about the contents of the html page have changed. Fix the glitch in boot_server_in_docker."
exit 1
fi
done
fi
exec ./node_modules/.bin/serve -s build -l "$PORT0"

View File

@ -32,7 +32,7 @@
"@types/node": "^18.6.5",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@uiw/react-md-editor": "^3.19.5",
"@uiw/react-md-editor": "^3.20.2",
"autoprefixer": "10.4.8",
"axios": "^0.27.2",
"bootstrap": "^5.2.0",
@ -58,11 +58,9 @@
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-jsonschema-form": "^1.8.1",
"react-markdown": "^8.0.3",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1",
"remark-gfm": "^3.0.1",
"serve": "^14.0.0",
"timepicker": "^1.13.18",
"typescript": "^4.7.4",
@ -6473,9 +6471,9 @@
}
},
"node_modules/@uiw/react-md-editor": {
"version": "3.19.5",
"resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-3.19.5.tgz",
"integrity": "sha512-uhFGLOrKEtADM8QUauTdG5x8wqNS15ry2jCYMBr/E1Or3njvg7jpB0KRv+QTgZDnglevCTjuQxZPH7I7hG2uKw==",
"version": "3.20.2",
"resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-3.20.2.tgz",
"integrity": "sha512-L3sp3dsbpTOcVX+mzkmwwDl2Jl/UEgrySTun4XCMck1QF1WX53z0sHbN6XET+veHOML9Tw8TUUECR7IqajYjDw==",
"dependencies": {
"@babel/runtime": "^7.14.6",
"@uiw/react-markdown-preview": "^4.1.5",
@ -36996,9 +36994,9 @@
}
},
"@uiw/react-md-editor": {
"version": "3.19.5",
"resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-3.19.5.tgz",
"integrity": "sha512-uhFGLOrKEtADM8QUauTdG5x8wqNS15ry2jCYMBr/E1Or3njvg7jpB0KRv+QTgZDnglevCTjuQxZPH7I7hG2uKw==",
"version": "3.20.2",
"resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-3.20.2.tgz",
"integrity": "sha512-L3sp3dsbpTOcVX+mzkmwwDl2Jl/UEgrySTun4XCMck1QF1WX53z0sHbN6XET+veHOML9Tw8TUUECR7IqajYjDw==",
"requires": {
"@babel/runtime": "^7.14.6",
"@uiw/react-markdown-preview": "^4.1.5",

View File

@ -27,7 +27,7 @@
"@types/node": "^18.6.5",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@uiw/react-md-editor": "^3.19.5",
"@uiw/react-md-editor": "^3.20.2",
"autoprefixer": "10.4.8",
"axios": "^0.27.2",
"bootstrap": "^5.2.0",
@ -53,11 +53,9 @@
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-jsonschema-form": "^1.8.1",
"react-markdown": "^8.0.3",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1",
"remark-gfm": "^3.0.1",
"serve": "^14.0.0",
"timepicker": "^1.13.18",
"typescript": "^4.7.4",

View File

@ -25,6 +25,10 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>SpiffWorkflow</title>
<script>
window.spiffworkflowFrontendJsenv = {};
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,17 +1,46 @@
const { port, hostname } = window.location;
let hostAndPort = `api.${hostname}`;
let protocol = 'https';
declare global {
interface SpiffworkflowFrontendJsenvObject {
[key: string]: string;
}
interface Window {
spiffworkflowFrontendJsenv: SpiffworkflowFrontendJsenvObject;
}
}
let appRoutingStrategy = 'subdomain_based';
if (
'spiffworkflowFrontendJsenv' in window &&
'APP_ROUTING_STRATEGY' in window.spiffworkflowFrontendJsenv
) {
appRoutingStrategy = window.spiffworkflowFrontendJsenv.APP_ROUTING_STRATEGY;
}
let hostAndPortAndPathPrefix;
if (appRoutingStrategy === 'subdomain_based') {
hostAndPortAndPathPrefix = `api.${hostname}`;
} else if (appRoutingStrategy === 'path_based') {
hostAndPortAndPathPrefix = `${hostname}/api`;
} else {
throw new Error(`Invalid app routing strategy: ${appRoutingStrategy}`);
}
if (/^\d+\./.test(hostname) || hostname === 'localhost') {
let serverPort = 7000;
if (!Number.isNaN(Number(port))) {
serverPort = Number(port) - 1;
}
hostAndPort = `${hostname}:${serverPort}`;
hostAndPortAndPathPrefix = `${hostname}:${serverPort}`;
protocol = 'http';
}
let url = `${protocol}://${hostAndPort}/v1.0`;
let url = `${protocol}://${hostAndPortAndPathPrefix}/v1.0`;
// this can only ever work locally since this is a static site.
// use spiffworkflowFrontendJsenv if you want to inject env vars
// that can be read by the static site.
if (process.env.REACT_APP_BACKEND_BASE_URL) {
url = process.env.REACT_APP_BACKEND_BASE_URL;
}

View File

@ -12,8 +12,7 @@ import {
// @ts-ignore
} from '@carbon/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import MDEditor from '@uiw/react-md-editor';
// eslint-disable-next-line import/no-named-as-default
import Form from '../themes/carbon';
import HttpService from '../services/HttpService';
@ -263,9 +262,7 @@ export default function TaskShow() {
}
return (
<div className="markdown">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{instructions}
</ReactMarkdown>
<MDEditor.Markdown source={instructions} />
</div>
);
};