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:
Fredrik Adelöw
2022-02-23 17:00:41 +01:00
committed by GitHub
8 changed files with 97 additions and 7 deletions
+5
View File
@@ -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;
+12
View File
@@ -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,
@@ -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,
@@ -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,