catalog-react: deprecate loadIdentityOwnerRefs and simplify implementation
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Deprecated `loadIdentityOwnerRefs`, since they can now be retrieved as `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead.
|
||||
@@ -781,7 +781,7 @@ export function loadCatalogOwnerRefs(
|
||||
identityOwnerRefs: string[],
|
||||
): Promise<string[]>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function loadIdentityOwnerRefs(
|
||||
identityApi: IdentityApi,
|
||||
): Promise<string[]>;
|
||||
|
||||
@@ -33,14 +33,11 @@ import {
|
||||
} from './useEntityOwnership';
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
type MockIdentityApi = jest.Mocked<
|
||||
Pick<IdentityApi, 'getUserId' | 'getIdToken'>
|
||||
>;
|
||||
type MockIdentityApi = jest.Mocked<Pick<IdentityApi, 'getBackstageIdentity'>>;
|
||||
type MockCatalogApi = jest.Mocked<Pick<CatalogApi, 'getEntityByName'>>;
|
||||
|
||||
const mockIdentityApi: MockIdentityApi = {
|
||||
getUserId: jest.fn(),
|
||||
getIdToken: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
};
|
||||
const mockCatalogApi: MockCatalogApi = {
|
||||
getEntityByName: jest.fn(),
|
||||
@@ -100,80 +97,19 @@ describe('useEntityOwnership', () => {
|
||||
],
|
||||
};
|
||||
|
||||
// these were generated on https://jwt.io, based off of its default example token
|
||||
// no ent at all
|
||||
const tokenNoEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
// "ent": []
|
||||
const tokenEmptyEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOltdfQ.Khyza2whczkoC4wSCLBhBaBB9-ktIkk7gpXEgQPHhtY';
|
||||
// "ent": ["user:default/user1"]
|
||||
const tokenUserEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIl19.CMCxjwI4rj_TD3uUoBNgFjkZI23LwRTbQnSPBxzncoY';
|
||||
// "ent": ["user:default/user1", "group:default/group1"]
|
||||
const tokenUserAndGroupEnt =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJlbnQiOlsidXNlcjpkZWZhdWx0L3VzZXIxIiwiZ3JvdXA6ZGVmYXVsdC9ncm91cDEiXX0.ZZmZrogbQKx0hnForw63ETkyAhUyeoBE8Hgloi45rdg';
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('loadIdentityOwnerRefs', () => {
|
||||
it('returns the user id when there is no relevant token info', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('user:ns/foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(undefined);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:ns/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenNoEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenEmptyEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns both the user id and the token parts', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
]);
|
||||
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce(tokenUserAndGroupEnt);
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
'user:default/user1',
|
||||
'group:default/group1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('gracefully ignores broken token', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValueOnce('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValueOnce('not a jwt');
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toEqual([
|
||||
'user:default/foo',
|
||||
]);
|
||||
it('passes through the ownershipEntityRefs', async () => {
|
||||
const refs = new Array<string>();
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: refs,
|
||||
});
|
||||
await expect(loadIdentityOwnerRefs(identityApi)).resolves.toBe(refs);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,9 +140,12 @@ describe('useEntityOwnership', () => {
|
||||
});
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
it('matches ownership via token claims', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('foo');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(tokenUserAndGroupEnt);
|
||||
it('matches ownership via ownership entity refs', async () => {
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValue({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/user1',
|
||||
ownershipEntityRefs: ['user:default/user1', 'group:default/group1'],
|
||||
});
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(undefined);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
@@ -224,32 +163,5 @@ describe('useEntityOwnership', () => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
});
|
||||
|
||||
it('matches ownership via catalog user entity', async () => {
|
||||
mockIdentityApi.getUserId.mockReturnValue('user2');
|
||||
mockIdentityApi.getIdToken.mockResolvedValue(undefined);
|
||||
mockCatalogApi.getEntityByName.mockResolvedValue(user2Entity);
|
||||
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityOwnership(),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(false);
|
||||
|
||||
await waitForValueToChange(() => result.current.loading);
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.isOwnedEntity(ownedEntity)).toBe(true);
|
||||
|
||||
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'user2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,33 +28,18 @@ import {
|
||||
identityApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import jwtDecoder from 'jwt-decode';
|
||||
import { useMemo } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { getEntityRelations } from '../utils/getEntityRelations';
|
||||
|
||||
// Takes a user ID from the identity, which can be on basically any form, and
|
||||
// returns an entity ref. E.g. if the input is "foo", it returns
|
||||
// "user:default/foo" to make sure it's a full ref.
|
||||
function extendUserId(id: string): string {
|
||||
try {
|
||||
const ref = parseEntityRef(id, {
|
||||
defaultKind: 'User',
|
||||
defaultNamespace: 'default',
|
||||
});
|
||||
return stringifyEntityRef(ref);
|
||||
} catch {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the relevant parts of the Backstage identity, and translates them into
|
||||
* a list of entity refs on string form that represent the user's ownership
|
||||
* connections.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead.
|
||||
*
|
||||
* @param identityApi - The IdentityApi implementation
|
||||
* @returns IdentityOwner refs as a string array
|
||||
@@ -62,30 +47,8 @@ function extendUserId(id: string): string {
|
||||
export async function loadIdentityOwnerRefs(
|
||||
identityApi: IdentityApi,
|
||||
): Promise<string[]> {
|
||||
const id = identityApi.getUserId();
|
||||
const token = await identityApi.getIdToken();
|
||||
const result: string[] = [];
|
||||
|
||||
if (id) {
|
||||
result.push(extendUserId(id));
|
||||
}
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwtDecoder(token) as any;
|
||||
if (decoded?.ent) {
|
||||
[decoded.ent]
|
||||
.flat()
|
||||
.filter(x => typeof x === 'string')
|
||||
.map(x => x.toLocaleLowerCase('en-US'))
|
||||
.forEach(x => result.push(x));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
const identity = await identityApi.getBackstageIdentity();
|
||||
return identity.ownershipEntityRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,9 +105,12 @@ export function useEntityOwnership(): {
|
||||
|
||||
// Trigger load only on mount
|
||||
const { loading, value: refs } = useAsync(async () => {
|
||||
const identityRefs = await loadIdentityOwnerRefs(identityApi);
|
||||
const catalogRefs = await loadCatalogOwnerRefs(catalogApi, identityRefs);
|
||||
return new Set([...identityRefs, ...catalogRefs]);
|
||||
const { ownershipEntityRefs } = await identityApi.getBackstageIdentity();
|
||||
const catalogRefs = await loadCatalogOwnerRefs(
|
||||
catalogApi,
|
||||
ownershipEntityRefs,
|
||||
);
|
||||
return new Set([...ownershipEntityRefs, ...catalogRefs]);
|
||||
}, []);
|
||||
|
||||
const isOwnedEntity = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user