mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-30 19:56:20 +00:00
Merge pull request #100 from sartography/feature/session_timeouts
Feature/session timeouts
This commit is contained in:
commit
2c1c173f36
@ -9,7 +9,6 @@ def main() -> None:
|
|||||||
"""Main."""
|
"""Main."""
|
||||||
app = create_app()
|
app = create_app()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
print("HEY")
|
|
||||||
failing_process_models = DataSetupService.save_all_process_models()
|
failing_process_models = DataSetupService.save_all_process_models()
|
||||||
for bpmn_errors in failing_process_models:
|
for bpmn_errors in failing_process_models:
|
||||||
print(bpmn_errors)
|
print(bpmn_errors)
|
||||||
|
@ -39,7 +39,8 @@ docker run \
|
|||||||
-e KEYCLOAK_LOGLEVEL=ALL \
|
-e KEYCLOAK_LOGLEVEL=ALL \
|
||||||
-e ROOT_LOGLEVEL=ALL \
|
-e ROOT_LOGLEVEL=ALL \
|
||||||
-e KEYCLOAK_ADMIN=admin \
|
-e KEYCLOAK_ADMIN=admin \
|
||||||
-e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.1 start-dev \
|
-e KEYCLOAK_ADMIN_PASSWORD=admin \
|
||||||
|
quay.io/keycloak/keycloak:20.0.1 start-dev \
|
||||||
-Dkeycloak.profile.feature.token_exchange=enabled \
|
-Dkeycloak.profile.feature.token_exchange=enabled \
|
||||||
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
|
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
|
||||||
|
|
||||||
|
128
spiffworkflow-backend/poetry.lock
generated
128
spiffworkflow-backend/poetry.lock
generated
@ -462,21 +462,6 @@ toml = "*"
|
|||||||
conda = ["pyyaml"]
|
conda = ["pyyaml"]
|
||||||
pipenv = ["pipenv"]
|
pipenv = ["pipenv"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ecdsa"
|
|
||||||
version = "0.18.0"
|
|
||||||
description = "ECDSA cryptographic signature library (pure python)"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
six = ">=1.9.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
gmpy = ["gmpy"]
|
|
||||||
gmpy2 = ["gmpy2"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exceptiongroup"
|
name = "exceptiongroup"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@ -668,6 +653,22 @@ python-versions = "*"
|
|||||||
Flask = ">=0.9"
|
Flask = ">=0.9"
|
||||||
Six = "*"
|
Six = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-jwt-extended"
|
||||||
|
version = "4.4.4"
|
||||||
|
description = "Extended JWT integration with Flask"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<4"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Flask = ">=2.0,<3.0"
|
||||||
|
PyJWT = ">=2.0,<3.0"
|
||||||
|
Werkzeug = ">=0.14"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
asymmetric-crypto = ["cryptography (>=3.3.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "Flask-Mail"
|
name = "Flask-Mail"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@ -1223,14 +1224,6 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyasn1"
|
|
||||||
version = "0.4.8"
|
|
||||||
description = "ASN.1 types and codecs"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycodestyle"
|
name = "pycodestyle"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@ -1384,41 +1377,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
six = ">=1.5"
|
six = ">=1.5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-jose"
|
|
||||||
version = "3.3.0"
|
|
||||||
description = "JOSE implementation in Python"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
ecdsa = "!=0.15"
|
|
||||||
pyasn1 = "*"
|
|
||||||
rsa = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
cryptography = ["cryptography (>=3.4.0)"]
|
|
||||||
pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"]
|
|
||||||
pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-keycloak"
|
|
||||||
version = "2.6.0"
|
|
||||||
description = "python-keycloak is a Python package providing access to the Keycloak API."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7,<4.0"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
python-jose = ">=3.3.0,<4.0.0"
|
|
||||||
requests = ">=2.20.0,<3.0.0"
|
|
||||||
requests-toolbelt = ">=0.9.1,<0.10.0"
|
|
||||||
urllib3 = ">=1.26.0,<2.0.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["Sphinx (>=5.0.2,<6.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.8,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=1.8.4,<2.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2022.6"
|
version = "2022.6"
|
||||||
@ -1494,17 +1452,6 @@ urllib3 = ">=1.21.1,<1.27"
|
|||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests-toolbelt"
|
|
||||||
version = "0.9.1"
|
|
||||||
description = "A utility belt for advanced users of python-requests"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
requests = ">=2.0.1,<3.0.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "restrictedpython"
|
name = "restrictedpython"
|
||||||
version = "6.0"
|
version = "6.0"
|
||||||
@ -1528,17 +1475,6 @@ python-versions = "*"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
docutils = ">=0.11,<1.0"
|
docutils = ">=0.11,<1.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rsa"
|
|
||||||
version = "4.9"
|
|
||||||
description = "Pure-Python RSA implementation"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6,<4"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyasn1 = ">=0.1.3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruamel.yaml"
|
name = "ruamel.yaml"
|
||||||
version = "0.17.21"
|
version = "0.17.21"
|
||||||
@ -1851,7 +1787,7 @@ lxml = "*"
|
|||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/sartography/SpiffWorkflow"
|
url = "https://github.com/sartography/SpiffWorkflow"
|
||||||
reference = "main"
|
reference = "main"
|
||||||
resolved_reference = "5eed83ab12f67c01c7836424a22fc425a33fc55d"
|
resolved_reference = "be26100bcbef8026e26312c665dae42faf476485"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "SQLAlchemy"
|
name = "SQLAlchemy"
|
||||||
@ -2222,7 +2158,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = ">=3.9,<3.12"
|
python-versions = ">=3.9,<3.12"
|
||||||
content-hash = "832c1b6cd8d9aebc8529fdce11167bddcb3634fd0767dd2e490b74ababcf2714"
|
content-hash = "8592e94ba80b7d0338a9c003ca4d0e189b5f470d97391438ddc1fc9050febedb"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alabaster = [
|
alabaster = [
|
||||||
@ -2443,10 +2379,6 @@ dparse = [
|
|||||||
{file = "dparse-0.6.2-py3-none-any.whl", hash = "sha256:8097076f1dd26c377f30d4745e6ec18fef42f3bf493933b842ac5bafad8c345f"},
|
{file = "dparse-0.6.2-py3-none-any.whl", hash = "sha256:8097076f1dd26c377f30d4745e6ec18fef42f3bf493933b842ac5bafad8c345f"},
|
||||||
{file = "dparse-0.6.2.tar.gz", hash = "sha256:d45255bda21f998bc7ddf2afd5e62505ba6134756ba2d42a84c56b0826614dfe"},
|
{file = "dparse-0.6.2.tar.gz", hash = "sha256:d45255bda21f998bc7ddf2afd5e62505ba6134756ba2d42a84c56b0826614dfe"},
|
||||||
]
|
]
|
||||||
ecdsa = [
|
|
||||||
{file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
|
|
||||||
{file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
|
|
||||||
]
|
|
||||||
exceptiongroup = [
|
exceptiongroup = [
|
||||||
{file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
|
{file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
|
||||||
{file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
|
{file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
|
||||||
@ -2494,6 +2426,10 @@ Flask-Cors = [
|
|||||||
{file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
|
{file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
|
||||||
{file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
|
{file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
|
||||||
]
|
]
|
||||||
|
flask-jwt-extended = [
|
||||||
|
{file = "Flask-JWT-Extended-4.4.4.tar.gz", hash = "sha256:62b521d75494c290a646ae8acc77123721e4364790f1e64af0038d823961fbf0"},
|
||||||
|
{file = "Flask_JWT_Extended-4.4.4-py2.py3-none-any.whl", hash = "sha256:a85eebfa17c339a7260c4643475af444784ba6de5588adda67406f0a75599553"},
|
||||||
|
]
|
||||||
Flask-Mail = [
|
Flask-Mail = [
|
||||||
{file = "Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"},
|
{file = "Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"},
|
||||||
]
|
]
|
||||||
@ -2988,10 +2924,6 @@ psycopg2 = [
|
|||||||
{file = "psycopg2-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308"},
|
{file = "psycopg2-2.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308"},
|
||||||
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
{file = "psycopg2-2.9.4.tar.gz", hash = "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"},
|
||||||
]
|
]
|
||||||
pyasn1 = [
|
|
||||||
{file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
|
|
||||||
{file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
|
|
||||||
]
|
|
||||||
pycodestyle = [
|
pycodestyle = [
|
||||||
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
|
||||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||||
@ -3059,14 +2991,6 @@ python-dateutil = [
|
|||||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||||
]
|
]
|
||||||
python-jose = [
|
|
||||||
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
|
|
||||||
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
|
|
||||||
]
|
|
||||||
python-keycloak = [
|
|
||||||
{file = "python-keycloak-2.6.0.tar.gz", hash = "sha256:08c530ff86f631faccb8033d9d9345cc3148cb2cf132ff7564f025292e4dbd96"},
|
|
||||||
{file = "python_keycloak-2.6.0-py3-none-any.whl", hash = "sha256:a1ce102b978beb56d385319b3ca20992b915c2c12d15a2d0c23f1104882f3fb6"},
|
|
||||||
]
|
|
||||||
pytz = [
|
pytz = [
|
||||||
{file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"},
|
{file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"},
|
||||||
{file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"},
|
{file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"},
|
||||||
@ -3205,10 +3129,6 @@ requests = [
|
|||||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
]
|
]
|
||||||
requests-toolbelt = [
|
|
||||||
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
|
|
||||||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
|
||||||
]
|
|
||||||
restrictedpython = [
|
restrictedpython = [
|
||||||
{file = "RestrictedPython-6.0-py3-none-any.whl", hash = "sha256:3479303f7bff48a7dedad76f96e7704993c5e86c5adbd67f607295d5352f0fb8"},
|
{file = "RestrictedPython-6.0-py3-none-any.whl", hash = "sha256:3479303f7bff48a7dedad76f96e7704993c5e86c5adbd67f607295d5352f0fb8"},
|
||||||
{file = "RestrictedPython-6.0.tar.gz", hash = "sha256:405cf0bd9eec2f19b1326b5f48228efe56d6590b4e91826b8cc3b2cd400a96ad"},
|
{file = "RestrictedPython-6.0.tar.gz", hash = "sha256:405cf0bd9eec2f19b1326b5f48228efe56d6590b4e91826b8cc3b2cd400a96ad"},
|
||||||
@ -3216,10 +3136,6 @@ restrictedpython = [
|
|||||||
restructuredtext-lint = [
|
restructuredtext-lint = [
|
||||||
{file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"},
|
{file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"},
|
||||||
]
|
]
|
||||||
rsa = [
|
|
||||||
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
|
||||||
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
|
||||||
]
|
|
||||||
"ruamel.yaml" = [
|
"ruamel.yaml" = [
|
||||||
{file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
|
{file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
|
||||||
{file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
|
{file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
|
||||||
|
@ -44,7 +44,6 @@ marshmallow-enum = "^1.5.1"
|
|||||||
marshmallow-sqlalchemy = "^0.28.0"
|
marshmallow-sqlalchemy = "^0.28.0"
|
||||||
PyJWT = "^2.6.0"
|
PyJWT = "^2.6.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
python-keycloak = "^2.5.0"
|
|
||||||
APScheduler = "*"
|
APScheduler = "*"
|
||||||
Jinja2 = "^3.1.2"
|
Jinja2 = "^3.1.2"
|
||||||
RestrictedPython = "^6.0"
|
RestrictedPython = "^6.0"
|
||||||
@ -72,6 +71,7 @@ simplejson = "^3.17.6"
|
|||||||
pytz = "^2022.6"
|
pytz = "^2022.6"
|
||||||
dateparser = "^1.1.2"
|
dateparser = "^1.1.2"
|
||||||
types-dateparser = "^1.1.4.1"
|
types-dateparser = "^1.1.4.1"
|
||||||
|
flask-jwt-extended = "^4.4.4"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
@ -23,6 +23,7 @@ from spiffworkflow_backend.routes.admin_blueprint.admin_blueprint import admin_b
|
|||||||
from spiffworkflow_backend.routes.openid_blueprint.openid_blueprint import (
|
from spiffworkflow_backend.routes.openid_blueprint.openid_blueprint import (
|
||||||
openid_blueprint,
|
openid_blueprint,
|
||||||
)
|
)
|
||||||
|
from spiffworkflow_backend.routes.user import set_new_access_token_in_cookie
|
||||||
from spiffworkflow_backend.routes.user import verify_token
|
from spiffworkflow_backend.routes.user import verify_token
|
||||||
from spiffworkflow_backend.routes.user_blueprint import user_blueprint
|
from spiffworkflow_backend.routes.user_blueprint import user_blueprint
|
||||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||||
@ -115,7 +116,7 @@ def create_app() -> flask.app.Flask:
|
|||||||
r"^https?:\/\/%s(.*)" % o.replace(".", r"\.")
|
r"^https?:\/\/%s(.*)" % o.replace(".", r"\.")
|
||||||
for o in app.config["CORS_ALLOW_ORIGINS"]
|
for o in app.config["CORS_ALLOW_ORIGINS"]
|
||||||
]
|
]
|
||||||
CORS(app, origins=origins_re, max_age=3600)
|
CORS(app, origins=origins_re, max_age=3600, supports_credentials=True)
|
||||||
|
|
||||||
connexion_app.add_api("api.yml", base_path=V1_API_PATH_PREFIX)
|
connexion_app.add_api("api.yml", base_path=V1_API_PATH_PREFIX)
|
||||||
|
|
||||||
@ -131,6 +132,7 @@ def create_app() -> flask.app.Flask:
|
|||||||
|
|
||||||
app.before_request(verify_token)
|
app.before_request(verify_token)
|
||||||
app.before_request(AuthorizationService.check_for_permission)
|
app.before_request(AuthorizationService.check_for_permission)
|
||||||
|
app.after_request(set_new_access_token_in_cookie)
|
||||||
|
|
||||||
return app # type: ignore
|
return app # type: ignore
|
||||||
|
|
||||||
|
@ -63,7 +63,6 @@ def setup_config(app: Flask) -> None:
|
|||||||
)
|
)
|
||||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||||
app.config.from_object("spiffworkflow_backend.config.default")
|
app.config.from_object("spiffworkflow_backend.config.default")
|
||||||
print("loaded config: default")
|
|
||||||
|
|
||||||
env_config_prefix = "spiffworkflow_backend.config."
|
env_config_prefix = "spiffworkflow_backend.config."
|
||||||
if (
|
if (
|
||||||
@ -71,7 +70,6 @@ def setup_config(app: Flask) -> None:
|
|||||||
and os.environ.get("SPIFFWORKFLOW_BACKEND_ENV") is not None
|
and os.environ.get("SPIFFWORKFLOW_BACKEND_ENV") is not None
|
||||||
):
|
):
|
||||||
load_config_file(app, f"{env_config_prefix}terraform_deployed_environment")
|
load_config_file(app, f"{env_config_prefix}terraform_deployed_environment")
|
||||||
print("loaded config: terraform_deployed_environment")
|
|
||||||
|
|
||||||
env_config_module = env_config_prefix + app.config["ENV_IDENTIFIER"]
|
env_config_module = env_config_prefix + app.config["ENV_IDENTIFIER"]
|
||||||
load_config_file(app, env_config_module)
|
load_config_file(app, env_config_module)
|
||||||
@ -90,14 +88,6 @@ def setup_config(app: Flask) -> None:
|
|||||||
"permissions",
|
"permissions",
|
||||||
app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"],
|
app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"],
|
||||||
)
|
)
|
||||||
print(
|
|
||||||
"set permissions file name config:"
|
|
||||||
f" {app.config['SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME']}"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"set permissions file name full path:"
|
|
||||||
f" {app.config['PERMISSIONS_FILE_FULLPATH']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# unversioned (see .gitignore) config that can override everything and include secrets.
|
# unversioned (see .gitignore) config that can override everything and include secrets.
|
||||||
# src/spiffworkflow_backend/config/secrets.py
|
# src/spiffworkflow_backend/config/secrets.py
|
||||||
|
@ -29,8 +29,11 @@ CONNECTOR_PROXY_URL = environ.get(
|
|||||||
|
|
||||||
# Open ID server
|
# Open ID server
|
||||||
OPEN_ID_SERVER_URL = environ.get(
|
OPEN_ID_SERVER_URL = environ.get(
|
||||||
"OPEN_ID_SERVER_URL", default="http://localhost:7002/realms/spiffworkflow"
|
"OPEN_ID_SERVER_URL",
|
||||||
|
default="http://localhost:7002/realms/spiffworkflow"
|
||||||
|
# "OPEN_ID_SERVER_URL", default="http://localhost:7000/openid"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Replace above line with this to use the built-in Open ID Server.
|
# Replace above line with this to use the built-in Open ID Server.
|
||||||
# OPEN_ID_SERVER_URL = environ.get("OPEN_ID_SERVER_URL", default="http://localhost:7000/openid")
|
# OPEN_ID_SERVER_URL = environ.get("OPEN_ID_SERVER_URL", default="http://localhost:7000/openid")
|
||||||
OPEN_ID_CLIENT_ID = environ.get("OPEN_ID_CLIENT_ID", default="spiffworkflow-backend")
|
OPEN_ID_CLIENT_ID = environ.get("OPEN_ID_CLIENT_ID", default="spiffworkflow-backend")
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
"""APIs for dealing with process groups, process models, and process instances."""
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
import json
|
from flask import make_response
|
||||||
|
|
||||||
import flask.wrappers
|
|
||||||
from flask.wrappers import Response
|
from flask.wrappers import Response
|
||||||
|
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
|
|
||||||
|
|
||||||
def status() -> flask.wrappers.Response:
|
def status() -> Response:
|
||||||
"""Status."""
|
"""Status."""
|
||||||
ProcessInstanceModel.query.filter().first()
|
ProcessInstanceModel.query.filter().first()
|
||||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
return make_response({"ok": True}, 200)
|
||||||
|
@ -88,7 +88,7 @@ def process_group_list(
|
|||||||
"pages": pages,
|
"pages": pages,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return Response(json.dumps(response_json), status=200, mimetype="application/json")
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
def process_group_show(
|
def process_group_show(
|
||||||
|
@ -7,6 +7,7 @@ from typing import Dict
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
import flask
|
||||||
import jwt
|
import jwt
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import g
|
from flask import g
|
||||||
@ -20,6 +21,7 @@ from spiffworkflow_backend.services.authentication_service import Authentication
|
|||||||
from spiffworkflow_backend.services.authentication_service import (
|
from spiffworkflow_backend.services.authentication_service import (
|
||||||
MissingAccessTokenError,
|
MissingAccessTokenError,
|
||||||
)
|
)
|
||||||
|
from spiffworkflow_backend.services.authentication_service import TokenExpiredError
|
||||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||||
from spiffworkflow_backend.services.user_service import UserService
|
from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
@ -55,6 +57,9 @@ def verify_token(
|
|||||||
if not token and "Authorization" in request.headers:
|
if not token and "Authorization" in request.headers:
|
||||||
token = request.headers["Authorization"].removeprefix("Bearer ")
|
token = request.headers["Authorization"].removeprefix("Bearer ")
|
||||||
|
|
||||||
|
# This should never be set here but just in case
|
||||||
|
_clear_auth_tokens_from_thread_local_data()
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
user_model = None
|
user_model = None
|
||||||
decoded_token = get_decoded_token(token)
|
decoded_token = get_decoded_token(token)
|
||||||
@ -71,12 +76,11 @@ def verify_token(
|
|||||||
f" internal token. {e}"
|
f" internal token. {e}"
|
||||||
)
|
)
|
||||||
elif "iss" in decoded_token.keys():
|
elif "iss" in decoded_token.keys():
|
||||||
|
user_info = None
|
||||||
try:
|
try:
|
||||||
if AuthenticationService.validate_id_token(token):
|
if AuthenticationService.validate_id_or_access_token(token):
|
||||||
user_info = decoded_token
|
user_info = decoded_token
|
||||||
except (
|
except TokenExpiredError as token_expired_error:
|
||||||
ApiError
|
|
||||||
) as ae: # API Error is only thrown in the token is outdated.
|
|
||||||
# Try to refresh the token
|
# Try to refresh the token
|
||||||
user = UserService.get_user_by_service_and_service_id(
|
user = UserService.get_user_by_service_and_service_id(
|
||||||
decoded_token["iss"], decoded_token["sub"]
|
decoded_token["iss"], decoded_token["sub"]
|
||||||
@ -90,17 +94,24 @@ def verify_token(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if auth_token and "error" not in auth_token:
|
if auth_token and "error" not in auth_token:
|
||||||
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
|
tld.new_access_token = auth_token["access_token"]
|
||||||
|
tld.new_id_token = auth_token["id_token"]
|
||||||
# We have the user, but this code is a bit convoluted, and will later demand
|
# We have the user, but this code is a bit convoluted, and will later demand
|
||||||
# a user_info object so it can look up the user. Sorry to leave this crap here.
|
# a user_info object so it can look up the user. Sorry to leave this crap here.
|
||||||
user_info = {"sub": user.service_id}
|
user_info = {
|
||||||
else:
|
"sub": user.service_id,
|
||||||
raise ae
|
"iss": user.service,
|
||||||
else:
|
}
|
||||||
raise ae
|
|
||||||
else:
|
if user_info is None:
|
||||||
raise ae
|
raise ApiError(
|
||||||
|
error_code="invalid_token",
|
||||||
|
message="Your token is expired. Please Login",
|
||||||
|
status_code=401,
|
||||||
|
) from token_expired_error
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Exception raised in get_token: {e}")
|
|
||||||
raise ApiError(
|
raise ApiError(
|
||||||
error_code="fail_get_user_info",
|
error_code="fail_get_user_info",
|
||||||
message="Cannot get user info from token",
|
message="Cannot get user info from token",
|
||||||
@ -150,8 +161,6 @@ def verify_token(
|
|||||||
g.token = token
|
g.token = token
|
||||||
get_scope(token)
|
get_scope(token)
|
||||||
return None
|
return None
|
||||||
# return {"uid": g.user.id, "sub": g.user.id, "scope": scope}
|
|
||||||
# return validate_scope(token, user_info, user_model)
|
|
||||||
else:
|
else:
|
||||||
raise ApiError(error_code="no_user_id", message="Cannot get a user id")
|
raise ApiError(error_code="no_user_id", message="Cannot get a user id")
|
||||||
|
|
||||||
@ -160,16 +169,28 @@ def verify_token(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_scope(token: Any) -> bool:
|
def set_new_access_token_in_cookie(
|
||||||
"""Validate_scope."""
|
response: flask.wrappers.Response,
|
||||||
print("validate_scope")
|
) -> flask.wrappers.Response:
|
||||||
# token = AuthenticationService.refresh_token(token)
|
"""Checks if a new token has been set in THREAD_LOCAL_DATA and sets cookies if appropriate.
|
||||||
# user_info = AuthenticationService.get_user_info_from_public_access_token(token)
|
|
||||||
# bearer_token = AuthenticationService.get_bearer_token(token)
|
It will also delete the cookies if the user has logged out.
|
||||||
# permission = AuthenticationService.get_permission_by_basic_token(token)
|
"""
|
||||||
# permissions = AuthenticationService.get_permissions_by_token_for_resource_and_scope(token)
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
# introspection = AuthenticationService.introspect_token(basic_token)
|
if hasattr(tld, "new_access_token") and tld.new_access_token:
|
||||||
return True
|
response.set_cookie("access_token", tld.new_access_token)
|
||||||
|
|
||||||
|
# id_token is required for logging out since this gets passed back to the openid server
|
||||||
|
if hasattr(tld, "new_id_token") and tld.new_id_token:
|
||||||
|
response.set_cookie("id_token", tld.new_id_token)
|
||||||
|
|
||||||
|
if hasattr(tld, "user_has_logged_out") and tld.user_has_logged_out:
|
||||||
|
response.set_cookie("id_token", "", max_age=0)
|
||||||
|
response.set_cookie("access_token", "", max_age=0)
|
||||||
|
|
||||||
|
_clear_auth_tokens_from_thread_local_data()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def encode_auth_token(sub: str, token_type: Optional[str] = None) -> str:
|
def encode_auth_token(sub: str, token_type: Optional[str] = None) -> str:
|
||||||
@ -226,7 +247,7 @@ def login_return(code: str, state: str, session_state: str) -> Optional[Response
|
|||||||
|
|
||||||
user_info = parse_id_token(id_token)
|
user_info = parse_id_token(id_token)
|
||||||
|
|
||||||
if AuthenticationService.validate_id_token(id_token):
|
if AuthenticationService.validate_id_or_access_token(id_token):
|
||||||
if user_info and "error" not in user_info:
|
if user_info and "error" not in user_info:
|
||||||
user_model = AuthorizationService.create_user_from_sign_in(user_info)
|
user_model = AuthorizationService.create_user_from_sign_in(user_info)
|
||||||
g.user = user_model.id
|
g.user = user_model.id
|
||||||
@ -234,11 +255,10 @@ def login_return(code: str, state: str, session_state: str) -> Optional[Response
|
|||||||
AuthenticationService.store_refresh_token(
|
AuthenticationService.store_refresh_token(
|
||||||
user_model.id, auth_token_object["refresh_token"]
|
user_model.id, auth_token_object["refresh_token"]
|
||||||
)
|
)
|
||||||
redirect_url = (
|
redirect_url = state_redirect_url
|
||||||
f"{state_redirect_url}?"
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
+ f"access_token={auth_token_object['access_token']}&"
|
tld.new_access_token = auth_token_object["access_token"]
|
||||||
+ f"id_token={id_token}"
|
tld.new_id_token = auth_token_object["id_token"]
|
||||||
)
|
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
raise ApiError(
|
raise ApiError(
|
||||||
@ -284,6 +304,8 @@ def logout(id_token: str, redirect_url: Optional[str]) -> Response:
|
|||||||
"""Logout."""
|
"""Logout."""
|
||||||
if redirect_url is None:
|
if redirect_url is None:
|
||||||
redirect_url = ""
|
redirect_url = ""
|
||||||
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
|
tld.user_has_logged_out = True
|
||||||
return AuthenticationService().logout(redirect_url=redirect_url, id_token=id_token)
|
return AuthenticationService().logout(redirect_url=redirect_url, id_token=id_token)
|
||||||
|
|
||||||
|
|
||||||
@ -312,15 +334,6 @@ def get_decoded_token(token: str) -> Optional[Dict]:
|
|||||||
error_code="unknown_token",
|
error_code="unknown_token",
|
||||||
message="Unknown token type in get_decoded_token",
|
message="Unknown token type in get_decoded_token",
|
||||||
)
|
)
|
||||||
# try:
|
|
||||||
# # see if we have an open_id token
|
|
||||||
# decoded_token = AuthorizationService.decode_auth_token(token)
|
|
||||||
# else:
|
|
||||||
# if 'sub' in decoded_token and 'iss' in decoded_token and 'aud' in decoded_token:
|
|
||||||
# token_type = 'id_token'
|
|
||||||
|
|
||||||
# if 'token_type' in decoded_token and 'sub' in decoded_token:
|
|
||||||
# return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_scope(token: str) -> str:
|
def get_scope(token: str) -> str:
|
||||||
@ -347,3 +360,14 @@ def get_user_from_decoded_internal_token(decoded_token: dict) -> Optional[UserMo
|
|||||||
return user
|
return user
|
||||||
user = UserService.create_user(service_id, service, service_id)
|
user = UserService.create_user(service_id, service, service_id)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_auth_tokens_from_thread_local_data() -> None:
|
||||||
|
"""_clear_auth_tokens_from_thread_local_data."""
|
||||||
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
|
if hasattr(tld, "new_access_token"):
|
||||||
|
delattr(tld, "new_access_token")
|
||||||
|
if hasattr(tld, "new_id_token"):
|
||||||
|
delattr(tld, "new_id_token")
|
||||||
|
if hasattr(tld, "user_has_logged_out"):
|
||||||
|
delattr(tld, "user_has_logged_out")
|
||||||
|
@ -20,6 +20,15 @@ class MissingAccessTokenError(Exception):
|
|||||||
"""MissingAccessTokenError."""
|
"""MissingAccessTokenError."""
|
||||||
|
|
||||||
|
|
||||||
|
# These could be either 'id' OR 'access' tokens and we can't always know which
|
||||||
|
class TokenExpiredError(Exception):
|
||||||
|
"""TokenExpiredError."""
|
||||||
|
|
||||||
|
|
||||||
|
class TokenInvalidError(Exception):
|
||||||
|
"""TokenInvalidError."""
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationProviderTypes(enum.Enum):
|
class AuthenticationProviderTypes(enum.Enum):
|
||||||
"""AuthenticationServiceProviders."""
|
"""AuthenticationServiceProviders."""
|
||||||
|
|
||||||
@ -125,18 +134,15 @@ class AuthenticationService:
|
|||||||
return auth_token_object
|
return auth_token_object
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_id_token(cls, id_token: str) -> bool:
|
def validate_id_or_access_token(cls, token: str) -> bool:
|
||||||
"""Https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation."""
|
"""Https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation."""
|
||||||
valid = True
|
valid = True
|
||||||
now = time.time()
|
now = time.time()
|
||||||
try:
|
try:
|
||||||
decoded_token = jwt.decode(id_token, options={"verify_signature": False})
|
decoded_token = jwt.decode(token, options={"verify_signature": False})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ApiError(
|
raise TokenInvalidError("Cannot decode token") from e
|
||||||
error_code="bad_id_token",
|
|
||||||
message="Cannot decode id_token",
|
|
||||||
status_code=401,
|
|
||||||
) from e
|
|
||||||
if decoded_token["iss"] != cls.server_url():
|
if decoded_token["iss"] != cls.server_url():
|
||||||
valid = False
|
valid = False
|
||||||
elif (
|
elif (
|
||||||
@ -153,15 +159,10 @@ class AuthenticationService:
|
|||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
current_app.logger.error(f"Invalid token in validate_id_token: {id_token}")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if now > decoded_token["exp"]:
|
if now > decoded_token["exp"]:
|
||||||
raise ApiError(
|
raise TokenExpiredError("Your token is expired. Please Login")
|
||||||
error_code="invalid_token",
|
|
||||||
message="Your token is expired. Please Login",
|
|
||||||
status_code=401,
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -412,59 +412,6 @@ class AuthorizationService:
|
|||||||
status_code=403,
|
status_code=403,
|
||||||
)
|
)
|
||||||
|
|
||||||
# def refresh_token(self, token: str) -> str:
|
|
||||||
# """Refresh_token."""
|
|
||||||
# # if isinstance(token, str):
|
|
||||||
# # token = eval(token)
|
|
||||||
# (
|
|
||||||
# open_id_server_url,
|
|
||||||
# open_id_client_id,
|
|
||||||
# open_id_realm_name,
|
|
||||||
# open_id_client_secret_key,
|
|
||||||
# ) = AuthorizationService.get_open_id_args()
|
|
||||||
# headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
||||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
|
||||||
# data = {
|
|
||||||
# "grant_type": "refresh_token",
|
|
||||||
# "client_id": "spiffworkflow-frontend",
|
|
||||||
# "subject_token": token,
|
|
||||||
# "refresh_token": token,
|
|
||||||
# }
|
|
||||||
# refresh_response = requests.post(request_url, headers=headers, data=data)
|
|
||||||
# refresh_token = json.loads(refresh_response.text)
|
|
||||||
# return refresh_token
|
|
||||||
|
|
||||||
# def get_bearer_token(self, basic_token: str) -> dict:
|
|
||||||
# """Get_bearer_token."""
|
|
||||||
# (
|
|
||||||
# open_id_server_url,
|
|
||||||
# open_id_client_id,
|
|
||||||
# open_id_realm_name,
|
|
||||||
# open_id_client_secret_key,
|
|
||||||
# ) = AuthorizationService.get_open_id_args()
|
|
||||||
#
|
|
||||||
# backend_basic_auth_string = f"{open_id_client_id}:{open_id_client_secret_key}"
|
|
||||||
# backend_basic_auth_bytes = bytes(backend_basic_auth_string, encoding="ascii")
|
|
||||||
# backend_basic_auth = base64.b64encode(backend_basic_auth_bytes)
|
|
||||||
#
|
|
||||||
# headers = {
|
|
||||||
# "Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
# "Authorization": f"Basic {backend_basic_auth.decode('utf-8')}",
|
|
||||||
# }
|
|
||||||
# data = {
|
|
||||||
# "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
||||||
# "client_id": open_id_client_id,
|
|
||||||
# "subject_token": basic_token,
|
|
||||||
# "audience": open_id_client_id,
|
|
||||||
# }
|
|
||||||
# request_url = f"{open_id_server_url}/realms/{open_id_realm_name}/protocol/openid-connect/token"
|
|
||||||
#
|
|
||||||
# backend_response = requests.post(request_url, headers=headers, data=data)
|
|
||||||
# # json_data = json.loads(backend_response.text)
|
|
||||||
# # bearer_token = json_data['access_token']
|
|
||||||
# bearer_token: dict = json.loads(backend_response.text)
|
|
||||||
# return bearer_token
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]:
|
def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]:
|
||||||
"""Decode the auth token.
|
"""Decode the auth token.
|
||||||
|
@ -3,6 +3,7 @@ from typing import Any
|
|||||||
from typing import List
|
from typing import List
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
from flask_bpmn.api.api_error import ApiError
|
from flask_bpmn.api.api_error import ApiError
|
||||||
from flask_bpmn.models.db import db
|
from flask_bpmn.models.db import db
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ class ErrorHandlingService:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# hmm... what to do if a notification method fails. Probably log, at least
|
# hmm... what to do if a notification method fails. Probably log, at least
|
||||||
print(e)
|
current_app.logger.error(e)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hanle_sentry_notification(_error: ApiError, _recipients: List) -> None:
|
def hanle_sentry_notification(_error: ApiError, _recipients: List) -> None:
|
||||||
|
@ -42,7 +42,6 @@ class MessageService:
|
|||||||
message_type="receive", status="ready"
|
message_type="receive", status="ready"
|
||||||
).all()
|
).all()
|
||||||
for message_instance_send in message_instances_send:
|
for message_instance_send in message_instances_send:
|
||||||
# print(f"message_instance_send.id: {message_instance_send.id}")
|
|
||||||
# check again in case another background process picked up the message
|
# check again in case another background process picked up the message
|
||||||
# while the previous one was running
|
# while the previous one was running
|
||||||
if message_instance_send.status != "ready":
|
if message_instance_send.status != "ready":
|
||||||
|
@ -55,9 +55,6 @@ class ServiceTaskDelegate:
|
|||||||
f"{connector_proxy_url()}/v1/do/{name}", json=params
|
f"{connector_proxy_url()}/v1/do/{name}", json=params
|
||||||
)
|
)
|
||||||
|
|
||||||
if proxied_response.status_code != 200:
|
|
||||||
print("got error from connector proxy")
|
|
||||||
|
|
||||||
parsed_response = json.loads(proxied_response.text)
|
parsed_response = json.loads(proxied_response.text)
|
||||||
|
|
||||||
if "refreshed_token_set" not in parsed_response:
|
if "refreshed_token_set" not in parsed_response:
|
||||||
@ -86,7 +83,7 @@ class ServiceTaskService:
|
|||||||
parsed_response = json.loads(response.text)
|
parsed_response = json.loads(response.text)
|
||||||
return parsed_response
|
return parsed_response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
current_app.logger.error(e)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
14
spiffworkflow-frontend/package-lock.json
generated
14
spiffworkflow-frontend/package-lock.json
generated
@ -39,6 +39,7 @@
|
|||||||
"bpmn-js": "^9.3.2",
|
"bpmn-js": "^9.3.2",
|
||||||
"bpmn-js-properties-panel": "^1.10.0",
|
"bpmn-js-properties-panel": "^1.10.0",
|
||||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
||||||
|
"cookie": "^0.5.0",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"diagram-js": "^8.5.0",
|
"diagram-js": "^8.5.0",
|
||||||
@ -66,6 +67,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/grep": "^3.1.0",
|
"@cypress/grep": "^3.1.0",
|
||||||
|
"@types/cookie": "^0.5.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"@typescript-eslint/parser": "^5.30.6",
|
"@typescript-eslint/parser": "^5.30.6",
|
||||||
"cypress": "^12",
|
"cypress": "^12",
|
||||||
@ -5654,6 +5656,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.7",
|
"version": "4.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||||
@ -35330,6 +35338,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/cookie": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/debug": {
|
"@types/debug": {
|
||||||
"version": "4.1.7",
|
"version": "4.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"bpmn-js": "^9.3.2",
|
"bpmn-js": "^9.3.2",
|
||||||
"bpmn-js-properties-panel": "^1.10.0",
|
"bpmn-js-properties-panel": "^1.10.0",
|
||||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
||||||
|
"cookie": "^0.5.0",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"diagram-js": "^8.5.0",
|
"diagram-js": "^8.5.0",
|
||||||
@ -102,6 +103,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/grep": "^3.1.0",
|
"@cypress/grep": "^3.1.0",
|
||||||
|
"@types/cookie": "^0.5.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"@typescript-eslint/parser": "^5.30.6",
|
"@typescript-eslint/parser": "^5.30.6",
|
||||||
"cypress": "^12",
|
"cypress": "^12",
|
||||||
|
@ -12,7 +12,6 @@ if (/^\d+\./.test(hostname) || hostname === 'localhost') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let url = `${protocol}://${hostAndPort}/v1.0`;
|
let url = `${protocol}://${hostAndPort}/v1.0`;
|
||||||
// Allow overriding the backend base url with an environment variable at build time.
|
|
||||||
if (process.env.REACT_APP_BACKEND_BASE_URL) {
|
if (process.env.REACT_APP_BACKEND_BASE_URL) {
|
||||||
url = process.env.REACT_APP_BACKEND_BASE_URL;
|
url = process.env.REACT_APP_BACKEND_BASE_URL;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ const doRender = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
UserService.getAuthTokenFromParams();
|
UserService.loginIfNeeded();
|
||||||
doRender();
|
doRender();
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
@ -42,7 +42,7 @@ export default function AuthenticationList() {
|
|||||||
row.id
|
row.id
|
||||||
}?redirect_url=${redirectUrl}/${
|
}?redirect_url=${redirectUrl}/${
|
||||||
row.id
|
row.id
|
||||||
}?token=${UserService.getAuthToken()}`}
|
}?token=${UserService.getAccessToken()}`}
|
||||||
>
|
>
|
||||||
{row.id}
|
{row.id}
|
||||||
</a>
|
</a>
|
||||||
|
@ -73,11 +73,6 @@ export default function JsonSchemaFormBuilder() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onFormFieldTitleChange = (newFormFieldTitle: string) => {
|
const onFormFieldTitleChange = (newFormFieldTitle: string) => {
|
||||||
console.log('newFormFieldTitle', newFormFieldTitle);
|
|
||||||
console.log(
|
|
||||||
'setFormFieldIdHasBeenUpdatedByUser',
|
|
||||||
formFieldIdHasBeenUpdatedByUser
|
|
||||||
);
|
|
||||||
if (!formFieldIdHasBeenUpdatedByUser) {
|
if (!formFieldIdHasBeenUpdatedByUser) {
|
||||||
setFormFieldId(underscorizeString(newFormFieldTitle));
|
setFormFieldId(underscorizeString(newFormFieldTitle));
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,13 @@ export default function TaskShow() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function customValidate(formData: any, errors: any) {
|
||||||
|
if (formData.pass1 !== formData.pass2) {
|
||||||
|
errors.pass2.addError("Passwords don't match");
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid fullWidth condensed>
|
<Grid fullWidth condensed>
|
||||||
<Column md={5} lg={8} sm={4}>
|
<Column md={5} lg={8} sm={4}>
|
||||||
@ -193,6 +200,7 @@ export default function TaskShow() {
|
|||||||
schema={jsonSchema}
|
schema={jsonSchema}
|
||||||
uiSchema={formUiSchema}
|
uiSchema={formUiSchema}
|
||||||
validator={validator}
|
validator={validator}
|
||||||
|
customValidate={customValidate}
|
||||||
>
|
>
|
||||||
{reactFragmentToHideSubmitButton}
|
{reactFragmentToHideSubmitButton}
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -11,7 +11,7 @@ const HttpMethods = {
|
|||||||
const getBasicHeaders = (): object => {
|
const getBasicHeaders = (): object => {
|
||||||
if (UserService.isLoggedIn()) {
|
if (UserService.isLoggedIn()) {
|
||||||
return {
|
return {
|
||||||
Authorization: `Bearer ${UserService.getAuthToken()}`,
|
Authorization: `Bearer ${UserService.getAccessToken()}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@ -64,6 +64,7 @@ backendCallProps) => {
|
|||||||
Object.assign(httpArgs, {
|
Object.assign(httpArgs, {
|
||||||
headers: new Headers(headers as any),
|
headers: new Headers(headers as any),
|
||||||
method: httpMethod,
|
method: httpMethod,
|
||||||
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedPath = path.replace(/^\/v1\.0/, '');
|
const updatedPath = path.replace(/^\/v1\.0/, '');
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import jwt from 'jwt-decode';
|
import jwt from 'jwt-decode';
|
||||||
|
import cookie from 'cookie';
|
||||||
import { BACKEND_BASE_URL } from '../config';
|
import { BACKEND_BASE_URL } from '../config';
|
||||||
|
|
||||||
// NOTE: this currently stores the jwt token in local storage
|
// NOTE: this currently stores the jwt token in local storage
|
||||||
@ -10,37 +11,46 @@ import { BACKEND_BASE_URL } from '../config';
|
|||||||
// Some explanation:
|
// Some explanation:
|
||||||
// https://dev.to/nilanth/how-to-secure-jwt-in-a-single-page-application-cko
|
// https://dev.to/nilanth/how-to-secure-jwt-in-a-single-page-application-cko
|
||||||
|
|
||||||
// const getCurrentLocation = (queryParams: string = window.location.search) => {
|
const getCookie = (key: string) => {
|
||||||
const getCurrentLocation = () => {
|
const parsedCookies = cookie.parse(document.cookie);
|
||||||
const queryParamString = '';
|
if (key in parsedCookies) {
|
||||||
// if (queryParams) {
|
return parsedCookies[key];
|
||||||
// queryParamString = `?${queryParams}`;
|
}
|
||||||
// }
|
return null;
|
||||||
return `${window.location.origin}${window.location.pathname}${queryParamString}`;
|
};
|
||||||
|
|
||||||
|
const getCurrentLocation = (queryParams: string = window.location.search) => {
|
||||||
|
let queryParamString = '';
|
||||||
|
if (queryParams) {
|
||||||
|
queryParamString = `${queryParams}`;
|
||||||
|
}
|
||||||
|
return encodeURIComponent(
|
||||||
|
`${window.location.origin}${window.location.pathname}${queryParamString}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const doLogin = () => {
|
const doLogin = () => {
|
||||||
const url = `${BACKEND_BASE_URL}/login?redirect_url=${getCurrentLocation()}`;
|
const url = `${BACKEND_BASE_URL}/login?redirect_url=${getCurrentLocation()}`;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// required for logging out
|
||||||
const getIdToken = () => {
|
const getIdToken = () => {
|
||||||
return localStorage.getItem('jwtIdToken');
|
return getCookie('id_token');
|
||||||
};
|
};
|
||||||
|
|
||||||
const doLogout = () => {
|
const doLogout = () => {
|
||||||
const idToken = getIdToken();
|
const idToken = getIdToken();
|
||||||
localStorage.removeItem('jwtAccessToken');
|
|
||||||
localStorage.removeItem('jwtIdToken');
|
|
||||||
const redirectUrl = `${window.location.origin}`;
|
const redirectUrl = `${window.location.origin}`;
|
||||||
const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirectUrl}&id_token=${idToken}`;
|
const url = `${BACKEND_BASE_URL}/logout?redirect_url=${redirectUrl}&id_token=${idToken}`;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAuthToken = () => {
|
const getAccessToken = () => {
|
||||||
return localStorage.getItem('jwtAccessToken');
|
return getCookie('access_token');
|
||||||
};
|
};
|
||||||
const isLoggedIn = () => {
|
const isLoggedIn = () => {
|
||||||
return !!getAuthToken();
|
return !!getAccessToken();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserEmail = () => {
|
const getUserEmail = () => {
|
||||||
@ -61,24 +71,8 @@ const getPreferredUsername = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: we could probably change this search to a hook
|
const loginIfNeeded = () => {
|
||||||
// and then could use useSearchParams here instead
|
if (!isLoggedIn()) {
|
||||||
const getAuthTokenFromParams = () => {
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
|
||||||
const accessToken = queryParams.get('access_token');
|
|
||||||
const idToken = queryParams.get('id_token');
|
|
||||||
|
|
||||||
queryParams.delete('access_token');
|
|
||||||
queryParams.delete('id_token');
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
localStorage.setItem('jwtAccessToken', accessToken);
|
|
||||||
if (idToken) {
|
|
||||||
localStorage.setItem('jwtIdToken', idToken);
|
|
||||||
}
|
|
||||||
// window.location.href = `${getCurrentLocation(queryParams.toString())}`;
|
|
||||||
window.location.href = `${getCurrentLocation()}`;
|
|
||||||
} else if (!isLoggedIn()) {
|
|
||||||
doLogin();
|
doLogin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -91,8 +85,8 @@ const UserService = {
|
|||||||
doLogin,
|
doLogin,
|
||||||
doLogout,
|
doLogout,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
getAuthToken,
|
getAccessToken,
|
||||||
getAuthTokenFromParams,
|
loginIfNeeded,
|
||||||
getPreferredUsername,
|
getPreferredUsername,
|
||||||
getUserEmail,
|
getUserEmail,
|
||||||
hasRole,
|
hasRole,
|
||||||
|
@ -45,7 +45,6 @@ export default function BaseInputTemplate<
|
|||||||
// Note: since React 15.2.0 we can't forward unknown element attributes, so we
|
// Note: since React 15.2.0 we can't forward unknown element attributes, so we
|
||||||
// exclude the "options" and "schema" ones here.
|
// exclude the "options" and "schema" ones here.
|
||||||
if (!id) {
|
if (!id) {
|
||||||
console.log('No id for', props);
|
|
||||||
throw new Error(`no id for props ${JSON.stringify(props)}`);
|
throw new Error(`no id for props ${JSON.stringify(props)}`);
|
||||||
}
|
}
|
||||||
const inputProps = {
|
const inputProps = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user