@backstage/plugin-catalog-backend-module-ldap 0.3.15 → 0.4.1

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,99 @@
1
1
  # @backstage/plugin-catalog-backend-module-ldap
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional.
8
+ - a6c367d937: Add config support for LDAP search options.
9
+ - 74375be2c6: Updated to no longer rely on the `RecursivePartial` export from `@backstage/plugin-catalog-backend`.
10
+ - e949d68059: Made sure to move the catalog-related github and ldap config into their right places
11
+ - Updated dependencies
12
+ - @backstage/plugin-catalog-backend@1.0.0
13
+ - @backstage/backend-tasks@0.2.1
14
+ - @backstage/catalog-model@1.0.0
15
+ - @backstage/config@1.0.0
16
+ - @backstage/errors@1.0.0
17
+ - @backstage/types@1.0.0
18
+
19
+ ## 0.4.0
20
+
21
+ ### Minor Changes
22
+
23
+ - 9461f73643: **BREAKING**: Added a `schedule` field to `LdapOrgEntityProvider.fromConfig`, which is required. If you want to retain the old behavior of scheduling the provider manually, you can set it to the string value `'manual'`. But you may want to leverage the ability to instead pass in the recurring task schedule information directly. This will allow you to simplify your backend setup code to not need an intermediate variable and separate scheduling code at the bottom.
24
+
25
+ All things said, a typical setup might now look as follows:
26
+
27
+ ```diff
28
+ // packages/backend/src/plugins/catalog.ts
29
+ +import { Duration } from 'luxon';
30
+ +import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
31
+ export default async function createPlugin(
32
+ env: PluginEnvironment,
33
+ ): Promise<Router> {
34
+ const builder = await CatalogBuilder.create(env);
35
+ + // The target parameter below needs to match the ldap.providers.target
36
+ + // value specified in your app-config.
37
+ + builder.addEntityProvider(
38
+ + LdapOrgEntityProvider.fromConfig(env.config, {
39
+ + id: 'our-ldap-master',
40
+ + target: 'ldaps://ds.example.net',
41
+ + logger: env.logger,
42
+ + schedule: env.scheduler.createScheduledTaskRunner({
43
+ + frequency: Duration.fromObject({ minutes: 60 }),
44
+ + timeout: Duration.fromObject({ minutes: 15 }),
45
+ + }),
46
+ + }),
47
+ + );
48
+ ```
49
+
50
+ ### Patch Changes
51
+
52
+ - f751e84572: Ignore search referrals instead of throwing an error.
53
+ - Updated dependencies
54
+ - @backstage/backend-tasks@0.2.0
55
+ - @backstage/plugin-catalog-backend@0.24.0
56
+ - @backstage/catalog-model@0.13.0
57
+
58
+ ## 0.4.0-next.0
59
+
60
+ ### Minor Changes
61
+
62
+ - 9461f73643: **BREAKING**: Added a `schedule` field to `LdapOrgEntityProvider.fromConfig`, which is required. If you want to retain the old behavior of scheduling the provider manually, you can set it to the string value `'manual'`. But you may want to leverage the ability to instead pass in the recurring task schedule information directly. This will allow you to simplify your backend setup code to not need an intermediate variable and separate scheduling code at the bottom.
63
+
64
+ All things said, a typical setup might now look as follows:
65
+
66
+ ```diff
67
+ // packages/backend/src/plugins/catalog.ts
68
+ +import { Duration } from 'luxon';
69
+ +import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
70
+ export default async function createPlugin(
71
+ env: PluginEnvironment,
72
+ ): Promise<Router> {
73
+ const builder = await CatalogBuilder.create(env);
74
+ + // The target parameter below needs to match the ldap.providers.target
75
+ + // value specified in your app-config.
76
+ + builder.addEntityProvider(
77
+ + LdapOrgEntityProvider.fromConfig(env.config, {
78
+ + id: 'our-ldap-master',
79
+ + target: 'ldaps://ds.example.net',
80
+ + logger: env.logger,
81
+ + schedule: env.scheduler.createScheduledTaskRunner({
82
+ + frequency: Duration.fromObject({ minutes: 60 }),
83
+ + timeout: Duration.fromObject({ minutes: 15 }),
84
+ + }),
85
+ + }),
86
+ + );
87
+ ```
88
+
89
+ ### Patch Changes
90
+
91
+ - f751e84572: Ignore search referrals instead of throwing an error.
92
+ - Updated dependencies
93
+ - @backstage/backend-tasks@0.2.0-next.0
94
+ - @backstage/plugin-catalog-backend@0.24.0-next.0
95
+ - @backstage/catalog-model@0.13.0-next.0
96
+
3
97
  ## 0.3.15
4
98
 
5
99
  ### Patch Changes
package/config.d.ts CHANGED
@@ -17,8 +17,223 @@
17
17
  import { JsonValue } from '@backstage/types';
18
18
 
