mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
Merge pull request #4980 from Infisical/feat/PKI-67
feature: refine PKI access control permissions
This commit is contained in:
@@ -58,3 +58,4 @@ docs/documentation/platform/pki/enrollment-methods/api.mdx:private-key:139
|
|||||||
docs/documentation/platform/pki/certificate-syncs/aws-secrets-manager.mdx:private-key:62
|
docs/documentation/platform/pki/certificate-syncs/aws-secrets-manager.mdx:private-key:62
|
||||||
docs/documentation/platform/pki/certificate-syncs/chef.mdx:private-key:61
|
docs/documentation/platform/pki/certificate-syncs/chef.mdx:private-key:61
|
||||||
backend/src/services/certificate-request/certificate-request-service.test.ts:private-key:246
|
backend/src/services/certificate-request/certificate-request-service.test.ts:private-key:246
|
||||||
|
backend/src/services/certificate-request/certificate-request-service.test.ts:private-key:248
|
||||||
+5
-2
@@ -4,7 +4,10 @@ import * as x509 from "@peculiar/x509";
|
|||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
import { expandInternalCa } from "@app/services/certificate-authority/certificate-authority-fns";
|
import { expandInternalCa } from "@app/services/certificate-authority/certificate-authority-fns";
|
||||||
@@ -83,7 +86,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
ProjectPermissionAppConnectionActions,
|
ProjectPermissionAppConnectionActions,
|
||||||
ProjectPermissionAuditLogsActions,
|
ProjectPermissionAuditLogsActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionCommitsActions,
|
ProjectPermissionCommitsActions,
|
||||||
@@ -45,7 +46,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSub.Environments,
|
ProjectPermissionSub.Environments,
|
||||||
ProjectPermissionSub.Tags,
|
ProjectPermissionSub.Tags,
|
||||||
ProjectPermissionSub.IpAllowList,
|
ProjectPermissionSub.IpAllowList,
|
||||||
ProjectPermissionSub.CertificateAuthorities,
|
|
||||||
ProjectPermissionSub.PkiAlerts,
|
ProjectPermissionSub.PkiAlerts,
|
||||||
ProjectPermissionSub.PkiCollections,
|
ProjectPermissionSub.PkiCollections,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities,
|
ProjectPermissionSub.SshCertificateAuthorities,
|
||||||
@@ -68,6 +68,18 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can([ProjectPermissionAuditLogsActions.Read], ProjectPermissionSub.AuditLogs);
|
can([ProjectPermissionAuditLogsActions.Read], ProjectPermissionSub.AuditLogs);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Create,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Delete,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Renew,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.SignIntermediate
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
|
);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionPkiTemplateActions.Read,
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
@@ -96,7 +108,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionCertificateActions.Edit,
|
ProjectPermissionCertificateActions.Edit,
|
||||||
ProjectPermissionCertificateActions.Create,
|
ProjectPermissionCertificateActions.Create,
|
||||||
ProjectPermissionCertificateActions.Delete,
|
ProjectPermissionCertificateActions.Delete,
|
||||||
ProjectPermissionCertificateActions.ReadPrivateKey
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
ProjectPermissionCertificateActions.Import
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
@@ -461,7 +474,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
// double check if all CRUD are needed for CA and Certificates
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
|
can([ProjectPermissionCertificateAuthorityActions.Read], ProjectPermissionSub.CertificateAuthorities);
|
||||||
can([ProjectPermissionPkiTemplateActions.Read], ProjectPermissionSub.CertificateTemplates);
|
can([ProjectPermissionPkiTemplateActions.Read], ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
@@ -469,7 +482,8 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionCertificateActions.Read,
|
ProjectPermissionCertificateActions.Read,
|
||||||
ProjectPermissionCertificateActions.Edit,
|
ProjectPermissionCertificateActions.Edit,
|
||||||
ProjectPermissionCertificateActions.Create,
|
ProjectPermissionCertificateActions.Create,
|
||||||
ProjectPermissionCertificateActions.Delete
|
ProjectPermissionCertificateActions.Delete,
|
||||||
|
ProjectPermissionCertificateActions.Import
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
@@ -600,7 +614,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
can(ProjectPermissionAuditLogsActions.Read, ProjectPermissionSub.AuditLogs);
|
can(ProjectPermissionAuditLogsActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
can(ProjectPermissionCertificateAuthorityActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
||||||
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
|
||||||
can(ProjectPermissionPkiTemplateActions.Read, ProjectPermissionSub.CertificateTemplates);
|
can(ProjectPermissionPkiTemplateActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||||
|
|||||||
@@ -23,12 +23,22 @@ export enum ProjectPermissionCommitsActions {
|
|||||||
PerformRollback = "perform-rollback"
|
PerformRollback = "perform-rollback"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionCertificateAuthorityActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
Renew = "renew",
|
||||||
|
SignIntermediate = "sign-intermediate"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionCertificateActions {
|
export enum ProjectPermissionCertificateActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
ReadPrivateKey = "read-private-key"
|
ReadPrivateKey = "read-private-key",
|
||||||
|
Import = "import"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
export enum ProjectPermissionSecretActions {
|
||||||
@@ -292,7 +302,8 @@ export type SecretSyncSubjectFields = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PkiSyncSubjectFields = {
|
export type PkiSyncSubjectFields = {
|
||||||
subscriberName: string;
|
subscriberName?: string;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DynamicSecretSubjectFields = {
|
export type DynamicSecretSubjectFields = {
|
||||||
@@ -332,6 +343,26 @@ export type PkiSubscriberSubjectFields = {
|
|||||||
// (dangtony98): consider adding [commonName] as a subject field in the future
|
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CertificateAuthoritySubjectFields = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateSubjectFields = {
|
||||||
|
commonName?: string;
|
||||||
|
altNames?: string;
|
||||||
|
serialNumber?: string;
|
||||||
|
friendlyName?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateProfileSubjectFields = {
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateTemplateV2SubjectFields = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AppConnectionSubjectFields = {
|
export type AppConnectionSubjectFields = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
};
|
};
|
||||||
@@ -399,8 +430,17 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [
|
||||||
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.CertificateAuthorities
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.CertificateAuthorities> & CertificateAuthoritySubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionSub.Certificates | (ForcedSubject<ProjectPermissionSub.Certificates> & CertificateSubjectFields)
|
||||||
|
]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
(
|
(
|
||||||
@@ -454,7 +494,13 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionSub.PamAccounts | (ForcedSubject<ProjectPermissionSub.PamAccounts> & PamAccountSubjectFields)
|
ProjectPermissionSub.PamAccounts | (ForcedSubject<ProjectPermissionSub.PamAccounts> & PamAccountSubjectFields)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions]
|
| [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions]
|
||||||
| [ProjectPermissionCertificateProfileActions, ProjectPermissionSub.CertificateProfiles];
|
| [
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.CertificateProfiles
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> & CertificateProfileSubjectFields)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||||
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
|
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
|
||||||
@@ -572,6 +618,17 @@ const SecretSyncConditionV2Schema = z
|
|||||||
|
|
||||||
const PkiSyncConditionSchema = z
|
const PkiSyncConditionSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
name: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
subscriberName: z.union([
|
subscriberName: z.union([
|
||||||
z.string(),
|
z.string(),
|
||||||
z
|
z
|
||||||
@@ -698,6 +755,7 @@ const PkiTemplateConditionSchema = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
})
|
})
|
||||||
@@ -749,6 +807,98 @@ const PamAccountConditionSchema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
const CertificateAuthorityConditionSchema = z
|
||||||
|
.object({
|
||||||
|
name: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
const CertificateConditionSchema = z
|
||||||
|
.object({
|
||||||
|
commonName: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
altNames: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
serialNumber: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
friendlyName: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
status: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
const CertificateProfileConditionSchema = z
|
||||||
|
.object({
|
||||||
|
slug: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
const GeneralPermissionSchema = [
|
const GeneralPermissionSchema = [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
@@ -828,18 +978,6 @@ const GeneralPermissionSchema = [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
|
||||||
subject: z.literal(ProjectPermissionSub.CertificateAuthorities).describe("The entity this permission pertains to."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
|
||||||
"Describe what action an entity can take."
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateActions).describe(
|
|
||||||
"Describe what action an entity can take."
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
z.object({
|
||||||
subject: z
|
subject: z
|
||||||
.literal(ProjectPermissionSub.SshCertificateAuthorities)
|
.literal(ProjectPermissionSub.SshCertificateAuthorities)
|
||||||
@@ -1130,7 +1268,30 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateProfileActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateProfileActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
),
|
||||||
|
conditions: CertificateProfileConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.CertificateAuthorities).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateAuthorityActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: CertificateAuthorityConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: CertificateConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
}),
|
}),
|
||||||
...GeneralPermissionSchema
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import type { MongoAbility, MongoQuery, RawRuleOf } from "@casl/ability";
|
||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
|
export interface PermissionFilterConfig {
|
||||||
|
operator: string;
|
||||||
|
value: unknown;
|
||||||
|
isPattern: boolean;
|
||||||
|
isInverted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionFilters = Record<string, Array<PermissionFilterConfig>>;
|
||||||
|
|
||||||
|
export interface ProcessedPermissionRules {
|
||||||
|
allowRules: Array<Record<string, Array<PermissionFilterConfig>>>;
|
||||||
|
forbidRules: Array<Record<string, Array<PermissionFilterConfig>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoRegexFilter {
|
||||||
|
$regex: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoEqFilter {
|
||||||
|
$eq: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoInFilter {
|
||||||
|
$in: unknown[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoNeFilter {
|
||||||
|
$ne: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoGlobFilter {
|
||||||
|
$glob: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds permission filters from CASL MongoDB-style conditions
|
||||||
|
* @param conditions - MongoDB-style conditions from CASL ability
|
||||||
|
* @param isInverted - Whether this rule is inverted (forbidden)
|
||||||
|
* @returns Record of field names to arrays of filter configurations
|
||||||
|
*/
|
||||||
|
const buildPermissionFiltersFromConditions = (conditions: MongoQuery, isInverted = false): PermissionFilters => {
|
||||||
|
const permissionFilters: PermissionFilters = {};
|
||||||
|
|
||||||
|
function addFilterToField(key: string, operator: string, value: unknown, isPattern: boolean) {
|
||||||
|
if (!permissionFilters[key]) {
|
||||||
|
permissionFilters[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert operators for inverted/forbidden rules
|
||||||
|
let finalOperator = operator;
|
||||||
|
if (isInverted) {
|
||||||
|
switch (operator) {
|
||||||
|
case "=":
|
||||||
|
finalOperator = "!=";
|
||||||
|
break;
|
||||||
|
case "!=":
|
||||||
|
finalOperator = "=";
|
||||||
|
break;
|
||||||
|
case "LIKE":
|
||||||
|
finalOperator = "NOT LIKE";
|
||||||
|
break;
|
||||||
|
case "NOT LIKE":
|
||||||
|
finalOperator = "LIKE";
|
||||||
|
break;
|
||||||
|
case "IN":
|
||||||
|
finalOperator = "NOT IN";
|
||||||
|
break;
|
||||||
|
case "NOT IN":
|
||||||
|
finalOperator = "IN";
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
finalOperator = "<=";
|
||||||
|
break;
|
||||||
|
case ">=":
|
||||||
|
finalOperator = "<";
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
finalOperator = ">=";
|
||||||
|
break;
|
||||||
|
case "<=":
|
||||||
|
finalOperator = ">";
|
||||||
|
break;
|
||||||
|
case "IS NULL":
|
||||||
|
finalOperator = "IS NOT NULL";
|
||||||
|
break;
|
||||||
|
case "IS NOT NULL":
|
||||||
|
finalOperator = "IS NULL";
|
||||||
|
break;
|
||||||
|
// Default: keep the same operator
|
||||||
|
default:
|
||||||
|
finalOperator = operator;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionFilters[key].push({ operator: finalOperator, value, isPattern, isInverted });
|
||||||
|
}
|
||||||
|
|
||||||
|
function processCondition(key: string, value: unknown) {
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
const valueObj = value as Record<string, unknown>;
|
||||||
|
|
||||||
|
const operatorKeys = ["$regex", "$eq", "$in", "$glob", "$ne"];
|
||||||
|
const presentOperators = operatorKeys.filter((op) => op in valueObj);
|
||||||
|
|
||||||
|
if (presentOperators.length > 1) {
|
||||||
|
if ("$eq" in valueObj) {
|
||||||
|
addFilterToField(key, "=", valueObj.$eq, false);
|
||||||
|
}
|
||||||
|
if ("$glob" in valueObj) {
|
||||||
|
addFilterToField(key, "LIKE", valueObj.$glob, true);
|
||||||
|
}
|
||||||
|
if ("$regex" in valueObj) {
|
||||||
|
const regexValue = valueObj.$regex as RegExp;
|
||||||
|
const regexPattern = regexValue.source;
|
||||||
|
const globPattern = regexPattern
|
||||||
|
.replace(new RE2("^\\\\\\^"), "")
|
||||||
|
.replace(new RE2("\\\\\\$$"), "")
|
||||||
|
.replace(new RE2("\\\\\\.\\*", "g"), "*");
|
||||||
|
addFilterToField(key, "LIKE", globPattern, true);
|
||||||
|
}
|
||||||
|
if ("$ne" in valueObj) {
|
||||||
|
const valueStr = String(valueObj.$ne);
|
||||||
|
const hasWildcards = valueStr.includes("*") || valueStr.includes("?");
|
||||||
|
addFilterToField(key, hasWildcards ? "NOT LIKE" : "!=", valueObj.$ne, hasWildcards);
|
||||||
|
}
|
||||||
|
if ("$in" in valueObj) {
|
||||||
|
const inValues = valueObj.$in as unknown[];
|
||||||
|
addFilterToField(key, "IN", inValues, false);
|
||||||
|
}
|
||||||
|
} else if ("$regex" in value) {
|
||||||
|
const regexFilter = value as MongoRegexFilter;
|
||||||
|
const regexPattern = regexFilter.$regex.source;
|
||||||
|
const globPattern = regexPattern
|
||||||
|
.replace(new RE2("^\\\\\\^"), "")
|
||||||
|
.replace(new RE2("\\\\\\$$"), "")
|
||||||
|
.replace(new RE2("\\\\\\.\\*", "g"), "*");
|
||||||
|
addFilterToField(key, "LIKE", globPattern, true);
|
||||||
|
} else if ("$eq" in value) {
|
||||||
|
const eqFilter = value as MongoEqFilter;
|
||||||
|
addFilterToField(key, "=", eqFilter.$eq, false);
|
||||||
|
} else if ("$in" in value) {
|
||||||
|
const inFilter = value as MongoInFilter;
|
||||||
|
addFilterToField(key, "IN", inFilter.$in, false);
|
||||||
|
} else if ("$glob" in value) {
|
||||||
|
const globFilter = value as MongoGlobFilter;
|
||||||
|
addFilterToField(key, "LIKE", globFilter.$glob, true);
|
||||||
|
} else if ("$ne" in value) {
|
||||||
|
const neFilter = value as MongoNeFilter;
|
||||||
|
const valueStr = String(neFilter.$ne);
|
||||||
|
const hasWildcards = valueStr.includes("*") || valueStr.includes("?");
|
||||||
|
addFilterToField(key, hasWildcards ? "NOT LIKE" : "!=", neFilter.$ne, hasWildcards);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addFilterToField(key, "=", value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processConditions(mongoConditions: MongoQuery) {
|
||||||
|
if (
|
||||||
|
mongoConditions &&
|
||||||
|
typeof mongoConditions === "object" &&
|
||||||
|
"$or" in mongoConditions &&
|
||||||
|
Array.isArray(mongoConditions.$or)
|
||||||
|
) {
|
||||||
|
mongoConditions.$or.forEach((orCondition: MongoQuery) => {
|
||||||
|
processConditions(orCondition);
|
||||||
|
});
|
||||||
|
} else if (mongoConditions && typeof mongoConditions === "object") {
|
||||||
|
Object.entries(mongoConditions).forEach(([key, value]) => {
|
||||||
|
if (key.startsWith("$")) return;
|
||||||
|
processCondition(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions && typeof conditions === "object") {
|
||||||
|
processConditions(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract permission filters for a subject and action,
|
||||||
|
* converting them into ProcessedPermissionRules format for use with Knex queries.
|
||||||
|
* @param ability - CASL MongoAbility instance
|
||||||
|
* @param action - Permission action to filter for
|
||||||
|
* @param subjectName - Permission subject to filter for
|
||||||
|
* @returns ProcessedPermissionRules object for use with applyPermissionFiltersToQuery
|
||||||
|
*/
|
||||||
|
export function getProcessedPermissionRules(
|
||||||
|
ability: MongoAbility,
|
||||||
|
action: string,
|
||||||
|
subjectName: string
|
||||||
|
): ProcessedPermissionRules {
|
||||||
|
const matchingRules = ability.rules.filter((rule: RawRuleOf<MongoAbility>) => {
|
||||||
|
const actionMatches = Array.isArray(rule.action) ? rule.action.includes(action) : rule.action === action;
|
||||||
|
const subjectMatches = Array.isArray(rule.subject)
|
||||||
|
? rule.subject.includes(subjectName)
|
||||||
|
: rule.subject === subjectName;
|
||||||
|
return actionMatches && subjectMatches && rule.conditions;
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowRules: Array<Record<string, Array<PermissionFilterConfig>>> = [];
|
||||||
|
const forbidRules: Array<Record<string, Array<PermissionFilterConfig>>> = [];
|
||||||
|
|
||||||
|
matchingRules.forEach((rule: RawRuleOf<MongoAbility>) => {
|
||||||
|
if (rule.conditions) {
|
||||||
|
const isInverted = rule.inverted || false;
|
||||||
|
const ruleFilters = buildPermissionFiltersFromConditions(rule.conditions, isInverted);
|
||||||
|
|
||||||
|
if (isInverted) {
|
||||||
|
forbidRules.push(ruleFilters);
|
||||||
|
} else {
|
||||||
|
allowRules.push(ruleFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { allowRules, forbidRules };
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import type { Knex } from "knex";
|
||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
|
export interface PermissionFilterConfig {
|
||||||
|
operator: string;
|
||||||
|
value: unknown;
|
||||||
|
isPattern: boolean;
|
||||||
|
isInverted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionFilters = Record<string, Array<PermissionFilterConfig>>;
|
||||||
|
|
||||||
|
export interface ProcessedPermissionRules {
|
||||||
|
allowRules: Array<Record<string, Array<PermissionFilterConfig>>>;
|
||||||
|
forbidRules: Array<Record<string, Array<PermissionFilterConfig>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a single filter configuration to a query
|
||||||
|
* @param query - The Knex query builder instance
|
||||||
|
* @param tableName - The name of the table to apply filters to
|
||||||
|
* @param key - The field name
|
||||||
|
* @param filterConfig - The filter configuration
|
||||||
|
*/
|
||||||
|
const applySingleFilter = (
|
||||||
|
query: Knex.QueryBuilder,
|
||||||
|
tableName: string,
|
||||||
|
key: string,
|
||||||
|
filterConfig: PermissionFilterConfig
|
||||||
|
): void => {
|
||||||
|
if (filterConfig.value !== undefined && filterConfig.value !== null) {
|
||||||
|
const { operator, value, isPattern } = filterConfig;
|
||||||
|
const fieldName = `${tableName}.${key}`;
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
case "=":
|
||||||
|
void query.andWhere(fieldName, "=", value as string | number);
|
||||||
|
break;
|
||||||
|
case "!=":
|
||||||
|
void query.andWhere(fieldName, "!=", value as string | number);
|
||||||
|
break;
|
||||||
|
case "LIKE": {
|
||||||
|
const likePattern = isPattern ? String(value).replace(new RE2("\\*", "g"), "%") : String(value);
|
||||||
|
void query.andWhere(fieldName, "like", likePattern);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "NOT LIKE": {
|
||||||
|
const notLikePattern = isPattern ? String(value).replace(new RE2("\\*", "g"), "%") : String(value);
|
||||||
|
void query.andWhere(fieldName, "not like", notLikePattern);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "IN": {
|
||||||
|
const inValues = Array.isArray(value) ? value : [value];
|
||||||
|
void query.andWhere(fieldName, "in", inValues as (string | number)[]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "NOT IN": {
|
||||||
|
const notInValues = Array.isArray(value) ? value : [value];
|
||||||
|
void query.andWhere(fieldName, "not in", notInValues as (string | number)[]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ">":
|
||||||
|
void query.andWhere(fieldName, ">", value as string | number);
|
||||||
|
break;
|
||||||
|
case ">=":
|
||||||
|
void query.andWhere(fieldName, ">=", value as string | number);
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
void query.andWhere(fieldName, "<", value as string | number);
|
||||||
|
break;
|
||||||
|
case "<=":
|
||||||
|
void query.andWhere(fieldName, "<=", value as string | number);
|
||||||
|
break;
|
||||||
|
case "IS NULL":
|
||||||
|
void query.andWhere(fieldName, "is", null);
|
||||||
|
break;
|
||||||
|
case "IS NOT NULL":
|
||||||
|
void query.andWhere(fieldName, "is not", null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
void query.andWhere(fieldName, "=", value as string | number);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies complex permission rules to a Knex query with proper OR/AND logic
|
||||||
|
* @param query - The Knex query builder instance
|
||||||
|
* @param tableName - The name of the table to apply filters to
|
||||||
|
* @param processedRules - Processed permission rules with allow and forbid rules
|
||||||
|
* @returns The modified query builder with permission rules applied
|
||||||
|
*/
|
||||||
|
export const applyProcessedPermissionRulesToQuery = (
|
||||||
|
originalQuery: Knex.QueryBuilder,
|
||||||
|
tableName: string,
|
||||||
|
processedRules?: ProcessedPermissionRules
|
||||||
|
): Knex.QueryBuilder => {
|
||||||
|
if (!processedRules || (processedRules.allowRules.length === 0 && processedRules.forbidRules.length === 0)) {
|
||||||
|
return originalQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = originalQuery;
|
||||||
|
|
||||||
|
if (processedRules.allowRules.length > 0) {
|
||||||
|
query = query.andWhere((allowBuilder) => {
|
||||||
|
processedRules.allowRules.forEach((rule, index) => {
|
||||||
|
const ruleBuilder = (ruleSubBuilder: Knex.QueryBuilder) => {
|
||||||
|
Object.entries(rule).forEach(([key, filterConfigs]) => {
|
||||||
|
filterConfigs.forEach((filterConfig) => {
|
||||||
|
applySingleFilter(ruleSubBuilder, tableName, key, filterConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
void allowBuilder.where(ruleBuilder);
|
||||||
|
} else {
|
||||||
|
void allowBuilder.orWhere(ruleBuilder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processedRules.forbidRules.length > 0) {
|
||||||
|
processedRules.forbidRules.forEach((forbidRule) => {
|
||||||
|
Object.entries(forbidRule).forEach(([key, filterConfigs]) => {
|
||||||
|
filterConfigs.forEach((filterConfig) => {
|
||||||
|
applySingleFilter(query, tableName, key, filterConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a string value for safe use in SQL LIKE queries
|
||||||
|
* @param value - The string value to sanitize
|
||||||
|
* @returns The sanitized string with SQL special characters escaped
|
||||||
|
*/
|
||||||
|
export const sanitizeForLike = (value: string): string => {
|
||||||
|
return String(value).replace(new RE2("[%_\\\\]", "g"), "\\$&");
|
||||||
|
};
|
||||||
@@ -176,7 +176,8 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId,
|
||||||
|
isInternal: true
|
||||||
});
|
});
|
||||||
const caType = (ca?.externalCa?.type as CaType) ?? CaType.INTERNAL;
|
const caType = (ca?.externalCa?.type as CaType) ?? CaType.INTERNAL;
|
||||||
useOrderFlow = caType !== CaType.INTERNAL;
|
useOrderFlow = caType !== CaType.INTERNAL;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { TableName } from "@app/db/schemas";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ProcessedPermissionRules } from "@app/lib/knex/permission-filter-utils";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
@@ -715,11 +716,21 @@ export const AcmeCertificateAuthorityFns = ({
|
|||||||
return castDbEntryToAcmeCertificateAuthority(updatedCa);
|
return castDbEntryToAcmeCertificateAuthority(updatedCa);
|
||||||
};
|
};
|
||||||
|
|
||||||
const listCertificateAuthorities = async ({ projectId }: { projectId: string }) => {
|
const listCertificateAuthorities = async ({
|
||||||
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
|
projectId,
|
||||||
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
permissionFilters
|
||||||
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.ACME
|
}: {
|
||||||
});
|
projectId: string;
|
||||||
|
permissionFilters?: ProcessedPermissionRules;
|
||||||
|
}) => {
|
||||||
|
const cas = await certificateAuthorityDAL.findWithAssociatedCa(
|
||||||
|
{
|
||||||
|
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
||||||
|
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.ACME
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
permissionFilters
|
||||||
|
);
|
||||||
|
|
||||||
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
||||||
};
|
};
|
||||||
|
|||||||
+16
-5
@@ -5,6 +5,7 @@ import RE2 from "re2";
|
|||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ProcessedPermissionRules } from "@app/lib/knex/permission-filter-utils";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
@@ -798,11 +799,21 @@ export const AzureAdCsCertificateAuthorityFns = ({
|
|||||||
return castDbEntryToAzureAdCsCertificateAuthority(updatedCa);
|
return castDbEntryToAzureAdCsCertificateAuthority(updatedCa);
|
||||||
};
|
};
|
||||||
|
|
||||||
const listCertificateAuthorities = async ({ projectId }: { projectId: string }) => {
|
const listCertificateAuthorities = async ({
|
||||||
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
|
projectId,
|
||||||
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
permissionFilters
|
||||||
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.AZURE_AD_CS
|
}: {
|
||||||
});
|
projectId: string;
|
||||||
|
permissionFilters?: ProcessedPermissionRules;
|
||||||
|
}) => {
|
||||||
|
const cas = await certificateAuthorityDAL.findWithAssociatedCa(
|
||||||
|
{
|
||||||
|
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
||||||
|
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.AZURE_AD_CS
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
permissionFilters
|
||||||
|
);
|
||||||
|
|
||||||
return cas.map(castDbEntryToAzureAdCsCertificateAuthority);
|
return cas.map(castDbEntryToAzureAdCsCertificateAuthority);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { TDbClient } from "@app/db";
|
|||||||
import { CertificateAuthoritiesSchema, TableName, TCertificateAuthorities } from "@app/db/schemas";
|
import { CertificateAuthoritiesSchema, TableName, TCertificateAuthorities } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, selectAllTableCols, TFindOpt } from "@app/lib/knex";
|
import { buildFindFilter, ormify, selectAllTableCols, TFindOpt } from "@app/lib/knex";
|
||||||
|
import {
|
||||||
|
applyProcessedPermissionRulesToQuery,
|
||||||
|
type ProcessedPermissionRules
|
||||||
|
} from "@app/lib/knex/permission-filter-utils";
|
||||||
|
|
||||||
export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAuthorityDALFactory>;
|
export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAuthorityDALFactory>;
|
||||||
|
|
||||||
@@ -220,10 +224,11 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
const findWithAssociatedCa = async (
|
const findWithAssociatedCa = async (
|
||||||
filter: Parameters<(typeof caOrm)["find"]>[0] & { dn?: string; type?: string; serialNumber?: string },
|
filter: Parameters<(typeof caOrm)["find"]>[0] & { dn?: string; type?: string; serialNumber?: string },
|
||||||
{ offset, limit, sort = [["createdAt", "desc"]] }: TFindOpt<TCertificateAuthorities> = {},
|
{ offset, limit, sort = [["createdAt", "desc"]] }: TFindOpt<TCertificateAuthorities> = {},
|
||||||
|
permissionFilters?: ProcessedPermissionRules,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const query = (tx || db.replicaNode())(TableName.CertificateAuthority)
|
let query = (tx || db.replicaNode())(TableName.CertificateAuthority)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.InternalCertificateAuthority,
|
TableName.InternalCertificateAuthority,
|
||||||
`${TableName.CertificateAuthority}.id`,
|
`${TableName.CertificateAuthority}.id`,
|
||||||
@@ -268,6 +273,14 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (permissionFilters) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(
|
||||||
|
query,
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
permissionFilters
|
||||||
|
) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
if (limit) void query.limit(limit);
|
if (limit) void query.limit(limit);
|
||||||
if (offset) void query.offset(offset);
|
if (offset) void query.offset(offset);
|
||||||
if (sort) {
|
if (sort) {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getProcessedPermissionRules } from "@app/lib/casl/permission-filter-utils";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -142,8 +146,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.Create,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -212,8 +216,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: certificateAuthority.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -227,6 +231,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
id: certificateAuthority.id,
|
id: certificateAuthority.id,
|
||||||
type,
|
type,
|
||||||
enableDirectIssuance: certificateAuthority.enableDirectIssuance,
|
enableDirectIssuance: certificateAuthority.enableDirectIssuance,
|
||||||
|
subject: ProjectPermissionSub.CertificateAuthorities,
|
||||||
name: certificateAuthority.name,
|
name: certificateAuthority.name,
|
||||||
projectId: certificateAuthority.projectId,
|
projectId: certificateAuthority.projectId,
|
||||||
configuration: certificateAuthority.internalCa,
|
configuration: certificateAuthority.internalCa,
|
||||||
@@ -275,8 +280,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: caName })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -328,15 +333,25 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissionFilters = getProcessedPermissionRules(
|
||||||
|
permission,
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
|
const cas = await certificateAuthorityDAL.findWithAssociatedCa(
|
||||||
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
{
|
||||||
$notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"]
|
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
|
||||||
});
|
$notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"]
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
permissionFilters
|
||||||
|
);
|
||||||
|
|
||||||
return cas
|
return cas
|
||||||
.filter((ca): ca is typeof ca & { internalCa: NonNullable<typeof ca.internalCa> } => Boolean(ca.internalCa))
|
.filter((ca): ca is typeof ca & { internalCa: NonNullable<typeof ca.internalCa> } => Boolean(ca.internalCa))
|
||||||
@@ -352,11 +367,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === CaType.ACME) {
|
if (type === CaType.ACME) {
|
||||||
return acmeFns.listCertificateAuthorities({ projectId });
|
return acmeFns.listCertificateAuthorities({ projectId, permissionFilters });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === CaType.AZURE_AD_CS) {
|
if (type === CaType.AZURE_AD_CS) {
|
||||||
return azureAdCsFns.listCertificateAuthorities({ projectId });
|
return azureAdCsFns.listCertificateAuthorities({ projectId, permissionFilters });
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||||
@@ -383,8 +398,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: certificateAuthority.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -459,8 +474,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionCertificateAuthorityActions.Delete,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: certificateAuthority.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!certificateAuthority.internalCa?.id && type === CaType.INTERNAL) {
|
if (!certificateAuthority.internalCa?.id && type === CaType.INTERNAL) {
|
||||||
@@ -524,8 +539,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: certificateAuthority.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -606,8 +621,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionCertificateAuthorityActions.Delete,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: certificateAuthority.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!certificateAuthority.internalCa?.id && type === CaType.INTERNAL) {
|
if (!certificateAuthority.internalCa?.id && type === CaType.INTERNAL) {
|
||||||
@@ -662,6 +677,13 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
actorAuthMethod: OrgServiceActor["authMethod"];
|
actorAuthMethod: OrgServiceActor["authMethod"];
|
||||||
actorOrgId?: string;
|
actorOrgId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||||
|
|
||||||
|
if (!certificateAuthority)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Could not find certificate authority with id "${caId}"`
|
||||||
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -672,8 +694,10 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: certificateAuthority.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return azureAdCsFns.getTemplates({
|
return azureAdCsFns.getTemplates({
|
||||||
@@ -687,32 +711,38 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
isInternal
|
||||||
}: {
|
}: {
|
||||||
caId: string;
|
caId: string;
|
||||||
actor: OrgServiceActor["type"];
|
actor: OrgServiceActor["type"];
|
||||||
actorId: string;
|
actorId: string;
|
||||||
actorAuthMethod: OrgServiceActor["authMethod"];
|
actorAuthMethod: OrgServiceActor["authMethod"];
|
||||||
actorOrgId?: string;
|
actorOrgId?: string;
|
||||||
|
isInternal?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
throw new NotFoundError({ message: "CA not found" });
|
throw new NotFoundError({ message: "CA not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
if (!isInternal) {
|
||||||
actor,
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actorId,
|
actor,
|
||||||
projectId: ca.projectId,
|
actorId,
|
||||||
actorAuthMethod,
|
projectId: ca.projectId,
|
||||||
actorOrgId,
|
actorAuthMethod,
|
||||||
actionProjectType: ActionProjectType.CertificateManager
|
actorOrgId,
|
||||||
});
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
);
|
name: ca.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return ca;
|
return ca;
|
||||||
};
|
};
|
||||||
|
|||||||
+21
-21
@@ -7,8 +7,8 @@ import { Knex } from "knex";
|
|||||||
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@@ -174,8 +174,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.Create,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: commonName })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
projectId = dto.projectId;
|
projectId = dto.projectId;
|
||||||
@@ -357,8 +357,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
return expandInternalCa(ca);
|
return expandInternalCa(ca);
|
||||||
@@ -383,8 +383,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,8 +416,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionCertificateAuthorityActions.Delete,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
await certificateAuthorityDAL.deleteById(ca.id);
|
await certificateAuthorityDAL.deleteById(ca.id);
|
||||||
@@ -442,8 +442,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.Create,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ca.internalCa.type === InternalCaType.ROOT)
|
if (ca.internalCa.type === InternalCaType.ROOT)
|
||||||
@@ -506,8 +506,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.Renew,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
@@ -793,8 +793,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
const caCertChains = await getCaCertChains({
|
const caCertChains = await getCaCertChains({
|
||||||
@@ -830,8 +830,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { caCert, caCertChain, serialNumber } = await getCaCertChain({
|
const { caCert, caCertChain, serialNumber } = await getCaCertChain({
|
||||||
@@ -911,8 +911,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.SignIntermediate,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
@@ -1059,8 +1059,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionCertificateAuthorityActions.Create,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ca.internalCa.parentCaId) {
|
if (ca.internalCa.parentCaId) {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import {
|
||||||
|
applyProcessedPermissionRulesToQuery,
|
||||||
|
type ProcessedPermissionRules
|
||||||
|
} from "@app/lib/knex/permission-filter-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EnrollmentType,
|
EnrollmentType,
|
||||||
@@ -276,6 +280,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
issuerType?: IssuerType;
|
issuerType?: IssuerType;
|
||||||
caId?: string;
|
caId?: string;
|
||||||
} = {},
|
} = {},
|
||||||
|
processedRules?: ProcessedPermissionRules,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
): Promise<TCertificateProfile[] | TCertificateProfileWithConfigs[]> => {
|
): Promise<TCertificateProfile[] | TCertificateProfileWithConfigs[]> => {
|
||||||
try {
|
try {
|
||||||
@@ -308,7 +313,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.issuerType`, issuerType);
|
baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.issuerType`, issuerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = baseQuery
|
let query = baseQuery
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.CertificateAuthority,
|
TableName.CertificateAuthority,
|
||||||
`${TableName.PkiCertificateProfile}.caId`,
|
`${TableName.PkiCertificateProfile}.caId`,
|
||||||
@@ -354,6 +359,14 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId")
|
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (processedRules) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(
|
||||||
|
query,
|
||||||
|
TableName.PkiCertificateProfile,
|
||||||
|
processedRules
|
||||||
|
) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
const results = (await query
|
const results = (await query
|
||||||
.orderBy(`${TableName.PkiCertificateProfile}.createdAt`, "desc")
|
.orderBy(`${TableName.PkiCertificateProfile}.createdAt`, "desc")
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@@ -432,6 +445,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
issuerType?: IssuerType;
|
issuerType?: IssuerType;
|
||||||
caId?: string;
|
caId?: string;
|
||||||
} = {},
|
} = {},
|
||||||
|
processedRules?: ProcessedPermissionRules,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
): Promise<number> => {
|
): Promise<number> => {
|
||||||
try {
|
try {
|
||||||
@@ -459,6 +473,14 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
query = query.where({ issuerType });
|
query = query.where({ issuerType });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processedRules) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(
|
||||||
|
query,
|
||||||
|
TableName.PkiCertificateProfile,
|
||||||
|
processedRules
|
||||||
|
) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await query.count("*").first();
|
const result = await query.count("*").first();
|
||||||
return parseInt((result as unknown as { count: string }).count || "0", 10);
|
return parseInt((result as unknown as { count: string }).count || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ describe("CertificateProfileService", () => {
|
|||||||
const mockPermissionService = {
|
const mockPermissionService = {
|
||||||
getProjectPermission: vi.fn().mockResolvedValue({
|
getProjectPermission: vi.fn().mockResolvedValue({
|
||||||
permission: {
|
permission: {
|
||||||
throwUnlessCan: vi.fn()
|
throwUnlessCan: vi.fn(),
|
||||||
|
rules: []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} as unknown as Pick<TPermissionServiceFactory, "getProjectPermission">;
|
} as unknown as Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -598,13 +599,18 @@ describe("CertificateProfileService", () => {
|
|||||||
|
|
||||||
expect(result.profiles).toEqual(mockProfiles);
|
expect(result.profiles).toEqual(mockProfiles);
|
||||||
expect(result.totalCount).toBe(1);
|
expect(result.totalCount).toBe(1);
|
||||||
expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith("project-123", {
|
expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith(
|
||||||
offset: 0,
|
"project-123",
|
||||||
limit: 20,
|
{
|
||||||
search: undefined,
|
offset: 0,
|
||||||
enrollmentType: undefined,
|
limit: 20,
|
||||||
caId: undefined
|
search: undefined,
|
||||||
});
|
enrollmentType: undefined,
|
||||||
|
caId: undefined,
|
||||||
|
issuerType: undefined
|
||||||
|
},
|
||||||
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should list profiles with filters", async () => {
|
it("should list profiles with filters", async () => {
|
||||||
@@ -618,13 +624,18 @@ describe("CertificateProfileService", () => {
|
|||||||
caId: "ca-123"
|
caId: "ca-123"
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith("project-123", {
|
expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith(
|
||||||
offset: 10,
|
"project-123",
|
||||||
limit: 5,
|
{
|
||||||
search: "test",
|
offset: 10,
|
||||||
enrollmentType: EnrollmentType.API,
|
limit: 5,
|
||||||
caId: "ca-123"
|
search: "test",
|
||||||
});
|
enrollmentType: EnrollmentType.API,
|
||||||
|
caId: "ca-123",
|
||||||
|
issuerType: undefined
|
||||||
|
},
|
||||||
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns";
|
import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns";
|
||||||
|
import { getProcessedPermissionRules } from "@app/lib/casl/permission-filter-utils";
|
||||||
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
@@ -299,7 +300,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Create,
|
ProjectPermissionCertificateProfileActions.Create,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: data.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
@@ -454,7 +457,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Edit,
|
ProjectPermissionCertificateProfileActions.Edit,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: existingProfile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (data.certificateTemplateId) {
|
if (data.certificateTemplateId) {
|
||||||
@@ -577,7 +582,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const converted = convertDalToService(profile);
|
const converted = convertDalToService(profile);
|
||||||
@@ -613,7 +620,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (profile.estConfig && profile.estConfig.caChain) {
|
if (profile.estConfig && profile.estConfig.caChain) {
|
||||||
@@ -687,7 +696,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const profile = await certificateProfileDAL.findBySlugAndProjectId(slug, projectId);
|
const profile = await certificateProfileDAL.findBySlugAndProjectId(slug, projectId);
|
||||||
@@ -739,21 +750,35 @@ export const certificateProfileServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateProfiles
|
ProjectPermissionSub.CertificateProfiles
|
||||||
);
|
);
|
||||||
|
|
||||||
const profiles = await certificateProfileDAL.findByProjectId(projectId, {
|
const processedRules = getProcessedPermissionRules(
|
||||||
offset,
|
permission,
|
||||||
limit,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
search,
|
ProjectPermissionSub.CertificateProfiles
|
||||||
enrollmentType,
|
);
|
||||||
issuerType,
|
|
||||||
caId
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalCount = await certificateProfileDAL.countByProjectId(projectId, {
|
const profiles = await certificateProfileDAL.findByProjectId(
|
||||||
search,
|
projectId,
|
||||||
enrollmentType,
|
{
|
||||||
issuerType,
|
offset,
|
||||||
caId
|
limit,
|
||||||
});
|
search,
|
||||||
|
enrollmentType,
|
||||||
|
issuerType,
|
||||||
|
caId
|
||||||
|
},
|
||||||
|
processedRules
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalCount = await certificateProfileDAL.countByProjectId(
|
||||||
|
projectId,
|
||||||
|
{
|
||||||
|
search,
|
||||||
|
enrollmentType,
|
||||||
|
issuerType,
|
||||||
|
caId
|
||||||
|
},
|
||||||
|
processedRules
|
||||||
|
);
|
||||||
|
|
||||||
const convertedProfiles = await Promise.all(
|
const convertedProfiles = await Promise.all(
|
||||||
profiles.map(async (profile) => {
|
profiles.map(async (profile) => {
|
||||||
@@ -838,7 +863,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Delete,
|
ProjectPermissionCertificateProfileActions.Delete,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const deletedProfile = await certificateProfileDAL.deleteById(profileId);
|
const deletedProfile = await certificateProfileDAL.deleteById(profileId);
|
||||||
@@ -884,7 +911,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const certificates = await certificateProfileDAL.getCertificatesByProfile(profileId, {
|
const certificates = await certificateProfileDAL.getCertificatesByProfile(profileId, {
|
||||||
@@ -925,17 +954,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
);
|
slug: profile.slug
|
||||||
|
})
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionCertificateActions.Read,
|
|
||||||
ProjectPermissionSub.Certificates
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionCertificateActions.ReadPrivateKey,
|
|
||||||
ProjectPermissionSub.Certificates
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const cert = await certificateProfileDAL.getLatestActiveCertificateForProfile(profileId);
|
const cert = await certificateProfileDAL.getLatestActiveCertificateForProfile(profileId);
|
||||||
@@ -944,6 +965,24 @@ export const certificateProfileServiceFactory = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||||
|
|
||||||
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||||
@@ -1037,7 +1076,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.Read,
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1088,7 +1129,9 @@ export const certificateProfileServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
|
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (profile.enrollmentType !== EnrollmentType.ACME) {
|
if (profile.enrollmentType !== EnrollmentType.ACME) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ActionProjectType } from "@app/db/schemas";
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionSet,
|
ProjectPermissionSet,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
@@ -79,8 +80,8 @@ describe("CertificateRequestService", () => {
|
|||||||
const mockPermission = {
|
const mockPermission = {
|
||||||
permission: createMongoAbility<ProjectPermissionSet>([
|
permission: createMongoAbility<ProjectPermissionSet>([
|
||||||
{
|
{
|
||||||
action: ProjectPermissionCertificateActions.Create,
|
action: ProjectPermissionCertificateProfileActions.IssueCert,
|
||||||
subject: ProjectPermissionSub.Certificates
|
subject: ProjectPermissionSub.CertificateProfiles
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
@@ -224,6 +225,10 @@ describe("CertificateRequestService", () => {
|
|||||||
{
|
{
|
||||||
action: ProjectPermissionCertificateActions.Read,
|
action: ProjectPermissionCertificateActions.Read,
|
||||||
subject: ProjectPermissionSub.Certificates
|
subject: ProjectPermissionSub.Certificates
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
subject: ProjectPermissionSub.Certificates
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
@@ -318,7 +323,7 @@ describe("CertificateRequestService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get certificate from request successfully when private key access is denied", async () => {
|
it("should get certificate from request successfully when user lacks private key permission", async () => {
|
||||||
const mockPermission = {
|
const mockPermission = {
|
||||||
permission: createMongoAbility<ProjectPermissionSet>([
|
permission: createMongoAbility<ProjectPermissionSet>([
|
||||||
{
|
{
|
||||||
@@ -348,7 +353,6 @@ describe("CertificateRequestService", () => {
|
|||||||
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
|
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
|
||||||
(mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert);
|
(mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert);
|
||||||
(mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody);
|
(mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody);
|
||||||
(mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key access denied"));
|
|
||||||
|
|
||||||
const result = await service.getCertificateFromRequest(mockGetData);
|
const result = await service.getCertificateFromRequest(mockGetData);
|
||||||
|
|
||||||
@@ -362,8 +366,68 @@ describe("CertificateRequestService", () => {
|
|||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
||||||
});
|
});
|
||||||
|
expect(mockCertificateService.getCertPrivateKey).not.toHaveBeenCalled();
|
||||||
|
expect(result).toEqual({
|
||||||
|
status: CertificateRequestStatus.ISSUED,
|
||||||
|
certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----",
|
||||||
|
privateKey: null,
|
||||||
|
serialNumber: "123456",
|
||||||
|
errorMessage: null,
|
||||||
|
createdAt: mockRequestWithCert.createdAt,
|
||||||
|
updatedAt: mockRequestWithCert.updatedAt
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get certificate from request successfully when user has private key permission but key retrieval fails", async () => {
|
||||||
|
const mockPermission = {
|
||||||
|
permission: createMongoAbility<ProjectPermissionSet>([
|
||||||
|
{
|
||||||
|
action: ProjectPermissionCertificateActions.Read,
|
||||||
|
subject: ProjectPermissionSub.Certificates
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
subject: ProjectPermissionSub.Certificates
|
||||||
|
}
|
||||||
|
])
|
||||||
|
};
|
||||||
|
const mockCertificate = {
|
||||||
|
id: "550e8400-e29b-41d4-a716-446655440009",
|
||||||
|
serialNumber: "123456",
|
||||||
|
commonName: "test.example.com"
|
||||||
|
};
|
||||||
|
const mockRequestWithCert = {
|
||||||
|
id: "550e8400-e29b-41d4-a716-446655440005",
|
||||||
|
projectId: "550e8400-e29b-41d4-a716-446655440003",
|
||||||
|
status: CertificateRequestStatus.ISSUED,
|
||||||
|
certificate: mockCertificate,
|
||||||
|
errorMessage: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
};
|
||||||
|
const mockCertBody = {
|
||||||
|
certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----"
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
|
||||||
|
(mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert);
|
||||||
|
(mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody);
|
||||||
|
(mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key not found"));
|
||||||
|
|
||||||
|
const result = await service.getCertificateFromRequest(mockGetData);
|
||||||
|
|
||||||
|
expect(mockCertificateRequestDAL.findByIdWithCertificate).toHaveBeenCalledWith(
|
||||||
|
"550e8400-e29b-41d4-a716-446655440005"
|
||||||
|
);
|
||||||
|
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
|
||||||
|
id: "550e8400-e29b-41d4-a716-446655440009",
|
||||||
|
actor: ActorType.USER,
|
||||||
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
|
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
||||||
|
});
|
||||||
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
||||||
id: "550e8400-e29b-41d4-a716-446655440008",
|
id: "550e8400-e29b-41d4-a716-446655440009",
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ActionProjectType } from "@app/db/schemas";
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
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";
|
||||||
@@ -110,8 +111,8 @@ export const certificateRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Create,
|
ProjectPermissionCertificateProfileActions.IssueCert,
|
||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.CertificateProfiles
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,20 +218,25 @@ export const certificateRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to get private key (may fail if user doesn't have permission)
|
const canReadPrivateKey = permission.can(
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
let privateKey: string | null = null;
|
let privateKey: string | null = null;
|
||||||
try {
|
if (canReadPrivateKey) {
|
||||||
const certPrivateKey = await certificateService.getCertPrivateKey({
|
try {
|
||||||
id: certificateRequest.certificate.id,
|
const certPrivateKey = await certificateService.getCertPrivateKey({
|
||||||
actor,
|
id: certificateRequest.certificate.id,
|
||||||
actorId,
|
actor,
|
||||||
actorAuthMethod,
|
actorId,
|
||||||
actorOrgId
|
actorAuthMethod,
|
||||||
});
|
actorOrgId
|
||||||
privateKey = certPrivateKey.certPrivateKey;
|
});
|
||||||
} catch (error) {
|
privateKey = certPrivateKey.certPrivateKey;
|
||||||
// Private key access denied - continue without it
|
} catch (error) {
|
||||||
privateKey = null;
|
privateKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { TableName } from "@app/db/schemas";
|
|||||||
import { TPkiCertificateTemplatesV2Insert } from "@app/db/schemas/pki-certificate-templates-v2";
|
import { TPkiCertificateTemplatesV2Insert } from "@app/db/schemas/pki-certificate-templates-v2";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
import {
|
||||||
|
applyProcessedPermissionRulesToQuery,
|
||||||
|
type ProcessedPermissionRules
|
||||||
|
} from "@app/lib/knex/permission-filter-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TCertificateTemplateV2,
|
TCertificateTemplateV2,
|
||||||
@@ -133,6 +137,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
} = {},
|
} = {},
|
||||||
|
processedRules?: ProcessedPermissionRules,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -146,6 +151,14 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processedRules) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(
|
||||||
|
query,
|
||||||
|
TableName.PkiCertificateTemplateV2,
|
||||||
|
processedRules
|
||||||
|
) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
const certificateTemplatesV2 = await query.orderBy("createdAt", "desc").offset(offset).limit(limit);
|
const certificateTemplatesV2 = await query.orderBy("createdAt", "desc").offset(offset).limit(limit);
|
||||||
|
|
||||||
return certificateTemplatesV2.map((template: Record<string, unknown>) => parseJsonFields(template));
|
return certificateTemplatesV2.map((template: Record<string, unknown>) => parseJsonFields(template));
|
||||||
@@ -159,6 +172,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
|||||||
options: {
|
options: {
|
||||||
search?: string;
|
search?: string;
|
||||||
} = {},
|
} = {},
|
||||||
|
processedRules?: ProcessedPermissionRules,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -172,6 +186,14 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processedRules) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(
|
||||||
|
query,
|
||||||
|
TableName.PkiCertificateTemplateV2,
|
||||||
|
processedRules
|
||||||
|
) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await query.count("*").first();
|
const result = await query.count("*").first();
|
||||||
return parseInt((result as unknown as { count: string }).count || "0", 10);
|
return parseInt((result as unknown as { count: string }).count || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+32
-16
@@ -267,14 +267,22 @@ describe("CertificateTemplateV2Service", () => {
|
|||||||
limit: 20
|
limit: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockCertificateTemplateV2DAL.findByProjectId).toHaveBeenCalledWith("project-123", {
|
expect(mockCertificateTemplateV2DAL.findByProjectId).toHaveBeenCalledWith(
|
||||||
offset: 0,
|
"project-123",
|
||||||
limit: 20,
|
{
|
||||||
search: undefined
|
offset: 0,
|
||||||
});
|
limit: 20,
|
||||||
expect(mockCertificateTemplateV2DAL.countByProjectId).toHaveBeenCalledWith("project-123", {
|
search: undefined
|
||||||
search: undefined
|
},
|
||||||
});
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
|
expect(mockCertificateTemplateV2DAL.countByProjectId).toHaveBeenCalledWith(
|
||||||
|
"project-123",
|
||||||
|
{
|
||||||
|
search: undefined
|
||||||
|
},
|
||||||
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
expect(result).toEqual({ templates, totalCount });
|
expect(result).toEqual({ templates, totalCount });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,14 +299,22 @@ describe("CertificateTemplateV2Service", () => {
|
|||||||
search: "web server"
|
search: "web server"
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockCertificateTemplateV2DAL.findByProjectId).toHaveBeenCalledWith("project-123", {
|
expect(mockCertificateTemplateV2DAL.findByProjectId).toHaveBeenCalledWith(
|
||||||
offset: 0,
|
"project-123",
|
||||||
limit: 20,
|
{
|
||||||
search: "web server"
|
offset: 0,
|
||||||
});
|
limit: 20,
|
||||||
expect(mockCertificateTemplateV2DAL.countByProjectId).toHaveBeenCalledWith("project-123", {
|
search: "web server"
|
||||||
search: "web server"
|
},
|
||||||
});
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
|
expect(mockCertificateTemplateV2DAL.countByProjectId).toHaveBeenCalledWith(
|
||||||
|
"project-123",
|
||||||
|
{
|
||||||
|
search: "web server"
|
||||||
|
},
|
||||||
|
{ allowRules: [], forbidRules: [] }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getProcessedPermissionRules } from "@app/lib/casl/permission-filter-utils";
|
||||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
@@ -77,12 +78,12 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateSubjectAttributePolicy = (
|
const validateSubjectAttributePolicy = (
|
||||||
subject: Array<{ type: string; allowed?: string[]; required?: string[]; denied?: string[] }>
|
subjectAttributes: Array<{ type: string; allowed?: string[]; required?: string[]; denied?: string[] }>
|
||||||
) => {
|
) => {
|
||||||
if (!subject || subject.length === 0) return;
|
if (!subjectAttributes || subjectAttributes.length === 0) return;
|
||||||
|
|
||||||
// Validate each subject attribute policy
|
// Validate each subject attribute policy
|
||||||
for (const attr of subject) {
|
for (const attr of subjectAttributes) {
|
||||||
// Ensure at least one field is provided
|
// Ensure at least one field is provided
|
||||||
if (!attr.allowed && !attr.required && !attr.denied) {
|
if (!attr.allowed && !attr.required && !attr.denied) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@@ -634,7 +635,9 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiTemplateActions.Create,
|
ProjectPermissionPkiTemplateActions.Create,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: data.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -711,7 +714,9 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiTemplateActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: existingTemplate.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const consolidatedData = {
|
const consolidatedData = {
|
||||||
@@ -784,7 +789,9 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiTemplateActions.Read,
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: template.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,16 +822,17 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionPkiTemplateActions.Read,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const template = await certificateTemplateV2DAL.findByNameAndProjectId(slug, projectId);
|
const template = await certificateTemplateV2DAL.findByNameAndProjectId(slug, projectId);
|
||||||
if (!template) {
|
if (!template) {
|
||||||
throw new NotFoundError({ message: "Certificate template not found" });
|
throw new NotFoundError({ message: "Certificate template not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: template.name
|
||||||
|
})
|
||||||
|
);
|
||||||
return template;
|
return template;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -864,13 +872,18 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateTemplates
|
ProjectPermissionSub.CertificateTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
const templates = await certificateTemplateV2DAL.findByProjectId(projectId, {
|
const processedRules = getProcessedPermissionRules(
|
||||||
offset,
|
permission,
|
||||||
limit,
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
search
|
ProjectPermissionSub.CertificateTemplates
|
||||||
});
|
);
|
||||||
|
const templates = await certificateTemplateV2DAL.findByProjectId(
|
||||||
|
projectId,
|
||||||
|
{ offset, limit, search },
|
||||||
|
processedRules
|
||||||
|
);
|
||||||
|
|
||||||
const totalCount = await certificateTemplateV2DAL.countByProjectId(projectId, { search });
|
const totalCount = await certificateTemplateV2DAL.countByProjectId(projectId, { search }, processedRules);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templates,
|
templates,
|
||||||
@@ -907,7 +920,9 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiTemplateActions.Delete,
|
ProjectPermissionPkiTemplateActions.Delete,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: template.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const isInUse = await certificateTemplateV2DAL.isTemplateInUse(templateId);
|
const isInUse = await certificateTemplateV2DAL.isTemplateInUse(templateId);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
@@ -167,7 +167,9 @@ const validateProfileAndPermissions = async (
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateProfileActions.IssueCert,
|
ProjectPermissionCertificateProfileActions.IssueCert,
|
||||||
ProjectPermissionSub.CertificateProfiles
|
subject(ProjectPermissionSub.CertificateProfiles, {
|
||||||
|
slug: profile.slug
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
@@ -999,11 +1001,27 @@ export const certificateV3ServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
const canReadPrivateKey = permission.can(
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
const privateKeyForResponse = canReadPrivateKey ? selfSignedResult.privateKey.toString("utf8") : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: selfSignedResult.certificate.toString("utf8"),
|
certificate: selfSignedResult.certificate.toString("utf8"),
|
||||||
issuingCaCertificate: "",
|
issuingCaCertificate: "",
|
||||||
certificateChain: selfSignedResult.certificate.toString("utf8"),
|
certificateChain: selfSignedResult.certificate.toString("utf8"),
|
||||||
privateKey: selfSignedResult.privateKey.toString("utf8"),
|
privateKey: privateKeyForResponse,
|
||||||
serialNumber: selfSignedResult.serialNumber,
|
serialNumber: selfSignedResult.serialNumber,
|
||||||
certificateId: certificateData.id,
|
certificateId: certificateData.id,
|
||||||
certificateRequestId,
|
certificateRequestId,
|
||||||
@@ -1100,11 +1118,28 @@ export const certificateV3ServiceFactory = ({
|
|||||||
finalCertificateChain = removeRootCaFromChain(finalCertificateChain);
|
finalCertificateChain = removeRootCaFromChain(finalCertificateChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user has permission to read private key
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
const canReadPrivateKey = permission.can(
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
const privateKeyForResponse = canReadPrivateKey ? bufferToString(privateKey) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: bufferToString(certificate),
|
certificate: bufferToString(certificate),
|
||||||
issuingCaCertificate: bufferToString(issuingCaCertificate),
|
issuingCaCertificate: bufferToString(issuingCaCertificate),
|
||||||
certificateChain: finalCertificateChain,
|
certificateChain: finalCertificateChain,
|
||||||
privateKey: bufferToString(privateKey),
|
privateKey: privateKeyForResponse,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
certificateId: cert.id,
|
certificateId: cert.id,
|
||||||
certificateRequestId,
|
certificateRequestId,
|
||||||
@@ -1489,10 +1524,12 @@ export const certificateV3ServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
if (profile) {
|
||||||
ProjectPermissionCertificateProfileActions.IssueCert,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSub.CertificateProfiles
|
ProjectPermissionCertificateProfileActions.IssueCert,
|
||||||
);
|
subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuerType = profile?.issuerType || (originalCert.caId ? IssuerType.CA : IssuerType.SELF_SIGNED);
|
const issuerType = profile?.issuerType || (originalCert.caId ? IssuerType.CA : IssuerType.SELF_SIGNED);
|
||||||
@@ -1869,7 +1906,11 @@ export const certificateV3ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Edit,
|
ProjectPermissionCertificateActions.Edit,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames ?? undefined,
|
||||||
|
serialNumber: certificate.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!certificate.profileId) {
|
if (!certificate.profileId) {
|
||||||
@@ -1972,7 +2013,11 @@ export const certificateV3ServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Edit,
|
ProjectPermissionCertificateActions.Edit,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames ?? undefined,
|
||||||
|
serialNumber: certificate.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!certificate.profileId) {
|
if (!certificate.profileId) {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName, TCertificates } from "@app/db/schemas";
|
import { TableName, TCertificates } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import {
|
||||||
|
applyProcessedPermissionRulesToQuery,
|
||||||
|
type ProcessedPermissionRules
|
||||||
|
} from "@app/lib/knex/permission-filter-utils";
|
||||||
|
|
||||||
import { CertStatus } from "./certificate-types";
|
import { CertStatus } from "./certificate-types";
|
||||||
|
|
||||||
@@ -140,7 +144,8 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findActiveCertificatesForSync = async (
|
const findActiveCertificatesForSync = async (
|
||||||
filter: Partial<TCertificates & { friendlyName?: string; commonName?: string }>,
|
filter: Partial<TCertificates & { friendlyName?: string; commonName?: string }>,
|
||||||
options?: { limit?: number; offset?: number }
|
options?: { limit?: number; offset?: number },
|
||||||
|
permissionFilters?: ProcessedPermissionRules
|
||||||
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
|
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
@@ -163,6 +168,10 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (permissionFilters) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(query, TableName.Certificate, permissionFilters) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.offset) {
|
if (options?.offset) {
|
||||||
query = query.offset(options.offset);
|
query = query.offset(options.offset);
|
||||||
}
|
}
|
||||||
@@ -267,7 +276,8 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findWithPrivateKeyInfo = async (
|
const findWithPrivateKeyInfo = async (
|
||||||
filter: Partial<TCertificates & { friendlyName?: string; commonName?: string }>,
|
filter: Partial<TCertificates & { friendlyName?: string; commonName?: string }>,
|
||||||
options?: { offset?: number; limit?: number; sort?: [string, "asc" | "desc"][] }
|
options?: { offset?: number; limit?: number; sort?: [string, "asc" | "desc"][] },
|
||||||
|
permissionFilters?: ProcessedPermissionRules
|
||||||
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
|
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
@@ -287,6 +297,10 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (permissionFilters) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(query, TableName.Certificate, permissionFilters) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.offset) {
|
if (options?.offset) {
|
||||||
query = query.offset(options.offset);
|
query = query.offset(options.offset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
@@ -108,7 +108,11 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Read,
|
ProjectPermissionCertificateActions.Read,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -140,7 +144,11 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.ReadPrivateKey,
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { certPrivateKey } = await getCertificateCredentials({
|
const { certPrivateKey } = await getCertificateCredentials({
|
||||||
@@ -174,7 +182,11 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Delete,
|
ProjectPermissionCertificateActions.Delete,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const deletedCert = await certificateDAL.deleteById(cert.id);
|
const deletedCert = await certificateDAL.deleteById(cert.id);
|
||||||
@@ -234,7 +246,13 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Delete,
|
ProjectPermissionCertificateActions.Delete,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber,
|
||||||
|
friendlyName: cert.friendlyName,
|
||||||
|
status: cert.status
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cert.status === CertStatus.REVOKED) throw new Error("Certificate already revoked");
|
if (cert.status === CertStatus.REVOKED) throw new Error("Certificate already revoked");
|
||||||
@@ -309,7 +327,11 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Read,
|
ProjectPermissionCertificateActions.Read,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||||
@@ -397,7 +419,7 @@ export const certificateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Create,
|
ProjectPermissionCertificateActions.Import,
|
||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -610,11 +632,23 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.Read,
|
ProjectPermissionCertificateActions.Read,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber,
|
||||||
|
friendlyName: cert.friendlyName,
|
||||||
|
status: cert.status
|
||||||
|
})
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.ReadPrivateKey,
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber,
|
||||||
|
friendlyName: cert.friendlyName,
|
||||||
|
status: cert.status
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||||
@@ -726,7 +760,13 @@ export const certificateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionCertificateActions.ReadPrivateKey,
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
ProjectPermissionSub.Certificates
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: cert.commonName,
|
||||||
|
altNames: cert.altNames ?? undefined,
|
||||||
|
serialNumber: cert.serialNumber,
|
||||||
|
friendlyName: cert.friendlyName,
|
||||||
|
status: cert.status
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get certificate bundle (certificate, chain, private key)
|
// Get certificate bundle (certificate, chain, private key)
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName, TPkiSyncs } from "@app/db/schemas";
|
import { TableName, TPkiSyncs } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
|
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import {
|
||||||
|
applyProcessedPermissionRulesToQuery,
|
||||||
|
type ProcessedPermissionRules
|
||||||
|
} from "@app/lib/knex/permission-filter-utils";
|
||||||
|
|
||||||
import { PkiSync } from "./pki-sync-enums";
|
import { PkiSync } from "./pki-sync-enums";
|
||||||
|
|
||||||
@@ -45,13 +49,15 @@ const basePkiSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: PkiSyncF
|
|||||||
const basePkiSyncWithSubscriberQuery = ({
|
const basePkiSyncWithSubscriberQuery = ({
|
||||||
filter,
|
filter,
|
||||||
db,
|
db,
|
||||||
tx
|
tx,
|
||||||
|
processedRules
|
||||||
}: {
|
}: {
|
||||||
db: TDbClient;
|
db: TDbClient;
|
||||||
filter?: PkiSyncFindFilter;
|
filter?: PkiSyncFindFilter;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
|
processedRules?: ProcessedPermissionRules;
|
||||||
}) => {
|
}) => {
|
||||||
const query = (tx || db.replicaNode())(TableName.PkiSync)
|
let query = (tx || db.replicaNode())(TableName.PkiSync)
|
||||||
.leftJoin(TableName.AppConnection, `${TableName.PkiSync}.connectionId`, `${TableName.AppConnection}.id`)
|
.leftJoin(TableName.AppConnection, `${TableName.PkiSync}.connectionId`, `${TableName.AppConnection}.id`)
|
||||||
.leftJoin(TableName.PkiSubscriber, `${TableName.PkiSync}.subscriberId`, `${TableName.PkiSubscriber}.id`)
|
.leftJoin(TableName.PkiSubscriber, `${TableName.PkiSync}.subscriberId`, `${TableName.PkiSubscriber}.id`)
|
||||||
.select(selectAllTableCols(TableName.PkiSync))
|
.select(selectAllTableCols(TableName.PkiSync))
|
||||||
@@ -82,6 +88,10 @@ const basePkiSyncWithSubscriberQuery = ({
|
|||||||
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.PkiSync, filter)));
|
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.PkiSync, filter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (processedRules) {
|
||||||
|
query = applyProcessedPermissionRulesToQuery(query, TableName.PkiSync, processedRules) as typeof query;
|
||||||
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,9 +194,18 @@ export const pkiSyncDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectIdWithSubscribers = async (projectId: string, tx?: Knex) => {
|
const findByProjectIdWithSubscribers = async (
|
||||||
|
projectId: string,
|
||||||
|
processedRules?: ProcessedPermissionRules,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const pkiSyncs = await basePkiSyncWithSubscriberQuery({ filter: { projectId }, db, tx });
|
const pkiSyncs = await basePkiSyncWithSubscriberQuery({
|
||||||
|
filter: { projectId },
|
||||||
|
db,
|
||||||
|
tx,
|
||||||
|
processedRules
|
||||||
|
});
|
||||||
return pkiSyncs.map(expandPkiSyncWithSubscriber);
|
return pkiSyncs.map(expandPkiSyncWithSubscriber);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find By Project ID With Subscribers - PKI Sync" });
|
throw new DatabaseError({ error, name: "Find By Project ID With Subscribers - PKI Sync" });
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ActionProjectType, TCertificateSyncs } from "@app/db/schemas";
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionPkiSyncActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionPkiSyncActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getProcessedPermissionRules } from "@app/lib/casl/permission-filter-utils";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
@@ -145,9 +146,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.Create,
|
ProjectPermissionPkiSyncActions.Create,
|
||||||
subscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: subscriber.name })
|
subscriberName: subscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the destination app type based on PKI sync destination
|
// Get the destination app type based on PKI sync destination
|
||||||
@@ -235,9 +237,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.Edit,
|
ProjectPermissionPkiSyncActions.Edit,
|
||||||
currentSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: currentSubscriber.name })
|
subscriberName: currentSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (name && name !== pkiSync.name) {
|
if (name && name !== pkiSync.name) {
|
||||||
@@ -331,9 +334,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.Delete,
|
ProjectPermissionPkiSyncActions.Delete,
|
||||||
pkiSyncSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: pkiSyncSubscriber.name })
|
subscriberName: pkiSyncSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return pkiSyncDAL.deleteById(id);
|
return pkiSyncDAL.deleteById(id);
|
||||||
@@ -354,7 +358,13 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs);
|
||||||
|
|
||||||
const pkiSyncsWithSubscribers = await pkiSyncDAL.findByProjectIdWithSubscribers(projectId);
|
const processedRules = getProcessedPermissionRules(
|
||||||
|
permission,
|
||||||
|
ProjectPermissionPkiSyncActions.Read,
|
||||||
|
ProjectPermissionSub.PkiSyncs
|
||||||
|
);
|
||||||
|
|
||||||
|
const pkiSyncsWithSubscribers = await pkiSyncDAL.findByProjectIdWithSubscribers(projectId, processedRules);
|
||||||
|
|
||||||
if (certificateId) {
|
if (certificateId) {
|
||||||
const syncsWithCertificateInfo = await Promise.all(
|
const syncsWithCertificateInfo = await Promise.all(
|
||||||
@@ -406,9 +416,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.Read,
|
ProjectPermissionPkiSyncActions.Read,
|
||||||
findSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: findSubscriber.name })
|
subscriberName: findSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@@ -442,9 +453,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.SyncCertificates,
|
ProjectPermissionPkiSyncActions.SyncCertificates,
|
||||||
syncSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: syncSubscriber.name })
|
subscriberName: syncSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: id });
|
await pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: id });
|
||||||
@@ -483,9 +495,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.ImportCertificates,
|
ProjectPermissionPkiSyncActions.ImportCertificates,
|
||||||
importSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: importSubscriber.name })
|
subscriberName: importSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await pkiSyncQueue.queuePkiSyncImportCertificatesById({ syncId: id });
|
await pkiSyncQueue.queuePkiSyncImportCertificatesById({ syncId: id });
|
||||||
@@ -516,9 +529,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPkiSyncActions.RemoveCertificates,
|
ProjectPermissionPkiSyncActions.RemoveCertificates,
|
||||||
removeSubscriber
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
? subject(ProjectPermissionSub.PkiSyncs, { subscriberName: removeSubscriber.name })
|
subscriberName: removeSubscriber?.name,
|
||||||
: ProjectPermissionSub.PkiSyncs
|
name: pkiSync.name
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await pkiSyncQueue.queuePkiSyncRemoveCertificatesById({ syncId: id });
|
await pkiSyncQueue.queuePkiSyncRemoveCertificatesById({ syncId: id });
|
||||||
@@ -549,7 +563,18 @@ export const pkiSyncServiceFactory = ({
|
|||||||
projectId: pkiSync.projectId
|
projectId: pkiSync.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Edit, ProjectPermissionSub.PkiSyncs);
|
let pkiSyncSubscriber;
|
||||||
|
if (pkiSync.subscriberId) {
|
||||||
|
pkiSyncSubscriber = await pkiSubscriberDAL.findById(pkiSync.subscriberId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSyncActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
|
subscriberName: pkiSyncSubscriber?.name,
|
||||||
|
name: pkiSync.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
await validateCertificatesProjectOwnership(certificateIds, pkiSync.projectId);
|
await validateCertificatesProjectOwnership(certificateIds, pkiSync.projectId);
|
||||||
|
|
||||||
@@ -588,7 +613,12 @@ export const pkiSyncServiceFactory = ({
|
|||||||
projectId: pkiSync.projectId
|
projectId: pkiSync.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Edit, ProjectPermissionSub.PkiSyncs);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSyncActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
|
name: pkiSync.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const removedCount = await certificateSyncDAL.removeCertificates(pkiSyncId, certificateIds);
|
const removedCount = await certificateSyncDAL.removeCertificates(pkiSyncId, certificateIds);
|
||||||
|
|
||||||
@@ -626,7 +656,12 @@ export const pkiSyncServiceFactory = ({
|
|||||||
projectId: pkiSync.projectId
|
projectId: pkiSync.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSyncActions.Read,
|
||||||
|
subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
|
name: pkiSync.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const result = await certificateSyncDAL.findWithDetails({
|
const result = await certificateSyncDAL.findWithDetails({
|
||||||
pkiSyncId,
|
pkiSyncId,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
@@ -39,6 +40,7 @@ import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certific
|
|||||||
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||||
import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
|
import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
|
||||||
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
|
import { getProcessedPermissionRules } from "@app/lib/casl/permission-filter-utils";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||||
@@ -911,7 +913,7 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -963,35 +965,38 @@ export const projectServiceFactory = ({
|
|||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const regularFilters = {
|
||||||
|
projectId,
|
||||||
|
...(friendlyName && { friendlyName }),
|
||||||
|
...(commonName && { commonName })
|
||||||
|
};
|
||||||
|
const permissionFilters = getProcessedPermissionRules(
|
||||||
|
permission,
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
const certificates = forPkiSync
|
const certificates = forPkiSync
|
||||||
? await certificateDAL.findActiveCertificatesForSync(
|
? await certificateDAL.findActiveCertificatesForSync(regularFilters, { offset, limit }, permissionFilters)
|
||||||
{
|
|
||||||
projectId,
|
|
||||||
...(friendlyName && { friendlyName }),
|
|
||||||
...(commonName && { commonName })
|
|
||||||
},
|
|
||||||
{ offset, limit }
|
|
||||||
)
|
|
||||||
: await certificateDAL.findWithPrivateKeyInfo(
|
: await certificateDAL.findWithPrivateKeyInfo(
|
||||||
|
regularFilters,
|
||||||
{
|
{
|
||||||
projectId,
|
offset,
|
||||||
...(friendlyName && { friendlyName }),
|
limit,
|
||||||
...(commonName && { commonName })
|
sort: [["notAfter", "desc"]]
|
||||||
},
|
},
|
||||||
{ offset, limit, sort: [["notAfter", "desc"]] }
|
permissionFilters
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const countFilter = {
|
||||||
|
projectId,
|
||||||
|
...(regularFilters.friendlyName && { friendlyName: String(regularFilters.friendlyName) }),
|
||||||
|
...(regularFilters.commonName && { commonName: String(regularFilters.commonName) })
|
||||||
|
};
|
||||||
|
|
||||||
const count = forPkiSync
|
const count = forPkiSync
|
||||||
? await certificateDAL.countActiveCertificatesForSync({
|
? await certificateDAL.countActiveCertificatesForSync(countFilter)
|
||||||
projectId,
|
: await certificateDAL.countCertificatesInProject(countFilter);
|
||||||
friendlyName,
|
|
||||||
commonName
|
|
||||||
})
|
|
||||||
: await certificateDAL.countCertificatesInProject({
|
|
||||||
projectId,
|
|
||||||
friendlyName,
|
|
||||||
commonName
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificates,
|
certificates,
|
||||||
|
|||||||
@@ -2493,8 +2493,6 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/endpoints/certificates/list",
|
"api-reference/endpoints/certificates/list",
|
||||||
"api-reference/endpoints/certificates/read",
|
"api-reference/endpoints/certificates/read",
|
||||||
"api-reference/endpoints/certificates/issue-certificate",
|
|
||||||
"api-reference/endpoints/certificates/sign-certificate",
|
|
||||||
"api-reference/endpoints/certificates/renew",
|
"api-reference/endpoints/certificates/renew",
|
||||||
"api-reference/endpoints/certificates/update-config",
|
"api-reference/endpoints/certificates/update-config",
|
||||||
"api-reference/endpoints/certificates/revoke",
|
"api-reference/endpoints/certificates/revoke",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionAuditLogsActions,
|
ProjectPermissionAuditLogsActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -12,6 +13,7 @@ export {
|
|||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionPkiSyncActions,
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export enum ProjectPermissionCertificateActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
ReadPrivateKey = "read-private-key"
|
List = "list",
|
||||||
|
ReadPrivateKey = "read-private-key",
|
||||||
|
Import = "import"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
export enum ProjectPermissionSecretActions {
|
||||||
@@ -67,6 +69,7 @@ export enum ProjectPermissionPkiSyncActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
|
List = "list",
|
||||||
SyncCertificates = "sync-certificates",
|
SyncCertificates = "sync-certificates",
|
||||||
ImportCertificates = "import-certificates",
|
ImportCertificates = "import-certificates",
|
||||||
RemoveCertificates = "remove-certificates"
|
RemoveCertificates = "remove-certificates"
|
||||||
@@ -128,13 +131,25 @@ export enum ProjectPermissionPkiTemplateActions {
|
|||||||
ListCerts = "list-certs"
|
ListCerts = "list-certs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionCertificateProfileActions {
|
export enum ProjectPermissionCertificateAuthorityActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
|
List = "list",
|
||||||
|
Renew = "renew",
|
||||||
|
SignIntermediate = "sign-intermediate"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionCertificateProfileActions {
|
||||||
|
Read = "read",
|
||||||
|
List = "list",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
IssueCert = "issue-cert",
|
IssueCert = "issue-cert",
|
||||||
RevealAcmeEabSecret = "reveal-acme-eab-secret"
|
RevealAcmeEabSecret = "reveal-acme-eab-secret",
|
||||||
|
RotateAcmeEabSecret = "rotate-acme-eab-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretRotationActions {
|
export enum ProjectPermissionSecretRotationActions {
|
||||||
@@ -230,6 +245,9 @@ export type ConditionalProjectPermissionSubject =
|
|||||||
| ProjectPermissionSub.SshHosts
|
| ProjectPermissionSub.SshHosts
|
||||||
| ProjectPermissionSub.PkiSubscribers
|
| ProjectPermissionSub.PkiSubscribers
|
||||||
| ProjectPermissionSub.CertificateTemplates
|
| ProjectPermissionSub.CertificateTemplates
|
||||||
|
| ProjectPermissionSub.CertificateAuthorities
|
||||||
|
| ProjectPermissionSub.Certificates
|
||||||
|
| ProjectPermissionSub.CertificateProfiles
|
||||||
| ProjectPermissionSub.SecretFolders
|
| ProjectPermissionSub.SecretFolders
|
||||||
| ProjectPermissionSub.SecretImports
|
| ProjectPermissionSub.SecretImports
|
||||||
| ProjectPermissionSub.SecretRotation
|
| ProjectPermissionSub.SecretRotation
|
||||||
@@ -361,7 +379,22 @@ export type SecretSyncSubjectFields = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PkiSyncSubjectFields = {
|
export type PkiSyncSubjectFields = {
|
||||||
subscriberId: string;
|
subscriberName: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateAuthoritySubjectFields = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateSubjectFields = {
|
||||||
|
commonName?: string;
|
||||||
|
altNames?: string;
|
||||||
|
serialNumber?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CertificateProfileSubjectFields = {
|
||||||
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretRotationSubjectFields = {
|
export type SecretRotationSubjectFields = {
|
||||||
@@ -457,8 +490,21 @@ export type ProjectPermissionSet =
|
|||||||
| (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [
|
||||||
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.CertificateAuthorities
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.CertificateAuthorities> &
|
||||||
|
CertificateAuthoritySubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionCertificateActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.Certificates
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.Certificates> & CertificateSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
(
|
(
|
||||||
@@ -484,7 +530,14 @@ export type ProjectPermissionSet =
|
|||||||
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionCertificateProfileActions, ProjectPermissionSub.CertificateProfiles]
|
| [
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.CertificateProfiles
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> &
|
||||||
|
CertificateProfileSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionAuditLogsActions,
|
ProjectPermissionAuditLogsActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -23,6 +24,7 @@ export {
|
|||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionPkiSyncActions,
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
[EventType.SIGN_INTERMEDIATE]: "Sign intermediate",
|
[EventType.SIGN_INTERMEDIATE]: "Sign intermediate",
|
||||||
[EventType.IMPORT_CA_CERT]: "Import CA certificate",
|
[EventType.IMPORT_CA_CERT]: "Import CA certificate",
|
||||||
[EventType.GET_CA_CRL]: "Get CA CRL",
|
[EventType.GET_CA_CRL]: "Get CA CRL",
|
||||||
[EventType.ISSUE_CERT]: "Issue certificate",
|
[EventType.ISSUE_CERT]: "Request certificate",
|
||||||
[EventType.IMPORT_CERT]: "Import certificate",
|
[EventType.IMPORT_CERT]: "Import certificate",
|
||||||
[EventType.GET_CERT]: "Get certificate",
|
[EventType.GET_CERT]: "Get certificate",
|
||||||
[EventType.DELETE_CERT]: "Delete certificate",
|
[EventType.DELETE_CERT]: "Delete certificate",
|
||||||
@@ -225,7 +225,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
[EventType.UPDATE_PKI_SUBSCRIBER]: "Update PKI subscriber",
|
[EventType.UPDATE_PKI_SUBSCRIBER]: "Update PKI subscriber",
|
||||||
[EventType.DELETE_PKI_SUBSCRIBER]: "Delete PKI subscriber",
|
[EventType.DELETE_PKI_SUBSCRIBER]: "Delete PKI subscriber",
|
||||||
[EventType.GET_PKI_SUBSCRIBER]: "Get PKI subscriber",
|
[EventType.GET_PKI_SUBSCRIBER]: "Get PKI subscriber",
|
||||||
[EventType.ISSUE_PKI_SUBSCRIBER_CERT]: "Issue PKI subscriber certificate",
|
[EventType.ISSUE_PKI_SUBSCRIBER_CERT]: "Request PKI subscriber certificate",
|
||||||
[EventType.SIGN_PKI_SUBSCRIBER_CERT]: "Sign PKI subscriber certificate",
|
[EventType.SIGN_PKI_SUBSCRIBER_CERT]: "Sign PKI subscriber certificate",
|
||||||
[EventType.AUTOMATED_RENEW_SUBSCRIBER_CERT]: "Automated renew PKI subscriber certificate",
|
[EventType.AUTOMATED_RENEW_SUBSCRIBER_CERT]: "Automated renew PKI subscriber certificate",
|
||||||
[EventType.LIST_PKI_SUBSCRIBER_CERTS]: "List PKI subscriber certificates",
|
[EventType.LIST_PKI_SUBSCRIBER_CERTS]: "List PKI subscriber certificates",
|
||||||
@@ -287,7 +287,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
[EventType.DELETE_CERTIFICATE_PROFILE]: "Delete Certificate Profile",
|
[EventType.DELETE_CERTIFICATE_PROFILE]: "Delete Certificate Profile",
|
||||||
[EventType.GET_CERTIFICATE_PROFILE]: "Get Certificate Profile",
|
[EventType.GET_CERTIFICATE_PROFILE]: "Get Certificate Profile",
|
||||||
[EventType.LIST_CERTIFICATE_PROFILES]: "List Certificate Profiles",
|
[EventType.LIST_CERTIFICATE_PROFILES]: "List Certificate Profiles",
|
||||||
[EventType.ISSUE_CERTIFICATE_FROM_PROFILE]: "Issue Certificate From Profile",
|
[EventType.ISSUE_CERTIFICATE_FROM_PROFILE]: "Request Certificate From Profile",
|
||||||
[EventType.SIGN_CERTIFICATE_FROM_PROFILE]: "Sign Certificate From Profile",
|
[EventType.SIGN_CERTIFICATE_FROM_PROFILE]: "Sign Certificate From Profile",
|
||||||
[EventType.ORDER_CERTIFICATE_FROM_PROFILE]: "Order Certificate From Profile",
|
[EventType.ORDER_CERTIFICATE_FROM_PROFILE]: "Order Certificate From Profile",
|
||||||
[EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE]:
|
[EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE]:
|
||||||
|
|||||||
+76
-57
@@ -1,4 +1,5 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
import { subject } from "@casl/ability";
|
||||||
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||||
@@ -7,6 +8,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
|
AccessRestrictedBanner,
|
||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -18,7 +20,7 @@ import {
|
|||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useProject
|
useProject
|
||||||
@@ -88,63 +90,80 @@ const Page = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
{data && (
|
{data && (
|
||||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
<ProjectPermissionCan
|
||||||
<Link
|
I={ProjectPermissionCertificateAuthorityActions.Read}
|
||||||
to="/organizations/$orgId/projects/cert-management/$projectId/certificate-authorities"
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
params={{
|
name: data.name
|
||||||
orgId: currentOrg.id,
|
})}
|
||||||
projectId
|
>
|
||||||
}}
|
{(isAllowed) =>
|
||||||
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
|
isAllowed ? (
|
||||||
>
|
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||||
<FontAwesomeIcon icon={faChevronLeft} />
|
<Link
|
||||||
Certificate Authorities
|
to="/organizations/$orgId/projects/cert-management/$projectId/certificate-authorities"
|
||||||
</Link>
|
params={{
|
||||||
<PageHeader
|
orgId: currentOrg.id,
|
||||||
scope={ProjectType.CertificateManager}
|
projectId
|
||||||
description="Manage certificate authority"
|
}}
|
||||||
title={data.name}
|
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
|
||||||
>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
|
||||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
|
||||||
<Tooltip content="More options">
|
|
||||||
<Button variant="outline_bg">More</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="p-1">
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Delete}
|
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
<FontAwesomeIcon icon={faChevronLeft} />
|
||||||
<DropdownMenuItem
|
Certificate Authorities
|
||||||
className={twMerge(
|
</Link>
|
||||||
isAllowed
|
<PageHeader
|
||||||
? "hover:bg-red-500! hover:text-white!"
|
scope={ProjectType.CertificateManager}
|
||||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
description="Manage certificate authority"
|
||||||
)}
|
title={data.name}
|
||||||
onClick={() => handlePopUpOpen("deleteCa")}
|
>
|
||||||
disabled={!isAllowed}
|
<DropdownMenu>
|
||||||
>
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
Delete CA
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
</DropdownMenuItem>
|
<Tooltip content="More options">
|
||||||
)}
|
<Button variant="outline_bg">More</Button>
|
||||||
</ProjectPermissionCan>
|
</Tooltip>
|
||||||
</DropdownMenuContent>
|
</div>
|
||||||
</DropdownMenu>
|
</DropdownMenuTrigger>
|
||||||
</PageHeader>
|
<DropdownMenuContent align="end" className="p-1">
|
||||||
<div className="flex">
|
<ProjectPermissionCan
|
||||||
<div className="mr-4 w-96">
|
I={ProjectPermissionCertificateAuthorityActions.Delete}
|
||||||
<CaDetailsSection caId={data.id} handlePopUpOpen={handlePopUpOpen} />
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
</div>
|
name: data.name
|
||||||
<div className="w-full">
|
})}
|
||||||
<CaCertificatesSection caId={data.id} />
|
>
|
||||||
<CaCrlsSection caId={data.id} />
|
{(canDelete) => (
|
||||||
</div>
|
<DropdownMenuItem
|
||||||
</div>
|
className={twMerge(
|
||||||
</div>
|
canDelete
|
||||||
|
? "hover:bg-red-500! hover:text-white!"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => handlePopUpOpen("deleteCa")}
|
||||||
|
disabled={!canDelete}
|
||||||
|
>
|
||||||
|
Delete CA
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</PageHeader>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="mr-4 w-96">
|
||||||
|
<CaDetailsSection caId={data.id} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<CaCertificatesSection caId={data.id} caName={data.name} />
|
||||||
|
<CaCrlsSection caId={data.id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="container mx-auto flex h-full items-center justify-center">
|
||||||
|
<AccessRestrictedBanner />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
)}
|
)}
|
||||||
<CaModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<CaModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
<CaRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<CaRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
|||||||
+3
-2
@@ -2,9 +2,10 @@ import { CaCertificatesTable } from "./CaCertificatesTable";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
caId: string;
|
caId: string;
|
||||||
|
caName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CaCertificatesSection = ({ caId }: Props) => {
|
export const CaCertificatesSection = ({ caId, caName }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
@@ -21,7 +22,7 @@ export const CaCertificatesSection = ({ caId }: Props) => {
|
|||||||
</IconButton> */}
|
</IconButton> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<CaCertificatesTable caId={caId} />
|
<CaCertificatesTable caId={caId} caName={caName} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+12
-6
@@ -1,3 +1,4 @@
|
|||||||
|
import { subject } from "@casl/ability";
|
||||||
import { faCertificate, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
import { faCertificate, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
@@ -22,14 +23,15 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionCertificateAuthorityActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { useGetCaCerts } from "@app/hooks/api";
|
import { useGetCaCerts } from "@app/hooks/api";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
caId: string;
|
caId: string;
|
||||||
|
caName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CaCertificatesTable = ({ caId }: Props) => {
|
export const CaCertificatesTable = ({ caId, caName }: Props) => {
|
||||||
const { data: caCerts, isPending } = useGetCaCerts(caId);
|
const { data: caCerts, isPending } = useGetCaCerts(caId);
|
||||||
|
|
||||||
const downloadTxtFile = (filename: string, content: string) => {
|
const downloadTxtFile = (filename: string, content: string) => {
|
||||||
@@ -77,8 +79,10 @@ export const CaCertificatesTable = ({ caId }: Props) => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionCertificateAuthorityActions.Read}
|
||||||
a={ProjectPermissionSub.Identity}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: caName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -96,8 +100,10 @@ export const CaCertificatesTable = ({ caId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionCertificateAuthorityActions.Read}
|
||||||
a={ProjectPermissionSub.Identity}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: caName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
+10
-6
@@ -1,10 +1,11 @@
|
|||||||
|
import { subject } from "@casl/ability";
|
||||||
import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, IconButton, Tooltip } from "@app/components/v2";
|
import { Button, IconButton, Tooltip } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionCertificateAuthorityActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import { CaStatus, CaType, InternalCaType, useGetCa } from "@app/hooks/api";
|
import { CaStatus, CaType, InternalCaType, useGetCa } from "@app/hooks/api";
|
||||||
import { caStatusToNameMap, caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
import { caStatusToNameMap, caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
||||||
@@ -39,7 +40,10 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
|
|||||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
<h3 className="text-lg font-medium text-mineshaft-100">CA Details</h3>
|
<h3 className="text-lg font-medium text-mineshaft-100">CA Details</h3>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Identity}>
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionCertificateAuthorityActions.Edit}
|
||||||
|
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
|
||||||
|
>
|
||||||
{(isAllowed) => {
|
{(isAllowed) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip content="Edit CA">
|
<Tooltip content="Edit CA">
|
||||||
@@ -154,8 +158,8 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
{ca.status === CaStatus.ACTIVE && (
|
{ca.status === CaStatus.ACTIVE && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionCertificateAuthorityActions.Renew}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
|
||||||
>
|
>
|
||||||
{(isAllowed) => {
|
{(isAllowed) => {
|
||||||
return (
|
return (
|
||||||
@@ -190,8 +194,8 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{ca.status === CaStatus.PENDING_CERTIFICATE && (
|
{ca.status === CaStatus.PENDING_CERTIFICATE && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionCertificateAuthorityActions.Create}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
|
||||||
>
|
>
|
||||||
{(isAllowed) => {
|
{(isAllowed) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
import {
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProject
|
||||||
|
} from "@app/context";
|
||||||
import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api";
|
import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -57,7 +61,7 @@ export const CaSection = () => {
|
|||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-medium text-mineshaft-100">Internal Certificate Authorities</p>
|
<p className="text-xl font-medium text-mineshaft-100">Internal Certificate Authorities</p>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionCertificateAuthorityActions.Create}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={ProjectPermissionSub.CertificateAuthorities}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
@@ -100,6 +104,7 @@ export const CaSection = () => {
|
|||||||
: "This action will prevent the CA from issuing new certificates."
|
: "This action will prevent the CA from issuing new certificates."
|
||||||
}
|
}
|
||||||
onChange={(isOpen) => handlePopUpToggle("caStatus", isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("caStatus", isOpen)}
|
||||||
|
buttonText="Confirm"
|
||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() =>
|
onDeleteApproved={() =>
|
||||||
onUpdateCaStatus(popUp?.caStatus?.data as { caId: string; status: CaStatus })
|
onUpdateCaStatus(popUp?.caStatus?.data as { caId: string; status: CaStatus })
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { subject } from "@casl/ability";
|
||||||
import { faBan, faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { faBan, faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
@@ -23,10 +24,11 @@ import {
|
|||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useProject
|
useProject,
|
||||||
|
useProjectPermission
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { CaStatus, CaType, useListCasByTypeAndProjectId } from "@app/hooks/api";
|
import { CaStatus, CaType, useListCasByTypeAndProjectId } from "@app/hooks/api";
|
||||||
import {
|
import {
|
||||||
@@ -53,6 +55,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
const { data, isPending } = useListCasByTypeAndProjectId(CaType.INTERNAL, currentProject.id);
|
const { data, isPending } = useListCasByTypeAndProjectId(CaType.INTERNAL, currentProject.id);
|
||||||
const cas = data as TInternalCertificateAuthority[];
|
const cas = data as TInternalCertificateAuthority[];
|
||||||
|
|
||||||
@@ -75,11 +78,23 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
cas &&
|
cas &&
|
||||||
cas.length > 0 &&
|
cas.length > 0 &&
|
||||||
cas.map((ca) => {
|
cas.map((ca) => {
|
||||||
|
const canReadCa = permission.can(
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
className={twMerge(
|
||||||
|
"h-10 transition-colors duration-100",
|
||||||
|
canReadCa && "cursor-pointer hover:bg-mineshaft-700",
|
||||||
|
!canReadCa && "cursor-not-allowed opacity-60"
|
||||||
|
)}
|
||||||
key={`ca-${ca.id}`}
|
key={`ca-${ca.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
canReadCa &&
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organizations/$orgId/projects/cert-management/$projectId/ca/$caId",
|
to: "/organizations/$orgId/projects/cert-management/$projectId/ca/$caId",
|
||||||
params: {
|
params: {
|
||||||
@@ -118,8 +133,10 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
{ca.status === CaStatus.PENDING_CERTIFICATE && (
|
{ca.status === CaStatus.PENDING_CERTIFICATE && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionCertificateAuthorityActions.Create}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -143,8 +160,10 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{ca.status !== CaStatus.PENDING_CERTIFICATE && (
|
{ca.status !== CaStatus.PENDING_CERTIFICATE && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Read}
|
I={ProjectPermissionCertificateAuthorityActions.Read}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -168,8 +187,10 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{(ca.status === CaStatus.ACTIVE || ca.status === CaStatus.DISABLED) && (
|
{(ca.status === CaStatus.ACTIVE || ca.status === CaStatus.DISABLED) && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionCertificateAuthorityActions.Edit}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -196,8 +217,10 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
)}
|
)}
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionCertificateAuthorityActions.Delete}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
+6
-2
@@ -4,7 +4,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
import {
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProject
|
||||||
|
} from "@app/context";
|
||||||
import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api";
|
import { CaStatus, CaType, useDeleteCa, useUpdateCa } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -61,7 +65,7 @@ export const ExternalCaSection = () => {
|
|||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-medium text-mineshaft-100">External Certificate Authorities</p>
|
<p className="text-xl font-medium text-mineshaft-100">External Certificate Authorities</p>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionCertificateAuthorityActions.Create}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={ProjectPermissionSub.CertificateAuthorities}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
+36
-11
@@ -1,3 +1,4 @@
|
|||||||
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
faBan,
|
faBan,
|
||||||
faCertificate,
|
faCertificate,
|
||||||
@@ -26,7 +27,12 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
import {
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProject,
|
||||||
|
useProjectPermission
|
||||||
|
} from "@app/context";
|
||||||
import { CaStatus, CaType, useListExternalCasByProjectId } from "@app/hooks/api";
|
import { CaStatus, CaType, useListExternalCasByProjectId } from "@app/hooks/api";
|
||||||
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
@@ -45,6 +51,7 @@ type Props = {
|
|||||||
|
|
||||||
export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
const { data, isPending } = useListExternalCasByProjectId(currentProject.id);
|
const { data, isPending } = useListExternalCasByProjectId(currentProject.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -65,17 +72,29 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
data &&
|
data &&
|
||||||
data.length > 0 &&
|
data.length > 0 &&
|
||||||
data.map((ca) => {
|
data.map((ca) => {
|
||||||
|
const canEditCa = permission.can(
|
||||||
|
ProjectPermissionCertificateAuthorityActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
className={twMerge(
|
||||||
|
"h-10 transition-colors duration-100",
|
||||||
|
canEditCa && "cursor-pointer hover:bg-mineshaft-700",
|
||||||
|
!canEditCa && "cursor-not-allowed opacity-60"
|
||||||
|
)}
|
||||||
key={`ca-${ca.id}`}
|
key={`ca-${ca.id}`}
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
|
canEditCa &&
|
||||||
handlePopUpOpen("ca", {
|
handlePopUpOpen("ca", {
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
name: ca.name,
|
name: ca.name,
|
||||||
type: ca.type
|
type: ca.type
|
||||||
});
|
})
|
||||||
}}
|
}
|
||||||
>
|
>
|
||||||
<Td>{ca.name}</Td>
|
<Td>{ca.name}</Td>
|
||||||
<Td>{ca.type}</Td>
|
<Td>{ca.type}</Td>
|
||||||
@@ -95,8 +114,10 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionCertificateAuthorityActions.Edit}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -120,8 +141,10 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
{(ca.status === CaStatus.ACTIVE || ca.status === CaStatus.DISABLED) && (
|
{(ca.status === CaStatus.ACTIVE || ca.status === CaStatus.DISABLED) && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionCertificateAuthorityActions.Edit}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -149,8 +172,10 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
)}
|
)}
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionCertificateAuthorityActions.Delete}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={subject(ProjectPermissionSub.CertificateAuthorities, {
|
||||||
|
name: ca.name
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle, preselectedTemplate
|
|||||||
setCertificateDetails(null);
|
setCertificateDetails(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title={`${cert ? "View" : "Issue"} Certificate`}>
|
<ModalContent title={`${cert ? "View" : "Request"} Certificate`}>
|
||||||
{!certificateDetails ? (
|
{!certificateDetails ? (
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
|
|||||||
+17
-9
@@ -6,6 +6,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
|||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useProject
|
useProject
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
@@ -104,12 +105,12 @@ export const CertificatesSection = () => {
|
|||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-medium text-mineshaft-100">Certificates</p>
|
<p className="text-xl font-medium text-mineshaft-100">Certificates</p>
|
||||||
<ProjectPermissionCan
|
<div className="flex gap-2">
|
||||||
I={ProjectPermissionCertificateActions.Create}
|
<ProjectPermissionCan
|
||||||
a={ProjectPermissionSub.Certificates}
|
I={ProjectPermissionCertificateActions.Import}
|
||||||
>
|
a={ProjectPermissionSub.Certificates}
|
||||||
{(isAllowed) => (
|
>
|
||||||
<div className="flex gap-2">
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
leftIcon={<FontAwesomeIcon icon={faArrowRight} />}
|
leftIcon={<FontAwesomeIcon icon={faArrowRight} />}
|
||||||
@@ -118,6 +119,13 @@ export const CertificatesSection = () => {
|
|||||||
>
|
>
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionCertificateProfileActions.IssueCert}
|
||||||
|
a={ProjectPermissionSub.CertificateProfiles}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -127,9 +135,9 @@ export const CertificatesSection = () => {
|
|||||||
>
|
>
|
||||||
Request
|
Request
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
)}
|
</ProjectPermissionCan>
|
||||||
</ProjectPermissionCan>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
|||||||
+116
-60
@@ -1,4 +1,5 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
faBan,
|
faBan,
|
||||||
faCertificate,
|
faCertificate,
|
||||||
@@ -40,8 +41,10 @@ import {
|
|||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionPkiSyncActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useProject
|
useProject,
|
||||||
|
useProjectPermission
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { useUpdateRenewalConfig } from "@app/hooks/api";
|
import { useUpdateRenewalConfig } from "@app/hooks/api";
|
||||||
import { caSupportsCapability } from "@app/hooks/api/ca/constants";
|
import { caSupportsCapability } from "@app/hooks/api/ca/constants";
|
||||||
@@ -96,6 +99,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||||
|
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
const { data, isPending } = useListWorkspaceCertificates({
|
const { data, isPending } = useListWorkspaceCertificates({
|
||||||
projectId: currentProject?.id ?? "",
|
projectId: currentProject?.id ?? "",
|
||||||
offset: (page - 1) * perPage,
|
offset: (page - 1) * perPage,
|
||||||
@@ -207,55 +211,72 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
return "opacity-0 group-hover:opacity-100";
|
return "opacity-0 group-hover:opacity-100";
|
||||||
})()}`}
|
})()}`}
|
||||||
>
|
>
|
||||||
{canShowAutoRenewalIcon && (
|
{canShowAutoRenewalIcon &&
|
||||||
<Tooltip
|
(() => {
|
||||||
content={(() => {
|
const canEditCertificate = permission.can(
|
||||||
if (hasFailed && certificate.renewalError) {
|
ProjectPermissionCertificateActions.Edit,
|
||||||
return `Auto-renewal failed: ${certificate.renewalError}`;
|
subject(ProjectPermissionSub.Certificates, {
|
||||||
}
|
commonName: certificate.commonName,
|
||||||
if (isAutoRenewalEnabled) {
|
altNames: certificate.altNames,
|
||||||
const expiryDate = new Date(certificate.notAfter);
|
serialNumber: certificate.serialNumber
|
||||||
const now = new Date();
|
})
|
||||||
const daysUntilExpiry = Math.ceil(
|
);
|
||||||
(expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
|
|
||||||
);
|
|
||||||
const daysUntilRenewal = Math.max(
|
|
||||||
0,
|
|
||||||
daysUntilExpiry - (certificate.renewBeforeDays || 0)
|
|
||||||
);
|
|
||||||
return `Auto-renews in ${daysUntilRenewal}d`;
|
|
||||||
}
|
|
||||||
return "Set auto renewal";
|
|
||||||
})()}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={(() => {
|
|
||||||
if (hasFailed) return "pr-1 text-red-500 hover:text-red-400";
|
|
||||||
return "pr-1 text-primary-500 hover:text-primary-400";
|
|
||||||
})()}
|
|
||||||
aria-label="Certificate auto-renewal"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (hasFailed) return;
|
|
||||||
|
|
||||||
handlePopUpOpen("manageRenewal", {
|
return (
|
||||||
certificateId: certificate.id,
|
<Tooltip
|
||||||
commonName: originalDisplayName,
|
content={(() => {
|
||||||
profileId: certificate.profileId || "",
|
if (hasFailed && certificate.renewalError) {
|
||||||
renewBeforeDays: certificate.renewBeforeDays || 7,
|
return `Auto-renewal failed: ${certificate.renewalError}`;
|
||||||
ttlDays: Math.ceil(
|
}
|
||||||
(new Date(certificate.notAfter).getTime() -
|
if (isAutoRenewalEnabled) {
|
||||||
new Date(certificate.notBefore).getTime()) /
|
const expiryDate = new Date(certificate.notAfter);
|
||||||
(24 * 60 * 60 * 1000)
|
const now = new Date();
|
||||||
)
|
const daysUntilExpiry = Math.ceil(
|
||||||
});
|
(expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
|
||||||
}}
|
);
|
||||||
>
|
const daysUntilRenewal = Math.max(
|
||||||
<FontAwesomeIcon icon={faClockRotateLeft} />
|
0,
|
||||||
</button>
|
daysUntilExpiry - (certificate.renewBeforeDays || 0)
|
||||||
</Tooltip>
|
);
|
||||||
)}
|
return `Auto-renews in ${daysUntilRenewal}d`;
|
||||||
|
}
|
||||||
|
return "Set auto renewal";
|
||||||
|
})()}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={(() => {
|
||||||
|
if (hasFailed) return "pr-1 text-red-500 hover:text-red-400";
|
||||||
|
return "pr-1 text-primary-500 hover:text-primary-400";
|
||||||
|
})()}
|
||||||
|
aria-label="Certificate auto-renewal"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!canEditCertificate) return;
|
||||||
|
if (hasFailed) return;
|
||||||
|
|
||||||
|
handlePopUpOpen("manageRenewal", {
|
||||||
|
certificateId: certificate.id,
|
||||||
|
commonName: originalDisplayName,
|
||||||
|
profileId: certificate.profileId || "",
|
||||||
|
renewBeforeDays: certificate.renewBeforeDays || 7,
|
||||||
|
ttlDays: Math.ceil(
|
||||||
|
(new Date(certificate.notAfter).getTime() -
|
||||||
|
new Date(certificate.notBefore).getTime()) /
|
||||||
|
(24 * 60 * 60 * 1000)
|
||||||
|
),
|
||||||
|
notAfter: certificate.notAfter,
|
||||||
|
renewalError: certificate.renewalError,
|
||||||
|
renewedFromCertificateId: certificate.renewedFromCertificateId,
|
||||||
|
renewedByCertificateId: certificate.renewedByCertificateId
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faClockRotateLeft} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
@@ -268,7 +289,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Read}
|
I={ProjectPermissionCertificateActions.Read}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -291,7 +317,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
{isLegacyTemplatesEnabled && (
|
{isLegacyTemplatesEnabled && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Read}
|
I={ProjectPermissionCertificateActions.Read}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -327,7 +358,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Edit}
|
I={ProjectPermissionCertificateActions.Edit}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => {
|
{(isAllowed) => {
|
||||||
return (
|
return (
|
||||||
@@ -391,7 +427,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Edit}
|
I={ProjectPermissionCertificateActions.Edit}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -428,7 +469,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Edit}
|
I={ProjectPermissionCertificateActions.Edit}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -455,8 +501,8 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
{certificate.status === CertStatus.ACTIVE &&
|
{certificate.status === CertStatus.ACTIVE &&
|
||||||
!certificate.renewedByCertificateId && (
|
!certificate.renewedByCertificateId && (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Edit}
|
I={ProjectPermissionPkiSyncActions.Edit}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={ProjectPermissionSub.PkiSyncs}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -478,21 +524,26 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
)}
|
)}
|
||||||
{/* Only show revoke button if CA supports revocation */}
|
{/* Only show revoke button if CA supports revocation and certificate is not already revoked */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const caType = caCapabilityMap[certificate.caId];
|
const caType = caCapabilityMap[certificate.caId];
|
||||||
const supportsRevocation =
|
const supportsRevocation =
|
||||||
!caType ||
|
!caType ||
|
||||||
caSupportsCapability(caType, CaCapability.REVOKE_CERTIFICATES);
|
caSupportsCapability(caType, CaCapability.REVOKE_CERTIFICATES);
|
||||||
|
|
||||||
if (!supportsRevocation) {
|
if (!supportsRevocation || isRevoked) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Delete}
|
I={ProjectPermissionCertificateActions.Delete}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -516,7 +567,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
})()}
|
})()}
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionCertificateActions.Delete}
|
I={ProjectPermissionCertificateActions.Delete}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={subject(ProjectPermissionSub.Certificates, {
|
||||||
|
commonName: certificate.commonName,
|
||||||
|
altNames: certificate.altNames,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
friendlyName: certificate.friendlyName
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
|||||||
+267
-245
@@ -67,7 +67,6 @@ export const PkiSyncRow = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
subscriberId,
|
|
||||||
lastSyncMessage,
|
lastSyncMessage,
|
||||||
destination,
|
destination,
|
||||||
lastSyncedAt,
|
lastSyncedAt,
|
||||||
@@ -117,263 +116,286 @@ export const PkiSyncRow = ({
|
|||||||
const destinationDetails = PKI_SYNC_MAP[destination];
|
const destinationDetails = PKI_SYNC_MAP[destination];
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr
|
<ProjectPermissionCan I={ProjectPermissionPkiSyncActions.Read} a={permissionSubject}>
|
||||||
onClick={() => {
|
{(isAllowed: boolean) => {
|
||||||
// console.log("PKI Sync navigation:", { syncId: id, projectId });
|
return (
|
||||||
navigate({
|
<Tr
|
||||||
to: ROUTE_PATHS.CertManager.PkiSyncDetailsByIDPage.path,
|
onClick={() => {
|
||||||
params: {
|
if (!isAllowed) {
|
||||||
syncId: id,
|
return;
|
||||||
projectId,
|
|
||||||
orgId: currentOrg.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className={twMerge(
|
|
||||||
"group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700",
|
|
||||||
syncStatus === PkiSyncStatus.Failed && "bg-red/5 hover:bg-red/10"
|
|
||||||
)}
|
|
||||||
key={`sync-${id}`}
|
|
||||||
>
|
|
||||||
<Td>
|
|
||||||
<img
|
|
||||||
alt={`${destinationDetails.name} sync`}
|
|
||||||
src={`/images/integrations/${destinationDetails.image}`}
|
|
||||||
className="min-w-7"
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
<Td className="max-w-0 min-w-32!">
|
|
||||||
<div>
|
|
||||||
<div className="flex w-full items-center">
|
|
||||||
<p className="truncate">{name}</p>
|
|
||||||
{description && (
|
|
||||||
<Tooltip content={description}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faInfoCircle}
|
|
||||||
size="xs"
|
|
||||||
className="ml-1 text-mineshaft-400"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="truncate text-xs leading-4 text-bunker-300">{destinationDetails.name}</p>
|
|
||||||
</div>
|
|
||||||
</Td>
|
|
||||||
<PkiSyncDestinationCol pkiSync={pkiSync} />
|
|
||||||
<Td>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{syncStatus && (
|
|
||||||
<Tooltip
|
|
||||||
position="left"
|
|
||||||
className="max-w-sm"
|
|
||||||
content={
|
|
||||||
[PkiSyncStatus.Succeeded, PkiSyncStatus.Failed].includes(syncStatus) ? (
|
|
||||||
<div className="flex flex-col gap-2 py-1 whitespace-normal">
|
|
||||||
{lastSyncedAt && (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className={`mb-2 flex self-start ${syncStatus === PkiSyncStatus.Failed ? "text-yellow" : "text-green"}`}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faCalendarCheck}
|
|
||||||
className="ml-1 pt-0.5 pr-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<div className="text-xs">Last Synced</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">
|
|
||||||
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{failureMessage && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 flex self-start text-red">
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
|
|
||||||
<div className="text-xs">Failure Reason</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words">
|
|
||||||
{failureMessage}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : undefined
|
|
||||||
}
|
}
|
||||||
>
|
// console.log("PKI Sync navigation:", { syncId: id, projectId });
|
||||||
|
navigate({
|
||||||
|
to: ROUTE_PATHS.CertManager.PkiSyncDetailsByIDPage.path,
|
||||||
|
params: {
|
||||||
|
syncId: id,
|
||||||
|
projectId,
|
||||||
|
orgId: currentOrg.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={twMerge(
|
||||||
|
"group h-10 transition-colors duration-100 hover:bg-mineshaft-700",
|
||||||
|
syncStatus === PkiSyncStatus.Failed && "bg-red/5 hover:bg-red/10",
|
||||||
|
isAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
||||||
|
)}
|
||||||
|
key={`sync-${id}`}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<img
|
||||||
|
alt={`${destinationDetails.name} sync`}
|
||||||
|
src={`/images/integrations/${destinationDetails.image}`}
|
||||||
|
className="min-w-7"
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td className="max-w-0 min-w-32!">
|
||||||
<div>
|
<div>
|
||||||
<PkiSyncStatusBadge status={syncStatus} />
|
<div className="flex w-full items-center">
|
||||||
</div>
|
<p className="truncate">{name}</p>
|
||||||
</Tooltip>
|
{description && (
|
||||||
)}
|
<Tooltip content={description}>
|
||||||
{!isAutoSyncEnabled && (
|
<FontAwesomeIcon
|
||||||
<Tooltip
|
icon={faInfoCircle}
|
||||||
className="text-xs"
|
size="xs"
|
||||||
content="Auto-Sync is disabled. Certificate changes in the PKI subscriber will not be automatically synced to the destination."
|
className="ml-1 text-mineshaft-400"
|
||||||
>
|
/>
|
||||||
<Badge variant="neutral">
|
|
||||||
<BanIcon />
|
|
||||||
{!syncStatus && "Auto-Sync Disabled"}
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{syncOption?.canImportCertificates && <PkiSyncImportStatusBadge mini pkiSync={pkiSync} />}
|
|
||||||
<PkiSyncRemoveStatusBadge mini pkiSync={pkiSync} />
|
|
||||||
</div>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Tooltip className="max-w-sm text-center" content="Options">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="Options"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="w-6"
|
|
||||||
variant="plain"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faEllipsisV} />
|
|
||||||
</IconButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent sideOffset={2} align="end">
|
|
||||||
<DropdownMenuItem
|
|
||||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleCopyId();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy Sync ID
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionPkiSyncActions.SyncCertificates}
|
|
||||||
a={permissionSubject}
|
|
||||||
>
|
|
||||||
{(isAllowed: boolean) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
icon={<FontAwesomeIcon icon={faRotate} />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onTriggerSyncCertificates(pkiSync);
|
|
||||||
}}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
position="left"
|
|
||||||
sideOffset={42}
|
|
||||||
content={`Manually trigger a sync for this ${destinationName} destination.`}
|
|
||||||
>
|
|
||||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
|
||||||
<span> Trigger Sync</span>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="text-bunker-300"
|
|
||||||
size="sm"
|
|
||||||
icon={faInfoCircle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</DropdownMenuItem>
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="truncate text-xs leading-4 text-bunker-300">
|
||||||
|
{destinationDetails.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<PkiSyncDestinationCol pkiSync={pkiSync} />
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{syncStatus && (
|
||||||
|
<Tooltip
|
||||||
|
position="left"
|
||||||
|
className="max-w-sm"
|
||||||
|
content={
|
||||||
|
[PkiSyncStatus.Succeeded, PkiSyncStatus.Failed].includes(syncStatus) ? (
|
||||||
|
<div className="flex flex-col gap-2 py-1 whitespace-normal">
|
||||||
|
{lastSyncedAt && (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={`mb-2 flex self-start ${syncStatus === PkiSyncStatus.Failed ? "text-yellow" : "text-green"}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCalendarCheck}
|
||||||
|
className="ml-1 pt-0.5 pr-1.5 text-sm"
|
||||||
|
/>
|
||||||
|
<div className="text-xs">Last Synced</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">
|
||||||
|
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{failureMessage && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-2 flex self-start text-red">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faXmark}
|
||||||
|
className="ml-1 pt-0.5 pr-1.5 text-sm"
|
||||||
|
/>
|
||||||
|
<div className="text-xs">Failure Reason</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words">
|
||||||
|
{failureMessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<PkiSyncStatusBadge status={syncStatus} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
{!isAutoSyncEnabled && (
|
||||||
{syncOption?.canImportCertificates && (
|
<Tooltip
|
||||||
<ProjectPermissionCan
|
className="text-xs"
|
||||||
I={ProjectPermissionPkiSyncActions.ImportCertificates}
|
content="Auto-Sync is disabled. Certificate changes in the PKI subscriber will not be automatically synced to the destination."
|
||||||
a={permissionSubject}
|
>
|
||||||
>
|
<Badge variant="neutral">
|
||||||
{(isAllowed: boolean) => (
|
<BanIcon />
|
||||||
|
{!syncStatus && "Auto-Sync Disabled"}
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{syncOption?.canImportCertificates && (
|
||||||
|
<PkiSyncImportStatusBadge mini pkiSync={pkiSync} />
|
||||||
|
)}
|
||||||
|
<PkiSyncRemoveStatusBadge mini pkiSync={pkiSync} />
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tooltip className="max-w-sm text-center" content="Options">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Options"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="w-6"
|
||||||
|
variant="plain"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEllipsisV} />
|
||||||
|
</IconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent sideOffset={2} align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
icon={<FontAwesomeIcon icon={faDownload} />}
|
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onTriggerImportCertificates(pkiSync);
|
handleCopyId();
|
||||||
}}
|
}}
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
>
|
||||||
<Tooltip
|
Copy Sync ID
|
||||||
position="left"
|
|
||||||
sideOffset={42}
|
|
||||||
content={`Import certificates from this ${destinationName} destination into Infisical.`}
|
|
||||||
>
|
|
||||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
|
||||||
<span>Import Certificates</span>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="text-bunker-300"
|
|
||||||
size="sm"
|
|
||||||
icon={faInfoCircle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
<ProjectPermissionCan
|
||||||
</ProjectPermissionCan>
|
I={ProjectPermissionPkiSyncActions.SyncCertificates}
|
||||||
)}
|
a={permissionSubject}
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionPkiSyncActions.RemoveCertificates}
|
|
||||||
a={permissionSubject}
|
|
||||||
>
|
|
||||||
{(isAllowed: boolean) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
icon={<FontAwesomeIcon icon={faEraser} />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onTriggerRemoveCertificates(pkiSync);
|
|
||||||
}}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
position="left"
|
|
||||||
sideOffset={42}
|
|
||||||
content={`Remove certificates synced by Infisical from this ${destinationName} destination.`}
|
|
||||||
>
|
>
|
||||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
{(allowed: boolean) => (
|
||||||
<span>Remove Certificates</span>
|
<DropdownMenuItem
|
||||||
<FontAwesomeIcon
|
icon={<FontAwesomeIcon icon={faRotate} />}
|
||||||
className="text-bunker-300"
|
onClick={(e) => {
|
||||||
size="sm"
|
e.stopPropagation();
|
||||||
icon={faInfoCircle}
|
onTriggerSyncCertificates(pkiSync);
|
||||||
/>
|
}}
|
||||||
</div>
|
isDisabled={!allowed}
|
||||||
</Tooltip>
|
>
|
||||||
</DropdownMenuItem>
|
<Tooltip
|
||||||
)}
|
position="left"
|
||||||
</ProjectPermissionCan>
|
sideOffset={42}
|
||||||
<ProjectPermissionCan I={ProjectPermissionPkiSyncActions.Edit} a={permissionSubject}>
|
content={`Manually trigger a sync for this ${destinationName} destination.`}
|
||||||
{(isAllowed: boolean) => (
|
>
|
||||||
<DropdownMenuItem
|
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||||
isDisabled={!isAllowed}
|
<span> Trigger Sync</span>
|
||||||
icon={<FontAwesomeIcon icon={isAutoSyncEnabled ? faToggleOff : faToggleOn} />}
|
<FontAwesomeIcon
|
||||||
onClick={(e) => {
|
className="text-bunker-300"
|
||||||
e.stopPropagation();
|
size="sm"
|
||||||
onToggleEnable(pkiSync);
|
icon={faInfoCircle}
|
||||||
}}
|
/>
|
||||||
>
|
</div>
|
||||||
{isAutoSyncEnabled ? "Disable" : "Enable"} Auto-Sync
|
</Tooltip>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
{syncOption?.canImportCertificates && (
|
||||||
I={ProjectPermissionPkiSyncActions.Delete}
|
<ProjectPermissionCan
|
||||||
a={permissionSubject}
|
I={ProjectPermissionPkiSyncActions.ImportCertificates}
|
||||||
>
|
a={permissionSubject}
|
||||||
{(isAllowed: boolean) => (
|
>
|
||||||
<DropdownMenuItem
|
{(allowed: boolean) => (
|
||||||
isDisabled={!isAllowed}
|
<DropdownMenuItem
|
||||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
icon={<FontAwesomeIcon icon={faDownload} />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete(pkiSync);
|
onTriggerImportCertificates(pkiSync);
|
||||||
}}
|
}}
|
||||||
>
|
isDisabled={!allowed}
|
||||||
Delete Sync
|
>
|
||||||
</DropdownMenuItem>
|
<Tooltip
|
||||||
)}
|
position="left"
|
||||||
</ProjectPermissionCan>
|
sideOffset={42}
|
||||||
</DropdownMenuContent>
|
content={`Import certificates from this ${destinationName} destination into Infisical.`}
|
||||||
</DropdownMenu>
|
>
|
||||||
</Tooltip>
|
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||||
</Td>
|
<span>Import Certificates</span>
|
||||||
</Tr>
|
<FontAwesomeIcon
|
||||||
|
className="text-bunker-300"
|
||||||
|
size="sm"
|
||||||
|
icon={faInfoCircle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
)}
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSyncActions.RemoveCertificates}
|
||||||
|
a={permissionSubject}
|
||||||
|
>
|
||||||
|
{(allowed: boolean) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
icon={<FontAwesomeIcon icon={faEraser} />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onTriggerRemoveCertificates(pkiSync);
|
||||||
|
}}
|
||||||
|
isDisabled={!allowed}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
position="left"
|
||||||
|
sideOffset={42}
|
||||||
|
content={`Remove certificates synced by Infisical from this ${destinationName} destination.`}
|
||||||
|
>
|
||||||
|
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||||
|
<span>Remove Certificates</span>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className="text-bunker-300"
|
||||||
|
size="sm"
|
||||||
|
icon={faInfoCircle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSyncActions.Edit}
|
||||||
|
a={permissionSubject}
|
||||||
|
>
|
||||||
|
{(allowed: boolean) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
isDisabled={!allowed}
|
||||||
|
icon={
|
||||||
|
<FontAwesomeIcon icon={isAutoSyncEnabled ? faToggleOff : faToggleOn} />
|
||||||
|
}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onToggleEnable(pkiSync);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isAutoSyncEnabled ? "Disable" : "Enable"} Auto-Sync
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSyncActions.Delete}
|
||||||
|
a={permissionSubject}
|
||||||
|
>
|
||||||
|
{(allowed: boolean) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
isDisabled={!allowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete(pkiSync);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete Sync
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Tooltip>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ProjectPermissionCan>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-2
@@ -53,7 +53,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
|
export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
|
||||||
const { destination, subscriberId, projectId, id } = pkiSync;
|
const { destination, projectId, id } = pkiSync;
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||||
@@ -112,7 +112,8 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
|
|||||||
}, [updatePkiSyncMutation, id, projectId, pkiSync.isAutoSyncEnabled]);
|
}, [updatePkiSyncMutation, id, projectId, pkiSync.isAutoSyncEnabled]);
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+5
-1
@@ -32,6 +32,7 @@ import {
|
|||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
|
||||||
import { useListPkiSyncCertificates, useRemoveCertificatesFromPkiSync } from "@app/hooks/api";
|
import { useListPkiSyncCertificates, useRemoveCertificatesFromPkiSync } from "@app/hooks/api";
|
||||||
import { CertificateSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
import { CertificateSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||||
|
|
||||||
@@ -84,8 +85,11 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
|||||||
const totalCount = data?.totalCount || 0;
|
const totalCount = data?.totalCount || 0;
|
||||||
const removeCertificatesFromSync = useRemoveCertificatesFromPkiSync();
|
const removeCertificatesFromSync = useRemoveCertificatesFromPkiSync();
|
||||||
|
|
||||||
|
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: pkiSync.subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveCertificate = async (certificateId: string) => {
|
const handleRemoveCertificate = async (certificateId: string) => {
|
||||||
|
|||||||
+3
-2
@@ -31,7 +31,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) => {
|
export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) => {
|
||||||
const { destination, subscriberId } = pkiSync;
|
const { destination } = pkiSync;
|
||||||
|
|
||||||
const destinationDetails = PKI_SYNC_MAP[destination];
|
const destinationDetails = PKI_SYNC_MAP[destination];
|
||||||
|
|
||||||
@@ -55,7 +55,8 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: subscriberId || ""
|
subscriberName: destinationDetails.name,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+6
-3
@@ -10,6 +10,7 @@ import { PkiSyncStatusBadge } from "@app/components/pki-syncs";
|
|||||||
import { IconButton } from "@app/components/v2";
|
import { IconButton } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
|
||||||
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||||
|
|
||||||
const GenericFieldLabel = ({
|
const GenericFieldLabel = ({
|
||||||
@@ -33,8 +34,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => {
|
export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => {
|
||||||
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, subscriberId, subscriber } =
|
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, subscriber } = pkiSync;
|
||||||
pkiSync;
|
|
||||||
|
|
||||||
const failureMessage = useMemo(() => {
|
const failureMessage = useMemo(() => {
|
||||||
if (syncStatus === PkiSyncStatus.Failed) {
|
if (syncStatus === PkiSyncStatus.Failed) {
|
||||||
@@ -50,8 +50,11 @@ export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => {
|
|||||||
return null;
|
return null;
|
||||||
}, [syncStatus, lastSyncMessage]);
|
}, [syncStatus, lastSyncMessage]);
|
||||||
|
|
||||||
|
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: subscriber?.id || subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+4
-1
@@ -7,6 +7,7 @@ import { IconButton } from "@app/components/v2";
|
|||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
|
||||||
import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||||
|
|
||||||
const GenericFieldLabel = ({
|
const GenericFieldLabel = ({
|
||||||
@@ -35,9 +36,11 @@ export const PkiSyncFieldMappingsSection = ({ pkiSync, onEditMappings }: Props)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fieldMappings = pkiSync.syncOptions?.fieldMappings;
|
const fieldMappings = pkiSync.syncOptions?.fieldMappings;
|
||||||
|
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: pkiSync.subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+5
-1
@@ -7,6 +7,7 @@ import { IconButton } from "@app/components/v2";
|
|||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
|
||||||
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
|
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||||
|
|
||||||
const GenericFieldLabel = ({
|
const GenericFieldLabel = ({
|
||||||
@@ -34,8 +35,11 @@ export const PkiSyncOptionsSection = ({ pkiSync, onEditOptions }: Props) => {
|
|||||||
syncOptions: { canRemoveCertificates }
|
syncOptions: { canRemoveCertificates }
|
||||||
} = pkiSync;
|
} = pkiSync;
|
||||||
|
|
||||||
|
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: pkiSync.subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+5
-1
@@ -9,6 +9,7 @@ import { IconButton, Tooltip } from "@app/components/v2";
|
|||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
|
||||||
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
|
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
|
||||||
|
|
||||||
const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
||||||
@@ -26,8 +27,11 @@ type Props = {
|
|||||||
export const PkiSyncSourceSection = ({ pkiSync, onEditSource }: Props) => {
|
export const PkiSyncSourceSection = ({ pkiSync, onEditSource }: Props) => {
|
||||||
const { subscriberId, subscriber } = pkiSync;
|
const { subscriberId, subscriber } = pkiSync;
|
||||||
|
|
||||||
|
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
|
||||||
|
|
||||||
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
|
||||||
subscriberId: subscriberId || ""
|
subscriberName: destinationName,
|
||||||
|
name: pkiSync.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { useState } from "react";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||||
import { ContentLoader, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { ContentLoader, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { useProject } from "@app/context";
|
import { useProject, useProjectPermission } from "@app/context";
|
||||||
|
import {
|
||||||
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateProfileActions,
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||||
|
|
||||||
import { CertificateProfilesTab } from "./components/CertificateProfilesTab";
|
import { CertificateProfilesTab } from "./components/CertificateProfilesTab";
|
||||||
@@ -20,8 +27,22 @@ enum TabSections {
|
|||||||
export const PoliciesPage = () => {
|
export const PoliciesPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
const [activeTab, setActiveTab] = useState(TabSections.CertificateProfiles);
|
const [activeTab, setActiveTab] = useState(TabSections.CertificateProfiles);
|
||||||
|
|
||||||
|
const canReadCertificateProfiles = permission.can(
|
||||||
|
ProjectPermissionCertificateProfileActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateProfiles
|
||||||
|
);
|
||||||
|
const canReadCertificateTemplates = permission.can(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
const canReadCertificates = permission.can(
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
if (!currentProject) {
|
if (!currentProject) {
|
||||||
return <ContentLoader />;
|
return <ContentLoader />;
|
||||||
}
|
}
|
||||||
@@ -56,15 +77,19 @@ export const PoliciesPage = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanel value={TabSections.CertificateProfiles}>
|
<TabPanel value={TabSections.CertificateProfiles}>
|
||||||
<CertificateProfilesTab />
|
{canReadCertificateProfiles ? <CertificateProfilesTab /> : <PermissionDeniedBanner />}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={TabSections.CertificateTemplatesV2}>
|
<TabPanel value={TabSections.CertificateTemplatesV2}>
|
||||||
<CertificateTemplatesV2Tab />
|
{canReadCertificateTemplates ? (
|
||||||
|
<CertificateTemplatesV2Tab />
|
||||||
|
) : (
|
||||||
|
<PermissionDeniedBanner />
|
||||||
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={TabSections.Certificates}>
|
<TabPanel value={TabSections.Certificates}>
|
||||||
<CertificatesTab />
|
{canReadCertificates ? <CertificatesTab /> : <PermissionDeniedBanner />}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+18
-19
@@ -4,10 +4,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import { useProjectPermission } from "@app/context";
|
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/context/ProjectPermissionContext/types";
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
@@ -21,8 +21,6 @@ import { ProfileList } from "./ProfileList";
|
|||||||
import { RevealAcmeEabSecretModal } from "./RevealAcmeEabSecretModal";
|
import { RevealAcmeEabSecretModal } from "./RevealAcmeEabSecretModal";
|
||||||
|
|
||||||
export const CertificateProfilesTab = () => {
|
export const CertificateProfilesTab = () => {
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
|
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
@@ -35,11 +33,6 @@ export const CertificateProfilesTab = () => {
|
|||||||
|
|
||||||
const deleteProfile = useDeleteCertificateProfile();
|
const deleteProfile = useDeleteCertificateProfile();
|
||||||
|
|
||||||
const canCreateProfile = permission.can(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateProfile = () => {
|
const handleCreateProfile = () => {
|
||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
};
|
};
|
||||||
@@ -84,16 +77,22 @@ export const CertificateProfilesTab = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canCreateProfile && (
|
<ProjectPermissionCan
|
||||||
<Button
|
I={ProjectPermissionCertificateProfileActions.Create}
|
||||||
colorSchema="primary"
|
a={ProjectPermissionSub.CertificateProfiles}
|
||||||
type="button"
|
>
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
{(isAllowed) => (
|
||||||
onClick={handleCreateProfile}
|
<Button
|
||||||
>
|
isDisabled={!isAllowed}
|
||||||
Create Profile
|
colorSchema="primary"
|
||||||
</Button>
|
type="button"
|
||||||
)}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={handleCreateProfile}
|
||||||
|
>
|
||||||
|
Create Profile
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProfileList
|
<ProfileList
|
||||||
|
|||||||
+74
-64
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
faCheck,
|
faCheck,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -23,9 +25,7 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v3";
|
import { Badge } from "@app/components/v3";
|
||||||
import { useProjectPermission } from "@app/context";
|
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/context/ProjectPermissionContext/types";
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
@@ -48,8 +48,6 @@ export const ProfileRow = ({
|
|||||||
onRevealProfileAcmeEabSecret,
|
onRevealProfileAcmeEabSecret,
|
||||||
onDeleteProfile
|
onDeleteProfile
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
|
|
||||||
const { data: caData } = useGetInternalCaById(profile.caId ?? "");
|
const { data: caData } = useGetInternalCaById(profile.caId ?? "");
|
||||||
|
|
||||||
const { popUp, handlePopUpToggle } = usePopUp(["issueCertificate"] as const);
|
const { popUp, handlePopUpToggle } = usePopUp(["issueCertificate"] as const);
|
||||||
@@ -72,26 +70,6 @@ export const ProfileRow = ({
|
|||||||
templateId: profile.certificateTemplateId
|
templateId: profile.certificateTemplateId
|
||||||
});
|
});
|
||||||
|
|
||||||
const canEditProfile = permission.can(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
|
||||||
);
|
|
||||||
|
|
||||||
const canRevealProfileAcmeEabSecret = permission.can(
|
|
||||||
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
|
|
||||||
ProjectPermissionSub.CertificateProfiles
|
|
||||||
);
|
|
||||||
|
|
||||||
const canIssueCertificate = permission.can(
|
|
||||||
ProjectPermissionCertificateProfileActions.IssueCert,
|
|
||||||
ProjectPermissionSub.CertificateProfiles
|
|
||||||
);
|
|
||||||
|
|
||||||
const canDeleteProfile = permission.can(
|
|
||||||
ProjectPermissionActions.Delete,
|
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
|
||||||
);
|
|
||||||
|
|
||||||
const getEnrollmentTypeBadge = (enrollmentType: string) => {
|
const getEnrollmentTypeBadge = (enrollmentType: string) => {
|
||||||
const config = {
|
const config = {
|
||||||
api: { variant: "ghost" as const, label: "API" },
|
api: { variant: "ghost" as const, label: "API" },
|
||||||
@@ -152,50 +130,82 @@ export const ProfileRow = ({
|
|||||||
>
|
>
|
||||||
Copy Profile ID
|
Copy Profile ID
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{canEditProfile && (
|
<ProjectPermissionCan
|
||||||
<DropdownMenuItem
|
I={ProjectPermissionCertificateProfileActions.Edit}
|
||||||
onClick={(e) => {
|
a={subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })}
|
||||||
e.stopPropagation();
|
>
|
||||||
onEditProfile(profile);
|
{(isAllowed) =>
|
||||||
}}
|
isAllowed && (
|
||||||
icon={<FontAwesomeIcon icon={faEdit} className="w-3" />}
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEditProfile(profile);
|
||||||
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faEdit} className="w-3" />}
|
||||||
|
>
|
||||||
|
Edit Profile
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
{profile.enrollmentType === "acme" && (
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret}
|
||||||
|
a={subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })}
|
||||||
>
|
>
|
||||||
Edit Profile
|
{(isAllowed) =>
|
||||||
</DropdownMenuItem>
|
isAllowed && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRevealProfileAcmeEabSecret(profile);
|
||||||
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faEye} className="w-3" />}
|
||||||
|
>
|
||||||
|
Reveal ACME EAB
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
)}
|
)}
|
||||||
{canRevealProfileAcmeEabSecret && profile.enrollmentType === "acme" && (
|
{profile.enrollmentType === "api" && (
|
||||||
<DropdownMenuItem
|
<ProjectPermissionCan
|
||||||
onClick={(e) => {
|
I={ProjectPermissionCertificateProfileActions.IssueCert}
|
||||||
e.stopPropagation();
|
a={subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })}
|
||||||
onRevealProfileAcmeEabSecret(profile);
|
|
||||||
}}
|
|
||||||
icon={<FontAwesomeIcon icon={faEye} className="w-3" />}
|
|
||||||
>
|
>
|
||||||
Reveal ACME EAB
|
{(isAllowed) =>
|
||||||
</DropdownMenuItem>
|
isAllowed && (
|
||||||
)}
|
<DropdownMenuItem
|
||||||
{canIssueCertificate && profile.enrollmentType === "api" && (
|
onClick={(e) => {
|
||||||
<DropdownMenuItem
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
handlePopUpToggle("issueCertificate");
|
||||||
e.stopPropagation();
|
}}
|
||||||
handlePopUpToggle("issueCertificate");
|
icon={<FontAwesomeIcon icon={faPlus} className="w-3" />}
|
||||||
}}
|
>
|
||||||
icon={<FontAwesomeIcon icon={faPlus} className="w-3" />}
|
Request Certificate
|
||||||
>
|
</DropdownMenuItem>
|
||||||
Request Certificate
|
)
|
||||||
</DropdownMenuItem>
|
}
|
||||||
)}
|
</ProjectPermissionCan>
|
||||||
{canDeleteProfile && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDeleteProfile(profile);
|
|
||||||
}}
|
|
||||||
icon={<FontAwesomeIcon icon={faTrash} className="w-3" />}
|
|
||||||
>
|
|
||||||
Delete Profile
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
)}
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionCertificateProfileActions.Delete}
|
||||||
|
a={subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })}
|
||||||
|
>
|
||||||
|
{(isAllowed) =>
|
||||||
|
isAllowed && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDeleteProfile(profile);
|
||||||
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} className="w-3" />}
|
||||||
|
>
|
||||||
|
Delete Profile
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<CertificateIssuanceModal
|
<CertificateIssuanceModal
|
||||||
|
|||||||
+17
-18
@@ -3,8 +3,8 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
import { useProjectPermission } from "@app/context";
|
|
||||||
import {
|
import {
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@@ -16,8 +16,6 @@ import { CreateTemplateModal } from "./CreateTemplateModal";
|
|||||||
import { TemplateList } from "./TemplateList";
|
import { TemplateList } from "./TemplateList";
|
||||||
|
|
||||||
export const CertificateTemplatesV2Tab = () => {
|
export const CertificateTemplatesV2Tab = () => {
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
|
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
@@ -26,11 +24,6 @@ export const CertificateTemplatesV2Tab = () => {
|
|||||||
|
|
||||||
const deleteTemplateV2 = useDeleteCertificateTemplateV2WithPolicies();
|
const deleteTemplateV2 = useDeleteCertificateTemplateV2WithPolicies();
|
||||||
|
|
||||||
const canCreateTemplate = permission.can(
|
|
||||||
ProjectPermissionPkiTemplateActions.Create,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateTemplate = () => {
|
const handleCreateTemplate = () => {
|
||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
};
|
};
|
||||||
@@ -70,16 +63,22 @@ export const CertificateTemplatesV2Tab = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canCreateTemplate && (
|
<ProjectPermissionCan
|
||||||
<Button
|
I={ProjectPermissionPkiTemplateActions.Create}
|
||||||
colorSchema="primary"
|
a={ProjectPermissionSub.CertificateTemplates}
|
||||||
type="button"
|
>
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
{(isAllowed) => (
|
||||||
onClick={handleCreateTemplate}
|
<Button
|
||||||
>
|
isDisabled={!isAllowed}
|
||||||
Create Template
|
colorSchema="primary"
|
||||||
</Button>
|
type="button"
|
||||||
)}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={handleCreateTemplate}
|
||||||
|
>
|
||||||
|
Create Template
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} />
|
<TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} />
|
||||||
|
|||||||
+5
-2
@@ -258,7 +258,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<
|
const { control, handleSubmit, reset, watch, setValue, formState, trigger } = useForm<
|
||||||
FormData & { preset: TemplatePresetId }
|
FormData & { preset: TemplatePresetId }
|
||||||
>({
|
>({
|
||||||
resolver: zodResolver(templateSchema),
|
resolver: zodResolver(templateSchema),
|
||||||
@@ -286,10 +286,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
|||||||
};
|
};
|
||||||
const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM;
|
const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM;
|
||||||
|
|
||||||
const handlePresetChange = (presetId: TemplatePresetId) => {
|
const handlePresetChange = async (presetId: TemplatePresetId) => {
|
||||||
setValue("preset", presetId);
|
setValue("preset", presetId);
|
||||||
|
|
||||||
if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) {
|
if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) {
|
||||||
|
await trigger();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +314,8 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
|||||||
if (selectedPreset.formData.keyAlgorithm) {
|
if (selectedPreset.formData.keyAlgorithm) {
|
||||||
setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm);
|
setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await trigger();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+79
-35
@@ -1,6 +1,17 @@
|
|||||||
import { faCircleInfo, faEdit, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { useCallback } from "react";
|
||||||
|
import { subject } from "@casl/ability";
|
||||||
|
import {
|
||||||
|
faCheck,
|
||||||
|
faCircleInfo,
|
||||||
|
faCopy,
|
||||||
|
faEdit,
|
||||||
|
faEllipsis,
|
||||||
|
faTrash
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -17,11 +28,12 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useProject, useProjectPermission } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/context/ProjectPermissionContext/types";
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
|
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
|
||||||
import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
|
import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
|
||||||
|
|
||||||
@@ -31,8 +43,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
|
const [isIdCopied, setIsIdCopied] = useToggle(false);
|
||||||
|
|
||||||
const { data, isLoading } = useListCertificateTemplatesV2({
|
const { data, isLoading } = useListCertificateTemplatesV2({
|
||||||
projectId: currentProject?.id || "",
|
projectId: currentProject?.id || "",
|
||||||
@@ -42,20 +54,25 @@ export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
|||||||
|
|
||||||
const templates = data?.certificateTemplates || [];
|
const templates = data?.certificateTemplates || [];
|
||||||
|
|
||||||
|
const handleCopyId = useCallback(
|
||||||
|
(templateId: string) => {
|
||||||
|
setIsIdCopied.on();
|
||||||
|
navigator.clipboard.writeText(templateId);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Template ID copied to clipboard",
|
||||||
|
type: "info"
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => setIsIdCopied.off(), 2000);
|
||||||
|
},
|
||||||
|
[setIsIdCopied]
|
||||||
|
);
|
||||||
|
|
||||||
if (!currentProject?.id) {
|
if (!currentProject?.id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canEditTemplate = permission.can(
|
|
||||||
ProjectPermissionPkiTemplateActions.Edit,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const canDeleteTemplate = permission.can(
|
|
||||||
ProjectPermissionPkiTemplateActions.Delete,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
return new Date(dateString).toLocaleDateString();
|
return new Date(dateString).toLocaleDateString();
|
||||||
};
|
};
|
||||||
@@ -110,28 +127,55 @@ export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
{canEditTemplate && (
|
<DropdownMenuItem
|
||||||
<DropdownMenuItem
|
onClick={(e) => {
|
||||||
onClick={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
handleCopyId(template.id);
|
||||||
onEditTemplate(template);
|
}}
|
||||||
}}
|
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
>
|
||||||
>
|
Copy Template ID
|
||||||
Edit Template
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
<ProjectPermissionCan
|
||||||
)}
|
I={ProjectPermissionPkiTemplateActions.Edit}
|
||||||
{canDeleteTemplate && (
|
a={subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
<DropdownMenuItem
|
name: template.name
|
||||||
onClick={(e) => {
|
})}
|
||||||
e.stopPropagation();
|
>
|
||||||
onDeleteTemplate(template);
|
{(isAllowed) =>
|
||||||
}}
|
isAllowed && (
|
||||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
<DropdownMenuItem
|
||||||
>
|
onClick={(e) => {
|
||||||
Delete Template
|
e.stopPropagation();
|
||||||
</DropdownMenuItem>
|
onEditTemplate(template);
|
||||||
)}
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||||
|
>
|
||||||
|
Edit Template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiTemplateActions.Delete}
|
||||||
|
a={subject(ProjectPermissionSub.CertificateTemplates, {
|
||||||
|
name: template.name
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{(isAllowed) =>
|
||||||
|
isAllowed && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDeleteTemplate(template);
|
||||||
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
>
|
||||||
|
Delete Template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
|
import { ConditionsFields } from "./ConditionsFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
position?: number;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CertificateAuthorityPermissionConditions = ({ position = 0, isDisabled }: Props) => {
|
||||||
|
return (
|
||||||
|
<ConditionsFields
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
subject={ProjectPermissionSub.CertificateAuthorities}
|
||||||
|
position={position}
|
||||||
|
selectOptions={[{ value: "name", label: "Name" }]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
|
import { ConditionsFields } from "./ConditionsFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
position?: number;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CertificatePermissionConditions = ({ position = 0, isDisabled }: Props) => {
|
||||||
|
return (
|
||||||
|
<ConditionsFields
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
subject={ProjectPermissionSub.Certificates}
|
||||||
|
position={position}
|
||||||
|
selectOptions={[
|
||||||
|
{ value: "commonName", label: "Common Name" },
|
||||||
|
{ value: "altNames", label: "Subject Alternative Names" },
|
||||||
|
{ value: "serialNumber", label: "Serial Number" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
|
import { ConditionsFields } from "./ConditionsFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
position?: number;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CertificateProfilePermissionConditions = ({ position = 0, isDisabled }: Props) => {
|
||||||
|
return (
|
||||||
|
<ConditionsFields
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
subject={ProjectPermissionSub.CertificateProfiles}
|
||||||
|
position={position}
|
||||||
|
selectOptions={[{ value: "slug", label: "Name" }]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -36,6 +36,7 @@ export const renderOperatorSelectItems = (type: string) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
|
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
|
||||||
|
<SelectItem value={PermissionConditionOperators.$NEQ}>Not Equal</SelectItem>
|
||||||
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob Match</SelectItem>
|
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob Match</SelectItem>
|
||||||
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
|
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ export const PkiSyncPermissionConditions = ({ position = 0, isDisabled }: Props)
|
|||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
subject={ProjectPermissionSub.PkiSyncs}
|
subject={ProjectPermissionSub.PkiSyncs}
|
||||||
position={position}
|
position={position}
|
||||||
selectOptions={[{ value: "subscriberName", label: "Subscriber Name" }]}
|
selectOptions={[{ value: "name", label: "Sync Name" }]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+98
-31
@@ -7,6 +7,7 @@ import { Tooltip } from "@app/components/v2";
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
ProjectPermissionCertificateProfileActions,
|
ProjectPermissionCertificateProfileActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@@ -56,7 +57,17 @@ const CertificatePolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionCertificateActions.Delete]: z.boolean().optional(),
|
[ProjectPermissionCertificateActions.Delete]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateActions.Edit]: z.boolean().optional(),
|
[ProjectPermissionCertificateActions.Edit]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateActions.Read]: z.boolean().optional(),
|
[ProjectPermissionCertificateActions.Read]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateActions.ReadPrivateKey]: z.boolean().optional()
|
[ProjectPermissionCertificateActions.ReadPrivateKey]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateActions.Import]: z.boolean().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
const CertificateAuthorityPolicyActionSchema = z.object({
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Create]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Delete]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Edit]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Read]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Renew]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.SignIntermediate]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const SecretPolicyActionSchema = z.object({
|
const SecretPolicyActionSchema = z.object({
|
||||||
@@ -208,7 +219,6 @@ const PkiSubscriberPolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionPkiSubscriberActions.Create]: z.boolean().optional(),
|
[ProjectPermissionPkiSubscriberActions.Create]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiSubscriberActions.Edit]: z.boolean().optional(),
|
[ProjectPermissionPkiSubscriberActions.Edit]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiSubscriberActions.Delete]: z.boolean().optional(),
|
[ProjectPermissionPkiSubscriberActions.Delete]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiSubscriberActions.IssueCert]: z.boolean().optional(),
|
|
||||||
[ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional()
|
[ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -217,7 +227,6 @@ const PkiTemplatePolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionPkiTemplateActions.Create]: z.boolean().optional(),
|
[ProjectPermissionPkiTemplateActions.Create]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiTemplateActions.Edit]: z.boolean().optional(),
|
[ProjectPermissionPkiTemplateActions.Edit]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiTemplateActions.Delete]: z.boolean().optional(),
|
[ProjectPermissionPkiTemplateActions.Delete]: z.boolean().optional(),
|
||||||
[ProjectPermissionPkiTemplateActions.IssueCert]: z.boolean().optional(),
|
|
||||||
[ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional()
|
[ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
const CertificateProfilePolicyActionSchema = z.object({
|
const CertificateProfilePolicyActionSchema = z.object({
|
||||||
@@ -225,7 +234,9 @@ const CertificateProfilePolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionCertificateProfileActions.Create]: z.boolean().optional(),
|
[ProjectPermissionCertificateProfileActions.Create]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateProfileActions.Edit]: z.boolean().optional(),
|
[ProjectPermissionCertificateProfileActions.Edit]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateProfileActions.Delete]: z.boolean().optional(),
|
[ProjectPermissionCertificateProfileActions.Delete]: z.boolean().optional(),
|
||||||
[ProjectPermissionCertificateProfileActions.IssueCert]: z.boolean().optional()
|
[ProjectPermissionCertificateProfileActions.IssueCert]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const SecretEventsPolicyActionSchema = z.object({
|
const SecretEventsPolicyActionSchema = z.object({
|
||||||
@@ -381,8 +392,18 @@ export const projectRoleFormSchema = z.object({
|
|||||||
[ProjectPermissionSub.Environments]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.Environments]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.AuditLogs]: AuditLogsPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.AuditLogs]: AuditLogsPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.IpAllowList]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.IpAllowList]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.CertificateAuthorities]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.CertificateAuthorities]: CertificateAuthorityPolicyActionSchema.extend({
|
||||||
[ProjectPermissionSub.Certificates]: CertificatePolicyActionSchema.array().default([]),
|
inverted: z.boolean().optional(),
|
||||||
|
conditions: ConditionSchema
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
|
[ProjectPermissionSub.Certificates]: CertificatePolicyActionSchema.extend({
|
||||||
|
inverted: z.boolean().optional(),
|
||||||
|
conditions: ConditionSchema
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
[ProjectPermissionSub.PkiSubscribers]: PkiSubscriberPolicyActionSchema.extend({
|
[ProjectPermissionSub.PkiSubscribers]: PkiSubscriberPolicyActionSchema.extend({
|
||||||
inverted: z.boolean().optional(),
|
inverted: z.boolean().optional(),
|
||||||
conditions: ConditionSchema
|
conditions: ConditionSchema
|
||||||
@@ -397,8 +418,12 @@ export const projectRoleFormSchema = z.object({
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.default([]),
|
.default([]),
|
||||||
[ProjectPermissionSub.CertificateProfiles]:
|
[ProjectPermissionSub.CertificateProfiles]: CertificateProfilePolicyActionSchema.extend({
|
||||||
CertificateProfilePolicyActionSchema.array().default([]),
|
inverted: z.boolean().optional(),
|
||||||
|
conditions: ConditionSchema
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
[ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default(
|
[ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default(
|
||||||
[]
|
[]
|
||||||
),
|
),
|
||||||
@@ -458,6 +483,9 @@ type TConditionalFields =
|
|||||||
| ProjectPermissionSub.DynamicSecrets
|
| ProjectPermissionSub.DynamicSecrets
|
||||||
| ProjectPermissionSub.PkiSubscribers
|
| ProjectPermissionSub.PkiSubscribers
|
||||||
| ProjectPermissionSub.CertificateTemplates
|
| ProjectPermissionSub.CertificateTemplates
|
||||||
|
| ProjectPermissionSub.CertificateAuthorities
|
||||||
|
| ProjectPermissionSub.Certificates
|
||||||
|
| ProjectPermissionSub.CertificateProfiles
|
||||||
| ProjectPermissionSub.SshHosts
|
| ProjectPermissionSub.SshHosts
|
||||||
| ProjectPermissionSub.SecretRotation
|
| ProjectPermissionSub.SecretRotation
|
||||||
| ProjectPermissionSub.Identity
|
| ProjectPermissionSub.Identity
|
||||||
@@ -479,6 +507,9 @@ export const isConditionalSubjects = (
|
|||||||
subject === ProjectPermissionSub.SecretRotation ||
|
subject === ProjectPermissionSub.SecretRotation ||
|
||||||
subject === ProjectPermissionSub.PkiSubscribers ||
|
subject === ProjectPermissionSub.PkiSubscribers ||
|
||||||
subject === ProjectPermissionSub.CertificateTemplates ||
|
subject === ProjectPermissionSub.CertificateTemplates ||
|
||||||
|
subject === ProjectPermissionSub.CertificateAuthorities ||
|
||||||
|
subject === ProjectPermissionSub.Certificates ||
|
||||||
|
subject === ProjectPermissionSub.CertificateProfiles ||
|
||||||
subject === ProjectPermissionSub.SecretSyncs ||
|
subject === ProjectPermissionSub.SecretSyncs ||
|
||||||
subject === ProjectPermissionSub.PkiSyncs ||
|
subject === ProjectPermissionSub.PkiSyncs ||
|
||||||
subject === ProjectPermissionSub.SecretEvents ||
|
subject === ProjectPermissionSub.SecretEvents ||
|
||||||
@@ -822,6 +853,31 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.CertificateAuthorities) {
|
||||||
|
const canRead = action.includes(ProjectPermissionCertificateAuthorityActions.Read);
|
||||||
|
const canCreate = action.includes(ProjectPermissionCertificateAuthorityActions.Create);
|
||||||
|
const canEdit = action.includes(ProjectPermissionCertificateAuthorityActions.Edit);
|
||||||
|
const canDelete = action.includes(ProjectPermissionCertificateAuthorityActions.Delete);
|
||||||
|
const canRenew = action.includes(ProjectPermissionCertificateAuthorityActions.Renew);
|
||||||
|
const canSignIntermediate = action.includes(
|
||||||
|
ProjectPermissionCertificateAuthorityActions.SignIntermediate
|
||||||
|
);
|
||||||
|
|
||||||
|
// from above statement we are sure it won't be undefined
|
||||||
|
formVal[subject]!.push({
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Read]: canRead,
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Create]: canCreate,
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Edit]: canEdit,
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Delete]: canDelete,
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.Renew]: canRenew,
|
||||||
|
[ProjectPermissionCertificateAuthorityActions.SignIntermediate]: canSignIntermediate,
|
||||||
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
|
inverted
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// for other subjects
|
// for other subjects
|
||||||
const canRead = action.includes(ProjectPermissionActions.Read);
|
const canRead = action.includes(ProjectPermissionActions.Read);
|
||||||
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
||||||
@@ -873,17 +929,22 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
const canEdit = action.includes(ProjectPermissionCertificateActions.Edit);
|
const canEdit = action.includes(ProjectPermissionCertificateActions.Edit);
|
||||||
const canDelete = action.includes(ProjectPermissionCertificateActions.Delete);
|
const canDelete = action.includes(ProjectPermissionCertificateActions.Delete);
|
||||||
const canCreate = action.includes(ProjectPermissionCertificateActions.Create);
|
const canCreate = action.includes(ProjectPermissionCertificateActions.Create);
|
||||||
|
const canImport = action.includes(ProjectPermissionCertificateActions.Import);
|
||||||
const canReadPrivateKey = action.includes(ProjectPermissionCertificateActions.ReadPrivateKey);
|
const canReadPrivateKey = action.includes(ProjectPermissionCertificateActions.ReadPrivateKey);
|
||||||
|
|
||||||
if (!formVal[subject]) formVal[subject] = [{}];
|
if (!formVal[subject]) formVal[subject] = [];
|
||||||
|
|
||||||
// from above statement we are sure it won't be undefined
|
// from above statement we are sure it won't be undefined
|
||||||
if (canRead) formVal[subject]![0].read = true;
|
formVal[subject]!.push({
|
||||||
if (canEdit) formVal[subject]![0].edit = true;
|
[ProjectPermissionCertificateActions.Read]: canRead,
|
||||||
if (canCreate) formVal[subject]![0].create = true;
|
[ProjectPermissionCertificateActions.Edit]: canEdit,
|
||||||
if (canDelete) formVal[subject]![0].delete = true;
|
[ProjectPermissionCertificateActions.Create]: canCreate,
|
||||||
if (canReadPrivateKey)
|
[ProjectPermissionCertificateActions.Delete]: canDelete,
|
||||||
formVal[subject]![0][ProjectPermissionCertificateActions.ReadPrivateKey] = true;
|
[ProjectPermissionCertificateActions.ReadPrivateKey]: canReadPrivateKey,
|
||||||
|
[ProjectPermissionCertificateActions.Import]: canImport,
|
||||||
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
|
inverted
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,9 +1190,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
[ProjectPermissionPkiSubscriberActions.Read]: action.includes(
|
[ProjectPermissionPkiSubscriberActions.Read]: action.includes(
|
||||||
ProjectPermissionPkiSubscriberActions.Read
|
ProjectPermissionPkiSubscriberActions.Read
|
||||||
),
|
),
|
||||||
[ProjectPermissionPkiSubscriberActions.IssueCert]: action.includes(
|
|
||||||
ProjectPermissionPkiSubscriberActions.IssueCert
|
|
||||||
),
|
|
||||||
[ProjectPermissionPkiSubscriberActions.ListCerts]: action.includes(
|
[ProjectPermissionPkiSubscriberActions.ListCerts]: action.includes(
|
||||||
ProjectPermissionPkiSubscriberActions.ListCerts
|
ProjectPermissionPkiSubscriberActions.ListCerts
|
||||||
),
|
),
|
||||||
@@ -1157,9 +1215,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
[ProjectPermissionPkiTemplateActions.Read]: action.includes(
|
[ProjectPermissionPkiTemplateActions.Read]: action.includes(
|
||||||
ProjectPermissionPkiTemplateActions.Read
|
ProjectPermissionPkiTemplateActions.Read
|
||||||
),
|
),
|
||||||
[ProjectPermissionPkiTemplateActions.IssueCert]: action.includes(
|
|
||||||
ProjectPermissionPkiTemplateActions.IssueCert
|
|
||||||
),
|
|
||||||
[ProjectPermissionPkiTemplateActions.ListCerts]: action.includes(
|
[ProjectPermissionPkiTemplateActions.ListCerts]: action.includes(
|
||||||
ProjectPermissionPkiTemplateActions.ListCerts
|
ProjectPermissionPkiTemplateActions.ListCerts
|
||||||
),
|
),
|
||||||
@@ -1187,7 +1242,15 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
),
|
),
|
||||||
[ProjectPermissionCertificateProfileActions.IssueCert]: action.includes(
|
[ProjectPermissionCertificateProfileActions.IssueCert]: action.includes(
|
||||||
ProjectPermissionCertificateProfileActions.IssueCert
|
ProjectPermissionCertificateProfileActions.IssueCert
|
||||||
)
|
),
|
||||||
|
[ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret]: action.includes(
|
||||||
|
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret
|
||||||
|
),
|
||||||
|
[ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret]: action.includes(
|
||||||
|
ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret
|
||||||
|
),
|
||||||
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
|
inverted
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -1196,7 +1259,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
if (subject === ProjectPermissionSub.PamAccounts) {
|
if (subject === ProjectPermissionSub.PamAccounts) {
|
||||||
if (!formVal[subject]) formVal[subject] = [];
|
if (!formVal[subject]) formVal[subject] = [];
|
||||||
|
|
||||||
formVal[subject].push({
|
formVal[subject]!.push({
|
||||||
[ProjectPermissionPamAccountActions.Access]: action.includes(
|
[ProjectPermissionPamAccountActions.Access]: action.includes(
|
||||||
ProjectPermissionPamAccountActions.Access
|
ProjectPermissionPamAccountActions.Access
|
||||||
),
|
),
|
||||||
@@ -1552,10 +1615,15 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.CertificateAuthorities]: {
|
[ProjectPermissionSub.CertificateAuthorities]: {
|
||||||
title: "Certificate Authorities",
|
title: "Certificate Authorities",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Read", value: "read" },
|
{ label: "Read", value: ProjectPermissionCertificateAuthorityActions.Read },
|
||||||
{ label: "Create", value: "create" },
|
{ label: "Create", value: ProjectPermissionCertificateAuthorityActions.Create },
|
||||||
{ label: "Modify", value: "edit" },
|
{ label: "Modify", value: ProjectPermissionCertificateAuthorityActions.Edit },
|
||||||
{ label: "Remove", value: "delete" }
|
{ label: "Remove", value: ProjectPermissionCertificateAuthorityActions.Delete },
|
||||||
|
{ label: "Renew", value: ProjectPermissionCertificateAuthorityActions.Renew },
|
||||||
|
{
|
||||||
|
label: "Sign Intermediate",
|
||||||
|
value: ProjectPermissionCertificateAuthorityActions.SignIntermediate
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ProjectPermissionSub.Certificates]: {
|
[ProjectPermissionSub.Certificates]: {
|
||||||
@@ -1563,7 +1631,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
actions: [
|
actions: [
|
||||||
{ label: "Read", value: ProjectPermissionCertificateActions.Read },
|
{ label: "Read", value: ProjectPermissionCertificateActions.Read },
|
||||||
{ label: "Read Private Key", value: ProjectPermissionCertificateActions.ReadPrivateKey },
|
{ label: "Read Private Key", value: ProjectPermissionCertificateActions.ReadPrivateKey },
|
||||||
{ label: "Create", value: ProjectPermissionCertificateActions.Create },
|
// { label: "Create", value: ProjectPermissionCertificateActions.Create }, // Hidden from UI - kept for backend compatibility
|
||||||
|
{ label: "Import", value: ProjectPermissionCertificateActions.Import },
|
||||||
{ label: "Modify", value: ProjectPermissionCertificateActions.Edit },
|
{ label: "Modify", value: ProjectPermissionCertificateActions.Edit },
|
||||||
{ label: "Remove", value: ProjectPermissionCertificateActions.Delete }
|
{ label: "Remove", value: ProjectPermissionCertificateActions.Delete }
|
||||||
]
|
]
|
||||||
@@ -1575,7 +1644,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
{ label: "Create", value: ProjectPermissionPkiTemplateActions.Create },
|
{ label: "Create", value: ProjectPermissionPkiTemplateActions.Create },
|
||||||
{ label: "Modify", value: ProjectPermissionPkiTemplateActions.Edit },
|
{ label: "Modify", value: ProjectPermissionPkiTemplateActions.Edit },
|
||||||
{ label: "Remove", value: ProjectPermissionPkiTemplateActions.Delete },
|
{ label: "Remove", value: ProjectPermissionPkiTemplateActions.Delete },
|
||||||
{ label: "Issue Certificates", value: ProjectPermissionPkiTemplateActions.IssueCert },
|
|
||||||
{ label: "List Certificates", value: ProjectPermissionPkiTemplateActions.ListCerts }
|
{ label: "List Certificates", value: ProjectPermissionPkiTemplateActions.ListCerts }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1586,7 +1654,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
{ label: "Create", value: ProjectPermissionCertificateProfileActions.Create },
|
{ label: "Create", value: ProjectPermissionCertificateProfileActions.Create },
|
||||||
{ label: "Modify", value: ProjectPermissionCertificateProfileActions.Edit },
|
{ label: "Modify", value: ProjectPermissionCertificateProfileActions.Edit },
|
||||||
{ label: "Remove", value: ProjectPermissionCertificateProfileActions.Delete },
|
{ label: "Remove", value: ProjectPermissionCertificateProfileActions.Delete },
|
||||||
{ label: "Issue Certificates", value: ProjectPermissionCertificateProfileActions.IssueCert }
|
{ label: "Request Certificates", value: ProjectPermissionCertificateProfileActions.IssueCert }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ProjectPermissionSub.SshCertificateAuthorities]: {
|
[ProjectPermissionSub.SshCertificateAuthorities]: {
|
||||||
@@ -1642,7 +1710,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
{ label: "Create", value: ProjectPermissionPkiSubscriberActions.Create },
|
{ label: "Create", value: ProjectPermissionPkiSubscriberActions.Create },
|
||||||
{ label: "Modify", value: ProjectPermissionPkiSubscriberActions.Edit },
|
{ label: "Modify", value: ProjectPermissionPkiSubscriberActions.Edit },
|
||||||
{ label: "Remove", value: ProjectPermissionPkiSubscriberActions.Delete },
|
{ label: "Remove", value: ProjectPermissionPkiSubscriberActions.Delete },
|
||||||
{ label: "Issue Certificate", value: ProjectPermissionPkiSubscriberActions.IssueCert },
|
|
||||||
{ label: "List Certificates", value: ProjectPermissionPkiSubscriberActions.ListCerts }
|
{ label: "List Certificates", value: ProjectPermissionPkiSubscriberActions.ListCerts }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+15
@@ -21,6 +21,9 @@ import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
|||||||
|
|
||||||
import { AddPoliciesButton } from "./AddPoliciesButton";
|
import { AddPoliciesButton } from "./AddPoliciesButton";
|
||||||
import { AppConnectionPermissionConditions } from "./AppConnectionPermissionConditions";
|
import { AppConnectionPermissionConditions } from "./AppConnectionPermissionConditions";
|
||||||
|
import { CertificateAuthorityPermissionConditions } from "./CertificateAuthorityPermissionConditions";
|
||||||
|
import { CertificatePermissionConditions } from "./CertificatePermissionConditions";
|
||||||
|
import { CertificateProfilePermissionConditions } from "./CertificateProfilePermissionConditions";
|
||||||
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
|
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
|
||||||
import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
|
import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
|
||||||
import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
|
import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
|
||||||
@@ -96,6 +99,18 @@ export const renderConditionalComponents = (
|
|||||||
return <PamAccountPermissionConditions isDisabled={isDisabled} />;
|
return <PamAccountPermissionConditions isDisabled={isDisabled} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.CertificateAuthorities) {
|
||||||
|
return <CertificateAuthorityPermissionConditions isDisabled={isDisabled} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.Certificates) {
|
||||||
|
return <CertificatePermissionConditions isDisabled={isDisabled} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.CertificateProfiles) {
|
||||||
|
return <CertificateProfilePermissionConditions isDisabled={isDisabled} />;
|
||||||
|
}
|
||||||
|
|
||||||
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
|
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user