From 93013bf4b6ae30fc156ed9fe7a16fe6a26c86196 Mon Sep 17 00:00:00 2001 From: Guohua Ouyang Date: Tue, 18 Jun 2019 20:41:42 +0800 Subject: [PATCH] add test for non-admin user Signed-off-by: Guohua Ouyang --- frontend/integration-tests/protractor.conf.ts | 1 + .../tests/kubevirt/kubevirt.login.scenario.ts | 17 ++- .../tests/kubevirt/non-admin.scenario.ts | 137 ++++++++++++++++++ .../tests/kubevirt/utils/consts.ts | 10 ++ .../tests/kubevirt/utils/mocks.ts | 35 +++++ .../tests/kubevirt/utils/utils.ts | 11 +- .../views/kubevirt/login.view.ts | 13 ++ .../integration-tests/views/login.view.ts | 7 +- 8 files changed, 209 insertions(+), 22 deletions(-) create mode 100644 frontend/integration-tests/tests/kubevirt/non-admin.scenario.ts create mode 100644 frontend/integration-tests/views/kubevirt/login.view.ts diff --git a/frontend/integration-tests/protractor.conf.ts b/frontend/integration-tests/protractor.conf.ts index 2f06dd4ced2..38b98f35fff 100644 --- a/frontend/integration-tests/protractor.conf.ts +++ b/frontend/integration-tests/protractor.conf.ts @@ -195,6 +195,7 @@ export const config: Config = { 'tests/kubevirt/node.maintenance.scenario.ts', 'tests/kubevirt/vm.nic.binding.scenario.ts', 'tests/kubevirt/template.actions.scenario.ts', + 'tests/kubevirt/non-admin.scenario.ts', ], kubevirtWindows: [ 'tests/kubevirt/kubevirt.login.scenario.ts', diff --git a/frontend/integration-tests/tests/kubevirt/kubevirt.login.scenario.ts b/frontend/integration-tests/tests/kubevirt/kubevirt.login.scenario.ts index 08f56458420..7e61eab6883 100644 --- a/frontend/integration-tests/tests/kubevirt/kubevirt.login.scenario.ts +++ b/frontend/integration-tests/tests/kubevirt/kubevirt.login.scenario.ts @@ -1,14 +1,19 @@ import { browser } from 'protractor'; import { appHost } from '../../protractor.conf'; import { execSync } from 'child_process'; -import { logIn } from './utils/utils'; +import { KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD } from './utils/consts'; +import * as loginView from '../../views/login.view'; + describe('Authentication', () => { - it('Web console logs in.', async() => { + beforeAll(async() => { await browser.get(appHost); - if (process.env.BRIDGE_BASE_ADDRESS !== undefined) { - await logIn(); - execSync(`oc login -u ${process.env.BRIDGE_AUTH_USERNAME} -p ${process.env.BRIDGE_AUTH_PASSWORD} --config=${process.env.KUBECONFIG}`); - } + await browser.sleep(3000); // Wait long enough for the login redirect to complete + }); + + it('Web console logs in.', async() => { + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); + expect(loginView.userDropdown.getText()).toContain(KUBEADMIN_IDP); + execSync(`oc login -u ${KUBEADMIN_USERNAME} -p ${BRIDGE_KUBEADMIN_PASSWORD}`); }); }); diff --git a/frontend/integration-tests/tests/kubevirt/non-admin.scenario.ts b/frontend/integration-tests/tests/kubevirt/non-admin.scenario.ts new file mode 100644 index 00000000000..d080ada2592 --- /dev/null +++ b/frontend/integration-tests/tests/kubevirt/non-admin.scenario.ts @@ -0,0 +1,137 @@ +/* eslint-disable no-undef */ +import { $, $$, browser, ExpectedConditions as until } from 'protractor'; +import { appHost, testName } from '../../protractor.conf'; +import { nonAdminSecret, nonAdminProvider } from './utils/mocks'; +import { createResources } from './utils/utils'; +import { KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD, BRIDGE_HTPASSWD_IDP, BRIDGE_HTPASSWD_USERNAME, BRIDGE_HTPASSWD_PASSWORD } from './utils/consts'; +import * as loginView from '../../views/login.view'; +import * as crudView from '../../views/crud.view'; +import Wizard from './models/wizard'; +import Yaml from './models/yaml'; +import { logout } from '../../views/kubevirt/login.view'; +import { execSync } from 'child_process'; + +describe('Test nonadmin user behaviour', () => { + const nonAdminNS = `${testName}-nonadmin`; + const userRole = ['view', 'edit']; + + const verifyPermissions = async function(ns: string, perm: string) { + const wizard = new Wizard(); + const yaml = new Yaml(); + + await browser.get(`${appHost}/k8s/ns/${ns}/virtualmachines`); + if (perm === 'noAccess') { + await browser.wait(until.presenceOf(crudView.errorMessage)); + expect(crudView.errorMessage.getText()).toContain('cannot list resource'); + + // TODO: check it can't use vm dialog once BZ1728523 is fixed. + } + if (perm === 'view') { + await crudView.isLoaded(); // no error indicates it can view resource. + // TODO: check it can't use vm dialog once BZ1728523 is fixed. + } + if (perm === 'edit') { + await crudView.isLoaded(); + await wizard.openWizard(); + await wizard.close(); + + await yaml.openYamlPage(); + await yaml.cancelCreateVM(); + } + + await browser.get(`${appHost}/k8s/ns/${ns}/vmtemplates`); + if (perm === 'noAccess') { + await browser.wait(until.presenceOf(crudView.errorMessage)); + expect(crudView.errorMessage.getText()).toContain('cannot list resource'); + } + if (perm === 'view') { + await crudView.isLoaded(); // no error indicates it can view resource. + // TODO: check it can't use vm dialog once BZ1728523 is fixed. + } + if (perm === 'edit') { + await crudView.isLoaded(); + await wizard.openWizard(); + await wizard.close(); + } + + await browser.get(`${appHost}/overview/ns/${ns}/`); + if (perm === 'noAccess') { + await browser.wait(until.presenceOf(crudView.errorMessage)); + expect(crudView.errorMessage.getText()).toContain('cannot list resource'); + } + if (perm === 'view') { + await crudView.isLoaded(); // no error indicates it can view resource. + // TODO: check it can't use vm dialog once BZ1728523 is fixed. + } + if (perm === 'edit') { + await crudView.isLoaded(); + } + }; + + const verifyPermissionWithRoleBinding = async function(ns: string, roleBinding: string, perm: string) { + execSync(`oc adm policy add-${roleBinding}-to-user ${perm} ${BRIDGE_HTPASSWD_USERNAME} -n ${ns}`); + await verifyPermissions(perm, ns); + execSync(`oc adm policy remove-${roleBinding}-from-user ${perm} ${BRIDGE_HTPASSWD_USERNAME} -n ${ns}`); + }; + + beforeAll(async() => { + try { + execSync('oc get -o yaml -n openshift-config secret test'); + } catch (error) { + createResources([nonAdminSecret, nonAdminProvider]); + } + }); + + afterAll(async() => { + await logout(); + await browser.wait(until.presenceOf($('.login-pf'))); + await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); + }); + + it('Login with nonadmin', async() => { + await logout(); + // wait for logout complete + await browser.sleep(10000); + // TODO: remove the refill appHost after bug 1721423 is fixed. + await browser.get(appHost); + await browser.wait(until.presenceOf($('.login-pf'))); + await loginView.login(BRIDGE_HTPASSWD_IDP, BRIDGE_HTPASSWD_USERNAME, BRIDGE_HTPASSWD_PASSWORD); + //expect(loginView.userDropdown.getText()).toContain(BRIDGE_HTPASSWD_IDP); + }); + + it(`Creates test namespace ${nonAdminNS} for nonAdmin tests`, async() => { + const resource = browser.params.openshift === 'true' ? 'projects' : 'namespaces'; + await browser.get(`${appHost}/k8s/cluster/${resource}`); + await crudView.isLoaded(); + const exists = await crudView.rowForName(nonAdminNS).isPresent(); + if (!exists) { + await crudView.createYAMLButton.click(); + await browser.sleep(3000); + await browser.wait(until.presenceOf($('.modal-body__field'))); + await $$('.modal-body__field').get(0).$('input').sendKeys(nonAdminNS); + await $$('.modal-body__field').get(1).$('input').sendKeys(`test-name=${nonAdminNS}`); + await $('.modal-content').$('#confirm-action').click(); + } + }); + + it('Nonadmin can use vm dialog in its own namespace but not other namespace', async() => { + await verifyPermissions(nonAdminNS, 'edit'); + await verifyPermissions(testName, 'noAccess'); + }); + + userRole.forEach(role => { + it(`Nonadmin with RoleBinding ${role} can ${role} vm objetcs in the binding NS but not other NS`, async() => { + execSync(`oc adm policy add-role-to-user ${role} ${BRIDGE_HTPASSWD_USERNAME} -n ${testName}`); + await verifyPermissions(testName, role); + await verifyPermissions('default', 'noAccess'); + execSync(`oc adm policy remove-role-from-user ${role} ${BRIDGE_HTPASSWD_USERNAME} -n ${testName}`); + }); + }); + + userRole.forEach((role) => { + it(`Nonadmin with ClusterRoleBinding ${role} can ${role} vm objetcs in the cluster`, async() => { + await verifyPermissionWithRoleBinding(testName, 'cluster-role', role); + await verifyPermissionWithRoleBinding('default', 'cluster-role', role); + }); + }); +}); diff --git a/frontend/integration-tests/tests/kubevirt/utils/consts.ts b/frontend/integration-tests/tests/kubevirt/utils/consts.ts index 43054b5e126..14f7607cff9 100644 --- a/frontend/integration-tests/tests/kubevirt/utils/consts.ts +++ b/frontend/integration-tests/tests/kubevirt/utils/consts.ts @@ -1,6 +1,16 @@ export const DASHES = '---'; export const STORAGE_CLASS = process.env.STORAGE_CLASS; +export const KUBEADMIN_IDP = 'kube:admin'; +export const KUBEADMIN_USERNAME = 'kubeadmin'; +export const { + BRIDGE_HTPASSWD_IDP = 'test', + BRIDGE_HTPASSWD_USERNAME = 'test', + BRIDGE_HTPASSWD_PASSWORD = 'test', + BRIDGE_KUBEADMIN_PASSWORD, + KUBECONFIG, +} = process.env; + // TIMEOUTS const SEC = 1000; export const CLONE_VM_TIMEOUT = 300 * SEC; diff --git a/frontend/integration-tests/tests/kubevirt/utils/mocks.ts b/frontend/integration-tests/tests/kubevirt/utils/mocks.ts index 7283973298f..b031071b1c2 100644 --- a/frontend/integration-tests/tests/kubevirt/utils/mocks.ts +++ b/frontend/integration-tests/tests/kubevirt/utils/mocks.ts @@ -6,6 +6,41 @@ import { STORAGE_CLASS } from './consts'; import { getRandomMacAddress } from './utils'; +export const nonAdminSecret = { + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name: 'test', + namespace: 'openshift-config', + }, + type: 'Opaque', + data: { + htpasswd: 'dGVzdDokMnkkMDUkeENGNUZMTGEvamp5ZmJpLm1mVlBnLjFQVmNGMFRucG4vNmF0RzB1bExUOEN2TnhCZjE2MG0K', + }, +}; + +export const nonAdminProvider = { + apiVersion: 'config.openshift.io/v1', + kind: 'OAuth', + metadata: { + name: 'cluster', + }, + spec: { + identityProviders: [ + { + name: 'test', + mappingMethod: 'claim', + type: 'HTPasswd', + htpasswd: { + fileData: { + name: 'test', + }, + }, + }, + ], + }, +}; + export const multusNad = { apiVersion: 'k8s.cni.cncf.io/v1', kind: 'NetworkAttachmentDefinition', diff --git a/frontend/integration-tests/tests/kubevirt/utils/utils.ts b/frontend/integration-tests/tests/kubevirt/utils/utils.ts index 3bf117393de..928b3ad6be2 100644 --- a/frontend/integration-tests/tests/kubevirt/utils/utils.ts +++ b/frontend/integration-tests/tests/kubevirt/utils/utils.ts @@ -4,8 +4,6 @@ import { execSync } from 'child_process'; import { $, by, ElementFinder, browser, ExpectedConditions as until } from 'protractor'; import { config } from '../../../protractor.conf'; -import { nameInput as loginNameInput, passwordInput as loginPasswordInput, submitButton as loginSubmitButton } from '../../../views/login.view'; -import { PAGE_LOAD_TIMEOUT } from './consts'; export function removeLeakedResources(leakedResources: Set) { @@ -37,7 +35,7 @@ export function removeLeakableResource(leakedResources: Set, resource) { } export function createResource(resource) { - execSync(`echo '${JSON.stringify(resource)}' | kubectl create -f -`); + execSync(`echo '${JSON.stringify(resource)}' | kubectl apply -f -`); } export function createResources(resources) { @@ -110,13 +108,6 @@ export async function getInputValue(elem: ElementFinder) { return elem.getAttribute('value'); } -export async function logIn() { - await fillInput(loginNameInput, process.env.BRIDGE_AUTH_USERNAME); - await fillInput(loginPasswordInput, process.env.BRIDGE_AUTH_PASSWORD); - await click(loginSubmitButton); - await browser.wait(until.visibilityOf($('img.pf-c-brand')), PAGE_LOAD_TIMEOUT); -} - export function getRandStr(length: number) { return Math.random().toString(36).replace(/[.]/g, '').substr(1, length); // First char is always 0 } diff --git a/frontend/integration-tests/views/kubevirt/login.view.ts b/frontend/integration-tests/views/kubevirt/login.view.ts new file mode 100644 index 00000000000..1209f42e58f --- /dev/null +++ b/frontend/integration-tests/views/kubevirt/login.view.ts @@ -0,0 +1,13 @@ +/* eslint-disable no-undef, no-unused-vars */ + +import { $, browser, element, by, ExpectedConditions as until } from 'protractor'; + +export const logOutLink = element(by.linkText('Log out')); +export const userDropdown = $('[data-test=user-dropdown] .pf-c-dropdown__toggle'); + +export const logout = async() => { + await browser.wait(until.presenceOf(userDropdown)); + await userDropdown.click(); + await browser.wait(until.presenceOf(logOutLink)); + await logOutLink.click(); +}; diff --git a/frontend/integration-tests/views/login.view.ts b/frontend/integration-tests/views/login.view.ts index 6696d83373b..187a70a7a79 100644 --- a/frontend/integration-tests/views/login.view.ts +++ b/frontend/integration-tests/views/login.view.ts @@ -1,7 +1,6 @@ /* eslint-disable no-undef, no-unused-vars */ import { $, browser, ExpectedConditions as until, by, element } from 'protractor'; -import { appHost } from '../protractor.conf'; export const nameInput = $('#inputUsername'); export const passwordInput = $('#inputPassword'); @@ -11,15 +10,11 @@ export const userDropdown = $('[data-test=user-dropdown] .pf-c-dropdown__toggle' export const selectProvider = async(provider: string) => { const idpLink = element(by.cssContainingText('.idp', provider)); - while (!(await idpLink.isPresent())) { - await browser.get(appHost); - await browser.sleep(3000); - } await idpLink.click(); }; export const login = async(providerName: string, username: string, password: string) => { - if (providerName) { + if (await $('a.idp').isPresent()) { await selectProvider(providerName); } await browser.wait(until.visibilityOf(nameInput));