1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-11 05:43:26 +00:00

Compare commits

...

13 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
Vincent Salucci
e74546e8c3 chore: bump version to v2025.9.0 (#881) 2025-09-22 12:05:12 -05:00
renovate[bot]
5ac0cc408e [deps]: Update node-abi to v3.77.0 (#871)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 10:42:44 -05:00
renovate[bot]
9044f94f43 [deps]: Update electron to v38 (#876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 08:44:13 -05:00
7 changed files with 115 additions and 23 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

@@ -71,7 +71,7 @@
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"dotenv": "17.2.0",
"electron": "37.4.0",
"electron": "38.1.0",
"electron-builder": "24.13.3",
"electron-log": "5.4.1",
"electron-reload": "2.0.0-alpha.1",
@@ -93,7 +93,7 @@
"jest-preset-angular": "14.6.0",
"lint-staged": "16.1.2",
"mini-css-extract-plugin": "2.9.2",
"node-abi": "3.75.0",
"node-abi": "3.77.0",
"node-forge": "1.3.1",
"node-loader": "2.1.0",
"prettier": "3.6.2",
@@ -12555,9 +12555,9 @@
}
},
"node_modules/electron": {
"version": "37.4.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.4.0.tgz",
"integrity": "sha512-HhsSdWowE5ODOeWNc/323Ug2C52mq/TqNBG+4uMeOA3G2dMXNc/nfyi0RYu1rJEgiaJLEjtHveeZZaYRYFsFCQ==",
"version": "38.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-38.1.0.tgz",
"integrity": "sha512-ypA8GF8RU4HD5pA1sa0/2U8k+92EPP2c7pX+3XbgB760F7OmqrFXtYkOilVw6HfV4+lk88XxqigmsUKTACQYoQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -20419,9 +20419,9 @@
}
},
"node_modules/node-abi": {
"version": "3.75.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
"integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==",
"version": "3.77.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.77.0.tgz",
"integrity": "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"

View File

@@ -2,7 +2,7 @@
"name": "@bitwarden/directory-connector",
"productName": "Bitwarden Directory Connector",
"description": "Sync your user directory to your Bitwarden organization.",
"version": "2025.8.0",
"version": "2025.9.0",
"keywords": [
"bitwarden",
"password",
@@ -99,7 +99,7 @@
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"dotenv": "17.2.0",
"electron": "37.4.0",
"electron": "38.1.0",
"electron-builder": "24.13.3",
"electron-log": "5.4.1",
"electron-reload": "2.0.0-alpha.1",
@@ -121,7 +121,7 @@
"jest-preset-angular": "14.6.0",
"lint-staged": "16.1.2",
"mini-css-extract-plugin": "2.9.2",
"node-abi": "3.75.0",
"node-abi": "3.77.0",
"node-forge": "1.3.1",
"node-loader": "2.1.0",
"prettier": "3.6.2",

0
run-ci Normal file
View File

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;