@things-factory/integration-msgraph 10.1.236 → 10.1.241

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.
@@ -24,6 +24,8 @@ class MsGraphConnector {
24
24
  const client = graph.Client.initWithMiddleware({
25
25
  authProvider: authProvider
26
26
  });
27
+ /* What the token carries can be read back when Graph refuses (the task names the roles granted). */
28
+ client.credential = credential;
27
29
  integration_base_1.ConnectionManager.addConnectionInstance(connection, client);
28
30
  integration_base_1.ConnectionManager.logger.info(`msgraph-connector connection(${connection.name}:${connection.endpoint}) is connected`);
29
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"msgraph-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/msgraph-connector.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,gCAA6B;AAE7B,uEAA+E;AAE/E,MAAM,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;AACxC,MAAM,KAAK,GAAG,OAAO,CAAC,mCAAmC,CAAC,CAAA;AAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,uEAAuE,CAAC,CAAA;AAEtG,MAAa,gBAAgB;IAC3B,KAAK,CAAC,KAAK,CAAC,iBAAiB;QAC3B,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAEjE,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAA;IAC1E,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAU;QACtB,IAAI,EACF,QAAQ,EAAE,GAAG,GAAG,kCAAkC,EAClD,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAC7C,GAAG,UAAU,CAAA;QAEd,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAClE,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;QACrF,MAAM,YAAY,GAAG,IAAI,aAAa,CAAC,qCAAqC,CAAC,UAAU,EAAE;YACvF,MAAM,EAAE,CAAC,sCAAsC,CAAC;SACjD,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC;YAC7C,YAAY,EAAE,YAAY;SAC3B,CAAC,CAAA;QAEF,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAE3D,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAC3B,gCAAgC,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,gBAAgB,CACvF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAAU;QACzB,MAAM,MAAM,GAAG,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAA;QAClE,wCAAwC;QACxC,oCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QAEtD,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IACnG,CAAC;IAED,IAAI,aAAa;QACf,2FAA2F;QAC3F,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,WAAW;gBAClB,kBAAkB,EAAE,IAAI;aACzB;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,cAAc;gBACpB,KAAK,EAAE,eAAe;gBACtB,kBAAkB,EAAE,IAAI;aACzB;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,WAAW;gBAClB,kBAAkB,EAAE,IAAI;aACzB;SACF,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,SAAS,CAAC,CAAA;IACpB,CAAC;IAED,IAAI,WAAW;QACb,OAAO,qBAAqB,CAAA;IAC9B,CAAC;CACF;AAxED,4CAwEC;AAED,oCAAiB,CAAC,iBAAiB,CAAC,mBAAmB,EAAE,IAAI,gBAAgB,EAAE,CAAC,CAAA","sourcesContent":["/* fetch polyfil */\nimport 'cross-fetch/polyfill'\n\nimport { ConnectionManager, Connector } from '@things-factory/integration-base'\n\nconst azure = require('@azure/identity')\nconst graph = require('@microsoft/microsoft-graph-client')\nconst authProviders = require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials')\n\nexport class MsGraphConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('msgraph-connector connections are ready')\n }\n\n async connect(connection) {\n var {\n endpoint: uri = 'https://graph.microsoft.com/v1.0',\n params: { clientId, clientSecret, tenantId }\n } = connection\n\n if (!clientId || !clientSecret || !tenantId) {\n throw new Error('Connection parameters should not be undefined')\n }\n\n const credential = new azure.ClientSecretCredential(tenantId, clientId, clientSecret)\n const authProvider = new authProviders.TokenCredentialAuthenticationProvider(credential, {\n scopes: ['https://graph.microsoft.com/.default']\n })\n\n const client = graph.Client.initWithMiddleware({\n authProvider: authProvider\n })\n\n ConnectionManager.addConnectionInstance(connection, client)\n\n ConnectionManager.logger.info(\n `msgraph-connector connection(${connection.name}:${connection.endpoint}) is connected`\n )\n }\n\n async disconnect(connection) {\n const client = ConnectionManager.getConnectionInstance(connection)\n // TODO implement how to stop connection\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`msgraph-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n /* To get proper settings, refer https://developer.microsoft.com/en-us/graph/quick-start */\n return [\n {\n type: 'string',\n name: 'clientId',\n label: 'client-id',\n useDomainAttribute: true\n },\n {\n type: 'secret',\n name: 'clientSecret',\n label: 'client-secret',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'tenantId',\n label: 'tenant-id',\n useDomainAttribute: true\n }\n ]\n }\n\n get taskPrefixes() {\n return ['msgraph']\n }\n\n get description() {\n return 'Graph API Connector'\n }\n}\n\nConnectionManager.registerConnector('msgraph-connector', new MsGraphConnector())\n"]}
1
+ {"version":3,"file":"msgraph-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/msgraph-connector.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,gCAA6B;AAE7B,uEAA+E;AAE/E,MAAM,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;AACxC,MAAM,KAAK,GAAG,OAAO,CAAC,mCAAmC,CAAC,CAAA;AAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,uEAAuE,CAAC,CAAA;AAEtG,MAAa,gBAAgB;IAC3B,KAAK,CAAC,KAAK,CAAC,iBAAiB;QAC3B,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAEjE,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAA;IAC1E,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAU;QACtB,IAAI,EACF,QAAQ,EAAE,GAAG,GAAG,kCAAkC,EAClD,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAC7C,GAAG,UAAU,CAAA;QAEd,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAClE,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;QACrF,MAAM,YAAY,GAAG,IAAI,aAAa,CAAC,qCAAqC,CAAC,UAAU,EAAE;YACvF,MAAM,EAAE,CAAC,sCAAsC,CAAC;SACjD,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC;YAC7C,YAAY,EAAE,YAAY;SAC3B,CAAC,CAAA;QACF,oGAAoG;QACpG,MAAM,CAAC,UAAU,GAAG,UAAU,CAAA;QAE9B,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAE3D,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAC3B,gCAAgC,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,gBAAgB,CACvF,CAAA;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAAU;QACzB,MAAM,MAAM,GAAG,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAA;QAClE,wCAAwC;QACxC,oCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QAEtD,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IACnG,CAAC;IAED,IAAI,aAAa;QACf,2FAA2F;QAC3F,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,WAAW;gBAClB,kBAAkB,EAAE,IAAI;aACzB;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,cAAc;gBACpB,KAAK,EAAE,eAAe;gBACtB,kBAAkB,EAAE,IAAI;aACzB;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,WAAW;gBAClB,kBAAkB,EAAE,IAAI;aACzB;SACF,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,SAAS,CAAC,CAAA;IACpB,CAAC;IAED,IAAI,WAAW;QACb,OAAO,qBAAqB,CAAA;IAC9B,CAAC;CACF;AA1ED,4CA0EC;AAED,oCAAiB,CAAC,iBAAiB,CAAC,mBAAmB,EAAE,IAAI,gBAAgB,EAAE,CAAC,CAAA","sourcesContent":["/* fetch polyfil */\nimport 'cross-fetch/polyfill'\n\nimport { ConnectionManager, Connector } from '@things-factory/integration-base'\n\nconst azure = require('@azure/identity')\nconst graph = require('@microsoft/microsoft-graph-client')\nconst authProviders = require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials')\n\nexport class MsGraphConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('msgraph-connector connections are ready')\n }\n\n async connect(connection) {\n var {\n endpoint: uri = 'https://graph.microsoft.com/v1.0',\n params: { clientId, clientSecret, tenantId }\n } = connection\n\n if (!clientId || !clientSecret || !tenantId) {\n throw new Error('Connection parameters should not be undefined')\n }\n\n const credential = new azure.ClientSecretCredential(tenantId, clientId, clientSecret)\n const authProvider = new authProviders.TokenCredentialAuthenticationProvider(credential, {\n scopes: ['https://graph.microsoft.com/.default']\n })\n\n const client = graph.Client.initWithMiddleware({\n authProvider: authProvider\n })\n /* What the token carries can be read back when Graph refuses (the task names the roles granted). */\n client.credential = credential\n\n ConnectionManager.addConnectionInstance(connection, client)\n\n ConnectionManager.logger.info(\n `msgraph-connector connection(${connection.name}:${connection.endpoint}) is connected`\n )\n }\n\n async disconnect(connection) {\n const client = ConnectionManager.getConnectionInstance(connection)\n // TODO implement how to stop connection\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`msgraph-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n /* To get proper settings, refer https://developer.microsoft.com/en-us/graph/quick-start */\n return [\n {\n type: 'string',\n name: 'clientId',\n label: 'client-id',\n useDomainAttribute: true\n },\n {\n type: 'secret',\n name: 'clientSecret',\n label: 'client-secret',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'tenantId',\n label: 'tenant-id',\n useDomainAttribute: true\n }\n ]\n }\n\n get taskPrefixes() {\n return ['msgraph']\n }\n\n get description() {\n return 'Graph API Connector'\n }\n}\n\nConnectionManager.registerConnector('msgraph-connector', new MsGraphConnector())\n"]}
@@ -1,2 +1,3 @@
1
1
  import './msgraph-get';
2
2
  import './msgraph-post';
3
+ import './msgraph-license-assignments';
@@ -2,4 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("./msgraph-get");
4
4
  require("./msgraph-post");
5
+ require("./msgraph-license-assignments");
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,yBAAsB;AACtB,0BAAuB","sourcesContent":["import './msgraph-get'\nimport './msgraph-post'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,yBAAsB;AACtB,0BAAuB;AACvB,yCAAsC","sourcesContent":["import './msgraph-get'\nimport './msgraph-post'\nimport './msgraph-license-assignments'\n"]}
@@ -0,0 +1,23 @@
1
+ export interface LicenseAssignments {
2
+ /** The tenant, as the connection names it. */
3
+ tenantId: string | null;
4
+ /** The domains the users sign in with - the tenant's domains that are in use. */
5
+ domains: string[];
6
+ /** Each plan bought; `renewalOn` and `trial` from the tenant's subscriptions of it (`/directory/subscriptions`). */
7
+ skus: {
8
+ skuId: string;
9
+ name: string;
10
+ enabled: number;
11
+ consumed: number;
12
+ renewalOn: string | null;
13
+ trial: boolean;
14
+ }[];
15
+ users: {
16
+ login: string;
17
+ mail: string | null;
18
+ name: string | null;
19
+ enabled: boolean;
20
+ skuIds: string[];
21
+ }[];
22
+ }
23
+ export declare function readLicenseAssignments(client: any, tenantId: string | null): Promise<LicenseAssignments>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readLicenseAssignments = readLicenseAssignments;
4
+ const integration_base_1 = require("@things-factory/integration-base");
5
+ /** The permission each read needs (application permissions, admin consent). */
6
+ const NEEDS = { '/subscribedSkus': 'LicenseAssignment.Read.All', '/directory/subscriptions': 'LicenseAssignment.Read.All', '/users': 'User.Read.All' };
7
+ /** Every page of a list - Graph answers a page at a time with the next page's address. A refusal says which read and what it needs. */
8
+ async function allOf(client, path, first) {
9
+ const rows = [];
10
+ try {
11
+ let page = await first.get();
12
+ for (;;) {
13
+ rows.push(...(page?.value ?? []));
14
+ const next = page?.['@odata.nextLink'];
15
+ if (!next)
16
+ return rows;
17
+ page = await client.api(next).get();
18
+ }
19
+ }
20
+ catch (error) {
21
+ const code = error?.code ? ` (${error.code})` : '';
22
+ throw new Error(`${path}${code}: ${error?.message ?? error} - needs the application permission ${NEEDS[path]} with admin consent${await rolesOf(client)}`);
23
+ }
24
+ }
25
+ /* The application permissions the token in use carries - a role granted after the token was issued is not in it yet. */
26
+ async function rolesOf(client) {
27
+ try {
28
+ const token = await client.credential?.getToken('https://graph.microsoft.com/.default');
29
+ if (!token?.token)
30
+ return '';
31
+ const claims = JSON.parse(Buffer.from(token.token.split('.')[1], 'base64url').toString('utf8'));
32
+ return `; the token carries roles [${(claims.roles ?? []).join(', ')}], issued ${new Date(claims.iat * 1000).toISOString()}`;
33
+ }
34
+ catch {
35
+ return '';
36
+ }
37
+ }
38
+ async function readLicenseAssignments(client, tenantId) {
39
+ const skus = await allOf(client, '/subscribedSkus', client.api('/subscribedSkus'));
40
+ /*
41
+ * A plan may be bought more than once (a trial, then a purchase): it is a trial while every live one is, and renews on
42
+ * the earliest day a live purchase does - a trial's day (when it ends) only while it is a trial.
43
+ */
44
+ const subscriptions = await allOf(client, '/directory/subscriptions', client.api('/directory/subscriptions'));
45
+ const termsOf = (skuId) => {
46
+ const mine = subscriptions.filter(one => one.skuId === skuId && one.status !== 'Deleted');
47
+ const trial = mine.length > 0 && mine.every(one => one.isTrial);
48
+ const days = mine
49
+ .filter(one => trial || !one.isTrial)
50
+ .map(one => String(one.nextLifecycleDateTime ?? '').slice(0, 10))
51
+ .filter(day => /^\d{4}-\d{2}-\d{2}$/.test(day))
52
+ .sort();
53
+ return { renewalOn: days[0] ?? null, trial };
54
+ };
55
+ const users = await allOf(client, '/users', client.api('/users').select(['userPrincipalName', 'mail', 'displayName', 'accountEnabled', 'assignedLicenses']).top(999));
56
+ const held = users.filter(user => user.assignedLicenses?.length);
57
+ return {
58
+ tenantId,
59
+ domains: [...new Set(users.map(user => String(user.userPrincipalName ?? '').split('@')[1]?.toLowerCase()).filter(Boolean))],
60
+ skus: skus.map(sku => ({ skuId: sku.skuId, name: sku.skuPartNumber, enabled: Number(sku.prepaidUnits?.enabled ?? 0), consumed: Number(sku.consumedUnits ?? 0), ...termsOf(sku.skuId) })),
61
+ users: held.map(user => ({
62
+ login: user.userPrincipalName,
63
+ mail: user.mail ?? null,
64
+ name: user.displayName ?? null,
65
+ enabled: user.accountEnabled !== false,
66
+ skuIds: user.assignedLicenses.map((one) => one.skuId)
67
+ }))
68
+ };
69
+ }
70
+ async function MsGraphLicenseAssignments(step, context) {
71
+ const { connection: connectionName } = step;
72
+ const { domain } = context;
73
+ const client = await integration_base_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
74
+ if (!client)
75
+ throw new Error(`no connection '${connectionName}'`);
76
+ const connection = await integration_base_1.ConnectionManager.getConnectionEntityByName(domain, connectionName);
77
+ return { data: await readLicenseAssignments(client, connection?.params?.tenantId ?? null) };
78
+ }
79
+ MsGraphLicenseAssignments.parameterSpec = [];
80
+ MsGraphLicenseAssignments.help = 'integration/task/msgraph-license-assignments';
81
+ integration_base_1.TaskRegistry.registerTaskHandler('msgraph-license-assignments', MsGraphLicenseAssignments);
82
+ //# sourceMappingURL=msgraph-license-assignments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msgraph-license-assignments.js","sourceRoot":"","sources":["../../../server/engine/task/msgraph-license-assignments.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConnectionManager, TaskRegistry } from '@things-factory/integration-base'\n\n/*\n * The licenses a Microsoft 365 tenant bought, when each renews and whether it is a trial, and who holds each (ADR-0177\n * 개정 2) - read with LicenseAssignment.Read.All\n * and User.Read.All. A scenario hands the answer to whoever keeps seats (saas `syncSaasLicenses`).\n */\n\nexport interface LicenseAssignments {\n /** The tenant, as the connection names it. */\n tenantId: string | null\n /** The domains the users sign in with - the tenant's domains that are in use. */\n domains: string[]\n /** Each plan bought; `renewalOn` and `trial` from the tenant's subscriptions of it (`/directory/subscriptions`). */\n skus: { skuId: string; name: string; enabled: number; consumed: number; renewalOn: string | null; trial: boolean }[]\n users: { login: string; mail: string | null; name: string | null; enabled: boolean; skuIds: string[] }[]\n}\n\n/** The permission each read needs (application permissions, admin consent). */\nconst NEEDS: Record<string, string> = { '/subscribedSkus': 'LicenseAssignment.Read.All', '/directory/subscriptions': 'LicenseAssignment.Read.All', '/users': 'User.Read.All' }\n\n/** Every page of a list - Graph answers a page at a time with the next page's address. A refusal says which read and what it needs. */\nasync function allOf(client: any, path: string, first: any): Promise<any[]> {\n const rows: any[] = []\n try {\n let page = await first.get()\n for (;;) {\n rows.push(...(page?.value ?? []))\n const next = page?.['@odata.nextLink']\n if (!next) return rows\n page = await client.api(next).get()\n }\n } catch (error: any) {\n const code = error?.code ? ` (${error.code})` : ''\n throw new Error(`${path}${code}: ${error?.message ?? error} - needs the application permission ${NEEDS[path]} with admin consent${await rolesOf(client)}`)\n }\n}\n\n/* The application permissions the token in use carries - a role granted after the token was issued is not in it yet. */\nasync function rolesOf(client: any): Promise<string> {\n try {\n const token = await client.credential?.getToken('https://graph.microsoft.com/.default')\n if (!token?.token) return ''\n const claims = JSON.parse(Buffer.from(token.token.split('.')[1], 'base64url').toString('utf8'))\n return `; the token carries roles [${(claims.roles ?? []).join(', ')}], issued ${new Date(claims.iat * 1000).toISOString()}`\n } catch {\n return ''\n }\n}\n\nexport async function readLicenseAssignments(client: any, tenantId: string | null): Promise<LicenseAssignments> {\n const skus = await allOf(client, '/subscribedSkus', client.api('/subscribedSkus'))\n /*\n * A plan may be bought more than once (a trial, then a purchase): it is a trial while every live one is, and renews on\n * the earliest day a live purchase does - a trial's day (when it ends) only while it is a trial.\n */\n const subscriptions = await allOf(client, '/directory/subscriptions', client.api('/directory/subscriptions'))\n const termsOf = (skuId: string) => {\n const mine = subscriptions.filter(one => one.skuId === skuId && one.status !== 'Deleted')\n const trial = mine.length > 0 && mine.every(one => one.isTrial)\n const days = mine\n .filter(one => trial || !one.isTrial)\n .map(one => String(one.nextLifecycleDateTime ?? '').slice(0, 10))\n .filter(day => /^\\d{4}-\\d{2}-\\d{2}$/.test(day))\n .sort()\n return { renewalOn: days[0] ?? null, trial }\n }\n const users = await allOf(client, '/users', client.api('/users').select(['userPrincipalName', 'mail', 'displayName', 'accountEnabled', 'assignedLicenses']).top(999))\n const held = users.filter(user => user.assignedLicenses?.length)\n return {\n tenantId,\n domains: [...new Set(users.map(user => String(user.userPrincipalName ?? '').split('@')[1]?.toLowerCase()).filter(Boolean))] as string[],\n skus: skus.map(sku => ({ skuId: sku.skuId, name: sku.skuPartNumber, enabled: Number(sku.prepaidUnits?.enabled ?? 0), consumed: Number(sku.consumedUnits ?? 0), ...termsOf(sku.skuId) })),\n users: held.map(user => ({\n login: user.userPrincipalName,\n mail: user.mail ?? null,\n name: user.displayName ?? null,\n enabled: user.accountEnabled !== false,\n skuIds: user.assignedLicenses.map((one: any) => one.skuId)\n }))\n }\n}\n\nasync function MsGraphLicenseAssignments(step, context) {\n const { connection: connectionName } = step\n const { domain } = context\n const client = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n if (!client) throw new Error(`no connection '${connectionName}'`)\n const connection = await ConnectionManager.getConnectionEntityByName(domain, connectionName)\n return { data: await readLicenseAssignments(client, connection?.params?.tenantId ?? null) }\n}\n\nMsGraphLicenseAssignments.parameterSpec = []\nMsGraphLicenseAssignments.help = 'integration/task/msgraph-license-assignments'\n\nTaskRegistry.registerTaskHandler('msgraph-license-assignments', MsGraphLicenseAssignments)\n"]}