1
0
mirror of https://github.com/bitwarden/directory-connector synced 2026-01-08 19:43:24 +00:00

Compare commits

..

10 Commits

Author SHA1 Message Date
Thomas Rittson
3fb611d635 Run ci 2025-09-27 15:30:09 +10:00
Thomas Rittson
b0dbf2b4a1 Revert "Revert unnecessary changes"
This reverts commit 88ebe3274f.
2025-09-27 15:20:35 +10:00
Thomas Rittson
88ebe3274f Revert unnecessary changes 2025-09-27 15:16:33 +10:00
Thomas Rittson
6081cae98b Adjust jsdoc comment 2025-09-27 15:06:30 +10:00
Thomas Rittson
1a62248932 Revert unrelated change 2025-09-27 15:06:30 +10:00
Thomas Rittson
6cb1c08f51 Fix test failure 2025-09-27 15:06:30 +10:00
Thomas Rittson
4411c3e7f1 Remove unicode char in ldif 2025-09-27 15:06:30 +10:00
Sugianto BW
0ce4547d34 suggestion changes 2025-09-27 15:06:30 +10:00
Sugianto BW
2ca8654492 New Data, New Test 2025-09-27 15:06:00 +10:00
Sugianto BW
88a1dc7334 fix LDAP membership 2025-09-27 15:06:00 +10:00
8 changed files with 106 additions and 30 deletions

View File

@@ -35,6 +35,29 @@ const data: Jsonify<GroupEntry>[] = [
externalId: "cn=Cleaners,ou=Janitorial,dc=bitwarden,dc=com",
name: "Cleaners",
},
{
userMemberExternalIds: [
"cn=Painterson Miki,ou=Product Development,dc=bitwarden,dc=com",
"cn=Virgina Pichocki,ou=Product Development,dc=bitwarden,dc=com",
"cn=Steffen Carsten,ou=Product Development,dc=bitwarden,dc=com",
],
groupMemberReferenceIds: [],
users: [],
referenceId: "cn=DevOps Team,dc=bitwarden,dc=com",
externalId: "cn=DevOps Team,dc=bitwarden,dc=com",
name: "DevOps Team",
},
{
userMemberExternalIds: [
"cn=Angus Merizzi,ou=Management,dc=bitwarden,dc=com",
"cn=Grissel Currer,ou=Management,dc=bitwarden,dc=com",
],
groupMemberReferenceIds: [],
users: [],
referenceId: "cn=Security Team,dc=bitwarden,dc=com",
externalId: "cn=Security Team,dc=bitwarden,dc=com",
name: "Security Team",
},
];
export const groupFixtures = data.map((g) => GroupEntry.fromJSON(g));

View File

