@backstage/plugin-catalog-backend-module-msgraph 0.2.19 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @backstage/plugin-catalog-backend-module-msgraph
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 331f258e06: **BREAKING**: `MicrosoftGraphOrgEntityProvider.fromConfig` now requires a `schedule` field in its options, which simplifies scheduling. If you want to retain the old behavior of calling its `run()` method manually, you can set the new field value to the string `'manual'`. But you may prefer to instead give it a scheduled task runner from the backend tasks package:
8
+
9
+ ```diff
10
+ // packages/backend/src/plugins/catalog.ts
11
+ +import { Duration } from 'luxon';
12
+ +import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
13
+
14
+ export default async function createPlugin(
15
+ env: PluginEnvironment,
16
+ ): Promise<Router> {
17
+ const builder = await CatalogBuilder.create(env);
18
+
19
+ + // The target parameter below needs to match one of the providers' target
20
+ + // value specified in your app-config.
21
+ + builder.addEntityProvider(
22
+ + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
23
+ + id: 'production',
24
+ + target: 'https://graph.microsoft.com/v1.0',
25
+ + logger: env.logger,
26
+ + schedule: env.scheduler.createScheduledTaskRunner({
27
+ + frequency: Duration.fromObject({ minutes: 5 }),
28
+ + timeout: Duration.fromObject({ minutes: 3 }),
29
+ + }),
30
+ + }),
31
+ + );
32
+ ```
33
+
34
+ ### Patch Changes
35
+
36
+ - 759b32b0ce: support advanced querying capabilities using the config option `queryMode`
37
+ - 89c7e47967: Minor README update
38
+ - 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional.
39
+ - f24ef7864e: Minor typo fixes
40
+ - Updated dependencies
41
+ - @backstage/plugin-catalog-backend@1.0.0
42
+ - @backstage/backend-tasks@0.2.1
43
+ - @backstage/catalog-model@1.0.0
44
+ - @backstage/config@1.0.0
45
+
3
46
  ## 0.2.19
4
47
 
5
48
  ### Patch Changes
package/README.md CHANGED
@@ -35,6 +35,11 @@ catalog:
35
35
  # the App registration in the Microsoft Azure Portal.
36
36
  clientId: ${MICROSOFT_GRAPH_CLIENT_ID}
37
37
  clientSecret: ${MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN}
38
+ # Optional mode for querying which defaults to "basic".
39
+ # By default, the Microsoft Graph API only provides the basic feature set
40
+ # for querying. Certain features are limited to advanced querying capabilities.
41
+ # (See https://docs.microsoft.com/en-us/graph/aad-advanced-queries)
42
+ queryMode: basic # basic | advanced
38
43
  # Optional parameter to include the expanded resource or collection referenced
39
44
  # by a single relationship (navigation property) in your results.
40
45
  # Only one relationship can be expanded in a single request.
@@ -87,26 +92,31 @@ yarn add @backstage/plugin-catalog-backend-module-msgraph
87
92
 
88
93
  4. The `MicrosoftGraphOrgEntityProvider` is not registered by default, so you
89
94
  have to register it in the catalog plugin. Pass the target to reference a
90
- provider from the configuration. As entity providers are not part of the
91
- entity refresh loop, you have to run them manually.
92
-
93
- ```typescript
94
- // packages/backend/src/plugins/catalog.ts
95
- const msGraphOrgEntityProvider = MicrosoftGraphOrgEntityProvider.fromConfig(
96
- env.config,
97
- {
98
- id: 'https://graph.microsoft.com/v1.0',
99
- target: 'https://graph.microsoft.com/v1.0',
100
- logger: env.logger,
101
- },
102
- );
103
- builder.addEntityProvider(msGraphOrgEntityProvider);
104
-
105
- // Trigger a read every 5 minutes
106
- useHotCleanup(
107
- module,
108
- runPeriodically(() => msGraphOrgEntityProvider.read(), 5 * 60 * 1000),
109
- );
95
+ provider from the configuration.
96
+
97
+ ```diff
98
+ // packages/backend/src/plugins/catalog.ts
99
+ +import { Duration } from 'luxon';
100
+ +import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
101
+
102
+ export default async function createPlugin(
103
+ env: PluginEnvironment,
104
+ ): Promise<Router> {
105
+ const builder = await CatalogBuilder.create(env);
106
+
107
+ + // The target parameter below needs to match one of the providers' target
108
+ + // value specified in your app-config (see above).
109
+ + builder.addEntityProvider(
110
+ + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
111
+ + id: 'production',
112
+ + target: 'https://graph.microsoft.com/v1.0',
113
+ + logger: env.logger,
114
+ + schedule: env.scheduler.createScheduledTaskRunner({
115
+ + frequency: Duration.fromObject({ minutes: 5 }),
116
+ + timeout: Duration.fromObject({ minutes: 3 }),
117
+ + }),
118
+ + }),
119
+ + );
110
120
  ```
111
121
 
112
122
  ### Using the Processor
@@ -117,8 +127,8 @@ useHotCleanup(
117
127
  ```typescript
118
128
  // packages/backend/src/plugins/catalog.ts
