fix: Updated patch for original PR26923 that filters ldap users

Signed-off-by: John Redwood <john.r.k.redwood@gmail.com>
This commit is contained in:
John Redwood
2024-11-23 00:27:02 +11:00
parent 78d34b172c
commit 732700ab82
6 changed files with 51 additions and 67 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-ldap': minor
---
Updated fix for ldap entity mapping which doesn't require extra config setting of dnCaseSensitive
@@ -205,7 +205,6 @@ export type LdapProviderConfig = {
export type LdapVendor = {
dnAttributeName: string;
uuidAttributeName: string;
dnCaseSensitive?: boolean;
decodeStringAttribute: (entry: SearchEntry, name: string) => string[];
};
@@ -275,6 +274,5 @@ export type UserTransformer = (
export type VendorConfig = {
dnAttributeName?: string;
uuidAttributeName?: string;
dnCaseSensitive?: boolean;
};
```
@@ -181,13 +181,6 @@ export type VendorConfig = {
* Attribute name for the unique identifier (UUID) of an entry,
*/
uuidAttributeName?: string;
/**
* Attribute to determine if we need to force the DN and members/memberOf values to be forced to same case.
* Some providers may provide lowercase members but multicase DN names which causes the group filtering to break.
* The default is off, but turning this on forces the inbound DN values and all member values to lowercase.
*/
dnCaseSensitive?: boolean;
};
const defaultUserConfig = {
@@ -263,7 +256,6 @@ function readVendorConfig(
return {
dnAttributeName: c.getOptionalString('dnAttributeName'),
uuidAttributeName: c.getOptionalString('uuidAttributeName'),
dnCaseSensitive: c.getOptionalBoolean('dnCaseSensitive'),
};
}
@@ -333,7 +333,6 @@ describe('readLdapUsers', () => {
it('transfers all attributes from for vendorDN case sensitivity', async () => {
const vendor = DefaultLdapVendor;
vendor.dnCaseSensitive = true;
client.getVendor.mockResolvedValue(vendor);
client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => {
await fn(
@@ -365,15 +364,13 @@ describe('readLdapUsers', () => {
},
];
const { users, userMemberOf } = await readLdapUsers(client, config, vendor);
// reset dnCaseSensitivity
vendor.dnCaseSensitive = false;
expect(users).toEqual([
expect.objectContaining({
metadata: {
name: 'uid-value',
description: 'description-value',
annotations: {
[LDAP_DN_ANNOTATION]: 'dn-value',
[LDAP_DN_ANNOTATION]: 'dn-VALUE',
[LDAP_RDN_ANNOTATION]: 'uid-value',
[LDAP_UUID_ANNOTATION]: 'uuid-value',
},
@@ -389,7 +386,7 @@ describe('readLdapUsers', () => {
}),
]);
expect(userMemberOf).toEqual(
new Map([['dn-value', new Set(['x', 'y', 'z'])]]),
new Map([['dn-VALUE', new Set(['x', 'Y', 'z'])]]),
);
});
it('fails to transfer all attributes from for due case sensitivity', async () => {
@@ -1111,14 +1108,13 @@ describe('defaultUserTransformerWithCaseSensitiveDNs', () => {
});
const vendor = DefaultLdapVendor;
vendor.dnCaseSensitive = true;
let output = await defaultUserTransformer(vendor, config, entry);
expect(output).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'User',
metadata: {
annotations: {
'backstage.io/ldap-dn': 'dn-value',
'backstage.io/ldap-dn': 'dn-VALUE',
'backstage.io/ldap-rdn': 'uid-value',
'backstage.io/ldap-uuid': 'uuid-value',
a: 2,
@@ -1136,14 +1132,12 @@ describe('defaultUserTransformerWithCaseSensitiveDNs', () => {
// exact same inputs again
output = await defaultUserTransformer(vendor, config, entry);
// reset dnCaseSensitivity
vendor.dnCaseSensitive = false;
expect(output).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'User',
metadata: {
annotations: {
'backstage.io/ldap-dn': 'dn-value',
'backstage.io/ldap-dn': 'dn-VALUE',
'backstage.io/ldap-rdn': 'uid-value',
'backstage.io/ldap-uuid': 'uuid-value',
a: 2,
@@ -1192,7 +1186,6 @@ describe('defaultGroupTransformerWithCaseSensitiveDNs', () => {
});
const vendor = DefaultLdapVendor;
vendor.dnCaseSensitive = true;
let output = await defaultGroupTransformer(vendor, config, entry);
expect(output).toEqual({
@@ -1200,7 +1193,7 @@ describe('defaultGroupTransformerWithCaseSensitiveDNs', () => {
kind: 'Group',
metadata: {
annotations: {
'backstage.io/ldap-dn': 'dn-value',
'backstage.io/ldap-dn': 'dn-VALUE',
'backstage.io/ldap-rdn': 'uid-value',
'backstage.io/ldap-uuid': 'uuid-value',
a: 2,
@@ -1220,14 +1213,12 @@ describe('defaultGroupTransformerWithCaseSensitiveDNs', () => {
// exact same inputs again
output = await defaultGroupTransformer(vendor, config, entry);
// reset dnCaseSensitivity
vendor.dnCaseSensitive = false;
expect(output).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'Group',
metadata: {
annotations: {
'backstage.io/ldap-dn': 'dn-value',
'backstage.io/ldap-dn': 'dn-VALUE',
'backstage.io/ldap-rdn': 'uid-value',
'backstage.io/ldap-uuid': 'uuid-value',
a: 2,
@@ -80,10 +80,7 @@ export async function defaultUserTransformer(
entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;
});
mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {
entity.metadata.annotations![LDAP_DN_ANNOTATION] = getCaseSensitivityValue(
v,
vendor,
);
entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;
});
mapStringAttr(entry, vendor, map.displayName, v => {
entity.spec.profile!.displayName = v;
@@ -125,8 +122,6 @@ export async function readLdapUsers(
vendorConfig?.dnAttributeName ?? vendorDefaults.dnAttributeName,
uuidAttributeName:
vendorConfig?.uuidAttributeName ?? vendorDefaults.uuidAttributeName,
dnCaseSensitive:
vendorConfig?.dnCaseSensitive ?? vendorDefaults.dnCaseSensitive,
decodeStringAttribute: vendorDefaults.decodeStringAttribute,
};
const transformer = opts?.transformer ?? defaultUserTransformer;
@@ -195,10 +190,7 @@ export async function defaultGroupTransformer(
entity.metadata.annotations![LDAP_UUID_ANNOTATION] = v;
});
mapStringAttr(entry, vendor, vendor.dnAttributeName, v => {
entity.metadata.annotations![LDAP_DN_ANNOTATION] = getCaseSensitivityValue(
v,
vendor,
);
entity.metadata.annotations![LDAP_DN_ANNOTATION] = v;
});
mapStringAttr(entry, vendor, map.type, v => {
entity.spec.type = v;
@@ -248,8 +240,6 @@ export async function readLdapGroups(
vendorConfig?.dnAttributeName ?? vendorDefaults.dnAttributeName,
uuidAttributeName:
vendorConfig?.uuidAttributeName ?? vendorDefaults.uuidAttributeName,
dnCaseSensitive:
vendorConfig?.dnCaseSensitive ?? vendorDefaults.dnCaseSensitive,
decodeStringAttribute: vendorDefaults.decodeStringAttribute,
};
@@ -351,25 +341,11 @@ function mapReferencesAttr(
const values = vendor.decodeStringAttribute(entry, attributeName);
const dn = vendor.decodeStringAttribute(entry, vendor.dnAttributeName);
if (values && dn && dn.length === 1) {
if (vendor.dnCaseSensitive) {
setter(
dn[0].toLocaleLowerCase('en-US'),
values.map(v => v.toLocaleLowerCase('en-US')),
);
} else {
setter(dn[0], values);
}
setter(dn[0], values);
}
}
}
/** Validates value exists and if required forced sensitivty value to lowercase */
function getCaseSensitivityValue(value: string, vendor: LdapVendor) {
return value && vendor.dnCaseSensitive
? value.toLocaleLowerCase('en-US')
: value;
}
// Inserts a number of values in a key-values mapping
function ensureItems(
target: Map<string, Set<string>>,
@@ -390,6 +366,27 @@ function ensureItems(
}
}
/**
* Helper function to search a Map ignoring case of the key which is the DN where
* memberOf could potentially return in lowercase.
* @param map - The map of DN's user or group as the key generally multicased.
* @param searchValue - The DN/memberOf search criteria which could potentially not match the map DN by case.
* @returns The value/result of the search criteria forcing key/search lowercase.
*/
function getValueFromMapWithInsensitiveKey(
map: Map<string, any>,
searchValue: string,
) {
for (const [key, value] of map) {
if (
key.toLocaleLowerCase('en-US') === searchValue.toLocaleLowerCase('en-US')
) {
return value;
}
}
return undefined; // In case no match is found, return undefined
}
/**
* Takes groups and entities with empty relations, and fills in the various
* relations that were returned by the readers, and forms the org hierarchy.
@@ -450,10 +447,10 @@ export function resolveRelations(
// express relations in different directions. Some may have a user memberOf
// overlay, some don't, for example.
for (const [userN, groupsN] of userMemberOf.entries()) {
const user = userMap.get(userN);
const user = getValueFromMapWithInsensitiveKey(userMap, userN);
if (user) {
for (const groupN of groupsN) {
const group = groupMap.get(groupN);
const group = getValueFromMapWithInsensitiveKey(groupMap, groupN);
if (group) {
ensureItems(newUserMemberOf, stringifyEntityRef(user), [
stringifyEntityRef(group),
@@ -463,10 +460,13 @@ export function resolveRelations(
}
}
for (const [groupN, parentsN] of groupMemberOf.entries()) {
const group = groupMap.get(groupN);
const group = getValueFromMapWithInsensitiveKey(groupMap, groupN);
if (group) {
for (const parentN of parentsN) {
const parentGroup = groupMap.get(parentN);
const parentGroup = getValueFromMapWithInsensitiveKey(
groupMap,
parentN,
);
if (parentGroup) {
ensureItems(newGroupParents, stringifyEntityRef(group), [
stringifyEntityRef(parentGroup),
@@ -479,18 +479,21 @@ export function resolveRelations(
}
}
for (const [groupN, membersN] of groupMember.entries()) {
const group = groupMap.get(groupN);
const group = getValueFromMapWithInsensitiveKey(groupMap, groupN);
if (group) {
for (const memberN of membersN) {
// Group members can be both users and groups in the input model, so
// try both
const memberUser = userMap.get(memberN);
const memberUser = getValueFromMapWithInsensitiveKey(userMap, memberN);
if (memberUser) {
ensureItems(newUserMemberOf, stringifyEntityRef(memberUser), [
stringifyEntityRef(group),
]);
} else {
const memberGroup = groupMap.get(memberN);
const memberGroup = getValueFromMapWithInsensitiveKey(
groupMap,
memberN,
);
if (memberGroup) {
ensureItems(newGroupChildren, stringifyEntityRef(group), [
stringifyEntityRef(memberGroup),
@@ -506,21 +509,21 @@ export function resolveRelations(
// Write down the relations again into the actual entities
for (const [userN, groupsN] of newUserMemberOf.entries()) {
const user = userMap.get(userN);
const user = getValueFromMapWithInsensitiveKey(userMap, userN);
if (user) {
user.spec.memberOf = Array.from(groupsN).sort();
}
}
for (const [groupN, parentsN] of newGroupParents.entries()) {
if (parentsN.size === 1) {
const group = groupMap.get(groupN);
const group = getValueFromMapWithInsensitiveKey(groupMap, groupN);
if (group) {
group.spec.parent = parentsN.values().next().value;
}
}
}
for (const [groupN, childrenN] of newGroupChildren.entries()) {
const group = groupMap.get(groupN);
const group = getValueFromMapWithInsensitiveKey(groupMap, groupN);
if (group) {
group.spec.children = Array.from(childrenN).sort();
}
@@ -31,11 +31,6 @@ export type LdapVendor = {
*/
uuidAttributeName: string;
/**
* The attribute that determines behaviour of the (dn,members,memberOf) for entries.
*/
dnCaseSensitive?: boolean;
/**
* Decode ldap entry values for a given attribute name to their string representation.
*