@@ -688,4 +688,27 @@ mobile: +1 804 319-5569
pager: +1 804 815-3661
roomNumber: 9273
manager: cn=Inga Schnirer,ou=Product Testing,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
# DevOps Team and Security Team identify their members by the member uid attribute,
# instead of the member Dn attribute.
# These test that group membership by uid works correctly.
dn: cn=DevOps Team,dc=bitwarden,dc=com
changetype: add
cn: DevOps Team
gidnumber: 800
memberuid: mikip
memberuid: pichockv
memberuid: carstens
objectclass: posixGroup
objectclass: top
dn: cn=Security Team,dc=bitwarden,dc=com
changetype: add
cn: Security Team
gidnumber: 900
memberuid: merizzia
memberuid: currerg
objectclass: posixGroup
objectclass: top

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@bitwarden/directory-connector",
"version": "2025.9.0",
"version": "2025.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/directory-connector",
"version": "2025.9.0",
"version": "2025.8.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"dependencies": {
@@ -26,7 +26,6 @@
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "14.0.0",
"core-js": "3.44.0",
"form-data": "4.0.4",
"google-auth-library": "10.3.0",
"googleapis": "153.0.0",
@@ -11509,17 +11508,6 @@
"webpack": "^5.1.0"
}
},
"node_modules/core-js": {
"version": "3.44.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz",
"integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-js-compat": {
"version": "3.44.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz",

View File

@@ -157,7 +157,6 @@
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "14.0.0",
"core-js": "3.44.0",
"form-data": "4.0.4",
"google-auth-library": "10.3.0",
"googleapis": "153.0.0",

0
run-ci Normal file
View File

View File

@@ -1,6 +1,3 @@
// core-js is required for bwdc cli which appears to require these pollyfills for dynamic imports
// see https://github.com/bitwarden/directory-connector/issues/878
import "core-js/stable";
import "zone.js";
import { NgModule } from "@angular/core";

View File

@@ -118,7 +118,7 @@ export class LdapDirectoryService implements IDirectoryService {
[delControl],
);
return regularUsers.concat(deletedUsers);
} catch (e) {
} catch {
this.logService.warning("Cannot query deleted users.");
return regularUsers;
}
@@ -192,14 +192,21 @@ export class LdapDirectoryService implements IDirectoryService {
this.syncConfig.userFilter,
);
const userPath = this.makeSearchPath(this.syncConfig.userPath);
const userIdMap = new Map<string, string>();
const userDnMap = new Map<string, string>();
const userUidMap = new Map<string, string>();
await this.search<string>(userPath, userFilter, (se: any) => {
userIdMap.set(this.getReferenceId(se), this.getExternalId(se, this.getReferenceId(se)));
const dn = this.getReferenceId(se);
const uid = this.getAttr<string>(se, "uid");
const externalId = this.getExternalId(se, dn);
userDnMap.set(dn, externalId);
if (uid != null) {
userUidMap.set(uid.toLowerCase(), externalId);
}
return se;
});
for (const se of groupSearchEntries) {
const group = this.buildGroup(se, userIdMap);
const group = this.buildGroup(se, userDnMap, userUidMap);
if (group != null) {
entries.push(group);
}
@@ -208,7 +215,20 @@ export class LdapDirectoryService implements IDirectoryService {
return entries;
}
private buildGroup(searchEntry: any, userMap: Map<string, string>) {
/**
* Builds a GroupEntry from LDAP search results, including membership.
* Supports user membership by DN or UID and nested group membership by DN.
*
* @param searchEntry - The LDAP search entry containing group data
* @param userDnMap - Map of user DNs to their external IDs
* @param userUidMap - Map of user UIDs to their external IDs
* @returns A populated GroupEntry object, or null if the group lacks required properties
*/
private buildGroup(
searchEntry: any,
userDnMap: Map<string, string>,
userUidMap: Map<string, string>,
) {
const group = new GroupEntry();
group.referenceId = this.getReferenceId(searchEntry);
if (group.referenceId == null) {
@@ -228,11 +248,34 @@ export class LdapDirectoryService implements IDirectoryService {
const members = this.getAttrVals<string>(searchEntry, this.syncConfig.memberAttribute);
if (members != null) {
for (const memDn of members) {
if (userMap.has(memDn) && !group.userMemberExternalIds.has(userMap.get(memDn))) {
group.userMemberExternalIds.add(userMap.get(memDn));
} else if (!group.groupMemberReferenceIds.has(memDn)) {
group.groupMemberReferenceIds.add(memDn);
// Parses a group member attribute and identifies it as a member DN, member Uid, or a group Dn
const getMemberAttributeType = (member: string): "memberDn" | "memberUid" | "groupDn" => {
const isDnLike = member.includes("=") && member.includes(",");
if (isDnLike) {
return userDnMap.has(member) ? "memberDn" : "groupDn";
}
return "memberUid";
};
for (const member of members) {
switch (getMemberAttributeType(member)) {
case "memberDn": {
const externalId = userDnMap.get(member);
if (externalId != null) {
group.userMemberExternalIds.add(externalId);
}
break;
}
case "memberUid": {
const externalId = userUidMap.get(member.toLowerCase());
if (externalId != null) {
group.userMemberExternalIds.add(externalId);
}
break;
}
case "groupDn":
group.groupMemberReferenceIds.add(member);
break;
}
}
}

View File

@@ -123,7 +123,10 @@ describe("SyncService", () => {
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(
expect.objectContaining({ overwriteExisting: false }),
);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(6);
// The expected number of calls may change if more data is added to the ldif
// Make sure it equals (number of users / 4) + (number of groups / 4)
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(7);
// @ts-expect-error Reset batch size to original state.
constants.batchSize = originalBatchSize;