119
129
  builder.addProcessor(
120
- MicrosoftGraphOrgReaderProcessor.fromConfig(config, {
121
- logger,
130
+ MicrosoftGraphOrgReaderProcessor.fromConfig(env.config, {
131
+ logger: env.logger,
122
132
  }),
123
133
  );
124
134
  ```
@@ -173,8 +183,8 @@ export async function myGroupTransformer(
173
183
 
174
184
  ```ts
175
185
  builder.addProcessor(
176
- MicrosoftGraphOrgReaderProcessor.fromConfig(config, {
177
- logger,
186
+ MicrosoftGraphOrgReaderProcessor.fromConfig(env.config, {
187
+ logger: env.logger,
178
188
  groupTransformer: myGroupTransformer,
179
189
  }),
180
190
  );
package/config.d.ts CHANGED
@@ -58,7 +58,7 @@ export interface Config {
58
58
  clientSecret: string;
59
59
 
60
60
  // TODO: Consider not making these config options and pass them in the
61
- // constructor instead. They are probably not environment specifc, so
61
+ // constructor instead. They are probably not environment specific, so
62
62
  // they could also be configured "in code".
63
63
 
64
64
  /**
package/dist/index.cjs.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var catalogModel = require('@backstage/catalog-model');
6
6
  var lodash = require('lodash');
7
+ var uuid = require('uuid');
7
8
  var msal = require('@azure/msal-node');
8
9
  var fetch = require('node-fetch');
9
10
  var qs = require('qs');
@@ -30,6 +31,7 @@ function _interopNamespace(e) {
30
31
  return Object.freeze(n);
31
32
  }
32
33
 
34
+ var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
33
35
  var msal__namespace = /*#__PURE__*/_interopNamespace(msal);
34
36
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
35
37
  var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
@@ -51,8 +53,12 @@ class MicrosoftGraphClient {
51
53
  const pca = new msal__namespace.ConfidentialClientApplication(clientConfig);
52
54
  return new MicrosoftGraphClient(config.target, pca);
53
55
  }
54
- async *requestCollection(path, query) {
55
- const headers = (query == null ? void 0 : query.search) ? {
56
+ async *requestCollection(path, query, queryMode) {
57
+ const appliedQueryMode = (query == null ? void 0 : query.search) ? "advanced" : queryMode != null ? queryMode : "basic";
58
+ if (appliedQueryMode === "advanced" && ((query == null ? void 0 : query.filter) || (query == null ? void 0 : query.select))) {
59
+ query.count = true;
60
+ }
61
+ const headers = appliedQueryMode === "advanced" ? {
56
62
  ConsistencyLevel: "eventual"
57
63
  } : {};
58
64
  let response = await this.requestApi(path, query, headers);
@@ -75,7 +81,8 @@ class MicrosoftGraphClient {
75
81
  $search: query == null ? void 0 : query.search,
76
82
  $filter: query == null ? void 0 : query.filter,
77
83
  $select: (_a = query == null ? void 0 : query.select) == null ? void 0 : _a.join(","),
78
- $expand: query == null ? void 0 : query.expand
84
+ $expand: query == null ? void 0 : query.expand,
85
+ $count: query == null ? void 0 : query.count
79
86
  }, {
80
87
  addQueryPrefix: true,
81
88
  encode: false
@@ -109,8 +116,8 @@ class MicrosoftGraphClient {
109
116
  async getUserPhoto(userId, sizeId) {
110
117
  return await this.getPhoto("users", userId, sizeId);
111
118
  }
112
- async *getUsers(query) {
113
- yield* this.requestCollection(`users`, query);
119
+ async *getUsers(query, queryMode) {
120
+ yield* this.requestCollection(`users`, query, queryMode);
114
121
  }
115
122
  async getGroupPhotoWithSizeLimit(groupId, maxSize) {
116
123
  return await this.getPhotoWithSizeLimit("groups", groupId, maxSize);
@@ -118,8 +125,8 @@ class MicrosoftGraphClient {
118
125
  async getGroupPhoto(groupId, sizeId) {
119
126
  return await this.getPhoto("groups", groupId, sizeId);
120
127
  }
121
- async *getGroups(query) {
122
- yield* this.requestCollection(`groups`, query);
128
+ async *getGroups(query, queryMode) {
129
+ yield* this.requestCollection(`groups`, query, queryMode);
123
130
  }
124
131
  async *getGroupMembers(groupId) {
125
132
  yield* this.requestCollection(`groups/${groupId}/members`);
@@ -191,6 +198,10 @@ function readMicrosoftGraphConfig(config) {
191
198
  if (userFilter && userGroupMemberSearch) {
192
199
  throw new Error(`userGroupMemberSearch cannot be specified when userFilter is defined.`);
193
200
  }
201
+ const queryMode = providerConfig.getOptionalString("queryMode");
202
+ if (queryMode !== void 0 && queryMode !== "basic" && queryMode !== "advanced") {
203
+ throw new Error(`queryMode must be one of: basic, advanced`);
204
+ }
194
205
  providers.push({
195
206
  target,
196
207
  authority,
@@ -203,7 +214,8 @@ function readMicrosoftGraphConfig(config) {
203
214
  userGroupMemberSearch,
204
215
  groupExpand,
205
216
  groupFilter,
206
- groupSearch
217
+ groupSearch,
218
+ queryMode
207
219
  });
208
220
  }
209
221
  return providers;
@@ -249,12 +261,13 @@ function buildOrgHierarchy(groups) {
249
261
  function buildMemberOf(groups, users) {
250
262
  const groupsByName = new Map(groups.map((g) => [g.metadata.name, g]));
251
263
  users.forEach((user) => {
264
+ var _a;
252
265
  const transitiveMemberOf = /* @__PURE__ */ new Set();
253
266
  const todo = [
254
- ...user.spec.memberOf,
267
+ ...(_a = user.spec.memberOf) != null ? _a : [],
255
268
  ...groups.filter((g) => {
256
- var _a;
257
- return (_a = g.spec.members) == null ? void 0 : _a.includes(user.metadata.name);
269
+ var _a2;
270
+ return (_a2 = g.spec.members) == null ? void 0 : _a2.includes(user.metadata.name);
258
271
  }).map((g) => g.metadata.name)
259
272
  ];
260
273
  for (; ; ) {
@@ -305,12 +318,12 @@ async function readMicrosoftGraphUsers(client, options) {
305
318
  var _a;
306
319
  const users = [];
307
320
  const limiter = limiterFactory__default["default"](10);
308
- const transformer = (_a = options == null ? void 0 : options.transformer) != null ? _a : defaultUserTransformer;
321
+ const transformer = (_a = options.transformer) != null ? _a : defaultUserTransformer;
309
322
  const promises = [];
310
323
  for await (const user of client.getUsers({
311
324
  filter: options.userFilter,
312
325
  expand: options.userExpand
313
- })) {
326
+ }, options.queryMode)) {
314
327
  promises.push(limiter(async () => {
315
328
  let userPhoto;
316
329
  try {
@@ -340,7 +353,7 @@ async function readMicrosoftGraphUsersInGroups(client, options) {
340
353
  expand: options.groupExpand,
341
354
  search: options.userGroupMemberSearch,
342
355
  filter: options.userGroupMemberFilter
343
- })) {
356
+ }, options.queryMode)) {
344
357
  userGroupMemberPromises.push(limiter(async () => {
345
358
  for await (const member of client.getGroupMembers(group.id)) {
346
359
  if (!member.id) {
@@ -472,7 +485,7 @@ async function readMicrosoftGraphGroups(client, tenantId, options) {
472
485
  expand: options == null ? void 0 : options.groupExpand,
473
486
  search: options == null ? void 0 : options.groupSearch,
474
487
  filter: options == null ? void 0 : options.groupFilter
475
- })) {
488
+ }, options == null ? void 0 : options.queryMode)) {
476
489
  promises.push(limiter(async () => {
477
490
  const entity = await transformer(group);
478
491
  if (!entity) {
@@ -547,6 +560,9 @@ function resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf)
547
560
  retrieveItems(groupMemberOf, id).forEach((p) => {
548
561
  const parentGroup = groupMap.get(p);
549
562
  if (parentGroup) {
563
+ if (!user.spec.memberOf) {
564
+ user.spec.memberOf = [];
565
+ }
550
566
  user.spec.memberOf.push(catalogModel.stringifyEntityRef(parentGroup));
551
567
  }
552
568
  });
@@ -555,8 +571,9 @@ function resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf)
555
571
  }
556
572
  async function readMicrosoftGraphOrg(client, tenantId, options) {
557
573
  const users = [];
558
- if (options.userGroupMemberFilter) {
574
+ if (options.userGroupMemberFilter || options.userGroupMemberSearch) {
559
575
  const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups(client, {
576
+ queryMode: options.queryMode,
560
577
  userGroupMemberFilter: options.userGroupMemberFilter,
561
578
  userGroupMemberSearch: options.userGroupMemberSearch,
562
579
  transformer: options.userTransformer,
@@ -565,6 +582,7 @@ async function readMicrosoftGraphOrg(client, tenantId, options) {
565
582
  users.push(...usersInGroups);
566
583
  } else {
567
584
  const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, {
585
+ queryMode: options.queryMode,
568
586
  userFilter: options.userFilter,
569
587
  userExpand: options.userExpand,
570
588
  transformer: options.userTransformer,
@@ -573,10 +591,11 @@ async function readMicrosoftGraphOrg(client, tenantId, options) {
573
591
  users.push(...usersWithFilter);
574
592
  }
575
593
  const { groups, rootGroup, groupMember, groupMemberOf } = await readMicrosoftGraphGroups(client, tenantId, {
576
- groupSearch: options == null ? void 0 : options.groupSearch,
577
- groupFilter: options == null ? void 0 : options.groupFilter,
578
- groupTransformer: options == null ? void 0 : options.groupTransformer,
579
- organizationTransformer: options == null ? void 0 : options.organizationTransformer
594
+ queryMode: options.queryMode,
595
+ groupSearch: options.groupSearch,
596
+ groupFilter: options.groupFilter,
597
+ groupTransformer: options.groupTransformer,
598
+ organizationTransformer: options.organizationTransformer
580
599
  });
581
600
  resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);
582
601
  users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
@@ -600,17 +619,17 @@ class MicrosoftGraphOrgEntityProvider {
600
619
  constructor(options) {
601
620
  this.options = options;
602
621
  }
603
- static fromConfig(config, options) {
604
- const c = config.getOptionalConfig("catalog.processors.microsoftGraphOrg");
605
- const providers = c ? readMicrosoftGraphConfig(c) : [];
622
+ static fromConfig(configRoot, options) {
623
+ const config = configRoot.getOptionalConfig("catalog.processors.microsoftGraphOrg");
624
+ const providers = config ? readMicrosoftGraphConfig(config) : [];
606
625
  const provider = providers.find((p) => options.target.startsWith(p.target));
607
626
  if (!provider) {
608
- throw new Error(`There is no Microsoft Graph Org provider that matches ${options.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`);
627
+ throw new Error(`There is no Microsoft Graph Org provider that matches "${options.target}". Please add a configuration entry for it under "catalog.processors.microsoftGraphOrg.providers".`);
609
628
  }
610
629
  const logger = options.logger.child({
611
630
  target: options.target
612
631
  });
613
- return new MicrosoftGraphOrgEntityProvider({
632
+ const result = new MicrosoftGraphOrgEntityProvider({
614
633
  id: options.id,
615
634
  userTransformer: options.userTransformer,
616
635
  groupTransformer: options.groupTransformer,
@@ -618,19 +637,25 @@ class MicrosoftGraphOrgEntityProvider {
618
637
  logger,
619
638
  provider
620
639
  });
640
+ result.schedule(options.schedule);
641
+ return result;
621
642
  }
622
643
  getProviderName() {
623
644
  return `MicrosoftGraphOrgEntityProvider:${this.options.id}`;
624
645
  }
625
646
  async connect(connection) {
647
+ var _a;
626
648
  this.connection = connection;
649
+ await ((_a = this.scheduleFn) == null ? void 0 : _a.call(this));
627
650
  }
628
- async read() {
651
+ async read(options) {
652
+ var _a;
629
653
  if (!this.connection) {
630
654
  throw new Error("Not initialized");
631
655
  }
656
+ const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.options.logger;
632
657
  const provider = this.options.provider;
633
- const { markReadComplete } = trackProgress(this.options.logger);
658
+ const { markReadComplete } = trackProgress(logger);
634
659
  const client = MicrosoftGraphClient.create(this.options.provider);
635
660
  const { users, groups } = await readMicrosoftGraphOrg(client, provider.tenantId, {
636
661
  userFilter: provider.userFilter,
@@ -638,10 +663,11 @@ class MicrosoftGraphOrgEntityProvider {
638
663
  userGroupMemberSearch: provider.userGroupMemberSearch,
639
664
  groupFilter: provider.groupFilter,
640
665
  groupSearch: provider.groupSearch,
666
+ queryMode: provider.queryMode,
641
667
  groupTransformer: this.options.groupTransformer,
642
668
  userTransformer: this.options.userTransformer,
643
669
  organizationTransformer: this.options.organizationTransformer,
644
- logger: this.options.logger
670
+ logger
645
671
  });
646
672
  const { markCommitComplete } = markReadComplete({ users, groups });
647
673
  await this.connection.applyMutation({
@@ -653,6 +679,29 @@ class MicrosoftGraphOrgEntityProvider {
653
679
  });
654
680
  markCommitComplete();
655
681
  }
682
+ schedule(schedule) {
683
+ if (schedule === "manual") {
684
+ return;
685
+ }
686
+ this.scheduleFn = async () => {
687
+ const id = `${this.getProviderName()}:refresh`;
688
+ await schedule.run({
689
+ id,
690
+ fn: async () => {
691
+ const logger = this.options.logger.child({
692
+ class: MicrosoftGraphOrgEntityProvider.prototype.constructor.name,
693
+ taskId: id,
694
+ taskInstanceId: uuid__namespace.v4()
695
+ });
696
+ try {
697
+ await this.read({ logger });
698
+ } catch (error) {
699
+ logger.error(error);
700
+ }
701
+ }
702
+ });
703
+ };
704
+ }
656
705
  }
657
706
  function trackProgress(logger) {
658
707
  let timestamp = Date.now();
@@ -673,8 +722,8 @@ function trackProgress(logger) {
673
722
  }
674
723
  function withLocations(providerId, entity) {
675
724
  var _a, _b, _c;
676
- const uuid = ((_a = entity.metadata.annotations) == null ? void 0 : _a[MICROSOFT_GRAPH_USER_ID_ANNOTATION]) || ((_b = entity.metadata.annotations) == null ? void 0 : _b[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]) || ((_c = entity.metadata.annotations) == null ? void 0 : _c[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]) || entity.metadata.name;
677
- const location = `msgraph:${providerId}/${encodeURIComponent(uuid)}`;
725
+ const uid = ((_a = entity.metadata.annotations) == null ? void 0 : _a[MICROSOFT_GRAPH_USER_ID_ANNOTATION]) || ((_b = entity.metadata.annotations) == null ? void 0 : _b[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]) || ((_c = entity.metadata.annotations) == null ? void 0 : _c[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]) || entity.metadata.name;
726
+ const location = `msgraph:${providerId}/${encodeURIComponent(uid)}`;
678
727
  return lodash.merge({
679
728
  metadata: {
680
729
  annotations: {
@@ -722,6 +771,7 @@ class MicrosoftGraphOrgReaderProcessor {
722
771
  groupExpand: provider.groupExpand,
723
772
  groupFilter: provider.groupFilter,
724
773
  groupSearch: provider.groupSearch,
774
+ queryMode: provider.queryMode,
725
775
  userTransformer: this.userTransformer,
726
776
  groupTransformer: this.groupTransformer,
727
777
  organizationTransformer: this.organizationTransformer,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/microsoftGraph/client.ts","../src/microsoftGraph/config.ts","../src/microsoftGraph/constants.ts","../src/microsoftGraph/helper.ts","../src/microsoftGraph/org.ts","../src/microsoftGraph/read.ts","../src/processors/MicrosoftGraphOrgEntityProvider.ts","../src/processors/MicrosoftGraphOrgReaderProcessor.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as msal from '@azure/msal-node';\nimport * as MicrosoftGraph from '@microsoft/microsoft-graph-types';\nimport fetch, { Response } from 'node-fetch';\nimport qs from 'qs';\nimport { MicrosoftGraphProviderConfig } from './config';\n\n/**\n * OData (Open Data Protocol) Query\n *\n * {@link https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview}\n * @public\n */\nexport type ODataQuery = {\n /**\n * search resources within a collection matching a free-text search expression.\n */\n search?: string;\n /**\n * filter a collection of resources\n */\n filter?: string;\n /**\n * specifies the related resources or media streams to be included in line with retrieved resources\n */\n expand?: string;\n /**\n * request a specific set of properties for each entity or complex type\n */\n select?: string[];\n};\n\n/**\n * Extends the base msgraph types to include the odata type.\n *\n * @public\n */\nexport type GroupMember =\n | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' })\n | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' });\n\n/**\n * A HTTP Client that communicates with Microsoft Graph API.\n * Simplify Authentication and API calls to get `User` and `Group` from Azure Active Directory\n *\n * Uses `msal-node` for authentication\n *\n * @public\n */\nexport class MicrosoftGraphClient {\n /**\n * Factory method that instantiate `msal` client and return\n * an instance of `MicrosoftGraphClient`\n *\n * @public\n *\n * @param config - Configuration for Interacting with Graph API\n */\n static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient {\n const clientConfig: msal.Configuration = {\n auth: {\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n authority: `${config.authority}/${config.tenantId}`,\n },\n };\n const pca = new msal.ConfidentialClientApplication(clientConfig);\n return new MicrosoftGraphClient(config.target, pca);\n }\n\n /**\n * @param baseUrl - baseUrl of Graph API {@link MicrosoftGraphProviderConfig.target}\n * @param pca - instance of `msal.ConfidentialClientApplication` that is used to acquire token for Graph API calls\n *\n */\n constructor(\n private readonly baseUrl: string,\n private readonly pca: msal.ConfidentialClientApplication,\n ) {}\n\n /**\n * Get a collection of resource from Graph API and\n * return an `AsyncIterable` of that resource\n *\n * @public\n * @param path - Resource in Microsoft Graph\n * @param query - OData Query {@link ODataQuery}\n *\n */\n async *requestCollection<T>(\n path: string,\n query?: ODataQuery,\n ): AsyncIterable<T> {\n const headers: Record<string, string> = query?.search\n ? {\n // Eventual consistency is required to use $search.\n // If a new user/group is not found, it'll eventually be imported on a subsequent read\n ConsistencyLevel: 'eventual',\n }\n : {};\n\n let response = await this.requestApi(path, query, headers);\n\n for (;;) {\n if (response.status !== 200) {\n await this.handleError(path, response);\n }\n\n const result = await response.json();\n\n // Graph API return array of collections\n const elements: T[] = result.value;\n\n yield* elements;\n\n // Follow cursor to the next page if one is available\n if (!result['@odata.nextLink']) {\n return;\n }\n\n response = await this.requestRaw(result['@odata.nextLink'], headers);\n }\n }\n\n /**\n * Abstract on top of {@link MicrosoftGraphClient.requestRaw}\n *\n * @public\n * @param path - Resource in Microsoft Graph\n * @param query - OData Query {@link ODataQuery}\n * @param headers - optional HTTP headers\n */\n async requestApi(\n path: string,\n query?: ODataQuery,\n headers?: Record<string, string>,\n ): Promise<Response> {\n const queryString = qs.stringify(\n {\n $search: query?.search,\n $filter: query?.filter,\n $select: query?.select?.join(','),\n $expand: query?.expand,\n },\n {\n addQueryPrefix: true,\n // Microsoft Graph doesn't like an encoded query string\n encode: false,\n },\n );\n\n return await this.requestRaw(\n `${this.baseUrl}/${path}${queryString}`,\n headers,\n );\n }\n\n /**\n * Makes a HTTP call to Graph API with token\n *\n * @param url - HTTP Endpoint of Graph API\n * @param headers - optional HTTP headers\n */\n async requestRaw(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Response> {\n // Make sure that we always have a valid access token (might be cached)\n const token = await this.pca.acquireTokenByClientCredential({\n scopes: ['https://graph.microsoft.com/.default'],\n });\n\n if (!token) {\n throw new Error('Error while requesting token for Microsoft Graph');\n }\n\n return await fetch(url, {\n headers: {\n ...headers,\n Authorization: `Bearer ${token.accessToken}`,\n },\n });\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * from Graph API\n *\n * @public\n * @param userId - The unique identifier for the `User` resource\n * @param query - OData Query {@link ODataQuery}\n *\n */\n async getUserProfile(\n userId: string,\n query?: ODataQuery,\n ): Promise<MicrosoftGraph.User> {\n const response = await this.requestApi(`users/${userId}`, query);\n\n if (response.status !== 200) {\n await this.handleError('user profile', response);\n }\n\n return await response.json();\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * of `User` from Graph API with size limit\n *\n * @param userId - The unique identifier for the `User` resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n async getUserPhotoWithSizeLimit(\n userId: string,\n maxSize: number,\n ): Promise<string | undefined> {\n return await this.getPhotoWithSizeLimit('users', userId, maxSize);\n }\n\n async getUserPhoto(\n userId: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n return await this.getPhoto('users', userId, sizeId);\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * from Graph API and return as `AsyncIterable`\n *\n * @public\n * @param query - OData Query {@link ODataQuery}\n *\n */\n async *getUsers(query?: ODataQuery): AsyncIterable<MicrosoftGraph.User> {\n yield* this.requestCollection<MicrosoftGraph.User>(`users`, query);\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * of `Group` from Graph API with size limit\n *\n * @param groupId - The unique identifier for the `Group` resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n async getGroupPhotoWithSizeLimit(\n groupId: string,\n maxSize: number,\n ): Promise<string | undefined> {\n return await this.getPhotoWithSizeLimit('groups', groupId, maxSize);\n }\n\n async getGroupPhoto(\n groupId: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n return await this.getPhoto('groups', groupId, sizeId);\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/group | Group}\n * from Graph API and return as `AsyncIterable`\n * @public\n * @param query - OData Query {@link ODataQuery}\n *\n */\n async *getGroups(query?: ODataQuery): AsyncIterable<MicrosoftGraph.Group> {\n yield* this.requestCollection<MicrosoftGraph.Group>(`groups`, query);\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * belonging to a `Group` from Graph API and return as `AsyncIterable`\n * @public\n * @param groupId - The unique identifier for the `Group` resource\n *\n */\n async *getGroupMembers(groupId: string): AsyncIterable<GroupMember> {\n yield* this.requestCollection<GroupMember>(`groups/${groupId}/members`);\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/organization | Organization}\n * from Graph API\n * @public\n * @param tenantId - The unique identifier for the `Organization` resource\n *\n */\n async getOrganization(\n tenantId: string,\n ): Promise<MicrosoftGraph.Organization> {\n const response = await this.requestApi(`organization/${tenantId}`);\n\n if (response.status !== 200) {\n await this.handleError(`organization/${tenantId}`, response);\n }\n\n return await response.json();\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * from Graph API\n *\n * @param entityName - type of parent resource, either `User` or `Group`\n * @param id - The unique identifier for the {@link entityName | entityName} resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n private async getPhotoWithSizeLimit(\n entityName: string,\n id: string,\n maxSize: number,\n ): Promise<string | undefined> {\n const response = await this.requestApi(`${entityName}/${id}/photos`);\n\n if (response.status === 404) {\n return undefined;\n } else if (response.status !== 200) {\n await this.handleError(`${entityName} photos`, response);\n }\n\n const result = await response.json();\n const photos = result.value as MicrosoftGraph.ProfilePhoto[];\n let selectedPhoto: MicrosoftGraph.ProfilePhoto | undefined = undefined;\n\n // Find the biggest picture that is smaller than the max size\n for (const p of photos) {\n if (\n !selectedPhoto ||\n (p.height! >= selectedPhoto.height! && p.height! <= maxSize)\n ) {\n selectedPhoto = p;\n }\n }\n\n if (!selectedPhoto) {\n return undefined;\n }\n\n return await this.getPhoto(entityName, id, selectedPhoto.id!);\n }\n\n private async getPhoto(\n entityName: string,\n id: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n const path = sizeId\n ? `${entityName}/${id}/photos/${sizeId}/$value`\n : `${entityName}/${id}/photo/$value`;\n const response = await this.requestApi(path);\n\n if (response.status === 404) {\n return undefined;\n } else if (response.status !== 200) {\n await this.handleError('photo', response);\n }\n\n return `data:image/jpeg;base64,${Buffer.from(\n await response.arrayBuffer(),\n ).toString('base64')}`;\n }\n\n private async handleError(path: string, response: Response): Promise<void> {\n const result = await response.json();\n const error = result.error as MicrosoftGraph.PublicError;\n\n throw new Error(\n `Error while reading ${path} from Microsoft Graph: ${error.code} - ${error.message}`,\n );\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\n\n/**\n * The configuration parameters for a single Microsoft Graph provider.\n *\n * @public\n */\nexport type MicrosoftGraphProviderConfig = {\n /**\n * The prefix of the target that this matches on, e.g.\n * \"https://graph.microsoft.com/v1.0\", with no trailing slash.\n */\n target: string;\n /**\n * The auth authority used.\n *\n * E.g. \"https://login.microsoftonline.com\"\n */\n authority?: string;\n /**\n * The tenant whose org data we are interested in.\n */\n tenantId: string;\n /**\n * The OAuth client ID to use for authenticating requests.\n */\n clientId: string;\n /**\n * The OAuth client secret to use for authenticating requests.\n */\n clientSecret: string;\n /**\n * The filter to apply to extract users.\n *\n * E.g. \"accountEnabled eq true and userType eq 'member'\"\n */\n userFilter?: string;\n /**\n * The expand argument to apply to users.\n *\n * E.g. \"manager\"\n */\n userExpand?: string;\n /**\n * The filter to apply to extract users by groups memberships.\n *\n * E.g. \"displayName eq 'Backstage Users'\"\n */\n userGroupMemberFilter?: string;\n /**\n * The search criteria to apply to extract users by groups memberships.\n *\n * E.g. \"\\\"displayName:-team\\\"\" would only match groups which contain '-team'\n */\n userGroupMemberSearch?: string;\n /**\n * The \"expand\" argument to apply to groups.\n *\n * E.g. \"member\"\n */\n groupExpand?: string;\n /**\n * The filter to apply to extract groups.\n *\n * E.g. \"securityEnabled eq false and mailEnabled eq true\"\n */\n groupFilter?: string;\n /**\n * The search criteria to apply to extract groups.\n *\n * E.g. \"\\\"displayName:-team\\\"\" would only match groups which contain '-team'\n */\n groupSearch?: string;\n};\n\n/**\n * Parses configuration.\n *\n * @param config - The root of the msgraph config hierarchy\n *\n * @public\n */\nexport function readMicrosoftGraphConfig(\n config: Config,\n): MicrosoftGraphProviderConfig[] {\n const providers: MicrosoftGraphProviderConfig[] = [];\n const providerConfigs = config.getOptionalConfigArray('providers') ?? [];\n\n for (const providerConfig of providerConfigs) {\n const target = trimEnd(providerConfig.getString('target'), '/');\n\n const authority = providerConfig.getOptionalString('authority')\n ? trimEnd(providerConfig.getOptionalString('authority'), '/')\n : 'https://login.microsoftonline.com';\n const tenantId = providerConfig.getString('tenantId');\n const clientId = providerConfig.getString('clientId');\n const clientSecret = providerConfig.getString('clientSecret');\n\n const userExpand = providerConfig.getOptionalString('userExpand');\n const userFilter = providerConfig.getOptionalString('userFilter');\n const userGroupMemberFilter = providerConfig.getOptionalString(\n 'userGroupMemberFilter',\n );\n const userGroupMemberSearch = providerConfig.getOptionalString(\n 'userGroupMemberSearch',\n );\n const groupExpand = providerConfig.getOptionalString('groupExpand');\n const groupFilter = providerConfig.getOptionalString('groupFilter');\n const groupSearch = providerConfig.getOptionalString('groupSearch');\n\n if (userFilter && userGroupMemberFilter) {\n throw new Error(\n `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`,\n );\n }\n if (userFilter && userGroupMemberSearch) {\n throw new Error(\n `userGroupMemberSearch cannot be specified when userFilter is defined.`,\n );\n }\n\n providers.push({\n target,\n authority,\n tenantId,\n clientId,\n clientSecret,\n userExpand,\n userFilter,\n userGroupMemberFilter,\n userGroupMemberSearch,\n groupExpand,\n groupFilter,\n groupSearch,\n });\n }\n\n return providers;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The tenant id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION =\n 'graph.microsoft.com/tenant-id';\n\n/**\n * The group id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION =\n 'graph.microsoft.com/group-id';\n\n/**\n * The user id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id';\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Takes an input string and cleans it up to become suitable as an entity name.\n *\n * @public\n */\nexport function normalizeEntityName(name: string): string {\n let cleaned = name\n .trim()\n .toLocaleLowerCase()\n .replace(/[^a-zA-Z0-9_\\-\\.]/g, '_');\n\n // invalid to end with _\n while (cleaned.endsWith('_')) {\n cleaned = cleaned.substring(0, cleaned.length - 1);\n }\n\n // cleans up format for groups like 'my group (Reader)'\n while (cleaned.includes('__')) {\n // replaceAll from node.js >= 15\n cleaned = cleaned.replace('__', '_');\n }\n\n return cleaned;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GroupEntity, UserEntity } from '@backstage/catalog-model';\n\n// TODO: Copied from plugin-catalog-backend, but we could also export them from\n// there. Or move them to catalog-model.\n\nexport function buildOrgHierarchy(groups: GroupEntity[]) {\n const groupsByName = new Map(groups.map(g => [g.metadata.name, g]));\n\n //\n // Make sure that g.parent.children contain g\n //\n\n for (const group of groups) {\n const selfName = group.metadata.name;\n const parentName = group.spec.parent;\n if (parentName) {\n const parent = groupsByName.get(parentName);\n if (parent && !parent.spec.children.includes(selfName)) {\n parent.spec.children.push(selfName);\n }\n }\n }\n\n //\n // Make sure that g.children.parent is g\n //\n\n for (const group of groups) {\n const selfName = group.metadata.name;\n for (const childName of group.spec.children) {\n const child = groupsByName.get(childName);\n if (child && !child.spec.parent) {\n child.spec.parent = selfName;\n }\n }\n }\n}\n\n// Ensure that users have their transitive group memberships. Requires that\n// the groups were previously processed with buildOrgHierarchy()\nexport function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) {\n const groupsByName = new Map(groups.map(g => [g.metadata.name, g]));\n\n users.forEach(user => {\n const transitiveMemberOf = new Set<string>();\n\n const todo = [\n ...user.spec.memberOf,\n ...groups\n .filter(g => g.spec.members?.includes(user.metadata.name))\n .map(g => g.metadata.name),\n ];\n\n for (;;) {\n const current = todo.pop();\n if (!current) {\n break;\n }\n\n if (!transitiveMemberOf.has(current)) {\n transitiveMemberOf.add(current);\n const group = groupsByName.get(current);\n if (group?.spec.parent) {\n todo.push(group.spec.parent);\n }\n }\n }\n\n user.spec.memberOf = [...transitiveMemberOf];\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n GroupEntity,\n stringifyEntityRef,\n UserEntity,\n} from '@backstage/catalog-model';\nimport * as MicrosoftGraph from '@microsoft/microsoft-graph-types';\nimport limiterFactory from 'p-limit';\nimport { Logger } from 'winston';\nimport { MicrosoftGraphClient } from './client';\nimport {\n MICROSOFT_GRAPH_GROUP_ID_ANNOTATION,\n MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,\n MICROSOFT_GRAPH_USER_ID_ANNOTATION,\n} from './constants';\nimport { normalizeEntityName } from './helper';\nimport { buildMemberOf, buildOrgHierarchy } from './org';\nimport {\n GroupTransformer,\n OrganizationTransformer,\n UserTransformer,\n} from './types';\n\n/**\n * The default implementation of the transformation from a graph user entry to\n * a User entity.\n *\n * @public\n */\nexport async function defaultUserTransformer(\n user: MicrosoftGraph.User,\n userPhoto?: string,\n): Promise<UserEntity | undefined> {\n if (!user.id || !user.displayName || !user.mail) {\n return undefined;\n }\n\n const name = normalizeEntityName(user.mail);\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'User',\n metadata: {\n name,\n annotations: {\n [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: user.id!,\n },\n },\n spec: {\n profile: {\n displayName: user.displayName!,\n email: user.mail!,\n\n // TODO: Additional fields?\n // jobTitle: user.jobTitle || undefined,\n // officeLocation: user.officeLocation || undefined,\n // mobilePhone: user.mobilePhone || undefined,\n },\n memberOf: [],\n },\n };\n\n if (userPhoto) {\n entity.spec.profile!.picture = userPhoto;\n }\n\n return entity;\n}\n\nexport async function readMicrosoftGraphUsers(\n client: MicrosoftGraphClient,\n options: {\n userFilter?: string;\n userExpand?: string;\n transformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n}> {\n const users: UserEntity[] = [];\n const limiter = limiterFactory(10);\n\n const transformer = options?.transformer ?? defaultUserTransformer;\n const promises: Promise<void>[] = [];\n\n for await (const user of client.getUsers({\n filter: options.userFilter,\n expand: options.userExpand,\n })) {\n // Process all users in parallel, otherwise it can take quite some time\n promises.push(\n limiter(async () => {\n let userPhoto;\n try {\n userPhoto = await client.getUserPhotoWithSizeLimit(\n user.id!,\n // We are limiting the photo size, as users with full resolution photos\n // can make the Backstage API slow\n 120,\n );\n } catch (e) {\n options.logger.warn(`Unable to load photo for ${user.id}`);\n }\n\n const entity = await transformer(user, userPhoto);\n\n if (!entity) {\n return;\n }\n\n users.push(entity);\n }),\n );\n }\n\n // Wait for all users and photos to be downloaded\n await Promise.all(promises);\n\n return { users };\n}\n\nexport async function readMicrosoftGraphUsersInGroups(\n client: MicrosoftGraphClient,\n options: {\n userExpand?: string;\n userGroupMemberSearch?: string;\n userGroupMemberFilter?: string;\n groupExpand?: string;\n transformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n}> {\n const users: UserEntity[] = [];\n\n const limiter = limiterFactory(10);\n\n const transformer = options.transformer ?? defaultUserTransformer;\n const userGroupMemberPromises: Promise<void>[] = [];\n const userPromises: Promise<void>[] = [];\n\n const groupMemberUsers: Set<string> = new Set();\n\n for await (const group of client.getGroups({\n expand: options.groupExpand,\n search: options.userGroupMemberSearch,\n filter: options.userGroupMemberFilter,\n })) {\n // Process all groups in parallel, otherwise it can take quite some time\n userGroupMemberPromises.push(\n limiter(async () => {\n for await (const member of client.getGroupMembers(group.id!)) {\n if (!member.id) {\n continue;\n }\n\n if (member['@odata.type'] === '#microsoft.graph.user') {\n groupMemberUsers.add(member.id);\n }\n }\n }),\n );\n }\n\n // Wait for all group members\n await Promise.all(userGroupMemberPromises);\n\n options.logger.info(`groupMemberUsers ${groupMemberUsers.size}`);\n for (const userId of groupMemberUsers) {\n // Process all users in parallel, otherwise it can take quite some time\n userPromises.push(\n limiter(async () => {\n let user;\n let userPhoto;\n try {\n user = await client.getUserProfile(userId, {\n expand: options.userExpand,\n });\n } catch (e) {\n options.logger.warn(`Unable to load user for ${userId}`);\n }\n if (user) {\n try {\n userPhoto = await client.getUserPhotoWithSizeLimit(\n user.id!,\n // We are limiting the photo size, as users with full resolution photos\n // can make the Backstage API slow\n 120,\n );\n } catch (e) {\n options.logger.warn(`Unable to load userphoto for ${userId}`);\n }\n\n const entity = await transformer(user, userPhoto);\n\n if (!entity) {\n return;\n }\n users.push(entity);\n }\n }),\n );\n }\n\n // Wait for all users and photos to be downloaded\n await Promise.all(userPromises);\n\n return { users };\n}\n\n/**\n * The default implementation of the transformation from a graph organization\n * entry to a Group entity.\n *\n * @public\n */\nexport async function defaultOrganizationTransformer(\n organization: MicrosoftGraph.Organization,\n): Promise<GroupEntity | undefined> {\n if (!organization.id || !organization.displayName) {\n return undefined;\n }\n\n const name = normalizeEntityName(organization.displayName!);\n return {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'Group',\n metadata: {\n name: name,\n description: organization.displayName!,\n annotations: {\n [MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]: organization.id!,\n },\n },\n spec: {\n type: 'root',\n profile: {\n displayName: organization.displayName!,\n },\n children: [],\n },\n };\n}\n\nexport async function readMicrosoftGraphOrganization(\n client: MicrosoftGraphClient,\n tenantId: string,\n options?: { transformer?: OrganizationTransformer },\n): Promise<{\n rootGroup?: GroupEntity; // With all relations empty\n}> {\n // For now we expect a single root organization\n const organization = await client.getOrganization(tenantId);\n const transformer = options?.transformer ?? defaultOrganizationTransformer;\n const rootGroup = await transformer(organization);\n\n return { rootGroup };\n}\n\nfunction extractGroupName(group: MicrosoftGraph.Group): string {\n if (group.securityEnabled) {\n return group.displayName as string;\n }\n return (group.mailNickname || group.displayName) as string;\n}\n\n/**\n * The default implementation of the transformation from a graph group entry to\n * a Group entity.\n *\n * @public\n */\nexport async function defaultGroupTransformer(\n group: MicrosoftGraph.Group,\n groupPhoto?: string,\n): Promise<GroupEntity | undefined> {\n if (!group.id || !group.displayName) {\n return undefined;\n }\n\n const name = normalizeEntityName(extractGroupName(group));\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'Group',\n metadata: {\n name: name,\n annotations: {\n [MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]: group.id,\n },\n },\n spec: {\n type: 'team',\n profile: {},\n children: [],\n },\n };\n\n if (group.description) {\n entity.metadata.description = group.description;\n }\n if (group.displayName) {\n entity.spec.profile!.displayName = group.displayName;\n }\n if (group.mail) {\n entity.spec.profile!.email = group.mail;\n }\n if (groupPhoto) {\n entity.spec.profile!.picture = groupPhoto;\n }\n\n return entity;\n}\n\nexport async function readMicrosoftGraphGroups(\n client: MicrosoftGraphClient,\n tenantId: string,\n options?: {\n groupExpand?: string;\n groupFilter?: string;\n groupSearch?: string;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n): Promise<{\n groups: GroupEntity[]; // With all relations empty\n rootGroup: GroupEntity | undefined; // With all relations empty\n groupMember: Map<string, Set<string>>;\n groupMemberOf: Map<string, Set<string>>;\n}> {\n const groups: GroupEntity[] = [];\n const groupMember: Map<string, Set<string>> = new Map();\n const groupMemberOf: Map<string, Set<string>> = new Map();\n const limiter = limiterFactory(10);\n\n const { rootGroup } = await readMicrosoftGraphOrganization(client, tenantId, {\n transformer: options?.organizationTransformer,\n });\n if (rootGroup) {\n groupMember.set(rootGroup.metadata.name, new Set<string>());\n groups.push(rootGroup);\n }\n\n const transformer = options?.groupTransformer ?? defaultGroupTransformer;\n const promises: Promise<void>[] = [];\n\n for await (const group of client.getGroups({\n expand: options?.groupExpand,\n search: options?.groupSearch,\n filter: options?.groupFilter,\n })) {\n // Process all groups in parallel, otherwise it can take quite some time\n promises.push(\n limiter(async () => {\n // TODO: Loading groups photos doesn't work right now as Microsoft Graph\n // doesn't allows this yet: https://microsoftgraph.uservoice.com/forums/920506-microsoft-graph-feature-requests/suggestions/37884922-allow-application-to-set-or-update-a-group-s-photo\n /* const groupPhoto = await client.getGroupPhotoWithSizeLimit(\n group.id!,\n // We are limiting the photo size, as groups with full resolution photos\n // can make the Backstage API slow\n 120,\n );*/\n\n const entity = await transformer(group /* , groupPhoto*/);\n\n if (!entity) {\n return;\n }\n\n for await (const member of client.getGroupMembers(group.id!)) {\n if (!member.id) {\n continue;\n }\n\n if (member['@odata.type'] === '#microsoft.graph.user') {\n ensureItem(groupMemberOf, member.id, group.id!);\n }\n\n if (member['@odata.type'] === '#microsoft.graph.group') {\n ensureItem(groupMember, group.id!, member.id);\n }\n }\n\n groups.push(entity);\n }),\n );\n }\n\n // Wait for all group members and photos to be loaded\n await Promise.all(promises);\n\n return {\n groups,\n rootGroup,\n groupMember,\n groupMemberOf,\n };\n}\n\nexport function resolveRelations(\n rootGroup: GroupEntity | undefined,\n groups: GroupEntity[],\n users: UserEntity[],\n groupMember: Map<string, Set<string>>,\n groupMemberOf: Map<string, Set<string>>,\n) {\n // Build reference lookup tables, we reference them by the id the the graph\n const groupMap: Map<string, GroupEntity> = new Map(); // by group-id or tenant-id\n\n for (const group of groups) {\n if (group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]) {\n groupMap.set(\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION],\n group,\n );\n }\n if (group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]) {\n groupMap.set(\n group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION],\n group,\n );\n }\n }\n\n // Resolve all member relationships into the reverse direction\n const parentGroups = new Map<string, Set<string>>();\n\n groupMember.forEach((members, groupId) =>\n members.forEach(m => ensureItem(parentGroups, m, groupId)),\n );\n\n // Make sure every group (except root) has at least one parent. If the parent is missing, add the root.\n if (rootGroup) {\n const tenantId =\n rootGroup.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION];\n\n groups.forEach(group => {\n const groupId =\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION];\n\n if (!groupId) {\n return;\n }\n\n if (retrieveItems(parentGroups, groupId).size === 0) {\n ensureItem(parentGroups, groupId, tenantId);\n ensureItem(groupMember, tenantId, groupId);\n }\n });\n }\n\n groups.forEach(group => {\n const id =\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ??\n group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION];\n\n retrieveItems(groupMember, id).forEach(m => {\n const childGroup = groupMap.get(m);\n if (childGroup) {\n group.spec.children.push(stringifyEntityRef(childGroup));\n }\n });\n\n retrieveItems(parentGroups, id).forEach(p => {\n const parentGroup = groupMap.get(p);\n if (parentGroup) {\n // TODO: Only having a single parent group might not match every companies model, but fine for now.\n group.spec.parent = stringifyEntityRef(parentGroup);\n }\n });\n });\n\n // Make sure that all groups have proper parents and children\n buildOrgHierarchy(groups);\n\n // Set relations for all users\n users.forEach(user => {\n const id = user.metadata.annotations![MICROSOFT_GRAPH_USER_ID_ANNOTATION];\n\n retrieveItems(groupMemberOf, id).forEach(p => {\n const parentGroup = groupMap.get(p);\n if (parentGroup) {\n user.spec.memberOf.push(stringifyEntityRef(parentGroup));\n }\n });\n });\n\n // Make sure all transitive memberships are available\n buildMemberOf(groups, users);\n}\n\n/**\n * Reads an entire org as Group and User entities.\n *\n * @public\n */\nexport async function readMicrosoftGraphOrg(\n client: MicrosoftGraphClient,\n tenantId: string,\n options: {\n userExpand?: string;\n userFilter?: string;\n userGroupMemberSearch?: string;\n userGroupMemberFilter?: string;\n groupExpand?: string;\n groupSearch?: string;\n groupFilter?: string;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n logger: Logger;\n },\n): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> {\n const users: UserEntity[] = [];\n\n if (options.userGroupMemberFilter) {\n const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups(\n client,\n {\n userGroupMemberFilter: options.userGroupMemberFilter,\n userGroupMemberSearch: options.userGroupMemberSearch,\n transformer: options.userTransformer,\n logger: options.logger,\n },\n );\n users.push(...usersInGroups);\n } else {\n const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, {\n userFilter: options.userFilter,\n userExpand: options.userExpand,\n transformer: options.userTransformer,\n logger: options.logger,\n });\n users.push(...usersWithFilter);\n }\n const { groups, rootGroup, groupMember, groupMemberOf } =\n await readMicrosoftGraphGroups(client, tenantId, {\n groupSearch: options?.groupSearch,\n groupFilter: options?.groupFilter,\n groupTransformer: options?.groupTransformer,\n organizationTransformer: options?.organizationTransformer,\n });\n\n resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);\n users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));\n groups.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));\n\n return { users, groups };\n}\n\nfunction ensureItem(\n target: Map<string, Set<string>>,\n key: string,\n value: string,\n) {\n let set = target.get(key);\n if (!set) {\n set = new Set();\n target.set(key, set);\n }\n set!.add(value);\n}\n\nfunction retrieveItems(\n target: Map<string, Set<string>>,\n key: string,\n): Set<string> {\n return target.get(key) ?? new Set();\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n Entity,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-backend';\nimport { merge } from 'lodash';\nimport { Logger } from 'winston';\nimport {\n GroupTransformer,\n MicrosoftGraphClient,\n MicrosoftGraphProviderConfig,\n MICROSOFT_GRAPH_GROUP_ID_ANNOTATION,\n MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,\n MICROSOFT_GRAPH_USER_ID_ANNOTATION,\n OrganizationTransformer,\n readMicrosoftGraphConfig,\n readMicrosoftGraphOrg,\n UserTransformer,\n} from '../microsoftGraph';\n\n/**\n * Reads user and group entries out of Microsoft Graph, and provides them as\n * User and Group entities for the catalog.\n *\n * @public\n */\nexport class MicrosoftGraphOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n\n static fromConfig(\n config: Config,\n options: {\n id: string;\n target: string;\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n ) {\n const c = config.getOptionalConfig('catalog.processors.microsoftGraphOrg');\n const providers = c ? readMicrosoftGraphConfig(c) : [];\n const provider = providers.find(p => options.target.startsWith(p.target));\n\n if (!provider) {\n throw new Error(\n `There is no Microsoft Graph Org provider that matches ${options.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`,\n );\n }\n\n const logger = options.logger.child({\n target: options.target,\n });\n\n return new MicrosoftGraphOrgEntityProvider({\n id: options.id,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n organizationTransformer: options.organizationTransformer,\n logger,\n provider,\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: MicrosoftGraphProviderConfig;\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n ) {}\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */\n getProviderName() {\n return `MicrosoftGraphOrgEntityProvider:${this.options.id}`;\n }\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read() {\n if (!this.connection) {\n throw new Error('Not initialized');\n }\n\n const provider = this.options.provider;\n const { markReadComplete } = trackProgress(this.options.logger);\n const client = MicrosoftGraphClient.create(this.options.provider);\n\n const { users, groups } = await readMicrosoftGraphOrg(\n client,\n provider.tenantId,\n {\n userFilter: provider.userFilter,\n userGroupMemberFilter: provider.userGroupMemberFilter,\n userGroupMemberSearch: provider.userGroupMemberSearch,\n groupFilter: provider.groupFilter,\n groupSearch: provider.groupSearch,\n groupTransformer: this.options.groupTransformer,\n userTransformer: this.options.userTransformer,\n organizationTransformer: this.options.organizationTransformer,\n logger: this.options.logger,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `msgraph-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, entity),\n })),\n });\n\n markCommitComplete();\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: Logger) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading msgraph users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} msgraph users and ${read.groups.length} msgraph groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n\n// Makes sure that emitted entities have a proper location based on their uuid\nexport function withLocations(providerId: string, entity: Entity): Entity {\n const uuid =\n entity.metadata.annotations?.[MICROSOFT_GRAPH_USER_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION] ||\n entity.metadata.name;\n const location = `msgraph:${providerId}/${encodeURIComponent(uuid)}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n LocationSpec,\n processingResult,\n} from '@backstage/plugin-catalog-backend';\nimport { Logger } from 'winston';\nimport {\n GroupTransformer,\n MicrosoftGraphClient,\n MicrosoftGraphProviderConfig,\n OrganizationTransformer,\n readMicrosoftGraphConfig,\n readMicrosoftGraphOrg,\n UserTransformer,\n} from '../microsoftGraph';\n\n/**\n * Extracts teams and users out of a the Microsoft Graph API.\n *\n * @public\n */\nexport class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {\n private readonly providers: MicrosoftGraphProviderConfig[];\n private readonly logger: Logger;\n private readonly userTransformer?: UserTransformer;\n private readonly groupTransformer?: GroupTransformer;\n private readonly organizationTransformer?: OrganizationTransformer;\n\n static fromConfig(\n config: Config,\n options: {\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n ) {\n const c = config.getOptionalConfig('catalog.processors.microsoftGraphOrg');\n return new MicrosoftGraphOrgReaderProcessor({\n ...options,\n providers: c ? readMicrosoftGraphConfig(c) : [],\n });\n }\n\n constructor(options: {\n providers: MicrosoftGraphProviderConfig[];\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n }) {\n this.providers = options.providers;\n this.logger = options.logger;\n this.userTransformer = options.userTransformer;\n this.groupTransformer = options.groupTransformer;\n this.organizationTransformer = options.organizationTransformer;\n }\n getProcessorName(): string {\n return 'MicrosoftGraphOrgReaderProcessor';\n }\n\n async readLocation(\n location: LocationSpec,\n _optional: boolean,\n emit: CatalogProcessorEmit,\n ): Promise<boolean> {\n if (location.type !== 'microsoft-graph-org') {\n return false;\n }\n\n const provider = this.providers.find(p =>\n location.target.startsWith(p.target),\n );\n if (!provider) {\n throw new Error(\n `There is no Microsoft Graph Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`,\n );\n }\n\n // Read out all of the raw data\n const startTimestamp = Date.now();\n this.logger.info('Reading Microsoft Graph users and groups');\n\n // We create a client each time as we need one that matches the specific provider\n const client = MicrosoftGraphClient.create(provider);\n const { users, groups } = await readMicrosoftGraphOrg(\n client,\n provider.tenantId,\n {\n userExpand: provider.userExpand,\n userFilter: provider.userFilter,\n userGroupMemberFilter: provider.userGroupMemberFilter,\n userGroupMemberSearch: provider.userGroupMemberSearch,\n groupExpand: provider.groupExpand,\n groupFilter: provider.groupFilter,\n groupSearch: provider.groupSearch,\n userTransformer: this.userTransformer,\n groupTransformer: this.groupTransformer,\n organizationTransformer: this.organizationTransformer,\n logger: this.logger,\n },\n );\n\n const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1);\n this.logger.debug(\n `Read ${users.length} users and ${groups.length} groups from Microsoft Graph in ${duration} seconds`,\n );\n\n // Done!\n for (const group of groups) {\n emit(processingResult.entity(location, group));\n }\n for (const user of users) {\n emit(processingResult.entity(location, user));\n }\n\n return true;\n }\n}\n"],"names":["msal","qs","fetch","trimEnd","limiterFactory","stringifyEntityRef","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","processingResult"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/microsoftGraph/client.ts","../src/microsoftGraph/config.ts","../src/microsoftGraph/constants.ts","../src/microsoftGraph/helper.ts","../src/microsoftGraph/org.ts","../src/microsoftGraph/read.ts","../src/processors/MicrosoftGraphOrgEntityProvider.ts","../src/processors/MicrosoftGraphOrgReaderProcessor.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as msal from '@azure/msal-node';\nimport * as MicrosoftGraph from '@microsoft/microsoft-graph-types';\nimport fetch, { Response } from 'node-fetch';\nimport qs from 'qs';\nimport { MicrosoftGraphProviderConfig } from './config';\n\n/**\n * OData (Open Data Protocol) Query\n *\n * {@link https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview}\n * {@link https://docs.microsoft.com/en-us/graph/query-parameters}\n * @public\n */\nexport type ODataQuery = {\n /**\n * search resources within a collection matching a free-text search expression.\n */\n search?: string;\n /**\n * filter a collection of resources\n */\n filter?: string;\n /**\n * specifies the related resources or media streams to be included in line with retrieved resources\n */\n expand?: string;\n /**\n * request a specific set of properties for each entity or complex type\n */\n select?: string[];\n /**\n * Retrieves the total count of matching resources.\n */\n count?: boolean;\n};\n\n/**\n * Extends the base msgraph types to include the odata type.\n *\n * @public\n */\nexport type GroupMember =\n | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' })\n | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' });\n\n/**\n * A HTTP Client that communicates with Microsoft Graph API.\n * Simplify Authentication and API calls to get `User` and `Group` from Azure Active Directory\n *\n * Uses `msal-node` for authentication\n *\n * @public\n */\nexport class MicrosoftGraphClient {\n /**\n * Factory method that instantiate `msal` client and return\n * an instance of `MicrosoftGraphClient`\n *\n * @public\n *\n * @param config - Configuration for Interacting with Graph API\n */\n static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient {\n const clientConfig: msal.Configuration = {\n auth: {\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n authority: `${config.authority}/${config.tenantId}`,\n },\n };\n const pca = new msal.ConfidentialClientApplication(clientConfig);\n return new MicrosoftGraphClient(config.target, pca);\n }\n\n /**\n * @param baseUrl - baseUrl of Graph API {@link MicrosoftGraphProviderConfig.target}\n * @param pca - instance of `msal.ConfidentialClientApplication` that is used to acquire token for Graph API calls\n *\n */\n constructor(\n private readonly baseUrl: string,\n private readonly pca: msal.ConfidentialClientApplication,\n ) {}\n\n /**\n * Get a collection of resource from Graph API and\n * return an `AsyncIterable` of that resource\n *\n * @public\n * @param path - Resource in Microsoft Graph\n * @param query - OData Query {@link ODataQuery}\n * @param queryMode - Mode to use while querying. Some features are only available at \"advanced\".\n */\n async *requestCollection<T>(\n path: string,\n query?: ODataQuery,\n queryMode?: 'basic' | 'advanced',\n ): AsyncIterable<T> {\n // upgrade to advanced query mode transparently when \"search\" is used\n // to stay backwards compatible.\n const appliedQueryMode = query?.search ? 'advanced' : queryMode ?? 'basic';\n\n // not needed for \"search\"\n // as of https://docs.microsoft.com/en-us/graph/aad-advanced-queries?tabs=http\n // even though a few other places say the opposite\n // - https://docs.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http#request-headers\n // - https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties\n if (appliedQueryMode === 'advanced' && (query?.filter || query?.select)) {\n query.count = true;\n }\n const headers: Record<string, string> =\n appliedQueryMode === 'advanced'\n ? {\n // Eventual consistency is required for advanced querying capabilities\n // like \"$search\" or parts of \"$filter\".\n // If a new user/group is not found, it'll eventually be imported on a subsequent read\n ConsistencyLevel: 'eventual',\n }\n : {};\n\n let response = await this.requestApi(path, query, headers);\n\n for (;;) {\n if (response.status !== 200) {\n await this.handleError(path, response);\n }\n\n const result = await response.json();\n\n // Graph API return array of collections\n const elements: T[] = result.value;\n\n yield* elements;\n\n // Follow cursor to the next page if one is available\n if (!result['@odata.nextLink']) {\n return;\n }\n\n response = await this.requestRaw(result['@odata.nextLink'], headers);\n }\n }\n\n /**\n * Abstract on top of {@link MicrosoftGraphClient.requestRaw}\n *\n * @public\n * @param path - Resource in Microsoft Graph\n * @param query - OData Query {@link ODataQuery}\n * @param headers - optional HTTP headers\n */\n async requestApi(\n path: string,\n query?: ODataQuery,\n headers?: Record<string, string>,\n ): Promise<Response> {\n const queryString = qs.stringify(\n {\n $search: query?.search,\n $filter: query?.filter,\n $select: query?.select?.join(','),\n $expand: query?.expand,\n $count: query?.count,\n },\n {\n addQueryPrefix: true,\n // Microsoft Graph doesn't like an encoded query string\n encode: false,\n },\n );\n\n return await this.requestRaw(\n `${this.baseUrl}/${path}${queryString}`,\n headers,\n );\n }\n\n /**\n * Makes a HTTP call to Graph API with token\n *\n * @param url - HTTP Endpoint of Graph API\n * @param headers - optional HTTP headers\n */\n async requestRaw(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Response> {\n // Make sure that we always have a valid access token (might be cached)\n const token = await this.pca.acquireTokenByClientCredential({\n scopes: ['https://graph.microsoft.com/.default'],\n });\n\n if (!token) {\n throw new Error('Error while requesting token for Microsoft Graph');\n }\n\n return await fetch(url, {\n headers: {\n ...headers,\n Authorization: `Bearer ${token.accessToken}`,\n },\n });\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * from Graph API\n *\n * @public\n * @param userId - The unique identifier for the `User` resource\n * @param query - OData Query {@link ODataQuery}\n *\n */\n async getUserProfile(\n userId: string,\n query?: ODataQuery,\n ): Promise<MicrosoftGraph.User> {\n const response = await this.requestApi(`users/${userId}`, query);\n\n if (response.status !== 200) {\n await this.handleError('user profile', response);\n }\n\n return await response.json();\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * of `User` from Graph API with size limit\n *\n * @param userId - The unique identifier for the `User` resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n async getUserPhotoWithSizeLimit(\n userId: string,\n maxSize: number,\n ): Promise<string | undefined> {\n return await this.getPhotoWithSizeLimit('users', userId, maxSize);\n }\n\n async getUserPhoto(\n userId: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n return await this.getPhoto('users', userId, sizeId);\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * from Graph API and return as `AsyncIterable`\n *\n * @public\n * @param query - OData Query {@link ODataQuery}\n * @param queryMode - Mode to use while querying. Some features are only available at \"advanced\".\n */\n async *getUsers(\n query?: ODataQuery,\n queryMode?: 'basic' | 'advanced',\n ): AsyncIterable<MicrosoftGraph.User> {\n yield* this.requestCollection<MicrosoftGraph.User>(\n `users`,\n query,\n queryMode,\n );\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * of `Group` from Graph API with size limit\n *\n * @param groupId - The unique identifier for the `Group` resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n async getGroupPhotoWithSizeLimit(\n groupId: string,\n maxSize: number,\n ): Promise<string | undefined> {\n return await this.getPhotoWithSizeLimit('groups', groupId, maxSize);\n }\n\n async getGroupPhoto(\n groupId: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n return await this.getPhoto('groups', groupId, sizeId);\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/group | Group}\n * from Graph API and return as `AsyncIterable`\n *\n * @public\n * @param query - OData Query {@link ODataQuery}\n * @param queryMode - Mode to use while querying. Some features are only available at \"advanced\".\n */\n async *getGroups(\n query?: ODataQuery,\n queryMode?: 'basic' | 'advanced',\n ): AsyncIterable<MicrosoftGraph.Group> {\n yield* this.requestCollection<MicrosoftGraph.Group>(\n `groups`,\n query,\n queryMode,\n );\n }\n\n /**\n * Get a collection of\n * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}\n * belonging to a `Group` from Graph API and return as `AsyncIterable`\n * @public\n * @param groupId - The unique identifier for the `Group` resource\n *\n */\n async *getGroupMembers(groupId: string): AsyncIterable<GroupMember> {\n yield* this.requestCollection<GroupMember>(`groups/${groupId}/members`);\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/organization | Organization}\n * from Graph API\n * @public\n * @param tenantId - The unique identifier for the `Organization` resource\n *\n */\n async getOrganization(\n tenantId: string,\n ): Promise<MicrosoftGraph.Organization> {\n const response = await this.requestApi(`organization/${tenantId}`);\n\n if (response.status !== 200) {\n await this.handleError(`organization/${tenantId}`, response);\n }\n\n return await response.json();\n }\n\n /**\n * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}\n * from Graph API\n *\n * @param entityName - type of parent resource, either `User` or `Group`\n * @param id - The unique identifier for the {@link entityName | entityName} resource\n * @param maxSize - Maximum pixel height of the photo\n *\n */\n private async getPhotoWithSizeLimit(\n entityName: string,\n id: string,\n maxSize: number,\n ): Promise<string | undefined> {\n const response = await this.requestApi(`${entityName}/${id}/photos`);\n\n if (response.status === 404) {\n return undefined;\n } else if (response.status !== 200) {\n await this.handleError(`${entityName} photos`, response);\n }\n\n const result = await response.json();\n const photos = result.value as MicrosoftGraph.ProfilePhoto[];\n let selectedPhoto: MicrosoftGraph.ProfilePhoto | undefined = undefined;\n\n // Find the biggest picture that is smaller than the max size\n for (const p of photos) {\n if (\n !selectedPhoto ||\n (p.height! >= selectedPhoto.height! && p.height! <= maxSize)\n ) {\n selectedPhoto = p;\n }\n }\n\n if (!selectedPhoto) {\n return undefined;\n }\n\n return await this.getPhoto(entityName, id, selectedPhoto.id!);\n }\n\n private async getPhoto(\n entityName: string,\n id: string,\n sizeId?: string,\n ): Promise<string | undefined> {\n const path = sizeId\n ? `${entityName}/${id}/photos/${sizeId}/$value`\n : `${entityName}/${id}/photo/$value`;\n const response = await this.requestApi(path);\n\n if (response.status === 404) {\n return undefined;\n } else if (response.status !== 200) {\n await this.handleError('photo', response);\n }\n\n return `data:image/jpeg;base64,${Buffer.from(\n await response.arrayBuffer(),\n ).toString('base64')}`;\n }\n\n private async handleError(path: string, response: Response): Promise<void> {\n const result = await response.json();\n const error = result.error as MicrosoftGraph.PublicError;\n\n throw new Error(\n `Error while reading ${path} from Microsoft Graph: ${error.code} - ${error.message}`,\n );\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\n\n/**\n * The configuration parameters for a single Microsoft Graph provider.\n *\n * @public\n */\nexport type MicrosoftGraphProviderConfig = {\n /**\n * The prefix of the target that this matches on, e.g.\n * \"https://graph.microsoft.com/v1.0\", with no trailing slash.\n */\n target: string;\n /**\n * The auth authority used.\n *\n * E.g. \"https://login.microsoftonline.com\"\n */\n authority?: string;\n /**\n * The tenant whose org data we are interested in.\n */\n tenantId: string;\n /**\n * The OAuth client ID to use for authenticating requests.\n */\n clientId: string;\n /**\n * The OAuth client secret to use for authenticating requests.\n */\n clientSecret: string;\n /**\n * The filter to apply to extract users.\n *\n * E.g. \"accountEnabled eq true and userType eq 'member'\"\n */\n userFilter?: string;\n /**\n * The \"expand\" argument to apply to users.\n *\n * E.g. \"manager\"\n */\n userExpand?: string;\n /**\n * The filter to apply to extract users by groups memberships.\n *\n * E.g. \"displayName eq 'Backstage Users'\"\n */\n userGroupMemberFilter?: string;\n /**\n * The search criteria to apply to extract users by groups memberships.\n *\n * E.g. \"\\\"displayName:-team\\\"\" would only match groups which contain '-team'\n */\n userGroupMemberSearch?: string;\n /**\n * The \"expand\" argument to apply to groups.\n *\n * E.g. \"member\"\n */\n groupExpand?: string;\n /**\n * The filter to apply to extract groups.\n *\n * E.g. \"securityEnabled eq false and mailEnabled eq true\"\n */\n groupFilter?: string;\n /**\n * The search criteria to apply to extract groups.\n *\n * E.g. \"\\\"displayName:-team\\\"\" would only match groups which contain '-team'\n */\n groupSearch?: string;\n /**\n * By default, the Microsoft Graph API only provides the basic feature set\n * for querying. Certain features are limited to advanced query capabilities\n * (see https://docs.microsoft.com/en-us/graph/aad-advanced-queries)\n * and need to be enabled.\n *\n * Some features like `$expand` are not available for advanced queries, though.\n */\n queryMode?: 'basic' | 'advanced';\n};\n\n/**\n * Parses configuration.\n *\n * @param config - The root of the msgraph config hierarchy\n *\n * @public\n */\nexport function readMicrosoftGraphConfig(\n config: Config,\n): MicrosoftGraphProviderConfig[] {\n const providers: MicrosoftGraphProviderConfig[] = [];\n const providerConfigs = config.getOptionalConfigArray('providers') ?? [];\n\n for (const providerConfig of providerConfigs) {\n const target = trimEnd(providerConfig.getString('target'), '/');\n\n const authority = providerConfig.getOptionalString('authority')\n ? trimEnd(providerConfig.getOptionalString('authority'), '/')\n : 'https://login.microsoftonline.com';\n const tenantId = providerConfig.getString('tenantId');\n const clientId = providerConfig.getString('clientId');\n const clientSecret = providerConfig.getString('clientSecret');\n\n const userExpand = providerConfig.getOptionalString('userExpand');\n const userFilter = providerConfig.getOptionalString('userFilter');\n const userGroupMemberFilter = providerConfig.getOptionalString(\n 'userGroupMemberFilter',\n );\n const userGroupMemberSearch = providerConfig.getOptionalString(\n 'userGroupMemberSearch',\n );\n const groupExpand = providerConfig.getOptionalString('groupExpand');\n const groupFilter = providerConfig.getOptionalString('groupFilter');\n const groupSearch = providerConfig.getOptionalString('groupSearch');\n\n if (userFilter && userGroupMemberFilter) {\n throw new Error(\n `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`,\n );\n }\n if (userFilter && userGroupMemberSearch) {\n throw new Error(\n `userGroupMemberSearch cannot be specified when userFilter is defined.`,\n );\n }\n\n const queryMode = providerConfig.getOptionalString('queryMode');\n if (\n queryMode !== undefined &&\n queryMode !== 'basic' &&\n queryMode !== 'advanced'\n ) {\n throw new Error(`queryMode must be one of: basic, advanced`);\n }\n\n providers.push({\n target,\n authority,\n tenantId,\n clientId,\n clientSecret,\n userExpand,\n userFilter,\n userGroupMemberFilter,\n userGroupMemberSearch,\n groupExpand,\n groupFilter,\n groupSearch,\n queryMode,\n });\n }\n\n return providers;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The tenant id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION =\n 'graph.microsoft.com/tenant-id';\n\n/**\n * The group id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION =\n 'graph.microsoft.com/group-id';\n\n/**\n * The user id used by the Microsoft Graph API\n *\n * @public\n */\nexport const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id';\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Takes an input string and cleans it up to become suitable as an entity name.\n *\n * @public\n */\nexport function normalizeEntityName(name: string): string {\n let cleaned = name\n .trim()\n .toLocaleLowerCase()\n .replace(/[^a-zA-Z0-9_\\-\\.]/g, '_');\n\n // invalid to end with _\n while (cleaned.endsWith('_')) {\n cleaned = cleaned.substring(0, cleaned.length - 1);\n }\n\n // cleans up format for groups like 'my group (Reader)'\n while (cleaned.includes('__')) {\n // replaceAll from node.js >= 15\n cleaned = cleaned.replace('__', '_');\n }\n\n return cleaned;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GroupEntity, UserEntity } from '@backstage/catalog-model';\n\n// TODO: Copied from plugin-catalog-backend, but we could also export them from\n// there. Or move them to catalog-model.\n\nexport function buildOrgHierarchy(groups: GroupEntity[]) {\n const groupsByName = new Map(groups.map(g => [g.metadata.name, g]));\n\n //\n // Make sure that g.parent.children contain g\n //\n\n for (const group of groups) {\n const selfName = group.metadata.name;\n const parentName = group.spec.parent;\n if (parentName) {\n const parent = groupsByName.get(parentName);\n if (parent && !parent.spec.children.includes(selfName)) {\n parent.spec.children.push(selfName);\n }\n }\n }\n\n //\n // Make sure that g.children.parent is g\n //\n\n for (const group of groups) {\n const selfName = group.metadata.name;\n for (const childName of group.spec.children) {\n const child = groupsByName.get(childName);\n if (child && !child.spec.parent) {\n child.spec.parent = selfName;\n }\n }\n }\n}\n\n// Ensure that users have their transitive group memberships. Requires that\n// the groups were previously processed with buildOrgHierarchy()\nexport function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) {\n const groupsByName = new Map(groups.map(g => [g.metadata.name, g]));\n\n users.forEach(user => {\n const transitiveMemberOf = new Set<string>();\n\n const todo = [\n ...(user.spec.memberOf ?? []),\n ...groups\n .filter(g => g.spec.members?.includes(user.metadata.name))\n .map(g => g.metadata.name),\n ];\n\n for (;;) {\n const current = todo.pop();\n if (!current) {\n break;\n }\n\n if (!transitiveMemberOf.has(current)) {\n transitiveMemberOf.add(current);\n const group = groupsByName.get(current);\n if (group?.spec.parent) {\n todo.push(group.spec.parent);\n }\n }\n }\n\n user.spec.memberOf = [...transitiveMemberOf];\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n GroupEntity,\n stringifyEntityRef,\n UserEntity,\n} from '@backstage/catalog-model';\nimport * as MicrosoftGraph from '@microsoft/microsoft-graph-types';\nimport limiterFactory from 'p-limit';\nimport { Logger } from 'winston';\nimport { MicrosoftGraphClient } from './client';\nimport {\n MICROSOFT_GRAPH_GROUP_ID_ANNOTATION,\n MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,\n MICROSOFT_GRAPH_USER_ID_ANNOTATION,\n} from './constants';\nimport { normalizeEntityName } from './helper';\nimport { buildMemberOf, buildOrgHierarchy } from './org';\nimport {\n GroupTransformer,\n OrganizationTransformer,\n UserTransformer,\n} from './types';\n\n/**\n * The default implementation of the transformation from a graph user entry to\n * a User entity.\n *\n * @public\n */\nexport async function defaultUserTransformer(\n user: MicrosoftGraph.User,\n userPhoto?: string,\n): Promise<UserEntity | undefined> {\n if (!user.id || !user.displayName || !user.mail) {\n return undefined;\n }\n\n const name = normalizeEntityName(user.mail);\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'User',\n metadata: {\n name,\n annotations: {\n [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: user.id!,\n },\n },\n spec: {\n profile: {\n displayName: user.displayName!,\n email: user.mail!,\n\n // TODO: Additional fields?\n // jobTitle: user.jobTitle || undefined,\n // officeLocation: user.officeLocation || undefined,\n // mobilePhone: user.mobilePhone || undefined,\n },\n memberOf: [],\n },\n };\n\n if (userPhoto) {\n entity.spec.profile!.picture = userPhoto;\n }\n\n return entity;\n}\n\nexport async function readMicrosoftGraphUsers(\n client: MicrosoftGraphClient,\n options: {\n queryMode?: 'basic' | 'advanced';\n userFilter?: string;\n userExpand?: string;\n transformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n}> {\n const users: UserEntity[] = [];\n const limiter = limiterFactory(10);\n\n const transformer = options.transformer ?? defaultUserTransformer;\n const promises: Promise<void>[] = [];\n\n for await (const user of client.getUsers(\n {\n filter: options.userFilter,\n expand: options.userExpand,\n },\n options.queryMode,\n )) {\n // Process all users in parallel, otherwise it can take quite some time\n promises.push(\n limiter(async () => {\n let userPhoto;\n try {\n userPhoto = await client.getUserPhotoWithSizeLimit(\n user.id!,\n // We are limiting the photo size, as users with full resolution photos\n // can make the Backstage API slow\n 120,\n );\n } catch (e) {\n options.logger.warn(`Unable to load photo for ${user.id}`);\n }\n\n const entity = await transformer(user, userPhoto);\n\n if (!entity) {\n return;\n }\n\n users.push(entity);\n }),\n );\n }\n\n // Wait for all users and photos to be downloaded\n await Promise.all(promises);\n\n return { users };\n}\n\nexport async function readMicrosoftGraphUsersInGroups(\n client: MicrosoftGraphClient,\n options: {\n queryMode?: 'basic' | 'advanced';\n userExpand?: string;\n userGroupMemberSearch?: string;\n userGroupMemberFilter?: string;\n groupExpand?: string;\n transformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n}> {\n const users: UserEntity[] = [];\n\n const limiter = limiterFactory(10);\n\n const transformer = options.transformer ?? defaultUserTransformer;\n const userGroupMemberPromises: Promise<void>[] = [];\n const userPromises: Promise<void>[] = [];\n\n const groupMemberUsers: Set<string> = new Set();\n\n for await (const group of client.getGroups(\n {\n expand: options.groupExpand,\n search: options.userGroupMemberSearch,\n filter: options.userGroupMemberFilter,\n },\n options.queryMode,\n )) {\n // Process all groups in parallel, otherwise it can take quite some time\n userGroupMemberPromises.push(\n limiter(async () => {\n for await (const member of client.getGroupMembers(group.id!)) {\n if (!member.id) {\n continue;\n }\n\n if (member['@odata.type'] === '#microsoft.graph.user') {\n groupMemberUsers.add(member.id);\n }\n }\n }),\n );\n }\n\n // Wait for all group members\n await Promise.all(userGroupMemberPromises);\n\n options.logger.info(`groupMemberUsers ${groupMemberUsers.size}`);\n for (const userId of groupMemberUsers) {\n // Process all users in parallel, otherwise it can take quite some time\n userPromises.push(\n limiter(async () => {\n let user;\n let userPhoto;\n try {\n user = await client.getUserProfile(userId, {\n expand: options.userExpand,\n });\n } catch (e) {\n options.logger.warn(`Unable to load user for ${userId}`);\n }\n if (user) {\n try {\n userPhoto = await client.getUserPhotoWithSizeLimit(\n user.id!,\n // We are limiting the photo size, as users with full resolution photos\n // can make the Backstage API slow\n 120,\n );\n } catch (e) {\n options.logger.warn(`Unable to load userphoto for ${userId}`);\n }\n\n const entity = await transformer(user, userPhoto);\n\n if (!entity) {\n return;\n }\n users.push(entity);\n }\n }),\n );\n }\n\n // Wait for all users and photos to be downloaded\n await Promise.all(userPromises);\n\n return { users };\n}\n\n/**\n * The default implementation of the transformation from a graph organization\n * entry to a Group entity.\n *\n * @public\n */\nexport async function defaultOrganizationTransformer(\n organization: MicrosoftGraph.Organization,\n): Promise<GroupEntity | undefined> {\n if (!organization.id || !organization.displayName) {\n return undefined;\n }\n\n const name = normalizeEntityName(organization.displayName!);\n return {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'Group',\n metadata: {\n name: name,\n description: organization.displayName!,\n annotations: {\n [MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]: organization.id!,\n },\n },\n spec: {\n type: 'root',\n profile: {\n displayName: organization.displayName!,\n },\n children: [],\n },\n };\n}\n\nexport async function readMicrosoftGraphOrganization(\n client: MicrosoftGraphClient,\n tenantId: string,\n options?: { transformer?: OrganizationTransformer },\n): Promise<{\n rootGroup?: GroupEntity; // With all relations empty\n}> {\n // For now we expect a single root organization\n const organization = await client.getOrganization(tenantId);\n const transformer = options?.transformer ?? defaultOrganizationTransformer;\n const rootGroup = await transformer(organization);\n\n return { rootGroup };\n}\n\nfunction extractGroupName(group: MicrosoftGraph.Group): string {\n if (group.securityEnabled) {\n return group.displayName as string;\n }\n return (group.mailNickname || group.displayName) as string;\n}\n\n/**\n * The default implementation of the transformation from a graph group entry to\n * a Group entity.\n *\n * @public\n */\nexport async function defaultGroupTransformer(\n group: MicrosoftGraph.Group,\n groupPhoto?: string,\n): Promise<GroupEntity | undefined> {\n if (!group.id || !group.displayName) {\n return undefined;\n }\n\n const name = normalizeEntityName(extractGroupName(group));\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1alpha1',\n kind: 'Group',\n metadata: {\n name: name,\n annotations: {\n [MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]: group.id,\n },\n },\n spec: {\n type: 'team',\n profile: {},\n children: [],\n },\n };\n\n if (group.description) {\n entity.metadata.description = group.description;\n }\n if (group.displayName) {\n entity.spec.profile!.displayName = group.displayName;\n }\n if (group.mail) {\n entity.spec.profile!.email = group.mail;\n }\n if (groupPhoto) {\n entity.spec.profile!.picture = groupPhoto;\n }\n\n return entity;\n}\n\nexport async function readMicrosoftGraphGroups(\n client: MicrosoftGraphClient,\n tenantId: string,\n options?: {\n queryMode?: 'basic' | 'advanced';\n groupExpand?: string;\n groupFilter?: string;\n groupSearch?: string;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n): Promise<{\n groups: GroupEntity[]; // With all relations empty\n rootGroup: GroupEntity | undefined; // With all relations empty\n groupMember: Map<string, Set<string>>;\n groupMemberOf: Map<string, Set<string>>;\n}> {\n const groups: GroupEntity[] = [];\n const groupMember: Map<string, Set<string>> = new Map();\n const groupMemberOf: Map<string, Set<string>> = new Map();\n const limiter = limiterFactory(10);\n\n const { rootGroup } = await readMicrosoftGraphOrganization(client, tenantId, {\n transformer: options?.organizationTransformer,\n });\n if (rootGroup) {\n groupMember.set(rootGroup.metadata.name, new Set<string>());\n groups.push(rootGroup);\n }\n\n const transformer = options?.groupTransformer ?? defaultGroupTransformer;\n const promises: Promise<void>[] = [];\n\n for await (const group of client.getGroups(\n {\n expand: options?.groupExpand,\n search: options?.groupSearch,\n filter: options?.groupFilter,\n },\n options?.queryMode,\n )) {\n // Process all groups in parallel, otherwise it can take quite some time\n promises.push(\n limiter(async () => {\n // TODO: Loading groups photos doesn't work right now as Microsoft Graph\n // doesn't allows this yet: https://microsoftgraph.uservoice.com/forums/920506-microsoft-graph-feature-requests/suggestions/37884922-allow-application-to-set-or-update-a-group-s-photo\n /* const groupPhoto = await client.getGroupPhotoWithSizeLimit(\n group.id!,\n // We are limiting the photo size, as groups with full resolution photos\n // can make the Backstage API slow\n 120,\n );*/\n\n const entity = await transformer(group /* , groupPhoto*/);\n\n if (!entity) {\n return;\n }\n\n for await (const member of client.getGroupMembers(group.id!)) {\n if (!member.id) {\n continue;\n }\n\n if (member['@odata.type'] === '#microsoft.graph.user') {\n ensureItem(groupMemberOf, member.id, group.id!);\n }\n\n if (member['@odata.type'] === '#microsoft.graph.group') {\n ensureItem(groupMember, group.id!, member.id);\n }\n }\n\n groups.push(entity);\n }),\n );\n }\n\n // Wait for all group members and photos to be loaded\n await Promise.all(promises);\n\n return {\n groups,\n rootGroup,\n groupMember,\n groupMemberOf,\n };\n}\n\nexport function resolveRelations(\n rootGroup: GroupEntity | undefined,\n groups: GroupEntity[],\n users: UserEntity[],\n groupMember: Map<string, Set<string>>,\n groupMemberOf: Map<string, Set<string>>,\n) {\n // Build reference lookup tables, we reference them by the id the the graph\n const groupMap: Map<string, GroupEntity> = new Map(); // by group-id or tenant-id\n\n for (const group of groups) {\n if (group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION]) {\n groupMap.set(\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION],\n group,\n );\n }\n if (group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION]) {\n groupMap.set(\n group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION],\n group,\n );\n }\n }\n\n // Resolve all member relationships into the reverse direction\n const parentGroups = new Map<string, Set<string>>();\n\n groupMember.forEach((members, groupId) =>\n members.forEach(m => ensureItem(parentGroups, m, groupId)),\n );\n\n // Make sure every group (except root) has at least one parent. If the parent is missing, add the root.\n if (rootGroup) {\n const tenantId =\n rootGroup.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION];\n\n groups.forEach(group => {\n const groupId =\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION];\n\n if (!groupId) {\n return;\n }\n\n if (retrieveItems(parentGroups, groupId).size === 0) {\n ensureItem(parentGroups, groupId, tenantId);\n ensureItem(groupMember, tenantId, groupId);\n }\n });\n }\n\n groups.forEach(group => {\n const id =\n group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ??\n group.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION];\n\n retrieveItems(groupMember, id).forEach(m => {\n const childGroup = groupMap.get(m);\n if (childGroup) {\n group.spec.children.push(stringifyEntityRef(childGroup));\n }\n });\n\n retrieveItems(parentGroups, id).forEach(p => {\n const parentGroup = groupMap.get(p);\n if (parentGroup) {\n // TODO: Only having a single parent group might not match every companies model, but fine for now.\n group.spec.parent = stringifyEntityRef(parentGroup);\n }\n });\n });\n\n // Make sure that all groups have proper parents and children\n buildOrgHierarchy(groups);\n\n // Set relations for all users\n users.forEach(user => {\n const id = user.metadata.annotations![MICROSOFT_GRAPH_USER_ID_ANNOTATION];\n\n retrieveItems(groupMemberOf, id).forEach(p => {\n const parentGroup = groupMap.get(p);\n if (parentGroup) {\n if (!user.spec.memberOf) {\n user.spec.memberOf = [];\n }\n user.spec.memberOf.push(stringifyEntityRef(parentGroup));\n }\n });\n });\n\n // Make sure all transitive memberships are available\n buildMemberOf(groups, users);\n}\n\n/**\n * Reads an entire org as Group and User entities.\n *\n * @public\n */\nexport async function readMicrosoftGraphOrg(\n client: MicrosoftGraphClient,\n tenantId: string,\n options: {\n userExpand?: string;\n userFilter?: string;\n userGroupMemberSearch?: string;\n userGroupMemberFilter?: string;\n groupExpand?: string;\n groupSearch?: string;\n groupFilter?: string;\n queryMode?: 'basic' | 'advanced';\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n logger: Logger;\n },\n): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> {\n const users: UserEntity[] = [];\n\n if (options.userGroupMemberFilter || options.userGroupMemberSearch) {\n const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups(\n client,\n {\n queryMode: options.queryMode,\n userGroupMemberFilter: options.userGroupMemberFilter,\n userGroupMemberSearch: options.userGroupMemberSearch,\n transformer: options.userTransformer,\n logger: options.logger,\n },\n );\n users.push(...usersInGroups);\n } else {\n const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, {\n queryMode: options.queryMode,\n userFilter: options.userFilter,\n userExpand: options.userExpand,\n transformer: options.userTransformer,\n logger: options.logger,\n });\n users.push(...usersWithFilter);\n }\n const { groups, rootGroup, groupMember, groupMemberOf } =\n await readMicrosoftGraphGroups(client, tenantId, {\n queryMode: options.queryMode,\n groupSearch: options.groupSearch,\n groupFilter: options.groupFilter,\n groupTransformer: options.groupTransformer,\n organizationTransformer: options.organizationTransformer,\n });\n\n resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);\n users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));\n groups.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));\n\n return { users, groups };\n}\n\nfunction ensureItem(\n target: Map<string, Set<string>>,\n key: string,\n value: string,\n) {\n let set = target.get(key);\n if (!set) {\n set = new Set();\n target.set(key, set);\n }\n set!.add(value);\n}\n\nfunction retrieveItems(\n target: Map<string, Set<string>>,\n key: string,\n): Set<string> {\n return target.get(key) ?? new Set();\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TaskRunner } from '@backstage/backend-tasks';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n Entity,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-backend';\nimport { merge } from 'lodash';\nimport * as uuid from 'uuid';\nimport { Logger } from 'winston';\nimport {\n GroupTransformer,\n MicrosoftGraphClient,\n MicrosoftGraphProviderConfig,\n MICROSOFT_GRAPH_GROUP_ID_ANNOTATION,\n MICROSOFT_GRAPH_TENANT_ID_ANNOTATION,\n MICROSOFT_GRAPH_USER_ID_ANNOTATION,\n OrganizationTransformer,\n readMicrosoftGraphConfig,\n readMicrosoftGraphOrg,\n UserTransformer,\n} from '../microsoftGraph';\n\n/**\n * Options for {@link MicrosoftGraphOrgEntityProvider}.\n *\n * @public\n */\nexport interface MicrosoftGraphOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The target that this provider should consume.\n *\n * Should exactly match the \"target\" field of one of the providers\n * configuration entries.\n */\n target: string;\n\n /**\n * The logger to use.\n */\n logger: Logger;\n\n /**\n * The refresh schedule to use.\n *\n * @remarks\n *\n * If you pass in 'manual', you are responsible for calling the `read` method\n * manually at some interval.\n *\n * But more commonly you will pass in the result of\n * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule: 'manual' | TaskRunner;\n\n /**\n * The function that transforms a user entry in msgraph to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in msgraph to an entity.\n */\n groupTransformer?: GroupTransformer;\n\n /**\n * The function that transforms an organization entry in msgraph to an entity.\n */\n organizationTransformer?: OrganizationTransformer;\n}\n\n/**\n * Reads user and group entries out of Microsoft Graph, and provides them as\n * User and Group entities for the catalog.\n *\n * @public\n */\nexport class MicrosoftGraphOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private scheduleFn?: () => Promise<void>;\n\n static fromConfig(\n configRoot: Config,\n options: MicrosoftGraphOrgEntityProviderOptions,\n ) {\n const config = configRoot.getOptionalConfig(\n 'catalog.processors.microsoftGraphOrg',\n );\n const providers = config ? readMicrosoftGraphConfig(config) : [];\n const provider = providers.find(p => options.target.startsWith(p.target));\n\n if (!provider) {\n throw new Error(\n `There is no Microsoft Graph Org provider that matches \"${options.target}\". Please add a configuration entry for it under \"catalog.processors.microsoftGraphOrg.providers\".`,\n );\n }\n\n const logger = options.logger.child({\n target: options.target,\n });\n\n const result = new MicrosoftGraphOrgEntityProvider({\n id: options.id,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n organizationTransformer: options.organizationTransformer,\n logger,\n provider,\n });\n\n result.schedule(options.schedule);\n\n return result;\n }\n\n constructor(\n private options: {\n id: string;\n provider: MicrosoftGraphProviderConfig;\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n ) {}\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */\n getProviderName() {\n return `MicrosoftGraphOrgEntityProvider:${this.options.id}`;\n }\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options?: { logger?: Logger }) {\n if (!this.connection) {\n throw new Error('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n const { markReadComplete } = trackProgress(logger);\n const client = MicrosoftGraphClient.create(this.options.provider);\n\n const { users, groups } = await readMicrosoftGraphOrg(\n client,\n provider.tenantId,\n {\n userFilter: provider.userFilter,\n userGroupMemberFilter: provider.userGroupMemberFilter,\n userGroupMemberSearch: provider.userGroupMemberSearch,\n groupFilter: provider.groupFilter,\n groupSearch: provider.groupSearch,\n queryMode: provider.queryMode,\n groupTransformer: this.options.groupTransformer,\n userTransformer: this.options.userTransformer,\n organizationTransformer: this.options.organizationTransformer,\n logger: logger,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `msgraph-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n private schedule(\n schedule: MicrosoftGraphOrgEntityProviderOptions['schedule'],\n ) {\n if (schedule === 'manual') {\n return;\n }\n\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await schedule.run({\n id,\n fn: async () => {\n const logger = this.options.logger.child({\n class: MicrosoftGraphOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: uuid.v4(),\n });\n\n try {\n await this.read({ logger });\n } catch (error) {\n logger.error(error);\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: Logger) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading msgraph users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} msgraph users and ${read.groups.length} msgraph groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n\n// Makes sure that emitted entities have a proper location based on their uuid\nexport function withLocations(providerId: string, entity: Entity): Entity {\n const uid =\n entity.metadata.annotations?.[MICROSOFT_GRAPH_USER_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] ||\n entity.metadata.annotations?.[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION] ||\n entity.metadata.name;\n const location = `msgraph:${providerId}/${encodeURIComponent(uid)}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n LocationSpec,\n processingResult,\n} from '@backstage/plugin-catalog-backend';\nimport { Logger } from 'winston';\nimport {\n GroupTransformer,\n MicrosoftGraphClient,\n MicrosoftGraphProviderConfig,\n OrganizationTransformer,\n readMicrosoftGraphConfig,\n readMicrosoftGraphOrg,\n UserTransformer,\n} from '../microsoftGraph';\n\n/**\n * Extracts teams and users out of a the Microsoft Graph API.\n *\n * @public\n */\nexport class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {\n private readonly providers: MicrosoftGraphProviderConfig[];\n private readonly logger: Logger;\n private readonly userTransformer?: UserTransformer;\n private readonly groupTransformer?: GroupTransformer;\n private readonly organizationTransformer?: OrganizationTransformer;\n\n static fromConfig(\n config: Config,\n options: {\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n },\n ) {\n const c = config.getOptionalConfig('catalog.processors.microsoftGraphOrg');\n return new MicrosoftGraphOrgReaderProcessor({\n ...options,\n providers: c ? readMicrosoftGraphConfig(c) : [],\n });\n }\n\n constructor(options: {\n providers: MicrosoftGraphProviderConfig[];\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n organizationTransformer?: OrganizationTransformer;\n }) {\n this.providers = options.providers;\n this.logger = options.logger;\n this.userTransformer = options.userTransformer;\n this.groupTransformer = options.groupTransformer;\n this.organizationTransformer = options.organizationTransformer;\n }\n\n getProcessorName(): string {\n return 'MicrosoftGraphOrgReaderProcessor';\n }\n\n async readLocation(\n location: LocationSpec,\n _optional: boolean,\n emit: CatalogProcessorEmit,\n ): Promise<boolean> {\n if (location.type !== 'microsoft-graph-org') {\n return false;\n }\n\n const provider = this.providers.find(p =>\n location.target.startsWith(p.target),\n );\n if (!provider) {\n throw new Error(\n `There is no Microsoft Graph Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`,\n );\n }\n\n // Read out all the raw data\n const startTimestamp = Date.now();\n this.logger.info('Reading Microsoft Graph users and groups');\n\n // We create a client each time as we need one that matches the specific provider\n const client = MicrosoftGraphClient.create(provider);\n const { users, groups } = await readMicrosoftGraphOrg(\n client,\n provider.tenantId,\n {\n userExpand: provider.userExpand,\n userFilter: provider.userFilter,\n userGroupMemberFilter: provider.userGroupMemberFilter,\n userGroupMemberSearch: provider.userGroupMemberSearch,\n groupExpand: provider.groupExpand,\n groupFilter: provider.groupFilter,\n groupSearch: provider.groupSearch,\n queryMode: provider.queryMode,\n userTransformer: this.userTransformer,\n groupTransformer: this.groupTransformer,\n organizationTransformer: this.organizationTransformer,\n logger: this.logger,\n },\n );\n\n const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1);\n this.logger.debug(\n `Read ${users.length} users and ${groups.length} groups from Microsoft Graph in ${duration} seconds`,\n );\n\n // Done!\n for (const group of groups) {\n emit(processingResult.entity(location, group));\n }\n for (const user of users) {\n emit(processingResult.entity(location, user));\n }\n\n return true;\n }\n}\n"],"names":["msal","qs","fetch","trimEnd","limiterFactory","stringifyEntityRef","uuid","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","processingResult"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { TaskRunner } from '@backstage/backend-tasks';
1
2
  import { Config } from '@backstage/config';
2
3
  import { EntityProvider, EntityProviderConnection, CatalogProcessor, LocationSpec, CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
3
4
  import { Logger } from 'winston';
@@ -42,7 +43,7 @@ declare type MicrosoftGraphProviderConfig = {
42
43
  */
43
44
  userFilter?: string;
44
45
  /**
45
- * The expand argument to apply to users.
46
+ * The "expand" argument to apply to users.
46
47
  *
47
48
  * E.g. "manager"
48
49
  */
@@ -77,6 +78,15 @@ declare type MicrosoftGraphProviderConfig = {
77
78
  * E.g. "\"displayName:-team\"" would only match groups which contain '-team'
78
79
  */
79
80
  groupSearch?: string;
81
+ /**
82
+ * By default, the Microsoft Graph API only provides the basic feature set
83
+ * for querying. Certain features are limited to advanced query capabilities
84
+ * (see https://docs.microsoft.com/en-us/graph/aad-advanced-queries)
85
+ * and need to be enabled.
86
+ *
87
+ * Some features like `$expand` are not available for advanced queries, though.
88
+ */
89
+ queryMode?: 'basic' | 'advanced';
80
90
  };
81
91
  /**
82
92
  * Parses configuration.
@@ -91,6 +101,7 @@ declare function readMicrosoftGraphConfig(config: Config): MicrosoftGraphProvide
91
101
  * OData (Open Data Protocol) Query
92
102
  *
93
103
  * {@link https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview}
104
+ * {@link https://docs.microsoft.com/en-us/graph/query-parameters}
94
105
  * @public
95
106
  */
96
107
  declare type ODataQuery = {
@@ -110,6 +121,10 @@ declare type ODataQuery = {
110
121
  * request a specific set of properties for each entity or complex type
111
122
  */
112
123
  select?: string[];
124
+ /**
125
+ * Retrieves the total count of matching resources.
126
+ */
127
+ count?: boolean;
113
128
  };
114
129
  /**
115
130
  * Extends the base msgraph types to include the odata type.
@@ -154,9 +169,9 @@ declare class MicrosoftGraphClient {
154
169
  * @public
155
170
  * @param path - Resource in Microsoft Graph
156
171
  * @param query - OData Query {@link ODataQuery}
157
- *
172
+ * @param queryMode - Mode to use while querying. Some features are only available at "advanced".
158
173
  */
159
- requestCollection<T>(path: string, query?: ODataQuery): AsyncIterable<T>;
174
+ requestCollection<T>(path: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced'): AsyncIterable<T>;
160
175
  /**
161
176
  * Abstract on top of {@link MicrosoftGraphClient.requestRaw}
162
177
  *
@@ -200,9 +215,9 @@ declare class MicrosoftGraphClient {
200
215
  *
201
216
  * @public
202
217
  * @param query - OData Query {@link ODataQuery}
203
- *
218
+ * @param queryMode - Mode to use while querying. Some features are only available at "advanced".
204
219
  */
205
- getUsers(query?: ODataQuery): AsyncIterable<MicrosoftGraph.User>;
220
+ getUsers(query?: ODataQuery, queryMode?: 'basic' | 'advanced'): AsyncIterable<MicrosoftGraph.User>;
206
221
  /**
207
222
  * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto}
208
223
  * of `Group` from Graph API with size limit
@@ -217,11 +232,12 @@ declare class MicrosoftGraphClient {
217
232
  * Get a collection of
218
233
  * {@link https://docs.microsoft.com/en-us/graph/api/resources/group | Group}
219
234
  * from Graph API and return as `AsyncIterable`
235
+ *
220
236
  * @public
221
237
  * @param query - OData Query {@link ODataQuery}
222
- *
238
+ * @param queryMode - Mode to use while querying. Some features are only available at "advanced".
223
239
  */
224
- getGroups(query?: ODataQuery): AsyncIterable<MicrosoftGraph.Group>;
240
+ getGroups(query?: ODataQuery, queryMode?: 'basic' | 'advanced'): AsyncIterable<MicrosoftGraph.Group>;
225
241
  /**
226
242
  * Get a collection of
227
243
  * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User}
@@ -332,6 +348,7 @@ declare function readMicrosoftGraphOrg(client: MicrosoftGraphClient, tenantId: s
332
348
  groupExpand?: string;
333
349
  groupSearch?: string;
334
350
  groupFilter?: string;
351
+ queryMode?: 'basic' | 'advanced';
335
352
  userTransformer?: UserTransformer;
336
353
  groupTransformer?: GroupTransformer;
337
354
  organizationTransformer?: OrganizationTransformer;
@@ -341,6 +358,55 @@ declare function readMicrosoftGraphOrg(client: MicrosoftGraphClient, tenantId: s
341
358
  groups: GroupEntity[];
342
359
  }>;
343
360
 
361
+ /**
362
+ * Options for {@link MicrosoftGraphOrgEntityProvider}.
363
+ *
364
+ * @public
365
+ */
366
+ interface MicrosoftGraphOrgEntityProviderOptions {
367
+ /**
368
+ * A unique, stable identifier for this provider.
369
+ *
370
+ * @example "production"
371
+ */
372
+ id: string;
373
+ /**
374
+ * The target that this provider should consume.
375
+ *
376
+ * Should exactly match the "target" field of one of the providers
377
+ * configuration entries.
378
+ */
379
+ target: string;
380
+ /**
381
+ * The logger to use.
382
+ */
383
+ logger: Logger;
384
+ /**
385
+ * The refresh schedule to use.
386
+ *
387
+ * @remarks
388
+ *
389
+ * If you pass in 'manual', you are responsible for calling the `read` method
390
+ * manually at some interval.
391
+ *
392
+ * But more commonly you will pass in the result of
393
+ * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}
394
+ * to enable automatic scheduling of tasks.
395
+ */
396
+ schedule: 'manual' | TaskRunner;
397
+ /**
398
+ * The function that transforms a user entry in msgraph to an entity.
399
+ */
400
+ userTransformer?: UserTransformer;
401
+ /**
402
+ * The function that transforms a group entry in msgraph to an entity.
403
+ */
404
+ groupTransformer?: GroupTransformer;
405
+ /**
406
+ * The function that transforms an organization entry in msgraph to an entity.
407
+ */
408
+ organizationTransformer?: OrganizationTransformer;
409
+ }
344
410
  /**
345
411
  * Reads user and group entries out of Microsoft Graph, and provides them as
346
412
  * User and Group entities for the catalog.
@@ -350,14 +416,8 @@ declare function readMicrosoftGraphOrg(client: MicrosoftGraphClient, tenantId: s
350
416
  declare class MicrosoftGraphOrgEntityProvider implements EntityProvider {
351
417
  private options;
352
418
  private connection?;
353
- static fromConfig(config: Config, options: {
354
- id: string;
355
- target: string;
356
- logger: Logger;
357
- userTransformer?: UserTransformer;
358
- groupTransformer?: GroupTransformer;
359
- organizationTransformer?: OrganizationTransformer;
360
- }): MicrosoftGraphOrgEntityProvider;
419
+ private scheduleFn?;
420
+ static fromConfig(configRoot: Config, options: MicrosoftGraphOrgEntityProviderOptions): MicrosoftGraphOrgEntityProvider;
361
421
  constructor(options: {
362
422
  id: string;
363
423
  provider: MicrosoftGraphProviderConfig;
@@ -374,7 +434,10 @@ declare class MicrosoftGraphOrgEntityProvider implements EntityProvider {
374
434
  * Runs one complete ingestion loop. Call this method regularly at some
375
435
  * appropriate cadence.
376
436
  */
377
- read(): Promise<void>;
437
+ read(options?: {
438
+ logger?: Logger;
439
+ }): Promise<void>;
440
+ private schedule;
378
441
  }
379
442
 
380
443
  /**
@@ -405,4 +468,4 @@ declare class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor {
405
468
  readLocation(location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit): Promise<boolean>;
406
469
  }
407
470
 
408
- export { GroupMember, GroupTransformer, MICROSOFT_GRAPH_GROUP_ID_ANNOTATION, MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, MICROSOFT_GRAPH_USER_ID_ANNOTATION, MicrosoftGraphClient, MicrosoftGraphOrgEntityProvider, MicrosoftGraphOrgReaderProcessor, MicrosoftGraphProviderConfig, ODataQuery, OrganizationTransformer, UserTransformer, defaultGroupTransformer, defaultOrganizationTransformer, defaultUserTransformer, normalizeEntityName, readMicrosoftGraphConfig, readMicrosoftGraphOrg };
471
+ export { GroupMember, GroupTransformer, MICROSOFT_GRAPH_GROUP_ID_ANNOTATION, MICROSOFT_GRAPH_TENANT_ID_ANNOTATION, MICROSOFT_GRAPH_USER_ID_ANNOTATION, MicrosoftGraphClient, MicrosoftGraphOrgEntityProvider, MicrosoftGraphOrgEntityProviderOptions, MicrosoftGraphOrgReaderProcessor, MicrosoftGraphProviderConfig, ODataQuery, OrganizationTransformer, UserTransformer, defaultGroupTransformer, defaultOrganizationTransformer, defaultUserTransformer, normalizeEntityName, readMicrosoftGraphConfig, readMicrosoftGraphOrg };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend-module-msgraph",
3
3
  "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph",
4
- "version": "0.2.19",
4
+ "version": "0.3.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -34,21 +34,23 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@azure/msal-node": "^1.1.0",
37
- "@backstage/catalog-model": "^0.13.0",
38
- "@backstage/config": "^0.1.15",
39
- "@backstage/plugin-catalog-backend": "^0.24.0",
37
+ "@backstage/backend-tasks": "^0.2.1",
38
+ "@backstage/catalog-model": "^1.0.0",
39
+ "@backstage/config": "^1.0.0",
40
+ "@backstage/plugin-catalog-backend": "^1.0.0",
40
41
  "@microsoft/microsoft-graph-types": "^2.6.0",
41
42
  "@types/node-fetch": "^2.5.12",
42
43
  "lodash": "^4.17.21",
43
44
  "node-fetch": "^2.6.7",
44
45
  "p-limit": "^3.0.2",
45
46
  "qs": "^6.9.4",
47
+ "uuid": "^8.0.0",
46
48
  "winston": "^3.2.1"
47
49
  },
48
50
  "devDependencies": {
49
- "@backstage/backend-common": "^0.13.0",
50
- "@backstage/backend-test-utils": "^0.1.21",
51
- "@backstage/cli": "^0.15.2",
51
+ "@backstage/backend-common": "^0.13.1",
52
+ "@backstage/backend-test-utils": "^0.1.22",
53
+ "@backstage/cli": "^0.16.0",
52
54
  "@types/lodash": "^4.14.151",
53
55
  "msw": "^0.35.0"
54
56
  },
@@ -57,5 +59,5 @@
57
59
  "config.d.ts"
58
60
  ],
59
61
  "configSchema": "config.d.ts",
60
- "gitHead": "60c4e39d1fcaeb10d6488ada1d907f34dc2a105a"
62
+ "gitHead": "e9496f746b31600dbfac7fa76987479e66426257"
61
63
  }