From b00413817d5ed3207bcf778806312631d1329673 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 20 Feb 2025 05:17:45 +0400 Subject: [PATCH] fix: add service token read value permissions --- ...kfill-secret-permissions-with-readvalue.ts | 62 +++++++++++++++++++ .../AddServiceTokenModal.tsx | 8 ++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts b/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts index 216cf7ede..c42f4d7bc 100644 --- a/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts +++ b/backend/src/db/migrations/20250218020306_backfill-secret-permissions-with-readvalue.ts @@ -89,6 +89,37 @@ export async function up(knex: Knex): Promise { const projectUserAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select( selectAllTableCols(TableName.ProjectUserAdditionalPrivilege) ); + const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken)); + + const updatedServiceTokens = serviceTokens.reduce((acc, serviceToken) => { + const { permissions } = serviceToken; // Service tokens are special, and include an array of actions only. + + if (permissions.includes(SecretActions.Read) && !permissions.includes(SecretActions.ReadValue)) { + permissions.push(SecretActions.ReadValue); + acc.push({ + ...serviceToken, + permissions + }); + } + return acc; + }, []); + + if (updatedServiceTokens.length > 0) { + await knex(TableName.ServiceToken) + .whereIn( + "id", + updatedServiceTokens.map((t) => t.id) + ) + .update({ + // @ts-expect-error -- raw query + permissions: knex.raw( + `CASE id + ${updatedServiceTokens.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")} + END`, + updatedServiceTokens.map((t) => t.permissions) + ) + }); + } const updatedRoles = projectRoles.reduce((acc, projectRole) => { const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(projectRole.permissions); @@ -157,6 +188,37 @@ export async function down(knex: Knex): Promise { const userAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select( selectAllTableCols(TableName.ProjectUserAdditionalPrivilege) ); + const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken)); + + const updatedServiceTokens = serviceTokens.reduce((acc, serviceToken) => { + const { permissions } = serviceToken; + + if (permissions.includes(SecretActions.ReadValue)) { + permissions.splice(permissions.indexOf(SecretActions.ReadValue), 1); + acc.push({ + ...serviceToken, + permissions + }); + } + return acc; + }, []); + + if (updatedServiceTokens.length > 0) { + await knex(TableName.ServiceToken) + .whereIn( + "id", + updatedServiceTokens.map((t) => t.id) + ) + .update({ + // @ts-expect-error -- raw query + permissions: knex.raw( + `CASE id + ${updatedServiceTokens.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")} + END`, + updatedServiceTokens.map((t) => t.permissions) + ) + }); + } const updatedRoles = projectRoles.reduce((acc, projectRole) => { const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(projectRole.permissions); diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx index 46a9c5680..a669a8360 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx @@ -58,6 +58,7 @@ const schema = z.object({ permissions: z .object({ read: z.boolean(), + readValue: z.boolean(), write: z.boolean() }) .required() @@ -296,14 +297,19 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => { name="permissions" defaultValue={{ read: true, + readValue: false, write: false }} render={({ field: { onChange, value }, fieldState: { error } }) => { const options = [ { - label: "Read (default)", + label: "Describe Secret (default)", value: "read" }, + { + label: "Read Value (optional)", + value: "readValue" + }, { label: "Write (optional)", value: "write"