1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00

New Data, New Test

This commit is contained in:
Sugianto BW
2025-08-28 11:18:57 +08:00
committed by Thomas Rittson
parent 88a1dc7334
commit 2ca8654492
6 changed files with 340 additions and 20 deletions

View File

@@ -35,6 +35,29 @@ const data: Jsonify<GroupEntry>[] = [
externalId: "cn=Cleaners,ou=Janitorial,dc=bitwarden,dc=com",
name: "Cleaners",
},
{
userMemberExternalIds: [
"cn=Benjamin Chen,ou=Product Development,dc=bitwarden,dc=com",
"cn=Karen Smith,ou=Product Development,dc=bitwarden,dc=com",
"cn=Robert Johnson,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=Thomas Williams,ou=Management,dc=bitwarden,dc=com",
"cn=Michelle Brown,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,173 @@ 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
dn: cn=DevOps Team,dc=bitwarden,dc=com
changetype: add
cn: DevOps Team
gidnumber: 800
memberuid: ChenB
memberuid: SmithK
memberuid: JohnsonR
objectclass: posixGroup
objectclass: top
dn: cn=Security Team,dc=bitwarden,dc=com
changetype: add
cn: Security Team
gidnumber: 900
memberuid: WilliamsT
memberuid: BrownM
objectclass: posixGroup
objectclass: top
dn: cn=Benjamin Chen,ou=Product Development,dc=bitwarden, dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Benjamin Chen
sn: Chen
description: This is Benjamin Chen's description
facsimileTelephoneNumber: +1 408 555-1234
l: San Jose
ou: Product Development
postalAddress: Product Development$San Jose
telephoneNumber: +1 408 555-5678
title: Senior DevOps Engineer
userPassword: Password1
uid: ChenB
givenName: Benjamin
mail: ChenB@9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3.bitwarden.com
carLicense: 2K8N9L
departmentNumber: 1001
employeeType: Employee
homePhone: +1 408 555-9876
initials: B. C.
mobile: +1 408 555-4321
pager: +1 408 555-7890
roomNumber: 7125
manager: cn=Roland Dyke,ou=Human Resources,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
dn: cn=Karen Smith,ou=Product Development,dc=bitwarden, dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Karen Smith
sn: Smith
description: This is Karen Smith's description
facsimileTelephoneNumber: +1 415 555-2345
l: San Francisco
ou: Product Development
postalAddress: Product Development$San Francisco
telephoneNumber: +1 415 555-6789
title: Senior Systems Administrator
userPassword: Password1
uid: SmithK
givenName: Karen
mail: SmithK@3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9.bitwarden.com
carLicense: 5M3P7Q
departmentNumber: 1002
employeeType: Employee
homePhone: +1 415 555-0987
initials: K. S.
mobile: +1 415 555-5432
pager: +1 415 555-8901
roomNumber: 7126
manager: cn=Roland Dyke,ou=Human Resources,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
dn: cn=Robert Johnson,ou=Product Development,dc=bitwarden, dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Robert Johnson
sn: Johnson
description: This is Robert Johnson's description
facsimileTelephoneNumber: +1 510 555-3456
l: Oakland
ou: Product Development
postalAddress: Product Development$Oakland
telephoneNumber: +1 510 555-7890
title: Cloud Infrastructure Engineer
userPassword: Password1
uid: JohnsonR
givenName: Robert
mail: JohnsonR@7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3.bitwarden.com
carLicense: 8X2Y4Z
departmentNumber: 1003
employeeType: Employee
homePhone: +1 510 555-1098
initials: R. J.
mobile: +1 510 555-6543
pager: +1 510 555-9012
roomNumber: 7127
manager: cn=Roland Dyke,ou=Human Resources,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
dn: cn=Thomas Williams,ou=Management,dc=bitwarden, dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Thomas Williams
sn: Williams
description: This is Thomas Williams's description
facsimileTelephoneNumber: +1 650 555-4567
l: Palo Alto
ou: Management
postalAddress: Management$Palo Alto
telephoneNumber: +1 650 555-8901
title: Chief Security Officer
userPassword: Password1
uid: WilliamsT
givenName: Thomas
mail: WilliamsT@1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7.bitwarden.com
carLicense: 6H9J2K
departmentNumber: 1004
employeeType: Employee
homePhone: +1 650 555-2109
initials: T. W.
mobile: +1 650 555-7654
pager: +1 650 555-0123
roomNumber: 7128
manager: cn=Roland Dyke,ou=Human Resources,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com
dn: cn=Michelle Brown,ou=Management,dc=bitwarden, dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Michelle Brown
sn: Brown
description: This is Michelle Brown's description
facsimileTelephoneNumber: +1 408 555-5678
l: Santa Clara
ou: Management
postalAddress: Management$Santa Clara
telephoneNumber: +1 408 555-9012
title: Security Analyst
userPassword: Password1
uid: BrownM
givenName: Michelle
mail: BrownM@5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1.bitwarden.com
carLicense: 4T6U8V
departmentNumber: 1005
employeeType: Employee
homePhone: +1 408 555-3210
initials: M. B.
mobile: +1 408 555-8765
pager: +1 408 555-1234
roomNumber: 7129
manager: cn=Roland Dyke,ou=Human Resources,dc=bitwarden, dc=com
secretary: cn=Keven Gilleland,ou=Administrative,dc=bitwarden, dc=com

View File

@@ -144,6 +144,41 @@ const data: Jsonify<UserEntry>[] = [
externalId: "cn=Loella Mak,ou=Payroll,dc=bitwarden,dc=com",
email: "makl@6ab3e25ca49d4d64aaf44844288a8ef7.bitwarden.com",
},
{
disabled: false,
deleted: false,
referenceId: "cn=Benjamin Chen,ou=Product Development,dc=bitwarden,dc=com",
externalId: "cn=Benjamin Chen,ou=Product Development,dc=bitwarden,dc=com",
email: "chenb@9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3.bitwarden.com",
},
{
disabled: false,
deleted: false,
referenceId: "cn=Karen Smith,ou=Product Development,dc=bitwarden,dc=com",
externalId: "cn=Karen Smith,ou=Product Development,dc=bitwarden,dc=com",
email: "smithk@3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9.bitwarden.com",
},
{
disabled: false,
deleted: false,
referenceId: "cn=Robert Johnson,ou=Product Development,dc=bitwarden,dc=com",
externalId: "cn=Robert Johnson,ou=Product Development,dc=bitwarden,dc=com",
email: "johnsonr@7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3.bitwarden.com",
},
{
disabled: false,
deleted: false,
referenceId: "cn=Thomas Williams,ou=Management,dc=bitwarden,dc=com",
externalId: "cn=Thomas Williams,ou=Management,dc=bitwarden,dc=com",
email: "williamst@1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7.bitwarden.com",
},
{
disabled: false,
deleted: false,
referenceId: "cn=Michelle Brown,ou=Management,dc=bitwarden,dc=com",
externalId: "cn=Michelle Brown,ou=Management,dc=bitwarden,dc=com",
email: "brownm@5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1.bitwarden.com",
},
];
export const userFixtures = data.map((v) => UserEntry.fromJSON(v));

View File

@@ -152,4 +152,88 @@ describe("ldapDirectoryService", () => {
expect(result).toEqual([[redTeam], undefined]);
});
});
describe("new groups and users", () => {
it("fetches DevOps Team and Security Team groups", async () => {
stateService.getDirectory
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(
getSyncConfiguration({
groups: true,
groupFilter: "(|(cn=DevOps Team)(cn=Security Team))",
}),
);
const devOpsTeam = groupFixtures.find(
(g) => g.referenceId === "cn=DevOps Team,dc=bitwarden,dc=com",
);
const securityTeam = groupFixtures.find(
(g) => g.referenceId === "cn=Security Team,dc=bitwarden,dc=com",
);
const result = await directoryService.getEntries(true, true);
expect(result[0]).toEqual(expect.arrayContaining([devOpsTeam, securityTeam]));
expect(result[0].length).toEqual(2);
});
it("fetches new users with correct group memberships", async () => {
stateService.getDirectory
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(
getSyncConfiguration({
users: true,
groups: true,
userFilter: "(|(uid=ChenB)(uid=SmithK)(uid=JohnsonR)(uid=WilliamsT)(uid=BrownM))",
}),
);
const newUsers = userFixtures.filter(
(u) =>
u.referenceId === "cn=Benjamin Chen,ou=Product Development,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Karen Smith,ou=Product Development,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Robert Johnson,ou=Product Development,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Thomas Williams,ou=Management,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Michelle Brown,ou=Management,dc=bitwarden,dc=com",
);
const devOpsTeam = groupFixtures.find(
(g) => g.referenceId === "cn=DevOps Team,dc=bitwarden,dc=com",
);
const securityTeam = groupFixtures.find(
(g) => g.referenceId === "cn=Security Team,dc=bitwarden,dc=com",
);
const result = await directoryService.getEntries(true, true);
// Verify users are fetched
expect(result[1]).toEqual(expect.arrayContaining(newUsers));
expect(result[1].length).toEqual(newUsers.length);
// Verify groups are fetched with correct membership
expect(result[0]).toEqual(expect.arrayContaining([devOpsTeam, securityTeam]));
// Verify DevOps Team has 3 members
const fetchedDevOpsTeam = result[0].find((g) => g.name === "DevOps Team");
expect(fetchedDevOpsTeam.userMemberExternalIds.size).toEqual(3);
expect(Array.from(fetchedDevOpsTeam.userMemberExternalIds)).toEqual(
expect.arrayContaining([
"cn=Benjamin Chen,ou=Product Development,dc=bitwarden,dc=com",
"cn=Karen Smith,ou=Product Development,dc=bitwarden,dc=com",
"cn=Robert Johnson,ou=Product Development,dc=bitwarden,dc=com",
]),
);
// Verify Security Team has 2 members
const fetchedSecurityTeam = result[0].find((g) => g.name === "Security Team");
expect(fetchedSecurityTeam.userMemberExternalIds.size).toEqual(2);
expect(Array.from(fetchedSecurityTeam.userMemberExternalIds)).toEqual(
expect.arrayContaining([
"cn=Thomas Williams,ou=Management,dc=bitwarden,dc=com",
"cn=Michelle Brown,ou=Management,dc=bitwarden,dc=com",
]),
);
});
});
});

