Skip to content

Commit

Permalink
Allow super user access to the coral UI (#2455)
Browse files Browse the repository at this point in the history
* Allow super user access to the coral UI.

Signed-off-by: Aindriu Lavelle <[email protected]>

* Remove unnessecary const and unit test

Signed-off-by: Aindriu Lavelle <[email protected]>

* Allow super user access to the coral UI.

Signed-off-by: Aindriu Lavelle <[email protected]>

* Update to fix dashboard landing page

Signed-off-by: Aindriu Lavelle <[email protected]>

* Remove request new topic button for superadmin

Signed-off-by: Mirjam Aulbach <[email protected]>

* Remove request new connector button for superadmin

Signed-off-by: Mirjam Aulbach <[email protected]>

* Remove unused variable

Signed-off-by: Mirjam Aulbach <[email protected]>

* Remove unneeded test for AuthUser

Signed-off-by: Mirjam Aulbach <[email protected]>

* Update e2e tests

Signed-off-by: Mirjam Aulbach <[email protected]>

* Add isSuperAdminUser to auth hook, extend tests

Signed-off-by: Mirjam Aulbach <[email protected]>

---------

Signed-off-by: Aindriu Lavelle <[email protected]>
Signed-off-by: Mirjam Aulbach <[email protected]>
Co-authored-by: Mirjam Aulbach <[email protected]>
  • Loading branch information
aindriu-aiven and programmiri authored Aug 29, 2024
1 parent 167895d commit 35804bc
Show file tree
Hide file tree
Showing 23 changed files with 355 additions and 231 deletions.
120 changes: 46 additions & 74 deletions coral/src/app/context-provider/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
import { cleanup, screen, within } from "@testing-library/react";
import { customRender } from "src/services/test-utils/render-with-wrappers";
import {
cleanup,
render,
renderHook,
screen,
waitFor,
within,
} from "@testing-library/react";
import {
AuthProvider,
useAuthContext,
} from "src/app/context-provider/AuthProvider";
import { waitForElementToBeRemoved } from "@testing-library/react/pure";
import { testAuthUser } from "src/domain/auth-user/auth-user-test-helper";
import { setupFeatureFlagMock } from "src/services/feature-flags/test-helper";
import { FeatureFlag } from "src/services/feature-flags/types";
import { QueryClientProvider } from "@tanstack/react-query";
import { getQueryClientForTests } from "src/services/test-utils/query-client-tests";

const getAuthMock = jest.fn();
jest.mock("src/domain/auth-user", () => ({
...jest.requireActual("src/domain/auth-user"),
getAuth: () => getAuthMock(),
}));

const AllProviders = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={getQueryClientForTests()}>
<AuthProvider>{children}</AuthProvider>
</QueryClientProvider>
);

const ChildComponent = () => {
const authUser = useAuthContext();

return <div data-testid={"auth-provider-child"}>{authUser?.username}</div>;
};

const mockAuthUser = { ...testAuthUser, username: "Jon Snow" };
const mockAuthSuperAdminUser = {
...testAuthUser,
username: "Arya Stark",
userrole: "SUPERADMIN",
};

