Merge pull request #9826 from Bonial-International-GmbH/PJ_msgraph_userExpand

feat(msgraph): add `userExpand` config option
This commit is contained in:
Patrik Oldsberg
2022-03-02 19:05:01 +01:00
committed by GitHub
9 changed files with 65 additions and 19 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-msgraph': patch
---
add `userExpand` config option to allow expanding a single relationship
@@ -35,6 +35,12 @@ catalog:
# the App registration in the Microsoft Azure Portal.
clientId: ${MICROSOFT_GRAPH_CLIENT_ID}
clientSecret: ${MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN}
# Optional parameter to include the expanded resource or collection referenced
# by a single relationship (navigation property) in your results.
# Only one relationship can be expanded in a single request.
# See https://docs.microsoft.com/en-us/graph/query-parameters#expand-parameter
# Can be combined with userGroupMember[...] instead of userFilter.
userExpand: manager
# 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
@@ -78,7 +78,10 @@ export class MicrosoftGraphClient {
userId: string,
maxSize: number,
): Promise<string | undefined>;
getUserProfile(userId: string): Promise<MicrosoftGraph.User>;
getUserProfile(
userId: string,
query?: ODataQuery,
): Promise<MicrosoftGraph.User>;
getUsers(query?: ODataQuery): AsyncIterable<MicrosoftGraph.User>;
requestApi(
path: string,
@@ -158,7 +161,7 @@ export type MicrosoftGraphProviderConfig = {
clientId: string;
clientSecret: string;
userFilter?: string;
userExpand?: string[];
userExpand?: string;
userGroupMemberFilter?: string;
userGroupMemberSearch?: string;
groupFilter?: string;
@@ -172,7 +175,7 @@ export function normalizeEntityName(name: string): string;
export type ODataQuery = {
search?: string;
filter?: string;
expand?: string[];
expand?: string;
select?: string[];
};
@@ -191,7 +194,7 @@ export function readMicrosoftGraphOrg(
client: MicrosoftGraphClient,
tenantId: string,
options: {
userExpand?: string[];
userExpand?: string;
userFilter?: string;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;
@@ -85,7 +85,7 @@ describe('MicrosoftGraphClient', () => {
const response = await client.requestApi('users', {
filter: 'test eq true',
expand: ['children'],
expand: 'children',
select: ['id', 'children'],
});
@@ -38,7 +38,7 @@ export type ODataQuery = {
/**
* specifies the related resources or media streams to be included in line with retrieved resources
*/
expand?: string[];
expand?: string;
/**
* request a specific set of properties for each entity or complex type
*/
@@ -155,7 +155,7 @@ export class MicrosoftGraphClient {
$search: query?.search,
$filter: query?.filter,
$select: query?.select?.join(','),
$expand: query?.expand?.join(','),
$expand: query?.expand,
},
{
addQueryPrefix: true,
@@ -203,10 +203,14 @@ export class MicrosoftGraphClient {
*
* @public
* @param userId - The unique identifier for the `User` resource
* @param query - OData Query {@link ODataQuery}
*
*/
async getUserProfile(userId: string): Promise<MicrosoftGraph.User> {
const response = await this.requestApi(`users/${userId}`);
async getUserProfile(
userId: string,
query?: ODataQuery,
): Promise<MicrosoftGraph.User> {
const response = await this.requestApi(`users/${userId}`, query);
if (response.status !== 200) {
await this.handleError('user profile', response);
@@ -53,6 +53,7 @@ describe('readMicrosoftGraphConfig', () => {
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com/',
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
groupFilter: 'securityEnabled eq false',
},
@@ -66,6 +67,7 @@ describe('readMicrosoftGraphConfig', () => {
clientId: 'clientId',
clientSecret: 'clientSecret',
authority: 'https://login.example.com',
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
groupFilter: 'securityEnabled eq false',
},
@@ -57,7 +57,7 @@ export type MicrosoftGraphProviderConfig = {
*
* E.g. "manager"
*/
userExpand?: string[];
userExpand?: string;
/**
* The filter to apply to extract users by groups memberships.
*
@@ -106,6 +106,8 @@ export function readMicrosoftGraphConfig(
const tenantId = providerConfig.getString('tenantId');
const clientId = providerConfig.getString('clientId');
const clientSecret = providerConfig.getString('clientSecret');
const userExpand = providerConfig.getOptionalString('userExpand');
const userFilter = providerConfig.getOptionalString('userFilter');
const userGroupMemberFilter = providerConfig.getOptionalString(
'userGroupMemberFilter',
@@ -133,6 +135,7 @@ export function readMicrosoftGraphConfig(
tenantId,
clientId,
clientSecret,
userExpand,
userFilter,
userGroupMemberFilter,
userGroupMemberSearch,
@@ -118,7 +118,7 @@ describe('read microsoft graph', () => {
expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120);
});
it('should read users with custom transformer', async () => {
it('should read users with userExpand and custom transformer', async () => {
async function* getExampleUsers() {
yield {
id: 'userid',
@@ -133,6 +133,7 @@ describe('read microsoft graph', () => {
);
const { users } = await readMicrosoftGraphUsers(client, {
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
transformer: async () => ({
apiVersion: 'backstage.io/v1alpha1',
@@ -154,6 +155,7 @@ describe('read microsoft graph', () => {
expect(client.getUsers).toBeCalledTimes(1);
expect(client.getUsers).toBeCalledWith({
expand: 'manager',
filter: 'accountEnabled eq true',
});
expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1);
@@ -227,12 +229,14 @@ describe('read microsoft graph', () => {
expect(client.getGroupMembers).toBeCalledWith('groupid');
expect(client.getUserProfile).toBeCalledTimes(1);
expect(client.getUserProfile).toBeCalledWith('userid');
expect(client.getUserProfile).toBeCalledWith('userid', {
expand: undefined,
});
expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120);
});
it('should read users with custom transformer', async () => {
it('should read users with userExpand and custom transformer', async () => {
async function* getExampleGroups() {
yield {
id: 'groupid',
@@ -266,6 +270,7 @@ describe('read microsoft graph', () => {
);
const { users } = await readMicrosoftGraphUsersInGroups(client, {
userExpand: 'manager',
userGroupMemberFilter: 'securityEnabled eq true',
transformer: async () => ({
apiVersion: 'backstage.io/v1alpha1',
@@ -293,7 +298,9 @@ describe('read microsoft graph', () => {
expect(client.getGroupMembers).toBeCalledWith('groupid');
expect(client.getUserProfile).toBeCalledTimes(1);
expect(client.getUserProfile).toBeCalledWith('userid');
expect(client.getUserProfile).toBeCalledWith('userid', {
expand: 'manager',
});
expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toBeCalledWith('userid', 120);
});
@@ -634,6 +641,14 @@ describe('read microsoft graph', () => {
};
}
async function getExampleUserProfile(userId: string) {
return {
id: userId,
displayName: 'User Name',
mail: 'user.name@example.com',
};
}
async function* getExampleGroups() {
yield {
id: 'groupid',
@@ -686,7 +701,7 @@ describe('read microsoft graph', () => {
});
});
it('should read users using userFilter', async () => {
it('should read users using userExpand and userFilter', async () => {
client.getOrganization.mockResolvedValue({
id: 'tenantid',
displayName: 'Organization Name',
@@ -705,12 +720,14 @@ describe('read microsoft graph', () => {
await readMicrosoftGraphOrg(client, 'tenantid', {
logger: getVoidLogger(),
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
groupFilter: 'securityEnabled eq false',
});
expect(client.getUsers).toBeCalledTimes(1);
expect(client.getUsers).toBeCalledWith({
expand: 'manager',
filter: 'accountEnabled eq true',
});
expect(client.getGroups).toBeCalledTimes(1);
@@ -719,13 +736,14 @@ describe('read microsoft graph', () => {
});
});
it('should read users using userGroupMemberFilter', async () => {
it('should read users using userExpand and userGroupMemberFilter', async () => {
client.getOrganization.mockResolvedValue({
id: 'tenantid',
displayName: 'Organization Name',
});
client.getUsers.mockImplementation(getExampleUsers);
client.getUserProfile.mockImplementation(getExampleUserProfile);
client.getUserPhotoWithSizeLimit.mockResolvedValue(
'data:image/jpeg;base64,...',
);
@@ -750,6 +768,8 @@ describe('read microsoft graph', () => {
expect(client.getGroups).toBeCalledWith({
filter: 'securityEnabled eq false',
});
expect(client.getUserProfile).toBeCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toBeCalledTimes(1);
});
});
});
@@ -85,7 +85,7 @@ export async function readMicrosoftGraphUsers(
client: MicrosoftGraphClient,
options: {
userFilter?: string;
userExpand?: string[];
userExpand?: string;
transformer?: UserTransformer;
logger: Logger;
},
@@ -137,6 +137,7 @@ export async function readMicrosoftGraphUsers(
export async function readMicrosoftGraphUsersInGroups(
client: MicrosoftGraphClient,
options: {
userExpand?: string;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;
transformer?: UserTransformer;
@@ -186,7 +187,9 @@ export async function readMicrosoftGraphUsersInGroups(
let user;
let userPhoto;
try {
user = await client.getUserProfile(userId);
user = await client.getUserProfile(userId, {
expand: options.userExpand,
});
} catch (e) {
options.logger.warn(`Unable to load user for ${userId}`);
}
@@ -506,7 +509,7 @@ export async function readMicrosoftGraphOrg(
client: MicrosoftGraphClient,
tenantId: string,
options: {
userExpand?: string[];
userExpand?: string;
userFilter?: string;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;