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');