View File

@@ -192,13 +192,13 @@ 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) => {
const dn = this.getReferenceId(se);
const uid = this.getAttr<string>(se, "uid");
const externalId = this.getExternalId(se, dn);
userIdMap.set(dn, externalId);
userDnMap.set(dn, externalId);
if (uid != null) {
userUidMap.set(uid, externalId);
}
@@ -206,7 +206,7 @@ export class LdapDirectoryService implements IDirectoryService {
});
for (const se of groupSearchEntries) {
const group = this.buildGroup(se, userIdMap, userUidMap);
const group = this.buildGroup(se, userDnMap, userUidMap);
if (group != null) {
entries.push(group);
}
@@ -217,7 +217,7 @@ export class LdapDirectoryService implements IDirectoryService {
private buildGroup(
searchEntry: any,
userMap: Map<string, string>,
userDnMap: Map<string, string>,
userUidMap: Map<string, string>,
) {
const group = new GroupEntry();
@@ -239,24 +239,33 @@ export class LdapDirectoryService implements IDirectoryService {
const members = this.getAttrVals<string>(searchEntry, this.syncConfig.memberAttribute);
if (members != null) {
for (const member of members) {
// Check if member is a DN (contains '=' and ',')
const isDn = member.includes("=") && member.includes(",");
const getMemberAttributeType = (member: string): "memberDn" | "memberUid" | "groupDn" => {
const isDnLike = member.includes("=") && member.includes(",");
if (isDnLike) {
return userDnMap.has(member) ? "memberDn" : "groupDn";
}
return "memberUid";
};
if (isDn) {
// Member is a DN
if (userMap.has(member) && !group.userMemberExternalIds.has(userMap.get(member))) {
group.userMemberExternalIds.add(userMap.get(member));
} else if (!group.groupMemberReferenceIds.has(member)) {
group.groupMemberReferenceIds.add(member);
for (const member of members) {
switch (getMemberAttributeType(member)) {
case "memberDn": {
const externalId = userDnMap.get(member);
if (externalId != null) {
group.userMemberExternalIds.add(externalId);
}
break;
}
} else {
// Member is likely a UID
if (userUidMap.has(member) && !group.userMemberExternalIds.has(userUidMap.get(member))) {
group.userMemberExternalIds.add(userUidMap.get(member));
} else if (!group.groupMemberReferenceIds.has(member)) {
group.groupMemberReferenceIds.add(member);
case "memberUid": {
const externalId = userUidMap.get(member);
if (externalId != null) {
group.userMemberExternalIds.add(externalId);
}
break;
}
case "groupDn":
group.groupMemberReferenceIds.add(member);
break;
}
}
}

View File

@@ -123,7 +123,7 @@ describe("SyncService", () => {
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(
expect.objectContaining({ overwriteExisting: false }),
);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(6);
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(9);
// @ts-expect-error Reset batch size to original state.
constants.batchSize = originalBatchSize;