@backstage/plugin-catalog-backend-module-ldap 0.3.8 → 0.3.11-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.cjs.js +45 -35
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +92 -33
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend-module-ldap
|
|
2
2
|
|
|
3
|
+
## 0.3.11-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @backstage/plugin-catalog-backend@0.21.2-next.0
|
|
9
|
+
|
|
10
|
+
## 0.3.10
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 3368dc6b62: Make sure to clone objects sent to `ldapjs` since the library modifies them
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @backstage/plugin-catalog-backend@0.21.0
|
|
17
|
+
- @backstage/config@0.1.13
|
|
18
|
+
- @backstage/catalog-model@0.9.10
|
|
19
|
+
|
|
20
|
+
## 0.3.10-next.0
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- 3368dc6b62: Make sure to clone objects sent to `ldapjs` since the library modifies them
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @backstage/plugin-catalog-backend@0.21.0-next.0
|
|
27
|
+
- @backstage/config@0.1.13-next.0
|
|
28
|
+
- @backstage/catalog-model@0.9.10-next.0
|
|
29
|
+
|
|
30
|
+
## 0.3.9
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- 2b19fd2e94: Make sure to avoid accidental data sharing / mutation of `set` values
|
|
35
|
+
- 722681b1b1: Clean up API report
|
|
36
|
+
- Updated dependencies
|
|
37
|
+
- @backstage/config@0.1.12
|
|
38
|
+
- @backstage/plugin-catalog-backend@0.20.0
|
|
39
|
+
- @backstage/errors@0.2.0
|
|
40
|
+
- @backstage/catalog-model@0.9.9
|
|
41
|
+
|
|
3
42
|
## 0.3.8
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -8,6 +8,7 @@ var errors = require('@backstage/errors');
|
|
|
8
8
|
var ldap = require('ldapjs');
|
|
9
9
|
var mergeWith = require('lodash/mergeWith');
|
|
10
10
|
var lodashSet = require('lodash/set');
|
|
11
|
+
var cloneDeep = require('lodash/cloneDeep');
|
|
11
12
|
var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
|
|
12
13
|
|
|
13
14
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -15,6 +16,7 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
15
16
|
var ldap__default = /*#__PURE__*/_interopDefaultLegacy(ldap);
|
|
16
17
|
var mergeWith__default = /*#__PURE__*/_interopDefaultLegacy(mergeWith);
|
|
17
18
|
var lodashSet__default = /*#__PURE__*/_interopDefaultLegacy(lodashSet);
|
|
19
|
+
var cloneDeep__default = /*#__PURE__*/_interopDefaultLegacy(cloneDeep);
|
|
18
20
|
|
|
19
21
|
function errorString(error) {
|
|
20
22
|
return `${error.code} ${error.name}: ${error.message}`;
|
|
@@ -83,7 +85,7 @@ class LdapClient {
|
|
|
83
85
|
this.logger = logger;
|
|
84
86
|
}
|
|
85
87
|
static async create(logger, target, bind) {
|
|
86
|
-
const client = ldap__default[
|
|
88
|
+
const client = ldap__default["default"].createClient({ url: target });
|
|
87
89
|
client.on("error", (err) => {
|
|
88
90
|
logger.warn(`LDAP client threw an error, ${errorString(err)}`);
|
|
89
91
|
});
|
|
@@ -91,7 +93,7 @@ class LdapClient {
|
|
|
91
93
|
return new LdapClient(client, logger);
|
|
92
94
|
}
|
|
93
95
|
return new Promise((resolve, reject) => {
|
|
94
|
-
const {dn, secret} = bind;
|
|
96
|
+
const { dn, secret } = bind;
|
|
95
97
|
client.bind(dn, secret, (err) => {
|
|
96
98
|
if (err) {
|
|
97
99
|
reject(`LDAP bind failed for ${dn}, ${errorString(err)}`);
|
|
@@ -108,7 +110,7 @@ class LdapClient {
|
|
|
108
110
|
this.logger.debug(`Read ${output.length} LDAP entries so far...`);
|
|
109
111
|
}, 5e3);
|
|
110
112
|
const search = new Promise((resolve, reject) => {
|
|
111
|
-
this.client.search(dn, options, (err, res) => {
|
|
113
|
+
this.client.search(dn, lodash.cloneDeep(options), (err, res) => {
|
|
112
114
|
if (err) {
|
|
113
115
|
reject(new Error(errorString(err)));
|
|
114
116
|
return;
|
|
@@ -143,7 +145,7 @@ class LdapClient {
|
|
|
143
145
|
async searchStreaming(dn, options, f) {
|
|
144
146
|
try {
|
|
145
147
|
return await new Promise((resolve, reject) => {
|
|
146
|
-
this.client.search(dn, options, (err, res) => {
|
|
148
|
+
this.client.search(dn, lodash.cloneDeep(options), (err, res) => {
|
|
147
149
|
if (err) {
|
|
148
150
|
reject(new Error(errorString(err)));
|
|
149
151
|
}
|
|
@@ -231,6 +233,14 @@ const defaultConfig = {
|
|
|
231
233
|
};
|
|
232
234
|
function readLdapConfig(config) {
|
|
233
235
|
var _a;
|
|
236
|
+
function freeze(data) {
|
|
237
|
+
return JSON.parse(JSON.stringify(data), (_key, value) => {
|
|
238
|
+
if (typeof value === "object" && value !== null) {
|
|
239
|
+
Object.freeze(value);
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
234
244
|
function readBindConfig(c) {
|
|
235
245
|
if (!c) {
|
|
236
246
|
return void 0;
|
|
@@ -249,7 +259,7 @@ function readLdapConfig(config) {
|
|
|
249
259
|
scope: c.getOptionalString("scope"),
|
|
250
260
|
filter: formatFilter(c.getOptionalString("filter")),
|
|
251
261
|
attributes: c.getOptionalStringArray("attributes"),
|
|
252
|
-
...paged !== void 0 ? {paged} : void 0
|
|
262
|
+
...paged !== void 0 ? { paged } : void 0
|
|
253
263
|
};
|
|
254
264
|
}
|
|
255
265
|
function readOptionsPagedConfig(c) {
|
|
@@ -263,15 +273,15 @@ function readLdapConfig(config) {
|
|
|
263
273
|
const pageSize = c.getOptionalNumber("paged.pageSize");
|
|
264
274
|
const pagePause = c.getOptionalBoolean("paged.pagePause");
|
|
265
275
|
return {
|
|
266
|
-
...pageSize !== void 0 ? {pageSize} : void 0,
|
|
267
|
-
...pagePause !== void 0 ? {pagePause} : void 0
|
|
276
|
+
...pageSize !== void 0 ? { pageSize } : void 0,
|
|
277
|
+
...pagePause !== void 0 ? { pagePause } : void 0
|
|
268
278
|
};
|
|
269
279
|
}
|
|
270
280
|
function readSetConfig(c) {
|
|
271
281
|
if (!c) {
|
|
272
282
|
return void 0;
|
|
273
283
|
}
|
|
274
|
-
return
|
|
284
|
+
return c.get();
|
|
275
285
|
}
|
|
276
286
|
function readUserMapConfig(c) {
|
|
277
287
|
if (!c) {
|
|
@@ -331,10 +341,10 @@ function readLdapConfig(config) {
|
|
|
331
341
|
users: readUserConfig(c.getConfig("users")),
|
|
332
342
|
groups: readGroupConfig(c.getConfig("groups"))
|
|
333
343
|
};
|
|
334
|
-
const merged = mergeWith__default[
|
|
344
|
+
const merged = mergeWith__default["default"]({}, defaultConfig, newConfig, (_into, from) => {
|
|
335
345
|
return Array.isArray(from) ? from : void 0;
|
|
336
346
|
});
|
|
337
|
-
return merged;
|
|
347
|
+
return freeze(merged);
|
|
338
348
|
});
|
|
339
349
|
}
|
|
340
350
|
|
|
@@ -366,7 +376,7 @@ function buildOrgHierarchy(groups) {
|
|
|
366
376
|
}
|
|
367
377
|
|
|
368
378
|
async function defaultUserTransformer(vendor, config, entry) {
|
|
369
|
-
const {set, map} = config;
|
|
379
|
+
const { set, map } = config;
|
|
370
380
|
const entity = {
|
|
371
381
|
apiVersion: "backstage.io/v1beta1",
|
|
372
382
|
kind: "User",
|
|
@@ -381,7 +391,7 @@ async function defaultUserTransformer(vendor, config, entry) {
|
|
|
381
391
|
};
|
|
382
392
|
if (set) {
|
|
383
393
|
for (const [path, value] of Object.entries(set)) {
|
|
384
|
-
lodashSet__default[
|
|
394
|
+
lodashSet__default["default"](entity, path, cloneDeep__default["default"](value));
|
|
385
395
|
}
|
|
386
396
|
}
|
|
387
397
|
mapStringAttr(entry, vendor, map.name, (v) => {
|
|
@@ -412,10 +422,10 @@ async function defaultUserTransformer(vendor, config, entry) {
|
|
|
412
422
|
}
|
|
413
423
|
async function readLdapUsers(client, config, opts) {
|
|
414
424
|
var _a;
|
|
415
|
-
const {dn, options, map} = config;
|
|
425
|
+
const { dn, options, map } = config;
|
|
416
426
|
const vendor = await client.getVendor();
|
|
417
427
|
const entities = [];
|
|
418
|
-
const userMemberOf = new Map();
|
|
428
|
+
const userMemberOf = /* @__PURE__ */ new Map();
|
|
419
429
|
const transformer = (_a = opts == null ? void 0 : opts.transformer) != null ? _a : defaultUserTransformer;
|
|
420
430
|
await client.searchStreaming(dn, options, async (user) => {
|
|
421
431
|
const entity = await transformer(vendor, config, user);
|
|
@@ -427,10 +437,10 @@ async function readLdapUsers(client, config, opts) {
|
|
|
427
437
|
});
|
|
428
438
|
entities.push(entity);
|
|
429
439
|
});
|
|
430
|
-
return {users: entities, userMemberOf};
|
|
440
|
+
return { users: entities, userMemberOf };
|
|
431
441
|
}
|
|
432
442
|
async function defaultGroupTransformer(vendor, config, entry) {
|
|
433
|
-
const {set, map} = config;
|
|
443
|
+
const { set, map } = config;
|
|
434
444
|
const entity = {
|
|
435
445
|
apiVersion: "backstage.io/v1beta1",
|
|
436
446
|
kind: "Group",
|
|
@@ -446,7 +456,7 @@ async function defaultGroupTransformer(vendor, config, entry) {
|
|
|
446
456
|
};
|
|
447
457
|
if (set) {
|
|
448
458
|
for (const [path, value] of Object.entries(set)) {
|
|
449
|
-
lodashSet__default[
|
|
459
|
+
lodashSet__default["default"](entity, path, cloneDeep__default["default"](value));
|
|
450
460
|
}
|
|
451
461
|
}
|
|
452
462
|
mapStringAttr(entry, vendor, map.name, (v) => {
|
|
@@ -481,9 +491,9 @@ async function defaultGroupTransformer(vendor, config, entry) {
|
|
|
481
491
|
async function readLdapGroups(client, config, opts) {
|
|
482
492
|
var _a;
|
|
483
493
|
const groups = [];
|
|
484
|
-
const groupMemberOf = new Map();
|
|
485
|
-
const groupMember = new Map();
|
|
486
|
-
const {dn, map, options} = config;
|
|
494
|
+
const groupMemberOf = /* @__PURE__ */ new Map();
|
|
495
|
+
const groupMember = /* @__PURE__ */ new Map();
|
|
496
|
+
const { dn, map, options } = config;
|
|
487
497
|
const vendor = await client.getVendor();
|
|
488
498
|
const transformer = (_a = opts == null ? void 0 : opts.transformer) != null ? _a : defaultGroupTransformer;
|
|
489
499
|
await client.searchStreaming(dn, options, async (entry) => {
|
|
@@ -509,14 +519,14 @@ async function readLdapGroups(client, config, opts) {
|
|
|
509
519
|
};
|
|
510
520
|
}
|
|
511
521
|
async function readLdapOrg(client, userConfig, groupConfig, options) {
|
|
512
|
-
const {users, userMemberOf} = await readLdapUsers(client, userConfig, {
|
|
522
|
+
const { users, userMemberOf } = await readLdapUsers(client, userConfig, {
|
|
513
523
|
transformer: options == null ? void 0 : options.userTransformer
|
|
514
524
|
});
|
|
515
|
-
const {groups, groupMemberOf, groupMember} = await readLdapGroups(client, groupConfig, {transformer: options == null ? void 0 : options.groupTransformer});
|
|
525
|
+
const { groups, groupMemberOf, groupMember } = await readLdapGroups(client, groupConfig, { transformer: options == null ? void 0 : options.groupTransformer });
|
|
516
526
|
resolveRelations(groups, users, userMemberOf, groupMemberOf, groupMember);
|
|
517
527
|
users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
|
|
518
528
|
groups.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
|
|
519
|
-
return {users, groups};
|
|
529
|
+
return { users, groups };
|
|
520
530
|
}
|
|
521
531
|
function mapReferencesAttr(entry, vendor, attributeName, setter) {
|
|
522
532
|
if (attributeName) {
|
|
@@ -531,7 +541,7 @@ function ensureItems(target, key, values) {
|
|
|
531
541
|
if (key) {
|
|
532
542
|
let set = target.get(key);
|
|
533
543
|
if (!set) {
|
|
534
|
-
set = new Set();
|
|
544
|
+
set = /* @__PURE__ */ new Set();
|
|
535
545
|
target.set(key, set);
|
|
536
546
|
}
|
|
537
547
|
for (const value of values) {
|
|
@@ -542,8 +552,8 @@ function ensureItems(target, key, values) {
|
|
|
542
552
|
}
|
|
543
553
|
}
|
|
544
554
|
function resolveRelations(groups, users, userMemberOf, groupMemberOf, groupMember) {
|
|
545
|
-
const userMap = new Map();
|
|
546
|
-
const groupMap = new Map();
|
|
555
|
+
const userMap = /* @__PURE__ */ new Map();
|
|
556
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
547
557
|
for (const user of users) {
|
|
548
558
|
userMap.set(user.metadata.name, user);
|
|
549
559
|
userMap.set(user.metadata.annotations[LDAP_DN_ANNOTATION], user);
|
|
@@ -558,9 +568,9 @@ function resolveRelations(groups, users, userMemberOf, groupMemberOf, groupMembe
|
|
|
558
568
|
groupMap.delete("");
|
|
559
569
|
userMap.delete(void 0);
|
|
560
570
|
groupMap.delete(void 0);
|
|
561
|
-
const newUserMemberOf = new Map();
|
|
562
|
-
const newGroupParents = new Map();
|
|
563
|
-
const newGroupChildren = new Map();
|
|
571
|
+
const newUserMemberOf = /* @__PURE__ */ new Map();
|
|
572
|
+
const newGroupParents = /* @__PURE__ */ new Map();
|
|
573
|
+
const newGroupChildren = /* @__PURE__ */ new Map();
|
|
564
574
|
for (const [userN, groupsN] of userMemberOf.entries()) {
|
|
565
575
|
const user = userMap.get(userN);
|
|
566
576
|
if (user) {
|
|
@@ -671,14 +681,14 @@ class LdapOrgEntityProvider {
|
|
|
671
681
|
if (!this.connection) {
|
|
672
682
|
throw new Error("Not initialized");
|
|
673
683
|
}
|
|
674
|
-
const {markReadComplete} = trackProgress(this.options.logger);
|
|
684
|
+
const { markReadComplete } = trackProgress(this.options.logger);
|
|
675
685
|
const client = await LdapClient.create(this.options.logger, this.options.provider.target, this.options.provider.bind);
|
|
676
|
-
const {users, groups} = await readLdapOrg(client, this.options.provider.users, this.options.provider.groups, {
|
|
686
|
+
const { users, groups } = await readLdapOrg(client, this.options.provider.users, this.options.provider.groups, {
|
|
677
687
|
groupTransformer: this.options.groupTransformer,
|
|
678
688
|
userTransformer: this.options.userTransformer,
|
|
679
689
|
logger: this.options.logger
|
|
680
690
|
});
|
|
681
|
-
const {markCommitComplete} = markReadComplete({users, groups});
|
|
691
|
+
const { markCommitComplete } = markReadComplete({ users, groups });
|
|
682
692
|
await this.connection.applyMutation({
|
|
683
693
|
type: "full",
|
|
684
694
|
entities: [...users, ...groups].map((entity) => ({
|
|
@@ -698,13 +708,13 @@ function trackProgress(logger) {
|
|
|
698
708
|
const readDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
|
|
699
709
|
timestamp = Date.now();
|
|
700
710
|
logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);
|
|
701
|
-
return {markCommitComplete};
|
|
711
|
+
return { markCommitComplete };
|
|
702
712
|
}
|
|
703
713
|
function markCommitComplete() {
|
|
704
714
|
const commitDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
|
|
705
715
|
logger.info(`Committed ${summary} in ${commitDuration} seconds.`);
|
|
706
716
|
}
|
|
707
|
-
return {markReadComplete};
|
|
717
|
+
return { markReadComplete };
|
|
708
718
|
}
|
|
709
719
|
function withLocations(providerId, entity) {
|
|
710
720
|
var _a;
|
|
@@ -745,7 +755,7 @@ class LdapOrgReaderProcessor {
|
|
|
745
755
|
const startTimestamp = Date.now();
|
|
746
756
|
this.logger.info("Reading LDAP users and groups");
|
|
747
757
|
const client = await LdapClient.create(this.logger, provider.target, provider.bind);
|
|
748
|
-
const {users, groups} = await readLdapOrg(client, provider.users, provider.groups, {
|
|
758
|
+
const { users, groups } = await readLdapOrg(client, provider.users, provider.groups, {
|
|
749
759
|
groupTransformer: this.groupTransformer,
|
|
750
760
|
userTransformer: this.userTransformer,
|
|
751
761
|
logger: this.logger
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -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 * @param entry The LDAP source entry\n * @param vendor The LDAP vendor\n * @param attributeName The source attribute to map. If the attribute is undefined the mapping will be silently ignored.\n * @param setter The function to be called with the decoded attribute from the source entry\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 */\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 // @ts-ignore\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 { Logger } from 'winston';\nimport { BindConfig } from './config';\nimport { errorString } from './util';\nimport {\n ActiveDirectoryVendor,\n DefaultLdapVendor,\n LdapVendor,\n} from './vendors';\n\nexport interface SearchCallback {\n (entry: SearchEntry): void;\n}\n\n/**\n * Basic wrapper for the ldapjs library.\n *\n * Helps out with promisifying calls, paging, binding etc.\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 this.client.search(dn, 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('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: SearchCallback,\n ): Promise<void> {\n try {\n return await new Promise<void>((resolve, reject) => {\n this.client.search(dn, 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 */\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 */\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 */\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 */\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 */\nexport function readLdapConfig(config: Config): LdapProviderConfig[] {\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 Object.fromEntries(c.keys().map(path => [path, c.get(path)]));\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 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 */\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 */\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 */\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 { 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\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, 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\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\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, 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\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 * Invokes the above \"raw\" read functions and stitches together the results\n * with all relations etc filled in.\n *\n * @param client The LDAP client\n * @param userConfig The user data configuration\n * @param groupConfig The group data configuration\n * @param options\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 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 the\n * 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 Entity,\n LOCATION_ANNOTATION,\n ORIGIN_LOCATION_ANNOTATION,\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 */\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 getProviderName() {\n return `LdapOrgEntityProvider:${this.options.id}`;\n }\n\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n }\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 [LOCATION_ANNOTATION]: location,\n [ORIGIN_LOCATION_ANNOTATION]: 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 { LocationSpec } from '@backstage/catalog-model';\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 results,\n} from '@backstage/plugin-catalog-backend';\n\n/**\n * Extracts teams and users out of an LDAP server.\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 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(results.entity(location, group));\n }\n for (const user of users) {\n emit(results.entity(location, user));\n }\n\n return true;\n }\n}\n"],"names":["ldap","ForwardedError","trimEnd","mergeWith","merge","LOCATION_ANNOTATION","ORIGIN_LOCATION_ANNOTATION","results"],"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","/*\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('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 Entity,\n LOCATION_ANNOTATION,\n ORIGIN_LOCATION_ANNOTATION,\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 [LOCATION_ANNOTATION]: location,\n [ORIGIN_LOCATION_ANNOTATION]: 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 { LocationSpec } from '@backstage/catalog-model';\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 results,\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 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(results.entity(location, group));\n }\n for (const user of users) {\n emit(results.entity(location, user));\n }\n\n return true;\n }\n}\n"],"names":["ldap","cloneDeep","ForwardedError","trimEnd","mergeWith","merge","LOCATION_ANNOTATION","ORIGIN_LOCATION_ANNOTATION","results"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { UserEntity, GroupEntity, LocationSpec } from '@backstage/catalog-model'
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The configuration parameters for a single LDAP provider.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
10
12
|
*/
|
|
11
13
|
declare type LdapProviderConfig = {
|
|
12
14
|
target: string;
|
|
@@ -16,6 +18,8 @@ declare type LdapProviderConfig = {
|
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
20
|
* The settings to use for the a command.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
19
23
|
*/
|
|
20
24
|
declare type BindConfig = {
|
|
21
25
|
dn: string;
|
|
@@ -23,6 +27,8 @@ declare type BindConfig = {
|
|
|
23
27
|
};
|
|
24
28
|
/**
|
|
25
29
|
* The settings that govern the reading and interpretation of users.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
26
32
|
*/
|
|
27
33
|
declare type UserConfig = {
|
|
28
34
|
dn: string;
|
|
@@ -42,6 +48,8 @@ declare type UserConfig = {
|
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
44
50
|
* The settings that govern the reading and interpretation of groups.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
45
53
|
*/
|
|
46
54
|
declare type GroupConfig = {
|
|
47
55
|
dn: string;
|
|
@@ -64,12 +72,16 @@ declare type GroupConfig = {
|
|
|
64
72
|
/**
|
|
65
73
|
* Parses configuration.
|
|
66
74
|
*
|
|
67
|
-
* @param config The root of the LDAP config hierarchy
|
|
75
|
+
* @param config - The root of the LDAP config hierarchy
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
68
78
|
*/
|
|
69
79
|
declare function readLdapConfig(config: Config): LdapProviderConfig[];
|
|
70
80
|
|
|
71
81
|
/**
|
|
72
82
|
* An LDAP Vendor handles unique nuances between different vendors.
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
73
85
|
*/
|
|
74
86
|
declare type LdapVendor = {
|
|
75
87
|
/**
|
|
@@ -83,19 +95,18 @@ declare type LdapVendor = {
|
|
|
83
95
|
/**
|
|
84
96
|
* Decode ldap entry values for a given attribute name to their string representation.
|
|
85
97
|
*
|
|
86
|
-
* @param entry The ldap entry
|
|
87
|
-
* @param name The attribute to decode
|
|
98
|
+
* @param entry - The ldap entry
|
|
99
|
+
* @param name - The attribute to decode
|
|
88
100
|
*/
|
|
89
101
|
decodeStringAttribute: (entry: SearchEntry, name: string) => string[];
|
|
90
102
|
};
|
|
91
103
|
|
|
92
|
-
interface SearchCallback {
|
|
93
|
-
(entry: SearchEntry): void;
|
|
94
|
-
}
|
|
95
104
|
/**
|
|
96
|
-
* Basic wrapper for the ldapjs library.
|
|
105
|
+
* Basic wrapper for the `ldapjs` library.
|
|
97
106
|
*
|
|
98
107
|
* Helps out with promisifying calls, paging, binding etc.
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
99
110
|
*/
|
|
100
111
|
declare class LdapClient {
|
|
101
112
|
private readonly client;
|
|
@@ -106,18 +117,18 @@ declare class LdapClient {
|
|
|
106
117
|
/**
|
|
107
118
|
* Performs an LDAP search operation.
|
|
108
119
|
*
|
|
109
|
-
* @param dn The fully qualified base DN to search within
|
|
110
|
-
* @param options The search options
|
|
120
|
+
* @param dn - The fully qualified base DN to search within
|
|
121
|
+
* @param options - The search options
|
|
111
122
|
*/
|
|
112
123
|
search(dn: string, options: SearchOptions): Promise<SearchEntry[]>;
|
|
113
124
|
/**
|
|
114
125
|
* Performs an LDAP search operation, calls a function on each entry to limit memory usage
|
|
115
126
|
*
|
|
116
|
-
* @param dn The fully qualified base DN to search within
|
|
117
|
-
* @param options The search options
|
|
118
|
-
* @param f The callback to call on each search entry
|
|
127
|
+
* @param dn - The fully qualified base DN to search within
|
|
128
|
+
* @param options - The search options
|
|
129
|
+
* @param f - The callback to call on each search entry
|
|
119
130
|
*/
|
|
120
|
-
searchStreaming(dn: string, options: SearchOptions, f:
|
|
131
|
+
searchStreaming(dn: string, options: SearchOptions, f: (entry: SearchEntry) => void): Promise<void>;
|
|
121
132
|
/**
|
|
122
133
|
* Get the Server Vendor.
|
|
123
134
|
* Currently only detects Microsoft Active Directory Servers.
|
|
@@ -134,12 +145,18 @@ declare class LdapClient {
|
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
/**
|
|
137
|
-
* Maps a single-valued attribute to a consumer
|
|
148
|
+
* Maps a single-valued attribute to a consumer.
|
|
149
|
+
*
|
|
150
|
+
* This helper can be useful when implementing a user or group transformer.
|
|
138
151
|
*
|
|
139
|
-
* @param entry The LDAP source entry
|
|
140
|
-
* @param vendor The LDAP vendor
|
|
141
|
-
* @param attributeName The source attribute to map. If the attribute is
|
|
142
|
-
*
|
|
152
|
+
* @param entry - The LDAP source entry
|
|
153
|
+
* @param vendor - The LDAP vendor
|
|
154
|
+
* @param attributeName - The source attribute to map. If the attribute is
|
|
155
|
+
* undefined the mapping will be silently ignored.
|
|
156
|
+
* @param setter - The function to be called with the decoded attribute from the
|
|
157
|
+
* source entry
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
143
160
|
*/
|
|
144
161
|
declare function mapStringAttr(entry: SearchEntry, vendor: LdapVendor, attributeName: string | undefined, setter: (value: string) => void): void;
|
|
145
162
|
|
|
@@ -151,6 +168,8 @@ declare function mapStringAttr(entry: SearchEntry, vendor: LdapVendor, attribute
|
|
|
151
168
|
* example, for an item with the fully qualified DN
|
|
152
169
|
* uid=john,ou=people,ou=spotify,dc=spotify,dc=net the generated entity would
|
|
153
170
|
* have this annotation, with the value "john".
|
|
171
|
+
*
|
|
172
|
+
* @public
|
|
154
173
|
*/
|
|
155
174
|
declare const LDAP_RDN_ANNOTATION = "backstage.io/ldap-rdn";
|
|
156
175
|
/**
|
|
@@ -161,6 +180,8 @@ declare const LDAP_RDN_ANNOTATION = "backstage.io/ldap-rdn";
|
|
|
161
180
|
* for an item with the DN uid=john,ou=people,ou=spotify,dc=spotify,dc=net the
|
|
162
181
|
* generated entity would have this annotation, with that full string as its
|
|
163
182
|
* value.
|
|
183
|
+
*
|
|
184
|
+
* @public
|
|
164
185
|
*/
|
|
165
186
|
declare const LDAP_DN_ANNOTATION = "backstage.io/ldap-dn";
|
|
166
187
|
/**
|
|
@@ -171,40 +192,63 @@ declare const LDAP_DN_ANNOTATION = "backstage.io/ldap-dn";
|
|
|
171
192
|
* for an item with the UUID 76ef928a-b251-1037-9840-d78227f36a7e, the
|
|
172
193
|
* generated entity would have this annotation, with that full string as its
|
|
173
194
|
* value.
|
|
195
|
+
*
|
|
196
|
+
* @public
|
|
174
197
|
*/
|
|
175
198
|
declare const LDAP_UUID_ANNOTATION = "backstage.io/ldap-uuid";
|
|
176
199
|
|
|
177
200
|
/**
|
|
178
201
|
* Customize the ingested User entity
|
|
179
202
|
*
|
|
180
|
-
* @param vendor The LDAP vendor that can be used to find and decode vendor
|
|
181
|
-
*
|
|
182
|
-
* @param
|
|
183
|
-
* @
|
|
203
|
+
* @param vendor - The LDAP vendor that can be used to find and decode vendor
|
|
204
|
+
* specific attributes
|
|
205
|
+
* @param config - The User specific config used by the default transformer.
|
|
206
|
+
* @param user - The found LDAP entry in its source format. This is the entry
|
|
207
|
+
* that you want to transform
|
|
208
|
+
* @returns A `UserEntity` or `undefined` if you want to ignore the found user
|
|
209
|
+
* for being ingested by the catalog
|
|
210
|
+
*
|
|
211
|
+
* @public
|
|
184
212
|
*/
|
|
185
213
|
declare type UserTransformer = (vendor: LdapVendor, config: UserConfig, user: SearchEntry) => Promise<UserEntity | undefined>;
|
|
186
214
|
/**
|
|
187
215
|
* Customize the ingested Group entity
|
|
188
216
|
*
|
|
189
|
-
* @param vendor The LDAP vendor that can be used to find and decode vendor
|
|
190
|
-
*
|
|
191
|
-
* @param
|
|
192
|
-
* @
|
|
217
|
+
* @param vendor - The LDAP vendor that can be used to find and decode vendor
|
|
218
|
+
* specific attributes
|
|
219
|
+
* @param config - The Group specific config used by the default transformer.
|
|
220
|
+
* @param group - The found LDAP entry in its source format. This is the entry
|
|
221
|
+
* that you want to transform
|
|
222
|
+
* @returns A `GroupEntity` or `undefined` if you want to ignore the found group
|
|
223
|
+
* for being ingested by the catalog
|
|
224
|
+
*
|
|
225
|
+
* @public
|
|
193
226
|
*/
|
|
194
227
|
declare type GroupTransformer = (vendor: LdapVendor, config: GroupConfig, group: SearchEntry) => Promise<GroupEntity | undefined>;
|
|
195
228
|
|
|
229
|
+
/**
|
|
230
|
+
* The default implementation of the transformation from an LDAP entry to a
|
|
231
|
+
* User entity.
|
|
232
|
+
*
|
|
233
|
+
* @public
|
|
234
|
+
*/
|
|
196
235
|
declare function defaultUserTransformer(vendor: LdapVendor, config: UserConfig, entry: SearchEntry): Promise<UserEntity | undefined>;
|
|
236
|
+
/**
|
|
237
|
+
* The default implementation of the transformation from an LDAP entry to a
|
|
238
|
+
* Group entity.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
197
242
|
declare function defaultGroupTransformer(vendor: LdapVendor, config: GroupConfig, entry: SearchEntry): Promise<GroupEntity | undefined>;
|
|
198
243
|
/**
|
|
199
244
|
* Reads users and groups out of an LDAP provider.
|
|
200
245
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
246
|
+
* @param client - The LDAP client
|
|
247
|
+
* @param userConfig - The user data configuration
|
|
248
|
+
* @param groupConfig - The group data configuration
|
|
249
|
+
* @param options - Additional options
|
|
203
250
|
*
|
|
204
|
-
* @
|
|
205
|
-
* @param userConfig The user data configuration
|
|
206
|
-
* @param groupConfig The group data configuration
|
|
207
|
-
* @param options
|
|
251
|
+
* @public
|
|
208
252
|
*/
|
|
209
253
|
declare function readLdapOrg(client: LdapClient, userConfig: UserConfig, groupConfig: GroupConfig, options: {
|
|
210
254
|
groupTransformer?: GroupTransformer;
|
|
@@ -218,6 +262,13 @@ declare function readLdapOrg(client: LdapClient, userConfig: UserConfig, groupCo
|
|
|
218
262
|
/**
|
|
219
263
|
* Reads user and group entries out of an LDAP service, and provides them as
|
|
220
264
|
* User and Group entities for the catalog.
|
|
265
|
+
*
|
|
266
|
+
* @remarks
|
|
267
|
+
*
|
|
268
|
+
* Add an instance of this class to your catalog builder, and then periodically
|
|
269
|
+
* call the {@link LdapOrgEntityProvider.read} method.
|
|
270
|
+
*
|
|
271
|
+
* @public
|
|
221
272
|
*/
|
|
222
273
|
declare class LdapOrgEntityProvider implements EntityProvider {
|
|
223
274
|
private options;
|
|
@@ -255,13 +306,21 @@ declare class LdapOrgEntityProvider implements EntityProvider {
|
|
|
255
306
|
userTransformer?: UserTransformer;
|
|
256
307
|
groupTransformer?: GroupTransformer;
|
|
257
308
|
});
|
|
309
|
+
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */
|
|
258
310
|
getProviderName(): string;
|
|
311
|
+
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
|
|
259
312
|
connect(connection: EntityProviderConnection): Promise<void>;
|
|
313
|
+
/**
|
|
314
|
+
* Runs one complete ingestion loop. Call this method regularly at some
|
|
315
|
+
* appropriate cadence.
|
|
316
|
+
*/
|
|
260
317
|
read(): Promise<void>;
|
|
261
318
|
}
|
|
262
319
|
|
|
263
320
|
/**
|
|
264
321
|
* Extracts teams and users out of an LDAP server.
|
|
322
|
+
*
|
|
323
|
+
* @public
|
|
265
324
|
*/
|
|
266
325
|
declare class LdapOrgReaderProcessor implements CatalogProcessor {
|
|
267
326
|
private readonly providers;
|
|
@@ -282,4 +341,4 @@ declare class LdapOrgReaderProcessor implements CatalogProcessor {
|
|
|
282
341
|
readLocation(location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit): Promise<boolean>;
|
|
283
342
|
}
|
|
284
343
|
|
|
285
|
-
export { GroupConfig, GroupTransformer, LDAP_DN_ANNOTATION, LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION, LdapClient, LdapOrgEntityProvider, LdapOrgReaderProcessor, LdapProviderConfig, LdapVendor, UserConfig, UserTransformer, defaultGroupTransformer, defaultUserTransformer, mapStringAttr, readLdapConfig, readLdapOrg };
|
|
344
|
+
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 };
|
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 modules that helps integrate towards LDAP",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.11-next.0",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"clean": "backstage-cli clean"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@backstage/catalog-model": "^0.9.
|
|
33
|
-
"@backstage/config": "^0.1.
|
|
34
|
-
"@backstage/errors": "^0.
|
|
35
|
-
"@backstage/plugin-catalog-backend": "^0.
|
|
32
|
+
"@backstage/catalog-model": "^0.9.10",
|
|
33
|
+
"@backstage/config": "^0.1.13",
|
|
34
|
+
"@backstage/errors": "^0.2.0",
|
|
35
|
+
"@backstage/plugin-catalog-backend": "^0.21.2-next.0",
|
|
36
36
|
"@backstage/types": "^0.1.1",
|
|
37
37
|
"@types/ldapjs": "^2.2.0",
|
|
38
38
|
"ldapjs": "^2.2.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"winston": "^3.2.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@backstage/cli": "^0.
|
|
43
|
+
"@backstage/cli": "^0.13.1-next.0",
|
|
44
44
|
"@types/lodash": "^4.14.151"
|
|
45
45
|
},
|
|
46
46
|
"files": [
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"config.d.ts"
|
|
49
49
|
],
|
|
50
50
|
"configSchema": "config.d.ts",
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "a28838ac5c80c7332caa6ca0569d2ec85151784f"
|
|
52
52
|
}
|