19
19
  export interface Config {
20
+ /**
21
+ * LdapOrgEntityProvider / LdapOrgReaderProcessor configuration
22
+ */
23
+ ldap?: {
24
+ /**
25
+ * The configuration parameters for each single LDAP provider.
26
+ */
27
+ providers: Array<{
28
+ /**
29
+ * The prefix of the target that this matches on, e.g.
30
+ * "ldaps://ds.example.net", with no trailing slash.
31
+ */
32
+ target: string;
33
+
34
+ /**
35
+ * The settings to use for the bind command. If none are specified,
36
+ * the bind command is not issued.
37
+ */
38
+ bind?: {
39
+ /**
40
+ * The DN of the user to auth as.
41
+ *
42
+ * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net"
43
+ */
44
+ dn: string;
45
+ /**
46
+ * The secret of the user to auth as (its password).
47
+ *
48
+ * @visibility secret
49
+ */
50
+ secret: string;
51
+ };
52
+
53
+ /**
54
+ * The settings that govern the reading and interpretation of users.
55
+ */
56
+ users: {
57
+ /**
58
+ * The DN under which users are stored.
59
+ *
60
+ * E.g. "ou=people,ou=example,dc=example,dc=net"
61
+ */
62
+ dn: string;
63
+ /**
64
+ * The search options to use. The default is scope "one" and
65
+ * attributes "*" and "+".
66
+ *
67
+ * It is common to want to specify a filter, to narrow down the set
68
+ * of matching items.
69
+ */
70
+ options: {
71
+ scope?: 'base' | 'one' | 'sub';
72
+ filter?: string;
73
+ attributes?: string | string[];
74
+ sizeLimit?: number;
75
+ timeLimit?: number;
76
+ derefAliases?: number;
77
+ typesOnly?: boolean;
78
+ paged?:
79
+ | boolean
80
+ | {
81
+ pageSize?: number;
82
+ pagePause?: boolean;
83
+ };
84
+ };
85
+ /**
86
+ * JSON paths (on a.b.c form) and hard coded values to set on those
87
+ * paths.
88
+ *
89
+ * This can be useful for example if you want to hard code a
90
+ * namespace or similar on the generated entities.
91
+ */
92
+ set?: { [key: string]: JsonValue };
93
+ /**
94
+ * Mappings from well known entity fields, to LDAP attribute names
95
+ */
96
+ map?: {
97
+ /**
98
+ * The name of the attribute that holds the relative
99
+ * distinguished name of each entry. Defaults to "uid".
100
+ */
101
+ rdn?: string;
102
+ /**
103
+ * The name of the attribute that shall be used for the value of
104
+ * the metadata.name field of the entity. Defaults to "uid".
105
+ */
106
+ name?: string;
107
+ /**
108
+ * The name of the attribute that shall be used for the value of
109
+ * the metadata.description field of the entity.
110
+ */
111
+ description?: string;
112
+ /**
113
+ * The name of the attribute that shall be used for the value of
114
+ * the spec.profile.displayName field of the entity. Defaults to
115
+ * "cn".
116
+ */
117
+ displayName?: string;
118
+ /**
119
+ * The name of the attribute that shall be used for the value of
120
+ * the spec.profile.email field of the entity. Defaults to
121
+ * "mail".
122
+ */
123
+ email?: string;
124
+ /**
125
+ * The name of the attribute that shall be used for the value of
126
+ * the spec.profile.picture field of the entity.
127
+ */
128
+ picture?: string;
129
+ /**
130
+ * The name of the attribute that shall be used for the values of
131
+ * the spec.memberOf field of the entity. Defaults to "memberOf".
132
+ */
133
+ memberOf?: string;
134
+ };
135
+ };
136
+
137
+ /**
138
+ * The settings that govern the reading and interpretation of groups.
139
+ */
140
+ groups: {
141
+ /**
142
+ * The DN under which groups are stored.
143
+ *
144
+ * E.g. "ou=people,ou=example,dc=example,dc=net"
145
+ */
146
+ dn: string;
147
+ /**
148
+ * The search options to use. The default is scope "one" and
149
+ * attributes "*" and "+".
150
+ *
151
+ * It is common to want to specify a filter, to narrow down the set
152
+ * of matching items.
153
+ */
154
+ options: {
155
+ scope?: 'base' | 'one' | 'sub';
156
+ filter?: string;
157
+ attributes?: string | string[];
158
+ sizeLimit?: number;
159
+ timeLimit?: number;
160
+ derefAliases?: number;
161
+ typesOnly?: boolean;
162
+ paged?:
163
+ | boolean
164
+ | {
165
+ pageSize?: number;
166
+ pagePause?: boolean;
167
+ };
168
+ };
169
+ /**
170
+ * JSON paths (on a.b.c form) and hard coded values to set on those
171
+ * paths.
172
+ *
173
+ * This can be useful for example if you want to hard code a
174
+ * namespace or similar on the generated entities.
175
+ */
176
+ set?: { [key: string]: JsonValue };
177
+ /**
178
+ * Mappings from well known entity fields, to LDAP attribute names
179
+ */
180
+ map?: {
181
+ /**
182
+ * The name of the attribute that holds the relative
183
+ * distinguished name of each entry. Defaults to "cn".
184
+ */
185
+ rdn?: string;
186
+ /**
187
+ * The name of the attribute that shall be used for the value of
188
+ * the metadata.name field of the entity. Defaults to "cn".
189
+ */
190
+ name?: string;
191
+ /**
192
+ * The name of the attribute that shall be used for the value of
193
+ * the metadata.description field of the entity. Defaults to
194
+ * "description".
195
+ */
196
+ description?: string;
197
+ /**
198
+ * The name of the attribute that shall be used for the value of
199
+ * the spec.type field of the entity. Defaults to "groupType".
200
+ */
201
+ type?: string;
202
+ /**
203
+ * The name of the attribute that shall be used for the value of
204
+ * the spec.profile.displayName field of the entity. Defaults to
205
+ * "cn".
206
+ */
207
+ displayName?: string;
208
+ /**
209
+ * The name of the attribute that shall be used for the value of
210
+ * the spec.profile.email field of the entity.
211
+ */
212
+ email?: string;
213
+ /**
214
+ * The name of the attribute that shall be used for the value of
215
+ * the spec.profile.picture field of the entity.
216
+ */
217
+ picture?: string;
218
+ /**
219
+ * The name of the attribute that shall be used for the values of
220
+ * the spec.parent field of the entity. Defaults to "memberOf".
221
+ */
222
+ memberOf?: string;
223
+ /**
224
+ * The name of the attribute that shall be used for the values of
225
+ * the spec.children field of the entity. Defaults to "member".
226
+ */
227
+ members?: string;
228
+ };
229
+ };
230
+ }>;
231
+ };
232
+
20
233
  /**
21
234
  * Configuration options for the catalog plugin.
235
+ *
236
+ * TODO(freben): Deprecate this entire block
22
237
  */
23
238
  catalog?: {
24
239
  /**
@@ -79,6 +294,10 @@ export interface Config {
79
294
  scope?: 'base' | 'one' | 'sub';
80
295
  filter?: string;
81
296
  attributes?: string | string[];
297
+ sizeLimit?: number;
298
+ timeLimit?: number;
299
+ derefAliases?: number;
300
+ typesOnly?: boolean;
82
301
  paged?:
83
302
  | boolean
84
303
  | {
@@ -159,6 +378,10 @@ export interface Config {
159
378
  scope?: 'base' | 'one' | 'sub';
160
379
  filter?: string;
161
380
  attributes?: string | string[];
381
+ sizeLimit?: number;
382
+ timeLimit?: number;
383
+ derefAliases?: number;
384
+ typesOnly?: boolean;
162
385
  paged?:
163
386
  | boolean
164
387
  | {
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 errors = require('@backstage/errors');
8
9
  var ldap = require('ldapjs');
9
10
  var mergeWith = require('lodash/mergeWith');
@@ -13,6 +14,25 @@ var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
13
14
 
14
15
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
15
16
 
17
+ function _interopNamespace(e) {
18
+ if (e && e.__esModule) return e;
19
+ var n = Object.create(null);
20
+ if (e) {
21
+ Object.keys(e).forEach(function (k) {
22
+ if (k !== 'default') {
23
+ var d = Object.getOwnPropertyDescriptor(e, k);
24
+ Object.defineProperty(n, k, d.get ? d : {
25
+ enumerable: true,
26
+ get: function () { return e[k]; }
27
+ });
28
+ }
29
+ });
30
+ }
31
+ n["default"] = e;
32
+ return Object.freeze(n);
33
+ }
34
+
35
+ var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
16
36
  var ldap__default = /*#__PURE__*/_interopDefaultLegacy(ldap);
17
37
  var mergeWith__default = /*#__PURE__*/_interopDefaultLegacy(mergeWith);
18
38
  var lodashSet__default = /*#__PURE__*/_interopDefaultLegacy(lodashSet);
@@ -116,7 +136,7 @@ class LdapClient {
116
136
  return;
117
137
  }
118
138
  res.on("searchReference", () => {
119
- reject(new Error("Unable to handle referral"));
139
+ this.logger.warn("Received unsupported search referral");
120
140
  });
121
141
  res.on("searchEntry", (entry) => {
122
142
  output.push(entry);
@@ -155,7 +175,7 @@ class LdapClient {
155
175
  reject(new Error(errorString(err)));
156
176
  }
157
177
  res.on("searchReference", () => {
158
- reject(new Error("Unable to handle referral"));
178
+ this.logger.warn("Received unsupported search referral");
159
179
  });
160
180
  res.on("searchEntry", (entry) => {
161
181
  f(entry);
@@ -264,6 +284,10 @@ function readLdapConfig(config) {
264
284
  scope: c.getOptionalString("scope"),
265
285
  filter: formatFilter(c.getOptionalString("filter")),
266
286
  attributes: c.getOptionalStringArray("attributes"),
287
+ sizeLimit: c.getOptionalNumber("sizeLimit"),
288
+ timeLimit: c.getOptionalNumber("timeLimit"),
289
+ derefAliases: c.getOptionalNumber("derefAliases"),
290
+ typesOnly: c.getOptionalBoolean("typesOnly"),
267
291
  ...paged !== void 0 ? { paged } : void 0
268
292
  };
269
293
  }
@@ -657,41 +681,44 @@ class LdapOrgEntityProvider {
657
681
  }
658
682
  static fromConfig(configRoot, options) {
659
683
  const config = configRoot.getOptionalConfig("ldap") || configRoot.getOptionalConfig("catalog.processors.ldapOrg");
660
- if (!config) {
661
- throw new TypeError(`There is no LDAP configuration. Please add it as "ldap.providers".`);
662
- }
663
- const providers = readLdapConfig(config);
684
+ const providers = config ? readLdapConfig(config) : [];
664
685
  const provider = providers.find((p) => options.target === p.target);
665
686
  if (!provider) {
666
- throw new TypeError(`There is no LDAP configuration that matches ${options.target}. Please add a configuration entry for it under "ldap.providers".`);
687
+ throw new TypeError(`There is no LDAP configuration that matches "${options.target}". Please add a configuration entry for it under "ldap.providers".`);
667
688
  }
668
689
  const logger = options.logger.child({
669
690
  target: options.target
670
691
  });
671
- return new LdapOrgEntityProvider({
692
+ const result = new LdapOrgEntityProvider({
672
693
  id: options.id,
673
694
  provider,
674
695
  userTransformer: options.userTransformer,
675
696
  groupTransformer: options.groupTransformer,
676
697
  logger
677
698
  });
699
+ result.schedule(options.schedule);
700
+ return result;
678
701
  }
679
702
  getProviderName() {
680
703
  return `LdapOrgEntityProvider:${this.options.id}`;
681
704
  }
682
705
  async connect(connection) {
706
+ var _a;
683
707
  this.connection = connection;
708
+ await ((_a = this.scheduleFn) == null ? void 0 : _a.call(this));
684
709
  }
685
- async read() {
710
+ async read(options) {
711
+ var _a;
686
712
  if (!this.connection) {
687
713
  throw new Error("Not initialized");
688
714
  }
689
- const { markReadComplete } = trackProgress(this.options.logger);
715
+ const logger = (_a = options == null ? void 0 : options.logger) != null ? _a : this.options.logger;
716
+ const { markReadComplete } = trackProgress(logger);
690
717
  const client = await LdapClient.create(this.options.logger, this.options.provider.target, this.options.provider.bind);
691
718
  const { users, groups } = await readLdapOrg(client, this.options.provider.users, this.options.provider.groups, {
692
719
  groupTransformer: this.options.groupTransformer,
693
720
  userTransformer: this.options.userTransformer,
694
- logger: this.options.logger
721
+ logger
695
722
  });
696
723
  const { markCommitComplete } = markReadComplete({ users, groups });
697
724
  await this.connection.applyMutation({
@@ -703,6 +730,29 @@ class LdapOrgEntityProvider {
703
730
  });
704
731
  markCommitComplete();
705
732
  }
733
+ schedule(schedule) {
734
+ if (schedule === "manual") {
735
+ return;
736
+ }
737
+ this.scheduleFn = async () => {
738
+ const id = `${this.getProviderName()}:refresh`;
739
+ await schedule.run({
740
+ id,
741
+ fn: async () => {
742
+ const logger = this.options.logger.child({
743
+ class: LdapOrgEntityProvider.prototype.constructor.name,
744
+ taskId: id,
745
+ taskInstanceId: uuid__namespace.v4()
746
+ });
747
+ try {
748
+ await this.read({ logger });
749
+ } catch (error) {
750
+ logger.error(error);
751
+ }
752
+ }
753
+ });
754
+ };
755
+ }
706
756
  }
707
757
  function trackProgress(logger) {
708
758
  let timestamp = Date.now();
@@ -736,11 +786,11 @@ function withLocations(providerId, entity) {
736
786
  }
737
787
 
738
788
  class LdapOrgReaderProcessor {
739
- static fromConfig(config, options) {
740
- const c = config.getOptionalConfig("catalog.processors.ldapOrg");
789
+ static fromConfig(configRoot, options) {
790
+ const config = configRoot.getOptionalConfig("ldap") || configRoot.getOptionalConfig("catalog.processors.ldapOrg");
741
791
  return new LdapOrgReaderProcessor({
742
792
  ...options,
743
- providers: c ? readLdapConfig(c) : []
793
+ providers: config ? readLdapConfig(config) : []
744
794
  });
745
795
  }
746
796
  constructor(options) {
@@ -758,7 +808,7 @@ class LdapOrgReaderProcessor {
758
808
  }
759
809
  const provider = this.providers.find((p) => location.target === p.target);
760
810
  if (!provider) {
761
- throw new Error(`There is no LDAP Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.ldapOrg.providers.`);
811
+ throw new Error(`There is no LDAP configuration that matches "${location.target}". Please add a configuration entry for it under "ldap.providers".`);
762
812
  }
763
813
  const startTimestamp = Date.now();
764
814
  this.logger.info("Reading LDAP users and groups");
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/ldap/util.ts","../src/ldap/vendors.ts","../src/ldap/client.ts","../src/ldap/config.ts","../src/ldap/constants.ts","../src/ldap/org.ts","../src/ldap/read.ts","../src/processors/LdapOrgEntityProvider.ts","../src/processors/LdapOrgReaderProcessor.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 { Error as LDAPError, SearchEntry } from 'ldapjs';\nimport { LdapVendor } from './vendors';\n\n/**\n * Builds a string form of an LDAP Error structure.\n *\n * @param error - The error\n */\nexport function errorString(error: LDAPError) {\n return `${error.code} ${error.name}: ${error.message}`;\n}\n\n/**\n * Maps a single-valued attribute to a consumer.\n *\n * This helper can be useful when implementing a user or group transformer.\n *\n * @param entry - The LDAP source entry\n * @param vendor - The LDAP vendor\n * @param attributeName - The source attribute to map. If the attribute is\n * undefined the mapping will be silently ignored.\n * @param setter - The function to be called with the decoded attribute from the\n * source entry\n *\n * @public\n */\nexport function mapStringAttr(\n entry: SearchEntry,\n vendor: LdapVendor,\n attributeName: string | undefined,\n setter: (value: string) => void,\n) {\n if (attributeName) {\n const values = vendor.decodeStringAttribute(entry, attributeName);\n if (values && values.length === 1) {\n setter(values[0]);\n }\n }\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 { SearchEntry } from 'ldapjs';\n\n/**\n * An LDAP Vendor handles unique nuances between different vendors.\n *\n * @public\n */\nexport type LdapVendor = {\n /**\n * The attribute name that holds the distinguished name (DN) for an entry.\n */\n dnAttributeName: string;\n /**\n * The attribute name that holds a universal unique identifier for an entry.\n */\n uuidAttributeName: string;\n /**\n * Decode ldap entry values for a given attribute name to their string representation.\n *\n * @param entry - The ldap entry\n * @param name - The attribute to decode\n */\n decodeStringAttribute: (entry: SearchEntry, name: string) => string[];\n};\n\nexport const DefaultLdapVendor: LdapVendor = {\n dnAttributeName: 'entryDN',\n uuidAttributeName: 'entryUUID',\n decodeStringAttribute: (entry, name) => {\n return decode(entry, name, value => {\n return value.toString();\n });\n },\n};\n\nexport const ActiveDirectoryVendor: LdapVendor = {\n dnAttributeName: 'distinguishedName',\n uuidAttributeName: 'objectGUID',\n decodeStringAttribute: (entry, name) => {\n const decoder = (value: string | Buffer) => {\n if (name === ActiveDirectoryVendor.uuidAttributeName) {\n return formatGUID(value);\n }\n return value.toString();\n };\n return decode(entry, name, decoder);\n },\n};\n\n// Decode an attribute to a consumer\nfunction decode(\n entry: SearchEntry,\n attributeName: string,\n decoder: (value: string | Buffer) => string,\n): string[] {\n const values = entry.raw[attributeName];\n if (Array.isArray(values)) {\n return values.map(v => {\n return decoder(v);\n });\n } else if (values) {\n return [decoder(values)];\n }\n return [];\n}\n\n// Formats a Microsoft Active Directory binary-encoded uuid to a readable string\n// See https://github.com/ldapjs/node-ldapjs/issues/297#issuecomment-137765214\nfunction formatGUID(objectGUID: string | Buffer): string {\n let data: Buffer;\n if (typeof objectGUID === 'string') {\n data = new Buffer(objectGUID, 'binary');\n } else {\n data = objectGUID;\n }\n // GUID_FORMAT_D\n let template = '{3}{2}{1}{0}-{5}{4}-{7}{6}-{8}{9}-{10}{11}{12}{13}{14}{15}';\n\n // check each byte\n for (let i = 0; i < data.length; i++) {\n let dataStr = data[i].toString(16);\n dataStr = data[i] >= 16 ? dataStr : `0${dataStr}`;\n\n // insert that character into the template\n template = template.replace(`{${i}}`, dataStr);\n }\n return template;\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 { ForwardedError } from '@backstage/errors';\nimport ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs';\nimport { cloneDeep } from 'lodash';\nimport { Logger } from 'winston';\nimport { BindConfig } from './config';\nimport { errorString } from './util';\nimport {\n ActiveDirectoryVendor,\n DefaultLdapVendor,\n LdapVendor,\n} from './vendors';\n\n/**\n * Basic wrapper for the `ldapjs` library.\n *\n * Helps out with promisifying calls, paging, binding etc.\n *\n * @public\n */\nexport class LdapClient {\n private vendor: Promise<LdapVendor> | undefined;\n\n static async create(\n logger: Logger,\n target: string,\n bind?: BindConfig,\n ): Promise<LdapClient> {\n const client = ldap.createClient({ url: target });\n\n // We want to have a catch-all error handler at the top, since the default\n // behavior of the client is to blow up the entire process when it fails,\n // unless an error handler is set.\n client.on('error', (err: ldap.Error) => {\n logger.warn(`LDAP client threw an error, ${errorString(err)}`);\n });\n\n if (!bind) {\n return new LdapClient(client, logger);\n }\n\n return new Promise<LdapClient>((resolve, reject) => {\n const { dn, secret } = bind;\n client.bind(dn, secret, err => {\n if (err) {\n reject(`LDAP bind failed for ${dn}, ${errorString(err)}`);\n } else {\n resolve(new LdapClient(client, logger));\n }\n });\n });\n }\n\n constructor(\n private readonly client: Client,\n private readonly logger: Logger,\n ) {}\n\n /**\n * Performs an LDAP search operation.\n *\n * @param dn - The fully qualified base DN to search within\n * @param options - The search options\n */\n async search(dn: string, options: SearchOptions): Promise<SearchEntry[]> {\n try {\n const output: SearchEntry[] = [];\n\n const logInterval = setInterval(() => {\n this.logger.debug(`Read ${output.length} LDAP entries so far...`);\n }, 5000);\n\n const search = new Promise<SearchEntry[]>((resolve, reject) => {\n // Note that we clone the (frozen) options, since ldapjs rudely tries to\n // overwrite parts of them\n this.client.search(dn, cloneDeep(options), (err, res) => {\n if (err) {\n reject(new Error(errorString(err)));\n return;\n }\n\n res.on('searchReference', () => {\n reject(new Error('Unable to handle referral'));\n });\n\n res.on('searchEntry', entry => {\n output.push(entry);\n });\n\n res.on('error', e => {\n reject(new Error(errorString(e)));\n });\n\n res.on('page', (_result, cb) => {\n if (cb) {\n cb();\n }\n });\n\n res.on('end', r => {\n if (!r) {\n reject(new Error('Null response'));\n } else if (r.status !== 0) {\n reject(new Error(`Got status ${r.status}: ${r.errorMessage}`));\n } else {\n resolve(output);\n }\n });\n });\n });\n\n return await search.finally(() => {\n clearInterval(logInterval);\n });\n } catch (e) {\n throw new ForwardedError(`LDAP search at DN \"${dn}\" failed`, e);\n }\n }\n\n /**\n * Performs an LDAP search operation, calls a function on each entry to limit memory usage\n *\n * @param dn - The fully qualified base DN to search within\n * @param options - The search options\n * @param f - The callback to call on each search entry\n */\n async searchStreaming(\n dn: string,\n options: SearchOptions,\n f: (entry: SearchEntry) => void,\n ): Promise<void> {\n try {\n return await new Promise<void>((resolve, reject) => {\n // Note that we clone the (frozen) options, since ldapjs rudely tries to\n // overwrite parts of them\n this.client.search(dn, cloneDeep(options), (err, res) => {\n if (err) {\n reject(new Error(errorString(err)));\n }\n\n res.on('searchReference', () => {\n reject(new Error('Unable to handle referral'));\n });\n\n res.on('searchEntry', entry => {\n f(entry);\n });\n\n res.on('error', e => {\n reject(new Error(errorString(e)));\n });\n\n res.on('end', r => {\n if (!r) {\n throw new Error('Null response');\n } else if (r.status !== 0) {\n throw new Error(`Got status ${r.status}: ${r.errorMessage}`);\n } else {\n resolve();\n }\n });\n });\n });\n } catch (e) {\n throw new ForwardedError(`LDAP search at DN \"${dn}\" failed`, e);\n }\n }\n\n /**\n * Get the Server Vendor.\n * Currently only detects Microsoft Active Directory Servers.\n *\n * @see https://ldapwiki.com/wiki/Determine%20LDAP%20Server%20Vendor\n */\n async getVendor(): Promise<LdapVendor> {\n if (this.vendor) {\n return this.vendor;\n }\n this.vendor = this.getRootDSE()\n .then(root => {\n if (root && root.raw?.forestFunctionality) {\n return ActiveDirectoryVendor;\n }\n return DefaultLdapVendor;\n })\n .catch(err => {\n this.vendor = undefined;\n throw err;\n });\n return this.vendor;\n }\n\n /**\n * Get the Root DSE.\n *\n * @see https://ldapwiki.com/wiki/RootDSE\n */\n async getRootDSE(): Promise<SearchEntry | undefined> {\n const result = await this.search('', {\n scope: 'base',\n filter: '(objectclass=*)',\n } as SearchOptions);\n if (result && result.length === 1) {\n return result[0];\n }\n return undefined;\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 { JsonValue } from '@backstage/types';\nimport { SearchOptions } from 'ldapjs';\nimport mergeWith from 'lodash/mergeWith';\nimport { RecursivePartial } from '@backstage/plugin-catalog-backend';\nimport { trimEnd } from 'lodash';\n\n/**\n * The configuration parameters for a single LDAP provider.\n *\n * @public\n */\nexport type LdapProviderConfig = {\n // The prefix of the target that this matches on, e.g.\n // \"ldaps://ds.example.net\", with no trailing slash.\n target: string;\n // The settings to use for the bind command. If none are specified, the bind\n // command is not issued.\n bind?: BindConfig;\n // The settings that govern the reading and interpretation of users\n users: UserConfig;\n // The settings that govern the reading and interpretation of groups\n groups: GroupConfig;\n};\n\n/**\n * The settings to use for the a command.\n *\n * @public\n */\nexport type BindConfig = {\n // The DN of the user to auth as, e.g.\n // uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net\n dn: string;\n // The secret of the user to auth as (its password)\n secret: string;\n};\n\n/**\n * The settings that govern the reading and interpretation of users.\n *\n * @public\n */\nexport type UserConfig = {\n // The DN under which users are stored.\n dn: string;\n // The search options to use.\n // Only the scope, filter, attributes, and paged fields are supported. The\n // default is scope \"one\" and attributes \"*\" and \"+\".\n options: SearchOptions;\n // JSON paths (on a.b.c form) and hard coded values to set on those paths\n set?: { [path: string]: JsonValue };\n // Mappings from well known entity fields, to LDAP attribute names\n map: {\n // The name of the attribute that holds the relative distinguished name of\n // each entry. Defaults to \"uid\".\n rdn: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.name field of the entity. Defaults to \"uid\".\n name: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.description field of the entity.\n description?: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.displayName field of the entity. Defaults to \"cn\".\n displayName: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.email field of the entity. Defaults to \"mail\".\n email: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.picture field of the entity.\n picture?: string;\n // The name of the attribute that shall be used for the values of the\n // spec.memberOf field of the entity. Defaults to \"memberOf\".\n memberOf: string;\n };\n};\n\n/**\n * The settings that govern the reading and interpretation of groups.\n *\n * @public\n */\nexport type GroupConfig = {\n // The DN under which groups are stored.\n dn: string;\n // The search options to use.\n // Only the scope, filter, attributes, and paged fields are supported.\n options: SearchOptions;\n // JSON paths (on a.b.c form) and hard coded values to set on those paths\n set?: { [path: string]: JsonValue };\n // Mappings from well known entity fields, to LDAP attribute names\n map: {\n // The name of the attribute that holds the relative distinguished name of\n // each entry. Defaults to \"cn\".\n rdn: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.name field of the entity. Defaults to \"cn\".\n name: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.description field of the entity. Defaults to \"description\".\n description: string;\n // The name of the attribute that shall be used for the value of the\n // spec.type field of the entity. Defaults to \"groupType\".\n type: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.displayName field of the entity. Defaults to \"cn\".\n displayName: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.email field of the entity.\n email?: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.picture field of the entity.\n picture?: string;\n // The name of the attribute that shall be used for the values of the\n // spec.parent field of the entity. Defaults to \"memberOf\".\n memberOf: string;\n // The name of the attribute that shall be used for the values of the\n // spec.children field of the entity. Defaults to \"member\".\n members: string;\n };\n};\n\nconst defaultConfig = {\n users: {\n options: {\n scope: 'one',\n attributes: ['*', '+'],\n },\n map: {\n rdn: 'uid',\n name: 'uid',\n displayName: 'cn',\n email: 'mail',\n memberOf: 'memberOf',\n },\n },\n groups: {\n options: {\n scope: 'one',\n attributes: ['*', '+'],\n },\n map: {\n rdn: 'cn',\n name: 'cn',\n description: 'description',\n displayName: 'cn',\n type: 'groupType',\n memberOf: 'memberOf',\n members: 'member',\n },\n },\n};\n\n/**\n * Parses configuration.\n *\n * @param config - The root of the LDAP config hierarchy\n *\n * @public\n */\nexport function readLdapConfig(config: Config): LdapProviderConfig[] {\n function freeze<T>(data: T): T {\n return JSON.parse(JSON.stringify(data), (_key, value) => {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n }\n return value;\n });\n }\n\n function readBindConfig(\n c: Config | undefined,\n ): LdapProviderConfig['bind'] | undefined {\n if (!c) {\n return undefined;\n }\n return {\n dn: c.getString('dn'),\n secret: c.getString('secret'),\n };\n }\n\n function readOptionsConfig(c: Config | undefined): SearchOptions {\n if (!c) {\n return {};\n }\n\n const paged = readOptionsPagedConfig(c);\n\n return {\n scope: c.getOptionalString('scope') as SearchOptions['scope'],\n filter: formatFilter(c.getOptionalString('filter')),\n attributes: c.getOptionalStringArray('attributes'),\n ...(paged !== undefined ? { paged } : undefined),\n };\n }\n\n function readOptionsPagedConfig(c: Config): SearchOptions['paged'] {\n const pagedConfig = c.getOptional('paged');\n if (pagedConfig === undefined) {\n return undefined;\n }\n\n if (pagedConfig === true || pagedConfig === false) {\n return pagedConfig;\n }\n\n const pageSize = c.getOptionalNumber('paged.pageSize');\n const pagePause = c.getOptionalBoolean('paged.pagePause');\n return {\n ...(pageSize !== undefined ? { pageSize } : undefined),\n ...(pagePause !== undefined ? { pagePause } : undefined),\n };\n }\n\n function readSetConfig(\n c: Config | undefined,\n ): { [path: string]: JsonValue } | undefined {\n if (!c) {\n return undefined;\n }\n return c.get();\n }\n\n function readUserMapConfig(\n c: Config | undefined,\n ): Partial<LdapProviderConfig['users']['map']> {\n if (!c) {\n return {};\n }\n\n return {\n rdn: c.getOptionalString('rdn'),\n name: c.getOptionalString('name'),\n description: c.getOptionalString('description'),\n displayName: c.getOptionalString('displayName'),\n email: c.getOptionalString('email'),\n picture: c.getOptionalString('picture'),\n memberOf: c.getOptionalString('memberOf'),\n };\n }\n\n function readGroupMapConfig(\n c: Config | undefined,\n ): Partial<LdapProviderConfig['groups']['map']> {\n if (!c) {\n return {};\n }\n\n return {\n rdn: c.getOptionalString('rdn'),\n name: c.getOptionalString('name'),\n description: c.getOptionalString('description'),\n type: c.getOptionalString('type'),\n displayName: c.getOptionalString('displayName'),\n email: c.getOptionalString('email'),\n picture: c.getOptionalString('picture'),\n memberOf: c.getOptionalString('memberOf'),\n members: c.getOptionalString('members'),\n };\n }\n\n function readUserConfig(\n c: Config,\n ): RecursivePartial<LdapProviderConfig['users']> {\n return {\n dn: c.getString('dn'),\n options: readOptionsConfig(c.getOptionalConfig('options')),\n set: readSetConfig(c.getOptionalConfig('set')),\n map: readUserMapConfig(c.getOptionalConfig('map')),\n };\n }\n\n function readGroupConfig(\n c: Config,\n ): RecursivePartial<LdapProviderConfig['groups']> {\n return {\n dn: c.getString('dn'),\n options: readOptionsConfig(c.getOptionalConfig('options')),\n set: readSetConfig(c.getOptionalConfig('set')),\n map: readGroupMapConfig(c.getOptionalConfig('map')),\n };\n }\n\n function formatFilter(filter?: string): string | undefined {\n // Remove extra whitespace between blocks to support multiline filters from the configuration\n return filter?.replace(/\\s*(\\(|\\))/g, '$1')?.trim();\n }\n\n const providerConfigs = config.getOptionalConfigArray('providers') ?? [];\n return providerConfigs.map(c => {\n const newConfig = {\n target: trimEnd(c.getString('target'), '/'),\n bind: readBindConfig(c.getOptionalConfig('bind')),\n users: readUserConfig(c.getConfig('users')),\n groups: readGroupConfig(c.getConfig('groups')),\n };\n const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => {\n // Replace arrays instead of merging, otherwise default behavior\n return Array.isArray(from) ? from : undefined;\n });\n return freeze(merged) as LdapProviderConfig;\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\n/**\n * The name of an entity annotation, that references the RDN of the LDAP object\n * it was ingested from.\n *\n * The RDN is the name of the leftmost attribute that identifies the item; for\n * example, for an item with the fully qualified DN\n * uid=john,ou=people,ou=spotify,dc=spotify,dc=net the generated entity would\n * have this annotation, with the value \"john\".\n *\n * @public\n */\nexport const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn';\n\n/**\n * The name of an entity annotation, that references the DN of the LDAP object\n * it was ingested from.\n *\n * The DN is the fully qualified name that identifies the item; for example,\n * for an item with the DN uid=john,ou=people,ou=spotify,dc=spotify,dc=net the\n * generated entity would have this annotation, with that full string as its\n * value.\n *\n * @public\n */\nexport const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn';\n\n/**\n * The name of an entity annotation, that references the UUID of the LDAP\n * object it was ingested from.\n *\n * The UUID is the globally unique ID that identifies the item; for example,\n * for an item with the UUID 76ef928a-b251-1037-9840-d78227f36a7e, the\n * generated entity would have this annotation, with that full string as its\n * value.\n *\n * @public\n */\nexport const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid';\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 { GroupEntity, UserEntity } from '@backstage/catalog-model';\nimport { SearchEntry } from 'ldapjs';\nimport lodashSet from 'lodash/set';\nimport cloneDeep from 'lodash/cloneDeep';\nimport { buildOrgHierarchy } from './org';\nimport { LdapClient } from './client';\nimport { GroupConfig, UserConfig } from './config';\nimport {\n LDAP_DN_ANNOTATION,\n LDAP_RDN_ANNOTATION,\n LDAP_UUID_ANNOTATION,\n} from './constants';\nimport { LdapVendor } from './vendors';\nimport { Logger } from 'winston';\nimport { GroupTransformer, UserTransformer } from './types';\nimport { mapStringAttr } from './util';\n\n/**\n * The default implementation of the transformation from an LDAP entry to a\n * User entity.\n *\n * @public\n */\nexport async function defaultUserTransformer(\n vendor: LdapVendor,\n config: UserConfig,\n entry: SearchEntry,\n): Promise<UserEntity | undefined> {\n const { set, map } = config;\n\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'User',\n metadata: {\n name: '',\n annotations: {},\n },\n spec: {\n profile: {},\n memberOf: [],\n },\n };\n\n if (set) {\n for (const [path, value] of Object.entries(set)) {\n lodashSet(entity, path, cloneDeep(value));\n }\n }\n\n mapStringAttr(entry, vendor, map.name, v => {\n entity.metadata.name = v;\n });\n mapStringAttr(entry, vendor, map.description, v => {\n entity.metadata.description = v;\n });\n mapStringAttr(entry, vendor, map.rdn, v => {\n entity.metadata.annotations![LDAP_RDN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.uuidAttributeName, v => {\n entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {\n entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, map.displayName, v => {\n entity.spec.profile!.displayName = v;\n });\n mapStringAttr(entry, vendor, map.email, v => {\n entity.spec.profile!.email = v;\n });\n mapStringAttr(entry, vendor, map.picture, v => {\n entity.spec.profile!.picture = v;\n });\n\n return entity;\n}\n\n/**\n * Reads users out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param config - The user data configuration\n * @param opts - Additional options\n */\nexport async function readLdapUsers(\n client: LdapClient,\n config: UserConfig,\n opts?: { transformer?: UserTransformer },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n userMemberOf: Map<string, Set<string>>; // DN -> DN or UUID of groups\n}> {\n const { dn, options, map } = config;\n const vendor = await client.getVendor();\n\n const entities: UserEntity[] = [];\n const userMemberOf: Map<string, Set<string>> = new Map();\n\n const transformer = opts?.transformer ?? defaultUserTransformer;\n\n await client.searchStreaming(dn, options, async user => {\n const entity = await transformer(vendor, config, user);\n\n if (!entity) {\n return;\n }\n\n mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => {\n ensureItems(userMemberOf, myDn, vs);\n });\n\n entities.push(entity);\n });\n\n return { users: entities, userMemberOf };\n}\n\n/**\n * The default implementation of the transformation from an LDAP entry to a\n * Group entity.\n *\n * @public\n */\nexport async function defaultGroupTransformer(\n vendor: LdapVendor,\n config: GroupConfig,\n entry: SearchEntry,\n): Promise<GroupEntity | undefined> {\n const { set, map } = config;\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'Group',\n metadata: {\n name: '',\n annotations: {},\n },\n spec: {\n type: 'unknown',\n profile: {},\n children: [],\n },\n };\n\n if (set) {\n for (const [path, value] of Object.entries(set)) {\n lodashSet(entity, path, cloneDeep(value));\n }\n }\n\n mapStringAttr(entry, vendor, map.name, v => {\n entity.metadata.name = v;\n });\n mapStringAttr(entry, vendor, map.description, v => {\n entity.metadata.description = v;\n });\n mapStringAttr(entry, vendor, map.rdn, v => {\n entity.metadata.annotations![LDAP_RDN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.uuidAttributeName, v => {\n entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {\n entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, map.type, v => {\n entity.spec.type = v;\n });\n mapStringAttr(entry, vendor, map.displayName, v => {\n entity.spec.profile!.displayName = v;\n });\n mapStringAttr(entry, vendor, map.email, v => {\n entity.spec.profile!.email = v;\n });\n mapStringAttr(entry, vendor, map.picture, v => {\n entity.spec.profile!.picture = v;\n });\n\n return entity;\n}\n\n/**\n * Reads groups out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param config - The group data configuration\n * @param opts - Additional options\n */\nexport async function readLdapGroups(\n client: LdapClient,\n config: GroupConfig,\n opts?: {\n transformer?: GroupTransformer;\n },\n): Promise<{\n groups: GroupEntity[]; // With all relations empty\n groupMemberOf: Map<string, Set<string>>; // DN -> DN or UUID of groups\n groupMember: Map<string, Set<string>>; // DN -> DN or UUID of groups & users\n}> {\n const groups: GroupEntity[] = [];\n const groupMemberOf: Map<string, Set<string>> = new Map();\n const groupMember: Map<string, Set<string>> = new Map();\n\n const { dn, map, options } = config;\n const vendor = await client.getVendor();\n\n const transformer = opts?.transformer ?? defaultGroupTransformer;\n\n await client.searchStreaming(dn, options, async entry => {\n if (!entry) {\n return;\n }\n\n const entity = await transformer(vendor, config, entry);\n\n if (!entity) {\n return;\n }\n\n mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => {\n ensureItems(groupMemberOf, myDn, vs);\n });\n mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => {\n ensureItems(groupMember, myDn, vs);\n });\n\n groups.push(entity);\n });\n\n return {\n groups,\n groupMemberOf,\n groupMember,\n };\n}\n\n/**\n * Reads users and groups out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param userConfig - The user data configuration\n * @param groupConfig - The group data configuration\n * @param options - Additional options\n *\n * @public\n */\nexport async function readLdapOrg(\n client: LdapClient,\n userConfig: UserConfig,\n groupConfig: GroupConfig,\n options: {\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[];\n groups: GroupEntity[];\n}> {\n // Invokes the above \"raw\" read functions and stitches together the results\n // with all relations etc filled in.\n\n const { users, userMemberOf } = await readLdapUsers(client, userConfig, {\n transformer: options?.userTransformer,\n });\n const { groups, groupMemberOf, groupMember } = await readLdapGroups(\n client,\n groupConfig,\n { transformer: options?.groupTransformer },\n );\n\n resolveRelations(groups, users, userMemberOf, groupMemberOf, groupMember);\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\n//\n// Helpers\n//\n\n// Maps a multi-valued attribute of references to other objects, to a consumer\nfunction mapReferencesAttr(\n entry: SearchEntry,\n vendor: LdapVendor,\n attributeName: string | undefined,\n setter: (sourceDn: string, targets: string[]) => void,\n) {\n if (attributeName) {\n const values = vendor.decodeStringAttribute(entry, attributeName);\n const dn = vendor.decodeStringAttribute(entry, vendor.dnAttributeName);\n if (values && dn && dn.length === 1) {\n setter(dn[0], values);\n }\n }\n}\n\n// Inserts a number of values in a key-values mapping\nfunction ensureItems(\n target: Map<string, Set<string>>,\n key: string,\n values: string[],\n) {\n if (key) {\n let set = target.get(key);\n if (!set) {\n set = new Set();\n target.set(key, set);\n }\n for (const value of values) {\n if (value) {\n set!.add(value);\n }\n }\n }\n}\n\n/**\n * Takes groups and entities with empty relations, and fills in the various\n * relations that were returned by the readers, and forms the org hierarchy.\n *\n * @param groups - Group entities with empty relations; modified in place\n * @param users - User entities with empty relations; modified in place\n * @param userMemberOf - For a user DN, the set of group DNs or UUIDs that the\n * user is a member of\n * @param groupMemberOf - For a group DN, the set of group DNs or UUIDs that\n * the group is a member of (parents in the hierarchy)\n * @param groupMember - For a group DN, the set of group DNs or UUIDs that are\n * members of the group (children in the hierarchy)\n */\nexport function resolveRelations(\n groups: GroupEntity[],\n users: UserEntity[],\n userMemberOf: Map<string, Set<string>>,\n groupMemberOf: Map<string, Set<string>>,\n groupMember: Map<string, Set<string>>,\n) {\n // Build reference lookup tables - all of the relations that are output from\n // the above calls can be expressed as either DNs or UUIDs so we need to be\n // able to find by both, as well as the name. Note that we expect them to not\n // collide here - this is a reasonable assumption as long as the fields are\n // the supported forms.\n const userMap: Map<string, UserEntity> = new Map(); // by name, dn, uuid\n const groupMap: Map<string, GroupEntity> = new Map(); // by name, dn, uuid\n for (const user of users) {\n userMap.set(user.metadata.name, user);\n userMap.set(user.metadata.annotations![LDAP_DN_ANNOTATION], user);\n userMap.set(user.metadata.annotations![LDAP_UUID_ANNOTATION], user);\n }\n for (const group of groups) {\n groupMap.set(group.metadata.name, group);\n groupMap.set(group.metadata.annotations![LDAP_DN_ANNOTATION], group);\n groupMap.set(group.metadata.annotations![LDAP_UUID_ANNOTATION], group);\n }\n\n // This can happen e.g. if entryUUID wasn't returned by the server\n userMap.delete('');\n groupMap.delete('');\n userMap.delete(undefined!);\n groupMap.delete(undefined!);\n\n // Fill in all of the immediate relations, now keyed on metadata.name. We\n // keep all parents at this point, whether the target model can support more\n // than one or not (it gets filtered farther down). And group children are\n // only groups in here.\n const newUserMemberOf: Map<string, Set<string>> = new Map();\n const newGroupParents: Map<string, Set<string>> = new Map();\n const newGroupChildren: Map<string, Set<string>> = new Map();\n\n // Resolve and store in the intermediaries. It may seem redundant that the\n // input data has both parent and children directions, as well as both\n // user->group and group->user - the reason is that different LDAP schemas\n // express relations in different directions. Some may have a user memberOf\n // overlay, some don't, for example.\n for (const [userN, groupsN] of userMemberOf.entries()) {\n const user = userMap.get(userN);\n if (user) {\n for (const groupN of groupsN) {\n const group = groupMap.get(groupN);\n if (group) {\n ensureItems(newUserMemberOf, user.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n for (const [groupN, parentsN] of groupMemberOf.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n for (const parentN of parentsN) {\n const parentGroup = groupMap.get(parentN);\n if (parentGroup) {\n ensureItems(newGroupParents, group.metadata.name, [\n parentGroup.metadata.name,\n ]);\n ensureItems(newGroupChildren, parentGroup.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n for (const [groupN, membersN] of groupMember.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n for (const memberN of membersN) {\n // Group members can be both users and groups in the input model, so\n // try both\n const memberUser = userMap.get(memberN);\n if (memberUser) {\n ensureItems(newUserMemberOf, memberUser.metadata.name, [\n group.metadata.name,\n ]);\n } else {\n const memberGroup = groupMap.get(memberN);\n if (memberGroup) {\n ensureItems(newGroupChildren, group.metadata.name, [\n memberGroup.metadata.name,\n ]);\n ensureItems(newGroupParents, memberGroup.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n }\n\n // Write down the relations again into the actual entities\n for (const [userN, groupsN] of newUserMemberOf.entries()) {\n const user = userMap.get(userN);\n if (user) {\n user.spec.memberOf = Array.from(groupsN).sort();\n }\n }\n for (const [groupN, parentsN] of newGroupParents.entries()) {\n if (parentsN.size === 1) {\n const group = groupMap.get(groupN);\n if (group) {\n group.spec.parent = parentsN.values().next().value;\n }\n }\n }\n for (const [groupN, childrenN] of newGroupChildren.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n group.spec.children = Array.from(childrenN).sort();\n }\n }\n\n // Fill out the rest of the hierarchy\n buildOrgHierarchy(groups);\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 LdapClient,\n LdapProviderConfig,\n LDAP_DN_ANNOTATION,\n readLdapConfig,\n readLdapOrg,\n UserTransformer,\n} from '../ldap';\n\n/**\n * Reads user and group entries out of an LDAP service, and provides them as\n * User and Group entities for the catalog.\n *\n * @remarks\n *\n * Add an instance of this class to your catalog builder, and then periodically\n * call the {@link LdapOrgEntityProvider.read} method.\n *\n * @public\n */\nexport class LdapOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n\n static fromConfig(\n configRoot: Config,\n options: {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n /**\n * The target that this provider should consume.\n *\n * Should exactly match the \"target\" field of one of the \"ldap.providers\"\n * configuration entries.\n *\n * @example \"ldaps://ds-read.example.net\"\n */\n target: string;\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n logger: Logger;\n },\n ): LdapOrgEntityProvider {\n // TODO(freben): Deprecate the old catalog.processors.ldapOrg config\n const config =\n configRoot.getOptionalConfig('ldap') ||\n configRoot.getOptionalConfig('catalog.processors.ldapOrg');\n if (!config) {\n throw new TypeError(\n `There is no LDAP configuration. Please add it as \"ldap.providers\".`,\n );\n }\n\n const providers = readLdapConfig(config);\n const provider = providers.find(p => options.target === p.target);\n if (!provider) {\n throw new TypeError(\n `There is no LDAP configuration that matches ${options.target}. Please add a configuration entry for it under \"ldap.providers\".`,\n );\n }\n\n const logger = options.logger.child({\n target: options.target,\n });\n\n return new LdapOrgEntityProvider({\n id: options.id,\n provider,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n logger,\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: LdapProviderConfig;\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {}\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */\n getProviderName() {\n return `LdapOrgEntityProvider:${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 { markReadComplete } = trackProgress(this.options.logger);\n\n // Be lazy and create the client each time; even though it's pretty\n // inefficient, we usually only do this once per entire refresh loop and\n // don't have to worry about timeouts and reconnects etc.\n const client = await LdapClient.create(\n this.options.logger,\n this.options.provider.target,\n this.options.provider.bind,\n );\n\n const { users, groups } = await readLdapOrg(\n client,\n this.options.provider.users,\n this.options.provider.groups,\n {\n groupTransformer: this.options.groupTransformer,\n userTransformer: this.options.userTransformer,\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: `ldap-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 LDAP users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} LDAP users and ${read.groups.length} LDAP 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 DN\nfunction withLocations(providerId: string, entity: Entity): Entity {\n const dn =\n entity.metadata.annotations?.[LDAP_DN_ANNOTATION] || entity.metadata.name;\n const location = `ldap://${providerId}/${encodeURIComponent(dn)}`;\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 { Logger } from 'winston';\nimport {\n GroupTransformer,\n LdapClient,\n LdapProviderConfig,\n readLdapConfig,\n readLdapOrg,\n UserTransformer,\n} from '../ldap';\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n LocationSpec,\n processingResult,\n} from '@backstage/plugin-catalog-backend';\n\n/**\n * Extracts teams and users out of an LDAP server.\n *\n * @public\n */\nexport class LdapOrgReaderProcessor implements CatalogProcessor {\n private readonly providers: LdapProviderConfig[];\n private readonly logger: Logger;\n private readonly groupTransformer?: GroupTransformer;\n private readonly userTransformer?: UserTransformer;\n\n static fromConfig(\n config: Config,\n options: {\n logger: Logger;\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n },\n ) {\n const c = config.getOptionalConfig('catalog.processors.ldapOrg');\n return new LdapOrgReaderProcessor({\n ...options,\n providers: c ? readLdapConfig(c) : [],\n });\n }\n\n constructor(options: {\n providers: LdapProviderConfig[];\n logger: Logger;\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n }) {\n this.providers = options.providers;\n this.logger = options.logger;\n this.groupTransformer = options.groupTransformer;\n this.userTransformer = options.userTransformer;\n }\n\n getProcessorName(): string {\n return 'LdapOrgReaderProcessor';\n }\n\n async readLocation(\n location: LocationSpec,\n _optional: boolean,\n emit: CatalogProcessorEmit,\n ): Promise<boolean> {\n if (location.type !== 'ldap-org') {\n return false;\n }\n\n const provider = this.providers.find(p => location.target === p.target);\n if (!provider) {\n throw new Error(\n `There is no LDAP Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.ldapOrg.providers.`,\n );\n }\n\n // Read out all of the raw data\n const startTimestamp = Date.now();\n this.logger.info('Reading LDAP users and groups');\n\n // Be lazy and create the client each time; even though it's pretty\n // inefficient, we usually only do this once per entire refresh loop and\n // don't have to worry about timeouts and reconnects etc.\n const client = await LdapClient.create(\n this.logger,\n provider.target,\n provider.bind,\n );\n const { users, groups } = await readLdapOrg(\n client,\n provider.users,\n provider.groups,\n {\n groupTransformer: this.groupTransformer,\n userTransformer: this.userTransformer,\n logger: this.logger,\n },\n );\n\n const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1);\n this.logger.debug(\n `Read ${users.length} LDAP users and ${groups.length} LDAP groups 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":["ldap","cloneDeep","ForwardedError","trimEnd","mergeWith","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","processingResult"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/ldap/util.ts","../src/ldap/vendors.ts","../src/ldap/client.ts","../src/ldap/config.ts","../src/ldap/constants.ts","../src/ldap/org.ts","../src/ldap/read.ts","../src/processors/LdapOrgEntityProvider.ts","../src/processors/LdapOrgReaderProcessor.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 { Error as LDAPError, SearchEntry } from 'ldapjs';\nimport { LdapVendor } from './vendors';\n\n/**\n * Builds a string form of an LDAP Error structure.\n *\n * @param error - The error\n */\nexport function errorString(error: LDAPError) {\n return `${error.code} ${error.name}: ${error.message}`;\n}\n\n/**\n * Maps a single-valued attribute to a consumer.\n *\n * This helper can be useful when implementing a user or group transformer.\n *\n * @param entry - The LDAP source entry\n * @param vendor - The LDAP vendor\n * @param attributeName - The source attribute to map. If the attribute is\n * undefined the mapping will be silently ignored.\n * @param setter - The function to be called with the decoded attribute from the\n * source entry\n *\n * @public\n */\nexport function mapStringAttr(\n entry: SearchEntry,\n vendor: LdapVendor,\n attributeName: string | undefined,\n setter: (value: string) => void,\n) {\n if (attributeName) {\n const values = vendor.decodeStringAttribute(entry, attributeName);\n if (values && values.length === 1) {\n setter(values[0]);\n }\n }\n}\n\nexport type RecursivePartial<T> = {\n [P in keyof T]?: T[P] extends (infer U)[]\n ? RecursivePartial<U>[]\n : T[P] extends object\n ? RecursivePartial<T[P]>\n : T[P];\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 { SearchEntry } from 'ldapjs';\n\n/**\n * An LDAP Vendor handles unique nuances between different vendors.\n *\n * @public\n */\nexport type LdapVendor = {\n /**\n * The attribute name that holds the distinguished name (DN) for an entry.\n */\n dnAttributeName: string;\n /**\n * The attribute name that holds a universal unique identifier for an entry.\n */\n uuidAttributeName: string;\n /**\n * Decode ldap entry values for a given attribute name to their string representation.\n *\n * @param entry - The ldap entry\n * @param name - The attribute to decode\n */\n decodeStringAttribute: (entry: SearchEntry, name: string) => string[];\n};\n\nexport const DefaultLdapVendor: LdapVendor = {\n dnAttributeName: 'entryDN',\n uuidAttributeName: 'entryUUID',\n decodeStringAttribute: (entry, name) => {\n return decode(entry, name, value => {\n return value.toString();\n });\n },\n};\n\nexport const ActiveDirectoryVendor: LdapVendor = {\n dnAttributeName: 'distinguishedName',\n uuidAttributeName: 'objectGUID',\n decodeStringAttribute: (entry, name) => {\n const decoder = (value: string | Buffer) => {\n if (name === ActiveDirectoryVendor.uuidAttributeName) {\n return formatGUID(value);\n }\n return value.toString();\n };\n return decode(entry, name, decoder);\n },\n};\n\n// Decode an attribute to a consumer\nfunction decode(\n entry: SearchEntry,\n attributeName: string,\n decoder: (value: string | Buffer) => string,\n): string[] {\n const values = entry.raw[attributeName];\n if (Array.isArray(values)) {\n return values.map(v => {\n return decoder(v);\n });\n } else if (values) {\n return [decoder(values)];\n }\n return [];\n}\n\n// Formats a Microsoft Active Directory binary-encoded uuid to a readable string\n// See https://github.com/ldapjs/node-ldapjs/issues/297#issuecomment-137765214\nfunction formatGUID(objectGUID: string | Buffer): string {\n let data: Buffer;\n if (typeof objectGUID === 'string') {\n data = new Buffer(objectGUID, 'binary');\n } else {\n data = objectGUID;\n }\n // GUID_FORMAT_D\n let template = '{3}{2}{1}{0}-{5}{4}-{7}{6}-{8}{9}-{10}{11}{12}{13}{14}{15}';\n\n // check each byte\n for (let i = 0; i < data.length; i++) {\n let dataStr = data[i].toString(16);\n dataStr = data[i] >= 16 ? dataStr : `0${dataStr}`;\n\n // insert that character into the template\n template = template.replace(`{${i}}`, dataStr);\n }\n return template;\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 { ForwardedError } from '@backstage/errors';\nimport ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs';\nimport { cloneDeep } from 'lodash';\nimport { Logger } from 'winston';\nimport { BindConfig } from './config';\nimport { errorString } from './util';\nimport {\n ActiveDirectoryVendor,\n DefaultLdapVendor,\n LdapVendor,\n} from './vendors';\n\n/**\n * Basic wrapper for the `ldapjs` library.\n *\n * Helps out with promisifying calls, paging, binding etc.\n *\n * @public\n */\nexport class LdapClient {\n private vendor: Promise<LdapVendor> | undefined;\n\n static async create(\n logger: Logger,\n target: string,\n bind?: BindConfig,\n ): Promise<LdapClient> {\n const client = ldap.createClient({ url: target });\n\n // We want to have a catch-all error handler at the top, since the default\n // behavior of the client is to blow up the entire process when it fails,\n // unless an error handler is set.\n client.on('error', (err: ldap.Error) => {\n logger.warn(`LDAP client threw an error, ${errorString(err)}`);\n });\n\n if (!bind) {\n return new LdapClient(client, logger);\n }\n\n return new Promise<LdapClient>((resolve, reject) => {\n const { dn, secret } = bind;\n client.bind(dn, secret, err => {\n if (err) {\n reject(`LDAP bind failed for ${dn}, ${errorString(err)}`);\n } else {\n resolve(new LdapClient(client, logger));\n }\n });\n });\n }\n\n constructor(\n private readonly client: Client,\n private readonly logger: Logger,\n ) {}\n\n /**\n * Performs an LDAP search operation.\n *\n * @param dn - The fully qualified base DN to search within\n * @param options - The search options\n */\n async search(dn: string, options: SearchOptions): Promise<SearchEntry[]> {\n try {\n const output: SearchEntry[] = [];\n\n const logInterval = setInterval(() => {\n this.logger.debug(`Read ${output.length} LDAP entries so far...`);\n }, 5000);\n\n const search = new Promise<SearchEntry[]>((resolve, reject) => {\n // Note that we clone the (frozen) options, since ldapjs rudely tries to\n // overwrite parts of them\n this.client.search(dn, cloneDeep(options), (err, res) => {\n if (err) {\n reject(new Error(errorString(err)));\n return;\n }\n\n res.on('searchReference', () => {\n this.logger.warn('Received unsupported search referral');\n });\n\n res.on('searchEntry', entry => {\n output.push(entry);\n });\n\n res.on('error', e => {\n reject(new Error(errorString(e)));\n });\n\n res.on('page', (_result, cb) => {\n if (cb) {\n cb();\n }\n });\n\n res.on('end', r => {\n if (!r) {\n reject(new Error('Null response'));\n } else if (r.status !== 0) {\n reject(new Error(`Got status ${r.status}: ${r.errorMessage}`));\n } else {\n resolve(output);\n }\n });\n });\n });\n\n return await search.finally(() => {\n clearInterval(logInterval);\n });\n } catch (e) {\n throw new ForwardedError(`LDAP search at DN \"${dn}\" failed`, e);\n }\n }\n\n /**\n * Performs an LDAP search operation, calls a function on each entry to limit memory usage\n *\n * @param dn - The fully qualified base DN to search within\n * @param options - The search options\n * @param f - The callback to call on each search entry\n */\n async searchStreaming(\n dn: string,\n options: SearchOptions,\n f: (entry: SearchEntry) => void,\n ): Promise<void> {\n try {\n return await new Promise<void>((resolve, reject) => {\n // Note that we clone the (frozen) options, since ldapjs rudely tries to\n // overwrite parts of them\n this.client.search(dn, cloneDeep(options), (err, res) => {\n if (err) {\n reject(new Error(errorString(err)));\n }\n\n res.on('searchReference', () => {\n this.logger.warn('Received unsupported search referral');\n });\n\n res.on('searchEntry', entry => {\n f(entry);\n });\n\n res.on('error', e => {\n reject(new Error(errorString(e)));\n });\n\n res.on('end', r => {\n if (!r) {\n throw new Error('Null response');\n } else if (r.status !== 0) {\n throw new Error(`Got status ${r.status}: ${r.errorMessage}`);\n } else {\n resolve();\n }\n });\n });\n });\n } catch (e) {\n throw new ForwardedError(`LDAP search at DN \"${dn}\" failed`, e);\n }\n }\n\n /**\n * Get the Server Vendor.\n * Currently only detects Microsoft Active Directory Servers.\n *\n * @see https://ldapwiki.com/wiki/Determine%20LDAP%20Server%20Vendor\n */\n async getVendor(): Promise<LdapVendor> {\n if (this.vendor) {\n return this.vendor;\n }\n this.vendor = this.getRootDSE()\n .then(root => {\n if (root && root.raw?.forestFunctionality) {\n return ActiveDirectoryVendor;\n }\n return DefaultLdapVendor;\n })\n .catch(err => {\n this.vendor = undefined;\n throw err;\n });\n return this.vendor;\n }\n\n /**\n * Get the Root DSE.\n *\n * @see https://ldapwiki.com/wiki/RootDSE\n */\n async getRootDSE(): Promise<SearchEntry | undefined> {\n const result = await this.search('', {\n scope: 'base',\n filter: '(objectclass=*)',\n } as SearchOptions);\n if (result && result.length === 1) {\n return result[0];\n }\n return undefined;\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 { JsonValue } from '@backstage/types';\nimport { SearchOptions } from 'ldapjs';\nimport mergeWith from 'lodash/mergeWith';\nimport { trimEnd } from 'lodash';\nimport { RecursivePartial } from './util';\n\n/**\n * The configuration parameters for a single LDAP provider.\n *\n * @public\n */\nexport type LdapProviderConfig = {\n // The prefix of the target that this matches on, e.g.\n // \"ldaps://ds.example.net\", with no trailing slash.\n target: string;\n // The settings to use for the bind command. If none are specified, the bind\n // command is not issued.\n bind?: BindConfig;\n // The settings that govern the reading and interpretation of users\n users: UserConfig;\n // The settings that govern the reading and interpretation of groups\n groups: GroupConfig;\n};\n\n/**\n * The settings to use for the a command.\n *\n * @public\n */\nexport type BindConfig = {\n // The DN of the user to auth as, e.g.\n // uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net\n dn: string;\n // The secret of the user to auth as (its password)\n secret: string;\n};\n\n/**\n * The settings that govern the reading and interpretation of users.\n *\n * @public\n */\nexport type UserConfig = {\n // The DN under which users are stored.\n dn: string;\n // The search options to use.\n // Only the scope, filter, attributes, and paged fields are supported. The\n // default is scope \"one\" and attributes \"*\" and \"+\".\n options: SearchOptions;\n // JSON paths (on a.b.c form) and hard coded values to set on those paths\n set?: { [path: string]: JsonValue };\n // Mappings from well known entity fields, to LDAP attribute names\n map: {\n // The name of the attribute that holds the relative distinguished name of\n // each entry. Defaults to \"uid\".\n rdn: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.name field of the entity. Defaults to \"uid\".\n name: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.description field of the entity.\n description?: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.displayName field of the entity. Defaults to \"cn\".\n displayName: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.email field of the entity. Defaults to \"mail\".\n email: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.picture field of the entity.\n picture?: string;\n // The name of the attribute that shall be used for the values of the\n // spec.memberOf field of the entity. Defaults to \"memberOf\".\n memberOf: string;\n };\n};\n\n/**\n * The settings that govern the reading and interpretation of groups.\n *\n * @public\n */\nexport type GroupConfig = {\n // The DN under which groups are stored.\n dn: string;\n // The search options to use.\n // Only the scope, filter, attributes, and paged fields are supported.\n options: SearchOptions;\n // JSON paths (on a.b.c form) and hard coded values to set on those paths\n set?: { [path: string]: JsonValue };\n // Mappings from well known entity fields, to LDAP attribute names\n map: {\n // The name of the attribute that holds the relative distinguished name of\n // each entry. Defaults to \"cn\".\n rdn: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.name field of the entity. Defaults to \"cn\".\n name: string;\n // The name of the attribute that shall be used for the value of the\n // metadata.description field of the entity. Defaults to \"description\".\n description: string;\n // The name of the attribute that shall be used for the value of the\n // spec.type field of the entity. Defaults to \"groupType\".\n type: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.displayName field of the entity. Defaults to \"cn\".\n displayName: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.email field of the entity.\n email?: string;\n // The name of the attribute that shall be used for the value of the\n // spec.profile.picture field of the entity.\n picture?: string;\n // The name of the attribute that shall be used for the values of the\n // spec.parent field of the entity. Defaults to \"memberOf\".\n memberOf: string;\n // The name of the attribute that shall be used for the values of the\n // spec.children field of the entity. Defaults to \"member\".\n members: string;\n };\n};\n\nconst defaultConfig = {\n users: {\n options: {\n scope: 'one',\n attributes: ['*', '+'],\n },\n map: {\n rdn: 'uid',\n name: 'uid',\n displayName: 'cn',\n email: 'mail',\n memberOf: 'memberOf',\n },\n },\n groups: {\n options: {\n scope: 'one',\n attributes: ['*', '+'],\n },\n map: {\n rdn: 'cn',\n name: 'cn',\n description: 'description',\n displayName: 'cn',\n type: 'groupType',\n memberOf: 'memberOf',\n members: 'member',\n },\n },\n};\n\n/**\n * Parses configuration.\n *\n * @param config - The root of the LDAP config hierarchy\n *\n * @public\n */\nexport function readLdapConfig(config: Config): LdapProviderConfig[] {\n function freeze<T>(data: T): T {\n return JSON.parse(JSON.stringify(data), (_key, value) => {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n }\n return value;\n });\n }\n\n function readBindConfig(\n c: Config | undefined,\n ): LdapProviderConfig['bind'] | undefined {\n if (!c) {\n return undefined;\n }\n return {\n dn: c.getString('dn'),\n secret: c.getString('secret'),\n };\n }\n\n function readOptionsConfig(c: Config | undefined): SearchOptions {\n if (!c) {\n return {};\n }\n\n const paged = readOptionsPagedConfig(c);\n\n return {\n scope: c.getOptionalString('scope') as SearchOptions['scope'],\n filter: formatFilter(c.getOptionalString('filter')),\n attributes: c.getOptionalStringArray('attributes'),\n sizeLimit: c.getOptionalNumber('sizeLimit'),\n timeLimit: c.getOptionalNumber('timeLimit'),\n derefAliases: c.getOptionalNumber('derefAliases'),\n typesOnly: c.getOptionalBoolean('typesOnly'),\n ...(paged !== undefined ? { paged } : undefined),\n };\n }\n\n function readOptionsPagedConfig(c: Config): SearchOptions['paged'] {\n const pagedConfig = c.getOptional('paged');\n if (pagedConfig === undefined) {\n return undefined;\n }\n\n if (pagedConfig === true || pagedConfig === false) {\n return pagedConfig;\n }\n\n const pageSize = c.getOptionalNumber('paged.pageSize');\n const pagePause = c.getOptionalBoolean('paged.pagePause');\n return {\n ...(pageSize !== undefined ? { pageSize } : undefined),\n ...(pagePause !== undefined ? { pagePause } : undefined),\n };\n }\n\n function readSetConfig(\n c: Config | undefined,\n ): { [path: string]: JsonValue } | undefined {\n if (!c) {\n return undefined;\n }\n return c.get();\n }\n\n function readUserMapConfig(\n c: Config | undefined,\n ): Partial<LdapProviderConfig['users']['map']> {\n if (!c) {\n return {};\n }\n\n return {\n rdn: c.getOptionalString('rdn'),\n name: c.getOptionalString('name'),\n description: c.getOptionalString('description'),\n displayName: c.getOptionalString('displayName'),\n email: c.getOptionalString('email'),\n picture: c.getOptionalString('picture'),\n memberOf: c.getOptionalString('memberOf'),\n };\n }\n\n function readGroupMapConfig(\n c: Config | undefined,\n ): Partial<LdapProviderConfig['groups']['map']> {\n if (!c) {\n return {};\n }\n\n return {\n rdn: c.getOptionalString('rdn'),\n name: c.getOptionalString('name'),\n description: c.getOptionalString('description'),\n type: c.getOptionalString('type'),\n displayName: c.getOptionalString('displayName'),\n email: c.getOptionalString('email'),\n picture: c.getOptionalString('picture'),\n memberOf: c.getOptionalString('memberOf'),\n members: c.getOptionalString('members'),\n };\n }\n\n function readUserConfig(\n c: Config,\n ): RecursivePartial<LdapProviderConfig['users']> {\n return {\n dn: c.getString('dn'),\n options: readOptionsConfig(c.getOptionalConfig('options')),\n set: readSetConfig(c.getOptionalConfig('set')),\n map: readUserMapConfig(c.getOptionalConfig('map')),\n };\n }\n\n function readGroupConfig(\n c: Config,\n ): RecursivePartial<LdapProviderConfig['groups']> {\n return {\n dn: c.getString('dn'),\n options: readOptionsConfig(c.getOptionalConfig('options')),\n set: readSetConfig(c.getOptionalConfig('set')),\n map: readGroupMapConfig(c.getOptionalConfig('map')),\n };\n }\n\n function formatFilter(filter?: string): string | undefined {\n // Remove extra whitespace between blocks to support multiline filters from the configuration\n return filter?.replace(/\\s*(\\(|\\))/g, '$1')?.trim();\n }\n\n const providerConfigs = config.getOptionalConfigArray('providers') ?? [];\n return providerConfigs.map(c => {\n const newConfig = {\n target: trimEnd(c.getString('target'), '/'),\n bind: readBindConfig(c.getOptionalConfig('bind')),\n users: readUserConfig(c.getConfig('users')),\n groups: readGroupConfig(c.getConfig('groups')),\n };\n const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => {\n // Replace arrays instead of merging, otherwise default behavior\n return Array.isArray(from) ? from : undefined;\n });\n return freeze(merged) as LdapProviderConfig;\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\n/**\n * The name of an entity annotation, that references the RDN of the LDAP object\n * it was ingested from.\n *\n * The RDN is the name of the leftmost attribute that identifies the item; for\n * example, for an item with the fully qualified DN\n * uid=john,ou=people,ou=spotify,dc=spotify,dc=net the generated entity would\n * have this annotation, with the value \"john\".\n *\n * @public\n */\nexport const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn';\n\n/**\n * The name of an entity annotation, that references the DN of the LDAP object\n * it was ingested from.\n *\n * The DN is the fully qualified name that identifies the item; for example,\n * for an item with the DN uid=john,ou=people,ou=spotify,dc=spotify,dc=net the\n * generated entity would have this annotation, with that full string as its\n * value.\n *\n * @public\n */\nexport const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn';\n\n/**\n * The name of an entity annotation, that references the UUID of the LDAP\n * object it was ingested from.\n *\n * The UUID is the globally unique ID that identifies the item; for example,\n * for an item with the UUID 76ef928a-b251-1037-9840-d78227f36a7e, the\n * generated entity would have this annotation, with that full string as its\n * value.\n *\n * @public\n */\nexport const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid';\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 { GroupEntity, UserEntity } from '@backstage/catalog-model';\nimport { SearchEntry } from 'ldapjs';\nimport lodashSet from 'lodash/set';\nimport cloneDeep from 'lodash/cloneDeep';\nimport { buildOrgHierarchy } from './org';\nimport { LdapClient } from './client';\nimport { GroupConfig, UserConfig } from './config';\nimport {\n LDAP_DN_ANNOTATION,\n LDAP_RDN_ANNOTATION,\n LDAP_UUID_ANNOTATION,\n} from './constants';\nimport { LdapVendor } from './vendors';\nimport { Logger } from 'winston';\nimport { GroupTransformer, UserTransformer } from './types';\nimport { mapStringAttr } from './util';\n\n/**\n * The default implementation of the transformation from an LDAP entry to a\n * User entity.\n *\n * @public\n */\nexport async function defaultUserTransformer(\n vendor: LdapVendor,\n config: UserConfig,\n entry: SearchEntry,\n): Promise<UserEntity | undefined> {\n const { set, map } = config;\n\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'User',\n metadata: {\n name: '',\n annotations: {},\n },\n spec: {\n profile: {},\n memberOf: [],\n },\n };\n\n if (set) {\n for (const [path, value] of Object.entries(set)) {\n lodashSet(entity, path, cloneDeep(value));\n }\n }\n\n mapStringAttr(entry, vendor, map.name, v => {\n entity.metadata.name = v;\n });\n mapStringAttr(entry, vendor, map.description, v => {\n entity.metadata.description = v;\n });\n mapStringAttr(entry, vendor, map.rdn, v => {\n entity.metadata.annotations![LDAP_RDN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.uuidAttributeName, v => {\n entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {\n entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, map.displayName, v => {\n entity.spec.profile!.displayName = v;\n });\n mapStringAttr(entry, vendor, map.email, v => {\n entity.spec.profile!.email = v;\n });\n mapStringAttr(entry, vendor, map.picture, v => {\n entity.spec.profile!.picture = v;\n });\n\n return entity;\n}\n\n/**\n * Reads users out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param config - The user data configuration\n * @param opts - Additional options\n */\nexport async function readLdapUsers(\n client: LdapClient,\n config: UserConfig,\n opts?: { transformer?: UserTransformer },\n): Promise<{\n users: UserEntity[]; // With all relations empty\n userMemberOf: Map<string, Set<string>>; // DN -> DN or UUID of groups\n}> {\n const { dn, options, map } = config;\n const vendor = await client.getVendor();\n\n const entities: UserEntity[] = [];\n const userMemberOf: Map<string, Set<string>> = new Map();\n\n const transformer = opts?.transformer ?? defaultUserTransformer;\n\n await client.searchStreaming(dn, options, async user => {\n const entity = await transformer(vendor, config, user);\n\n if (!entity) {\n return;\n }\n\n mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => {\n ensureItems(userMemberOf, myDn, vs);\n });\n\n entities.push(entity);\n });\n\n return { users: entities, userMemberOf };\n}\n\n/**\n * The default implementation of the transformation from an LDAP entry to a\n * Group entity.\n *\n * @public\n */\nexport async function defaultGroupTransformer(\n vendor: LdapVendor,\n config: GroupConfig,\n entry: SearchEntry,\n): Promise<GroupEntity | undefined> {\n const { set, map } = config;\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'Group',\n metadata: {\n name: '',\n annotations: {},\n },\n spec: {\n type: 'unknown',\n profile: {},\n children: [],\n },\n };\n\n if (set) {\n for (const [path, value] of Object.entries(set)) {\n lodashSet(entity, path, cloneDeep(value));\n }\n }\n\n mapStringAttr(entry, vendor, map.name, v => {\n entity.metadata.name = v;\n });\n mapStringAttr(entry, vendor, map.description, v => {\n entity.metadata.description = v;\n });\n mapStringAttr(entry, vendor, map.rdn, v => {\n entity.metadata.annotations![LDAP_RDN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.uuidAttributeName, v => {\n entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {\n entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;\n });\n mapStringAttr(entry, vendor, map.type, v => {\n entity.spec.type = v;\n });\n mapStringAttr(entry, vendor, map.displayName, v => {\n entity.spec.profile!.displayName = v;\n });\n mapStringAttr(entry, vendor, map.email, v => {\n entity.spec.profile!.email = v;\n });\n mapStringAttr(entry, vendor, map.picture, v => {\n entity.spec.profile!.picture = v;\n });\n\n return entity;\n}\n\n/**\n * Reads groups out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param config - The group data configuration\n * @param opts - Additional options\n */\nexport async function readLdapGroups(\n client: LdapClient,\n config: GroupConfig,\n opts?: {\n transformer?: GroupTransformer;\n },\n): Promise<{\n groups: GroupEntity[]; // With all relations empty\n groupMemberOf: Map<string, Set<string>>; // DN -> DN or UUID of groups\n groupMember: Map<string, Set<string>>; // DN -> DN or UUID of groups & users\n}> {\n const groups: GroupEntity[] = [];\n const groupMemberOf: Map<string, Set<string>> = new Map();\n const groupMember: Map<string, Set<string>> = new Map();\n\n const { dn, map, options } = config;\n const vendor = await client.getVendor();\n\n const transformer = opts?.transformer ?? defaultGroupTransformer;\n\n await client.searchStreaming(dn, options, async entry => {\n if (!entry) {\n return;\n }\n\n const entity = await transformer(vendor, config, entry);\n\n if (!entity) {\n return;\n }\n\n mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => {\n ensureItems(groupMemberOf, myDn, vs);\n });\n mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => {\n ensureItems(groupMember, myDn, vs);\n });\n\n groups.push(entity);\n });\n\n return {\n groups,\n groupMemberOf,\n groupMember,\n };\n}\n\n/**\n * Reads users and groups out of an LDAP provider.\n *\n * @param client - The LDAP client\n * @param userConfig - The user data configuration\n * @param groupConfig - The group data configuration\n * @param options - Additional options\n *\n * @public\n */\nexport async function readLdapOrg(\n client: LdapClient,\n userConfig: UserConfig,\n groupConfig: GroupConfig,\n options: {\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n logger: Logger;\n },\n): Promise<{\n users: UserEntity[];\n groups: GroupEntity[];\n}> {\n // Invokes the above \"raw\" read functions and stitches together the results\n // with all relations etc filled in.\n\n const { users, userMemberOf } = await readLdapUsers(client, userConfig, {\n transformer: options?.userTransformer,\n });\n const { groups, groupMemberOf, groupMember } = await readLdapGroups(\n client,\n groupConfig,\n { transformer: options?.groupTransformer },\n );\n\n resolveRelations(groups, users, userMemberOf, groupMemberOf, groupMember);\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\n//\n// Helpers\n//\n\n// Maps a multi-valued attribute of references to other objects, to a consumer\nfunction mapReferencesAttr(\n entry: SearchEntry,\n vendor: LdapVendor,\n attributeName: string | undefined,\n setter: (sourceDn: string, targets: string[]) => void,\n) {\n if (attributeName) {\n const values = vendor.decodeStringAttribute(entry, attributeName);\n const dn = vendor.decodeStringAttribute(entry, vendor.dnAttributeName);\n if (values && dn && dn.length === 1) {\n setter(dn[0], values);\n }\n }\n}\n\n// Inserts a number of values in a key-values mapping\nfunction ensureItems(\n target: Map<string, Set<string>>,\n key: string,\n values: string[],\n) {\n if (key) {\n let set = target.get(key);\n if (!set) {\n set = new Set();\n target.set(key, set);\n }\n for (const value of values) {\n if (value) {\n set!.add(value);\n }\n }\n }\n}\n\n/**\n * Takes groups and entities with empty relations, and fills in the various\n * relations that were returned by the readers, and forms the org hierarchy.\n *\n * @param groups - Group entities with empty relations; modified in place\n * @param users - User entities with empty relations; modified in place\n * @param userMemberOf - For a user DN, the set of group DNs or UUIDs that the\n * user is a member of\n * @param groupMemberOf - For a group DN, the set of group DNs or UUIDs that\n * the group is a member of (parents in the hierarchy)\n * @param groupMember - For a group DN, the set of group DNs or UUIDs that are\n * members of the group (children in the hierarchy)\n */\nexport function resolveRelations(\n groups: GroupEntity[],\n users: UserEntity[],\n userMemberOf: Map<string, Set<string>>,\n groupMemberOf: Map<string, Set<string>>,\n groupMember: Map<string, Set<string>>,\n) {\n // Build reference lookup tables - all of the relations that are output from\n // the above calls can be expressed as either DNs or UUIDs so we need to be\n // able to find by both, as well as the name. Note that we expect them to not\n // collide here - this is a reasonable assumption as long as the fields are\n // the supported forms.\n const userMap: Map<string, UserEntity> = new Map(); // by name, dn, uuid\n const groupMap: Map<string, GroupEntity> = new Map(); // by name, dn, uuid\n for (const user of users) {\n userMap.set(user.metadata.name, user);\n userMap.set(user.metadata.annotations![LDAP_DN_ANNOTATION], user);\n userMap.set(user.metadata.annotations![LDAP_UUID_ANNOTATION], user);\n }\n for (const group of groups) {\n groupMap.set(group.metadata.name, group);\n groupMap.set(group.metadata.annotations![LDAP_DN_ANNOTATION], group);\n groupMap.set(group.metadata.annotations![LDAP_UUID_ANNOTATION], group);\n }\n\n // This can happen e.g. if entryUUID wasn't returned by the server\n userMap.delete('');\n groupMap.delete('');\n userMap.delete(undefined!);\n groupMap.delete(undefined!);\n\n // Fill in all of the immediate relations, now keyed on metadata.name. We\n // keep all parents at this point, whether the target model can support more\n // than one or not (it gets filtered farther down). And group children are\n // only groups in here.\n const newUserMemberOf: Map<string, Set<string>> = new Map();\n const newGroupParents: Map<string, Set<string>> = new Map();\n const newGroupChildren: Map<string, Set<string>> = new Map();\n\n // Resolve and store in the intermediaries. It may seem redundant that the\n // input data has both parent and children directions, as well as both\n // user->group and group->user - the reason is that different LDAP schemas\n // express relations in different directions. Some may have a user memberOf\n // overlay, some don't, for example.\n for (const [userN, groupsN] of userMemberOf.entries()) {\n const user = userMap.get(userN);\n if (user) {\n for (const groupN of groupsN) {\n const group = groupMap.get(groupN);\n if (group) {\n ensureItems(newUserMemberOf, user.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n for (const [groupN, parentsN] of groupMemberOf.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n for (const parentN of parentsN) {\n const parentGroup = groupMap.get(parentN);\n if (parentGroup) {\n ensureItems(newGroupParents, group.metadata.name, [\n parentGroup.metadata.name,\n ]);\n ensureItems(newGroupChildren, parentGroup.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n for (const [groupN, membersN] of groupMember.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n for (const memberN of membersN) {\n // Group members can be both users and groups in the input model, so\n // try both\n const memberUser = userMap.get(memberN);\n if (memberUser) {\n ensureItems(newUserMemberOf, memberUser.metadata.name, [\n group.metadata.name,\n ]);\n } else {\n const memberGroup = groupMap.get(memberN);\n if (memberGroup) {\n ensureItems(newGroupChildren, group.metadata.name, [\n memberGroup.metadata.name,\n ]);\n ensureItems(newGroupParents, memberGroup.metadata.name, [\n group.metadata.name,\n ]);\n }\n }\n }\n }\n }\n\n // Write down the relations again into the actual entities\n for (const [userN, groupsN] of newUserMemberOf.entries()) {\n const user = userMap.get(userN);\n if (user) {\n user.spec.memberOf = Array.from(groupsN).sort();\n }\n }\n for (const [groupN, parentsN] of newGroupParents.entries()) {\n if (parentsN.size === 1) {\n const group = groupMap.get(groupN);\n if (group) {\n group.spec.parent = parentsN.values().next().value;\n }\n }\n }\n for (const [groupN, childrenN] of newGroupChildren.entries()) {\n const group = groupMap.get(groupN);\n if (group) {\n group.spec.children = Array.from(childrenN).sort();\n }\n }\n\n // Fill out the rest of the hierarchy\n buildOrgHierarchy(groups);\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 LdapClient,\n LdapProviderConfig,\n LDAP_DN_ANNOTATION,\n readLdapConfig,\n readLdapOrg,\n UserTransformer,\n} from '../ldap';\n\n/**\n * Options for {@link LdapOrgEntityProvider}.\n *\n * @public\n */\nexport interface LdapOrgEntityProviderOptions {\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 \"ldap.providers\"\n * configuration entries.\n *\n * @example \"ldaps://ds-read.example.net\"\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 LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n/**\n * Reads user and group entries out of an LDAP service, and provides them as\n * User and Group entities for the catalog.\n *\n * @remarks\n *\n * Add an instance of this class to your catalog builder, and then periodically\n * call the {@link LdapOrgEntityProvider.read} method.\n *\n * @public\n */\nexport class LdapOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private scheduleFn?: () => Promise<void>;\n\n static fromConfig(\n configRoot: Config,\n options: LdapOrgEntityProviderOptions,\n ): LdapOrgEntityProvider {\n // TODO(freben): Deprecate the old catalog.processors.ldapOrg config\n const config =\n configRoot.getOptionalConfig('ldap') ||\n configRoot.getOptionalConfig('catalog.processors.ldapOrg');\n const providers = config ? readLdapConfig(config) : [];\n const provider = providers.find(p => options.target === p.target);\n if (!provider) {\n throw new TypeError(\n `There is no LDAP configuration that matches \"${options.target}\". Please add a configuration entry for it under \"ldap.providers\".`,\n );\n }\n\n const logger = options.logger.child({\n target: options.target,\n });\n\n const result = new LdapOrgEntityProvider({\n id: options.id,\n provider,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n logger,\n });\n\n result.schedule(options.schedule);\n\n return result;\n }\n\n constructor(\n private options: {\n id: string;\n provider: LdapProviderConfig;\n logger: Logger;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {}\n\n /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */\n getProviderName() {\n return `LdapOrgEntityProvider:${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 single complete ingestion. This is only necessary if you use\n * manual scheduling.\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 { markReadComplete } = trackProgress(logger);\n\n // Be lazy and create the client each time; even though it's pretty\n // inefficient, we usually only do this once per entire refresh loop and\n // don't have to worry about timeouts and reconnects etc.\n const client = await LdapClient.create(\n this.options.logger,\n this.options.provider.target,\n this.options.provider.bind,\n );\n\n const { users, groups } = await readLdapOrg(\n client,\n this.options.provider.users,\n this.options.provider.groups,\n {\n groupTransformer: this.options.groupTransformer,\n userTransformer: this.options.userTransformer,\n 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: `ldap-org-provider:${this.options.id}`,\n entity: withLocations(this.options.id, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n private schedule(schedule: LdapOrgEntityProviderOptions['schedule']) {\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: LdapOrgEntityProvider.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 LDAP users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} LDAP users and ${read.groups.length} LDAP 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 DN\nfunction withLocations(providerId: string, entity: Entity): Entity {\n const dn =\n entity.metadata.annotations?.[LDAP_DN_ANNOTATION] || entity.metadata.name;\n const location = `ldap://${providerId}/${encodeURIComponent(dn)}`;\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 { Logger } from 'winston';\nimport {\n GroupTransformer,\n LdapClient,\n LdapProviderConfig,\n readLdapConfig,\n readLdapOrg,\n UserTransformer,\n} from '../ldap';\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n LocationSpec,\n processingResult,\n} from '@backstage/plugin-catalog-backend';\n\n/**\n * Extracts teams and users out of an LDAP server.\n *\n * @public\n */\nexport class LdapOrgReaderProcessor implements CatalogProcessor {\n private readonly providers: LdapProviderConfig[];\n private readonly logger: Logger;\n private readonly groupTransformer?: GroupTransformer;\n private readonly userTransformer?: UserTransformer;\n\n static fromConfig(\n configRoot: Config,\n options: {\n logger: Logger;\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n },\n ) {\n // TODO(freben): Deprecate the old catalog.processors.ldapOrg config\n const config =\n configRoot.getOptionalConfig('ldap') ||\n configRoot.getOptionalConfig('catalog.processors.ldapOrg');\n return new LdapOrgReaderProcessor({\n ...options,\n providers: config ? readLdapConfig(config) : [],\n });\n }\n\n constructor(options: {\n providers: LdapProviderConfig[];\n logger: Logger;\n groupTransformer?: GroupTransformer;\n userTransformer?: UserTransformer;\n }) {\n this.providers = options.providers;\n this.logger = options.logger;\n this.groupTransformer = options.groupTransformer;\n this.userTransformer = options.userTransformer;\n }\n\n getProcessorName(): string {\n return 'LdapOrgReaderProcessor';\n }\n\n async readLocation(\n location: LocationSpec,\n _optional: boolean,\n emit: CatalogProcessorEmit,\n ): Promise<boolean> {\n if (location.type !== 'ldap-org') {\n return false;\n }\n\n const provider = this.providers.find(p => location.target === p.target);\n if (!provider) {\n throw new Error(\n `There is no LDAP configuration that matches \"${location.target}\". Please add a configuration entry for it under \"ldap.providers\".`,\n );\n }\n\n // Read out all of the raw data\n const startTimestamp = Date.now();\n this.logger.info('Reading LDAP users and groups');\n\n // Be lazy and create the client each time; even though it's pretty\n // inefficient, we usually only do this once per entire refresh loop and\n // don't have to worry about timeouts and reconnects etc.\n const client = await LdapClient.create(\n this.logger,\n provider.target,\n provider.bind,\n );\n const { users, groups } = await readLdapOrg(\n client,\n provider.users,\n provider.groups,\n {\n groupTransformer: this.groupTransformer,\n userTransformer: this.userTransformer,\n logger: this.logger,\n },\n );\n\n const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1);\n this.logger.debug(\n `Read ${users.length} LDAP users and ${groups.length} LDAP groups 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":["ldap","cloneDeep","ForwardedError","trimEnd","mergeWith","lodashSet","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';
@@ -259,6 +260,53 @@ declare function readLdapOrg(client: LdapClient, userConfig: UserConfig, groupCo
259
260
  groups: GroupEntity[];
260
261
  }>;
261
262
 
263
+ /**
264
+ * Options for {@link LdapOrgEntityProvider}.
265
+ *
266
+ * @public
267
+ */
268
+ interface LdapOrgEntityProviderOptions {
269
+ /**
270
+ * A unique, stable identifier for this provider.
271
+ *
272
+ * @example "production"
273
+ */
274
+ id: string;
275
+ /**
276
+ * The target that this provider should consume.
277
+ *
278
+ * Should exactly match the "target" field of one of the "ldap.providers"
279
+ * configuration entries.
280
+ *
281
+ * @example "ldaps://ds-read.example.net"
282
+ */
283
+ target: string;
284
+ /**
285
+ * The logger to use.
286
+ */
287
+ logger: Logger;
288
+ /**
289
+ * The refresh schedule to use.
290
+ *
291
+ * @remarks
292
+ *
293
+ * If you pass in 'manual', you are responsible for calling the `read` method
294
+ * manually at some interval.
295
+ *
296
+ * But more commonly you will pass in the result of
297
+ * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}
298
+ * to enable automatic scheduling of tasks.
299
+ */
300
+ schedule: 'manual' | TaskRunner;
301
+ /**
302
+ * The function that transforms a user entry in LDAP to an entity.
303
+ */
304
+ userTransformer?: UserTransformer;
305
+ /**
306
+ * The function that transforms a group entry in LDAP to an entity.
307
+ */
308
+ groupTransformer?: GroupTransformer;
309
+ }
262
310
  /**
263
311
  * Reads user and group entries out of an LDAP service, and provides them as
264
312
  * User and Group entities for the catalog.
@@ -273,32 +321,8 @@ declare function readLdapOrg(client: LdapClient, userConfig: UserConfig, groupCo
273
321
  declare class LdapOrgEntityProvider implements EntityProvider {
274
322
  private options;
275
323
  private connection?;
276
- static fromConfig(configRoot: Config, options: {
277
- /**
278
- * A unique, stable identifier for this provider.
279
- *
280
- * @example "production"
281
- */
282
- id: string;
283
- /**
284
- * The target that this provider should consume.
285
- *
286
- * Should exactly match the "target" field of one of the "ldap.providers"
287
- * configuration entries.
288
- *
289
- * @example "ldaps://ds-read.example.net"
290
- */
291
- target: string;
292
- /**
293
- * The function that transforms a user entry in LDAP to an entity.
294
- */
295
- userTransformer?: UserTransformer;
296
- /**
297
- * The function that transforms a group entry in LDAP to an entity.
298
- */
299
- groupTransformer?: GroupTransformer;
300
- logger: Logger;
301
- }): LdapOrgEntityProvider;
324
+ private scheduleFn?;
325
+ static fromConfig(configRoot: Config, options: LdapOrgEntityProviderOptions): LdapOrgEntityProvider;
302
326
  constructor(options: {
303
327
  id: string;
304
328
  provider: LdapProviderConfig;
@@ -311,10 +335,13 @@ declare class LdapOrgEntityProvider implements EntityProvider {
311
335
  /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
312
336
  connect(connection: EntityProviderConnection): Promise<void>;
313
337
  /**
314
- * Runs one complete ingestion loop. Call this method regularly at some
315
- * appropriate cadence.
338
+ * Runs one single complete ingestion. This is only necessary if you use
339
+ * manual scheduling.
316
340
  */
317
- read(): Promise<void>;
341
+ read(options?: {
342
+ logger?: Logger;
343
+ }): Promise<void>;
344
+ private schedule;
318
345
  }
319
346
 
320
347
  /**
@@ -327,7 +354,7 @@ declare class LdapOrgReaderProcessor implements CatalogProcessor {
327
354
  private readonly logger;
328
355
  private readonly groupTransformer?;
329
356
  private readonly userTransformer?;
330
- static fromConfig(config: Config, options: {
357
+ static fromConfig(configRoot: Config, options: {
331
358
  logger: Logger;
332
359
  groupTransformer?: GroupTransformer;
333
360
  userTransformer?: UserTransformer;
@@ -342,4 +369,4 @@ declare class LdapOrgReaderProcessor implements CatalogProcessor {
342
369
  readLocation(location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit): Promise<boolean>;
343
370
  }
344
371
 
345
- export { BindConfig, GroupConfig, GroupTransformer, LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION, LdapClient, LdapOrgEntityProvider, LdapOrgReaderProcessor, LdapProviderConfig, LdapVendor, UserConfig, UserTransformer, defaultGroupTransformer, defaultUserTransformer, mapStringAttr, readLdapConfig, readLdapOrg };
372
+ export { BindConfig, GroupConfig, GroupTransformer, LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION, LdapClient, LdapOrgEntityProvider, LdapOrgEntityProviderOptions, LdapOrgReaderProcessor, LdapProviderConfig, LdapVendor, UserConfig, UserTransformer, defaultGroupTransformer, defaultUserTransformer, mapStringAttr, readLdapConfig, readLdapOrg };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend-module-ldap",
3
3
  "description": "A Backstage catalog backend module that helps integrate towards LDAP",
4
- "version": "0.3.15",
4
+ "version": "0.4.1",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -33,18 +33,20 @@
33
33
  "start": "backstage-cli package start"
34
34
  },
35
35
  "dependencies": {
36
- "@backstage/catalog-model": "^0.12.0",
37
- "@backstage/config": "^0.1.15",
38
- "@backstage/errors": "^0.2.2",
39
- "@backstage/plugin-catalog-backend": "^0.23.0",
40
- "@backstage/types": "^0.1.3",
36
+ "@backstage/backend-tasks": "^0.2.1",
37
+ "@backstage/catalog-model": "^1.0.0",
38
+ "@backstage/config": "^1.0.0",
39
+ "@backstage/errors": "^1.0.0",
40
+ "@backstage/plugin-catalog-backend": "^1.0.0",
41
+ "@backstage/types": "^1.0.0",
41
42
  "@types/ldapjs": "^2.2.0",
42
43
  "ldapjs": "^2.2.0",
43
44
  "lodash": "^4.17.21",
45
+ "uuid": "^8.0.0",
44
46
  "winston": "^3.2.1"
45
47
  },
46
48
  "devDependencies": {
47
- "@backstage/cli": "^0.15.0",
49
+ "@backstage/cli": "^0.16.0",
48
50
  "@types/lodash": "^4.14.151"
49
51
  },
50
52
  "files": [
@@ -52,5 +54,5 @@
52
54
  "config.d.ts"
53
55
  ],
54
56
  "configSchema": "config.d.ts",
55
- "gitHead": "04bb0dd824b78f6b57dac62c3015e681f094045c"
57
+ "gitHead": "e9496f746b31600dbfac7fa76987479e66426257"
56
58
  }