From e38fa970dbe542a73a0938dae9705dcf0ba94df7 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 12:24:00 -0500 Subject: [PATCH 01/45] Add preliminary hardcoded job run ability check --- ui/app/abilities/job.js | 5 +++ ui/app/routes/jobs/run.js | 7 ++++ ui/package.json | 1 + ui/tests/acceptance/job-run-test.js | 12 +++++++ ui/tests/helpers/resolver.js | 2 ++ ui/yarn.lock | 55 ++++++++++++++++++++++++++--- 6 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 ui/app/abilities/job.js diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js new file mode 100644 index 000000000000..dc9de6377126 --- /dev/null +++ b/ui/app/abilities/job.js @@ -0,0 +1,5 @@ +import { Ability } from 'ember-can'; + +export default Ability.extend({ + canRun: true, +}); diff --git a/ui/app/routes/jobs/run.js b/ui/app/routes/jobs/run.js index 1cdf52cdfe6b..309407118a1a 100644 --- a/ui/app/routes/jobs/run.js +++ b/ui/app/routes/jobs/run.js @@ -2,6 +2,7 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; export default Route.extend({ + can: service(), store: service(), system: service(), @@ -12,6 +13,12 @@ export default Route.extend({ }, ], + beforeModel(transition) { + if (this.can.cannot('run job')) { + this.transitionTo('jobs'); + } + }, + model() { return this.store.createRecord('job', { namespace: this.get('system.activeNamespace'), diff --git a/ui/package.json b/ui/package.json index 9f7e9ee36e5a..d35b10c85ef3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -42,6 +42,7 @@ "d3-transition": "^1.1.0", "ember-ajax": "^5.0.0", "ember-auto-import": "^1.2.21", + "ember-can": "^1.1.1", "ember-cli": "~3.4.4", "ember-cli-babel": "^7.1.2", "ember-cli-dependency-checker": "^3.0.0", diff --git a/ui/tests/acceptance/job-run-test.js b/ui/tests/acceptance/job-run-test.js index fed997f480fd..4e51fbb5289e 100644 --- a/ui/tests/acceptance/job-run-test.js +++ b/ui/tests/acceptance/job-run-test.js @@ -2,6 +2,7 @@ import { currentURL } from '@ember/test-helpers'; import { assign } from '@ember/polyfills'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; +import { Ability } from 'ember-can'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; import JobRun from 'nomad-ui/tests/pages/jobs/run'; @@ -85,4 +86,15 @@ module('Acceptance | job run', function(hooks) { `Redirected to the job overview page for ${newJobName} and switched the namespace to ${newNamespace}` ); }); + + test('when the user doesn’t have permission to run a job, redirects to the job overview page', async function(assert) { + const mockJobAbility = Ability.extend({ + canRun: false, + }); + + this.owner.register('ability:job', mockJobAbility); + + await JobRun.visit(); + assert.equal(currentURL(), '/jobs'); + }); }); diff --git a/ui/tests/helpers/resolver.js b/ui/tests/helpers/resolver.js index 319b45fc1c2a..bd9319849b96 100644 --- a/ui/tests/helpers/resolver.js +++ b/ui/tests/helpers/resolver.js @@ -8,4 +8,6 @@ resolver.namespace = { podModulePrefix: config.podModulePrefix, }; +resolver.pluralizedTypes.ability = 'abilities'; + export default resolver; diff --git a/ui/yarn.lock b/ui/yarn.lock index 8d682c2b4831..93c69e8ce73e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1109,6 +1109,13 @@ ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +amd-name-resolver@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-0.0.7.tgz#814301adfe8a2f109f6e84d5e935196efb669615" + integrity sha1-gUMBrf6KLxCfboTV6TUZbvtmlhU= + dependencies: + ensure-posix-path "^1.0.1" + amd-name-resolver@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-1.2.0.tgz#fc41b3848824b557313897d71f8d5a0184fbe679" @@ -1586,7 +1593,7 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-debug-macros@^0.1.10: +babel-plugin-debug-macros@^0.1.10, babel-plugin-debug-macros@^0.1.11: version "0.1.11" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.11.tgz#6c562bf561fccd406ce14ab04f42c218cf956605" integrity sha512-hZw5qNNGAR02Y+yBUrtsnJHh8OXavkayPRqKGAXnIm4t5rWVpj3ArwsC7TWdpZsBguQvHAeyTxZ7s23yY60HHg== @@ -1607,6 +1614,13 @@ babel-plugin-debug-macros@^0.3.0: dependencies: semver "^5.3.0" +babel-plugin-ember-modules-api-polyfill@^2.3.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.9.0.tgz#8503e7b4192aeb336b00265e6235258ff6b754aa" + integrity sha512-c03h50291phJ2gQxo/aIOvFQE2c6glql1A7uagE3XbPXpKVAJOUxtVDjvWG6UAB6BC5ynsJfMWvY0w4TPRKIHQ== + dependencies: + ember-rfc176-data "^0.3.9" + babel-plugin-ember-modules-api-polyfill@^2.6.0, babel-plugin-ember-modules-api-polyfill@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.8.0.tgz#70244800f750bf1c9f380910c1b2eed1db80ab4a" @@ -1883,7 +1897,7 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-polyfill@^6.26.0: +babel-polyfill@^6.16.0, babel-polyfill@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= @@ -1892,7 +1906,7 @@ babel-polyfill@^6.26.0: core-js "^2.5.0" regenerator-runtime "^0.10.5" -babel-preset-env@^1.7.0: +babel-preset-env@^1.5.1, babel-preset-env@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== @@ -2206,7 +2220,7 @@ broccoli-asset-rewrite@^2.0.0: dependencies: broccoli-filter "^1.2.3" -broccoli-babel-transpiler@^6.5.0: +broccoli-babel-transpiler@^6.1.2, broccoli-babel-transpiler@^6.5.0: version "6.5.1" resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.5.1.tgz#a4afc8d3b59b441518eb9a07bd44149476e30738" integrity sha512-w6GcnkxvHcNCte5FcLGEG1hUdQvlfvSN/6PtGWU/otg69Ugk8rUk51h41R0Ugoc+TNxyeFG1opRt2RlA87XzNw== @@ -2321,7 +2335,7 @@ broccoli-config-replace@^1.1.2: debug "^2.2.0" fs-extra "^0.24.0" -broccoli-debug@^0.6.1, broccoli-debug@^0.6.4, broccoli-debug@^0.6.5: +broccoli-debug@^0.6.1, broccoli-debug@^0.6.2, broccoli-debug@^0.6.4, broccoli-debug@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.5.tgz#164a5cdafd8936e525e702bf8f91f39d758e2e78" integrity sha512-RIVjHvNar9EMCLDW/FggxFRXqpjhncM/3qq87bn/y+/zR9tqEkHvTqbyOc4QnB97NO2m6342w4wGkemkaeOuWg== @@ -4066,11 +4080,37 @@ ember-basic-dropdown@^1.1.0: ember-cli-htmlbars "^3.0.1" ember-maybe-in-element "^0.2.0" +ember-can@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ember-can/-/ember-can-1.1.1.tgz#04e02532fc0b45363192208788fba89f0fa5c928" + integrity sha512-IqRVqI86/SEOBNbjlUrHd5aE7ynyVdBDTXveX1EW75lJBaTjhuKuOd1A2dPC9p1m2rHfOttzwgumjJ3BX85m1g== + dependencies: + ember-cli-babel "6.12.0" + ember-cli-babel-plugin-helpers@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac" integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA== +ember-cli-babel@6.12.0: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.12.0.tgz#3adcdbe1278da1fcd0b9038f1360cb4ac5d4414c" + integrity sha512-LMwZ3Xf3Q3jQUXaJtLLJsbbhRZRNv/iea64lZ8OgqZp1fh66CSXfmqV3L9QSuYQKPDNqFiu2v6IpOT08C6GU6w== + dependencies: + amd-name-resolver "0.0.7" + babel-plugin-debug-macros "^0.1.11" + babel-plugin-ember-modules-api-polyfill "^2.3.0" + babel-plugin-transform-es2015-modules-amd "^6.24.0" + babel-polyfill "^6.16.0" + babel-preset-env "^1.5.1" + broccoli-babel-transpiler "^6.1.2" + broccoli-debug "^0.6.2" + broccoli-funnel "^1.0.0" + broccoli-source "^1.1.0" + clone "^2.0.0" + ember-cli-version-checker "^2.1.0" + semver "^5.4.1" + ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.18.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.0: version "6.18.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957" @@ -4820,6 +4860,11 @@ ember-rfc176-data@^0.3.8: resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.8.tgz#d46bbef9a0d57c803217b258cfd2e90d8e191848" integrity sha512-SQup3iG7SDLZNuf7nMMx5BC5truO8AYKRi80gApeQ07NsbuXV4LH75i5eOaxF0i8l9+H1tzv34kGe6rEh0C1NQ== +ember-rfc176-data@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.9.tgz#44b6e051ead6c044ea87bd551f402e2cf89a7e3d" + integrity sha512-EiTo5YQS0Duy0xp9gCP8ekzv9vxirNi7MnIB4zWs+thtWp/mEKgf5mkiiLU2+oo8C5DuavVHhoPQDmyxh8Io1Q== + ember-router-generator@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" From be5d47a3cba3e2e16fd45a4039c993c430106150 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 13:03:48 -0500 Subject: [PATCH 02/45] Change run job button to respect permissions --- ui/app/templates/jobs/index.hbs | 7 ++++++- ui/tests/acceptance/jobs-list-test.js | 18 +++++++++++++++++- ui/tests/pages/jobs/list.js | 6 +++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 169bd5b5d234..5c9dc24aa114 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -13,6 +13,7 @@ {{/if}} {{#if (media "isMobile")}} + {{! FIXME this is duplicated for non-mobile below!}}
{{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}}
@@ -47,7 +48,11 @@ {{#if (not (media "isMobile"))}}
- {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} + {{#if (can "run job")}} + {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} + {{else}} +
Run Job
+ {{/if}}
{{/if}} diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 069151ea0e08..ca6e46b2cf3e 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -1,6 +1,7 @@ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; +import { Ability } from 'ember-can'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import JobsList from 'nomad-ui/tests/pages/jobs/list'; @@ -61,11 +62,26 @@ module('Acceptance | jobs list', function(hooks) { test('the new job button transitions to the new job page', async function(assert) { await JobsList.visit(); - await JobsList.runJob(); + await JobsList.runJobButton.click(); assert.equal(currentURL(), '/jobs/run'); }); + test('the job run button is disabled when the token lacks permission', async function(assert) { + const mockJobAbility = Ability.extend({ + canRun: false, + }); + + this.owner.register('ability:job', mockJobAbility); + + await JobsList.visit(); + + assert.ok(JobsList.runJobButton.isDisabled); + + await JobsList.runJobButton.click(); + assert.equal(currentURL(), '/jobs'); + }); + test('when there are no jobs, there is an empty message', async function(assert) { await JobsList.visit(); diff --git a/ui/tests/pages/jobs/list.js b/ui/tests/pages/jobs/list.js index 2de345e8d219..5d8017cba743 100644 --- a/ui/tests/pages/jobs/list.js +++ b/ui/tests/pages/jobs/list.js @@ -4,6 +4,7 @@ import { collection, clickable, fillable, + is, isPresent, text, visitable, @@ -18,7 +19,10 @@ export default create({ search: fillable('[data-test-jobs-search] input'), - runJob: clickable('[data-test-run-job]'), + runJobButton: { + scope: '[data-test-run-job]', + isDisabled: is('[disabled]'), + }, jobs: collection('[data-test-job-row]', { id: attribute('data-test-job-row'), From e38bbefe78b05759494b42e3c8209de298ce3f71 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 15:19:13 -0500 Subject: [PATCH 03/45] Change permissions to require management token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This isn’t a valid solution, but it gets closer to one by ensuring the token is loaded when the application boots. Then I can add another step to load and parse the token’s policies. --- ui/app/abilities/job.js | 6 ++- ui/app/routes/application.js | 53 ++++++++++++++++----------- ui/app/services/token.js | 14 +++++++ ui/tests/acceptance/job-run-test.js | 14 ++++--- ui/tests/acceptance/jobs-list-test.js | 15 ++++---- 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index dc9de6377126..f369a249b823 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -1,5 +1,9 @@ import { Ability } from 'ember-can'; +import { inject as service } from '@ember/service'; +import { equal } from '@ember/object/computed'; export default Ability.extend({ - canRun: true, + token: service(), + + canRun: equal('token.selfToken.type', 'management'), }); diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index dbc9baa35e54..c0788f3be9da 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -8,6 +8,7 @@ export default Route.extend({ config: service(), system: service(), store: service(), + token: service(), queryParams: { region: { @@ -22,28 +23,38 @@ export default Route.extend({ }, beforeModel(transition) { - return RSVP.all([this.get('system.regions'), this.get('system.defaultRegion')]).then( - promises => { - if (!this.get('system.shouldShowRegions')) return promises; - - const queryParam = transition.queryParams.region; - const defaultRegion = this.get('system.defaultRegion.region'); - const currentRegion = this.get('system.activeRegion') || defaultRegion; - - // Only reset the store if the region actually changed - if ( - (queryParam && queryParam !== currentRegion) || - (!queryParam && currentRegion !== defaultRegion) - ) { - this.system.reset(); - this.store.unloadAll(); - } - - this.set('system.activeRegion', queryParam || defaultRegion); - - return promises; + let fetchSelfToken = RSVP.resolve(true); + + if (this.get('token.secret')) { + fetchSelfToken = this.get('token.fetchSelfToken') + .perform() + .catch(); + } + + return RSVP.all([ + this.get('system.regions'), + this.get('system.defaultRegion'), + fetchSelfToken, + ]).then(promises => { + if (!this.get('system.shouldShowRegions')) return promises; + + const queryParam = transition.queryParams.region; + const defaultRegion = this.get('system.defaultRegion.region'); + const currentRegion = this.get('system.activeRegion') || defaultRegion; + + // Only reset the store if the region actually changed + if ( + (queryParam && queryParam !== currentRegion) || + (!queryParam && currentRegion !== defaultRegion) + ) { + this.system.reset(); + this.store.unloadAll(); } - ); + + this.set('system.activeRegion', queryParam || defaultRegion); + + return promises; + }); }, // Model is being used as a way to transfer the provided region diff --git a/ui/app/services/token.js b/ui/app/services/token.js index f4aece596916..9d3ca17cab01 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -1,6 +1,9 @@ import Service, { inject as service } from '@ember/service'; import { computed } from '@ember/object'; +import { alias } from '@ember/object/computed'; +import { getOwner } from '@ember/application'; import { assign } from '@ember/polyfills'; +import { task } from 'ember-concurrency'; import queryString from 'query-string'; import fetch from 'nomad-ui/utils/fetch'; @@ -22,6 +25,17 @@ export default Service.extend({ }, }), + fetchSelfToken: task(function*() { + const TokenAdapter = getOwner(this).lookup('adapter:token'); + try { + return yield TokenAdapter.findSelf(); + } catch (e) { + return null; + } + }), + + selfToken: alias('fetchSelfToken.lastSuccessful.value'), + // All non Ember Data requests should go through authorizedRequest. // However, the request that gets regions falls into that category. // This authorizedRawRequest is necessary in order to fetch data diff --git a/ui/tests/acceptance/job-run-test.js b/ui/tests/acceptance/job-run-test.js index 4e51fbb5289e..c07e45202796 100644 --- a/ui/tests/acceptance/job-run-test.js +++ b/ui/tests/acceptance/job-run-test.js @@ -2,7 +2,6 @@ import { currentURL } from '@ember/test-helpers'; import { assign } from '@ember/polyfills'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { Ability } from 'ember-can'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; import JobRun from 'nomad-ui/tests/pages/jobs/run'; @@ -10,6 +9,8 @@ import JobRun from 'nomad-ui/tests/pages/jobs/run'; const newJobName = 'new-job'; const newJobTaskGroupName = 'redis'; +let managementToken, clientToken; + const jsonJob = overrides => { return JSON.stringify( assign( @@ -46,6 +47,11 @@ module('Acceptance | job run', function(hooks) { hooks.beforeEach(function() { // Required for placing allocations (a result of creating jobs) server.create('node'); + + managementToken = server.create('token'); + clientToken = server.create('token'); + + window.localStorage.nomadTokenSecret = managementToken.secretId; }); test('visiting /jobs/run', async function(assert) { @@ -88,11 +94,7 @@ module('Acceptance | job run', function(hooks) { }); test('when the user doesn’t have permission to run a job, redirects to the job overview page', async function(assert) { - const mockJobAbility = Ability.extend({ - canRun: false, - }); - - this.owner.register('ability:job', mockJobAbility); + window.localStorage.nomadTokenSecret = clientToken.secretId; await JobRun.visit(); assert.equal(currentURL(), '/jobs'); diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index ca6e46b2cf3e..5a5a2f89537e 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -1,10 +1,11 @@ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { Ability } from 'ember-can'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import JobsList from 'nomad-ui/tests/pages/jobs/list'; +let managementToken, clientToken; + module('Acceptance | jobs list', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -12,6 +13,11 @@ module('Acceptance | jobs list', function(hooks) { hooks.beforeEach(function() { // Required for placing allocations (a result of creating jobs) server.create('node'); + + managementToken = server.create('token'); + clientToken = server.create('token'); + + window.localStorage.nomadTokenSecret = managementToken.secretId; }); test('visiting /jobs', async function(assert) { @@ -68,12 +74,7 @@ module('Acceptance | jobs list', function(hooks) { }); test('the job run button is disabled when the token lacks permission', async function(assert) { - const mockJobAbility = Ability.extend({ - canRun: false, - }); - - this.owner.register('ability:job', mockJobAbility); - + window.localStorage.nomadTokenSecret = clientToken.secretId; await JobsList.visit(); assert.ok(JobsList.runJobButton.isDisabled); From 3c57124fcf73fc2d5af9b98efa3c0895b1a538ab Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 15:19:54 -0500 Subject: [PATCH 04/45] Remove unused parameter --- ui/app/routes/jobs/run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/routes/jobs/run.js b/ui/app/routes/jobs/run.js index 309407118a1a..7bda3b103f88 100644 --- a/ui/app/routes/jobs/run.js +++ b/ui/app/routes/jobs/run.js @@ -13,7 +13,7 @@ export default Route.extend({ }, ], - beforeModel(transition) { + beforeModel() { if (this.can.cannot('run job')) { this.transitionTo('jobs'); } From 19a3e306194520fa22740cf7bac5c3ddd6d83cc2 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 15:31:02 -0500 Subject: [PATCH 05/45] Add token-clearing before regions test --- ui/tests/acceptance/regions-test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js index c8b54c90eb0f..6f24a7f7ac00 100644 --- a/ui/tests/acceptance/regions-test.js +++ b/ui/tests/acceptance/regions-test.js @@ -145,6 +145,8 @@ module('Acceptance | regions (many)', function(hooks) { }); test('when the region is not the default region, all api requests include the region query param', async function(assert) { + // FIXME clear localStorage before every test? 🤔 + window.localStorage.removeItem('nomadTokenSecret'); const region = server.db.regions[1].id; await JobsList.visit({ region }); From c77e4586a2bf2a0a96c002da9918f14009546bde Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 9 Jul 2019 16:17:53 -0500 Subject: [PATCH 06/45] Add placeholder HCL-to-JSON parser --- ui/app/models/policy.js | 11 +++++++++++ ui/package.json | 1 + ui/yarn.lock | 14 ++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/ui/app/models/policy.js b/ui/app/models/policy.js index 47193a6b78d9..4efdf790be84 100644 --- a/ui/app/models/policy.js +++ b/ui/app/models/policy.js @@ -1,8 +1,19 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; +import { computed } from '@ember/object'; +import hclToJson from 'hcl-to-json'; export default Model.extend({ name: attr('string'), description: attr('string'), rules: attr('string'), + + // FIXME remove if/when API can return rules in JSON + rulesJson: computed('rules', function() { + try { + return hclToJson(this.get('rules')); + } catch (e) { + return null; + } + }), }); diff --git a/ui/package.json b/ui/package.json index d35b10c85ef3..e4c7be126310 100644 --- a/ui/package.json +++ b/ui/package.json @@ -86,6 +86,7 @@ "eslint": "^5.16.0", "flat": "^4.0.0", "fuse.js": "^3.4.4", + "hcl-to-json": "^0.1.1", "husky": "^1.3.1", "ivy-codemirror": "^2.1.0", "lint-staged": "^8.1.5", diff --git a/ui/yarn.lock b/ui/yarn.lock index 93c69e8ce73e..8e846d688cb8 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -6200,6 +6200,15 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hcl-to-json@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/hcl-to-json/-/hcl-to-json-0.1.1.tgz#39674aa2a5a4d2b4c2b6762c8196af4af4f12903" + integrity sha512-sj1RPsdgX/ilBGZGnyjbSHQbRe20hyA6VDXYBGJedHSCdwSWkr/7tr85N7FGeM7KvBjIQX7Gl897bo0Ug73Z/A== + dependencies: + debug "^3.0.1" + lodash.get "^4.4.2" + lodash.set "^4.3.2" + heimdalljs-fs-monitor@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/heimdalljs-fs-monitor/-/heimdalljs-fs-monitor-0.2.2.tgz#a76d98f52dbf3aa1b7c20cebb0132e2f5eeb9204" @@ -7528,6 +7537,11 @@ lodash.restparam@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= +lodash.set@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= + lodash.support@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/lodash.support/-/lodash.support-2.3.0.tgz#7eaf038af4f0d6aab776b44aa6dcfc80334c9bfd" From 6509b6cfdbfaddfb41491667af8c5779ab9c8e69 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 11:24:10 -0500 Subject: [PATCH 07/45] Add policy-fetch and interpretation --- ui/app/abilities/job.js | 39 +++++++++++++- ui/app/routes/application.js | 6 +-- ui/app/services/token.js | 15 ++++++ ui/tests/acceptance/jobs-list-test.js | 35 ++++++++++++ ui/tests/unit/abilities/job-test.js | 76 +++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 ui/tests/unit/abilities/job-test.js diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index f369a249b823..a2892aa66e87 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -1,9 +1,44 @@ import { Ability } from 'ember-can'; import { inject as service } from '@ember/service'; -import { equal } from '@ember/object/computed'; +import { computed, getWithDefault } from '@ember/object'; +import { alias, equal, or } from '@ember/object/computed'; export default Ability.extend({ + system: service(), token: service(), - canRun: equal('token.selfToken.type', 'management'), + canRun: or('selfTokenIsManagement', 'policiesSupportRunning'), + + selfTokenIsManagement: equal('token.selfToken.type', 'management'), + + activeNamespace: alias('system.activeNamespace.name'), + + rulesForActiveNamespace: computed( + 'activeNamespace', + 'token.selfTokenPolicies.@each.namespace' /* FIXME not quite */, + function() { + const activeNamespace = this.activeNamespace; + + return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { + if (getWithDefault(policy, 'rulesJson.namespace', {})[activeNamespace]) { + rules.push(policy.rulesJson.namespace[activeNamespace]); + } + + return rules; + }, []); + } + ), + + policiesSupportRunning: computed( + 'rulesForActiveNamespace.@each.policy', + 'rulesForActiveNamespace.@each.capabilities', + function() { + return this.rulesForActiveNamespace.some(rules => { + const policy = rules.policy; + const capabilities = getWithDefault(rules, 'capabilities', []); + + return policy == 'write' || capabilities.includes('submit-job'); + }); + } + ), }); diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index c0788f3be9da..68fc1cd52f1e 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -23,10 +23,10 @@ export default Route.extend({ }, beforeModel(transition) { - let fetchSelfToken = RSVP.resolve(true); + let fetchSelfTokenAndPolicies = RSVP.resolve(true); if (this.get('token.secret')) { - fetchSelfToken = this.get('token.fetchSelfToken') + fetchSelfTokenAndPolicies = this.get('token.fetchSelfTokenAndPolicies') .perform() .catch(); } @@ -34,7 +34,7 @@ export default Route.extend({ return RSVP.all([ this.get('system.regions'), this.get('system.defaultRegion'), - fetchSelfToken, + fetchSelfTokenAndPolicies, ]).then(promises => { if (!this.get('system.shouldShowRegions')) return promises; diff --git a/ui/app/services/token.js b/ui/app/services/token.js index 9d3ca17cab01..40db841d066a 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -36,6 +36,21 @@ export default Service.extend({ selfToken: alias('fetchSelfToken.lastSuccessful.value'), + fetchSelfTokenPolicies: task(function*() { + try { + return this.selfToken.get('policies'); + } catch (e) { + return null; + } + }), + + selfTokenPolicies: alias('fetchSelfTokenPolicies.lastSuccessful.value'), + + fetchSelfTokenAndPolicies: task(function*() { + yield this.fetchSelfToken.perform(); + yield this.fetchSelfTokenPolicies.perform(); + }), + // All non Ember Data requests should go through authorizedRequest. // However, the request that gets regions falls into that category. // This authorizedRawRequest is necessary in order to fetch data diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 5a5a2f89537e..e681bb95438b 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -83,6 +83,41 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(currentURL(), '/jobs'); }); + test('the job run button state can change between namespaces', async function(assert) { + server.createList('namespace', 2); + const job1 = server.create('job', { namespaceId: server.db.namespaces[0].id }); + const job2 = server.create('job', { namespaceId: server.db.namespaces[1].id }); + + window.localStorage.nomadTokenSecret = clientToken.secretId; + + const policy = server.create('policy', { + id: 'something', + name: 'something', + rules: ` + namespace "${job1.namespaceId}" { + policy = "write" + } + + namespace "${job2.namespaceId}" { + capabilities = ["list-jobs"] + } + + node { + policy = "read" + }`, + }); + + clientToken.policyIds = [policy.id]; + clientToken.save(); + + await JobsList.visit(); + assert.notOk(JobsList.runJobButton.isDisabled); + + const secondNamespace = server.db.namespaces[1]; + await JobsList.visit({ namespace: secondNamespace.id }); + assert.ok(JobsList.runJobButton.isDisabled); + }); + test('when there are no jobs, there is an empty message', async function(assert) { await JobsList.visit(); diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js new file mode 100644 index 000000000000..d3f4b47c0e5f --- /dev/null +++ b/ui/tests/unit/abilities/job-test.js @@ -0,0 +1,76 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import Service from '@ember/service'; + +module('Unit | Ability | job run FIXME just for ease of filtering', function(hooks) { + setupTest(hooks); + + test('it permits job run for management tokens', function(assert) { + const mockToken = Service.extend({ + selfToken: { type: 'management' }, + }); + + this.owner.register('service:token', mockToken); + + const jobAbility = this.owner.lookup('ability:job'); + assert.ok(jobAbility.canRun); + }); + + test('it permits job run for client tokens with a policy that has namespace write', function(assert) { + const mockSystem = Service.extend({ + activeNamespace: { + name: 'aNamespace', + }, + }); + + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJson: { + namespace: { + aNamespace: { + policy: 'write', + }, + }, + }, + }, + ], + }); + + this.owner.register('service:system', mockSystem); + this.owner.register('service:token', mockToken); + + const jobAbility = this.owner.lookup('ability:job'); + assert.ok(jobAbility.canRun); + }); + + test('it blocks job run for client tokens with a policy that has namespace read', function(assert) { + const mockSystem = Service.extend({ + activeNamespace: { + name: 'aNamespace', + }, + }); + + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJson: { + namespace: { + aNamespace: { + policy: 'read', + }, + }, + }, + }, + ], + }); + + this.owner.register('service:system', mockSystem); + this.owner.register('service:token', mockToken); + + const jobAbility = this.owner.lookup('ability:job'); + assert.notOk(jobAbility.canRun); + }); +}); From 4f207c272a01f2e9f93c7b4a6434b7f86e857aa1 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 11:32:51 -0500 Subject: [PATCH 08/45] Add missing yield --- ui/app/services/token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/services/token.js b/ui/app/services/token.js index 40db841d066a..9f052b2090e1 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -38,7 +38,7 @@ export default Service.extend({ fetchSelfTokenPolicies: task(function*() { try { - return this.selfToken.get('policies'); + return yield this.selfToken.get('policies'); } catch (e) { return null; } From dc98403824ceb056cc616e2cb3546e141393a503 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 12:04:44 -0500 Subject: [PATCH 09/45] Add fetch of anonymous policy with no token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The endpoint doesn’t actually support this 😳 --- ui/app/abilities/job.js | 6 ++++-- ui/app/routes/application.js | 10 +++------- ui/app/services/token.js | 10 +++++++++- ui/tests/acceptance/jobs-list-test.js | 21 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index a2892aa66e87..aa2bfde19463 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -1,7 +1,7 @@ import { Ability } from 'ember-can'; import { inject as service } from '@ember/service'; import { computed, getWithDefault } from '@ember/object'; -import { alias, equal, or } from '@ember/object/computed'; +import { equal, or } from '@ember/object/computed'; export default Ability.extend({ system: service(), @@ -11,7 +11,9 @@ export default Ability.extend({ selfTokenIsManagement: equal('token.selfToken.type', 'management'), - activeNamespace: alias('system.activeNamespace.name'), + activeNamespace: computed('system.activeNamespace.name', function() { + return this.get('system.activeNamespace.name') || 'default'; + }), rulesForActiveNamespace: computed( 'activeNamespace', diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index 68fc1cd52f1e..329a3333363b 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -23,13 +23,9 @@ export default Route.extend({ }, beforeModel(transition) { - let fetchSelfTokenAndPolicies = RSVP.resolve(true); - - if (this.get('token.secret')) { - fetchSelfTokenAndPolicies = this.get('token.fetchSelfTokenAndPolicies') - .perform() - .catch(); - } + const fetchSelfTokenAndPolicies = this.get('token.fetchSelfTokenAndPolicies') + .perform() + .catch(); return RSVP.all([ this.get('system.regions'), diff --git a/ui/app/services/token.js b/ui/app/services/token.js index 9f052b2090e1..f77ff4193380 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -8,6 +8,7 @@ import queryString from 'query-string'; import fetch from 'nomad-ui/utils/fetch'; export default Service.extend({ + store: service(), system: service(), secret: computed({ @@ -38,7 +39,14 @@ export default Service.extend({ fetchSelfTokenPolicies: task(function*() { try { - return yield this.selfToken.get('policies'); + if (this.selfToken) { + return yield this.selfToken.get('policies'); + } else { + return yield this.store + .findRecord('policy', 'anonymous') + .then(policy => [policy]) + .catch(() => []); + } } catch (e) { return null; } diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index e681bb95438b..2a1af7d4a186 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -118,6 +118,27 @@ module('Acceptance | jobs list', function(hooks) { assert.ok(JobsList.runJobButton.isDisabled); }); + // FIXME it appears to not be possible to fetch the anonymous policy anonymously? + test('the anonymous policy is fetched to check whether to show the job run button', async function(assert) { + window.localStorage.removeItem('nomadTokenSecret'); + + const policy = server.create('policy', { + id: 'anonymous', + name: 'anonymous', + rules: ` + namespace "default" { + policy = "write" + } + + node { + policy = "read" + }`, + }); + + await JobsList.visit(); + assert.notOk(JobsList.runJobButton.isDisabled); + }); + test('when there are no jobs, there is an empty message', async function(assert) { await JobsList.visit(); From 6446054d56f1b2841ee810b3a8b93c2d095c854f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 12:06:28 -0500 Subject: [PATCH 10/45] Remove unused variable --- ui/tests/acceptance/jobs-list-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 2a1af7d4a186..b424c7950489 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -122,7 +122,7 @@ module('Acceptance | jobs list', function(hooks) { test('the anonymous policy is fetched to check whether to show the job run button', async function(assert) { window.localStorage.removeItem('nomadTokenSecret'); - const policy = server.create('policy', { + server.create('policy', { id: 'anonymous', name: 'anonymous', rules: ` From 00291da504b5610d2a7ad59262b52c8bb2e905f1 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 12:07:49 -0500 Subject: [PATCH 11/45] Add anonymous policy to default Mirage scenario --- ui/mirage/scenarios/default.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ui/mirage/scenarios/default.js b/ui/mirage/scenarios/default.js index 62c4c427c3cd..d2c7b9d69daa 100644 --- a/ui/mirage/scenarios/default.js +++ b/ui/mirage/scenarios/default.js @@ -39,6 +39,20 @@ function smallCluster(server) { server.createList('agent', 3); server.createList('node', 5); server.createList('job', 5); + + // FIXME for demonstration only + server.create('policy', { + id: 'anonymous', + name: 'anonymous', + rules: ` + namespace "default" { + policy = "write" + } + + node { + policy = "read" + }`, + }); } function mediumCluster(server) { From 25922789b2d18eaf171bac7cd595545c6420afa0 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 12:08:43 -0500 Subject: [PATCH 12/45] Add Mirage hack to serve anonymous policy This should have been part of dc98403. --- ui/mirage/config.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/mirage/config.js b/ui/mirage/config.js index 24ddf66618fd..15f9add04f2d 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -267,6 +267,15 @@ export default function() { const secret = req.requestHeaders['X-Nomad-Token']; const tokenForSecret = tokens.findBy({ secretId: secret }); + // FIXME this isn’t how the endpoint truly behaves; it requires a token + if (req.params.id === 'anonymous') { + if (policy) { + return this.serialize(policy); + } else { + return new Response(404, {}, null); + } + } + // Return the policy only if the token that matches the request header // includes the policy or if the token that matches the request header // is of type management From 7452cb9af6ca5422aa721b80c0088ff91930795f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 12:26:04 -0500 Subject: [PATCH 13/45] Add tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s cut off by the edge of the viewport for now! --- ui/app/templates/jobs/index.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 5c9dc24aa114..cc246010b618 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -51,7 +51,7 @@ {{#if (can "run job")}} {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} {{else}} -
Run Job
+
Run Job
{{/if}} {{/if}} From 5aa4e03114fc09b845d64986d44fb61dcb77c706 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 13:44:53 -0500 Subject: [PATCH 14/45] Add filtering of policy route 404 These unrelated tests were failing because they assumed that the first 404 was stable. --- ui/tests/acceptance/allocation-detail-test.js | 4 +++- ui/tests/acceptance/client-detail-test.js | 4 +++- ui/tests/acceptance/job-allocations-test.js | 4 +++- ui/tests/acceptance/job-definition-test.js | 4 +++- ui/tests/acceptance/job-deployments-test.js | 4 +++- ui/tests/acceptance/job-detail-test.js | 4 +++- ui/tests/acceptance/job-evaluations-test.js | 4 +++- ui/tests/acceptance/job-versions-test.js | 4 +++- ui/tests/acceptance/task-detail-test.js | 4 +++- ui/tests/acceptance/task-group-detail-test.js | 4 +++- 10 files changed, 30 insertions(+), 10 deletions(-) diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index 66bc2b71b769..985cc374665a 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -148,7 +148,9 @@ module('Acceptance | allocation detail', function(hooks) { await Allocation.visit({ id: 'not-a-real-allocation' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' ); diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index df44f873d3cd..a307471674ec 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -326,7 +326,9 @@ module('Acceptance | client detail', function(hooks) { await ClientDetail.visit({ id: 'not-a-real-node' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/node/not-a-real-node', 'A request to the nonexistent node is made' ); diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index 546d40e7c880..a2f9c9ff35c5 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -103,7 +103,9 @@ module('Acceptance | job allocations', function(hooks) { await Allocations.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/job-definition-test.js b/ui/tests/acceptance/job-definition-test.js index 0b69f11e2e52..0c8a8c4ac0d4 100644 --- a/ui/tests/acceptance/job-definition-test.js +++ b/ui/tests/acceptance/job-definition-test.js @@ -78,7 +78,9 @@ module('Acceptance | job definition', function(hooks) { await Definition.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/job-deployments-test.js b/ui/tests/acceptance/job-deployments-test.js index 3293b35e9525..1b1198b9817b 100644 --- a/ui/tests/acceptance/job-deployments-test.js +++ b/ui/tests/acceptance/job-deployments-test.js @@ -219,7 +219,9 @@ module('Acceptance | job deployments', function(hooks) { await Deployments.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/job-detail-test.js b/ui/tests/acceptance/job-detail-test.js index 9ed22aaa0405..0472c08a40e8 100644 --- a/ui/tests/acceptance/job-detail-test.js +++ b/ui/tests/acceptance/job-detail-test.js @@ -47,7 +47,9 @@ moduleForJob( await JobDetail.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/job-evaluations-test.js b/ui/tests/acceptance/job-evaluations-test.js index 9a7752b372bd..07870bfe45dc 100644 --- a/ui/tests/acceptance/job-evaluations-test.js +++ b/ui/tests/acceptance/job-evaluations-test.js @@ -52,7 +52,9 @@ module('Acceptance | job evaluations', function(hooks) { await Evaluations.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/job-versions-test.js b/ui/tests/acceptance/job-versions-test.js index a06cf8a97add..3311890d0905 100644 --- a/ui/tests/acceptance/job-versions-test.js +++ b/ui/tests/acceptance/job-versions-test.js @@ -39,7 +39,9 @@ module('Acceptance | job versions', function(hooks) { await Versions.visit({ id: 'not-a-real-job' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index a19ce0d89ddc..0c9ca4d3de40 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -143,7 +143,9 @@ module('Acceptance | task detail', function(hooks) { await Task.visit({ id: 'not-a-real-allocation', name: task.name }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' ); diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index fbfca4f78a91..a06f6f42fab5 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -223,7 +223,9 @@ module('Acceptance | task group detail', function(hooks) { await TaskGroup.visit({ id: 'not-a-real-job', name: 'not-a-real-task-group' }); assert.equal( - server.pretender.handledRequests.findBy('status', 404).url, + server.pretender.handledRequests + .reject(request => request.url.includes('policy')) + .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); From 7d132c0045261a2b67f5c4d126aa2c62c1ea39ea Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 10 Jul 2019 13:45:22 -0500 Subject: [PATCH 15/45] Change request-examination to ignore policy query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here’s another similar instance. --- ui/tests/acceptance/regions-test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js index 6f24a7f7ac00..0db48c4cd6e8 100644 --- a/ui/tests/acceptance/regions-test.js +++ b/ui/tests/acceptance/regions-test.js @@ -154,7 +154,12 @@ module('Acceptance | regions (many)', function(hooks) { await JobsList.jobs.objectAt(0).clickRow(); await PageLayout.gutter.visitClients(); await PageLayout.gutter.visitServers(); - const [regionsRequest, defaultRegionRequest, ...appRequests] = server.pretender.handledRequests; + const [ + , + regionsRequest, + defaultRegionRequest, + ...appRequests + ] = server.pretender.handledRequests; assert.notOk( regionsRequest.url.includes('region='), From 701ba1b889f381218908f372d9dbd6ef229605fc Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 17 Jul 2019 13:47:09 -0500 Subject: [PATCH 16/45] Add placeholder test for default fallback --- ui/tests/unit/abilities/job-test.js | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index d3f4b47c0e5f..a57b632702b8 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -1,4 +1,4 @@ -import { module, test } from 'qunit'; +import { module, skip, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; @@ -45,6 +45,39 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo assert.ok(jobAbility.canRun); }); + // TODO is this true, that a more-permissive default wins? + skip('it permits job run for client tokens with a policy that has default namespace write', function(assert) { + const mockSystem = Service.extend({ + activeNamespace: { + name: 'aNamespace', + }, + }); + + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJson: { + namespace: { + aNamespace: { + policy: 'read', + }, + default: { + policy: 'write', + }, + }, + }, + }, + ], + }); + + this.owner.register('service:system', mockSystem); + this.owner.register('service:token', mockToken); + + const jobAbility = this.owner.lookup('ability:job'); + assert.ok(jobAbility.canRun); + }); + test('it blocks job run for client tokens with a policy that has namespace read', function(assert) { const mockSystem = Service.extend({ activeNamespace: { From b1d618e1631975f35f060565e6e04389500721bf Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 18 Jul 2019 08:56:42 -0500 Subject: [PATCH 17/45] Add fallback to default namespace when no matches My placeholder test was incorrect. --- ui/app/abilities/job.js | 8 ++++++-- ui/tests/unit/abilities/job-test.js | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index aa2bfde19463..5cfe04d42687 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -22,8 +22,12 @@ export default Ability.extend({ const activeNamespace = this.activeNamespace; return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - if (getWithDefault(policy, 'rulesJson.namespace', {})[activeNamespace]) { - rules.push(policy.rulesJson.namespace[activeNamespace]); + const policyNamespaces = getWithDefault(policy, 'rulesJson.namespace', {}); + + if (policyNamespaces[activeNamespace]) { + rules.push(policyNamespaces[activeNamespace]); + } else if (policyNamespaces.default) { + rules.push(policyNamespaces.default); } return rules; diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index a57b632702b8..b916959971a5 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -1,4 +1,4 @@ -import { module, skip, test } from 'qunit'; +import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; @@ -45,11 +45,10 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo assert.ok(jobAbility.canRun); }); - // TODO is this true, that a more-permissive default wins? - skip('it permits job run for client tokens with a policy that has default namespace write', function(assert) { + test('it permits job run for client tokens with a policy that has default namespace write and no policy for active namespace', function(assert) { const mockSystem = Service.extend({ activeNamespace: { - name: 'aNamespace', + name: 'anotherNamespace', }, }); From 80d5720794621a9cce096dc3e6408902840434b1 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 18 Jul 2019 09:48:18 -0500 Subject: [PATCH 18/45] Add partial support for globs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The “greatest number of matched characters” part is still forthcoming. --- ui/app/abilities/job.js | 28 +++++++++++++++--- ui/tests/unit/abilities/job-test.js | 44 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 5cfe04d42687..42c972ffcf36 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -24,10 +24,10 @@ export default Ability.extend({ return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { const policyNamespaces = getWithDefault(policy, 'rulesJson.namespace', {}); - if (policyNamespaces[activeNamespace]) { - rules.push(policyNamespaces[activeNamespace]); - } else if (policyNamespaces.default) { - rules.push(policyNamespaces.default); + const matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); + + if (matchingNamespace) { + rules.push(policyNamespaces[matchingNamespace]); } return rules; @@ -47,4 +47,24 @@ export default Ability.extend({ }); } ), + + _findMatchingNamespace(policyNamespaces, activeNamespace) { + if (policyNamespaces[activeNamespace]) { + return activeNamespace; + } + + const namespaceNames = Object.keys(policyNamespaces); + const globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); + + const matchingNamespaceName = globNamespaceNames.find(namespaceName => { + // TODO what kind of protection/sanitisation is needed here, if any? + return activeNamespace.match(new RegExp(namespaceName)); + }); + + if (matchingNamespaceName) { + return matchingNamespaceName; + } else if (policyNamespaces.default) { + return 'default'; + } + }, }); diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index b916959971a5..59c402709ac8 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -105,4 +105,48 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo const jobAbility = this.owner.lookup('ability:job'); assert.notOk(jobAbility.canRun); }); + + test('it handles globs in namespace names', function(assert) { + const mockSystem = Service.extend({ + activeNamespace: { + name: 'aNamespace', + }, + }); + + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJson: { + namespace: { + 'production-*': { + policy: 'write', + }, + 'production-api': { + policy: 'write', + }, + 'production-web': { + policy: 'deny', + }, + }, + }, + }, + ], + }); + + this.owner.register('service:system', mockSystem); + this.owner.register('service:token', mockToken); + + const jobAbility = this.owner.lookup('ability:job'); + const systemService = this.owner.lookup('service:system'); + + systemService.set('activeNamespace.name', 'production-web'); + assert.notOk(jobAbility.canRun); + + systemService.set('activeNamespace.name', 'production-api'); + assert.ok(jobAbility.canRun); + + systemService.set('activeNamespace.name', 'production-other'); + assert.ok(jobAbility.canRun); + }); }); From de122482173844347ae96310c928832c10e46da2 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 18 Jul 2019 11:13:37 -0500 Subject: [PATCH 19/45] Add match-length-comparison for glob names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s too bad reduce is so unwieldy in Javascript… maybe this would be better a for loop ☹️ --- ui/app/abilities/job.js | 25 +++++++++++++++++++++---- ui/tests/unit/abilities/job-test.js | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 42c972ffcf36..bca897ecbf7e 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -56,10 +56,27 @@ export default Ability.extend({ const namespaceNames = Object.keys(policyNamespaces); const globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); - const matchingNamespaceName = globNamespaceNames.find(namespaceName => { - // TODO what kind of protection/sanitisation is needed here, if any? - return activeNamespace.match(new RegExp(namespaceName)); - }); + const matchingNamespaceName = globNamespaceNames.reduce( + (mostMatching, namespaceName) => { + // TODO what kind of protection/sanitisation is needed here, if any? + // and, can there be more than one *? + const namespaceNameRegExp = new RegExp(namespaceName.replace('*', '.*')); + const characterDifference = activeNamespace.length - namespaceName.length; + + if ( + characterDifference < mostMatching.mostMatchingCharacterDifference && + activeNamespace.match(namespaceNameRegExp) + ) { + return { + mostMatchingNamespaceName: namespaceName, + mostMatchingCharacterDifference: characterDifference, + }; + } else { + return mostMatching; + } + }, + { mostMatchingNamespaceName: null, mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER } + ).mostMatchingNamespaceName; if (matchingNamespaceName) { return matchingNamespaceName; diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index 59c402709ac8..a286314aaaae 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -128,6 +128,12 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo 'production-web': { policy: 'deny', }, + '*-suffixed': { + policy: 'write', + }, + '*-more-suffixed': { + policy: 'deny', + }, }, }, }, @@ -148,5 +154,14 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo systemService.set('activeNamespace.name', 'production-other'); assert.ok(jobAbility.canRun); + + systemService.set('activeNamespace.name', 'something-suffixed'); + assert.ok(jobAbility.canRun); + + systemService.set('activeNamespace.name', 'something-more-suffixed'); + assert.notOk( + jobAbility.canRun, + 'expected the namespace with the greatest number of matched characters to be chosen' + ); }); }); From d949352d2b904789a3524a063ad055226098b6e5 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 19 Jul 2019 12:20:59 -0500 Subject: [PATCH 20/45] Add ability to match more than one wildcard --- ui/app/abilities/job.js | 4 ++-- ui/tests/unit/abilities/job-test.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index bca897ecbf7e..bb467f236830 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -59,8 +59,8 @@ export default Ability.extend({ const matchingNamespaceName = globNamespaceNames.reduce( (mostMatching, namespaceName) => { // TODO what kind of protection/sanitisation is needed here, if any? - // and, can there be more than one *? - const namespaceNameRegExp = new RegExp(namespaceName.replace('*', '.*')); + // and, is * the only matchable character? + const namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); const characterDifference = activeNamespace.length - namespaceName.length; if ( diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index a286314aaaae..661369714ff8 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -134,6 +134,9 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo '*-more-suffixed': { policy: 'deny', }, + '*-abc-*': { + policy: 'write', + }, }, }, }, @@ -163,5 +166,8 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo jobAbility.canRun, 'expected the namespace with the greatest number of matched characters to be chosen' ); + + systemService.set('activeNamespace.name', '000-abc-999'); + assert.ok(jobAbility.canRun, 'expected to be able to match against more than one wildcard'); }); }); From 313552adbd0ed3c4e2b2776a4e1c1235a4fa8de7 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 25 Jul 2019 15:09:55 -0500 Subject: [PATCH 21/45] Remove HCL-parsing in favour of API-provided JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is written assuming #6017 is merged as-is. It’s trivial to change the property name if needed! --- ui/app/abilities/job.js | 17 +++--- ui/app/models/policy.js | 12 +---- ui/package.json | 1 - ui/tests/acceptance/jobs-list-test.js | 21 ++++++++ ui/tests/unit/abilities/job-test.js | 74 +++++++++++++++------------ ui/yarn.lock | 14 ----- 6 files changed, 73 insertions(+), 66 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index bb467f236830..d1d64b5337e5 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -17,17 +17,17 @@ export default Ability.extend({ rulesForActiveNamespace: computed( 'activeNamespace', - 'token.selfTokenPolicies.@each.namespace' /* FIXME not quite */, + 'token.selfTokenPolicies.@each.Namespaces' /* FIXME not quite */, function() { const activeNamespace = this.activeNamespace; return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - const policyNamespaces = getWithDefault(policy, 'rulesJson.namespace', {}); + const policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); const matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); if (matchingNamespace) { - rules.push(policyNamespaces[matchingNamespace]); + rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace)); } return rules; @@ -40,8 +40,8 @@ export default Ability.extend({ 'rulesForActiveNamespace.@each.capabilities', function() { return this.rulesForActiveNamespace.some(rules => { - const policy = rules.policy; - const capabilities = getWithDefault(rules, 'capabilities', []); + const policy = rules.Policy; + const capabilities = getWithDefault(rules, 'Capabilities', []); return policy == 'write' || capabilities.includes('submit-job'); }); @@ -49,11 +49,12 @@ export default Ability.extend({ ), _findMatchingNamespace(policyNamespaces, activeNamespace) { - if (policyNamespaces[activeNamespace]) { + const namespaceNames = policyNamespaces.mapBy('Name'); + + if (namespaceNames.includes(activeNamespace)) { return activeNamespace; } - const namespaceNames = Object.keys(policyNamespaces); const globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); const matchingNamespaceName = globNamespaceNames.reduce( @@ -80,7 +81,7 @@ export default Ability.extend({ if (matchingNamespaceName) { return matchingNamespaceName; - } else if (policyNamespaces.default) { + } else if (namespaceNames.includes('default')) { return 'default'; } }, diff --git a/ui/app/models/policy.js b/ui/app/models/policy.js index 4efdf790be84..8b333617bd21 100644 --- a/ui/app/models/policy.js +++ b/ui/app/models/policy.js @@ -1,19 +1,9 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; -import { computed } from '@ember/object'; -import hclToJson from 'hcl-to-json'; export default Model.extend({ name: attr('string'), description: attr('string'), rules: attr('string'), - - // FIXME remove if/when API can return rules in JSON - rulesJson: computed('rules', function() { - try { - return hclToJson(this.get('rules')); - } catch (e) { - return null; - } - }), + rulesJSON: attr(), }); diff --git a/ui/package.json b/ui/package.json index e4c7be126310..d35b10c85ef3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -86,7 +86,6 @@ "eslint": "^5.16.0", "flat": "^4.0.0", "fuse.js": "^3.4.4", - "hcl-to-json": "^0.1.1", "husky": "^1.3.1", "ivy-codemirror": "^2.1.0", "lint-staged": "^8.1.5", diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index b424c7950489..7a542f65987f 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -105,6 +105,19 @@ module('Acceptance | jobs list', function(hooks) { node { policy = "read" }`, + // TODO worth keeping HCL rules for comparison? + rulesJSON: { + Namespaces: [ + { + Name: job1.namespaceId, + Policy: 'write', + }, + { + Name: job2.namespaceId, + Capabilities: ['list-jobs'], + }, + ], + }, }); clientToken.policyIds = [policy.id]; @@ -133,6 +146,14 @@ module('Acceptance | jobs list', function(hooks) { node { policy = "read" }`, + rulesJSON: { + Namespaces: [ + { + Name: 'default', + Policy: 'write', + }, + ], + }, }); await JobsList.visit(); diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index 661369714ff8..061c04f7dbb2 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -27,12 +27,13 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo selfToken: { type: 'client' }, selfTokenPolicies: [ { - rulesJson: { - namespace: { - aNamespace: { - policy: 'write', + rulesJSON: { + Namespaces: [ + { + Name: 'aNamespace', + Policy: 'write', }, - }, + ], }, }, ], @@ -56,15 +57,17 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo selfToken: { type: 'client' }, selfTokenPolicies: [ { - rulesJson: { - namespace: { - aNamespace: { - policy: 'read', + rulesJSON: { + Namespaces: [ + { + Name: 'aNamespace', + Policy: 'read', }, - default: { - policy: 'write', + { + Name: 'default', + Policy: 'write', }, - }, + ], }, }, ], @@ -88,12 +91,13 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo selfToken: { type: 'client' }, selfTokenPolicies: [ { - rulesJson: { - namespace: { - aNamespace: { - policy: 'read', + rulesJSON: { + Namespaces: [ + { + Name: 'aNamespace', + Policy: 'read', }, - }, + ], }, }, ], @@ -117,27 +121,33 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo selfToken: { type: 'client' }, selfTokenPolicies: [ { - rulesJson: { - namespace: { - 'production-*': { - policy: 'write', + rulesJSON: { + Namespaces: [ + { + Name: 'production-*', + Policy: 'write', }, - 'production-api': { - policy: 'write', + { + Name: 'production-api', + Policy: 'write', }, - 'production-web': { - policy: 'deny', + { + Name: 'production-web', + Policy: 'deny', }, - '*-suffixed': { - policy: 'write', + { + Name: '*-suffixed', + Policy: 'write', }, - '*-more-suffixed': { - policy: 'deny', + { + Name: '*-more-suffixed', + Policy: 'deny', }, - '*-abc-*': { - policy: 'write', + { + Name: '*-abc-*', + Policy: 'write', }, - }, + ], }, }, ], diff --git a/ui/yarn.lock b/ui/yarn.lock index 8e846d688cb8..93c69e8ce73e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -6200,15 +6200,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hcl-to-json@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/hcl-to-json/-/hcl-to-json-0.1.1.tgz#39674aa2a5a4d2b4c2b6762c8196af4af4f12903" - integrity sha512-sj1RPsdgX/ilBGZGnyjbSHQbRe20hyA6VDXYBGJedHSCdwSWkr/7tr85N7FGeM7KvBjIQX7Gl897bo0Ug73Z/A== - dependencies: - debug "^3.0.1" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - heimdalljs-fs-monitor@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/heimdalljs-fs-monitor/-/heimdalljs-fs-monitor-0.2.2.tgz#a76d98f52dbf3aa1b7c20cebb0132e2f5eeb9204" @@ -7537,11 +7528,6 @@ lodash.restparam@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= -lodash.set@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= - lodash.support@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/lodash.support/-/lodash.support-2.3.0.tgz#7eaf038af4f0d6aab776b44aa6dcfc80334c9bfd" From d2de141bc3085a88c164849e94f76a4203cc3514 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 25 Jul 2019 15:41:16 -0500 Subject: [PATCH 22/45] Add question about only looking at capabilities --- ui/app/abilities/job.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index d1d64b5337e5..830bf447e3b8 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -40,6 +40,9 @@ export default Ability.extend({ 'rulesForActiveNamespace.@each.capabilities', function() { return this.rulesForActiveNamespace.some(rules => { + // TODO given that the API returns a fully-expanded set of rules, + // where just a policy word turns into an array of capabilities, + // maybe checking capabilities is the only necessity? const policy = rules.Policy; const capabilities = getWithDefault(rules, 'Capabilities', []); From a6641e2b382d2a3419b4584a271ebf50efb7e140 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 25 Jul 2019 15:42:10 -0500 Subject: [PATCH 23/45] Remove question about matchable characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This regex shows what’s permitted: https://github.com/hashicorp/nomad/blob/5abbee5d396c0ce693cae570a6a8ac43aa640cdc/acl/policy.go#L38 --- ui/app/abilities/job.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 830bf447e3b8..db10e5c2e1e7 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -63,7 +63,6 @@ export default Ability.extend({ const matchingNamespaceName = globNamespaceNames.reduce( (mostMatching, namespaceName) => { // TODO what kind of protection/sanitisation is needed here, if any? - // and, is * the only matchable character? const namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); const characterDifference = activeNamespace.length - namespaceName.length; From 2a5e0841ecb024517c804741a0af7aca18abde50 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 16:55:59 -0500 Subject: [PATCH 24/45] Change ability to check capabilities, not policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API change in #6017 returns JSON that contains the expanded policy, including all capabilities implied by a policy. So even if you set `policy="write"`, you get back a list of capabilities that includes `submit-job`. There’s therefore no reason to examine the returned policy. --- ui/app/abilities/job.js | 7 +------ ui/tests/acceptance/jobs-list-test.js | 8 ++++---- ui/tests/unit/abilities/job-test.js | 26 +++++++++++++------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index db10e5c2e1e7..7406141dd80b 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -40,13 +40,8 @@ export default Ability.extend({ 'rulesForActiveNamespace.@each.capabilities', function() { return this.rulesForActiveNamespace.some(rules => { - // TODO given that the API returns a fully-expanded set of rules, - // where just a policy word turns into an array of capabilities, - // maybe checking capabilities is the only necessity? - const policy = rules.Policy; const capabilities = getWithDefault(rules, 'Capabilities', []); - - return policy == 'write' || capabilities.includes('submit-job'); + return capabilities.includes('submit-job'); }); } ), diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index b043fb79403a..80f810108ae9 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -96,7 +96,7 @@ module('Acceptance | jobs list', function(hooks) { name: 'something', rules: ` namespace "${job1.namespaceId}" { - policy = "write" + capabilities = ["list-jobs", "submit-job"] } namespace "${job2.namespaceId}" { @@ -111,7 +111,7 @@ module('Acceptance | jobs list', function(hooks) { Namespaces: [ { Name: job1.namespaceId, - Policy: 'write', + Capabilities: ['list-jobs', 'submit-job'], }, { Name: job2.namespaceId, @@ -141,7 +141,7 @@ module('Acceptance | jobs list', function(hooks) { name: 'anonymous', rules: ` namespace "default" { - policy = "write" + capabilities = ["list-jobs", "submit-job"] } node { @@ -151,7 +151,7 @@ module('Acceptance | jobs list', function(hooks) { Namespaces: [ { Name: 'default', - Policy: 'write', + Capabilities: ['list-jobs', 'submit-job'], }, ], }, diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index 061c04f7dbb2..8e45fe0ecbc7 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -16,7 +16,7 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo assert.ok(jobAbility.canRun); }); - test('it permits job run for client tokens with a policy that has namespace write', function(assert) { + test('it permits job run for client tokens with a policy that has namespace submit-job', function(assert) { const mockSystem = Service.extend({ activeNamespace: { name: 'aNamespace', @@ -31,7 +31,7 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo Namespaces: [ { Name: 'aNamespace', - Policy: 'write', + Capabilities: ['submit-job'], }, ], }, @@ -46,7 +46,7 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo assert.ok(jobAbility.canRun); }); - test('it permits job run for client tokens with a policy that has default namespace write and no policy for active namespace', function(assert) { + test('it permits job run for client tokens with a policy that has default namespace submit-job and no capabilities for active namespace', function(assert) { const mockSystem = Service.extend({ activeNamespace: { name: 'anotherNamespace', @@ -61,11 +61,11 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo Namespaces: [ { Name: 'aNamespace', - Policy: 'read', + Capabilities: [], }, { Name: 'default', - Policy: 'write', + Capabilities: ['submit-job'], }, ], }, @@ -80,7 +80,7 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo assert.ok(jobAbility.canRun); }); - test('it blocks job run for client tokens with a policy that has namespace read', function(assert) { + test('it blocks job run for client tokens with a policy that has no submit-job capability', function(assert) { const mockSystem = Service.extend({ activeNamespace: { name: 'aNamespace', @@ -95,7 +95,7 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo Namespaces: [ { Name: 'aNamespace', - Policy: 'read', + Capabilities: ['list-jobs'], }, ], }, @@ -125,27 +125,27 @@ module('Unit | Ability | job run FIXME just for ease of filtering', function(hoo Namespaces: [ { Name: 'production-*', - Policy: 'write', + Capabilities: ['submit-job'], }, { Name: 'production-api', - Policy: 'write', + Capabilities: ['submit-job'], }, { Name: 'production-web', - Policy: 'deny', + Capabilities: [], }, { Name: '*-suffixed', - Policy: 'write', + Capabilities: ['submit-job'], }, { Name: '*-more-suffixed', - Policy: 'deny', + Capabilities: [], }, { Name: '*-abc-*', - Policy: 'write', + Capabilities: ['submit-job'], }, ], }, From 62efe99d7a115af889640b5ecc3b83aee89c650f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 16:56:50 -0500 Subject: [PATCH 25/45] Remove HCL versions of rules These were only being used with hcl2json. --- ui/tests/acceptance/jobs-list-test.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 80f810108ae9..cfea8b08145d 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -94,19 +94,6 @@ module('Acceptance | jobs list', function(hooks) { const policy = server.create('policy', { id: 'something', name: 'something', - rules: ` - namespace "${job1.namespaceId}" { - capabilities = ["list-jobs", "submit-job"] - } - - namespace "${job2.namespaceId}" { - capabilities = ["list-jobs"] - } - - node { - policy = "read" - }`, - // TODO worth keeping HCL rules for comparison? rulesJSON: { Namespaces: [ { @@ -139,14 +126,6 @@ module('Acceptance | jobs list', function(hooks) { server.create('policy', { id: 'anonymous', name: 'anonymous', - rules: ` - namespace "default" { - capabilities = ["list-jobs", "submit-job"] - } - - node { - policy = "read" - }`, rulesJSON: { Namespaces: [ { From 89ef21597d36edaa0220ed9f31f8770980c41554 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:07:39 -0500 Subject: [PATCH 26/45] Add link to explain namespace-matching --- ui/app/abilities/job.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 7406141dd80b..2712b4f7f468 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -46,6 +46,8 @@ export default Ability.extend({ } ), + // Chooses the closest namespace as described at the bottom here: + // https://www.nomadproject.io/guides/security/acl.html#namespace-rules _findMatchingNamespace(policyNamespaces, activeNamespace) { const namespaceNames = policyNamespaces.mapBy('Name'); From 08f534197676b7312cff74446b3480f0243e4d47 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:48:59 -0500 Subject: [PATCH 27/45] Update to latest Ember-can --- ui/package.json | 2 +- ui/yarn.lock | 109 +++++++++++++++++++----------------------------- 2 files changed, 43 insertions(+), 68 deletions(-) diff --git a/ui/package.json b/ui/package.json index f35f554ecbe5..7ea660f67d46 100644 --- a/ui/package.json +++ b/ui/package.json @@ -43,7 +43,7 @@ "d3-transition": "^1.1.0", "ember-ajax": "^5.0.0", "ember-auto-import": "^1.2.21", - "ember-can": "^1.1.1", + "ember-can": "^2.0.0", "ember-cli": "~3.4.4", "ember-cli-babel": "^7.1.2", "ember-cli-clipboard": "^0.13.0", diff --git a/ui/yarn.lock b/ui/yarn.lock index 0a6d9256009f..b6be26bd8617 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1114,13 +1114,6 @@ ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -amd-name-resolver@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-0.0.7.tgz#814301adfe8a2f109f6e84d5e935196efb669615" - integrity sha1-gUMBrf6KLxCfboTV6TUZbvtmlhU= - dependencies: - ensure-posix-path "^1.0.1" - amd-name-resolver@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-1.2.0.tgz#fc41b3848824b557313897d71f8d5a0184fbe679" @@ -1598,7 +1591,7 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-debug-macros@^0.1.10, babel-plugin-debug-macros@^0.1.11: +babel-plugin-debug-macros@^0.1.10: version "0.1.11" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.11.tgz#6c562bf561fccd406ce14ab04f42c218cf956605" integrity sha512-hZw5qNNGAR02Y+yBUrtsnJHh8OXavkayPRqKGAXnIm4t5rWVpj3ArwsC7TWdpZsBguQvHAeyTxZ7s23yY60HHg== @@ -1619,13 +1612,6 @@ babel-plugin-debug-macros@^0.3.0: dependencies: semver "^5.3.0" -babel-plugin-ember-modules-api-polyfill@^2.3.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.9.0.tgz#8503e7b4192aeb336b00265e6235258ff6b754aa" - integrity sha512-c03h50291phJ2gQxo/aIOvFQE2c6glql1A7uagE3XbPXpKVAJOUxtVDjvWG6UAB6BC5ynsJfMWvY0w4TPRKIHQ== - dependencies: - ember-rfc176-data "^0.3.9" - babel-plugin-ember-modules-api-polyfill@^2.6.0, babel-plugin-ember-modules-api-polyfill@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.8.0.tgz#70244800f750bf1c9f380910c1b2eed1db80ab4a" @@ -1909,7 +1895,7 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-polyfill@^6.16.0, babel-polyfill@^6.26.0: +babel-polyfill@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= @@ -1918,7 +1904,7 @@ babel-polyfill@^6.16.0, babel-polyfill@^6.26.0: core-js "^2.5.0" regenerator-runtime "^0.10.5" -babel-preset-env@^1.5.1, babel-preset-env@^1.7.0: +babel-preset-env@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== @@ -2232,7 +2218,7 @@ broccoli-asset-rewrite@^2.0.0: dependencies: broccoli-filter "^1.2.3" -broccoli-babel-transpiler@^6.1.2, broccoli-babel-transpiler@^6.5.0: +broccoli-babel-transpiler@^6.5.0: version "6.5.1" resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.5.1.tgz#a4afc8d3b59b441518eb9a07bd44149476e30738" integrity sha512-w6GcnkxvHcNCte5FcLGEG1hUdQvlfvSN/6PtGWU/otg69Ugk8rUk51h41R0Ugoc+TNxyeFG1opRt2RlA87XzNw== @@ -2347,7 +2333,7 @@ broccoli-config-replace@^1.1.2: debug "^2.2.0" fs-extra "^0.24.0" -broccoli-debug@^0.6.1, broccoli-debug@^0.6.2, broccoli-debug@^0.6.4, broccoli-debug@^0.6.5: +broccoli-debug@^0.6.1, broccoli-debug@^0.6.4, broccoli-debug@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.5.tgz#164a5cdafd8936e525e702bf8f91f39d758e2e78" integrity sha512-RIVjHvNar9EMCLDW/FggxFRXqpjhncM/3qq87bn/y+/zR9tqEkHvTqbyOc4QnB97NO2m6342w4wGkemkaeOuWg== @@ -4106,36 +4092,45 @@ ember-basic-dropdown@^1.1.0: ember-cli-htmlbars "^3.0.1" ember-maybe-in-element "^0.2.0" -ember-can@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ember-can/-/ember-can-1.1.1.tgz#04e02532fc0b45363192208788fba89f0fa5c928" - integrity sha512-IqRVqI86/SEOBNbjlUrHd5aE7ynyVdBDTXveX1EW75lJBaTjhuKuOd1A2dPC9p1m2rHfOttzwgumjJ3BX85m1g== +ember-can@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-can/-/ember-can-2.0.0.tgz#b01400380b42aedd9570b89521997a34677feab6" + integrity sha512-4c0HcXUC1HiNwGmW7Gp72Ojhlr/uULQTdJp85up4G3MjonCdV0ZdvPLsMIQITBgWqWY/H5HezMjrdaIDFuEDBA== dependencies: - ember-cli-babel "6.12.0" + ember-cli-babel "7.8.0" + ember-inflector "3.0.1" ember-cli-babel-plugin-helpers@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac" integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA== -ember-cli-babel@6.12.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.12.0.tgz#3adcdbe1278da1fcd0b9038f1360cb4ac5d4414c" - integrity sha512-LMwZ3Xf3Q3jQUXaJtLLJsbbhRZRNv/iea64lZ8OgqZp1fh66CSXfmqV3L9QSuYQKPDNqFiu2v6IpOT08C6GU6w== +ember-cli-babel@7.8.0, ember-cli-babel@^7.7.3: + version "7.8.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.8.0.tgz#e596500eca0f5a7c9aaee755f803d1542f578acf" + integrity sha512-xUBgJQ81fqd7k/KIiGU+pjpoXhrmmRf9pUrqLenNSU5N+yeNFT5a1+w0b+p1F7oBphfXVwuxApdZxrmAHOdA3Q== dependencies: - amd-name-resolver "0.0.7" - babel-plugin-debug-macros "^0.1.11" - babel-plugin-ember-modules-api-polyfill "^2.3.0" - babel-plugin-transform-es2015-modules-amd "^6.24.0" - babel-polyfill "^6.16.0" - babel-preset-env "^1.5.1" - broccoli-babel-transpiler "^6.1.2" - broccoli-debug "^0.6.2" - broccoli-funnel "^1.0.0" + "@babel/core" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.3.4" + "@babel/plugin-proposal-decorators" "^7.3.0" + "@babel/plugin-transform-modules-amd" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.2.0" + "@babel/polyfill" "^7.0.0" + "@babel/preset-env" "^7.0.0" + "@babel/runtime" "^7.2.0" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.3.0" + babel-plugin-ember-modules-api-polyfill "^2.9.0" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.1.2" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" broccoli-source "^1.1.0" - clone "^2.0.0" - ember-cli-version-checker "^2.1.0" - semver "^5.4.1" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.0" + ember-cli-version-checker "^2.1.2" + ensure-posix-path "^1.0.2" + semver "^5.5.0" ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.18.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.0: version "6.18.0" @@ -4183,33 +4178,6 @@ ember-cli-babel@^7.1.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.2.0, ember-cl ensure-posix-path "^1.0.2" semver "^5.5.0" -ember-cli-babel@^7.7.3: - version "7.8.0" - resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.8.0.tgz#e596500eca0f5a7c9aaee755f803d1542f578acf" - integrity sha512-xUBgJQ81fqd7k/KIiGU+pjpoXhrmmRf9pUrqLenNSU5N+yeNFT5a1+w0b+p1F7oBphfXVwuxApdZxrmAHOdA3Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.3.4" - "@babel/plugin-proposal-decorators" "^7.3.0" - "@babel/plugin-transform-modules-amd" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.2.0" - "@babel/polyfill" "^7.0.0" - "@babel/preset-env" "^7.0.0" - "@babel/runtime" "^7.2.0" - amd-name-resolver "^1.2.1" - babel-plugin-debug-macros "^0.3.0" - babel-plugin-ember-modules-api-polyfill "^2.9.0" - babel-plugin-module-resolver "^3.1.1" - broccoli-babel-transpiler "^7.1.2" - broccoli-debug "^0.6.4" - broccoli-funnel "^2.0.1" - broccoli-source "^1.1.0" - clone "^2.1.2" - ember-cli-babel-plugin-helpers "^1.1.0" - ember-cli-version-checker "^2.1.2" - ensure-posix-path "^1.0.2" - semver "^5.5.0" - ember-cli-broccoli-sane-watcher@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.2.2.tgz#9bb1b04ddeb2c086aecd8693cbaeca1d88dc160c" @@ -4798,6 +4766,13 @@ ember-getowner-polyfill@^2.0.1, ember-getowner-polyfill@^2.2.0: ember-cli-version-checker "^2.1.0" ember-factory-for-polyfill "^1.3.1" +ember-inflector@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-3.0.1.tgz#04be6df4d7e4000f6d6bd70787cdc995f77be4ab" + integrity sha512-fngrwMsnhkBt51KZgwNwQYxgURwV4lxtoHdjxf7RueGZ5zM7frJLevhHw7pbQNGqXZ3N+MRkhfNOLkdDK9kFdA== + dependencies: + ember-cli-babel "^6.6.0" + "ember-inflector@^2.0.0 || ^3.0.0", ember-inflector@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-3.0.0.tgz#7e1ee8aaa0fa773ba0905d8b7c0786354d890ee1" From 892f544785aa168027d0cf7afdbdecaacf60c30f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:51:00 -0500 Subject: [PATCH 28/45] Add conditional button for mobile --- ui/app/templates/jobs/index.hbs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 02f9db3e4c58..89b9d111da85 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -14,9 +14,12 @@ {{/if}} {{#if (media "isMobile")}} - {{! FIXME this is duplicated for non-mobile below!}}
- {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} + {{#if (can "run job")}} + {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} + {{else}} +
Run Job
+ {{/if}}
{{/if}}
From 7b354edcbf422428da1527e241ad691f9332986b Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:53:02 -0500 Subject: [PATCH 29/45] Correct module name --- ui/tests/unit/abilities/job-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index 8e45fe0ecbc7..f0e337192476 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -2,7 +2,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; -module('Unit | Ability | job run FIXME just for ease of filtering', function(hooks) { +module('Unit | Ability | job', function(hooks) { setupTest(hooks); test('it permits job run for management tokens', function(assert) { From 05f47867e631ecd3a9c8f469e720f35a4d2f2920 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:56:27 -0500 Subject: [PATCH 30/45] Correct dependent key --- ui/app/abilities/job.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 2712b4f7f468..0dc2354df330 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -15,25 +15,21 @@ export default Ability.extend({ return this.get('system.activeNamespace.name') || 'default'; }), - rulesForActiveNamespace: computed( - 'activeNamespace', - 'token.selfTokenPolicies.@each.Namespaces' /* FIXME not quite */, - function() { - const activeNamespace = this.activeNamespace; + rulesForActiveNamespace: computed('activeNamespace', 'token.selfTokenPolicies.[]', function() { + const activeNamespace = this.activeNamespace; - return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - const policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); + return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { + const policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); - const matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); + const matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); - if (matchingNamespace) { - rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace)); - } + if (matchingNamespace) { + rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace)); + } - return rules; - }, []); - } - ), + return rules; + }, []); + }), policiesSupportRunning: computed( 'rulesForActiveNamespace.@each.policy', From e9ddcf2deb38b4bf0501cfbc05a38f4e3e903c28 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 29 Aug 2019 17:58:57 -0500 Subject: [PATCH 31/45] Add explanatory comment --- ui/app/abilities/job.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 0dc2354df330..98c1897483a1 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -55,7 +55,7 @@ export default Ability.extend({ const matchingNamespaceName = globNamespaceNames.reduce( (mostMatching, namespaceName) => { - // TODO what kind of protection/sanitisation is needed here, if any? + // Convert * wildcards to .* for regex matching const namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); const characterDifference = activeNamespace.length - namespaceName.length; From f878d7271f8876c36685d451f906c2d69f0a7fa8 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 26 Nov 2019 12:18:15 -0600 Subject: [PATCH 32/45] Remove comment superseded by #6021 --- ui/tests/acceptance/jobs-list-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index df835fe2b092..31b3042dee1d 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -119,7 +119,6 @@ module('Acceptance | jobs list', function(hooks) { assert.ok(JobsList.runJobButton.isDisabled); }); - // FIXME it appears to not be possible to fetch the anonymous policy anonymously? test('the anonymous policy is fetched to check whether to show the job run button', async function(assert) { window.localStorage.removeItem('nomadTokenSecret'); From 284e615993145907811fbbb0b73609347af6103b Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 26 Nov 2019 14:15:09 -0600 Subject: [PATCH 33/45] Remove unused dependent key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the API expands a policy shorthand like “write” into its constituent capabilities, examining the policy is no longer needed. --- ui/app/abilities/job.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 98c1897483a1..727ba34d9277 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -31,16 +31,12 @@ export default Ability.extend({ }, []); }), - policiesSupportRunning: computed( - 'rulesForActiveNamespace.@each.policy', - 'rulesForActiveNamespace.@each.capabilities', - function() { - return this.rulesForActiveNamespace.some(rules => { - const capabilities = getWithDefault(rules, 'Capabilities', []); - return capabilities.includes('submit-job'); - }); - } - ), + policiesSupportRunning: computed('rulesForActiveNamespace.@each.capabilities', function() { + return this.rulesForActiveNamespace.some(rules => { + const capabilities = getWithDefault(rules, 'Capabilities', []); + return capabilities.includes('submit-job'); + }); + }), // Chooses the closest namespace as described at the bottom here: // https://www.nomadproject.io/guides/security/acl.html#namespace-rules From d5c30717dee21b6f1891a1690cb0e3ebf464a223 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 2 Dec 2019 14:40:42 -0600 Subject: [PATCH 34/45] Remove extraneous whitespace --- ui/mirage/scenarios/default.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/mirage/scenarios/default.js b/ui/mirage/scenarios/default.js index 87d66777aeb7..df8e0799ed2c 100644 --- a/ui/mirage/scenarios/default.js +++ b/ui/mirage/scenarios/default.js @@ -39,7 +39,6 @@ function smallCluster(server) { server.createList('agent', 3); server.createList('node', 5); server.createList('job', 5); - server.createList('allocFile', 5); server.create('allocFile', 'dir', { depth: 2 }); From 1dd92847000e66040159087fe297cdc9577126b4 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 2 Dec 2019 14:42:29 -0600 Subject: [PATCH 35/45] Change const to let --- ui/app/abilities/job.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 727ba34d9277..6cbec5062d9c 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -16,12 +16,12 @@ export default Ability.extend({ }), rulesForActiveNamespace: computed('activeNamespace', 'token.selfTokenPolicies.[]', function() { - const activeNamespace = this.activeNamespace; + let activeNamespace = this.activeNamespace; return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - const policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); + let policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); - const matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); + let matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); if (matchingNamespace) { rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace)); @@ -33,7 +33,7 @@ export default Ability.extend({ policiesSupportRunning: computed('rulesForActiveNamespace.@each.capabilities', function() { return this.rulesForActiveNamespace.some(rules => { - const capabilities = getWithDefault(rules, 'Capabilities', []); + let capabilities = getWithDefault(rules, 'Capabilities', []); return capabilities.includes('submit-job'); }); }), @@ -41,19 +41,19 @@ export default Ability.extend({ // Chooses the closest namespace as described at the bottom here: // https://www.nomadproject.io/guides/security/acl.html#namespace-rules _findMatchingNamespace(policyNamespaces, activeNamespace) { - const namespaceNames = policyNamespaces.mapBy('Name'); + let namespaceNames = policyNamespaces.mapBy('Name'); if (namespaceNames.includes(activeNamespace)) { return activeNamespace; } - const globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); + let globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); - const matchingNamespaceName = globNamespaceNames.reduce( + let matchingNamespaceName = globNamespaceNames.reduce( (mostMatching, namespaceName) => { // Convert * wildcards to .* for regex matching - const namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); - const characterDifference = activeNamespace.length - namespaceName.length; + let namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); + let characterDifference = activeNamespace.length - namespaceName.length; if ( characterDifference < mostMatching.mostMatchingCharacterDifference && From c3a0afacca5460be2895f063cd67d7fb0b179f9d Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 2 Dec 2019 14:46:56 -0600 Subject: [PATCH 36/45] Remove extraneous autoformat changes --- ui/app/components/job-deployments-stream.js | 4 +++- ui/app/components/job-versions-stream.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/app/components/job-deployments-stream.js b/ui/app/components/job-deployments-stream.js index 163fd9938139..7a5e3d5d5e54 100644 --- a/ui/app/components/job-deployments-stream.js +++ b/ui/app/components/job-deployments-stream.js @@ -10,7 +10,9 @@ export default Component.extend({ deployments: overridable(() => []), sortedDeployments: computed('deployments.@each.versionSubmitTime', function() { - return this.deployments.sortBy('versionSubmitTime').reverse(); + return this.deployments + .sortBy('versionSubmitTime') + .reverse(); }), annotatedDeployments: computed('sortedDeployments.@each.version', function() { diff --git a/ui/app/components/job-versions-stream.js b/ui/app/components/job-versions-stream.js index f2961a36b6ce..3fc938ea0d65 100644 --- a/ui/app/components/job-versions-stream.js +++ b/ui/app/components/job-versions-stream.js @@ -13,7 +13,9 @@ export default Component.extend({ verbose: true, annotatedVersions: computed('versions.[]', function() { - const versions = this.versions.sortBy('submitTime').reverse(); + const versions = this.versions + .sortBy('submitTime') + .reverse(); return versions.map((version, index) => { const meta = {}; From 71c2fac0565b6a77db918623221db57483074e1d Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 3 Dec 2019 15:29:05 -0600 Subject: [PATCH 37/45] Remove comment superseded by #6021 --- ui/mirage/config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/mirage/config.js b/ui/mirage/config.js index 08018404164f..a8ca27446aa7 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -278,7 +278,6 @@ export default function() { const secret = req.requestHeaders['X-Nomad-Token']; const tokenForSecret = tokens.findBy({ secretId: secret }); - // FIXME this isn’t how the endpoint truly behaves; it requires a token if (req.params.id === 'anonymous') { if (policy) { return this.serialize(policy); From 6f68c0e1bab6fc3431d5607eb76ac8727f7fa994 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Tue, 3 Dec 2019 15:37:17 -0600 Subject: [PATCH 38/45] Remove Mirage anonymous policy --- ui/mirage/scenarios/default.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ui/mirage/scenarios/default.js b/ui/mirage/scenarios/default.js index df8e0799ed2c..0a7269415bb4 100644 --- a/ui/mirage/scenarios/default.js +++ b/ui/mirage/scenarios/default.js @@ -41,20 +41,6 @@ function smallCluster(server) { server.createList('job', 5); server.createList('allocFile', 5); server.create('allocFile', 'dir', { depth: 2 }); - - // FIXME for demonstration only - server.create('policy', { - id: 'anonymous', - name: 'anonymous', - rules: ` - namespace "default" { - policy = "write" - } - - node { - policy = "read" - }`, - }); } function mediumCluster(server) { From 777df99bcaac8fe1ea90c0cf46c64b90e283e475 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 4 Dec 2019 11:12:07 -0600 Subject: [PATCH 39/45] Remove FIXME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m hesitant to add storage-clearing before every test. --- ui/tests/acceptance/regions-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js index e2dcdfb46e14..2bfeb53f2df4 100644 --- a/ui/tests/acceptance/regions-test.js +++ b/ui/tests/acceptance/regions-test.js @@ -147,7 +147,6 @@ module('Acceptance | regions (many)', function(hooks) { }); test('when the region is not the default region, all api requests include the region query param', async function(assert) { - // FIXME clear localStorage before every test? 🤔 window.localStorage.removeItem('nomadTokenSecret'); const region = server.db.regions[1].id; From c50c55ce5c49c4766a254758dc71815bc2095413 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 4 Dec 2019 11:32:08 -0600 Subject: [PATCH 40/45] Change tooltip to be right-aligned --- ui/app/styles/components/tooltip.scss | 4 ++++ ui/app/templates/jobs/index.hbs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/app/styles/components/tooltip.scss b/ui/app/styles/components/tooltip.scss index 38bddb5e6aed..8531269c7d26 100644 --- a/ui/app/styles/components/tooltip.scss +++ b/ui/app/styles/components/tooltip.scss @@ -46,6 +46,10 @@ transition: top 0.1s ease-in-out; } +.tooltip.is-right-aligned::after { + transform: translateX(-75%); +} + .tooltip:hover::after, .tooltip.always-active::after { opacity: 1; diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index c8291217c786..87ab89579252 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -18,7 +18,7 @@ {{#if (can "run job")}} {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} {{else}} -
Run Job
+
Run Job
{{/if}}
{{/if}} @@ -55,7 +55,7 @@ {{#if (can "run job")}} {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} {{else}} -
Run Job
+
Run Job
{{/if}} {{/if}} From 94304e6f12764cb03e766c322cfc40c692a9d9b7 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 5 Dec 2019 11:10:11 -0600 Subject: [PATCH 41/45] Remove uses of getWithDefault Thanks to @johncowen for pointing out that this is on the way to being deprecated: https://github.com/hashicorp/nomad/pull/5944#discussion_r354187458 https://github.com/emberjs/rfcs/pull/554 --- ui/app/abilities/job.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 6cbec5062d9c..5829b4861504 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -1,6 +1,6 @@ import { Ability } from 'ember-can'; import { inject as service } from '@ember/service'; -import { computed, getWithDefault } from '@ember/object'; +import { computed, get } from '@ember/object'; import { equal, or } from '@ember/object/computed'; export default Ability.extend({ @@ -19,7 +19,7 @@ export default Ability.extend({ let activeNamespace = this.activeNamespace; return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - let policyNamespaces = getWithDefault(policy, 'rulesJSON.Namespaces', []); + let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || []; let matchingNamespace = this._findMatchingNamespace(policyNamespaces, activeNamespace); @@ -33,7 +33,7 @@ export default Ability.extend({ policiesSupportRunning: computed('rulesForActiveNamespace.@each.capabilities', function() { return this.rulesForActiveNamespace.some(rules => { - let capabilities = getWithDefault(rules, 'Capabilities', []); + let capabilities = get(rules, 'Capabilities') || []; return capabilities.includes('submit-job'); }); }), From e0545619b4e131939f0e500f4076af50a92e76f6 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 5 Dec 2019 11:16:59 -0600 Subject: [PATCH 42/45] Change disabled div-buttons to true buttons --- ui/app/templates/jobs/index.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 87ab89579252..ab743c4d978d 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -18,7 +18,7 @@ {{#if (can "run job")}} {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} {{else}} -
Run Job
+ {{/if}} {{/if}} @@ -55,7 +55,7 @@ {{#if (can "run job")}} {{#link-to "jobs.run" data-test-run-job class="button is-primary"}}Run Job{{/link-to}} {{else}} -
Run Job
+ {{/if}} {{/if}} From 9057d26f93498d443994287169d880c20551a23f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 5 Dec 2019 12:59:23 -0600 Subject: [PATCH 43/45] Replace error-handling with simpler implementation --- ui/app/services/token.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/app/services/token.js b/ui/app/services/token.js index f77ff4193380..88bfcd38fdee 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -42,13 +42,11 @@ export default Service.extend({ if (this.selfToken) { return yield this.selfToken.get('policies'); } else { - return yield this.store - .findRecord('policy', 'anonymous') - .then(policy => [policy]) - .catch(() => []); + let policy = yield this.store.findRecord('policy', 'anonymous'); + return [policy]; } } catch (e) { - return null; + return []; } }), From 27cedda5ef9cf5663e34f9fa1d49faf5f9bb89c1 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Thu, 12 Dec 2019 09:16:48 -0600 Subject: [PATCH 44/45] Remove resolver override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This must have seemed/been necessary at some point but doesn’t break anything when removed! --- ui/tests/helpers/resolver.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/tests/helpers/resolver.js b/ui/tests/helpers/resolver.js index bd9319849b96..319b45fc1c2a 100644 --- a/ui/tests/helpers/resolver.js +++ b/ui/tests/helpers/resolver.js @@ -8,6 +8,4 @@ resolver.namespace = { podModulePrefix: config.podModulePrefix, }; -resolver.pluralizedTypes.ability = 'abilities'; - export default resolver; From ad82387c1ab96efc6a01b592fe9a7d5623e31c8a Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 20 Jan 2020 12:36:14 -0600 Subject: [PATCH 45/45] Convert test request-filtering to avoid Emberisms --- ui/tests/acceptance/allocation-detail-test.js | 2 +- ui/tests/acceptance/client-detail-test.js | 2 +- ui/tests/acceptance/job-allocations-test.js | 2 +- ui/tests/acceptance/job-definition-test.js | 2 +- ui/tests/acceptance/job-deployments-test.js | 2 +- ui/tests/acceptance/job-detail-test.js | 2 +- ui/tests/acceptance/job-evaluations-test.js | 2 +- ui/tests/acceptance/job-versions-test.js | 2 +- ui/tests/acceptance/task-detail-test.js | 2 +- ui/tests/acceptance/task-group-detail-test.js | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index 055256c7a50d..e0f700e2c542 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -220,7 +220,7 @@ module('Acceptance | allocation detail', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index 7b7770eb1a8a..733a1f6ec181 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -324,7 +324,7 @@ module('Acceptance | client detail', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/node/not-a-real-node', 'A request to the nonexistent node is made' diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index ce91ce32247b..011cbf8b8751 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -106,7 +106,7 @@ module('Acceptance | job allocations', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/job-definition-test.js b/ui/tests/acceptance/job-definition-test.js index 3bb3b99dcfa9..973fb5a5f751 100644 --- a/ui/tests/acceptance/job-definition-test.js +++ b/ui/tests/acceptance/job-definition-test.js @@ -80,7 +80,7 @@ module('Acceptance | job definition', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/job-deployments-test.js b/ui/tests/acceptance/job-deployments-test.js index d8c29f960203..e683c1c2c765 100644 --- a/ui/tests/acceptance/job-deployments-test.js +++ b/ui/tests/acceptance/job-deployments-test.js @@ -221,7 +221,7 @@ module('Acceptance | job deployments', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/job-detail-test.js b/ui/tests/acceptance/job-detail-test.js index 29a1a06a81c7..68e820f32c9f 100644 --- a/ui/tests/acceptance/job-detail-test.js +++ b/ui/tests/acceptance/job-detail-test.js @@ -48,7 +48,7 @@ moduleForJob( assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/job-evaluations-test.js b/ui/tests/acceptance/job-evaluations-test.js index 994416a9e8e5..1bc3a67e670d 100644 --- a/ui/tests/acceptance/job-evaluations-test.js +++ b/ui/tests/acceptance/job-evaluations-test.js @@ -55,7 +55,7 @@ module('Acceptance | job evaluations', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/job-versions-test.js b/ui/tests/acceptance/job-versions-test.js index e3fbd597c9be..7990437be450 100644 --- a/ui/tests/acceptance/job-versions-test.js +++ b/ui/tests/acceptance/job-versions-test.js @@ -41,7 +41,7 @@ module('Acceptance | job versions', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index fa0bd3d04bb4..febfcc317f73 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -146,7 +146,7 @@ module('Acceptance | task detail', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index 3f8649d23aa8..44512450655a 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -226,7 +226,7 @@ module('Acceptance | task group detail', function(hooks) { assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('policy')) + .filter(request => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made'