Merge pull request #9721 from goenning/go/add-search-criteria-msgraph
plugin-catalog-backend-module-msgraph: add configuration to use search criteria to select groups
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
add configuration to use search criteria to select groups
|
||||
@@ -80,9 +80,16 @@ export class MicrosoftGraphClient {
|
||||
): Promise<string | undefined>;
|
||||
getUserProfile(userId: string): Promise<MicrosoftGraph.User>;
|
||||
getUsers(query?: ODataQuery): AsyncIterable<MicrosoftGraph.User>;
|
||||
requestApi(path: string, query?: ODataQuery): Promise<Response_2>;
|
||||
requestApi(
|
||||
path: string,
|
||||
query?: ODataQuery,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Response_2>;
|
||||
requestCollection<T>(path: string, query?: ODataQuery): AsyncIterable<T>;
|
||||
requestRaw(url: string): Promise<Response_2>;
|
||||
requestRaw(
|
||||
url: string,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Response_2>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -153,7 +160,9 @@ export type MicrosoftGraphProviderConfig = {
|
||||
userFilter?: string;
|
||||
userExpand?: string[];
|
||||
userGroupMemberFilter?: string;
|
||||
userGroupMemberSearch?: string;
|
||||
groupFilter?: string;
|
||||
groupSearch?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -161,6 +170,7 @@ export function normalizeEntityName(name: string): string;
|
||||
|
||||
// @public
|
||||
export type ODataQuery = {
|
||||
search?: string;
|
||||
filter?: string;
|
||||
expand?: string[];
|
||||
select?: string[];
|
||||
@@ -183,7 +193,9 @@ export function readMicrosoftGraphOrg(
|
||||
options: {
|
||||
userExpand?: string[];
|
||||
userFilter?: string;
|
||||
userGroupMemberSearch?: string;
|
||||
userGroupMemberFilter?: string;
|
||||
groupSearch?: string;
|
||||
groupFilter?: string;
|
||||
userTransformer?: UserTransformer;
|
||||
groupTransformer?: GroupTransformer;
|
||||
|
||||
@@ -73,12 +73,24 @@ export interface Config {
|
||||
* E.g. "securityEnabled eq false and mailEnabled eq true"
|
||||
*/
|
||||
groupFilter?: string;
|
||||
/**
|
||||
* The search criteria to apply to extract users by groups memberships.
|
||||
*
|
||||
* E.g. "\"displayName:-team\"" would only match groups which contain '-team'
|
||||
*/
|
||||
groupSearch?: string;
|
||||
/**
|
||||
* The filter to apply to extract users by groups memberships.
|
||||
*
|
||||
* E.g. "displayName eq 'Backstage Users'"
|
||||
*/
|
||||
userGroupMemberFilter?: string;
|
||||
/**
|
||||
* The search criteria to apply to extract groups.
|
||||
*
|
||||
* E.g. "\"displayName:-team\"" would only match groups which contain '-team'
|
||||
*/
|
||||
userGroupMemberSearch?: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,6 +27,10 @@ import { MicrosoftGraphProviderConfig } from './config';
|
||||
* @public
|
||||
*/
|
||||
export type ODataQuery = {
|
||||
/**
|
||||
* search resources within a collection matching a free-text search expression.
|
||||
*/
|
||||
search?: string;
|
||||
/**
|
||||
* filter a collection of resources
|
||||
*/
|
||||
@@ -102,7 +106,15 @@ export class MicrosoftGraphClient {
|
||||
path: string,
|
||||
query?: ODataQuery,
|
||||
): AsyncIterable<T> {
|
||||
let response = await this.requestApi(path, query);
|
||||
const headers: Record<string, string> = query?.search
|
||||
? {
|
||||
// Eventual consistency is required to use $search.
|
||||
// If a new user/group is not found, it'll eventually be imported on a subsequent read
|
||||
ConsistencyLevel: 'eventual',
|
||||
}
|
||||
: {};
|
||||
|
||||
let response = await this.requestApi(path, query, headers);
|
||||
|
||||
for (;;) {
|
||||
if (response.status !== 200) {
|
||||
@@ -121,7 +133,7 @@ export class MicrosoftGraphClient {
|
||||
return;
|
||||
}
|
||||
|
||||
response = await this.requestRaw(result['@odata.nextLink']);
|
||||
response = await this.requestRaw(result['@odata.nextLink'], headers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,10 +143,16 @@ export class MicrosoftGraphClient {
|
||||
* @public
|
||||
* @param path - Resource in Microsoft Graph
|
||||
* @param query - OData Query {@link ODataQuery}
|
||||
* @param headers - optional HTTP headers
|
||||
*/
|
||||
async requestApi(path: string, query?: ODataQuery): Promise<Response> {
|
||||
async requestApi(
|
||||
path: string,
|
||||
query?: ODataQuery,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Response> {
|
||||
const queryString = qs.stringify(
|
||||
{
|
||||
$search: query?.search,
|
||||
$filter: query?.filter,
|
||||
$select: query?.select?.join(','),
|
||||
$expand: query?.expand?.join(','),
|
||||
@@ -146,15 +164,22 @@ export class MicrosoftGraphClient {
|
||||
},
|
||||
);
|
||||
|
||||
return await this.requestRaw(`${this.baseUrl}/${path}${queryString}`);
|
||||
return await this.requestRaw(
|
||||
`${this.baseUrl}/${path}${queryString}`,
|
||||
headers,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a HTTP call to Graph API with token
|
||||
*
|
||||
* @param url - HTTP Endpoint of Graph API
|
||||
* @param headers - optional HTTP headers
|
||||
*/
|
||||
async requestRaw(url: string): Promise<Response> {
|
||||
async requestRaw(
|
||||
url: string,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<Response> {
|
||||
// Make sure that we always have a valid access token (might be cached)
|
||||
const token = await this.pca.acquireTokenByClientCredential({
|
||||
scopes: ['https://graph.microsoft.com/.default'],
|
||||
@@ -166,6 +191,7 @@ export class MicrosoftGraphClient {
|
||||
|
||||
return await fetch(url, {
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -64,12 +64,24 @@ export type MicrosoftGraphProviderConfig = {
|
||||
* E.g. "displayName eq 'Backstage Users'"
|
||||
*/
|
||||
userGroupMemberFilter?: string;
|
||||
/**
|
||||
* The search criteria to apply to extract users by groups memberships.
|
||||
*
|
||||
* E.g. "\"displayName:-team\"" would only match groups which contain '-team'
|
||||
*/
|
||||
userGroupMemberSearch?: string;
|
||||
/**
|
||||
* The filter to apply to extract groups.
|
||||
*
|
||||
* E.g. "securityEnabled eq false and mailEnabled eq true"
|
||||
*/
|
||||
groupFilter?: string;
|
||||
/**
|
||||
* The search criteria to apply to extract groups.
|
||||
*
|
||||
* E.g. "\"displayName:-team\"" would only match groups which contain '-team'
|
||||
*/
|
||||
groupSearch?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,13 +110,22 @@ export function readMicrosoftGraphConfig(
|
||||
const userGroupMemberFilter = providerConfig.getOptionalString(
|
||||
'userGroupMemberFilter',
|
||||
);
|
||||
const userGroupMemberSearch = providerConfig.getOptionalString(
|
||||
'userGroupMemberSearch',
|
||||
);
|
||||
const groupFilter = providerConfig.getOptionalString('groupFilter');
|
||||
const groupSearch = providerConfig.getOptionalString('groupSearch');
|
||||
|
||||
if (userFilter && userGroupMemberFilter) {
|
||||
throw new Error(
|
||||
`userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`,
|
||||
);
|
||||
}
|
||||
if (userFilter && userGroupMemberSearch) {
|
||||
throw new Error(
|
||||
`userGroupMemberSearch cannot be specified when userFilter is defined.`,
|
||||
);
|
||||
}
|
||||
|
||||
providers.push({
|
||||
target,
|
||||
@@ -114,7 +135,9 @@ export function readMicrosoftGraphConfig(
|
||||
clientSecret,
|
||||
userFilter,
|
||||
userGroupMemberFilter,
|
||||
userGroupMemberSearch,
|
||||
groupFilter,
|
||||
groupSearch,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export async function readMicrosoftGraphUsers(
|
||||
export async function readMicrosoftGraphUsersInGroups(
|
||||
client: MicrosoftGraphClient,
|
||||
options: {
|
||||
userGroupMemberSearch?: string;
|
||||
userGroupMemberFilter?: string;
|
||||
transformer?: UserTransformer;
|
||||
logger: Logger;
|
||||
@@ -155,6 +156,7 @@ export async function readMicrosoftGraphUsersInGroups(
|
||||
const groupMemberUsers: Set<string> = new Set();
|
||||
|
||||
for await (const group of client.getGroups({
|
||||
search: options?.userGroupMemberSearch,
|
||||
filter: options?.userGroupMemberFilter,
|
||||
})) {
|
||||
// Process all groups in parallel, otherwise it can take quite some time
|
||||
@@ -324,6 +326,7 @@ export async function readMicrosoftGraphGroups(
|
||||
client: MicrosoftGraphClient,
|
||||
tenantId: string,
|
||||
options?: {
|
||||
groupSearch?: string;
|
||||
groupFilter?: string;
|
||||
groupTransformer?: GroupTransformer;
|
||||
organizationTransformer?: OrganizationTransformer;
|
||||
@@ -351,6 +354,7 @@ export async function readMicrosoftGraphGroups(
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for await (const group of client.getGroups({
|
||||
search: options?.groupSearch,
|
||||
filter: options?.groupFilter,
|
||||
})) {
|
||||
// Process all groups in parallel, otherwise it can take quite some time
|
||||
@@ -504,7 +508,9 @@ export async function readMicrosoftGraphOrg(
|
||||
options: {
|
||||
userExpand?: string[];
|
||||
userFilter?: string;
|
||||
userGroupMemberSearch?: string;
|
||||
userGroupMemberFilter?: string;
|
||||
groupSearch?: string;
|
||||
groupFilter?: string;
|
||||
userTransformer?: UserTransformer;
|
||||
groupTransformer?: GroupTransformer;
|
||||
@@ -519,6 +525,7 @@ export async function readMicrosoftGraphOrg(
|
||||
client,
|
||||
{
|
||||
userGroupMemberFilter: options.userGroupMemberFilter,
|
||||
userGroupMemberSearch: options.userGroupMemberSearch,
|
||||
transformer: options.userTransformer,
|
||||
logger: options.logger,
|
||||
},
|
||||
@@ -535,6 +542,7 @@ export async function readMicrosoftGraphOrg(
|
||||
}
|
||||
const { groups, rootGroup, groupMember, groupMemberOf } =
|
||||
await readMicrosoftGraphGroups(client, tenantId, {
|
||||
groupSearch: options?.groupSearch,
|
||||
groupFilter: options?.groupFilter,
|
||||
groupTransformer: options?.groupTransformer,
|
||||
organizationTransformer: options?.organizationTransformer,
|
||||
|
||||
+2
@@ -123,7 +123,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
{
|
||||
userFilter: provider.userFilter,
|
||||
userGroupMemberFilter: provider.userGroupMemberFilter,
|
||||
userGroupMemberSearch: provider.userGroupMemberSearch,
|
||||
groupFilter: provider.groupFilter,
|
||||
groupSearch: provider.groupSearch,
|
||||
groupTransformer: this.options.groupTransformer,
|
||||
userTransformer: this.options.userTransformer,
|
||||
organizationTransformer: this.options.organizationTransformer,
|
||||
|
||||
+2
@@ -108,7 +108,9 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {
|
||||
userExpand: provider.userExpand,
|
||||
userFilter: provider.userFilter,
|
||||
userGroupMemberFilter: provider.userGroupMemberFilter,
|
||||
userGroupMemberSearch: provider.userGroupMemberSearch,
|
||||
groupFilter: provider.groupFilter,
|
||||
groupSearch: provider.groupSearch,
|
||||
userTransformer: this.userTransformer,
|
||||
groupTransformer: this.groupTransformer,
|
||||
organizationTransformer: this.organizationTransformer,
|
||||
|
||||
Reference in New Issue
Block a user