mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
feat: finished fixing scim group
This commit is contained in:
@@ -28,6 +28,23 @@ const ScimUserSchema = z.object({
|
|||||||
active: z.boolean()
|
active: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ScimGroupSchema = z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const registerScimRouter = async (server: FastifyZodProvider) => {
|
export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
@@ -249,304 +266,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Users/:orgMembershipId",
|
|
||||||
method: "PATCH",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
orgMembershipId: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
Operations: z.array(
|
|
||||||
z.union([
|
|
||||||
z.object({
|
|
||||||
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
|
||||||
path: z.string().trim()
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]),
|
|
||||||
path: z.string().trim().optional(),
|
|
||||||
value: z.any()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: ScimUserSchema
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const user = await req.server.services.scim.updateScimUser({
|
|
||||||
orgMembershipId: req.params.orgMembershipId,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
operations: req.body.Operations
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server.route({
|
|
||||||
url: "/Groups",
|
|
||||||
method: "POST",
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional() // okta-specific
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const group = await req.server.services.scim.createScimGroup({
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
...req.body
|
|
||||||
});
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Groups",
|
|
||||||
method: "GET",
|
|
||||||
schema: {
|
|
||||||
querystring: z.object({
|
|
||||||
startIndex: z.coerce.number().default(1),
|
|
||||||
count: z.coerce.number().default(20),
|
|
||||||
filter: z.string().trim().optional()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
Resources: z.array(
|
|
||||||
z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
),
|
|
||||||
itemsPerPage: z.number(),
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
startIndex: z.number(),
|
|
||||||
totalResults: z.number()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const groups = await req.server.services.scim.listScimGroups({
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
startIndex: req.query.startIndex,
|
|
||||||
filter: req.query.filter,
|
|
||||||
limit: req.query.count
|
|
||||||
});
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Groups/:groupId",
|
|
||||||
method: "GET",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
groupId: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const group = await req.server.services.scim.getScimGroup({
|
|
||||||
groupId: req.params.groupId,
|
|
||||||
orgId: req.permission.orgId
|
|
||||||
});
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Groups/:groupId",
|
|
||||||
method: "PUT",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
groupId: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const group = await req.server.services.scim.updateScimGroupNamePut({
|
|
||||||
groupId: req.params.groupId,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
...req.body
|
|
||||||
});
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Groups/:groupId",
|
|
||||||
method: "PATCH",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
groupId: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
Operations: z.array(
|
|
||||||
z.union([
|
|
||||||
z.object({
|
|
||||||
op: z.union([z.literal("replace"), z.literal("Replace")]),
|
|
||||||
value: z.object({
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim()
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
|
||||||
path: z.string().trim()
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
op: z.union([z.literal("add"), z.literal("Add")]),
|
|
||||||
path: z.string().trim(),
|
|
||||||
value: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string().trim(),
|
|
||||||
display: z.string().trim().optional()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const group = await req.server.services.scim.updateScimGroupNamePatch({
|
|
||||||
groupId: req.params.groupId,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
operations: req.body.Operations
|
|
||||||
});
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Groups/:groupId",
|
|
||||||
method: "DELETE",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
groupId: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const group = await req.server.services.scim.deleteScimGroup({
|
|
||||||
groupId: req.params.groupId,
|
|
||||||
orgId: req.permission.orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/Users/:orgMembershipId",
|
url: "/Users/:orgMembershipId",
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -558,10 +277,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
id: z.string().trim(),
|
id: z.string().trim(),
|
||||||
userName: z.string().trim(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z
|
||||||
familyName: z.string().trim(),
|
.object({
|
||||||
givenName: z.string().trim()
|
familyName: z.string().trim().optional(),
|
||||||
}),
|
givenName: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
displayName: z.string().trim(),
|
displayName: z.string().trim(),
|
||||||
active: z.boolean()
|
active: z.boolean()
|
||||||
}),
|
}),
|
||||||
@@ -602,4 +323,236 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Users/:orgMembershipId",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
orgMembershipId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
Operations: z.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
||||||
|
path: z.string().trim(),
|
||||||
|
value: z
|
||||||
|
.object({
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]),
|
||||||
|
path: z.string().trim().optional(),
|
||||||
|
value: z.any().optional()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimUserSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await req.server.services.scim.updateScimUser({
|
||||||
|
orgMembershipId: req.params.orgMembershipId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
operations: req.body.Operations
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.route({
|
||||||
|
url: "/Groups",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimGroupSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.createScimGroup({
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
startIndex: z.coerce.number().default(1),
|
||||||
|
count: z.coerce.number().default(20),
|
||||||
|
filter: z.string().trim().optional(),
|
||||||
|
excludedAttributes: z.string().trim().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
Resources: z.array(ScimGroupSchema),
|
||||||
|
itemsPerPage: z.number(),
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
startIndex: z.number(),
|
||||||
|
totalResults: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const groups = await req.server.services.scim.listScimGroups({
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
startIndex: req.query.startIndex,
|
||||||
|
filter: req.query.filter,
|
||||||
|
limit: req.query.count,
|
||||||
|
isMembersExcluded: req.query.excludedAttributes === "members"
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimGroupSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.getScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "PUT",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimGroupSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.replaceScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
Operations: z.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
||||||
|
path: z.string().trim(),
|
||||||
|
value: z
|
||||||
|
.object({
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]),
|
||||||
|
path: z.string().trim().optional(),
|
||||||
|
value: z.any()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimGroupSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
console.log(JSON.stringify(req.body, null, 4));
|
||||||
|
const group = await req.server.services.scim.updateScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
operations: req.body.Operations
|
||||||
|
});
|
||||||
|
console.log(group);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "DELETE",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.deleteScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -114,14 +114,18 @@ export const buildScimGroupList = ({
|
|||||||
export const buildScimGroup = ({
|
export const buildScimGroup = ({
|
||||||
groupId,
|
groupId,
|
||||||
name,
|
name,
|
||||||
members
|
members,
|
||||||
|
updatedAt,
|
||||||
|
createdAt
|
||||||
}: {
|
}: {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
name: string;
|
name: string;
|
||||||
members: {
|
members: {
|
||||||
value: string;
|
value: string;
|
||||||
display: string;
|
display?: string;
|
||||||
}[];
|
}[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
}): TScimGroup => {
|
}): TScimGroup => {
|
||||||
const scimGroup = {
|
const scimGroup = {
|
||||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||||
@@ -130,7 +134,8 @@ export const buildScimGroup = ({
|
|||||||
members,
|
members,
|
||||||
meta: {
|
meta: {
|
||||||
resourceType: "Group",
|
resourceType: "Group",
|
||||||
location: null
|
created: createdAt,
|
||||||
|
lastModified: updatedAt
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou
|
|||||||
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 { logger } from "@app/lib/logger";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
@@ -33,14 +32,7 @@ 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 { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import {
|
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
|
||||||
buildScimGroup,
|
|
||||||
buildScimGroupList,
|
|
||||||
buildScimUser,
|
|
||||||
buildScimUserList,
|
|
||||||
extractScimValueFromPath,
|
|
||||||
parseScimFilter
|
|
||||||
} from "./scim-fns";
|
|
||||||
import {
|
import {
|
||||||
TCreateScimGroupDTO,
|
TCreateScimGroupDTO,
|
||||||
TCreateScimTokenDTO,
|
TCreateScimTokenDTO,
|
||||||
@@ -612,7 +604,7 @@ export const scimServiceFactory = ({
|
|||||||
return {}; // intentionally return empty object upon success
|
return {}; // intentionally return empty object upon success
|
||||||
};
|
};
|
||||||
|
|
||||||
const listScimGroups = async ({ orgId, startIndex, limit, filter }: TListScimGroupsDTO) => {
|
const listScimGroups = async ({ orgId, startIndex, limit, filter, isMembersExcluded }: TListScimGroupsDTO) => {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (!plan.groups)
|
if (!plan.groups)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -645,6 +637,21 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const scimGroups: TScimGroup[] = [];
|
const scimGroups: TScimGroup[] = [];
|
||||||
|
if (isMembersExcluded) {
|
||||||
|
return buildScimGroupList({
|
||||||
|
scimGroups: groups.map((group) =>
|
||||||
|
buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: [],
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
updatedAt: group.updatedAt
|
||||||
|
})
|
||||||
|
),
|
||||||
|
startIndex,
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for await (const group of groups) {
|
for await (const group of groups) {
|
||||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||||
@@ -654,7 +661,9 @@ export const scimServiceFactory = ({
|
|||||||
members: members.map((member) => ({
|
members: members.map((member) => ({
|
||||||
value: member.orgMembershipId,
|
value: member.orgMembershipId,
|
||||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||||
}))
|
})),
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
updatedAt: group.updatedAt
|
||||||
});
|
});
|
||||||
scimGroups.push(scimGroup);
|
scimGroups.push(scimGroup);
|
||||||
}
|
}
|
||||||
@@ -738,7 +747,9 @@ export const scimServiceFactory = ({
|
|||||||
members: orgMemberships.map(({ id, firstName, lastName }) => ({
|
members: orgMemberships.map(({ id, firstName, lastName }) => ({
|
||||||
value: id,
|
value: id,
|
||||||
display: `${firstName} ${lastName}`
|
display: `${firstName} ${lastName}`
|
||||||
}))
|
})),
|
||||||
|
createdAt: newGroup.group.createdAt,
|
||||||
|
updatedAt: newGroup.group.updatedAt
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -781,31 +792,17 @@ export const scimServiceFactory = ({
|
|||||||
members: orgMemberships.map(({ id, firstName, lastName }) => ({
|
members: orgMemberships.map(({ id, firstName, lastName }) => ({
|
||||||
value: id,
|
value: id,
|
||||||
display: `${firstName} ${lastName}`
|
display: `${firstName} ${lastName}`
|
||||||
}))
|
})),
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
updatedAt: group.updatedAt
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateScimGroupNamePut = async ({ groupId, orgId, displayName, members }: TUpdateScimGroupNamePutDTO) => {
|
const $replaceGroupDAL = async (
|
||||||
const plan = await licenseService.getPlan(orgId);
|
groupId: string,
|
||||||
if (!plan.groups)
|
orgId: string,
|
||||||
throw new BadRequestError({
|
{ displayName, members = [] }: { displayName: string; members: { value: string }[] }
|
||||||
message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group."
|
) => {
|
||||||
});
|
|
||||||
|
|
||||||
const org = await orgDAL.findById(orgId);
|
|
||||||
if (!org) {
|
|
||||||
throw new ScimRequestError({
|
|
||||||
detail: "Organization Not Found",
|
|
||||||
status: 404
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!org.scimEnabled)
|
|
||||||
throw new ScimRequestError({
|
|
||||||
detail: "SCIM is disabled for the organization",
|
|
||||||
status: 403
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
const [group] = await groupDAL.update(
|
const [group] = await groupDAL.update(
|
||||||
{
|
{
|
||||||
@@ -824,74 +821,96 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (members) {
|
const orgMemberships = members.length
|
||||||
const orgMemberships = await orgMembershipDAL.find({
|
? await orgMembershipDAL.find({
|
||||||
$in: {
|
$in: {
|
||||||
id: members.map((member) => member.value)
|
id: members.map((member) => member.value)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
|
||||||
|
const userGroupMembers = await userGroupMembershipDAL.find({
|
||||||
|
groupId: group.id
|
||||||
|
});
|
||||||
|
const directMemberUserIds = userGroupMembers.filter((el) => !el.isPending).map((membership) => membership.userId);
|
||||||
|
|
||||||
|
const pendingGroupAdditionsUserIds = userGroupMembers
|
||||||
|
.filter((el) => el.isPending)
|
||||||
|
.map((pendingGroupAddition) => pendingGroupAddition.userId);
|
||||||
|
|
||||||
|
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
|
||||||
|
const allMembersUserIdsSet = new Set(allMembersUserIds);
|
||||||
|
|
||||||
|
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
|
||||||
|
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
|
||||||
|
|
||||||
|
if (toAddUserIds.length) {
|
||||||
|
await addUsersToGroupByUserIds({
|
||||||
|
group,
|
||||||
|
userIds: toAddUserIds.map((member) => member.userId as string),
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
orgDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
tx
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
|
if (toRemoveUserIds.length) {
|
||||||
|
await removeUsersFromGroupByUserIds({
|
||||||
const directMemberUserIds = (
|
group,
|
||||||
await userGroupMembershipDAL.find({
|
userIds: toRemoveUserIds,
|
||||||
groupId: group.id,
|
userDAL,
|
||||||
isPending: false
|
userGroupMembershipDAL,
|
||||||
})
|
groupProjectDAL,
|
||||||
).map((membership) => membership.userId);
|
projectKeyDAL,
|
||||||
|
tx
|
||||||
const pendingGroupAdditionsUserIds = (
|
});
|
||||||
await userGroupMembershipDAL.find({
|
|
||||||
groupId: group.id,
|
|
||||||
isPending: true
|
|
||||||
})
|
|
||||||
).map((pendingGroupAddition) => pendingGroupAddition.userId);
|
|
||||||
|
|
||||||
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
|
|
||||||
const allMembersUserIdsSet = new Set(allMembersUserIds);
|
|
||||||
|
|
||||||
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
|
|
||||||
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
|
|
||||||
|
|
||||||
if (toAddUserIds.length) {
|
|
||||||
await addUsersToGroupByUserIds({
|
|
||||||
group,
|
|
||||||
userIds: toAddUserIds.map((member) => member.userId as string),
|
|
||||||
userDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
orgDAL,
|
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
projectDAL,
|
|
||||||
projectBotDAL,
|
|
||||||
tx
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toRemoveUserIds.length) {
|
|
||||||
await removeUsersFromGroupByUserIds({
|
|
||||||
group,
|
|
||||||
userIds: toRemoveUserIds,
|
|
||||||
userDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
tx
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return updatedGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceScimGroup = async ({ groupId, orgId, displayName, members }: TUpdateScimGroupNamePutDTO) => {
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update SCIM group due to plan restriction. Upgrade plan to update SCIM group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const org = await orgDAL.findById(orgId);
|
||||||
|
if (!org) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Organization Not Found",
|
||||||
|
status: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!org.scimEnabled)
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "SCIM is disabled for the organization",
|
||||||
|
status: 403
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedGroup = await $replaceGroupDAL(groupId, orgId, { displayName, members });
|
||||||
|
|
||||||
return buildScimGroup({
|
return buildScimGroup({
|
||||||
groupId: updatedGroup.id,
|
groupId: updatedGroup.id,
|
||||||
name: updatedGroup.name,
|
name: updatedGroup.name,
|
||||||
members
|
members,
|
||||||
|
updatedAt: updatedGroup.updatedAt,
|
||||||
|
createdAt: updatedGroup.createdAt
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
|
const updateScimGroup = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (!plan.groups)
|
if (!plan.groups)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -913,7 +932,7 @@ export const scimServiceFactory = ({
|
|||||||
status: 403
|
status: 403
|
||||||
});
|
});
|
||||||
|
|
||||||
let group = await groupDAL.findOne({
|
const group = await groupDAL.findOne({
|
||||||
id: groupId,
|
id: groupId,
|
||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
@@ -925,64 +944,28 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const operation of operations) {
|
|
||||||
if (operation.op === "replace" || operation.op === "Replace") {
|
|
||||||
group = await groupDAL.updateById(group.id, {
|
|
||||||
name: operation.value.displayName
|
|
||||||
});
|
|
||||||
} else if (operation.op === "add" || operation.op === "Add") {
|
|
||||||
try {
|
|
||||||
const orgMemberships = await orgMembershipDAL.find({
|
|
||||||
$in: {
|
|
||||||
id: operation.value.map((member) => member.value)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await addUsersToGroupByUserIds({
|
|
||||||
group,
|
|
||||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
|
||||||
userDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
orgDAL,
|
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
projectDAL,
|
|
||||||
projectBotDAL
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
logger.info("Repeat SCIM user-group add operation");
|
|
||||||
}
|
|
||||||
} else if (operation.op === "remove" || operation.op === "Remove") {
|
|
||||||
const orgMembershipId = extractScimValueFromPath(operation.path);
|
|
||||||
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
|
|
||||||
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
|
|
||||||
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
|
|
||||||
await removeUsersFromGroupByUserIds({
|
|
||||||
group,
|
|
||||||
userIds: [orgMembership.userId as string],
|
|
||||||
userDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new ScimRequestError({
|
|
||||||
detail: "Invalid Operation",
|
|
||||||
status: 400
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||||
|
const scimGroup = buildScimGroup({
|
||||||
return buildScimGroup({
|
|
||||||
groupId: group.id,
|
groupId: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
members: members.map((member) => ({
|
members: members.map((member) => ({
|
||||||
|
value: member.orgMembershipId
|
||||||
|
})),
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
updatedAt: group.updatedAt
|
||||||
|
});
|
||||||
|
scimPatch(scimGroup, operations);
|
||||||
|
// remove members is a weird case not following scim convention
|
||||||
|
await $replaceGroupDAL(groupId, orgId, { displayName: scimGroup.displayName, members: scimGroup.members });
|
||||||
|
|
||||||
|
const updatedScimMembers = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||||
|
return {
|
||||||
|
...scimGroup,
|
||||||
|
members: updatedScimMembers.map((member) => ({
|
||||||
value: member.orgMembershipId,
|
value: member.orgMembershipId,
|
||||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||||
}))
|
}))
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
|
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
|
||||||
@@ -1058,8 +1041,8 @@ export const scimServiceFactory = ({
|
|||||||
createScimGroup,
|
createScimGroup,
|
||||||
getScimGroup,
|
getScimGroup,
|
||||||
deleteScimGroup,
|
deleteScimGroup,
|
||||||
updateScimGroupNamePut,
|
replaceScimGroup,
|
||||||
updateScimGroupNamePatch,
|
updateScimGroup,
|
||||||
fnValidateScimToken
|
fnValidateScimToken
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export type TListScimGroupsDTO = {
|
|||||||
filter?: string;
|
filter?: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
isMembersExcluded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TListScimGroups = {
|
export type TListScimGroups = {
|
||||||
@@ -104,31 +105,7 @@ export type TUpdateScimGroupNamePutDTO = {
|
|||||||
export type TUpdateScimGroupNamePatchDTO = {
|
export type TUpdateScimGroupNamePatchDTO = {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
operations: (TRemoveOp | TReplaceOp | TAddOp)[];
|
operations: ScimPatchOperation[];
|
||||||
};
|
|
||||||
|
|
||||||
// akhilmhdh: I know, this is done due to lack of time. Need to change later to support as normalized rather than like this
|
|
||||||
// Forgive akhil blame tony
|
|
||||||
type TReplaceOp = {
|
|
||||||
op: "replace" | "Replace";
|
|
||||||
value: {
|
|
||||||
id: string;
|
|
||||||
displayName: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type TRemoveOp = {
|
|
||||||
op: "remove" | "Remove";
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TAddOp = {
|
|
||||||
op: "add" | "Add";
|
|
||||||
path: string;
|
|
||||||
value: {
|
|
||||||
value: string;
|
|
||||||
display?: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteScimGroupDTO = {
|
export type TDeleteScimGroupDTO = {
|
||||||
@@ -174,10 +151,11 @@ export type TScimGroup = {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
members: {
|
members: {
|
||||||
value: string;
|
value: string;
|
||||||
display: string;
|
display?: string;
|
||||||
}[];
|
}[];
|
||||||
meta: {
|
meta: {
|
||||||
resourceType: string;
|
resourceType: string;
|
||||||
location: null;
|
created: Date;
|
||||||
|
lastModified: Date;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export const generateKnexQueryFromScim = (
|
|||||||
switch (scimFilterAst.op) {
|
switch (scimFilterAst.op) {
|
||||||
case "eq": {
|
case "eq": {
|
||||||
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
const attrPath = getAttributeField(scimFilterAst.attrPath);
|
||||||
console.log(attrPath, scimFilterAst.compValue);
|
|
||||||
if (attrPath) void query.where(attrPath, scimFilterAst.compValue);
|
if (attrPath) void query.where(attrPath, scimFilterAst.compValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user