Skip to content

Commit

Permalink
test: rbac config [TESTENG-108] (#10032)
Browse files Browse the repository at this point in the history
Co-authored-by: John Kim <[email protected]>
  • Loading branch information
2 people authored and thiagodallacqua-hpe committed Oct 28, 2024
1 parent a5d5a87 commit 262e017
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 20 deletions.
47 changes: 47 additions & 0 deletions .circleci/devcluster/react-rbac.devcluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
temp_dir: /tmp/priority_scheduler

stages:
- db:
name: db

- master:
pre:
- sh: make -C tools prep-root
config_file:
security:
initial_user_password: $INITIAL_USER_PASSWORD
authz:
rbac_ui_enabled: true
port: 8082
db:
host: localhost
port: 5432
password: postgres
user: postgres
name: determined
checkpoint_storage:
type: shared_fs
host_path: /tmp
storage_path: determined-cp
log:
level: debug
root: tools/build
cache:
cache_dir: /tmp/determined-cache
launch_error: false
telemetry:
enabled: false
resource_manager:
default_aux_resource_pool: default
default_compute_resource_pool: default
type: agent

- agent:
name: agent1
config_file:
master_host: 127.0.0.1
master_port: 8082
agent_id: agent1
container_master_host: $DOCKER_LOCALHOST
agent_reconnect_attempts: 24
agent_reconnect_backoff: 5
24 changes: 23 additions & 1 deletion .circleci/real_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,12 @@ jobs:

test-e2e-react:
parameters:
devcluster-config:
type: enum
enum:
- react.devcluster.yaml
- react-rbac.devcluster.yaml
- react-sso.devcluster.yaml
ee:
type: boolean
default: false
Expand Down Expand Up @@ -2148,7 +2154,7 @@ jobs:
- run: make -C agent get-deps
- install-devcluster
- start-devcluster:
devcluster-config: react.devcluster.yaml
devcluster-config: <<parameters.devcluster-config>>
- run: make -C webui/react get-playwright-ci
- run: SERVER_ADDRESS=${PW_SERVER_ADDRESS} npm run build --prefix webui/react
- wait-for-master:
Expand Down Expand Up @@ -4112,6 +4118,7 @@ workflows:
dev-mode: true
- test-e2e-react:
name: test-e2e-react-oss
devcluster-config: react.devcluster.yaml
requires:
- build-go-oss
context:
Expand Down Expand Up @@ -4201,13 +4208,27 @@ workflows:
- test-e2e-react:
name: test-e2e-react-ee
ee: true
devcluster-config: react-rbac.devcluster.yaml
requires:
- build-go-ee
context:
- playwright
- github-read
- dev-ci-cluster-default-user-credentials
filters: *any-upstream
# this will be used once sso tests are prioritized (after rbac tests)
# - test-e2e-react:
# name: test-e2e-react-ee-sso
# ee: true
# devcluster-config: react-sso.devcluster.yaml
# playwright-options: "-g sso"
# requires:
# - build-go-ee
# context:
# - playwright
# - github-read
# - dev-ci-cluster-default-user-credentials
# filters: *any-upstream
- build-docs:
requires:
- build-helm
Expand Down Expand Up @@ -5443,6 +5464,7 @@ workflows:
context: github-read
- test-e2e-react:
ee: << pipeline.parameters.ee >>
devcluster-config: react.devcluster.yaml
playwright-options: << pipeline.parameters.e2e-react >>
requires:
- build-go
Expand Down
51 changes: 51 additions & 0 deletions webui/react/src/e2e/fixtures/api.roles.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import streamConsumers from 'stream/consumers';

import _ from 'lodash';

import { RBACApi, V1AssignRolesRequest, V1AssignRolesResponse } from 'services/api-ts-sdk/api';

import { ApiAuthFixture } from './api.auth.fixture';

export class ApiRoleFixture {
readonly apiAuth: ApiAuthFixture;
constructor(apiAuth: ApiAuthFixture) {
this.apiAuth = apiAuth;
}

new({ roleProps = {} } = {}): V1AssignRolesRequest {
const defaults = {};
return {
...defaults,
...roleProps,
};
}

private static normalizeUrl(url: string): string {
if (url.endsWith('/')) {
return url.substring(0, url.length - 1);
}
return url;
}

private async startRoleRequest(): Promise<RBACApi> {
return new RBACApi(
{ apiKey: await this.apiAuth.getBearerToken() },
ApiRoleFixture.normalizeUrl(this.apiAuth.baseURL),
fetch,
);
}

async createAssignment(req: V1AssignRolesRequest): Promise<V1AssignRolesResponse> {
const roleResp = await (await this.startRoleRequest())
.assignRoles(req, {})
.catch(async function (error) {
const respBody = await streamConsumers.text(error.body);
throw new Error(
`Create Assignment Failed: ${error.status} Request: ${JSON.stringify(
req,
)} Response: ${respBody}`,
);
});
return _.merge(req, roleResp);
}
}
7 changes: 5 additions & 2 deletions webui/react/src/e2e/fixtures/auth.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Page } from '@playwright/test';

