diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 482d4d0314..e772832c1e 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -135,8 +135,7 @@ export default async function createPlugin({ // Resolve group membership from the Backstage catalog const fullEnt = await ctx.catalogIdentityClient.resolveCatalogMembership({ - sub, - ent, + entityRefs: [sub].concat(ent), logger: ctx.logger, }); const token = await ctx.tokenIssuer.issueToken({ @@ -149,8 +148,8 @@ export default async function createPlugin({ ... ``` -The `resolveCatalogMembership` method will retrieve the `sub` and `ent` entities -from the catalog, if possible, and check for +The `resolveCatalogMembership` method will retrieve the referenced entities from +the catalog, if possible, and check for [memberOf](../features/software-catalog/well-known-relations.md#memberof-and-hasmember) relations to add additional entity claims. diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index c55ae17919..6ffcfd6f46 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -119,8 +119,7 @@ describe('CatalogIdentityClient', () => { }); const claims = await client.resolveCatalogMembership({ - sub: 'inigom', - ent: ['User:default/imontoya', 'User:reality/mpatinkin'], + entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'], }); expect(catalogApi.getEntities).toHaveBeenCalledWith({ @@ -144,6 +143,7 @@ describe('CatalogIdentityClient', () => { }); expect(claims).toMatchObject([ + 'user:default/inigom', 'user:default/imontoya', 'user:reality/mpatinkin', 'group:default/team-a', diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index 179843839c..6e8494f805 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -31,8 +31,7 @@ type UserQuery = { }; type MemberClaimQuery = { - sub: string; - ent: string[]; + entityRefs: string[]; logger?: Logger; }; @@ -79,41 +78,33 @@ export class CatalogIdentityClient { } /** - * Resolve additional entity claims from the catalog, using the passed-in subject and entity - * claims. Designed to be used within a `signInResolver` where additional entity claims might be - * provided, but group membership and transient group membership lean on imported catalog - * relations. + * Resolve additional entity claims from the catalog, using the passed-in entity names. Designed + * to be used within a `signInResolver` where additional entity claims might be provided, but + * group membership and transient group membership lean on imported catalog relations. * - * Returns a superset of the `ent` argument that can be passed directly to `issueToken` as `ent`. + * Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`. */ async resolveCatalogMembership({ - sub, - ent, + entityRefs, logger, }: MemberClaimQuery): Promise { - const subRef: EntityName = parseEntityRef(sub, { - defaultKind: 'user', - defaultNamespace: 'default', - }); + let resolvedEntityRefs: Array = []; + resolvedEntityRefs = entityRefs + .map((ref: string) => { + try { + const parsedRef = parseEntityRef(ref.toLocaleLowerCase('en-US'), { + defaultKind: 'user', + defaultNamespace: 'default', + }); + return parsedRef; + } catch { + logger?.debug(`Failed to parse entityRef from ${ref}, ignoring`); + return null; + } + }) + .filter((ref): ref is EntityName => ref !== null); - let entityRefs: Array = []; - if (ent) { - entityRefs = ent - .map((ref: string) => { - try { - const parsedRef = parseEntityRef(ref.toLocaleLowerCase('en-US')); - return parsedRef; - } catch { - logger?.debug( - `Failed to parse entityRef from '${sub}' ent claim: ${ref}, ignoring`, - ); - return null; - } - }) - .filter((ref): ref is EntityName => ref !== null); - } - - const filter = [subRef].concat(entityRefs).map(ref => ({ + const filter = resolvedEntityRefs.map(ref => ({ kind: ref.kind, 'metadata.namespace': ref.namespace, 'metadata.name': ref.name, @@ -124,12 +115,10 @@ export class CatalogIdentityClient { if (entityRefs.length !== entities.length) { const foundEntityNames = entities.map(stringifyEntityRef); - const missingEntityNames = entityRefs + const missingEntityNames = resolvedEntityRefs .map(stringifyEntityRef) .filter(s => !foundEntityNames.includes(s)); - logger?.debug( - `Entities not found for '${sub}' claims: ${missingEntityNames.join()}`, - ); + logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`); } const memberOf = entities.flatMap( @@ -139,11 +128,11 @@ export class CatalogIdentityClient { .map(r => r.target) ?? [], ); - const newEnt = [...new Set(entityRefs.concat(memberOf))].map( + const newEntityRefs = [...new Set(resolvedEntityRefs.concat(memberOf))].map( stringifyEntityRef, ); - logger?.debug(`Found claims for ${sub} in the catalog: ${newEnt.join()}`); - return newEnt; + logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`); + return newEntityRefs; } }