mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-11 05:43:26 +00:00
Compare commits
13 Commits
ac/pm-2489
...
sugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb611d635 | ||
|
|
b0dbf2b4a1 | ||
|
|
88ebe3274f | ||
|
|
6081cae98b | ||
|
|
1a62248932 | ||
|
|
6cb1c08f51 | ||
|
|
4411c3e7f1 | ||
|
|
0ce4547d34 | ||
|
|
2ca8654492 | ||
|
|
88a1dc7334 | ||
|
|
e74546e8c3 | ||
|
|
5ac0cc408e | ||
|
|
9044f94f43 |
@@ -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));
|
||||
|
||||
@@ -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
16
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user