diff --git a/.changeset/hungry-cougars-press.md b/.changeset/hungry-cougars-press.md new file mode 100644 index 0000000000..c99e1f4c76 --- /dev/null +++ b/.changeset/hungry-cougars-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Fix mapping between users and groups for FreeIPA when using the LdapOrgProcessor diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index e7028f34f1..dbd3f4ff43 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -12,6 +12,10 @@ groups - directly from an LDAP compatible service. The result is a hierarchy of [`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind entities that mirror your org setup. +## Supported vendors + +Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md). + ## Installation This guide will use the Entity Provider method. If you for some reason prefer diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index a9283ecb94..d9d0faf9a2 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -23,6 +23,7 @@ import { errorString } from './util'; import { ActiveDirectoryVendor, DefaultLdapVendor, + FreeIpaVendor, LdapVendor, } from './vendors'; @@ -199,6 +200,8 @@ export class LdapClient { .then(root => { if (root && root.raw?.forestFunctionality) { return ActiveDirectoryVendor; + } else if (root && root.raw?.ipaDomainLevel) { + return FreeIpaVendor; } return DefaultLdapVendor; }) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 49803b9c39..fc451fde97 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -32,7 +32,11 @@ import { resolveRelations, } from './read'; import { RecursivePartial } from './util'; -import { ActiveDirectoryVendor, DefaultLdapVendor } from './vendors'; +import { + ActiveDirectoryVendor, + DefaultLdapVendor, + FreeIpaVendor, +} from './vendors'; function user(data: RecursivePartial): UserEntity { return merge( @@ -195,6 +199,62 @@ describe('readLdapUsers', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + + it('transfers all attributes from FreeIPA', async () => { + client.getVendor.mockResolvedValue(FreeIpaVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + dn: ['dn-value'], + ipaUniqueID: ['uuid-value'], + }), + ); + }); + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, + }; + const { users, userMemberOf } = await readLdapUsers(client, config); + expect(users).toEqual([ + expect.objectContaining({ + metadata: { + name: 'uid-value', + description: 'description-value', + annotations: { + [LDAP_DN_ANNOTATION]: 'dn-value', + [LDAP_RDN_ANNOTATION]: 'uid-value', + [LDAP_UUID_ANNOTATION]: 'uuid-value', + }, + }, + spec: { + profile: { + displayName: 'cn-value', + email: 'mail-value', + picture: 'avatarUrl-value', + }, + memberOf: [], + }, + }), + ]); + expect(userMemberOf).toEqual( + new Map([['dn-value', new Set(['x', 'y', 'z'])]]), + ); + }); }); describe('readLdapGroups', () => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts index 447df8aaa5..d2df250638 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts @@ -63,6 +63,16 @@ export const ActiveDirectoryVendor: LdapVendor = { }, }; +export const FreeIpaVendor: LdapVendor = { + dnAttributeName: 'dn', + uuidAttributeName: 'ipaUniqueID', + decodeStringAttribute: (entry, name) => { + return decode(entry, name, value => { + return value.toString(); + }); + }, +}; + // Decode an attribute to a consumer function decode( entry: SearchEntry,