mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
feat: completed migration of permission v1 to v2. Pending intense testing
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { unpackRules } from "@casl/ability/extra";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const CHUNK_SIZE = 1000;
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (!hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
const docs = await knex(TableName.ProjectRoles).select("*");
|
||||||
|
const updatedDocs = docs
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (updatedDocs.length) {
|
||||||
|
for (let i = 0; i < updatedDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select("*");
|
||||||
|
const updatedIdentityPrivilegesDocs = identityPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (updatedIdentityPrivilegesDocs.length) {
|
||||||
|
for (let i = 0; i < updatedIdentityPrivilegesDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedIdentityPrivilegesDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select("*");
|
||||||
|
const updatedUserPriviegeDocs = userPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (docs.length) {
|
||||||
|
for (let i = 0; i < updatedUserPriviegeDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedUserPriviegeDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.dropColumn("version");
|
||||||
|
});
|
||||||
|
|
||||||
|
// permission change can be ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import ms from "ms";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@@ -79,7 +80,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -159,7 +162,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -244,7 +249,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
data: {
|
data: {
|
||||||
...updatedInfo,
|
...updatedInfo,
|
||||||
permissions: permission ? JSON.stringify(packRules(permission)) : undefined
|
permissions: permission
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(permission)))
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import { ProjectPermissionV1Schema } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionV1Schema
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -61,7 +64,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectSlug: req.params.projectSlug,
|
projectSlug: req.params.projectSlug,
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions)))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
@@ -122,7 +125,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: req.params.roleId,
|
roleId: req.params.roleId,
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
permissions: req.body.permissions
|
||||||
|
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions)))
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { ProjectSpecificPrivilegePermissionSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -31,7 +34,9 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
permissions: ProjectSpecificPrivilegePermissionSchema.describe(
|
||||||
|
PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -49,7 +54,17 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
permissions: JSON.stringify(req.body.permissions)
|
permissions: JSON.stringify(
|
||||||
|
packRules(
|
||||||
|
backfillPermissionV1SchemaToV2Schema(
|
||||||
|
req.body.permissions.actions.map((action) => ({
|
||||||
|
action,
|
||||||
|
subject: req.body.permissions.subject,
|
||||||
|
conditions: req.body.permissions.conditions
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -75,7 +90,9 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
permissions: ProjectSpecificPrivilegePermissionSchema.describe(
|
||||||
|
PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions
|
||||||
|
),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
@@ -104,7 +121,17 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
permissions: JSON.stringify(req.body.permissions)
|
permissions: JSON.stringify(
|
||||||
|
packRules(
|
||||||
|
backfillPermissionV1SchemaToV2Schema(
|
||||||
|
req.body.permissions.actions.map((action) => ({
|
||||||
|
action,
|
||||||
|
subject: req.body.permissions.subject,
|
||||||
|
conditions: req.body.permissions.conditions
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -131,7 +158,9 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
message: "Slug must be a valid slug"
|
message: "Slug must be a valid slug"
|
||||||
})
|
})
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
permissions: ProjectSpecificPrivilegePermissionSchema.describe(
|
||||||
|
PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions
|
||||||
|
).optional(),
|
||||||
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
@@ -160,7 +189,19 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
permissions: req.body.permissions
|
||||||
|
? JSON.stringify(
|
||||||
|
packRules(
|
||||||
|
backfillPermissionV1SchemaToV2Schema(
|
||||||
|
req.body.permissions.actions.map((action) => ({
|
||||||
|
action,
|
||||||
|
subject: req.body.permissions!.subject,
|
||||||
|
conditions: req.body.permissions!.conditions
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
privilegeId: req.params.privilegeId
|
privilegeId: req.params.privilegeId
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
|||||||
+1
-12
@@ -1,10 +1,10 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -33,15 +33,6 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
// TODO(akhilmhdh): move this to more centralized
|
// TODO(akhilmhdh): move this to more centralized
|
||||||
export const UnpackedPermissionSchema = z.object({
|
|
||||||
subject: z
|
|
||||||
.union([z.string().min(1), z.string().array()])
|
|
||||||
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
|
||||||
.optional(),
|
|
||||||
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
|
||||||
conditions: z.unknown().optional(),
|
|
||||||
inverted: z.boolean().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
const unpackPermissions = (permissions: unknown) =>
|
||||||
UnpackedPermissionSchema.array().parse(
|
UnpackedPermissionSchema.array().parse(
|
||||||
@@ -204,7 +195,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -325,7 +315,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return identityPrivileges.map((el) => ({
|
return identityPrivileges.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
|
|
||||||
permissions: unpackPermissions(el.permissions)
|
permissions: unpackPermissions(el.permissions)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
|
||||||
import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types";
|
import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types";
|
||||||
|
|
||||||
@@ -752,17 +751,35 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
};
|
};
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
export const SecretV2SubjectFieldMapper = (arg: string) => {
|
export const backfillPermissionV1SchemaToV2Schema = (data: z.infer<typeof ProjectPermissionV1Schema>[]) => {
|
||||||
switch (arg) {
|
const formattedData = UnpackedPermissionSchema.array().parse(data);
|
||||||
case "environment":
|
const secretSubjects = formattedData.filter((el) => el.subject === ProjectPermissionSub.Secrets);
|
||||||
return null;
|
|
||||||
case "secretPath":
|
// this means the folder permission as readonly is set
|
||||||
return null;
|
const hasReadOnlyFolder = formattedData.filter((el) => el.subject === ProjectPermissionSub.SecretFolders);
|
||||||
case "secretName":
|
const secretImportPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
||||||
return `${TableName.SecretV2}.key`;
|
...el,
|
||||||
case "secretTags":
|
subject: ProjectPermissionSub.SecretImports as const
|
||||||
return `${TableName.SecretTag}.slug`;
|
}));
|
||||||
default:
|
|
||||||
throw new BadRequestError({ message: `Invalid dynamic knex operator field: ${arg}` });
|
const secretFolderPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
||||||
}
|
...el,
|
||||||
|
subject: ProjectPermissionSub.DynamicSecrets
|
||||||
|
}));
|
||||||
|
|
||||||
|
const dynamicSecretPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
action: el.action.includes(ProjectPermissionActions.Edit)
|
||||||
|
? [...el.action, ProjectPermissionDynamicSecretActions.Lease]
|
||||||
|
: el.action,
|
||||||
|
subject: ProjectPermissionSub.DynamicSecrets
|
||||||
|
}));
|
||||||
|
|
||||||
|
return formattedData.concat(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
secretImportPolicies,
|
||||||
|
dynamicSecretPolicies,
|
||||||
|
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+34
-8
@@ -1,11 +1,13 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSet, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||||
import {
|
import {
|
||||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||||
@@ -26,6 +28,11 @@ export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
|||||||
typeof projectUserAdditionalPrivilegeServiceFactory
|
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const unpackPermissions = (permissions: unknown) =>
|
||||||
|
UnpackedPermissionSchema.array().parse(
|
||||||
|
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||||
|
);
|
||||||
|
|
||||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
@@ -82,7 +89,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateById = async ({
|
const updateById = async ({
|
||||||
@@ -131,7 +141,11 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
@@ -142,7 +156,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
temporaryRange: null,
|
temporaryRange: null,
|
||||||
temporaryMode: null
|
temporaryMode: null
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||||
@@ -165,7 +182,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
return deletedPrivilege;
|
return {
|
||||||
|
...deletedPrivilege,
|
||||||
|
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPrivilegeDetailsById = async ({
|
const getPrivilegeDetailsById = async ({
|
||||||
@@ -193,7 +213,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
return userPrivilege;
|
return {
|
||||||
|
...userPrivilege,
|
||||||
|
permissions: unpackPermissions(userPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const listPrivileges = async ({
|
const listPrivileges = async ({
|
||||||
@@ -219,7 +242,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
userId: projectMembership.userId,
|
userId: projectMembership.userId,
|
||||||
projectId: projectMembership.projectId
|
projectId: projectMembership.projectId
|
||||||
});
|
});
|
||||||
return userPrivileges;
|
return userPrivileges.map((el) => ({
|
||||||
|
...el,
|
||||||
|
permissions: unpackPermissions(el.permissions)
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
SecretApprovalPoliciesSchema,
|
SecretApprovalPoliciesSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
import { UnpackedPermissionSchema } from "./santizedSchemas/permission";
|
||||||
|
|
||||||
// sometimes the return data must be santizied to avoid leaking important values
|
// sometimes the return data must be santizied to avoid leaking important values
|
||||||
// always prefer pick over omit in zod
|
// always prefer pick over omit in zod
|
||||||
export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const UnpackedPermissionSchema = z.object({
|
||||||
|
subject: z
|
||||||
|
.union([z.string().min(1), z.string().array()])
|
||||||
|
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
||||||
|
.optional(),
|
||||||
|
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
||||||
|
conditions: z.unknown().optional(),
|
||||||
|
inverted: z.boolean().optional()
|
||||||
|
});
|
||||||
@@ -2,7 +2,6 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
|||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -10,6 +9,7 @@ import {
|
|||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
|
||||||
import { ActorAuthMethod } from "../auth/auth-type";
|
import { ActorAuthMethod } from "../auth/auth-type";
|
||||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
@@ -16,10 +15,7 @@ export const useCreateProjectUserAdditionalPrivilege = () => {
|
|||||||
|
|
||||||
return useMutation<{ privilege: TProjectUserPrivilege }, {}, TCreateProjectUserPrivilegeDTO>({
|
return useMutation<{ privilege: TProjectUserPrivilege }, {}, TCreateProjectUserPrivilegeDTO>({
|
||||||
mutationFn: async (dto) => {
|
mutationFn: async (dto) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/additional-privilege/users/permanent", {
|
const { data } = await apiRequest.post("/api/v1/additional-privilege/users/permanent", dto);
|
||||||
...dto,
|
|
||||||
permissions: packRules(dto.permissions)
|
|
||||||
});
|
|
||||||
return data.privilege;
|
return data.privilege;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectMembershipId }) => {
|
onSuccess: (_, { projectMembershipId }) => {
|
||||||
@@ -35,7 +31,7 @@ export const useUpdateProjectUserAdditionalPrivilege = () => {
|
|||||||
mutationFn: async (dto) => {
|
mutationFn: async (dto) => {
|
||||||
const { data } = await apiRequest.patch(
|
const { data } = await apiRequest.patch(
|
||||||
`/api/v1/additional-privilege/users/${dto.privilegeId}`,
|
`/api/v1/additional-privilege/users/${dto.privilegeId}`,
|
||||||
{ ...dto, permissions: dto.permissions ? packRules(dto.permissions) : undefined }
|
dto
|
||||||
);
|
);
|
||||||
return data.privilege;
|
return data.privilege;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
@@ -18,10 +17,7 @@ const fetchProjectUserPrivilegeDetails = async (privilegeId: string) => {
|
|||||||
} = await apiRequest.get<{
|
} = await apiRequest.get<{
|
||||||
privilege: Omit<TProjectUserPrivilege, "permissions"> & { permissions: unknown };
|
privilege: Omit<TProjectUserPrivilege, "permissions"> & { permissions: unknown };
|
||||||
}>(`/api/v1/additional-privilege/users/${privilegeId}`);
|
}>(`/api/v1/additional-privilege/users/${privilegeId}`);
|
||||||
return {
|
return privilege;
|
||||||
...privilege,
|
|
||||||
permissions: unpackRules(privilege.permissions as PackRule<TProjectPermission>[])
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetProjectUserPrivilegeDetails = (privilegeId: string) => {
|
export const useGetProjectUserPrivilegeDetails = (privilegeId: string) => {
|
||||||
@@ -44,7 +40,7 @@ export const useListProjectUserPrivileges = (projectMembershipId: string) => {
|
|||||||
}>("/api/v1/additional-privilege/users", { params: { projectMembershipId } });
|
}>("/api/v1/additional-privilege/users", { params: { projectMembershipId } });
|
||||||
return privileges.map((el) => ({
|
return privileges.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
permissions: unpackRules(el.permissions as PackRule<TProjectPermission>[])
|
permissions: el.permissions as TProjectPermission[]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,15 @@ export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
|||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TProjectSpecificPrivilegePermission = {
|
||||||
|
conditions: {
|
||||||
|
environment: string;
|
||||||
|
secretPath?: { $glob: string };
|
||||||
|
};
|
||||||
|
actions: string[];
|
||||||
|
subject: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TProjectUserPrivilege = {
|
export type TProjectUserPrivilege = {
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -12,21 +21,21 @@ export type TProjectUserPrivilege = {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
permissions?: TProjectPermission[];
|
permissions?: TProjectPermission[];
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
isTemporary: true;
|
isTemporary: true;
|
||||||
temporaryMode: string;
|
temporaryMode: string;
|
||||||
temporaryRange: string;
|
temporaryRange: string;
|
||||||
temporaryAccessStartTime: string;
|
temporaryAccessStartTime: string;
|
||||||
temporaryAccessEndTime?: string;
|
temporaryAccessEndTime?: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
isTemporary: false;
|
isTemporary: false;
|
||||||
temporaryMode?: null;
|
temporaryMode?: null;
|
||||||
temporaryRange?: null;
|
temporaryRange?: null;
|
||||||
temporaryAccessStartTime?: null;
|
temporaryAccessStartTime?: null;
|
||||||
temporaryAccessEndTime?: null;
|
temporaryAccessEndTime?: null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TCreateProjectUserPrivilegeDTO = {
|
export type TCreateProjectUserPrivilegeDTO = {
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
@@ -35,7 +44,7 @@ export type TCreateProjectUserPrivilegeDTO = {
|
|||||||
temporaryMode?: ProjectUserAdditionalPrivilegeTemporaryMode;
|
temporaryMode?: ProjectUserAdditionalPrivilegeTemporaryMode;
|
||||||
temporaryRange?: string;
|
temporaryRange?: string;
|
||||||
temporaryAccessStartTime?: string;
|
temporaryAccessStartTime?: string;
|
||||||
permissions: TProjectPermission[];
|
permissions: TProjectSpecificPrivilegePermission;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateProjectUserPrivlegeDTO = {
|
export type TUpdateProjectUserPrivlegeDTO = {
|
||||||
|
|||||||
+14
-16
@@ -184,20 +184,20 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
{ action: ProjectPermissionActions.Delete, allowed: data.delete },
|
{ action: ProjectPermissionActions.Delete, allowed: data.delete },
|
||||||
{ action: ProjectPermissionActions.Edit, allowed: data.edit }
|
{ action: ProjectPermissionActions.Edit, allowed: data.edit }
|
||||||
];
|
];
|
||||||
const conditions: Record<string, any> = { environment: data.environmentSlug };
|
const conditions: { environment: string; secretPath?: { $glob: string } } = {
|
||||||
|
environment: data.environmentSlug
|
||||||
|
};
|
||||||
if (data.secretPath) {
|
if (data.secretPath) {
|
||||||
conditions.secretPath = { $glob: removeTrailingSlash(data.secretPath) };
|
conditions.secretPath = { $glob: removeTrailingSlash(data.secretPath) };
|
||||||
}
|
}
|
||||||
await updateUserPrivilege.mutateAsync({
|
await updateUserPrivilege.mutateAsync({
|
||||||
privilegeId: privilege.id,
|
privilegeId: privilege.id,
|
||||||
...data.temporaryAccess,
|
...data.temporaryAccess,
|
||||||
permissions: actions
|
permissions: {
|
||||||
.filter(({ allowed }) => allowed)
|
subject: ProjectPermissionSub.Secrets,
|
||||||
.map(({ action }) => ({
|
conditions,
|
||||||
action,
|
actions: actions.filter((i) => i.allowed).map((i) => i.action)
|
||||||
subject: [ProjectPermissionSub.Secrets],
|
},
|
||||||
conditions
|
|
||||||
})),
|
|
||||||
projectMembershipId: privilege.projectMembershipId
|
projectMembershipId: privilege.projectMembershipId
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -642,15 +642,13 @@ export const SpecificPrivilegeSection = ({ membershipId }: Props) => {
|
|||||||
if (createUserPrivilege.isLoading) return;
|
if (createUserPrivilege.isLoading) return;
|
||||||
try {
|
try {
|
||||||
await createUserPrivilege.mutateAsync({
|
await createUserPrivilege.mutateAsync({
|
||||||
permissions: [
|
permissions: {
|
||||||
{
|
actions: [ProjectPermissionActions.Read],
|
||||||
action: ProjectPermissionActions.Read,
|
subject: ProjectPermissionSub.Secrets,
|
||||||
subject: [ProjectPermissionSub.Secrets],
|
conditions: {
|
||||||
conditions: {
|
environment: currentWorkspace?.environments?.[0].slug || ""
|
||||||
environment: currentWorkspace?.environments?.[0].slug
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
projectMembershipId: membershipId
|
projectMembershipId: membershipId
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
Reference in New Issue
Block a user