@things-factory/integration-google-workspace 0.0.0-stage → 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.
Files changed (30) hide show
  1. package/dist-server/engine/connector/google-workspace-connector.d.ts +25 -0
  2. package/dist-server/engine/connector/google-workspace-connector.js +62 -0
  3. package/dist-server/engine/connector/google-workspace-connector.js.map +1 -0
  4. package/dist-server/engine/connector/index.d.ts +1 -0
  5. package/dist-server/engine/connector/index.js +4 -0
  6. package/dist-server/engine/connector/index.js.map +1 -0
  7. package/dist-server/engine/index.d.ts +2 -0
  8. package/dist-server/engine/index.js +5 -0
  9. package/dist-server/engine/index.js.map +1 -0
  10. package/dist-server/engine/task/google-workspace-license-assignments.d.ts +20 -0
  11. package/dist-server/engine/task/google-workspace-license-assignments.js +71 -0
  12. package/dist-server/engine/task/google-workspace-license-assignments.js.map +1 -0
  13. package/dist-server/engine/task/index.d.ts +1 -0
  14. package/dist-server/engine/task/index.js +4 -0
  15. package/dist-server/engine/task/index.js.map +1 -0
  16. package/dist-server/index.d.ts +2 -0
  17. package/dist-server/index.js +7 -0
  18. package/dist-server/index.js.map +1 -0
  19. package/dist-server/tsconfig.tsbuildinfo +1 -0
  20. package/helps/integration/connector/google-workspace-connector.md +13 -0
  21. package/helps/integration/task/google-workspace-license-assignments.md +14 -0
  22. package/package.json +28 -4
  23. package/things-factory.config.js +1 -0
  24. package/translations/en.json +5 -0
  25. package/translations/es.json +5 -0
  26. package/translations/ja.json +5 -0
  27. package/translations/ko.json +5 -0
  28. package/translations/ms.json +5 -0
  29. package/translations/zh.json +5 -0
  30. package/README.md +0 -3
