From ff7c6cec1abdd0acf79be5edeae0f6ea5ecd69c4 Mon Sep 17 00:00:00 2001 From: Mustansar Anwar ul Samad Date: Mon, 13 Sep 2021 12:51:40 +1200 Subject: [PATCH 1/4] Allow loading users using group membership The userFilter loads users by filtering on their attributes only. This allows to load users based on group membership e.g. for cases where only a certain set of users should be loaded using their group membership Signed-off-by: Mustansar Anwar ul Samad --- .changeset/chilly-pumpkins-invent.md | 5 + .../catalog-backend-module-msgraph/README.md | 3 + .../src/microsoftGraph/config.ts | 10 ++ .../src/microsoftGraph/read.test.ts | 140 ++++++++++++++++++ .../src/microsoftGraph/read.ts | 104 ++++++++++++- .../MicrosoftGraphOrgReaderProcessor.ts | 1 + 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 .changeset/chilly-pumpkins-invent.md diff --git a/.changeset/chilly-pumpkins-invent.md b/.changeset/chilly-pumpkins-invent.md new file mode 100644 index 0000000000..19e98d7efb --- /dev/null +++ b/.changeset/chilly-pumpkins-invent.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Allow loading users using group membership diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index 5dedb800f1..d534b7f167 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -56,6 +56,9 @@ catalog: # See https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties # and for the syntax https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter userFilter: accountEnabled eq true and userType eq 'member' + # Optional filter for users, use group membership to get users + # This appends the users to the list of users retrieved via userFilter + userGroupMemberFilter: "displayName eq 'Backstage Users'" # Optional filter for group, see Microsoft Graph API for the syntax # See https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties groupFilter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index db8114eb39..591df9baf7 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -51,6 +51,12 @@ export type MicrosoftGraphProviderConfig = { * E.g. "accountEnabled eq true and userType eq 'member'" */ userFilter?: string; + /** + * The filter to apply to extract users by groups memberships. + * + * E.g. "displayName eq 'Backstage Users'" + */ + userGroupMemberFilter?: string; /** * The filter to apply to extract groups. * @@ -74,6 +80,9 @@ export function readMicrosoftGraphConfig( const clientId = providerConfig.getString('clientId'); const clientSecret = providerConfig.getString('clientSecret'); const userFilter = providerConfig.getOptionalString('userFilter'); + const userGroupMemberFilter = providerConfig.getOptionalString( + 'userGroupMemberFilter', + ); const groupFilter = providerConfig.getOptionalString('groupFilter'); providers.push({ @@ -83,6 +92,7 @@ export function readMicrosoftGraphConfig( clientId, clientSecret, userFilter, + userGroupMemberFilter, groupFilter, }); } diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index 82279b76c6..ffe5146057 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -21,6 +21,7 @@ import { readMicrosoftGraphGroups, readMicrosoftGraphOrganization, readMicrosoftGraphUsers, + readMicrosoftGraphUsersInGroups, resolveRelations, } from './read'; import { getVoidLogger } from '@backstage/backend-common'; @@ -59,6 +60,7 @@ function group(data: Partial): GroupEntity { describe('read microsoft graph', () => { const client: jest.Mocked = { getUsers: jest.fn(), + getUserProfile: jest.fn(), getGroups: jest.fn(), getGroupMembers: jest.fn(), getUserPhotoWithSizeLimit: jest.fn(), @@ -158,6 +160,144 @@ describe('read microsoft graph', () => { }); }); + describe('readMicrosoftGraphUsersInGroups', () => { + it('should read users from Groups', async () => { + async function* getExampleGroups() { + yield { + id: 'groupid', + displayName: 'Group Name', + description: 'Group Description', + mail: 'group@example.com', + }; + } + + async function* getExampleGroupMembers(): AsyncIterable { + yield { + '@odata.type': '#microsoft.graph.group', + id: 'childgroupid', + }; + yield { + '@odata.type': '#microsoft.graph.user', + id: 'userid', + }; + } + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + + client.getUserProfile.mockResolvedValue({ + id: 'userid', + displayName: 'User Name', + mail: 'user.name@example.com', + }); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + const { users } = await readMicrosoftGraphUsersInGroups(client, { + userGroupMemberFilter: 'securityEnabled eq true', + logger: getVoidLogger(), + }); + + expect(users).toEqual([ + user({ + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'userid', + }, + name: 'user.name_example.com', + }, + spec: { + profile: { + displayName: 'User Name', + email: 'user.name@example.com', + picture: 'data:image/jpeg;base64,...', + }, + memberOf: [], + }, + }), + ]); + + expect(client.getGroups).toBeCalledTimes(1); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq true', + }); + expect(client.getGroupMembers).toBeCalledTimes(1); + expect(client.getGroupMembers).toBeCalledWith('groupid'); + + expect(client.getUserProfile).toBeCalledTimes(1); + expect(client.getUserProfile).toBeCalledWith('userid'); + expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1); + expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120); + }); + + it('should read users with custom transformer', async () => { + async function* getExampleGroups() { + yield { + id: 'groupid', + displayName: 'Group Name', + description: 'Group Description', + mail: 'group@example.com', + }; + } + + async function* getExampleGroupMembers(): AsyncIterable { + yield { + '@odata.type': '#microsoft.graph.group', + id: 'childgroupid', + }; + yield { + '@odata.type': '#microsoft.graph.user', + id: 'userid', + }; + } + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + + client.getUserProfile.mockResolvedValue({ + id: 'userid', + displayName: 'User Name', + mail: 'user.name@example.com', + }); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + const { users } = await readMicrosoftGraphUsersInGroups(client, { + userGroupMemberFilter: 'securityEnabled eq true', + transformer: async () => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'x' }, + spec: { memberOf: [] }, + }), + logger: getVoidLogger(), + }); + + expect(users).toEqual([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'x' }, + spec: { memberOf: [] }, + }, + ]); + + expect(client.getGroups).toBeCalledTimes(1); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq true', + }); + expect(client.getGroupMembers).toBeCalledTimes(1); + expect(client.getGroupMembers).toBeCalledWith('groupid'); + + expect(client.getUserProfile).toBeCalledTimes(1); + expect(client.getUserProfile).toBeCalledWith('userid'); + expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1); + expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120); + }); + }); + describe('readMicrosoftGraphOrganization', () => { it('should read organization', async () => { client.getOrganization.mockResolvedValue({ diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 096cf8a479..ec4eb22d1e 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -125,6 +125,95 @@ export async function readMicrosoftGraphUsers( return { users }; } +export async function readMicrosoftGraphUsersInGroups( + client: MicrosoftGraphClient, + options: { + userGroupMemberFilter?: string; + transformer?: UserTransformer; + logger: Logger; + }, +): Promise<{ + users: UserEntity[]; // With all relations empty +}> { + const users: UserEntity[] = []; + + if (!options.userGroupMemberFilter) { + return { users }; + } + + const limiter = limiterFactory(10); + + const transformer = options?.transformer ?? defaultUserTransformer; + const userGroupMemberPromises: Promise[] = []; + const userPromises: Promise[] = []; + + const groupMemberUsers: Set = new Set(); + + for await (const group of client.getGroups({ + filter: options?.userGroupMemberFilter, + })) { + // Process all groups in parallel, otherwise it can take quite some time + userGroupMemberPromises.push( + limiter(async () => { + for await (const member of client.getGroupMembers(group.id!)) { + if (!member.id) { + continue; + } + + if (member['@odata.type'] === '#microsoft.graph.user') { + if (!groupMemberUsers.has(member.id)) { + groupMemberUsers.add(member.id); + } + } + } + }), + ); + } + + // Wait for all group members + await Promise.all(userGroupMemberPromises); + + options.logger.info(`groupMemberUsers ${groupMemberUsers.size}`); + for (const userId of groupMemberUsers) { + // Process all users in parallel, otherwise it can take quite some time + userPromises.push( + limiter(async () => { + let user; + let userPhoto; + try { + user = await client.getUserProfile(userId); + } catch (e) { + options.logger.warn(`Unable to load user for ${userId}`); + } + if (user) { + try { + userPhoto = await client.getUserPhotoWithSizeLimit( + user.id!, + // We are limiting the photo size, as users with full resolution photos + // can make the Backstage API slow + 120, + ); + } catch (e) { + options.logger.warn(`Unable to load userphoto for ${userId}`); + } + + const entity = await transformer(user, userPhoto); + + if (!entity) { + return; + } + users.push(entity); + } + }), + ); + } + + // Wait for all users and photos to be downloaded + await Promise.all(userPromises); + + return { users }; +} + export async function defaultOrganizationTransformer( organization: MicrosoftGraph.Organization, ): Promise { @@ -387,6 +476,7 @@ export async function readMicrosoftGraphOrg( tenantId: string, options: { userFilter?: string; + userGroupMemberFilter?: string; groupFilter?: string; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; @@ -394,11 +484,23 @@ export async function readMicrosoftGraphOrg( logger: Logger; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { - const { users } = await readMicrosoftGraphUsers(client, { + const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups( + client, + { + userGroupMemberFilter: options.userGroupMemberFilter, + transformer: options.userTransformer, + logger: options.logger, + }, + ); + + const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, { userFilter: options.userFilter, transformer: options.userTransformer, logger: options.logger, }); + + const users: UserEntity[] = usersWithFilter.concat(usersInGroups); + const { groups, rootGroup, groupMember, groupMemberOf } = await readMicrosoftGraphGroups(client, tenantId, { groupFilter: options?.groupFilter, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index 161cc8799f..cb7729e04a 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -101,6 +101,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { provider.tenantId, { userFilter: provider.userFilter, + userGroupMemberFilter: provider.userGroupMemberFilter, groupFilter: provider.groupFilter, userTransformer: this.userTransformer, groupTransformer: this.groupTransformer, From 76599b5429be498b0687a4dc01216d130560b086 Mon Sep 17 00:00:00 2001 From: Mustansar Anwar ul Samad Date: Mon, 4 Oct 2021 17:08:44 +1300 Subject: [PATCH 2/4] Make userFilter and userGroupMemberFilter mutually exclusive Signed-off-by: Mustansar Anwar ul Samad --- .../catalog-backend-module-msgraph/README.md | 9 +- .../src/microsoftGraph/read.test.ts | 129 ++++++++++++++++++ .../src/microsoftGraph/read.ts | 41 +++--- .../MicrosoftGraphOrgReaderProcessor.ts | 6 + 4 files changed, 161 insertions(+), 24 deletions(-) diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index d534b7f167..4c41d42915 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -55,15 +55,20 @@ catalog: # Optional filter for user, see Microsoft Graph API for the syntax # See https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties # and for the syntax https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter + # This and userGroupMemberFilter are mutually exclusive, only one can be specified userFilter: accountEnabled eq true and userType eq 'member' - # Optional filter for users, use group membership to get users - # This appends the users to the list of users retrieved via userFilter + # Optional filter for users, use group membership to get users. + # This and userFilter are mutually exclusive, only one can be specified userGroupMemberFilter: "displayName eq 'Backstage Users'" # Optional filter for group, see Microsoft Graph API for the syntax # See https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties groupFilter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') ``` +`userFilter` and `userGroupMemberFilter` are mutually exclusive, only one can be provided. If both are provided, an error will be thrown. + +By default, all users are loaded. If you want to filter users based on their attributes, use `userFilter`. `userGroupMemberFilter` can be used if you want to load users based on their group membership. + 5. Add a location that ingests from Microsoft Graph: ```yaml diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index ffe5146057..fa86d77b27 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -19,6 +19,7 @@ import merge from 'lodash/merge'; import { GroupMember, MicrosoftGraphClient } from './client'; import { readMicrosoftGraphGroups, + readMicrosoftGraphOrg, readMicrosoftGraphOrganization, readMicrosoftGraphUsers, readMicrosoftGraphUsersInGroups, @@ -537,4 +538,132 @@ describe('read microsoft graph', () => { ); }); }); + + describe('readMicrosoftGraphOrg', () => { + async function* getExampleUsers() { + yield { + id: 'userid', + displayName: 'User Name', + mail: 'user.name@example.com', + }; + } + + async function* getExampleGroups() { + yield { + id: 'groupid', + displayName: 'Group Name', + description: 'Group Description', + mail: 'group@example.com', + }; + } + + async function* getExampleGroupMembers(): AsyncIterable { + yield { + '@odata.type': '#microsoft.graph.group', + id: 'childgroupid', + }; + yield { + '@odata.type': '#microsoft.graph.user', + id: 'userid', + }; + } + + it('should read all users if no filter provided', async () => { + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + + client.getUsers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + await readMicrosoftGraphOrg(client, 'tenantid', { + logger: getVoidLogger(), + groupFilter: 'securityEnabled eq false', + }); + + expect(client.getUsers).toBeCalledTimes(1); + expect(client.getUsers).toBeCalledWith({ + filter: undefined, + }); + expect(client.getGroups).toBeCalledTimes(1); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq false', + }); + }); + + it('should read users using userFilter', async () => { + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + + client.getUsers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + await readMicrosoftGraphOrg(client, 'tenantid', { + logger: getVoidLogger(), + userFilter: 'accountEnabled eq true', + groupFilter: 'securityEnabled eq false', + }); + + expect(client.getUsers).toBeCalledTimes(1); + expect(client.getUsers).toBeCalledWith({ + filter: 'accountEnabled eq true', + }); + expect(client.getGroups).toBeCalledTimes(1); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq false', + }); + }); + + it('should read users using userGroupMemberFilter', async () => { + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + + client.getUsers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + await readMicrosoftGraphOrg(client, 'tenantid', { + logger: getVoidLogger(), + userGroupMemberFilter: 'name eq backstage-group', + groupFilter: 'securityEnabled eq false', + }); + + expect(client.getUsers).toBeCalledTimes(0); + expect(client.getGroups).toBeCalledTimes(2); + expect(client.getGroups).toBeCalledWith({ + filter: 'name eq backstage-group', + }); + expect(client.getGroups).toBeCalledWith({ + filter: 'securityEnabled eq false', + }); + }); + }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index ec4eb22d1e..fa229ca956 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -137,10 +137,6 @@ export async function readMicrosoftGraphUsersInGroups( }> { const users: UserEntity[] = []; - if (!options.userGroupMemberFilter) { - return { users }; - } - const limiter = limiterFactory(10); const transformer = options?.transformer ?? defaultUserTransformer; @@ -161,9 +157,7 @@ export async function readMicrosoftGraphUsersInGroups( } if (member['@odata.type'] === '#microsoft.graph.user') { - if (!groupMemberUsers.has(member.id)) { - groupMemberUsers.add(member.id); - } + groupMemberUsers.add(member.id); } } }), @@ -484,23 +478,26 @@ export async function readMicrosoftGraphOrg( logger: Logger; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { - const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups( - client, - { - userGroupMemberFilter: options.userGroupMemberFilter, + const users: UserEntity[] = []; + + if (options.userGroupMemberFilter) { + const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups( + client, + { + userGroupMemberFilter: options.userGroupMemberFilter, + transformer: options.userTransformer, + logger: options.logger, + }, + ); + users.push(...usersInGroups); + } else { + const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, { + userFilter: options.userFilter, transformer: options.userTransformer, logger: options.logger, - }, - ); - - const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, { - userFilter: options.userFilter, - transformer: options.userTransformer, - logger: options.logger, - }); - - const users: UserEntity[] = usersWithFilter.concat(usersInGroups); - + }); + users.push(...usersWithFilter); + } const { groups, rootGroup, groupMember, groupMemberOf } = await readMicrosoftGraphGroups(client, tenantId, { groupFilter: options?.groupFilter, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index cb7729e04a..7f4e55c0cc 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -90,6 +90,12 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { ); } + if (provider.userFilter && provider.userGroupMemberFilter) { + throw new Error( + `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`, + ); + } + // Read out all of the raw data const startTimestamp = Date.now(); this.logger.info('Reading Microsoft Graph users and groups'); From 44a6a7bbf5639a44d5a912e25b5388689125be47 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 5 Oct 2021 11:23:07 +0200 Subject: [PATCH 3/4] chore: updating config schema for msgraph Signed-off-by: blam --- plugins/catalog-backend-module-msgraph/config.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index 9f6a33ee32..c7eb5ae8e4 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -73,6 +73,12 @@ export interface Config { * E.g. "securityEnabled eq false and mailEnabled eq true" */ groupFilter?: string; + /** + * The filter to apply to extract users by groups memberships. + * + * E.g. "displayName eq 'Backstage Users'" + */ + userGroupMemberFilter?: string; }>; }; }; From 9e2358a888cd782f1183073a23d6980273567ad5 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 5 Oct 2021 11:29:17 +0200 Subject: [PATCH 4/4] chore: updating api-report Signed-off-by: blam --- plugins/catalog-backend-module-msgraph/api-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 4e58b68cb7..a716d4a29e 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -143,6 +143,7 @@ export type MicrosoftGraphProviderConfig = { clientId: string; clientSecret: string; userFilter?: string; + userGroupMemberFilter?: string; groupFilter?: string; }; @@ -173,6 +174,7 @@ export function readMicrosoftGraphOrg( tenantId: string, options: { userFilter?: string; + userGroupMemberFilter?: string; groupFilter?: string; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer;