diff --git a/spiffworkflow-backend/bin/boot_server_in_docker b/spiffworkflow-backend/bin/boot_server_in_docker index 1c306a08..462a2812 100755 --- a/spiffworkflow-backend/bin/boot_server_in_docker +++ b/spiffworkflow-backend/bin/boot_server_in_docker @@ -17,6 +17,10 @@ function log_info() { # run migrations export FLASK_APP=/app/src/spiffworkflow_backend +if [[ -z "${FLASK_DEBUG:-}" ]]; then + export FLASK_DEBUG=0 +fi + if [[ "${SPIFFWORKFLOW_BACKEND_WAIT_FOR_DB_TO_BE_READY:-}" == "true" ]]; then echo 'Waiting for db to be ready...' poetry run python ./bin/wait_for_db_to_be_ready.py diff --git a/spiffworkflow-backend/bin/local_development_environment_setup b/spiffworkflow-backend/bin/local_development_environment_setup index 270728e0..a4c68ee3 100755 --- a/spiffworkflow-backend/bin/local_development_environment_setup +++ b/spiffworkflow-backend/bin/local_development_environment_setup @@ -68,6 +68,7 @@ if [[ -z "${SPIFFWORKFLOW_BACKEND_ENV:-}" ]]; then fi export FLASK_SESSION_SECRET_KEY="e7711a3ba96c46c68e084a86952de16f" +export FLASK_DEBUG=1 if [[ -z "${SPIFFWORKFLOW_BACKEND_RUN_BACKGROUND_SCHEDULER_IN_CREATE_APP:-}" ]]; then SPIFFWORKFLOW_BACKEND_RUN_BACKGROUND_SCHEDULER_IN_CREATE_APP=true diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 942185d1..d8209cf7 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -2954,7 +2954,7 @@ paths: type: string get: operationId: spiffworkflow_backend.routes.secrets_controller.secret_show - summary: Return a secret value for a key + summary: Return info about a secret for a key. Does not include the value. tags: - Secrets responses: @@ -2998,6 +2998,27 @@ paths: "404": description: Secret does not exist + /secrets/show-value/{key}: + parameters: + - name: key + in: path + required: true + description: The key we are using + schema: + type: string + get: + operationId: spiffworkflow_backend.routes.secrets_controller.secret_show_value + summary: Return a secret value for a key + tags: + - Secrets + responses: + "200": + description: We return a secret + content: + application/json: + schema: + $ref: "#/components/schemas/Secret" + /permissions-check: post: operationId: spiffworkflow_backend.routes.process_api_blueprint.permissions_check diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/secrets_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/secrets_controller.py index f604b55d..dc54d7cc 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/secrets_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/secrets_controller.py @@ -16,6 +16,11 @@ from spiffworkflow_backend.services.user_service import UserService def secret_show(key: str) -> Response: secret = SecretService.get_secret(key) + return make_response(jsonify(secret), 200) + + +def secret_show_value(key: str) -> Response: + secret = SecretService.get_secret(key) # normal serialization does not include the secret value, but this is the one endpoint where we want to return the goods secret_as_dict = secret.serialized() diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secrets_controller.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secrets_controller.py index 6f4569e1..54d20603 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secrets_controller.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_secrets_controller.py @@ -40,7 +40,7 @@ class TestSecretsController(SecretServiceTestHelpers): assert SecretService._decrypt(secret["value"]) == self.test_value assert secret["user_id"] == with_super_admin_user.id - def test_get_secret( + def test_get_secret_api( self, app: Flask, client: FlaskClient, @@ -56,6 +56,24 @@ class TestSecretsController(SecretServiceTestHelpers): assert secret_response assert secret_response.status_code == 200 assert secret_response.json + assert "value" not in secret_response.json + + def test_get_secret_value( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test get secret.""" + self.add_test_secret(with_super_admin_user) + secret_response = client.get( + f"/v1.0/secrets/show-value/{self.test_key}", + headers=self.logged_in_headers(with_super_admin_user), + ) + assert secret_response + assert secret_response.status_code == 200 + assert secret_response.json assert SecretService._decrypt(secret_response.json["value"]) == self.test_value def test_update_secret( diff --git a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx index 45780f91..a15eeab8 100644 --- a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx +++ b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx @@ -37,15 +37,18 @@ export const useUriListForPermissions = () => { processModelShowPath: `/v1.0/process-models/${params.process_model_id}`, processModelTestsPath: `/v1.0/process-model-tests/run/${params.process_model_id}`, secretListPath: `/v1.0/secrets`, + secretShowPath: `/v1.0/secrets/${params.secret_identifier}`, + secretShowValuePath: `/v1.0/secrets/show-value/${params.secret_identifier}`, userSearch: `/v1.0/users/search`, userExists: `/v1.0/users/exists/by-username`, }; }, [ - params.process_model_id, + params.secret_identifier, params.file_name, + params.page_identifier, params.process_group_id, params.process_instance_id, - params.page_identifier, + params.process_model_id, ]); return { targetUris }; diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 289b9b97..fd8d8b1a 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -16,8 +16,8 @@ export interface ApiActions { export interface Secret { id: number; key: string; - value: string; creator_user_id: string; + value?: string; } export interface Onboarding { diff --git a/spiffworkflow-frontend/src/routes/Configuration.tsx b/spiffworkflow-frontend/src/routes/Configuration.tsx index 3c989fdb..6dd78b1b 100644 --- a/spiffworkflow-frontend/src/routes/Configuration.tsx +++ b/spiffworkflow-frontend/src/routes/Configuration.tsx @@ -98,7 +98,7 @@ export default function Configuration({ extensionUxElements }: OwnProps) { } /> } /> } /> - } /> + } /> } /> (false); + const { targetUris } = useUriListForPermissions(); + const permissionRequestData: PermissionsToCheck = { + [targetUris.secretShowPath]: ['PUT', 'DELETE', 'GET'], + [targetUris.secretShowValuePath]: ['GET'], + }; + const { ability, permissionsLoaded } = usePermissionFetcher( + permissionRequestData + ); + useEffect(() => { HttpService.makeCallToBackend({ - path: `/secrets/${params.key}`, + path: `/secrets/${params.secret_identifier}`, successCallback: setSecret, }); - }, [params.key]); + }, [params.secret_identifier]); const handleSecretValueChange = (event: any) => { if (secret) { @@ -67,26 +79,63 @@ export default function SecretShow() { /> ); - if (secret) { + const handleShowSecretValue = () => { + if (secret === null) { + return; + } + HttpService.makeCallToBackend({ + path: `/secrets/show-value/${secret.key}`, + successCallback: (result: Secret) => { + setSecret(result); + setDisplaySecretValue(true); + }, + }); + }; + + if (secret && permissionsLoaded) { return ( <> {showSuccessNotification && successNotificationComponent}

Secret Key: {secret.key}

- - + {(secretReadAllowed: boolean) => { + if (secretReadAllowed) { + return ( + + ); + } + return ( + + + + ); + }} +
@@ -103,7 +152,7 @@ export default function SecretShow() { - + {displaySecretValue && ( <> )}
{params.key}{params.secret_identifier} @@ -112,14 +161,23 @@ export default function SecretShow() { name="secret_value" value={secret.value} onChange={handleSecretValueChange} + disabled={ + !ability.can('PUT', targetUris.secretShowPath) + } /> - {displaySecretValue && ( - - )} + + {displaySecretValue && ( + + )} +