@@ -0,0 +1,25 @@
1
+ import { Connector } from '@things-factory/integration-base';
2
+ export declare const WORKSPACE_SCOPES: string[];
3
+ export interface WorkspaceInstance {
4
+ licensing: any;
5
+ directory: any;
6
+ }
7
+ /** The service account's key (the JSON Google gives), checked so a wrong paste is said at connect - never the value itself. */
8
+ export declare function serviceAccountOf(key: string | null | undefined): {
9
+ client_email: string;
10
+ private_key: string;
11
+ };
12
+ export declare class GoogleWorkspaceConnector implements Connector {
13
+ ready(connectionConfigs: any): Promise<void>;
14
+ connect(connection: any): Promise<void>;
15
+ disconnect(connection: any): Promise<void>;
16
+ get parameterSpec(): {
17
+ type: string;
18
+ name: string;
19
+ label: string;
20
+ useDomainAttribute: boolean;
21
+ }[];
22
+ get taskPrefixes(): string[];
23
+ get help(): string;
24
+ get description(): string;
25
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GoogleWorkspaceConnector = exports.WORKSPACE_SCOPES = void 0;
4
+ exports.serviceAccountOf = serviceAccountOf;
5
+ const googleapis_1 = require("googleapis");
6
+ const integration_base_1 = require("@things-factory/integration-base");
7
+ /*
8
+ * Google Workspace connector (ADR-0177 개정 4) - a service account with domain-wide delegation, acting as an admin of
9
+ * the Workspace. Read only: the customer (Directory) and its license assignments (Enterprise License Manager).
10
+ */
11
+ exports.WORKSPACE_SCOPES = ['https://www.googleapis.com/auth/apps.licensing', 'https://www.googleapis.com/auth/admin.directory.customer.readonly'];
12
+ /** The service account's key (the JSON Google gives), checked so a wrong paste is said at connect - never the value itself. */
13
+ function serviceAccountOf(key) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(String(key ?? ''));
17
+ }
18
+ catch {
19
+ throw new Error('the service account key is not the JSON key file Google gives - paste the whole file');
20
+ }
21
+ if (!parsed?.client_email || !parsed?.private_key)
22
+ throw new Error('the service account key has no client_email or private_key - paste the JSON key of a service account');
23
+ return parsed;
24
+ }
25
+ class GoogleWorkspaceConnector {
26
+ async ready(connectionConfigs) {
27
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)));
28
+ integration_base_1.ConnectionManager.logger.info('google-workspace-connector connections are ready');
29
+ }
30
+ async connect(connection) {
31
+ const { params } = connection;
32
+ const account = serviceAccountOf(params?.serviceAccountKey);
33
+ if (!params?.adminEmail)
34
+ throw new Error('the connection names no admin of the Workspace to act as (adminEmail)');
35
+ const auth = new googleapis_1.google.auth.JWT({ email: account.client_email, key: account.private_key, scopes: exports.WORKSPACE_SCOPES, subject: params.adminEmail });
36
+ const instance = { licensing: googleapis_1.google.licensing({ version: 'v1', auth }), directory: googleapis_1.google.admin({ version: 'directory_v1', auth }) };
37
+ integration_base_1.ConnectionManager.addConnectionInstance(connection, instance);
38
+ integration_base_1.ConnectionManager.logger.info(`google-workspace-connector connection(${connection.name}) is connected`);
39
+ }
40
+ async disconnect(connection) {
41
+ integration_base_1.ConnectionManager.removeConnectionInstance(connection);
42
+ integration_base_1.ConnectionManager.logger.info(`google-workspace-connector connection(${connection.name}) is disconnected`);
43
+ }
44
+ get parameterSpec() {
45
+ return [
46
+ { type: 'secret', name: 'serviceAccountKey', label: 'service-account-key', useDomainAttribute: true },
47
+ { type: 'string', name: 'adminEmail', label: 'admin-email', useDomainAttribute: true }
48
+ ];
49
+ }
50
+ get taskPrefixes() {
51
+ return ['google-workspace'];
52
+ }
53
+ get help() {
54
+ return 'integration/connector/google-workspace-connector';
55
+ }
56
+ get description() {
57
+ return 'Google Workspace Connector';
58
+ }
59
+ }
60
+ exports.GoogleWorkspaceConnector = GoogleWorkspaceConnector;
61
+ integration_base_1.ConnectionManager.registerConnector('google-workspace-connector', new GoogleWorkspaceConnector());
62
+ //# sourceMappingURL=google-workspace-connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-workspace-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/google-workspace-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import { google } from 'googleapis'\n\nimport { ConnectionManager, Connector } from '@things-factory/integration-base'\n\n/*\n * Google Workspace connector (ADR-0177 개정 4) - a service account with domain-wide delegation, acting as an admin of\n * the Workspace. Read only: the customer (Directory) and its license assignments (Enterprise License Manager).\n */\nexport const WORKSPACE_SCOPES = ['https://www.googleapis.com/auth/apps.licensing', 'https://www.googleapis.com/auth/admin.directory.customer.readonly']\n\nexport interface WorkspaceInstance {\n licensing: any\n directory: any\n}\n\n/** The service account's key (the JSON Google gives), checked so a wrong paste is said at connect - never the value itself. */\nexport function serviceAccountOf(key: string | null | undefined): { client_email: string; private_key: string } {\n let parsed: any\n try {\n parsed = JSON.parse(String(key ?? ''))\n } catch {\n throw new Error('the service account key is not the JSON key file Google gives - paste the whole file')\n }\n if (!parsed?.client_email || !parsed?.private_key) throw new Error('the service account key has no client_email or private_key - paste the JSON key of a service account')\n return parsed\n}\n\nexport class GoogleWorkspaceConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n ConnectionManager.logger.info('google-workspace-connector connections are ready')\n }\n\n async connect(connection) {\n const { params } = connection\n const account = serviceAccountOf(params?.serviceAccountKey)\n if (!params?.adminEmail) throw new Error('the connection names no admin of the Workspace to act as (adminEmail)')\n const auth = new google.auth.JWT({ email: account.client_email, key: account.private_key, scopes: WORKSPACE_SCOPES, subject: params.adminEmail })\n const instance: WorkspaceInstance = { licensing: google.licensing({ version: 'v1', auth }), directory: google.admin({ version: 'directory_v1', auth }) }\n ConnectionManager.addConnectionInstance(connection, instance)\n ConnectionManager.logger.info(`google-workspace-connector connection(${connection.name}) is connected`)\n }\n\n async disconnect(connection) {\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`google-workspace-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n { type: 'secret', name: 'serviceAccountKey', label: 'service-account-key', useDomainAttribute: true },\n { type: 'string', name: 'adminEmail', label: 'admin-email', useDomainAttribute: true }\n ]\n }\n\n get taskPrefixes() {\n return ['google-workspace']\n }\n\n get help() {\n return 'integration/connector/google-workspace-connector'\n }\n\n get description() {\n return 'Google Workspace Connector'\n }\n}\n\nConnectionManager.registerConnector('google-workspace-connector', new GoogleWorkspaceConnector())\n"]}
@@ -0,0 +1 @@
1
+ import './google-workspace-connector';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./google-workspace-connector");
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/connector/index.ts"],"names":[],"mappings":";;AAAA,wCAAqC","sourcesContent":["import './google-workspace-connector'\n"]}
@@ -0,0 +1,2 @@
1
+ import './connector';
2
+ import './task';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./connector");
4
+ require("./task");
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;AAAA,uBAAoB;AACpB,kBAAe","sourcesContent":["import './connector'\nimport './task'\n"]}
@@ -0,0 +1,20 @@
1
+ import type { WorkspaceInstance } from '../connector/google-workspace-connector';
2
+ /** Google Workspace itself; other products (Vault, Cloud Identity Premium, ...) by their product ids. */
3
+ export declare const DEFAULT_PRODUCTS: string[];
4
+ export declare function readWorkspaceLicenses(instance: WorkspaceInstance, products?: string[]): Promise<{
5
+ tenantId: any;
6
+ domains: string[];
7
+ skus: {
8
+ skuId: string;
9
+ name: string;
10
+ enabled: number;
11
+ consumed: number;
12
+ }[];
13
+ users: {
14
+ login: string;
15
+ mail: string;
16
+ name: null;
17
+ enabled: true;
18
+ skuIds: string[];
19
+ }[];
20
+ }>;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_PRODUCTS = void 0;
4
+ exports.readWorkspaceLicenses = readWorkspaceLicenses;
5
+ const integration_base_1 = require("@things-factory/integration-base");
6
+ /*
7
+ * The licenses a Google Workspace customer assigns and to whom (ADR-0177 개정 4) - in the shape a Microsoft 365 tenant's
8
+ * come in (integration-msgraph `msgraph-license-assignments`), so one sync keeps both (saas `syncSaasLicenses`). The
9
+ * License Manager says who holds what, not how many were bought: a plan's count is the seats assigned.
10
+ */
11
+ /** Google Workspace itself; other products (Vault, Cloud Identity Premium, ...) by their product ids. */
12
+ exports.DEFAULT_PRODUCTS = ['Google-Apps'];
13
+ /** Every page of a product's assignments. */
14
+ async function assignmentsOf(licensing, productId, customerId) {
15
+ const rows = [];
16
+ let pageToken;
17
+ do {
18
+ const { data } = await licensing.licenseAssignments.listForProduct({ productId, customerId, maxResults: 1000, pageToken });
19
+ rows.push(...(data?.items ?? []));
20
+ pageToken = data?.nextPageToken || undefined;
21
+ } while (pageToken);
22
+ return rows;
23
+ }
24
+ const refusalOf = (what, error) => new Error(`${what}: ${error?.errors?.[0]?.message ?? error?.message ?? error} - the service account needs domain-wide delegation of apps.licensing and admin.directory.customer.readonly, acting as a Workspace admin`);
25
+ async function readWorkspaceLicenses(instance, products = exports.DEFAULT_PRODUCTS) {
26
+ let customer;
27
+ try {
28
+ customer = (await instance.directory.customers.get({ customerKey: 'my_customer' })).data;
29
+ }
30
+ catch (error) {
31
+ throw refusalOf('the Workspace customer', error);
32
+ }
33
+ const assignments = [];
34
+ for (const productId of products)
35
+ try {
36
+ assignments.push(...(await assignmentsOf(instance.licensing, productId, customer.id)));
37
+ }
38
+ catch (error) {
39
+ throw refusalOf(`the licenses of ${productId}`, error);
40
+ }
41
+ const skus = new Map();
42
+ const users = new Map();
43
+ for (const one of assignments) {
44
+ const sku = skus.get(one.skuId) ?? { skuId: one.skuId, name: one.skuName || one.skuId, held: 0 };
45
+ sku.held++;
46
+ skus.set(one.skuId, sku);
47
+ const login = String(one.userId).toLowerCase();
48
+ const user = users.get(login) ?? { login, mail: login, name: null, enabled: true, skuIds: [] };
49
+ if (!user.skuIds.includes(one.skuId))
50
+ user.skuIds.push(one.skuId);
51
+ users.set(login, user);
52
+ }
53
+ return {
54
+ tenantId: customer.id,
55
+ domains: [...new Set([customer.customerDomain, ...[...users.keys()].map(login => login.split('@')[1])].filter(Boolean).map((one) => one.toLowerCase()))],
56
+ skus: [...skus.values()].map(sku => ({ skuId: sku.skuId, name: sku.name, enabled: sku.held, consumed: sku.held })),
57
+ users: [...users.values()]
58
+ };
59
+ }
60
+ async function GoogleWorkspaceLicenseAssignments(step, context) {
61
+ const { connection: connectionName, params } = step;
62
+ const instance = await integration_base_1.ConnectionManager.getConnectionInstanceByName(context.domain, connectionName);
63
+ if (!instance)
64
+ throw new Error(`no connection '${connectionName}'`);
65
+ const products = String(params?.products ?? '').split(',').map(one => one.trim()).filter(Boolean);
66
+ return { data: await readWorkspaceLicenses(instance, products.length ? products : exports.DEFAULT_PRODUCTS) };
67
+ }
68
+ GoogleWorkspaceLicenseAssignments.parameterSpec = [{ type: 'string', name: 'products', label: 'products', placeholder: 'Google-Apps' }];
69
+ GoogleWorkspaceLicenseAssignments.help = 'integration/task/google-workspace-license-assignments';
70
+ integration_base_1.TaskRegistry.registerTaskHandler('google-workspace-license-assignments', GoogleWorkspaceLicenseAssignments);
71
+ //# sourceMappingURL=google-workspace-license-assignments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-workspace-license-assignments.js","sourceRoot":"","sources":["../../../server/engine/task/google-workspace-license-assignments.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConnectionManager, TaskRegistry } from '@things-factory/integration-base'\n\nimport type { WorkspaceInstance } from '../connector/google-workspace-connector'\n\n/*\n * The licenses a Google Workspace customer assigns and to whom (ADR-0177 개정 4) - in the shape a Microsoft 365 tenant's\n * come in (integration-msgraph `msgraph-license-assignments`), so one sync keeps both (saas `syncSaasLicenses`). The\n * License Manager says who holds what, not how many were bought: a plan's count is the seats assigned.\n */\n\n/** Google Workspace itself; other products (Vault, Cloud Identity Premium, ...) by their product ids. */\nexport const DEFAULT_PRODUCTS = ['Google-Apps']\n\n/** Every page of a product's assignments. */\nasync function assignmentsOf(licensing: any, productId: string, customerId: string): Promise<any[]> {\n const rows: any[] = []\n let pageToken: string | undefined\n do {\n const { data } = await licensing.licenseAssignments.listForProduct({ productId, customerId, maxResults: 1000, pageToken })\n rows.push(...(data?.items ?? []))\n pageToken = data?.nextPageToken || undefined\n } while (pageToken)\n return rows\n}\n\nconst refusalOf = (what: string, error: any) =>\n new Error(`${what}: ${error?.errors?.[0]?.message ?? error?.message ?? error} - the service account needs domain-wide delegation of apps.licensing and admin.directory.customer.readonly, acting as a Workspace admin`)\n\nexport async function readWorkspaceLicenses(instance: WorkspaceInstance, products: string[] = DEFAULT_PRODUCTS) {\n let customer: any\n try {\n customer = (await instance.directory.customers.get({ customerKey: 'my_customer' })).data\n } catch (error) {\n throw refusalOf('the Workspace customer', error)\n }\n const assignments: any[] = []\n for (const productId of products)\n try {\n assignments.push(...(await assignmentsOf(instance.licensing, productId, customer.id)))\n } catch (error) {\n throw refusalOf(`the licenses of ${productId}`, error)\n }\n\n const skus = new Map<string, { skuId: string; name: string; held: number }>()\n const users = new Map<string, { login: string; mail: string; name: null; enabled: true; skuIds: string[] }>()\n for (const one of assignments) {\n const sku = skus.get(one.skuId) ?? { skuId: one.skuId, name: one.skuName || one.skuId, held: 0 }\n sku.held++\n skus.set(one.skuId, sku)\n const login = String(one.userId).toLowerCase()\n const user = users.get(login) ?? { login, mail: login, name: null, enabled: true as const, skuIds: [] }\n if (!user.skuIds.includes(one.skuId)) user.skuIds.push(one.skuId)\n users.set(login, user)\n }\n return {\n tenantId: customer.id,\n domains: [...new Set([customer.customerDomain, ...[...users.keys()].map(login => login.split('@')[1])].filter(Boolean).map((one: string) => one.toLowerCase()))],\n skus: [...skus.values()].map(sku => ({ skuId: sku.skuId, name: sku.name, enabled: sku.held, consumed: sku.held })),\n users: [...users.values()]\n }\n}\n\nasync function GoogleWorkspaceLicenseAssignments(step, context) {\n const { connection: connectionName, params } = step\n const instance: WorkspaceInstance | undefined = await ConnectionManager.getConnectionInstanceByName(context.domain, connectionName)\n if (!instance) throw new Error(`no connection '${connectionName}'`)\n const products = String(params?.products ?? '').split(',').map(one => one.trim()).filter(Boolean)\n return { data: await readWorkspaceLicenses(instance, products.length ? products : DEFAULT_PRODUCTS) }\n}\n\nGoogleWorkspaceLicenseAssignments.parameterSpec = [{ type: 'string', name: 'products', label: 'products', placeholder: 'Google-Apps' }]\nGoogleWorkspaceLicenseAssignments.help = 'integration/task/google-workspace-license-assignments'\n\nTaskRegistry.registerTaskHandler('google-workspace-license-assignments', GoogleWorkspaceLicenseAssignments)\n"]}
@@ -0,0 +1 @@
1
+ import './google-workspace-license-assignments';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./google-workspace-license-assignments");
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,kDAA+C","sourcesContent":["import './google-workspace-license-assignments'\n"]}
@@ -0,0 +1,2 @@
1
+ import './engine';
2
+ export { readWorkspaceLicenses } from './engine/task/google-workspace-license-assignments';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readWorkspaceLicenses = void 0;
4
+ require("./engine");
5
+ var google_workspace_license_assignments_1 = require("./engine/task/google-workspace-license-assignments");
6
+ Object.defineProperty(exports, "readWorkspaceLicenses", { enumerable: true, get: function () { return google_workspace_license_assignments_1.readWorkspaceLicenses; } });
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,oBAAiB;AAEjB,2GAA0F;AAAjF,6IAAA,qBAAqB,OAAA","sourcesContent":["import './engine'\n\nexport { readWorkspaceLicenses } from './engine/task/google-workspace-license-assignments'\n"]}