describe("AuthProvider.tsx", () => {
describe("gets the auth user", () => {
beforeEach(() => {
getAuthMock.mockReturnValue({});
customRender(
<AuthProvider>
render(
<AllProviders>
<ChildComponent />
</AuthProvider>,
{
queryClient: true,
}
</AllProviders>
);
});

Expand All @@ -54,13 +69,10 @@ describe("AuthProvider.tsx", () => {
describe("renders an auth provider with given children when auth user is available", () => {
beforeEach(async () => {
getAuthMock.mockReturnValue(mockAuthUser);
customRender(
<AuthProvider>
render(
<AllProviders>
<ChildComponent />
</AuthProvider>,
{
queryClient: true,
}
</AllProviders>
);

await waitForElementToBeRemoved(screen.getByText("Loading Klaw"));
Expand All @@ -84,75 +96,35 @@ describe("AuthProvider.tsx", () => {
});
});

describe("does not render the auth provider with given children when auth user is SUPERADMIN", () => {
beforeEach(async () => {
getAuthMock.mockReturnValue({ mockAuthUser, userrole: "SUPERADMIN" });
customRender(
<AuthProvider>
<ChildComponent />
</AuthProvider>,
{
queryClient: true,
}
);

await waitForElementToBeRemoved(screen.getByText("Loading Klaw"));
});

describe("useAuthContext hook", () => {
afterEach(() => {
jest.resetAllMocks();
cleanup();
});

it("does not returns context provider with given children", () => {
const childElement = screen.queryByTestId("auth-provider-child");
expect(childElement).not.toBeInTheDocument();
});
it("returns the correct user data and identifies a SUPERADMIN user", async () => {
getAuthMock.mockResolvedValue(mockAuthSuperAdminUser);

it("shows a dialog informing superadmin they can not access coral", async () => {
const dialog = await screen.findByRole("dialog", {
name: "You're currently logged in as superadmin.",
const { result } = renderHook(() => useAuthContext(), {
wrapper: AllProviders,
});

expect(dialog).toBeVisible();
});
});

describe("render the auth provider with given children when auth user is SUPERADMIN and feature flag is enabled", () => {
beforeEach(async () => {
setupFeatureFlagMock(
FeatureFlag.FEATURE_FLAG_SUPER_ADMIN_ACCESS_CORAL,
true
);
getAuthMock.mockReturnValue({ mockAuthUser, userrole: "SUPERADMIN" });
customRender(
<AuthProvider>
<ChildComponent />
</AuthProvider>,
{
queryClient: true,
}
);

await waitForElementToBeRemoved(screen.getByText("Loading Klaw"));
});

afterEach(() => {
jest.resetAllMocks();
cleanup();
});

it("returns context provider with given children", () => {
const childElement = screen.getByTestId("auth-provider-child");
expect(childElement).toBeVisible();
await waitFor(() => {
expect(result.current.username).toEqual("Arya Stark");
expect(result.current.isSuperAdminUser).toBe(true);
});
});

it("does not show dialog informing superadmin they can not access coral", () => {
const dialog = screen.queryByText(
"You're currently logged in as superadmin."
);
it("returns the correct user data and identifies a non-SUPERADMIN user", async () => {
getAuthMock.mockResolvedValue(mockAuthUser);
const { result } = renderHook(() => useAuthContext(), {
wrapper: AllProviders,
});

expect(dialog).not.toBeInTheDocument();
await waitFor(() => {
expect(result.current.username).toEqual("Jon Snow");
expect(result.current.isSuperAdminUser).toBe(false);
});
});
});
});
31 changes: 10 additions & 21 deletions coral/src/app/context-provider/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { AuthUser, getAuth, isSuperAdmin } from "src/domain/auth-user";
import { BasePage } from "src/app/layout/page/BasePage";
import { Box, Icon } from "@aivenio/aquarium";
import loading from "@aivenio/aquarium/icons/loading";
import { NoCoralAccessSuperadmin } from "src/app/components/NoCoralAccessSuperadmin";
import useFeatureFlag from "src/services/feature-flags/hook/useFeatureFlag";
import { FeatureFlag } from "src/services/feature-flags/types";

/** We don't do Authentication on Corals side
* at the moment, so we only have a AuthUser
Expand Down Expand Up @@ -50,31 +47,22 @@ const AuthContext = createContext<AuthUser>({
},
});

const useAuthContext = () => useContext(AuthContext);
type UseAuthContext = AuthUser & { isSuperAdminUser: boolean };

const useAuthContext = (): UseAuthContext => {
const authUser = useContext(AuthContext);

const isSuperAdminUser = isSuperAdmin(authUser);

return { ...authUser, isSuperAdminUser };
};

const AuthProvider = ({ children }: { children: ReactNode }) => {
const { data: authUser, isLoading } = useQuery<AuthUser | undefined>(
["user-getAuth-data"],
getAuth
);

const superAdminAccessCoralEnabled = useFeatureFlag(
FeatureFlag.FEATURE_FLAG_SUPER_ADMIN_ACCESS_CORAL
);

// SUPERADMIN does not have access to coral, so we show a reduced page with
// information about that and nothing else.
if (
!isLoading &&
authUser &&
isSuperAdmin(authUser) &&
!superAdminAccessCoralEnabled
) {
return (
<BasePage headerContent={<></>} content={<NoCoralAccessSuperadmin />} />
);
}

if (!isLoading && authUser) {
return (
<AuthContext.Provider value={authUser}>{children}</AuthContext.Provider>
Expand All @@ -94,3 +82,4 @@ const AuthProvider = ({ children }: { children: ReactNode }) => {
};

export { useAuthContext, AuthProvider };
export type { UseAuthContext };
75 changes: 23 additions & 52 deletions coral/src/app/features/configuration/clusters/Clusters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,16 @@ import { KlawApiError } from "src/services/api";
import { mockIntersectionObserver } from "src/services/test-utils/mock-intersection-observer";
import { userEvent } from "@testing-library/user-event";
import { clusterTypeToString } from "src/services/formatter/cluster-type-formatter";
import { useAuthContext } from "src/app/context-provider/AuthProvider";
import { AuthUser } from "src/domain/auth-user";

const INITIAL_AUTH_DATA: AuthUser = {
username: "",
userrole: "",
teamname: "",
teamId: "",
canSwitchTeams: "",
totalTeamTopics: 0,
totalOrgTopics: 0,
permissions: {
addDeleteEditClusters: false,
canShutdownKw: false,
canUpdatePermissions: false,
addEditRoles: false,
viewTopics: false,
requestItems: false,
viewKafkaConnect: false,
syncBackTopics: false,
syncBackSchemas: false,
syncBackAcls: false,
updateServerConfig: false,
showServerConfigEnvProperties: false,
addUser: false,
addTeams: false,
syncTopicsAcls: false,
syncConnectors: false,
manageConnectors: false,
syncSchemas: false,
approveAtleastOneRequest: false,
approveDeclineTopics: false,
approveDeclineOperationalReqs: false,
approveDeclineSubscriptions: false,
approveDeclineSchemas: false,
approveDeclineConnectors: false,
showAddDeleteTenants: false,
addDeleteEditEnvs: false,
},
import {
UseAuthContext,
useAuthContext,
} from "src/app/context-provider/AuthProvider";

import { testAuthUser } from "src/domain/auth-user/auth-user-test-helper";

const INITIAL_AUTH_USER_CONTEXT_DATA: UseAuthContext = {
...testAuthUser,
isSuperAdminUser: false,
};

jest.mock("src/domain/cluster/cluster-api.ts");
Expand Down Expand Up @@ -114,7 +85,7 @@ describe("Clusters.tsx", () => {
totalPages: 1,
entries: [],
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });
});
Expand All @@ -137,7 +108,7 @@ describe("Clusters.tsx", () => {
totalPages: 1,
entries: [],
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });
await waitForElementToBeRemoved(screen.getByTestId("skeleton-table"));
Expand Down Expand Up @@ -168,7 +139,7 @@ describe("Clusters.tsx", () => {
beforeAll(async () => {
jest.spyOn(console, "error").mockImplementation((error) => error);
mockGetClustersPaginated.mockRejectedValue(testError);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -201,7 +172,7 @@ describe("Clusters.tsx", () => {
totalPages: 1,
entries: testCluster,
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -271,7 +242,7 @@ describe("Clusters.tsx", () => {
totalPages: 3,
entries: testCluster,
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -307,7 +278,7 @@ describe("Clusters.tsx", () => {
totalPages: 5,
entries: testCluster,
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -350,7 +321,7 @@ describe("Clusters.tsx", () => {
totalPages: 5,
entries: testCluster,
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -390,7 +361,7 @@ describe("Clusters.tsx", () => {
totalPages: 5,
entries: testCluster,
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, { queryClient: true, memoryRouter: true });

Expand Down Expand Up @@ -432,7 +403,7 @@ describe("Clusters.tsx", () => {
totalPages: 1,
entries: [testCluster[0]],
});
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_DATA);
mockUseAuthContext.mockReturnValue(INITIAL_AUTH_USER_CONTEXT_DATA);

customRender(<Clusters />, {
queryClient: true,
Expand Down Expand Up @@ -475,9 +446,9 @@ describe("Clusters.tsx", () => {
});

mockUseAuthContext.mockReturnValue({
...INITIAL_AUTH_DATA,
...INITIAL_AUTH_USER_CONTEXT_DATA,
permissions: {
...INITIAL_AUTH_DATA.permissions,
...INITIAL_AUTH_USER_CONTEXT_DATA.permissions,
addDeleteEditClusters: true,
},
});
Expand Down Expand Up @@ -541,9 +512,9 @@ describe("Clusters.tsx", () => {
});

mockUseAuthContext.mockReturnValue({
...INITIAL_AUTH_DATA,
...INITIAL_AUTH_USER_CONTEXT_DATA,
permissions: {
...INITIAL_AUTH_DATA.permissions,
...INITIAL_AUTH_USER_CONTEXT_DATA.permissions,
addDeleteEditClusters: false,
},
});
Expand Down
Loading

0 comments on commit 35804bc

Please sign in to comment.