diff --git a/.changeset/eleven-donuts-bathe.md b/.changeset/eleven-donuts-bathe.md new file mode 100644 index 0000000000..3eb33b087d --- /dev/null +++ b/.changeset/eleven-donuts-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Switched the handling of the `BackstageIdentityResponse` so that the returned `identity.userEntityRef` is always a full entity reference. If `userEntityRef` was previously set to `jane`, it will now be `user:default/jane`. The `userEntityRef` in the response is parsed from the `sub` claim in the payload of the Backstage token. diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 98f4cd9fa1..a7463bb7dc 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -233,7 +233,7 @@ describe('OAuthAdapter', () => { identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', - userEntityRef: 'jimmymarkum', + userEntityRef: 'user:default/jimmymarkum', }, }, }); diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts index fd5fe28505..37b5bc1f68 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts @@ -144,7 +144,7 @@ describe('AwsAlbAuthProvider', () => { identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', - userEntityRef: 'jimmymarkum', + userEntityRef: 'user:default/jimmymarkum', }, }, profile: { diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts index 4e1aeb88bf..048a3b9344 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts @@ -167,7 +167,7 @@ describe('Oauth2ProxyAuthProvider', () => { identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', - userEntityRef: 'jimmymarkum', + userEntityRef: 'user:default/jimmymarkum', }, token: mockToken, }, diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts new file mode 100644 index 0000000000..24442379c4 --- /dev/null +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse'; + +function mkToken(payload: unknown) { + return `a.${Buffer.from(JSON.stringify(payload), 'utf8').toString( + 'base64', + )}.z`; +} + +describe('prepareBackstageIdentityResponse', () => { + it('parses a complete token to determine the identity', () => { + const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'] }); + expect( + prepareBackstageIdentityResponse({ + id: 'x', + token, + }), + ).toEqual({ + id: 'x', + token, + idToken: token, + identity: { + type: 'user', + userEntityRef: 'k:ns/n', + ownershipEntityRefs: ['k:ns/o'], + }, + }); + }); + + it('populates incomplete identities', () => { + expect( + prepareBackstageIdentityResponse({ + id: 'x', + token: mkToken({ sub: 'n' }), + }), + ).toEqual({ + id: 'x', + token: expect.any(String), + idToken: expect.any(String), + identity: { + type: 'user', + userEntityRef: 'user:default/n', + ownershipEntityRefs: [], + }, + }); + expect( + prepareBackstageIdentityResponse({ + id: 'x', + token: mkToken({ sub: 'k:n' }), + }), + ).toEqual({ + id: 'x', + token: expect.any(String), + idToken: expect.any(String), + identity: { + type: 'user', + userEntityRef: 'k:default/n', + ownershipEntityRefs: [], + }, + }); + expect( + prepareBackstageIdentityResponse({ + id: 'x', + token: mkToken({ sub: 'ns/n' }), + }), + ).toEqual({ + id: 'x', + token: expect.any(String), + idToken: expect.any(String), + identity: { + type: 'user', + userEntityRef: 'user:ns/n', + ownershipEntityRefs: [], + }, + }); + expect( + prepareBackstageIdentityResponse({ + id: 'x', + token: mkToken({ sub: 'n', ent: ['k:ns/o'] }), + }), + ).toEqual({ + id: 'x', + token: expect.any(String), + idToken: expect.any(String), + identity: { + type: 'user', + userEntityRef: 'user:default/n', + ownershipEntityRefs: ['k:ns/o'], + }, + }); + }); +}); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts index 1de8ebb9b2..bb99cb3487 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult } from './types'; function parseJwtPayload(token: string) { @@ -32,6 +37,13 @@ export function prepareBackstageIdentityResponse( result: BackstageSignInResult, ): BackstageIdentityResponse { const { sub, ent } = parseJwtPayload(result.token); + + const userEntityRef = stringifyEntityRef( + parseEntityRef(sub, { + defaultKind: 'user', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }), + ); return { ...{ // TODO: idToken is for backwards compatibility and can be removed in the future @@ -40,7 +52,7 @@ export function prepareBackstageIdentityResponse( }, identity: { type: 'user', - userEntityRef: sub, + userEntityRef, ownershipEntityRefs: ent ?? [], }, };