Improve list endpoints permissions check and address PR comments

This commit is contained in:
Carlos Monastyrski
2025-12-04 04:58:32 -03:00
parent b53348a684
commit 022c2a860e
35 changed files with 860 additions and 170 deletions

View File

@@ -74,7 +74,6 @@ const buildAdminPermissionRules = () => {
ProjectPermissionCertificateAuthorityActions.Create,
ProjectPermissionCertificateAuthorityActions.Edit,
ProjectPermissionCertificateAuthorityActions.Delete,
ProjectPermissionCertificateAuthorityActions.List,
ProjectPermissionCertificateAuthorityActions.Renew,
ProjectPermissionCertificateAuthorityActions.SignIntermediate
],
@@ -111,7 +110,7 @@ const buildAdminPermissionRules = () => {
ProjectPermissionCertificateActions.Delete,
ProjectPermissionCertificateActions.ReadPrivateKey,
ProjectPermissionCertificateActions.Import,
ProjectPermissionCertificateActions.List
ProjectPermissionCertificateActions.Read
],
ProjectPermissionSub.Certificates
);
@@ -119,7 +118,6 @@ const buildAdminPermissionRules = () => {
can(
[
ProjectPermissionCertificateProfileActions.Read,
ProjectPermissionCertificateProfileActions.List,
ProjectPermissionCertificateProfileActions.Edit,
ProjectPermissionCertificateProfileActions.Create,
ProjectPermissionCertificateProfileActions.Delete,
@@ -487,7 +485,7 @@ const buildMemberPermissionRules = () => {
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete,
ProjectPermissionCertificateActions.Import,
ProjectPermissionCertificateActions.List
ProjectPermissionCertificateActions.Read
],
ProjectPermissionSub.Certificates
);
@@ -495,7 +493,6 @@ const buildMemberPermissionRules = () => {
can(
[
ProjectPermissionCertificateProfileActions.Read,
ProjectPermissionCertificateProfileActions.List,
ProjectPermissionCertificateProfileActions.Edit,
ProjectPermissionCertificateProfileActions.Create,
ProjectPermissionCertificateProfileActions.Delete
@@ -621,7 +618,6 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionCertificateAuthorityActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCertificateActions.List, ProjectPermissionSub.Certificates);
can(ProjectPermissionPkiTemplateActions.Read, ProjectPermissionSub.CertificateTemplates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);

View File

@@ -28,7 +28,6 @@ export enum ProjectPermissionCertificateAuthorityActions {
Create = "create",
Edit = "edit",
Delete = "delete",
List = "list",
Renew = "renew",
SignIntermediate = "sign-intermediate"
}
@@ -38,7 +37,6 @@ export enum ProjectPermissionCertificateActions {
Create = "create",
Edit = "edit",
Delete = "delete",
List = "list",
ReadPrivateKey = "read-private-key",
Import = "import"
}
@@ -129,7 +127,6 @@ export enum ProjectPermissionPkiSubscriberActions {
export enum ProjectPermissionCertificateProfileActions {
Read = "read",
List = "list",
Create = "create",
Edit = "edit",
Delete = "delete",
@@ -153,7 +150,6 @@ export enum ProjectPermissionPkiSyncActions {
Create = "create",
Edit = "edit",
Delete = "delete",
List = "list",
SyncCertificates = "sync-certificates",
ImportCertificates = "import-certificates",
RemoveCertificates = "remove-certificates"
@@ -256,7 +252,6 @@ export enum ProjectPermissionSub {
CertificateAuthorities = "certificate-authorities",
Certificates = "certificates",
CertificateTemplates = "certificate-templates",
CertificateTemplatesV2 = "certificate-templates-v2",
SshCertificateAuthorities = "ssh-certificate-authorities",
SshCertificates = "ssh-certificates",
SshCertificateTemplates = "ssh-certificate-templates",
@@ -453,13 +448,6 @@ export type ProjectPermissionSet =
| (ForcedSubject<ProjectPermissionSub.CertificateTemplates> & PkiTemplateSubjectFields)
)
]
| [
ProjectPermissionActions,
(
| ProjectPermissionSub.CertificateTemplatesV2
| (ForcedSubject<ProjectPermissionSub.CertificateTemplatesV2> & CertificateTemplateV2SubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
@@ -640,6 +628,17 @@ const PkiSyncConditionSchema = z
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
})
.partial()
]),
subscriberName: 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();
@@ -756,6 +755,7 @@ const PkiTemplateConditionSchema = z
z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
})
@@ -875,7 +875,8 @@ const CertificateConditionSchema = z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
})
.partial()
])
@@ -898,22 +899,6 @@ const CertificateProfileConditionSchema = z
})
.partial();
const CertificateTemplateV2ConditionSchema = 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 GeneralPermissionSchema = [
z.object({
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
@@ -1308,16 +1293,6 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({
subject: z.literal(ProjectPermissionSub.CertificateTemplatesV2).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),
conditions: CertificateTemplateV2ConditionSchema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
...GeneralPermissionSchema
]);

View File

@@ -0,0 +1,341 @@
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
*/
export 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;
};
/**
* Convert CASL condition object to MongoDB-compatible database filter
* @param condition - CASL condition object
* @returns MongoDB filter object
*/
export function convertConditionToDbFilter(condition: Record<string, unknown>): Record<string, unknown> {
const dbFilter: Record<string, unknown> = {};
for (const key in condition) {
if (Object.prototype.hasOwnProperty.call(condition, key)) {
const value = condition[key];
if (value && typeof value === "object" && !Array.isArray(value)) {
const operator = Object.keys(value)[0];
const opValue = (value as Record<string, unknown>)[operator];
switch (operator) {
case "$glob": {
// Convert "*.es" glob → regex /^.*\.es$/i
const regex = new RegExp(`^${String(opValue).replace(/\*/g, ".*")}$`, "i");
dbFilter[key] = { $regex: regex };
break;
}
case "$in":
dbFilter[key] = { $in: opValue };
break;
case "$eq":
dbFilter[key] = { $eq: opValue };
break;
default:
dbFilter[key] = value;
break;
}
} else {
dbFilter[key] = { $eq: value };
}
}
}
return dbFilter;
}
/**
* Extract ALL conditions for a subject and action,
* converting them into DB-safe filter objects.
* @param ability - CASL MongoAbility instance
* @param action - Permission action to filter for
* @param subjectName - Permission subject to filter for
* @returns MongoDB filter object with $or conditions
*/
export function getDbFiltersForAbility(
ability: MongoAbility,
action: string,
subjectName: string
): Record<string, unknown> {
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 converted = matchingRules.map((rule: RawRuleOf<MongoAbility>) =>
convertConditionToDbFilter(rule.conditions as Record<string, unknown>)
);
if (converted.length === 0) {
return {};
}
return { $or: converted };
}
/**
* 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 };
}
/**
* Extract permission filters for a subject and action,
* converting them into PermissionFilters format for use with Knex queries.
* This is a simplified version that maintains backward compatibility.
* @param ability - CASL MongoAbility instance
* @param action - Permission action to filter for
* @param subjectName - Permission subject to filter for
* @returns PermissionFilters object for use with applyPermissionFiltersToQuery
*/
export function getPermissionFiltersForAbility(
ability: MongoAbility,
action: string,
subjectName: string
): PermissionFilters {
const processedRules = getProcessedPermissionRules(ability, action, subjectName);
const mergedFilters: PermissionFilters = {};
processedRules.allowRules.forEach((rule) => {
Object.entries(rule).forEach(([key, filterConfigs]) => {
if (!mergedFilters[key]) {
mergedFilters[key] = [];
}
mergedFilters[key].push(...filterConfigs);
});
});
processedRules.forbidRules.forEach((rule) => {
Object.entries(rule).forEach(([key, filterConfigs]) => {
if (!mergedFilters[key]) {
mergedFilters[key] = [];
}
mergedFilters[key].push(...filterConfigs);
});
});
return mergedFilters;
}

View File

@@ -0,0 +1,281 @@
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 permission filters to a Knex query for any table
* @param query - The Knex query builder instance
* @param tableName - The name of the table to apply filters to
* @param permissionFilters - Record of field names to arrays of filter configurations
* @returns The modified query builder with permission filters applied
*/
export const applyPermissionFiltersToQuery = (
originalQuery: Knex.QueryBuilder,
tableName: string,
permissionFilters?: PermissionFilters
): Knex.QueryBuilder => {
if (!permissionFilters) {
return originalQuery;
}
let query = originalQuery;
Object.entries(permissionFilters).forEach(([key, filterConfigs]) => {
filterConfigs.forEach((filterConfig) => {
if (filterConfig.value !== undefined && filterConfig.value !== null) {
const { operator, value, isPattern } = filterConfig;
const fieldName = `${tableName}.${key}`;
switch (operator) {
case "=":
query = query.andWhere(fieldName, "=", value as string | number);
break;
case "!=":
query = query.andWhere(fieldName, "!=", value as string | number);
break;
case "LIKE": {
const likePattern = isPattern ? String(value).replace(new RE2("\\*", "g"), "%") : String(value);
query = query.andWhere(fieldName, "like", likePattern);
break;
}
case "NOT LIKE": {
const notLikePattern = isPattern ? String(value).replace(new RE2("\\*", "g"), "%") : String(value);
query = query.andWhere(fieldName, "not like", notLikePattern);
break;
}
case "IN": {
const inValues = Array.isArray(value) ? value : [value];
query = query.andWhere(fieldName, "in", inValues as (string | number)[]);
break;
}
case "NOT IN": {
const notInValues = Array.isArray(value) ? value : [value];
query = query.andWhere(fieldName, "not in", notInValues as (string | number)[]);
break;
}
case ">":
query = query.andWhere(fieldName, ">", value as string | number);
break;
case ">=":
query = query.andWhere(fieldName, ">=", value as string | number);
break;
case "<":
query = query.andWhere(fieldName, "<", value as string | number);
break;
case "<=":
query = query.andWhere(fieldName, "<=", value as string | number);
break;
case "IS NULL":
query = query.andWhere(fieldName, "is", null);
break;
case "IS NOT NULL":
query = query.andWhere(fieldName, "is not", null);
break;
default:
query = query.andWhere(fieldName, "=", value as string | number);
break;
}
}
});
});
return query;
};
/**
* 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) => {
query = query.andWhere((forbidBuilder) => {
let hasConditions = false;
Object.entries(forbidRule).forEach(([key, filterConfigs]) => {
filterConfigs.forEach((filterConfig) => {
const negatedConfig = { ...filterConfig };
switch (filterConfig.operator) {
case "=":
negatedConfig.operator = "!=";
break;
case "!=":
negatedConfig.operator = "=";
break;
case "LIKE":
negatedConfig.operator = "NOT LIKE";
break;
case "NOT LIKE":
negatedConfig.operator = "LIKE";
break;
case "IN":
negatedConfig.operator = "NOT IN";
break;
case "NOT IN":
negatedConfig.operator = "IN";
break;
case ">":
negatedConfig.operator = "<=";
break;
case ">=":
negatedConfig.operator = "<";
break;
case "<":
negatedConfig.operator = ">=";
break;
case "<=":
negatedConfig.operator = ">";
break;
case "IS NULL":
negatedConfig.operator = "IS NOT NULL";
break;
case "IS NOT NULL":
negatedConfig.operator = "IS NULL";
break;
default:
negatedConfig.operator = "!=";
break;
}
if (hasConditions) {
void forbidBuilder.orWhere((subBuilder) => {
applySingleFilter(subBuilder, tableName, key, negatedConfig);
});
} else {
void forbidBuilder.where((subBuilder) => {
applySingleFilter(subBuilder, tableName, key, negatedConfig);
});
hasConditions = true;
}
});
});
});
});
}
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"), "\\$&");
};

View File

@@ -715,11 +715,21 @@ export const AcmeCertificateAuthorityFns = ({
return castDbEntryToAcmeCertificateAuthority(updatedCa);
};
const listCertificateAuthorities = async ({ projectId }: { projectId: string }) => {
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.ACME
});
const listCertificateAuthorities = async ({
projectId,
permissionFilters
}: {
projectId: string;
permissionFilters?: import("@app/lib/knex/permission-filter-utils").PermissionFilters;
}) => {
const cas = await certificateAuthorityDAL.findWithAssociatedCa(
{
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.ACME
},
{},
permissionFilters
);
return cas.map(castDbEntryToAcmeCertificateAuthority);
};

View File

@@ -798,11 +798,21 @@ export const AzureAdCsCertificateAuthorityFns = ({
return castDbEntryToAzureAdCsCertificateAuthority(updatedCa);
};
const listCertificateAuthorities = async ({ projectId }: { projectId: string }) => {
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: CaType.AZURE_AD_CS
});
const listCertificateAuthorities = async ({
projectId,
permissionFilters
}: {
projectId: string;
permissionFilters?: import("@app/lib/knex/permission-filter-utils").PermissionFilters;
}) => {
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);
};

View File

@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
import { CertificateAuthoritiesSchema, TableName, TCertificateAuthorities } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindOpt } from "@app/lib/knex";
import { applyPermissionFiltersToQuery, type PermissionFilters } from "@app/lib/knex/permission-filter-utils";
export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAuthorityDALFactory>;
@@ -220,10 +221,11 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
const findWithAssociatedCa = async (
filter: Parameters<(typeof caOrm)["find"]>[0] & { dn?: string; type?: string; serialNumber?: string },
{ offset, limit, sort = [["createdAt", "desc"]] }: TFindOpt<TCertificateAuthorities> = {},
permissionFilters?: PermissionFilters,
tx?: Knex
) => {
try {
const query = (tx || db.replicaNode())(TableName.CertificateAuthority)
let query = (tx || db.replicaNode())(TableName.CertificateAuthority)
.leftJoin(
TableName.InternalCertificateAuthority,
`${TableName.CertificateAuthority}.id`,
@@ -268,6 +270,10 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
);
if (permissionFilters) {
query = applyPermissionFiltersToQuery(query, TableName.CertificateAuthority, permissionFilters) as typeof query;
}
if (limit) void query.limit(limit);
if (offset) void query.offset(offset);
if (sort) {

View File

@@ -6,6 +6,7 @@ import {
ProjectPermissionCertificateAuthorityActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { getPermissionFiltersForAbility } from "@app/lib/casl/permission-filter-utils";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
@@ -332,15 +333,25 @@ export const certificateAuthorityServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateAuthorityActions.List,
ProjectPermissionCertificateAuthorityActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
const permissionFilters = getPermissionFiltersForAbility(
permission,
ProjectPermissionCertificateAuthorityActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
if (type === CaType.INTERNAL) {
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
$notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"]
});
const cas = await certificateAuthorityDAL.findWithAssociatedCa(
{
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId,
$notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"]
},
{},
permissionFilters
);
return cas
.filter((ca): ca is typeof ca & { internalCa: NonNullable<typeof ca.internalCa> } => Boolean(ca.internalCa))
@@ -356,11 +367,11 @@ export const certificateAuthorityServiceFactory = ({
}
if (type === CaType.ACME) {
return acmeFns.listCertificateAuthorities({ projectId });
return acmeFns.listCertificateAuthorities({ projectId, permissionFilters });
}
if (type === CaType.AZURE_AD_CS) {
return azureAdCsFns.listCertificateAuthorities({ projectId });
return azureAdCsFns.listCertificateAuthorities({ projectId, permissionFilters });
}
throw new BadRequestError({ message: "Invalid certificate authority type" });

View File

@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { applyPermissionFiltersToQuery, type PermissionFilters } from "@app/lib/knex/permission-filter-utils";
import {
EnrollmentType,
@@ -276,6 +277,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
issuerType?: IssuerType;
caId?: string;
} = {},
permissionFilters?: PermissionFilters,
tx?: Knex
): Promise<TCertificateProfile[] | TCertificateProfileWithConfigs[]> => {
try {
@@ -308,7 +310,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.issuerType`, issuerType);
}
const query = baseQuery
let query = baseQuery
.leftJoin(
TableName.CertificateAuthority,
`${TableName.PkiCertificateProfile}.caId`,
@@ -354,6 +356,14 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId")
);
if (permissionFilters) {
query = applyPermissionFiltersToQuery(
query,
TableName.PkiCertificateProfile,
permissionFilters
) as typeof query;
}
const results = (await query
.orderBy(`${TableName.PkiCertificateProfile}.createdAt`, "desc")
.offset(offset)
@@ -432,6 +442,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
issuerType?: IssuerType;
caId?: string;
} = {},
permissionFilters?: PermissionFilters,
tx?: Knex
): Promise<number> => {
try {
@@ -459,6 +470,14 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
query = query.where({ issuerType });
}
if (permissionFilters) {
query = applyPermissionFiltersToQuery(
query,
TableName.PkiCertificateProfile,
permissionFilters
) as typeof query;
}
const result = await query.count("*").first();
return parseInt((result as unknown as { count: string }).count || "0", 10);
} catch (error) {

View File

@@ -10,6 +10,7 @@ import {
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns";
import { getPermissionFiltersForAbility } from "@app/lib/casl/permission-filter-utils";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
@@ -745,25 +746,39 @@ export const certificateProfileServiceFactory = ({
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateProfileActions.List,
ProjectPermissionCertificateProfileActions.Read,
ProjectPermissionSub.CertificateProfiles
);
const profiles = await certificateProfileDAL.findByProjectId(projectId, {
offset,
limit,
search,
enrollmentType,
issuerType,
caId
});
const permissionFilters = getPermissionFiltersForAbility(
permission,
ProjectPermissionCertificateProfileActions.Read,
ProjectPermissionSub.CertificateProfiles
);
const totalCount = await certificateProfileDAL.countByProjectId(projectId, {
search,
enrollmentType,
issuerType,
caId
});
const profiles = await certificateProfileDAL.findByProjectId(
projectId,
{
offset,
limit,
search,
enrollmentType,
issuerType,
caId
},
permissionFilters
);
const totalCount = await certificateProfileDAL.countByProjectId(
projectId,
{
search,
enrollmentType,
issuerType,
caId
},
permissionFilters
);
const convertedProfiles = await Promise.all(
profiles.map(async (profile) => {

View File

@@ -5,6 +5,7 @@ import { TableName } from "@app/db/schemas";
import { TPkiCertificateTemplatesV2Insert } from "@app/db/schemas/pki-certificate-templates-v2";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { applyPermissionFiltersToQuery, type PermissionFilters } from "@app/lib/knex/permission-filter-utils";
import {
TCertificateTemplateV2,
@@ -133,6 +134,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
limit?: number;
search?: string;
} = {},
permissionFilters?: PermissionFilters,
tx?: Knex
) => {
try {
@@ -146,6 +148,14 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
});
}
if (permissionFilters) {
query = applyPermissionFiltersToQuery(
query,
TableName.PkiCertificateTemplateV2,
permissionFilters
) as typeof query;
}
const certificateTemplatesV2 = await query.orderBy("createdAt", "desc").offset(offset).limit(limit);
return certificateTemplatesV2.map((template: Record<string, unknown>) => parseJsonFields(template));
@@ -159,6 +169,7 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
options: {
search?: string;
} = {},
permissionFilters?: PermissionFilters,
tx?: Knex
) => {
try {
@@ -172,6 +183,14 @@ export const certificateTemplateV2DALFactory = (db: TDbClient) => {
});
}
if (permissionFilters) {
query = applyPermissionFiltersToQuery(
query,
TableName.PkiCertificateTemplateV2,
permissionFilters
) as typeof query;
}
const result = await query.count("*").first();
return parseInt((result as unknown as { count: string }).count || "0", 10);
} catch (error) {

View File

@@ -8,6 +8,7 @@ import {
ProjectPermissionPkiTemplateActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { getPermissionFiltersForAbility } from "@app/lib/casl/permission-filter-utils";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -871,13 +872,18 @@ export const certificateTemplateV2ServiceFactory = ({
ProjectPermissionSub.CertificateTemplates
);
const templates = await certificateTemplateV2DAL.findByProjectId(projectId, {
offset,
limit,
search
});
const permissionFilters = getPermissionFiltersForAbility(
permission,
ProjectPermissionPkiTemplateActions.Read,
ProjectPermissionSub.CertificateTemplates
);
const templates = await certificateTemplateV2DAL.findByProjectId(
projectId,
{ offset, limit, search },
permissionFilters
);
const totalCount = await certificateTemplateV2DAL.countByProjectId(projectId, { search });
const totalCount = await certificateTemplateV2DAL.countByProjectId(projectId, { search }, permissionFilters);
return {
templates,

View File

@@ -1491,10 +1491,12 @@ export const certificateV3ServiceFactory = ({
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateProfileActions.IssueCert,
subject(ProjectPermissionSub.CertificateProfiles, { slug: profile?.slug || "*" })
);
if (profile) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateProfileActions.IssueCert,
subject(ProjectPermissionSub.CertificateProfiles, { slug: profile.slug })
);
}
}
const issuerType = profile?.issuerType || (originalCert.caId ? IssuerType.CA : IssuerType.SELF_SIGNED);

View File

@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
import { TableName, TCertificates } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { applyPermissionFiltersToQuery, type PermissionFilters } from "@app/lib/knex/permission-filter-utils";
import { CertStatus } from "./certificate-types";
@@ -140,7 +141,8 @@ export const certificateDALFactory = (db: TDbClient) => {
const findActiveCertificatesForSync = async (
filter: Partial<TCertificates & { friendlyName?: string; commonName?: string }>,
options?: { limit?: number; offset?: number }
options?: { limit?: number; offset?: number },
permissionFilters?: PermissionFilters
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
try {
let query = db
@@ -163,6 +165,10 @@ export const certificateDALFactory = (db: TDbClient) => {
}
});
if (permissionFilters) {
query = applyPermissionFiltersToQuery(query, TableName.Certificate, permissionFilters) as typeof query;
}
if (options?.offset) {
query = query.offset(options.offset);
}
@@ -267,7 +273,8 @@ export const certificateDALFactory = (db: TDbClient) => {
const findWithPrivateKeyInfo = async (
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?: PermissionFilters
): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => {
try {
let query = db
@@ -287,6 +294,10 @@ export const certificateDALFactory = (db: TDbClient) => {
}
});
if (permissionFilters) {
query = applyPermissionFiltersToQuery(query, TableName.Certificate, permissionFilters) as typeof query;
}
if (options?.offset) {
query = query.offset(options.offset);
}

View File

@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
import { TableName, TPkiSyncs } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
import { applyPermissionFiltersToQuery, type PermissionFilters } from "@app/lib/knex/permission-filter-utils";
import { PkiSync } from "./pki-sync-enums";
@@ -45,13 +46,15 @@ const basePkiSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: PkiSyncF
const basePkiSyncWithSubscriberQuery = ({
filter,
db,
tx
tx,
permissionFilters
}: {
db: TDbClient;
filter?: PkiSyncFindFilter;
tx?: Knex;
permissionFilters?: PermissionFilters;
}) => {
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.PkiSubscriber, `${TableName.PkiSync}.subscriberId`, `${TableName.PkiSubscriber}.id`)
.select(selectAllTableCols(TableName.PkiSync))
@@ -82,6 +85,10 @@ const basePkiSyncWithSubscriberQuery = ({
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.PkiSync, filter)));
}
if (permissionFilters) {
query = applyPermissionFiltersToQuery(query, TableName.PkiSync, permissionFilters) as typeof query;
}
return query;
};
@@ -184,9 +191,18 @@ export const pkiSyncDALFactory = (db: TDbClient) => {
}
};
const findByProjectIdWithSubscribers = async (projectId: string, tx?: Knex) => {
const findByProjectIdWithSubscribers = async (
projectId: string,
permissionFilters?: PermissionFilters,
tx?: Knex
) => {
try {
const pkiSyncs = await basePkiSyncWithSubscriberQuery({ filter: { projectId }, db, tx });
const pkiSyncs = await basePkiSyncWithSubscriberQuery({
filter: { projectId },
db,
tx,
permissionFilters
});
return pkiSyncs.map(expandPkiSyncWithSubscriber);
} catch (error) {
throw new DatabaseError({ error, name: "Find By Project ID With Subscribers - PKI Sync" });

View File

@@ -4,6 +4,7 @@ import { ActionProjectType, TCertificateSyncs } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionPkiSyncActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getPermissionFiltersForAbility } from "@app/lib/casl/permission-filter-utils";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
@@ -355,9 +356,15 @@ export const pkiSyncServiceFactory = ({
projectId
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.List, ProjectPermissionSub.PkiSyncs);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs);
const pkiSyncsWithSubscribers = await pkiSyncDAL.findByProjectIdWithSubscribers(projectId);
const permissionFilters = getPermissionFiltersForAbility(
permission,
ProjectPermissionPkiSyncActions.Read,
ProjectPermissionSub.PkiSyncs
);
const pkiSyncsWithSubscribers = await pkiSyncDAL.findByProjectIdWithSubscribers(projectId, permissionFilters);
if (certificateId) {
const syncsWithCertificateInfo = await Promise.all(

View File

@@ -40,6 +40,7 @@ import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certific
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 { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { buildPermissionFiltersFromConditions, getDbFiltersForAbility } from "@app/lib/casl/permission-filter-utils";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { DatabaseErrorCode } from "@app/lib/error-codes";
@@ -912,7 +913,7 @@ export const projectServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateAuthorityActions.List,
ProjectPermissionCertificateAuthorityActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
@@ -960,39 +961,44 @@ export const projectServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateActions.List,
ProjectPermissionCertificateActions.Read,
ProjectPermissionSub.Certificates
);
const filters = getDbFiltersForAbility(
permission,
ProjectPermissionCertificateActions.Read,
ProjectPermissionSub.Certificates
);
const regularFilters = {
projectId,
...(friendlyName && { friendlyName }),
...(commonName && { commonName })
};
const permissionFilters = buildPermissionFiltersFromConditions(filters || {});
const certificates = forPkiSync
? await certificateDAL.findActiveCertificatesForSync(
{
projectId,
...(friendlyName && { friendlyName }),
...(commonName && { commonName })
},
{ offset, limit }
)
? await certificateDAL.findActiveCertificatesForSync(regularFilters, { offset, limit }, permissionFilters)
: await certificateDAL.findWithPrivateKeyInfo(
regularFilters,
{
projectId,
...(friendlyName && { friendlyName }),
...(commonName && { commonName })
offset,
limit,
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
? await certificateDAL.countActiveCertificatesForSync({
projectId,
friendlyName,
commonName
})
: await certificateDAL.countCertificatesInProject({
projectId,
friendlyName,
commonName
});
? await certificateDAL.countActiveCertificatesForSync(countFilter)
: await certificateDAL.countCertificatesInProject(countFilter);
return {
certificates,

View File

@@ -2493,8 +2493,6 @@
"pages": [
"api-reference/endpoints/certificates/list",
"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/update-config",
"api-reference/endpoints/certificates/revoke",

View File

@@ -391,8 +391,6 @@ export type CertificateSubjectFields = {
commonName?: string;
altNames?: string;
serialNumber?: string;
friendlyName?: string;
status?: string;
};
export type CertificateProfileSubjectFields = {

View File

@@ -93,7 +93,6 @@ const Page = () => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Read}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: data.id,
name: data.name
})}
>
@@ -128,7 +127,6 @@ const Page = () => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Delete}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: data.id,
name: data.name
})}
>

View File

@@ -42,7 +42,7 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
<h3 className="text-lg font-medium text-mineshaft-100">CA Details</h3>
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Edit}
a={subject(ProjectPermissionSub.CertificateAuthorities, { caId: ca.id, name: ca.name })}
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
>
{(isAllowed) => {
return (
@@ -159,7 +159,7 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
{ca.status === CaStatus.ACTIVE && (
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Renew}
a={subject(ProjectPermissionSub.CertificateAuthorities, { caId: ca.id, name: ca.name })}
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
>
{(isAllowed) => {
return (
@@ -195,7 +195,7 @@ export const CaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {
{ca.status === CaStatus.PENDING_CERTIFICATE && (
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Create}
a={subject(ProjectPermissionSub.CertificateAuthorities, { caId: ca.id, name: ca.name })}
a={subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name })}
>
{(isAllowed) => {
return (

View File

@@ -104,6 +104,7 @@ export const CaSection = () => {
: "This action will prevent the CA from issuing new certificates."
}
onChange={(isOpen) => handlePopUpToggle("caStatus", isOpen)}
buttonText="Confirm"
deleteKey="confirm"
onDeleteApproved={() =>
onUpdateCaStatus(popUp?.caStatus?.data as { caId: string; status: CaStatus })

View File

@@ -81,7 +81,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
const canReadCa = permission.can(
ProjectPermissionCertificateAuthorityActions.Read,
subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})
);
@@ -136,7 +135,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Create}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>
@@ -164,7 +162,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Read}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>
@@ -192,7 +189,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Edit}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>
@@ -223,7 +219,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Delete}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>

View File

@@ -75,7 +75,6 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
const canReadCa = permission.can(
ProjectPermissionCertificateAuthorityActions.Read,
subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})
);
@@ -117,7 +116,6 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Edit}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>
@@ -145,7 +143,6 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Edit}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>
@@ -177,7 +174,6 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
<ProjectPermissionCan
I={ProjectPermissionCertificateAuthorityActions.Delete}
a={subject(ProjectPermissionSub.CertificateAuthorities, {
caId: ca.id,
name: ca.name
})}
>

View File

@@ -67,7 +67,6 @@ export const PkiSyncRow = ({
const navigate = useNavigate();
const {
id,
subscriberId,
lastSyncMessage,
destination,
lastSyncedAt,
@@ -117,7 +116,6 @@ export const PkiSyncRow = ({
const destinationDetails = PKI_SYNC_MAP[destination];
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriberId || "",
subscriberName: destinationName,
name
});

View File

@@ -53,7 +53,7 @@ type Props = {
};
export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
const { destination, subscriberId, projectId, id } = pkiSync;
const { destination, projectId, id } = pkiSync;
const navigate = useNavigate();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
@@ -112,7 +112,6 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => {
}, [updatePkiSyncMutation, id, projectId, pkiSync.isAutoSyncEnabled]);
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -88,7 +88,6 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: pkiSync.subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -31,7 +31,7 @@ type Props = {
};
export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) => {
const { destination, subscriberId } = pkiSync;
const { destination } = pkiSync;
const destinationDetails = PKI_SYNC_MAP[destination];
@@ -55,7 +55,6 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props)
}
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriberId || "",
subscriberName: destinationDetails.name,
name: pkiSync.name
});

View File

@@ -34,8 +34,7 @@ type Props = {
};
export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => {
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, subscriberId, subscriber } =
pkiSync;
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, subscriber } = pkiSync;
const failureMessage = useMemo(() => {
if (syncStatus === PkiSyncStatus.Failed) {
@@ -54,7 +53,6 @@ export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriber?.id || subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -39,7 +39,6 @@ export const PkiSyncFieldMappingsSection = ({ pkiSync, onEditMappings }: Props)
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: pkiSync.subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -38,7 +38,6 @@ export const PkiSyncOptionsSection = ({ pkiSync, onEditOptions }: Props) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: pkiSync.subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -30,7 +30,6 @@ export const PkiSyncSourceSection = ({ pkiSync, onEditSource }: Props) => {
const destinationName = PKI_SYNC_MAP[pkiSync.destination].name;
const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, {
subscriberId: subscriberId || "",
subscriberName: destinationName,
name: pkiSync.name
});

View File

@@ -14,7 +14,6 @@ export const CertificatePermissionConditions = ({ position = 0, isDisabled }: Pr
subject={ProjectPermissionSub.Certificates}
position={position}
selectOptions={[
{ value: "id", label: "Certificate ID" },
{ value: "commonName", label: "Common Name" },
{ value: "altNames", label: "Subject Alternative Names" },
{ value: "serialNumber", label: "Serial Number" }

View File

@@ -36,6 +36,7 @@ export const renderOperatorSelectItems = (type: string) => {
return (
<>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$NEQ}>Not Equal</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob Match</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
</>

View File

@@ -57,7 +57,6 @@ const CertificatePolicyActionSchema = z.object({
[ProjectPermissionCertificateActions.Delete]: z.boolean().optional(),
[ProjectPermissionCertificateActions.Edit]: z.boolean().optional(),
[ProjectPermissionCertificateActions.Read]: z.boolean().optional(),
[ProjectPermissionCertificateActions.List]: z.boolean().optional(),
[ProjectPermissionCertificateActions.ReadPrivateKey]: z.boolean().optional(),
[ProjectPermissionCertificateActions.Import]: z.boolean().optional()
});
@@ -67,7 +66,6 @@ const CertificateAuthorityPolicyActionSchema = z.object({
[ProjectPermissionCertificateAuthorityActions.Delete]: z.boolean().optional(),
[ProjectPermissionCertificateAuthorityActions.Edit]: z.boolean().optional(),
[ProjectPermissionCertificateAuthorityActions.Read]: z.boolean().optional(),
[ProjectPermissionCertificateAuthorityActions.List]: z.boolean().optional(),
[ProjectPermissionCertificateAuthorityActions.Renew]: z.boolean().optional(),
[ProjectPermissionCertificateAuthorityActions.SignIntermediate]: z.boolean().optional()
});
@@ -123,7 +121,6 @@ const PkiSyncPolicyActionSchema = z.object({
[ProjectPermissionPkiSyncActions.Create]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.Edit]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.Delete]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.List]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.SyncCertificates]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.ImportCertificates]: z.boolean().optional(),
[ProjectPermissionPkiSyncActions.RemoveCertificates]: z.boolean().optional()
@@ -234,7 +231,6 @@ const PkiTemplatePolicyActionSchema = z.object({
});
const CertificateProfilePolicyActionSchema = z.object({
[ProjectPermissionCertificateProfileActions.Read]: z.boolean().optional(),
[ProjectPermissionCertificateProfileActions.List]: z.boolean().optional(),
[ProjectPermissionCertificateProfileActions.Create]: z.boolean().optional(),
[ProjectPermissionCertificateProfileActions.Edit]: z.boolean().optional(),
[ProjectPermissionCertificateProfileActions.Delete]: z.boolean().optional(),
@@ -696,7 +692,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const canEdit = action.includes(ProjectPermissionPkiSyncActions.Edit);
const canDelete = action.includes(ProjectPermissionPkiSyncActions.Delete);
const canCreate = action.includes(ProjectPermissionPkiSyncActions.Create);
const canList = action.includes(ProjectPermissionPkiSyncActions.List);
const canSyncCertificates = action.includes(
ProjectPermissionPkiSyncActions.SyncCertificates
);
@@ -715,7 +710,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
[ProjectPermissionPkiSyncActions.Create]: canCreate,
[ProjectPermissionPkiSyncActions.Edit]: canEdit,
[ProjectPermissionPkiSyncActions.Delete]: canDelete,
[ProjectPermissionPkiSyncActions.List]: canList,
[ProjectPermissionPkiSyncActions.SyncCertificates]: canSyncCertificates,
[ProjectPermissionPkiSyncActions.ImportCertificates]: canImportCertificates,
[ProjectPermissionPkiSyncActions.RemoveCertificates]: canRemoveCertificates,
@@ -864,7 +858,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const canCreate = action.includes(ProjectPermissionCertificateAuthorityActions.Create);
const canEdit = action.includes(ProjectPermissionCertificateAuthorityActions.Edit);
const canDelete = action.includes(ProjectPermissionCertificateAuthorityActions.Delete);
const canList = action.includes(ProjectPermissionCertificateAuthorityActions.List);
const canRenew = action.includes(ProjectPermissionCertificateAuthorityActions.Renew);
const canSignIntermediate = action.includes(
ProjectPermissionCertificateAuthorityActions.SignIntermediate
@@ -876,7 +869,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
[ProjectPermissionCertificateAuthorityActions.Create]: canCreate,
[ProjectPermissionCertificateAuthorityActions.Edit]: canEdit,
[ProjectPermissionCertificateAuthorityActions.Delete]: canDelete,
[ProjectPermissionCertificateAuthorityActions.List]: canList,
[ProjectPermissionCertificateAuthorityActions.Renew]: canRenew,
[ProjectPermissionCertificateAuthorityActions.SignIntermediate]: canSignIntermediate,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
@@ -937,7 +929,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const canEdit = action.includes(ProjectPermissionCertificateActions.Edit);
const canDelete = action.includes(ProjectPermissionCertificateActions.Delete);
const canCreate = action.includes(ProjectPermissionCertificateActions.Create);
const canList = action.includes(ProjectPermissionCertificateActions.List);
const canImport = action.includes(ProjectPermissionCertificateActions.Import);
const canReadPrivateKey = action.includes(ProjectPermissionCertificateActions.ReadPrivateKey);
@@ -949,7 +940,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
[ProjectPermissionCertificateActions.Edit]: canEdit,
[ProjectPermissionCertificateActions.Create]: canCreate,
[ProjectPermissionCertificateActions.Delete]: canDelete,
[ProjectPermissionCertificateActions.List]: canList,
[ProjectPermissionCertificateActions.ReadPrivateKey]: canReadPrivateKey,
[ProjectPermissionCertificateActions.Import]: canImport,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
@@ -1250,9 +1240,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
[ProjectPermissionCertificateProfileActions.Read]: action.includes(
ProjectPermissionCertificateProfileActions.Read
),
[ProjectPermissionCertificateProfileActions.List]: action.includes(
ProjectPermissionCertificateProfileActions.List
),
[ProjectPermissionCertificateProfileActions.IssueCert]: action.includes(
ProjectPermissionCertificateProfileActions.IssueCert
),
@@ -1629,7 +1616,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
title: "Certificate Authorities",
actions: [
{ label: "Read", value: ProjectPermissionCertificateAuthorityActions.Read },
{ label: "List", value: ProjectPermissionCertificateAuthorityActions.List },
{ label: "Create", value: ProjectPermissionCertificateAuthorityActions.Create },
{ label: "Modify", value: ProjectPermissionCertificateAuthorityActions.Edit },
{ label: "Remove", value: ProjectPermissionCertificateAuthorityActions.Delete },
@@ -1644,7 +1630,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
title: "Certificates",
actions: [
{ label: "Read", value: ProjectPermissionCertificateActions.Read },
{ label: "List", value: ProjectPermissionCertificateActions.List },
{ label: "Read Private Key", value: ProjectPermissionCertificateActions.ReadPrivateKey },
// { label: "Create", value: ProjectPermissionCertificateActions.Create }, // Hidden from UI - kept for backend compatibility
{ label: "Import", value: ProjectPermissionCertificateActions.Import },
@@ -1666,7 +1651,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
title: "Certificate Profiles",
actions: [
{ label: "Read", value: ProjectPermissionCertificateProfileActions.Read },
{ label: "List", value: ProjectPermissionCertificateProfileActions.List },
{ label: "Create", value: ProjectPermissionCertificateProfileActions.Create },
{ label: "Modify", value: ProjectPermissionCertificateProfileActions.Edit },
{ label: "Remove", value: ProjectPermissionCertificateProfileActions.Delete },
@@ -1799,7 +1783,6 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
title: "Certificate Syncs",
actions: [
{ label: "Read", value: ProjectPermissionPkiSyncActions.Read },
{ label: "List", value: ProjectPermissionPkiSyncActions.List },
{ label: "Create", value: ProjectPermissionPkiSyncActions.Create },
{ label: "Modify", value: ProjectPermissionPkiSyncActions.Edit },
{ label: "Remove", value: ProjectPermissionPkiSyncActions.Delete },