Merge pull request #9826 from Bonial-International-GmbH/PJ_msgraph_userExpand
feat(msgraph): add `userExpand` config option
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user