Files
infisical/backend/src/lib/knex/scim.ts

144 lines
4.8 KiB
TypeScript

import { Knex } from "knex";
import { Compare, Filter, parse } from "scim2-parse-filter";
import { TableName } from "@app/db/schemas";
const appendParentToGroupingOperator = (parentPath: string, filter: Filter) => {
if (filter.op !== "[]" && filter.op !== "and" && filter.op !== "or" && filter.op !== "not") {
return { ...filter, attrPath: `${parentPath}.${(filter as Compare).attrPath}` };
}
return filter;
};
const processDynamicQuery = (
rootQuery: Knex.QueryBuilder,
scimRootFilterAst: Filter,
getAttributeField: (attr: string) => string | null,
depth = 0
) => {
if (depth > 20) return;
const stack = [
{
scimFilterAst: scimRootFilterAst,
query: rootQuery
}
];
while (stack.length) {
const { scimFilterAst, query } = stack.pop()!;
switch (scimFilterAst.op) {
case "eq": {
let sanitizedValue = scimFilterAst.compValue;
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
sanitizedValue = sanitizedValue.toLowerCase();
}
if (attrPath) void query.where(attrPath, sanitizedValue);
break;
}
case "pr": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.whereNotNull(attrPath);
break;
}
case "gt": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.where(attrPath, ">", scimFilterAst.compValue);
break;
}
case "ge": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.where(attrPath, ">=", scimFilterAst.compValue);
break;
}
case "lt": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.where(attrPath, "<", scimFilterAst.compValue);
break;
}
case "le": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.where(attrPath, "<=", scimFilterAst.compValue);
break;
}
case "sw": {
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath) void query.whereILike(attrPath, `${scimFilterAst.compValue}%`);
break;
}
case "ew": {
let sanitizedValue = scimFilterAst.compValue;
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
sanitizedValue = sanitizedValue.toLowerCase();
}
if (attrPath) void query.whereILike(attrPath, `%${sanitizedValue}`);
break;
}
case "co": {
let sanitizedValue = scimFilterAst.compValue;
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
sanitizedValue = sanitizedValue.toLowerCase();
}
if (attrPath) void query.whereILike(attrPath, `%${sanitizedValue}%`);
break;
}
case "ne": {
let sanitizedValue = scimFilterAst.compValue;
const attrPath = getAttributeField(scimFilterAst.attrPath);
if (attrPath === `${TableName.Users}.email` && typeof sanitizedValue === "string") {
sanitizedValue = sanitizedValue.toLowerCase();
}
if (attrPath) void query.whereNot(attrPath, "=", sanitizedValue);
break;
}
case "and": {
scimFilterAst.filters.forEach((el) => {
void query.andWhere((subQueryBuilder) => {
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
});
});
break;
}
case "or": {
scimFilterAst.filters.forEach((el) => {
void query.orWhere((subQueryBuilder) => {
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
});
});
break;
}
case "not": {
void query.whereNot((subQueryBuilder) => {
processDynamicQuery(subQueryBuilder, scimFilterAst.filter, getAttributeField, depth + 1);
});
break;
}
case "[]": {
void query.where((subQueryBuilder) => {
processDynamicQuery(
subQueryBuilder,
appendParentToGroupingOperator(scimFilterAst.attrPath, scimFilterAst.valFilter),
getAttributeField,
depth + 1
);
});
break;
}
default:
break;
}
}
};
export const generateKnexQueryFromScim = (
rootQuery: Knex.QueryBuilder,
rootScimFilter: string,
getAttributeField: (attr: string) => string | null
) => {
const scimRootFilterAst = parse(rootScimFilter);
return processDynamicQuery(rootQuery, scimRootFilterAst, getAttributeField);
};