Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BED-4868 Wrong Reader Count for AZKeyVaults #1017

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 101 additions & 10 deletions cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import { render, screen } from 'src/test-utils';
import userEvent from '@testing-library/user-event';
import { rest, RequestHandler } from 'msw';
import { setupServer } from 'msw/node';
import { ActiveDirectoryNodeKind, allSections } from 'bh-shared-ui';
import { ActiveDirectoryNodeKind, allSections, AzureNodeKind } from 'bh-shared-ui';
import EntityInfoDataTable from './EntityInfoDataTable';
import { EntityInfoPanelContextProvider } from './EntityInfoPanelContextProvider';

const objectId = 'fake-object-id';
const sections = allSections[ActiveDirectoryNodeKind.GPO]!(objectId);
const adGpoSections = allSections[ActiveDirectoryNodeKind.GPO]!(objectId);
const azKeyVaultSections = allSections[AzureNodeKind.KeyVault]!(objectId);

const queryCount = {
controllers: {
Expand Down Expand Up @@ -58,25 +59,70 @@ const queryCount = {
},
} as const;

const keyVaultTest = {
KeyReaders: {
count: 0,
limit: 128,
skip: 0,
data: [],
},
CertificateReaders: {
count: 8,
limit: 128,
skip: 0,
data: [],
},
SecretReaders: {
count: 3003,
limit: 128,
skip: 0,
data: [],
},
AllReaders: {
count: 1998,
limit: 128,
skip: 0,
data: [],
},
} as const;

const handlers: Array<RequestHandler> = [
rest.get(`api/v2/gpos/${objectId}/:asset`, (req, res, ctx) => {
const asset = req.params.asset as keyof typeof queryCount;
return res(ctx.json(queryCount[asset]));
}),

rest.get(`api/v2/azure/key-vaults*`, (req, res, ctx) => {
if (req.url.searchParams.get('related_entity_type') === 'all-readers') {
return res(ctx.json(keyVaultTest.AllReaders));
}

if (req.url.searchParams.get('related_entity_type') === 'key-readers') {
return res(ctx.json(keyVaultTest.KeyReaders));
}

if (req.url.searchParams.get('related_entity_type') === 'secret-readers') {
return res(ctx.json(keyVaultTest.SecretReaders));
}

if (req.url.searchParams.get('related_entity_type') === 'certificate-readers') {
return res(ctx.json(keyVaultTest.CertificateReaders));
}
}),
];

const server = setupServer(...handlers);

describe('EntityInfoDataTable', () => {
describe('Node count', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('EntityInfoDataTable', () => {
describe('Node count for nested table that counts all sections', () => {
it('sums nested section node counts', async () => {
render(
<EntityInfoPanelContextProvider>
<EntityInfoDataTable {...sections[0]} />
<EntityInfoDataTable {...adGpoSections[0]} />
</EntityInfoPanelContextProvider>
);
const sum = await screen.findByText('5,064');
Expand All @@ -93,7 +139,7 @@ describe('EntityInfoDataTable', () => {

render(
<EntityInfoPanelContextProvider>
<EntityInfoDataTable {...sections[0]} />
<EntityInfoDataTable {...adGpoSections[0]} />
</EntityInfoPanelContextProvider>
);

Expand All @@ -113,7 +159,7 @@ describe('EntityInfoDataTable', () => {
const user = userEvent.setup();
render(
<EntityInfoPanelContextProvider>
<EntityInfoDataTable {...sections[0]} />
<EntityInfoDataTable {...adGpoSections[0]} />
</EntityInfoPanelContextProvider>
);

Expand All @@ -127,4 +173,49 @@ describe('EntityInfoDataTable', () => {
expect(zero.textContent).toBe('0');
});
});

describe('Node count for Vault Readers nested table', () => {
it('Verify Vault Reader count is the count returned by All Readers', async () => {
const user = userEvent.setup();

render(
<EntityInfoPanelContextProvider>
<EntityInfoDataTable {...azKeyVaultSections[0]} />
</EntityInfoPanelContextProvider>
);

// wait for the total count to be available - this holds off all following tests until the counts have been returned
const sum = await screen.findByText('1,998');
expect(sum).not.toBeNull();

// verify the vault reader count is as expected
const vaultReadersHeader = await screen.findByText('Vault Readers');
const vaultReadersCount = vaultReadersHeader.nextElementSibling;
expect(vaultReadersCount).toHaveTextContent('1,998');

// expand the Vault Readers accordian
const button = await screen.findByRole('button');
await user.click(button);

// verify the key readers count is as expected
const keyReadersHeader = await screen.findByText('Key Readers');
const keyReadersCount = keyReadersHeader.nextElementSibling;
expect(keyReadersCount).toHaveTextContent('0');

// verify the certificate readers count is as expected
const certReadersHeader = await screen.findByText('Certificate Readers');
const certReadersCount = certReadersHeader.nextElementSibling;
expect(certReadersCount).toHaveTextContent('8');

// verify the secret readers count is as expected
const secretReadersHeader = await screen.findByText('Secret Readers');
const secretReadersCount = secretReadersHeader.nextElementSibling;
expect(secretReadersCount).toHaveTextContent('3,003');

// verify the all readers count is as expected
const allReadersHeader = await screen.findByText('All Readers');
const allReadersCount = allReadersHeader.nextElementSibling;
expect(allReadersCount).toHaveTextContent('1,998');
});
});
});
18 changes: 12 additions & 6 deletions cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { addSnackbar } from 'src/ducks/global/actions';
import { transformFlatGraphResponse } from 'src/utils';
import EntityInfoCollapsibleSection from './EntityInfoCollapsibleSection';

const EntityInfoDataTable: React.FC<EntityInfoDataTableProps> = ({ id, label, endpoint, sections }) => {
const EntityInfoDataTable: React.FC<EntityInfoDataTableProps> = ({ id, label, endpoint, countLabel, sections }) => {
const dispatch = useDispatch();

const countQuery = useQuery(
Expand Down Expand Up @@ -83,12 +83,18 @@ const EntityInfoDataTable: React.FC<EntityInfoDataTableProps> = ({ id, label, en

let count: number | undefined;
if (Array.isArray(countQuery.data)) {
count = countQuery.data.reduce((acc, val) => {
const count = val.count ?? 0;
return acc + count;
}, 0);
if (countLabel !== undefined) {
countQuery.data.forEach((sectionData: any) => {
if (sectionData.countLabel === countLabel) count = sectionData.count;
});
} else {
count = countQuery.data.reduce((acc, val) => {
const count = val?.count ?? 0;
return acc + count;
}, 0);
}
} else if (countQuery.data) {
count = countQuery.data.count ?? 0;
count = countQuery.data?.count ?? 0;
}

return (
Expand Down
23 changes: 19 additions & 4 deletions packages/javascript/bh-shared-ui/src/utils/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface EntityInfoDataTableProps {
id: string;
label: string;
endpoint?: ({ counts, skip, limit, type }: EntitySectionEndpointParams) => Promise<any>;
countLabel?: string;
sections?: EntityInfoDataTableProps[];
}

Expand Down Expand Up @@ -286,6 +287,7 @@ export const allSections: Partial<Record<EntityKinds, (id: string) => EntityInfo
{
id,
label: 'Vault Readers',
countLabel: 'All Readers',
sections: [
{
id,
Expand All @@ -295,7 +297,11 @@ export const allSections: Partial<Record<EntityKinds, (id: string) => EntityInfo
.getAZEntityInfoV2('key-vaults', id, 'key-readers', counts, skip, limit, type, {
signal: controller.signal,
})
.then((res) => res.data),
.then((res) => {
if (type !== 'graph') res.data.countLabel = 'Key Readers';
return res.data;
}),

},
{
id,
Expand All @@ -305,7 +311,10 @@ export const allSections: Partial<Record<EntityKinds, (id: string) => EntityInfo
.getAZEntityInfoV2('key-vaults', id, 'certificate-readers', counts, skip, limit, type, {
signal: controller.signal,
})
.then((res) => res.data),
.then((res) => {
if (type !== 'graph') res.data.countLabel = 'Certificate Readers';
return res.data;
}),
},
{
id,
Expand All @@ -315,7 +324,10 @@ export const allSections: Partial<Record<EntityKinds, (id: string) => EntityInfo
.getAZEntityInfoV2('key-vaults', id, 'secret-readers', counts, skip, limit, type, {
signal: controller.signal,
})
.then((res) => res.data),
.then((res) => {
if (type !== 'graph') res.data.countLabel = 'Secret Readers';
return res.data;
}),
},
{
id,
Expand All @@ -325,7 +337,10 @@ export const allSections: Partial<Record<EntityKinds, (id: string) => EntityInfo
.getAZEntityInfoV2('key-vaults', id, 'all-readers', counts, skip, limit, type, {
signal: controller.signal,
})
.then((res) => res.data),
.then((res) => {
if (type !== 'graph') res.data.countLabel = 'All Readers';
return res.data;
}),
},
],
},
Expand Down
Loading