import { expect } from 'e2e/fixtures/global-fixtures';
import { DefaultRoute } from 'e2e/models/pages/DefaultRoute';
import { SignIn } from 'e2e/models/pages/SignIn';
import { password, username } from 'e2e/utils/envVars';

Expand All @@ -18,7 +19,7 @@ export class AuthFixture {
}

async login({
expectedURL = /dashboard/,
expectedURL,
username = this.#USERNAME,
password = this.#PASSWORD,
}: {
Expand All @@ -35,7 +36,9 @@ export class AuthFixture {
await detAuth.username.pwLocator.fill(username);
await detAuth.password.pwLocator.fill(password);
await detAuth.submit.pwLocator.click();
await this.#page.waitForURL(expectedURL);

const defaultPage = new DefaultRoute(this.#page);
await this.#page.waitForURL(expectedURL ?? defaultPage.url);
}

async logout(): Promise<void> {
Expand Down
16 changes: 15 additions & 1 deletion webui/react/src/e2e/fixtures/global-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {

import { ApiAuthFixture } from './api.auth.fixture';
import { ApiProjectFixture } from './api.project.fixture';
import { ApiRoleFixture } from './api.roles.fixture';
import { ApiUserFixture } from './api.user.fixture';
import { ApiWorkspaceFixture } from './api.workspace.fixture';
import { AuthFixture } from './auth.fixture';
Expand All @@ -39,6 +40,7 @@ type CustomWorkerFixtures = {
newProject: { request: V1PostProjectRequest; response: V1PostProjectResponse };
backgroundApiAuth: ApiAuthFixture;
backgroundApiUser: ApiUserFixture;
backgroundApiRole: ApiRoleFixture;
backgroundApiWorkspace: ApiWorkspaceFixture;
backgroundApiProject: ApiProjectFixture;
backgroundAuthedPage: Page;
Expand Down Expand Up @@ -128,6 +130,13 @@ export const test = baseTest.extend<CustomFixtures, CustomWorkerFixtures>({
},
{ scope: 'worker' },
],
backgroundApiRole: [
async ({ backgroundApiAuth }, use) => {
const backgroundApiRole = new ApiRoleFixture(backgroundApiAuth);
await use(backgroundApiRole);
},
{ scope: 'worker' },
],
/**
* Allows calling the user api without a page so that it can run in beforeAll(). You will need to get a bearer
* token by calling backgroundApiUser.apiAuth.loginAPI(). This will also provision a page in the background which
Expand Down Expand Up @@ -181,7 +190,7 @@ export const test = baseTest.extend<CustomFixtures, CustomWorkerFixtures>({
* Creates an admin and logs in as that admin for the duraction of the test suite
*/
newAdmin: [
async ({ backgroundApiUser }, use, workerInfo) => {
async ({ backgroundApiUser, backgroundApiRole }, use, workerInfo) => {
const request = backgroundApiUser.new({
userProps: {
user: {
Expand All @@ -192,6 +201,11 @@ export const test = baseTest.extend<CustomFixtures, CustomWorkerFixtures>({
},
});
const adminUser = await backgroundApiUser.createUser(request);
await backgroundApiRole.createAssignment({
userRoleAssignments: [
{ roleAssignment: { role: { roleId: 1 } }, userId: adminUser.user!.id! },
],
});
await use({ request, response: adminUser });
await backgroundApiUser.patchUser(adminUser.user!.id!, { active: false });
},
Expand Down
27 changes: 16 additions & 11 deletions webui/react/src/e2e/fixtures/user.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expect } from 'e2e/fixtures/global-fixtures';
import { UserManagement } from 'e2e/models/pages/Admin/UserManagement';
import { safeName } from 'e2e/utils/naming';
import { repeatWithFallback } from 'e2e/utils/polling';
import { isRbacEnabled } from 'e2e/utils/rbac';
import { TestUser } from 'e2e/utils/users';

interface CreateUserFields {
Expand Down Expand Up @@ -43,16 +44,18 @@ export class UserFixture {
);
}

const checkedAttribute =
await this.userManagementPage.createUserModal.adminToggle.pwLocator.getAttribute(
'aria-checked',
);
if (checkedAttribute === null) {
throw new Error('Expected attribute aria-checked to be present.');
}
const adminState = JSON.parse(checkedAttribute);
if (!!formValues.admin !== adminState) {
await this.userManagementPage.createUserModal.adminToggle.pwLocator.click();
if (!isRbacEnabled()) {
const checkedAttribute =
await this.userManagementPage.createUserModal.adminToggle.pwLocator.getAttribute(
'aria-checked',
);
if (checkedAttribute === null) {
throw new Error('Expected attribute aria-checked to be present.');
}
const adminState = JSON.parse(checkedAttribute);
if (!!formValues.admin !== adminState) {
await this.userManagementPage.createUserModal.adminToggle.pwLocator.click();
}
}

// password and username are required to create a user; if these are filled, submit should be enabled
Expand Down Expand Up @@ -180,7 +183,9 @@ export class UserFixture {
} else {
await row.user.alias.pwLocator.waitFor({ state: 'hidden' });
}
await expect(row.role.pwLocator).toContainText(user.admin ? 'Admin' : 'Member');
if (!isRbacEnabled()) {
await expect(row.role.pwLocator).toContainText(user.admin ? 'Admin' : 'Member');
}
await expect(row.status.pwLocator).toContainText(user.active ? 'Active' : 'Inactive');
}

Expand Down
11 changes: 11 additions & 0 deletions webui/react/src/e2e/models/pages/DefaultRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DeterminedPage } from 'e2e/models/common/base/BasePage';
import { isRbacEnabled } from 'e2e/utils/rbac';

/**
* Represents the DefaultRoute page from src/pages/DefaultRoute.tsx
*/
export class DefaultRoute extends DeterminedPage {
// only redirects to Dashboard or WorkspaceList page
readonly title = isRbacEnabled() ? 'Workspaces' : 'Home';
readonly url = isRbacEnabled() ? /workspaces/ : /dashboard/;
}
6 changes: 4 additions & 2 deletions webui/react/src/e2e/tests/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect, test } from 'e2e/fixtures/global-fixtures';
import { Cluster } from 'e2e/models/pages/Cluster';
import { DefaultRoute } from 'e2e/models/pages/DefaultRoute';
import { SignIn } from 'e2e/models/pages/SignIn';

test.describe('Authentication', () => {
Expand All @@ -13,8 +14,9 @@ test.describe('Authentication', () => {
test('Login and Logout', async ({ page, auth }) => {
await test.step('Login', async () => {
await auth.login();
await expect(page).toHaveDeterminedTitle('Home');
await expect(page).toHaveURL(/dashboard/);
const defaultPage = new DefaultRoute(page);
await expect(page).toHaveDeterminedTitle(defaultPage.title);
await expect(page).toHaveURL(defaultPage.url);
});

await test.step('Logout', async () => {
Expand Down
5 changes: 2 additions & 3 deletions webui/react/src/e2e/tests/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ test.describe('Navigation', () => {
// we need any page to access the sidebar, and i haven't modeled the homepage yet
const userManagementPage = new UserManagement(authedPage);

await test.step('Login steps', async () => {
await expect(authedPage).toHaveDeterminedTitle('Home');
await expect(authedPage).toHaveURL(/dashboard/);
await test.step('Load page', async () => {
await userManagementPage.goto();
});

await test.step('Uncategorized', async () => {
Expand Down
18 changes: 18 additions & 0 deletions webui/react/src/e2e/utils/rbac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { detExecSync } from 'e2e/utils/detCLI';

let rbacEnabled: boolean;

const getRbacEnabled = (): boolean => {
const masterInfo = detExecSync('master info');
const regexp = /rbacEnabled:\s*(?<enabled>true|false)/;

const { groups } = regexp.exec(masterInfo) || {};

return groups?.enabled === 'true';
};

export const isRbacEnabled = (): boolean => {
if (rbacEnabled === undefined) rbacEnabled = getRbacEnabled();

return rbacEnabled;
};

0 comments on commit 262e017

Please sign in to comment.