mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 23:27:35 +00:00
Begin add push groups SCIM
This commit is contained in:
@@ -25,7 +25,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.createTable(TableName.UserGroupMembership, (t) => {
|
await knex.schema.createTable(TableName.UserGroupMembership, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); // link to user and link to groups cascade on groups
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); // link to user and link to groups cascade on groups
|
||||||
t.uuid("userId").notNullable();
|
t.uuid("userId").notNullable();
|
||||||
t.foreign("userId").references("id").inTable(TableName.Users);
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
t.uuid("groupId").notNullable();
|
t.uuid("groupId").notNullable();
|
||||||
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
|
|||||||
@@ -250,40 +250,81 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO
|
||||||
server.route({
|
server.route({
|
||||||
url: "/Users/:userId",
|
url: "/Groups",
|
||||||
method: "PATCH",
|
method: "POST",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
|
||||||
userId: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
body: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
Operations: z.array(
|
displayName: z.string().trim(),
|
||||||
z.object({
|
members: z.array(z.any()).length(0)
|
||||||
op: z.string().trim(),
|
|
||||||
path: z.string().trim().optional(),
|
|
||||||
value: z.union([
|
|
||||||
z.object({
|
|
||||||
active: z.boolean()
|
|
||||||
}),
|
|
||||||
z.string().trim()
|
|
||||||
])
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(z.any()).length(0),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
console.log("create group");
|
||||||
|
console.log("create group req.body: ", req.body);
|
||||||
|
|
||||||
|
const group = await req.server.services.scim.createScimGroup({
|
||||||
|
displayName: req.body.displayName,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("create group resulting group: ", group);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: GET /api/v1/scim/Groups/8432b0aa-93e0-4b55-af3c-cd3fcc176f81
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
// body: z.object({
|
||||||
|
// schemas: z.array(z.string()),
|
||||||
|
// Operations: z.array(
|
||||||
|
// z.object({
|
||||||
|
// op: z.string().trim(),
|
||||||
|
// path: z.string().trim().optional(),
|
||||||
|
// value: z.union([
|
||||||
|
// z.object({
|
||||||
|
// active: z.boolean()
|
||||||
|
// }),
|
||||||
|
// z.string().trim()
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({})
|
200: z.object({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await req.server.services.scim.updateScimUser({
|
console.log("get scim group endpoint start");
|
||||||
userId: req.params.userId,
|
const group = await req.server.services.scim.getScimGroup({
|
||||||
orgId: req.permission.orgId,
|
groupId: req.params.groupId,
|
||||||
operations: req.body.Operations
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return user;
|
console.log("get scim group endpoint end: ", group);
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: true,
|
||||||
scim: false,
|
scim: true,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
groups: true,
|
groups: true,
|
||||||
status: null,
|
status: null,
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: true,
|
||||||
scim: false,
|
scim: true,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
groups: true,
|
groups: true,
|
||||||
status: null,
|
status: null,
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export type TFeatureSet = {
|
|||||||
customAlerts: false;
|
customAlerts: false;
|
||||||
auditLogs: false;
|
auditLogs: false;
|
||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
samlSSO: false;
|
samlSSO: true;
|
||||||
scim: false;
|
scim: true;
|
||||||
ldap: false;
|
ldap: false;
|
||||||
groups: true;
|
groups: true;
|
||||||
status: null;
|
status: null;
|
||||||
|
|||||||
@@ -62,3 +62,28 @@ export const buildScimUser = ({
|
|||||||
|
|
||||||
return scimUser;
|
return scimUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildScimGroup = ({
|
||||||
|
groupId,
|
||||||
|
name,
|
||||||
|
members
|
||||||
|
}: {
|
||||||
|
groupId: string;
|
||||||
|
name: string;
|
||||||
|
members: {
|
||||||
|
value: string;
|
||||||
|
display: string;
|
||||||
|
}[];
|
||||||
|
}) => {
|
||||||
|
const scimGroup = {
|
||||||
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||||
|
id: groupId,
|
||||||
|
displayName: name,
|
||||||
|
members,
|
||||||
|
meta: {
|
||||||
|
resourceType: "Group"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return scimGroup;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||||
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -17,11 +20,13 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { buildScimUser, buildScimUserList } from "./scim-fns";
|
import { buildScimGroup, buildScimUser, buildScimUserList } from "./scim-fns";
|
||||||
import {
|
import {
|
||||||
|
TCreateScimGroupDTO,
|
||||||
TCreateScimTokenDTO,
|
TCreateScimTokenDTO,
|
||||||
TCreateScimUserDTO,
|
TCreateScimUserDTO,
|
||||||
TDeleteScimTokenDTO,
|
TDeleteScimTokenDTO,
|
||||||
|
TGetScimGroupDTO,
|
||||||
TGetScimUserDTO,
|
TGetScimUserDTO,
|
||||||
TListScimUsers,
|
TListScimUsers,
|
||||||
TListScimUsersDTO,
|
TListScimUsersDTO,
|
||||||
@@ -39,6 +44,7 @@ type TScimServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "findAllGroupMembers">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
@@ -53,6 +59,7 @@ export const scimServiceFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService
|
||||||
}: TScimServiceFactoryDep) => {
|
}: TScimServiceFactoryDep) => {
|
||||||
@@ -423,6 +430,59 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createScimGroup = async ({ displayName, orgId }: TCreateScimGroupDTO) => {
|
||||||
|
// TODO 1: impl basic
|
||||||
|
// TODO 2: impl linking case
|
||||||
|
console.log("createScimGroup args: ", {
|
||||||
|
displayName,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await groupDAL.create({
|
||||||
|
name: displayName,
|
||||||
|
slug: slugify(`${displayName}-${alphaNumericNanoId(4)}`),
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.NoAccess
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("createScimGroup the group: ", group);
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScimGroup = async ({ groupId, orgId }: TGetScimGroupDTO) => {
|
||||||
|
console.log("getScimGroup args: ", {
|
||||||
|
groupId,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await groupDAL.findOne({
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("getScimGroup group: ", group);
|
||||||
|
const users = await groupDAL.findAllGroupMembers(group.orgId, group.id);
|
||||||
|
console.log("getScimGroup users: ", users);
|
||||||
|
|
||||||
|
// TODO: get members
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: users
|
||||||
|
.filter((user) => user.isPartOfGroup)
|
||||||
|
.map((user) => ({
|
||||||
|
value: user.id,
|
||||||
|
display: `${user.firstName} ${user.lastName}`
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
|
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
|
||||||
const scimToken = await scimDAL.findById(token.scimTokenId);
|
const scimToken = await scimDAL.findById(token.scimTokenId);
|
||||||
if (!scimToken) throw new UnauthorizedError();
|
if (!scimToken) throw new UnauthorizedError();
|
||||||
@@ -455,6 +515,8 @@ export const scimServiceFactory = ({
|
|||||||
createScimUser,
|
createScimUser,
|
||||||
updateScimUser,
|
updateScimUser,
|
||||||
replaceScimUser,
|
replaceScimUser,
|
||||||
|
createScimGroup,
|
||||||
|
getScimGroup,
|
||||||
fnValidateScimToken
|
fnValidateScimToken
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,6 +59,16 @@ export type TReplaceScimUserDTO = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TCreateScimGroupDTO = {
|
||||||
|
displayName: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetScimGroupDTO = {
|
||||||
|
groupId: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TScimTokenJwtPayload = {
|
export type TScimTokenJwtPayload = {
|
||||||
scimTokenId: string;
|
scimTokenId: string;
|
||||||
authTokenType: string;
|
authTokenType: string;
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user