diff --git a/awx/ui/test/e2e/commands/navigateTo.js b/awx/ui/test/e2e/commands/navigateTo.js new file mode 100644 index 0000000000..4b8bd3048a --- /dev/null +++ b/awx/ui/test/e2e/commands/navigateTo.js @@ -0,0 +1,8 @@ +exports.command = function navigateTo (url) { + this.url(url); + + this.waitForElementVisible('div.spinny'); + this.waitForElementNotVisible('div.spinny'); + + return this; +}; diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js index 9f81fd2083..1cc8a6ea72 100644 --- a/awx/ui/test/e2e/fixtures.js +++ b/awx/ui/test/e2e/fixtures.js @@ -7,7 +7,7 @@ import { spread } from './api'; -const sid = uuid().substr(0, 8); +const session = `e2e-${uuid().substr(0, 8)}`; const store = {}; @@ -50,24 +50,43 @@ const getOrCreate = (endpoint, data) => { return store[endpoint][identity].then(created => created.data); }; -const getOrganization = () => getOrCreate('/organizations/', { - name: `e2e-organization-${sid}` +const getOrganization = (namespace = session) => getOrCreate('/organizations/', { + name: `${namespace}-organization`, + description: namespace }); -const getInventory = () => getOrganization() +const getInventory = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventories/', { - name: `e2e-inventory-${sid}`, + name: `${namespace}-inventory`, + description: namespace, organization: organization.id })); -const getInventoryScript = () => getOrganization() +const getInventoryScript = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventory_scripts/', { - name: `e2e-inventory-script-${sid}`, + name: `${namespace}-inventory-script`, + description: namespace, organization: organization.id, script: '#!/usr/bin/env python' })); -const getAdminAWSCredential = () => { +const getInventorySource = (namespace = session) => { + const promises = [ + getInventory(namespace), + getInventoryScript(namespace) + ]; + + return all(promises) + .then(spread((inventory, inventoryScript) => getOrCreate('/inventory_sources/', { + name: `${namespace}-inventory-source-custom`, + description: namespace, + source: 'custom', + inventory: inventory.id, + source_script: inventoryScript.id + }))); +}; + +const getAdminAWSCredential = (namespace = session) => { const promises = [ get('/me/'), getOrCreate('/credential_types/', { @@ -80,7 +99,8 @@ const getAdminAWSCredential = () => { const [admin] = me.data.results; return getOrCreate('/credentials/', { - name: `e2e-aws-credential-${sid}`, + name: `${namespace}-credential-aws`, + description: namespace, credential_type: credentialType.id, user: admin.id, inputs: { @@ -92,7 +112,7 @@ const getAdminAWSCredential = () => { })); }; -const getAdminMachineCredential = () => { +const getAdminMachineCredential = (namespace = session) => { const promises = [ get('/me/'), getOrCreate('/credential_types/', { name: 'Machine' }) @@ -103,30 +123,34 @@ const getAdminMachineCredential = () => { const [admin] = me.data.results; return getOrCreate('/credentials/', { - name: `e2e-machine-credential-${sid}`, + name: `${namespace}-credential-machine-admin`, + description: namespace, credential_type: credentialType.id, user: admin.id }); })); }; -const getTeam = () => getOrganization() +const getTeam = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/teams/', { - name: `e2e-team-${sid}`, + name: `${namespace}-team`, + description: namespace, organization: organization.id, })); -const getSmartInventory = () => getOrganization() +const getSmartInventory = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventories/', { - name: `e2e-smart-inventory-${sid}`, + name: `${namespace}-smart-inventory`, + description: namespace, organization: organization.id, host_filter: 'search=localhost', kind: 'smart' })); -const getNotificationTemplate = () => getOrganization() +const getNotificationTemplate = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/notification_templates/', { - name: `e2e-notification-template-${sid}`, + name: `${namespace}-notification-template`, + description: namespace, organization: organization.id, notification_type: 'slack', notification_configuration: { @@ -135,9 +159,10 @@ const getNotificationTemplate = () => getOrganization() } })); -const getProject = () => getOrganization() +const getProject = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/projects/', { - name: `e2e-project-${sid}`, + name: `${namespace}-project`, + description: namespace, organization: organization.id, scm_url: 'https://github.com/ansible/ansible-tower-samples', scm_type: 'git' @@ -168,7 +193,7 @@ const waitForJob = endpoint => { }); }; -const getUpdatedProject = () => getProject() +const getUpdatedProject = (namespace = session) => getProject(namespace) .then(project => { const updateURL = project.related.current_update; @@ -179,27 +204,28 @@ const getUpdatedProject = () => getProject() return project; }); -const getJobTemplate = () => { +const getJobTemplate = (namespace = session) => { const promises = [ - getInventory(), - getAdminMachineCredential(), - getUpdatedProject() + getInventory(namespace), + getAdminMachineCredential(namespace), + getUpdatedProject(namespace) ]; return all(promises) - .then(spread((inventory, credential, project) => getOrCreate('/job_templates', { - name: `e2e-job-template-${sid}`, + .then(spread((inventory, credential, project) => getOrCreate('/job_templates/', { + name: `${namespace}-job-template`, + description: namespace, inventory: inventory.id, credential: credential.id, project: project.id, - playbook: 'hello_world.yml' + playbook: 'hello_world.yml', }))); }; -const getAuditor = () => getOrganization() +const getAuditor = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/users/', { + username: `auditor-${uuid().substr(0, 8)}`, organization: organization.id, - username: `e2e-auditor-${sid}`, first_name: 'auditor', last_name: 'last', email: 'null@ansible.com', @@ -208,15 +234,54 @@ const getAuditor = () => getOrganization() password: 'password' })); -const getUser = () => getOrCreate('/users/', { - username: `e2e-user-${sid}`, - first_name: `user-${sid}-first`, - last_name: `user-${sid}-last`, - email: `null-${sid}@ansible.com`, - is_superuser: false, - is_system_auditor: false, - password: 'password' -}); +const getUser = (namespace = session) => getOrganization(namespace) + .then(organization => getOrCreate('/users/', { + username: `user-${uuid().substr(0, 8)}`, + organization: organization.id, + first_name: 'firstname', + last_name: 'lastname', + email: 'null@ansible.com', + is_superuser: false, + is_system_auditor: false, + password: 'password' + })); + +const getJobTemplateAdmin = (namespace = session) => { + const rolePromise = getJobTemplate(namespace) + .then(obj => obj.summary_fields.object_roles.admin_role); + + const userPromise = getOrganization(namespace) + .then(obj => getOrCreate('/users/', { + username: `job-template-admin-${uuid().substr(0, 8)}`, + organization: obj.id, + first_name: 'firstname', + last_name: 'lastname', + email: 'null@ansible.com', + is_superuser: false, + is_system_auditor: false, + password: 'password' + })); + + const assignRolePromise = Promise.all([userPromise, rolePromise]) + .then(spread((user, role) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id }))); + + return Promise.all([userPromise, assignRolePromise]) + .then(spread(user => user)); +}; + +const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace) + .then(source => getOrCreate(source.related.schedules, { + name: `${source.name}-schedule`, + description: namespace, + rrule: 'DTSTART:20171104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' + })); + +const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace) + .then(template => getOrCreate(template.related.schedules, { + name: `${template.name}-schedule`, + description: namespace, + rrule: 'DTSTART:20171104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' + })); module.exports = { getAdminAWSCredential, @@ -224,7 +289,11 @@ module.exports = { getAuditor, getInventory, getInventoryScript, + getInventorySource, + getInventorySourceSchedule, getJobTemplate, + getJobTemplateAdmin, + getJobTemplateSchedule, getNotificationTemplate, getOrCreate, getOrganization, diff --git a/awx/ui/test/e2e/tests/test-xss.js b/awx/ui/test/e2e/tests/test-xss.js new file mode 100644 index 0000000000..0ea5382289 --- /dev/null +++ b/awx/ui/test/e2e/tests/test-xss.js @@ -0,0 +1,658 @@ +import { + getAdminMachineCredential, + getInventory, + getInventoryScript, + getInventorySource, + getInventorySourceSchedule, + getJobTemplate, + getJobTemplateAdmin, + getJobTemplateSchedule, + getNotificationTemplate, + getOrganization, + getSmartInventory, + getTeam, + getUpdatedProject, +} from '../fixtures'; + +const data = {}; +const urls = {}; +const pages = {}; + +module.exports = { + before: (client, done) => { + const namespace = '
test
'; + + const resources = [ + getOrganization(namespace).then(obj => { data.organization = obj; }), + getInventory(namespace).then(obj => { data.inventory = obj; }), + getInventoryScript(namespace).then(obj => { data.inventoryScript = obj; }), + getSmartInventory(namespace).then(obj => { data.smartInventory = obj; }), + getInventorySource(namespace).then(obj => { data.inventorySource = obj; }), + getInventorySourceSchedule(namespace).then(obj => { data.sourceSchedule = obj; }), + getUpdatedProject(namespace).then(obj => { data.project = obj; }), + getAdminMachineCredential(namespace).then(obj => { data.credential = obj; }), + getJobTemplate(namespace).then(obj => { data.jobTemplate = obj; }), + getJobTemplateSchedule(namespace).then(obj => { data.jobTemplateSchedule = obj; }), + getTeam(namespace).then(obj => { data.team = obj; }), + getJobTemplateAdmin(namespace).then(obj => { data.user = obj; }), + getNotificationTemplate(namespace).then(obj => { data.notification = obj; }), + ]; + + Promise.all(resources) + .then(() => { + pages.organizations = client.page.organizations(); + pages.inventories = client.page.inventories(); + pages.inventoryScripts = client.page.inventoryScripts(); + pages.projects = client.page.projects(); + pages.credentials = client.page.credentials(); + pages.templates = client.page.templates(); + pages.teams = client.page.teams(); + pages.users = client.page.users(); + pages.notificationTemplates = client.page.notificationTemplates(); + + urls.organization = `${pages.organizations.url()}/${data.organization.id}`; + urls.inventory = `${pages.inventories.url()}/inventory/${data.inventory.id}`; + urls.inventoryScript = `${pages.inventoryScripts.url()}/${data.inventoryScript.id}`; + urls.inventorySource = `${urls.inventory}/inventory_sources/edit/${data.inventorySource.id}`; + urls.sourceSchedule = `${urls.inventorySource}/schedules/${data.sourceSchedule.id}`; + urls.smartInventory = `${pages.inventories.url()}/smart/${data.smartInventory.id}`; + urls.project = `${pages.projects.url()}/${data.project.id}`; + urls.credential = `${pages.credentials.url()}/${data.credential.id}`; + urls.jobTemplate = `${pages.templates.url()}/job_template/${data.jobTemplate.id}`; + urls.jobTemplateSchedule = `${urls.jobTemplate}/schedules/${data.jobTemplateSchedule.id}`; + urls.team = `${pages.teams.url()}/${data.team.id}`; + urls.user = `${pages.users.url()}/${data.user.id}`; + urls.notification = `${pages.notificationTemplates.url()}/${data.notification.id}`; + + client.useCss(); + client.login(); + client.resizeWindow(1200, 800); + client.waitForAngular(); + + done(); + }); + }, + 'check template form for unsanitized content': client => { + const multiCredentialOpen = 'multi-credential button i[class*="search"]'; + const multiCredentialExit = 'multi-credential-modal button[class*="exit"]'; + + client.url(urls.jobTemplate); + + client.expect.element('#job_template_form').visible; + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.expect.element(multiCredentialOpen).visible; + client.expect.element(multiCredentialOpen).enabled; + + client.pause(2000).click(multiCredentialOpen); + + client.expect.element('#multi-credential-modal').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click(multiCredentialExit); + + client.pause(500).expect.element('div.spinny').not.visible; + client.expect.element('#multi-credential-modal').not.present; + }, + 'check template roles list for unsanitized content': client => { + const itemDelete = `#permissions_table tr[id="${data.user.id}"] div[class*="RoleList-deleteContainer"]`; + + client.expect.element('#permissions_tab').visible; + client.expect.element('#permissions_tab').enabled; + + client.click('#permissions_tab'); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.expect.element('div[ui-view="related"]').visible; + client.expect.element('div[ui-view="related"] smart-search input').enabled; + + client.sendKeys('div[ui-view="related"] smart-search input', `id:${data.user.id}`); + client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').not.visible; + + client.expect.element(itemDelete).visible; + client.expect.element(itemDelete).enabled; + + client.click(itemDelete); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt-header').text.equal('USER ACCESS REMOVAL'); + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check template permissions view for unsanitized content': client => { + client.expect.element('button[aw-tool-tip="Add a permission"]').visible; + client.expect.element('button[aw-tool-tip="Add a permission"]').enabled; + + client.click('button[aw-tool-tip="Add a permission"]'); + client.expect.element('div.spinny').not.visible; + + client.expect.element('div[class="AddPermissions-header"]').visible; + client.expect.element('div[class="AddPermissions-header"]').attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled; + + client.click('div[class="AddPermissions-dialog"] button[class*="exit"]'); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + // client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('#job_template_tab').enabled; + + client.click('#job_template_tab'); + + client.expect.element('#job_template_form').visible; + }, + 'check template list for unsanitized content': client => { + const itemRow = `#templates_table tr[id="${data.jobTemplate.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.jobTemplate.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check user form for unsanitized content': client => { + client.navigateTo(urls.user); + + client.expect.element('#user_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check user roles list for unsanitized content': client => { + const { admin_role } = data.jobTemplate.summary_fields.object_roles; + const itemDelete = `#permissions_table tr[id="${admin_role.id}"] #delete-action`; + + client.expect.element('#permissions_tab').visible; + client.expect.element('#permissions_tab').enabled; + + client.click('#permissions_tab'); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.expect.element('div[ui-view="related"]').visible; + client.expect.element('div[ui-view="related"] smart-search input').enabled; + + client.sendKeys('div[ui-view="related"] smart-search input', `id:${admin_role.id}`); + client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').not.visible; + + client.expect.element(itemDelete).visible; + client.expect.element(itemDelete).enabled; + + client.click(itemDelete); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt-header').text.equal('REMOVE ROLE'); + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check user permissions view for unsanitized content': client => { + client.expect.element('button[aw-tool-tip="Grant Permission"]').enabled; + + client.click('button[aw-tool-tip="Grant Permission"]'); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + client.expect.element('div[class="AddPermissions-header"]').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled; + + client.click('div[class="AddPermissions-dialog"] button[class*="exit"]'); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + }, + 'check notification form for unsanitized content': client => { + client.navigateTo(urls.notification); + + client.expect.element('#notification_template_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check notification list for unsanitized content': client => { + const itemRow = `#notification_templates_table tr[id="${data.notification.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.notification.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check organization form for unsanitized content': client => { + client.navigateTo(urls.organization); + + client.expect.element('#organization_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check organization list for unsanitized content': client => { + const itemName = '#OrgCards h3[class*="-label"]'; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.organization.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click('#OrgCards i[class*="trash"]'); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check inventory form for unsanitized content': client => { + client.navigateTo(urls.inventory); + + client.expect.element('#inventory_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check inventory list for unsanitized content': client => { + const itemRow = `#inventories_table tr[id="${data.inventory.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.inventory.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + // client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check smart inventory form for unsanitized content': client => { + client.url(urls.smartInventory); + + client.expect.element('#smartinventory_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check inventory script form for unsanitized content': client => { + client.navigateTo(urls.inventoryScript); + + client.expect.element('#inventory_script_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check inventory script list for unsanitized content': client => { + const itemRow = `#inventory_scripts_table tr[id="${data.inventoryScript.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.inventoryScript.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check project form for unsanitized content': client => { + client.navigateTo(urls.project); + + client.expect.element('#project_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check project list for unsanitized content': client => { + const itemRow = `#projects_table tr[id="${data.project.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.project.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check credential form for unsanitized content': client => { + client.navigateTo(urls.credential); + + client.expect.element('div[ui-view="edit"] form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check credential list for unsanitized content': client => { + const itemRow = `#credentials_table tr[id="${data.credential.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[ui-view="list"] smart-search').visible; + client.expect.element('div[ui-view="list"] smart-search input').enabled; + + client.sendKeys('div[ui-view="list"] smart-search input', `id:${data.credential.id}`); + client.sendKeys('div[ui-view="list"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check team form for unsanitized content': client => { + client.navigateTo(urls.team); + + client.expect.element('#team_form').visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check team list for unsanitized content': client => { + const itemRow = `#teams_table tr[id="${data.team.id}"]`; + const itemName = `${itemRow} td[class*="name-"] a`; + + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.team.id}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; + + client.expect.element('.List-titleBadge').text.equal('1'); + client.expect.element(itemName).visible; + + client.moveToElement(itemName, 0, 0, () => { + client.expect.element(itemName).attribute('aria-describedby'); + + client.getAttribute(itemName, 'aria-describedby', ({ value }) => { + const tooltip = `#${value}`; + + client.expect.element(tooltip).present; + client.expect.element(tooltip).visible; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.expect.element(tooltip).attribute('innerHTML') + .contains('<div id="xss" class="xss">test</div>'); + }); + }); + + client.click(`${itemRow} i[class*="trash"]`); + + client.expect.element('#prompt-header').visible; + client.expect.element('#prompt_cancel_btn').enabled; + + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + + client.click('#prompt_cancel_btn'); + + client.expect.element('#prompt-header').not.visible; + }, + 'check inventory source schedule view for unsanitized content': client => { + client.navigateTo(urls.sourceSchedule); + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + }, + 'check job template schedule view for unsanitized content': client => { + client.navigateTo(urls.jobTemplateSchedule); + client.expect.element('#xss').not.present; + client.expect.element('[class=xss]').not.present; + client.end(); + }, +};