Merge pull request #24524 from grantila/grantila/azure-ad-disabling-user-photos

Azure AD disabling user photos (fix) and handle huge organizations
This commit is contained in:
Fredrik Adelöw
2024-04-26 10:26:13 +02:00
committed by GitHub
8 changed files with 112 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-msgraph': patch
---
Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-msgraph': patch
---
Handle fetching huge amounts of users from Azure without crashing
+12
View File
@@ -146,6 +146,18 @@ microsoftGraphOrg:
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
### User photos
By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`:
```yaml
microsoftGraphOrg:
providerId:
user:
filter: ...
loadPhotos: false
```
## Customizing Transformation
Ingested entities can be customized by providing custom transformers.
@@ -54,6 +54,8 @@ catalog:
# 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
filter: accountEnabled eq true and userType eq 'member'
# Set to false to not load user photos.
loadPhotos: true
# See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0
select: ['id', 'displayName', 'description']
# Optional configuration block
@@ -955,6 +955,43 @@ describe('read microsoft graph', () => {
);
});
it('should ignore loading photos if loadPhotos is false', async () => {
client.getOrganization.mockResolvedValue(getExampleOrg());
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(),
loadUserPhotos: false,
});
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(0);
expect(client.getUsers).toHaveBeenCalledTimes(1);
expect(client.getUsers).toHaveBeenCalledWith(
{
top: 999,
},
undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
{
top: 999,
},
undefined,
);
});
it('should read users with userSelect', async () => {
client.getOrganization.mockResolvedValue({
id: 'tenantid',
@@ -1027,5 +1064,51 @@ describe('read microsoft graph', () => {
);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
});
it('should handle loading huge amounts of users', async () => {
client.getOrganization.mockResolvedValue(getExampleOrg());
const userCount = 200_000;
async function* getHugeAmountsOfExampleUsers() {
for (let i = 0; i < userCount; ++i) {
yield {
id: `userid-${i}`,
displayName: 'User Name',
mail: 'user.name@example.com',
};
}
}
client.getUsers.mockImplementation(getHugeAmountsOfExampleUsers);
client.getGroups.mockImplementation(getExampleGroups);
client.getGroupMembers.mockImplementation(getExampleGroupMembers);
client.getGroupPhotoWithSizeLimit.mockResolvedValue(
'data:image/jpeg;base64,...',
);
const { users } = await readMicrosoftGraphOrg(client, 'tenantid', {
logger: getVoidLogger(),
loadUserPhotos: false,
});
expect(users.length).toBe(userCount);
expect(client.getUsers).toHaveBeenCalledTimes(1);
expect(client.getUsers).toHaveBeenCalledWith(
{
top: 999,
},
undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
{
top: 999,
},
undefined,
);
});
});
});
@@ -384,7 +384,7 @@ export async function readMicrosoftGraphOrg(
logger: LoggerService;
},
): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> {
const users: UserEntity[] = [];
let users: UserEntity[] = [];
if (options.userGroupMemberFilter || options.userGroupMemberSearch) {
const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups(
@@ -401,7 +401,7 @@ export async function readMicrosoftGraphOrg(
logger: options.logger,
},
);
users.push(...usersInGroups);
users = usersInGroups;
} else {
const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, {
queryMode: options.queryMode,
@@ -412,7 +412,7 @@ export async function readMicrosoftGraphOrg(
transformer: options.userTransformer,
logger: options.logger,
});
users.push(...usersWithFilter);
users = usersWithFilter;
}
const { groups, rootGroup, groupMember, groupMemberOf } =
await readMicrosoftGraphGroups(client, tenantId, {
@@ -310,6 +310,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
userExpand: provider.userExpand,
userFilter: provider.userFilter,
userSelect: provider.userSelect,
loadUserPhotos: provider.loadUserPhotos,
userGroupMemberFilter: provider.userGroupMemberFilter,
userGroupMemberSearch: provider.userGroupMemberSearch,
groupExpand: provider.groupExpand,
@@ -112,6 +112,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {
{
userExpand: provider.userExpand,
userFilter: provider.userFilter,
loadUserPhotos: provider.loadUserPhotos,
userGroupMemberFilter: provider.userGroupMemberFilter,
userGroupMemberSearch: provider.userGroupMemberSearch,
groupExpand: provider.groupExpand,