Auth for secrets (#1369)

* added new api to show secrets so we can use that in permissions

* updated frontend to use new secret show value api

* cleaned up secret_show method

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-04-09 17:44:47 +00:00 committed by GitHub
parent 9458500b8a
commit 293aa867a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 139 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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(

View File

@ -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 };

View File

@ -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 {

View File

@ -98,7 +98,7 @@ export default function Configuration({ extensionUxElements }: OwnProps) {
<Route path="/" element={<SecretList />} />
<Route path="secrets" element={<SecretList />} />
<Route path="secrets/new" element={<SecretNew />} />
<Route path="secrets/:key" element={<SecretShow />} />
<Route path="secrets/:secret_identifier" element={<SecretShow />} />
<Route path="authentications" element={<AuthenticationList />} />
<Route
path="extension/:page_identifier"

View File

@ -3,9 +3,12 @@ import { useParams, useNavigate } from 'react-router-dom';
// @ts-ignore
import { Stack, Table, Button, TextInput } from '@carbon/react';
import HttpService from '../services/HttpService';
import { Secret } from '../interfaces';
import { PermissionsToCheck, Secret } from '../interfaces';
import { Notification } from '../components/Notification';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import { usePermissionFetcher } from '../hooks/PermissionService';
import { Can } from '../contexts/Can';
export default function SecretShow() {
const navigate = useNavigate();
@ -16,12 +19,21 @@ export default function SecretShow() {
const [showSuccessNotification, setShowSuccessNotification] =
useState<boolean>(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}
<h1>Secret Key: {secret.key}</h1>
<Stack orientation="horizontal" gap={3}>
<ButtonWithConfirmation
description="Delete Secret?"
onConfirmation={deleteSecret}
buttonLabel="Delete"
/>
<Button
disabled={displaySecretValue}
variant="warning"
onClick={() => {
setDisplaySecretValue(true);
}}
<Can I="DELETE" a={targetUris.secretShowPath} ability={ability}>
<ButtonWithConfirmation
description="Delete Secret?"
onConfirmation={deleteSecret}
buttonLabel="Delete"
/>
</Can>
<Can
I="GET"
a={targetUris.secretShowValuePath}
ability={ability}
passThrough
>
Retrieve secret value
</Button>
{(secretReadAllowed: boolean) => {
if (secretReadAllowed) {
return (
<Button
disabled={displaySecretValue}
variant="warning"
onClick={handleShowSecretValue}
>
Retrieve secret value
</Button>
);
}
return (
<Can I="PUT" a={targetUris.secretShowPath} ability={ability}>
<Button
disabled={displaySecretValue}
variant="warning"
onClick={() => setDisplaySecretValue(true)}
>
Edit secret value
</Button>
</Can>
);
}}
</Can>
</Stack>
<div>
<Table striped bordered>
@ -103,7 +152,7 @@ export default function SecretShow() {
</thead>
<tbody>
<tr>
<td>{params.key}</td>
<td>{params.secret_identifier}</td>
{displaySecretValue && (
<>
<td aria-label="Secret value">
@ -112,14 +161,23 @@ export default function SecretShow() {
name="secret_value"
value={secret.value}
onChange={handleSecretValueChange}
disabled={
!ability.can('PUT', targetUris.secretShowPath)
}
/>
</td>
<td>
{displaySecretValue && (
<Button variant="warning" onClick={updateSecretValue}>
Update Value
</Button>
)}
<Can
I="PUT"
a={targetUris.secretShowPath}
ability={ability}
>
{displaySecretValue && (
<Button variant="warning" onClick={updateSecretValue}>
Update Value
</Button>
)}
</Can>
</td>
</>
)}