From 51149cdae26b1c27a7c773152f80cbfc2792b035 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Wed, 22 Sep 2021 18:32:51 +0100 Subject: [PATCH] ui: Remove legacy ACLs (#11096) --- ui/packages/consul-ui/app/adapters/acl.js | 74 --- .../app/components/consul/acl/list/index.hbs | 130 ------ .../consul/acl/notifications/index.hbs | 31 -- .../consul/acl/search-bar/index.hbs | 127 ------ .../app/controllers/dc/acls/create.js | 2 - .../consul-ui/app/controllers/dc/acls/edit.js | 30 -- .../consul-ui/app/filter/predicates/acl.js | 6 - ui/packages/consul-ui/app/forms/acl.js | 6 - .../consul-ui/app/mixins/acl/with-actions.js | 33 -- ui/packages/consul-ui/app/models/acl.js | 19 - ui/packages/consul-ui/app/router.js | 428 +++++++++--------- ui/packages/consul-ui/app/routes/dc/acls.js | 3 - .../consul-ui/app/routes/dc/acls/create.js | 39 -- .../consul-ui/app/routes/dc/acls/edit.js | 28 -- .../consul-ui/app/routes/dc/acls/index.js | 45 -- ui/packages/consul-ui/app/routing/route.js | 16 + ui/packages/consul-ui/app/serializers/acl.js | 14 - ui/packages/consul-ui/app/services/filter.js | 2 - ui/packages/consul-ui/app/services/form.js | 2 - .../consul-ui/app/services/repository/acl.js | 17 - .../app/services/repository/auth-method.js | 8 - .../app/services/repository/binding-rule.js | 8 - .../app/services/repository/policy.js | 8 - .../consul-ui/app/services/repository/role.js | 8 - .../app/services/repository/token.js | 19 +- ui/packages/consul-ui/app/services/sort.js | 2 - .../consul-ui/app/sort/comparators/acl.js | 3 - .../consul-ui/app/templates/dc/acls/-form.hbs | 51 --- .../consul-ui/app/templates/dc/acls/edit.hbs | 53 --- .../consul-ui/app/templates/dc/acls/index.hbs | 98 +--- .../consul-ui/app/utils/acls-status.js | 64 --- .../utils/http/acl/is-valid-server-error.js | 12 - ui/packages/consul-ui/app/validations/acl.js | 5 - ui/packages/consul-ui/ember-cli-build.js | 3 + .../lib/startup/templates/body.html.js | 20 + ui/packages/consul-ui/package.json | 1 + ui/packages/consul-ui/server/index.js | 2 +- .../tests/integration/adapters/acl-test.js | 106 ----- .../consul-ui/tests/unit/adapters/acl-test.js | 12 - .../unit/controllers/dc/acls/create-test.js | 12 - .../unit/controllers/dc/acls/edit-test.js | 12 - .../unit/mixins/acl/with-actions-test.js | 75 --- .../consul-ui/tests/unit/models/acl-test.js | 14 - .../tests/unit/routes/dc/acls-test.js | 11 - .../tests/unit/routes/dc/acls/create-test.js | 11 - .../tests/unit/routes/dc/acls/edit-test.js | 11 - .../tests/unit/routes/dc/acls/index-test.js | 11 - .../tests/unit/search/predicates/acl-test.js | 43 -- .../tests/unit/serializers/acl-test.js | 24 - .../unit/services/repository/acl-test.js | 12 - .../tests/unit/utils/acls-status-test.js | 89 ---- .../http/acl/is-valid-server-error-test.js | 49 -- ui/packages/consul-ui/vendor/acls/routes.js | 15 + ui/yarn.lock | 5 + 54 files changed, 282 insertions(+), 1647 deletions(-) delete mode 100644 ui/packages/consul-ui/app/adapters/acl.js delete mode 100644 ui/packages/consul-ui/app/components/consul/acl/list/index.hbs delete mode 100644 ui/packages/consul-ui/app/components/consul/acl/notifications/index.hbs delete mode 100644 ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs delete mode 100644 ui/packages/consul-ui/app/controllers/dc/acls/create.js delete mode 100644 ui/packages/consul-ui/app/controllers/dc/acls/edit.js delete mode 100644 ui/packages/consul-ui/app/filter/predicates/acl.js delete mode 100644 ui/packages/consul-ui/app/forms/acl.js delete mode 100644 ui/packages/consul-ui/app/mixins/acl/with-actions.js delete mode 100644 ui/packages/consul-ui/app/models/acl.js delete mode 100644 ui/packages/consul-ui/app/routes/dc/acls.js delete mode 100644 ui/packages/consul-ui/app/routes/dc/acls/create.js delete mode 100644 ui/packages/consul-ui/app/routes/dc/acls/edit.js delete mode 100644 ui/packages/consul-ui/app/routes/dc/acls/index.js delete mode 100644 ui/packages/consul-ui/app/serializers/acl.js delete mode 100644 ui/packages/consul-ui/app/services/repository/acl.js delete mode 100644 ui/packages/consul-ui/app/sort/comparators/acl.js delete mode 100644 ui/packages/consul-ui/app/templates/dc/acls/-form.hbs delete mode 100644 ui/packages/consul-ui/app/templates/dc/acls/edit.hbs delete mode 100644 ui/packages/consul-ui/app/utils/acls-status.js delete mode 100644 ui/packages/consul-ui/app/utils/http/acl/is-valid-server-error.js delete mode 100644 ui/packages/consul-ui/app/validations/acl.js delete mode 100644 ui/packages/consul-ui/tests/integration/adapters/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/adapters/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/controllers/dc/acls/create-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/controllers/dc/acls/edit-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/mixins/acl/with-actions-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/models/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/routes/dc/acls-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/routes/dc/acls/create-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/routes/dc/acls/edit-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/routes/dc/acls/index-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/serializers/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/services/repository/acl-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/utils/acls-status-test.js delete mode 100644 ui/packages/consul-ui/tests/unit/utils/http/acl/is-valid-server-error-test.js create mode 100644 ui/packages/consul-ui/vendor/acls/routes.js diff --git a/ui/packages/consul-ui/app/adapters/acl.js b/ui/packages/consul-ui/app/adapters/acl.js deleted file mode 100644 index c0b2dea80..000000000 --- a/ui/packages/consul-ui/app/adapters/acl.js +++ /dev/null @@ -1,74 +0,0 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; -import { SLUG_KEY } from 'consul-ui/models/acl'; -import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; - -// The old ACL system doesn't support the `ns=` query param -// TODO: Update to use this.formatDatacenter() -export default class AclAdapter extends Adapter { - requestForQuery(request, { dc, index }) { - // https://www.consul.io/api/acl.html#list-acls - return request` - GET /v1/acl/list?${{ dc }} - - ${{ index }} - `; - } - - requestForQueryRecord(request, { dc, index, id }) { - if (typeof id === 'undefined') { - throw new Error('You must specify an id'); - } - // https://www.consul.io/api/acl.html#read-acl-token - return request` - GET /v1/acl/info/${id}?${{ dc }} - - ${{ index }} - `; - } - - requestForCreateRecord(request, serialized, data) { - // https://www.consul.io/api/acl.html#create-acl-token - return request` - PUT /v1/acl/create?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - - ${serialized} - `; - } - - requestForUpdateRecord(request, serialized, data) { - // the id is in the data, don't add it into the URL - // https://www.consul.io/api/acl.html#update-acl-token - return request` - PUT /v1/acl/update?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - - ${serialized} - `; - } - - requestForDeleteRecord(request, serialized, data) { - // https://www.consul.io/api/acl.html#delete-acl-token - return request` - PUT /v1/acl/destroy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - `; - } - - requestForCloneRecord(request, serialized, data) { - // https://www.consul.io/api/acl.html#clone-acl-token - return request` - PUT /v1/acl/clone/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - `; - } - - clone(store, type, id, snapshot) { - return this.rpc( - function(adapter, request, serialized, unserialized) { - return adapter.requestForCloneRecord(request, serialized, unserialized); - }, - function(serializer, respond, serialized, unserialized) { - return serializer.respondForCreateRecord(respond, serialized, unserialized); - }, - snapshot, - type.modelName - ); - } -} diff --git a/ui/packages/consul-ui/app/components/consul/acl/list/index.hbs b/ui/packages/consul-ui/app/components/consul/acl/list/index.hbs deleted file mode 100644 index 763dd16f7..000000000 --- a/ui/packages/consul-ui/app/components/consul/acl/list/index.hbs +++ /dev/null @@ -1,130 +0,0 @@ -
- - - Name - Type - - - - {{item.Name}} - - - {{#if (eq item.Type 'management')}} - {{item.Type}} - {{else}} - {{item.Type}} - {{/if}} - - - - - - More - - -
  • - -{{#if (can "write acl" item=item)}} - Edit -{{else}} - View -{{/if}} - -
  • - {{#if (eq item.ID token.SecretID) }} -
  • - -
    -
    -
    -
    - Confirm logout -
    -

    - Are you sure you want to stop using this ACL token? This will log you out. -

    -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    -
  • - {{else}} -
  • - -
    -
    -
    -
    - Confirm use -
    -

    - Are you sure you want to use this ACL token? -

    -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    -
  • - {{/if}} -{{#if (can "duplicate acl" item=item)}} -
  • - -
  • -{{/if}} -{{#if (can "delete acl" item=item)}} -
  • - -
    -
    -
    -
    - Confirm Delete -
    -

    - Are you sure you want to delete this token? -

    -
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    -
  • - {{/if}} -
    -
    -
    -
    -
    diff --git a/ui/packages/consul-ui/app/components/consul/acl/notifications/index.hbs b/ui/packages/consul-ui/app/components/consul/acl/notifications/index.hbs deleted file mode 100644 index a82fd4ba6..000000000 --- a/ui/packages/consul-ui/app/components/consul/acl/notifications/index.hbs +++ /dev/null @@ -1,31 +0,0 @@ -{{#if (eq @type 'create')}} - {{#if (eq @status 'success') }} - Your ACL token has been added. - {{else}} - There was an error adding your ACL token. - {{/if}} -{{else if (eq @type 'update') }} - {{#if (eq @status 'success') }} - Your ACL token has been saved. - {{else}} - There was an error saving your ACL token. - {{/if}} -{{ else if (eq @type 'delete')}} - {{#if (eq @status 'success') }} - Your ACL token was deleted. - {{else}} - There was an error deleting your ACL token. - {{/if}} -{{ else if (eq @type 'use')}} - {{#if (eq @status 'success') }} - Now using new ACL token. - {{else}} - There was an error using that ACL token. - {{/if}} -{{ else if (eq @type 'clone')}} - {{#if (eq @status 'success') }} - Your ACL token was cloned. - {{else}} - There was an error cloning your ACL token. - {{/if}} -{{/if}} diff --git a/ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs b/ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs deleted file mode 100644 index 978dc749b..000000000 --- a/ui/packages/consul-ui/app/components/consul/acl/search-bar/index.hbs +++ /dev/null @@ -1,127 +0,0 @@ - - <:status as |search|> - -{{#let - - (t (concat "components.consul.acl.search-bar." search.status.key) - default=(array - (concat "common.search." search.status.key) - (concat "common.consul." search.status.key) - ) - ) - - (t (concat "components.consul.acl.search-bar." search.status.value) - default=(array - (concat "common.search." search.status.value) - (concat "common.consul." search.status.value) - (concat "common.brand." search.status.value) - ) - ) - -as |key value|}} - -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -{{/let}} - - - <:search as |search|> - -{{#if @filter.searchproperty}} - - - - {{t "common.search.searchproperty"}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - {{#each @filter.searchproperty.default as |prop|}} - - {{/each}} - {{/let}} - - - {{/if}} - - - <:filter as |search|> - - - - {{t "components.consul.acl.search-bar.kind.name"}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - {{#each (array "management" "client") as |state|}} - - {{/each}} - {{/let}} - - - - <:sort as |search|> - - - - {{#let (from-entries (array - (array "Name:asc" (t "common.sort.alpha.asc")) - (array "Name:desc" (t "common.sort.alpha.desc")) - )) - as |selectable| - }} - {{get selectable @sort.value}} - {{/let}} - - - - {{#let components.Optgroup components.Option as |Optgroup Option|}} - - - - - {{/let}} - - - -
    diff --git a/ui/packages/consul-ui/app/controllers/dc/acls/create.js b/ui/packages/consul-ui/app/controllers/dc/acls/create.js deleted file mode 100644 index f4c701cbb..000000000 --- a/ui/packages/consul-ui/app/controllers/dc/acls/create.js +++ /dev/null @@ -1,2 +0,0 @@ -import Controller from './edit'; -export default class CreateController extends Controller {} diff --git a/ui/packages/consul-ui/app/controllers/dc/acls/edit.js b/ui/packages/consul-ui/app/controllers/dc/acls/edit.js deleted file mode 100644 index 7a639982d..000000000 --- a/ui/packages/consul-ui/app/controllers/dc/acls/edit.js +++ /dev/null @@ -1,30 +0,0 @@ -import Controller from '@ember/controller'; -import { inject as service } from '@ember/service'; - -export default Controller.extend({ - builder: service('form'), - dom: service('dom'), - init: function() { - this._super(...arguments); - this.form = this.builder.form('acl'); - }, - setProperties: function(model) { - // essentially this replaces the data with changesets - this._super( - Object.keys(model).reduce((prev, key, i) => { - switch (key) { - case 'item': - prev[key] = this.form.setData(prev[key]).getData(); - break; - } - return prev; - }, model) - ); - }, - actions: { - change: function(e, value, item) { - const event = this.dom.normalizeEvent(e, value); - this.form.handleEvent(event); - }, - }, -}); diff --git a/ui/packages/consul-ui/app/filter/predicates/acl.js b/ui/packages/consul-ui/app/filter/predicates/acl.js deleted file mode 100644 index dfc15b58c..000000000 --- a/ui/packages/consul-ui/app/filter/predicates/acl.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - kind: { - management: (item, value) => item.Type === value, - client: (item, value) => item.Type === value, - }, -}; diff --git a/ui/packages/consul-ui/app/forms/acl.js b/ui/packages/consul-ui/app/forms/acl.js deleted file mode 100644 index a6c250304..000000000 --- a/ui/packages/consul-ui/app/forms/acl.js +++ /dev/null @@ -1,6 +0,0 @@ -import validations from 'consul-ui/validations/acl'; -import builderFactory from 'consul-ui/utils/form/builder'; -const builder = builderFactory(); -export default function(container, name = '', v = validations, form = builder) { - return form(name, {}).setValidators(v); -} diff --git a/ui/packages/consul-ui/app/mixins/acl/with-actions.js b/ui/packages/consul-ui/app/mixins/acl/with-actions.js deleted file mode 100644 index 5b969af3b..000000000 --- a/ui/packages/consul-ui/app/mixins/acl/with-actions.js +++ /dev/null @@ -1,33 +0,0 @@ -import Mixin from '@ember/object/mixin'; -import { get } from '@ember/object'; -import { inject as service } from '@ember/service'; -import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions'; - -export default Mixin.create(WithBlockingActions, { - settings: service('settings'), - actions: { - use: function(item) { - return this.settings.persist({ - token: { - Namespace: 'default', - AccessorID: null, - SecretID: get(item, 'ID'), - }, - }); - }, - logout: function(item) { - return this.settings.delete('token'); - }, - clone: function(item) { - return this.feedback.execute(() => { - return this.repo.clone(item).then(item => { - // cloning is similar to delete in that - // if you clone from the listing page, stay on the listing page - // whereas if you clone form another token, take me back to the listing page - // so I can see it - return this.afterDelete(...arguments); - }); - }, 'clone'); - }, - }, -}); diff --git a/ui/packages/consul-ui/app/models/acl.js b/ui/packages/consul-ui/app/models/acl.js deleted file mode 100644 index d442ae3c7..000000000 --- a/ui/packages/consul-ui/app/models/acl.js +++ /dev/null @@ -1,19 +0,0 @@ -import Model, { attr } from '@ember-data/model'; - -export const PRIMARY_KEY = 'uid'; -export const SLUG_KEY = 'ID'; - -export default class Acl extends Model { - @attr('string') uid; - @attr('string') ID; - - @attr('string') Datacenter; - // TODO: Why didn't I have to do this for KV's? This is to ensure that Name - // is '' and not null when creating maybe its due to the fact that `Key` is - // the primaryKey in Kv's - @attr('string', { defaultValue: () => '' }) Name; - @attr('string') Type; - @attr('string') Rules; - @attr('number') CreateIndex; - @attr('number') ModifyIndex; -} diff --git a/ui/packages/consul-ui/app/router.js b/ui/packages/consul-ui/app/router.js index d22192b76..540ae7093 100644 --- a/ui/packages/consul-ui/app/router.js +++ b/ui/packages/consul-ui/app/router.js @@ -1,227 +1,236 @@ /* globals requirejs */ import EmberRouter from '@ember/routing/router'; +import config from './config/environment'; import { runInDebug } from '@ember/debug'; +import merge from 'deepmerge'; import { env } from 'consul-ui/env'; import walk, { dump } from 'consul-ui/utils/routing/walk'; -export const routes = { - // Our parent datacenter resource sets the namespace - // for the entire application - dc: { - _options: { path: '/:dc' }, - // Services represent a consul service - services: { - _options: { path: '/services' }, - // Show an individual service - show: { - _options: { path: '/:name' }, - instances: { - _options: { path: '/instances' }, +const doc = document; +const appName = config.modulePrefix; +const appNameJS = appName + .split('-') + .map((item, i) => (i ? `${item.substr(0, 1).toUpperCase()}${item.substr(1)}` : item)) + .join(''); + +export const routes = merge.all( + [ + { + // Our parent datacenter resource sets the namespace + // for the entire application + dc: { + _options: { path: '/:dc' }, + // Services represent a consul service + services: { + _options: { path: '/services' }, + // Show an individual service + show: { + _options: { path: '/:name' }, + instances: { + _options: { path: '/instances' }, + }, + intentions: { + _options: { path: '/intentions' }, + edit: { + _options: { path: '/:intention_id' }, + }, + create: { + _options: { path: '/create' }, + }, + }, + topology: { + _options: { path: '/topology' }, + }, + services: { + _options: { path: '/services' }, + }, + upstreams: { + _options: { path: '/upstreams' }, + }, + routing: { + _options: { path: '/routing' }, + }, + tags: { + _options: { path: '/tags' }, + }, + }, + instance: { + _options: { path: '/:name/instances/:node/:id' }, + healthchecks: { + _options: { path: '/health-checks' }, + }, + upstreams: { + _options: { path: '/upstreams' }, + }, + exposedpaths: { + _options: { path: '/exposed-paths' }, + }, + addresses: { + _options: { path: '/addresses' }, + }, + metadata: { + _options: { path: '/metadata' }, + }, + }, + notfound: { + _options: { path: '/:name/:node/:id' }, + }, }, + // Nodes represent a consul node + nodes: { + _options: { path: '/nodes' }, + // Show an individual node + show: { + _options: { path: '/:name' }, + healthchecks: { + _options: { path: '/health-checks' }, + }, + services: { + _options: { path: '/service-instances' }, + }, + rtt: { + _options: { path: '/round-trip-time' }, + }, + sessions: { + _options: { path: '/lock-sessions' }, + }, + metadata: { + _options: { path: '/metadata' }, + }, + }, + }, + // Intentions represent a consul intention intentions: { _options: { path: '/intentions' }, edit: { - _options: { path: '/:intention_id' }, + _options: { + path: '/:intention_id', + abilities: ['read intentions'], + }, }, create: { - _options: { path: '/create' }, + _options: { + path: '/create', + abilities: ['create intentions'], + }, }, }, - topology: { - _options: { path: '/topology' }, + // Key/Value + kv: { + _options: { path: '/kv' }, + folder: { + _options: { path: '/*key' }, + }, + edit: { + _options: { path: '/*key/edit' }, + }, + create: { + _options: { + path: '/*key/create', + abilities: ['create kvs'], + }, + }, + 'root-create': { + _options: { + path: '/create', + abilities: ['create kvs'], + }, + }, }, - services: { - _options: { path: '/services' }, + // ACLs + acls: { + _options: { + path: '/acls', + abilities: ['access acls'], + }, + policies: { + _options: { + path: '/policies', + abilities: ['read policies'], + }, + edit: { + _options: { path: '/:id' }, + }, + create: { + _options: { + path: '/create', + abilities: ['create policies'], + }, + }, + }, + roles: { + _options: { + path: '/roles', + abilities: ['read roles'], + }, + edit: { + _options: { path: '/:id' }, + }, + create: { + _options: { + path: '/create', + abilities: ['create roles'], + }, + }, + }, + tokens: { + _options: { + path: '/tokens', + abilities: ['access acls'], + }, + edit: { + _options: { path: '/:id' }, + }, + create: { + _options: { + path: '/create', + abilities: ['create tokens'], + }, + }, + }, + 'auth-methods': { + _options: { + path: '/auth-methods', + abilities: ['read auth-methods'], + }, + show: { + _options: { path: '/:id' }, + 'auth-method': { + _options: { path: '/auth-method' }, + }, + 'binding-rules': { + _options: { path: '/binding-rules' }, + }, + 'nspace-rules': { + _options: { path: '/nspace-rules' }, + }, + }, + }, }, - upstreams: { - _options: { path: '/upstreams' }, - }, - routing: { - _options: { path: '/routing' }, - }, - tags: { - _options: { path: '/tags' }, + 'routing-config': { + _options: { path: '/routing-config/:name' }, }, }, - instance: { - _options: { path: '/:name/instances/:node/:id' }, - healthchecks: { - _options: { path: '/health-checks' }, - }, - upstreams: { - _options: { path: '/upstreams' }, - }, - exposedpaths: { - _options: { path: '/exposed-paths' }, - }, - addresses: { - _options: { path: '/addresses' }, - }, - metadata: { - _options: { path: '/metadata' }, - }, + // Shows a datacenter picker. If you only have one + // it just redirects you through. + index: { + _options: { path: '/' }, + }, + // The settings page is global. + settings: { + _options: { path: '/setting' }, }, notfound: { - _options: { path: '/:name/:node/:id' }, + _options: { path: '/*notfound' }, }, }, - // Nodes represent a consul node - nodes: { - _options: { path: '/nodes' }, - // Show an individual node - show: { - _options: { path: '/:name' }, - healthchecks: { - _options: { path: '/health-checks' }, - }, - services: { - _options: { path: '/service-instances' }, - }, - rtt: { - _options: { path: '/round-trip-time' }, - }, - sessions: { - _options: { path: '/lock-sessions' }, - }, - metadata: { - _options: { path: '/metadata' }, - }, - }, - }, - // Intentions represent a consul intention - intentions: { - _options: { path: '/intentions' }, - edit: { - _options: { - path: '/:intention_id', - abilities: ['read intentions'], - }, - }, - create: { - _options: { - path: '/create', - abilities: ['create intentions'], - }, - }, - }, - // Key/Value - kv: { - _options: { path: '/kv' }, - folder: { - _options: { path: '/*key' }, - }, - edit: { - _options: { path: '/*key/edit' }, - }, - create: { - _options: { - path: '/*key/create', - abilities: ['create kvs'], - }, - }, - 'root-create': { - _options: { - path: '/create', - abilities: ['create kvs'], - }, - }, - }, - // ACLs - acls: { - _options: { - path: '/acls', - abilities: ['access acls'], - }, - edit: { - _options: { path: '/:acl' }, - }, - create: { - _options: { - path: '/create', - abilities: ['create acls'], - }, - }, - policies: { - _options: { - path: '/policies', - abilities: ['read policies'], - }, - edit: { - _options: { path: '/:id' }, - }, - create: { - _options: { - path: '/create', - abilities: ['create policies'], - }, - }, - }, - roles: { - _options: { - path: '/roles', - abilities: ['read roles'], - }, - edit: { - _options: { path: '/:id' }, - }, - create: { - _options: { - path: '/create', - abilities: ['create roles'], - }, - }, - }, - tokens: { - _options: { - path: '/tokens', - abilities: env('CONSUL_ACLS_ENABLED') ? ['read tokens'] : ['access acls'], - }, - edit: { - _options: { path: '/:id' }, - }, - create: { - _options: { - path: '/create', - abilities: ['create tokens'], - }, - }, - }, - 'auth-methods': { - _options: { - path: '/auth-methods', - abilities: ['read auth-methods'], - }, - show: { - _options: { path: '/:id' }, - 'auth-method': { - _options: { path: '/auth-method' }, - }, - 'binding-rules': { - _options: { path: '/binding-rules' }, - }, - 'nspace-rules': { - _options: { path: '/nspace-rules' }, - }, - }, - }, - }, - 'routing-config': { - _options: { path: '/routing-config/:name' }, - }, - }, - // Shows a datacenter picker. If you only have one - // it just redirects you through. - index: { - _options: { path: '/' }, - }, - // The settings page is global. - settings: { - _options: { path: '/setting' }, - }, - notfound: { - _options: { path: '/*notfound' }, - }, -}; + ].concat( + ...[...doc.querySelectorAll(`script[data-${appName}-routes]`)].map($item => + JSON.parse($item.dataset[`${appNameJS}Routes`]) + ) + ) +); + if (env('CONSUL_NSPACES_ENABLED')) { routes.dc.nspaces = { _options: { @@ -242,7 +251,7 @@ if (env('CONSUL_NSPACES_ENABLED')) { runInDebug(() => { // check to see if we are running docfy and if so add its routes to our // route config - const docfyOutput = requirejs.entries['consul-ui/docfy-output']; + const docfyOutput = requirejs.entries[`${appName}/docfy-output`]; if (typeof docfyOutput !== 'undefined') { const output = {}; docfyOutput.callback(output); @@ -269,13 +278,6 @@ runInDebug(() => { })(routes, output.default.nested); } }); -export default class Router extends EmberRouter { - location = env('locationType'); - rootURL = env('rootURL'); -} - -Router.map(walk(routes)); - // To print the Ember route DSL use `Routes()` in Web Inspectors console // or `javascript:Routes()` in the location bar of your browser runInDebug(() => { @@ -295,3 +297,9 @@ runInDebug(() => { return; }; }); + +export default class Router extends EmberRouter { + location = env('locationType'); + rootURL = env('rootURL'); +} +Router.map(walk(routes)); diff --git a/ui/packages/consul-ui/app/routes/dc/acls.js b/ui/packages/consul-ui/app/routes/dc/acls.js deleted file mode 100644 index 33a8e84e7..000000000 --- a/ui/packages/consul-ui/app/routes/dc/acls.js +++ /dev/null @@ -1,3 +0,0 @@ -import Route from 'consul-ui/routing/route'; -import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions'; -export default class AclsRoute extends Route.extend(WithBlockingActions) {} diff --git a/ui/packages/consul-ui/app/routes/dc/acls/create.js b/ui/packages/consul-ui/app/routes/dc/acls/create.js deleted file mode 100644 index f35340681..000000000 --- a/ui/packages/consul-ui/app/routes/dc/acls/create.js +++ /dev/null @@ -1,39 +0,0 @@ -import { inject as service } from '@ember/service'; -import Route from 'consul-ui/routing/route'; -import { hash } from 'rsvp'; -import { get } from '@ember/object'; - -import WithAclActions from 'consul-ui/mixins/acl/with-actions'; - -export default class CreateRoute extends Route.extend(WithAclActions) { - templateName = 'dc/acls/edit'; - - @service('repository/acl') - repo; - - beforeModel() { - this.repo.invalidate(); - } - - model(params) { - this.item = this.repo.create({ - Datacenter: this.modelFor('dc').dc.Name, - }); - return hash({ - create: true, - item: this.item, - types: ['management', 'client'], - }); - } - - setupController(controller, model) { - super.setupController(...arguments); - controller.setProperties(model); - } - - deactivate() { - if (get(this.item, 'isNew')) { - this.item.destroyRecord(); - } - } -} diff --git a/ui/packages/consul-ui/app/routes/dc/acls/edit.js b/ui/packages/consul-ui/app/routes/dc/acls/edit.js deleted file mode 100644 index 0835c4e9e..000000000 --- a/ui/packages/consul-ui/app/routes/dc/acls/edit.js +++ /dev/null @@ -1,28 +0,0 @@ -import { inject as service } from '@ember/service'; -import Route from 'consul-ui/routing/route'; -import { hash } from 'rsvp'; - -import WithAclActions from 'consul-ui/mixins/acl/with-actions'; - -export default class EditRoute extends Route.extend(WithAclActions) { - @service('repository/acl') - repo; - - @service('settings') - settings; - - model(params) { - return hash({ - item: this.repo.findBySlug({ - dc: this.modelFor('dc').dc.Name, - id: params.id, - }), - types: ['management', 'client'], - }); - } - - setupController(controller, model) { - super.setupController(...arguments); - controller.setProperties(model); - } -} diff --git a/ui/packages/consul-ui/app/routes/dc/acls/index.js b/ui/packages/consul-ui/app/routes/dc/acls/index.js deleted file mode 100644 index 2e896fc29..000000000 --- a/ui/packages/consul-ui/app/routes/dc/acls/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import { inject as service } from '@ember/service'; -import Route from 'consul-ui/routing/route'; -import { get } from '@ember/object'; - -import WithAclActions from 'consul-ui/mixins/acl/with-actions'; - -export default class IndexRoute extends Route.extend(WithAclActions) { - @service('repository/acl') repo; - @service('settings') settings; - - queryParams = { - sortBy: 'sort', - kind: 'kind', - search: { - as: 'filter', - replace: true, - }, - }; - - async beforeModel(transition) { - const token = await this.settings.findBySlug('token'); - // If you don't have a token set or you have a - // token set with AccessorID set to not null (new ACL mode) - // then rewrite to the new acls - if (!token || get(token, 'AccessorID') !== null) { - // If you return here, you get a TransitionAborted error in the tests only - // everything works fine either way checking things manually - this.replaceWith('dc.acls.tokens'); - } - } - - async model(params) { - const _items = this.repo.findAllByDatacenter({ dc: this.modelFor('dc').dc.Name }); - const _token = this.settings.findBySlug('token'); - return { - items: await _items, - token: await _token, - }; - } - - setupController(controller, model) { - super.setupController(...arguments); - controller.setProperties(model); - } -} diff --git a/ui/packages/consul-ui/app/routing/route.js b/ui/packages/consul-ui/app/routing/route.js index 5117763af..9ec09a47b 100644 --- a/ui/packages/consul-ui/app/routing/route.js +++ b/ui/packages/consul-ui/app/routing/route.js @@ -15,6 +15,22 @@ export default class BaseRoute extends Route { @service('repository/permission') permissions; @service('router') router; + redirect(model, transition) { + // remove any references to index as it is the same as the root routeName + const routeName = this.routeName + .split('.') + .filter(item => item !== 'index') + .join('.'); + const to = get(routes, `${routeName}._options.redirect`); + if (typeof to !== 'undefined') { + // TODO: Does this need to return? + // Almost remember things getting strange if you returned from here + // which is why I didn't do it originally so be sure to look properly if + // you feel like adding a return + this.replaceWith(`${routeName}${to}`, model); + } + } + /** * Inspects a custom `abilities` array on the router for this route. Every * abililty needs to 'pass' for the route not to throw a 403 error. Anything diff --git a/ui/packages/consul-ui/app/serializers/acl.js b/ui/packages/consul-ui/app/serializers/acl.js deleted file mode 100644 index f363532b0..000000000 --- a/ui/packages/consul-ui/app/serializers/acl.js +++ /dev/null @@ -1,14 +0,0 @@ -import Serializer from './application'; -import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/acl'; - -export default class AclSerializer extends Serializer { - primaryKey = PRIMARY_KEY; - slugKey = SLUG_KEY; - - respondForQueryRecord(respond, query) { - return super.respondForQueryRecord( - cb => respond((headers, body) => cb(headers, body[0])), - query - ); - } -} diff --git a/ui/packages/consul-ui/app/services/filter.js b/ui/packages/consul-ui/app/services/filter.js index 7a521f9e1..1ba36ec48 100644 --- a/ui/packages/consul-ui/app/services/filter.js +++ b/ui/packages/consul-ui/app/services/filter.js @@ -1,7 +1,6 @@ import Service from '@ember/service'; import { andOr } from 'consul-ui/utils/filter'; -import acl from 'consul-ui/filter/predicates/acl'; import service from 'consul-ui/filter/predicates/service'; import serviceInstance from 'consul-ui/filter/predicates/service-instance'; import healthCheck from 'consul-ui/filter/predicates/health-check'; @@ -13,7 +12,6 @@ import policy from 'consul-ui/filter/predicates/policy'; import authMethod from 'consul-ui/filter/predicates/auth-method'; const predicates = { - acl: andOr(acl), service: andOr(service), ['service-instance']: andOr(serviceInstance), ['health-check']: andOr(healthCheck), diff --git a/ui/packages/consul-ui/app/services/form.js b/ui/packages/consul-ui/app/services/form.js index 3c40b4c20..80cff14f3 100644 --- a/ui/packages/consul-ui/app/services/form.js +++ b/ui/packages/consul-ui/app/services/form.js @@ -2,7 +2,6 @@ import Service, { inject as service } from '@ember/service'; import builderFactory from 'consul-ui/utils/form/builder'; import kv from 'consul-ui/forms/kv'; -import acl from 'consul-ui/forms/acl'; import token from 'consul-ui/forms/token'; import policy from 'consul-ui/forms/policy'; import role from 'consul-ui/forms/role'; @@ -13,7 +12,6 @@ const builder = builderFactory(); const forms = { kv: kv, - acl: acl, token: token, policy: policy, role: role, diff --git a/ui/packages/consul-ui/app/services/repository/acl.js b/ui/packages/consul-ui/app/services/repository/acl.js deleted file mode 100644 index 2f17409d7..000000000 --- a/ui/packages/consul-ui/app/services/repository/acl.js +++ /dev/null @@ -1,17 +0,0 @@ -import RepositoryService from 'consul-ui/services/repository'; -import { get } from '@ember/object'; -import { PRIMARY_KEY } from 'consul-ui/models/acl'; -const modelName = 'acl'; -export default class AclService extends RepositoryService { - getModelName() { - return modelName; - } - - getPrimaryKey() { - return PRIMARY_KEY; - } - - clone(item) { - return this.store.clone(this.getModelName(), get(item, this.getPrimaryKey())); - } -} diff --git a/ui/packages/consul-ui/app/services/repository/auth-method.js b/ui/packages/consul-ui/app/services/repository/auth-method.js index b8fc1e80c..fb4875551 100644 --- a/ui/packages/consul-ui/app/services/repository/auth-method.js +++ b/ui/packages/consul-ui/app/services/repository/auth-method.js @@ -1,11 +1,7 @@ import RepositoryService from 'consul-ui/services/repository'; -import statusFactory from 'consul-ui/utils/acls-status'; -import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/auth-method'; import dataSource from 'consul-ui/decorators/data-source'; -const isValidServerError = isValidServerErrorFactory(); -const status = statusFactory(isValidServerError, Promise); const MODEL_NAME = 'auth-method'; export default class AuthMethodService extends RepositoryService { @@ -30,8 +26,4 @@ export default class AuthMethodService extends RepositoryService { async findBySlug() { return super.findBySlug(...arguments); } - - status(obj) { - return status(obj); - } } diff --git a/ui/packages/consul-ui/app/services/repository/binding-rule.js b/ui/packages/consul-ui/app/services/repository/binding-rule.js index ab03f1578..ba1305385 100644 --- a/ui/packages/consul-ui/app/services/repository/binding-rule.js +++ b/ui/packages/consul-ui/app/services/repository/binding-rule.js @@ -1,11 +1,7 @@ import RepositoryService from 'consul-ui/services/repository'; -import statusFactory from 'consul-ui/utils/acls-status'; -import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/binding-rule'; import dataSource from 'consul-ui/decorators/data-source'; -const isValidServerError = isValidServerErrorFactory(); -const status = statusFactory(isValidServerError, Promise); const MODEL_NAME = 'binding-rule'; export default class BindingRuleService extends RepositoryService { @@ -25,8 +21,4 @@ export default class BindingRuleService extends RepositoryService { async findAllByAuthMethod() { return super.findAll(...arguments); } - - status(obj) { - return status(obj); - } } diff --git a/ui/packages/consul-ui/app/services/repository/policy.js b/ui/packages/consul-ui/app/services/repository/policy.js index 29842ad33..4fdbfd811 100644 --- a/ui/packages/consul-ui/app/services/repository/policy.js +++ b/ui/packages/consul-ui/app/services/repository/policy.js @@ -1,13 +1,9 @@ import RepositoryService from 'consul-ui/services/repository'; import { get } from '@ember/object'; import { inject as service } from '@ember/service'; -import statusFactory from 'consul-ui/utils/acls-status'; -import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/policy'; import dataSource from 'consul-ui/decorators/data-source'; -const isValidServerError = isValidServerErrorFactory(); -const status = statusFactory(isValidServerError, Promise); const MODEL_NAME = 'policy'; export default class PolicyService extends RepositoryService { @@ -47,10 +43,6 @@ export default class PolicyService extends RepositoryService { .getData(); } - status(obj) { - return status(obj); - } - persist(item) { // only if a policy doesn't have a template, save it // right now only ServiceIdentities have templates and diff --git a/ui/packages/consul-ui/app/services/repository/role.js b/ui/packages/consul-ui/app/services/repository/role.js index 2971f65b2..9fe0f7ea3 100644 --- a/ui/packages/consul-ui/app/services/repository/role.js +++ b/ui/packages/consul-ui/app/services/repository/role.js @@ -1,12 +1,8 @@ import RepositoryService from 'consul-ui/services/repository'; import { inject as service } from '@ember/service'; -import statusFactory from 'consul-ui/utils/acls-status'; -import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role'; import dataSource from 'consul-ui/decorators/data-source'; -const isValidServerError = isValidServerErrorFactory(); -const status = statusFactory(isValidServerError, Promise); const MODEL_NAME = 'role'; export default class RoleService extends RepositoryService { @@ -45,8 +41,4 @@ export default class RoleService extends RepositoryService { .setData(item) .getData(); } - - status(obj) { - return status(obj); - } } diff --git a/ui/packages/consul-ui/app/services/repository/token.js b/ui/packages/consul-ui/app/services/repository/token.js index 65853d2d8..1497140e4 100644 --- a/ui/packages/consul-ui/app/services/repository/token.js +++ b/ui/packages/consul-ui/app/services/repository/token.js @@ -2,12 +2,8 @@ import RepositoryService from 'consul-ui/services/repository'; import { get } from '@ember/object'; import { inject as service } from '@ember/service'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/token'; -import statusFactory from 'consul-ui/utils/acls-status'; -import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error'; import dataSource from 'consul-ui/decorators/data-source'; -const isValidServerError = isValidServerErrorFactory(); -const status = statusFactory(isValidServerError, Promise); const MODEL_NAME = 'token'; export default class TokenService extends RepositoryService { @@ -24,10 +20,6 @@ export default class TokenService extends RepositoryService { return SLUG_KEY; } - status(obj) { - return status(obj); - } - @dataSource('/:partition/:ns/:dc/tokens') async findAll() { return super.findAll(...arguments); @@ -53,21 +45,14 @@ export default class TokenService extends RepositoryService { @dataSource('/:partition/:ns/:dc/token/self/:secret') self(params) { - // TODO: Does this need ns passing through? + // This request does not need ns or partition passing through as its + // inferred from the token itself. return this.store .self(this.getModelName(), { secret: params.secret, dc: params.dc, }) .catch(e => { - // If we get this 500 RPC error, it means we are a legacy ACL cluster - // set AccessorID to null - which for the frontend means legacy mode - if (isValidServerError(e)) { - return { - AccessorID: null, - SecretID: params.secret, - }; - } return Promise.reject(e); }); } diff --git a/ui/packages/consul-ui/app/services/sort.js b/ui/packages/consul-ui/app/services/sort.js index 44ff5aac7..07fa0b109 100644 --- a/ui/packages/consul-ui/app/services/sort.js +++ b/ui/packages/consul-ui/app/services/sort.js @@ -2,7 +2,6 @@ import Service from '@ember/service'; import service from 'consul-ui/sort/comparators/service'; import serviceInstance from 'consul-ui/sort/comparators/service-instance'; import upstreamInstance from 'consul-ui/sort/comparators/upstream-instance'; -import acl from 'consul-ui/sort/comparators/acl'; import kv from 'consul-ui/sort/comparators/kv'; import healthCheck from 'consul-ui/sort/comparators/health-check'; import intention from 'consul-ui/sort/comparators/intention'; @@ -34,7 +33,6 @@ const comparators = { ['upstream-instance']: upstreamInstance(options), ['health-check']: healthCheck(options), ['auth-method']: authMethod(options), - acl: acl(options), kv: kv(options), intention: intention(options), token: token(options), diff --git a/ui/packages/consul-ui/app/sort/comparators/acl.js b/ui/packages/consul-ui/app/sort/comparators/acl.js deleted file mode 100644 index 93a22f098..000000000 --- a/ui/packages/consul-ui/app/sort/comparators/acl.js +++ /dev/null @@ -1,3 +0,0 @@ -export default ({ properties }) => (key = 'Name:asc') => { - return properties(['Name'])(key); -}; diff --git a/ui/packages/consul-ui/app/templates/dc/acls/-form.hbs b/ui/packages/consul-ui/app/templates/dc/acls/-form.hbs deleted file mode 100644 index 1a2e01ca8..000000000 --- a/ui/packages/consul-ui/app/templates/dc/acls/-form.hbs +++ /dev/null @@ -1,51 +0,0 @@ -
    -
    - -
    - {{#each types as |type|}} - - {{/each}} -
    - -{{#if create }} - -{{/if}} -
    -
    -{{#if (and create (can "create acls")) }} - {{! we only need to check for an empty name here as ember munges autofocus, once we have autofocus back revisit this}} - -{{else}} - {{#if (can "write acl" item=item)}} - - {{/if}} -{{/if}} - -{{# if (and (not create) (can "delete acl" item=item) ) }} - - - - - - - - -{{/if}} -
    -
    - diff --git a/ui/packages/consul-ui/app/templates/dc/acls/edit.hbs b/ui/packages/consul-ui/app/templates/dc/acls/edit.hbs deleted file mode 100644 index 8f35906c1..000000000 --- a/ui/packages/consul-ui/app/templates/dc/acls/edit.hbs +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - -
      -
    1. All Tokens
    2. -
    -
    - -

    - {{#if item.Name }} - {{item.Name}} - {{else}} - New token - {{/if}} -

    -
    - - {{#if (not create) }} - - Copy token ID - - {{#if (can "duplicate acl" item=item)}} - - - - - - -

    - {{message}} -

    - - -
    -
    - {{/if}} - {{/if}} -
    - - {{ partial 'dc/acls/form'}} - -
    -
    \ No newline at end of file diff --git a/ui/packages/consul-ui/app/templates/dc/acls/index.hbs b/ui/packages/consul-ui/app/templates/dc/acls/index.hbs index 2ed2c657b..26070ffdc 100644 --- a/ui/packages/consul-ui/app/templates/dc/acls/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/acls/index.hbs @@ -1,101 +1,5 @@ - {{#let - - (hash - value=(or sortBy "Name:asc") - change=(action (mut sortBy) value="target.selected") - ) - - (hash - kind=(hash - value=(if kind (split kind ',') undefined) - change=(action (mut kind) value="target.selectedItems") - ) - ) - - items - - as |sort filters items|}} - - - - - - -

    - ACL Tokens {{format-number items.length}} total -

    - -
    - - {{#if (can "create acls")}} - Create - {{/if}} - - - {{#if (gt items.length 0) }} - - {{/if}} - - - - - - - - - - -

    - {{#if (gt items.length 0)}} - No ACLs found - {{else}} - Welcome to ACLs - {{/if}} -

    -
    - -

    - {{#if (gt items.length 0)}} - No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for. - {{else}} - There don't seem to be any ACLs yet, or you may not have access to view ACLs yet. - {{/if}} -

    -
    -
    -
    -
    -
    -
    - - {{/let}} + {{did-insert (route-action 'replaceWith' 'dc.acls.tokens')}}
    diff --git a/ui/packages/consul-ui/app/utils/acls-status.js b/ui/packages/consul-ui/app/utils/acls-status.js deleted file mode 100644 index acc34dc59..000000000 --- a/ui/packages/consul-ui/app/utils/acls-status.js +++ /dev/null @@ -1,64 +0,0 @@ -// This is used by all acl routes to check whether -// acls are enabled on the server, and whether the user -// has a valid token -// Right now this is very acl specific, but is likely to be -// made a bit more less specific - -export default function(isValidServerError, P = Promise) { - return function(obj) { - const propName = Object.keys(obj)[0]; - const p = obj[propName]; - let authorize; - let enable; - return { - isAuthorized: new P(function(resolve) { - authorize = function(bool) { - resolve(bool); - }; - }), - isEnabled: new P(function(resolve) { - enable = function(bool) { - resolve(bool); - }; - }), - [propName]: p - .catch(function(e) { - if (e.errors && e.errors[0]) { - switch (e.errors[0].status) { - case '500': - if (isValidServerError(e)) { - enable(true); - authorize(false); - } else { - enable(false); - authorize(false); - return P.reject(e); - } - break; - case '403': - enable(true); - authorize(false); - break; - case '401': - enable(false); - authorize(false); - break; - default: - enable(false); - authorize(false); - throw e; - } - return []; - } - enable(false); - authorize(false); - throw e; - }) - .then(function(res) { - enable(true); - authorize(true); - return res; - }), - }; - }; -} diff --git a/ui/packages/consul-ui/app/utils/http/acl/is-valid-server-error.js b/ui/packages/consul-ui/app/utils/http/acl/is-valid-server-error.js deleted file mode 100644 index 2c1dcb67f..000000000 --- a/ui/packages/consul-ui/app/utils/http/acl/is-valid-server-error.js +++ /dev/null @@ -1,12 +0,0 @@ -// very specific error check just for one specific ACL case -// likely to be reused at a later date, so lets use the specific -// case we need right now as default -const UNKNOWN_METHOD_ERROR = "rpc error making call: rpc: can't find method ACL"; -export default function(response = UNKNOWN_METHOD_ERROR) { - return function(e) { - if (e && e.errors && e.errors[0] && e.errors[0].detail) { - return e.errors[0].detail.indexOf(response) !== -1; - } - return false; - }; -} diff --git a/ui/packages/consul-ui/app/validations/acl.js b/ui/packages/consul-ui/app/validations/acl.js deleted file mode 100644 index 5878e768e..000000000 --- a/ui/packages/consul-ui/app/validations/acl.js +++ /dev/null @@ -1,5 +0,0 @@ -import { validatePresence, validateLength } from 'ember-changeset-validations/validators'; -export default { - Name: [validatePresence(true), validateLength({ min: 1 })], - Type: validatePresence(true), -}; diff --git a/ui/packages/consul-ui/ember-cli-build.js b/ui/packages/consul-ui/ember-cli-build.js index cd993ed90..0ede17347 100644 --- a/ui/packages/consul-ui/ember-cli-build.js +++ b/ui/packages/consul-ui/ember-cli-build.js @@ -163,6 +163,9 @@ module.exports = function(defaults, $ = process.env) { app.import('vendor/metrics-providers/prometheus.js', { outputFile: 'assets/metrics-providers/prometheus.js', }); + app.import('vendor/acls/routes.js', { + outputFile: 'assets/acls/routes.js', + }); app.import('vendor/init.js', { outputFile: 'assets/init.js', }); diff --git a/ui/packages/consul-ui/lib/startup/templates/body.html.js b/ui/packages/consul-ui/lib/startup/templates/body.html.js index 975cbd650..cafa6fb7c 100644 --- a/ui/packages/consul-ui/lib/startup/templates/body.html.js +++ b/ui/packages/consul-ui/lib/startup/templates/body.html.js @@ -41,6 +41,26 @@ ${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.oper "codemirror/mode/yaml/yaml.js": "${rootURL}assets/codemirror/mode/yaml/yaml.js" } +${ + environment === 'production' + ? ` +{{if .ACLsEnabled}} + +{{end}} +` + : ` + +` +} ${environment === 'test' ? `` : ``} diff --git a/ui/packages/consul-ui/package.json b/ui/packages/consul-ui/package.json index 35c17e2f9..a4e9383c3 100644 --- a/ui/packages/consul-ui/package.json +++ b/ui/packages/consul-ui/package.json @@ -85,6 +85,7 @@ "d3-selection": "^2.0.0", "d3-shape": "^2.0.0", "dayjs": "^1.9.3", + "deepmerge": "^4.2.2", "ember-assign-helper": "^0.3.0", "ember-auto-import": "^1.5.3", "ember-can": "^3.0.0", diff --git a/ui/packages/consul-ui/server/index.js b/ui/packages/consul-ui/server/index.js index a2abdb55e..7b0bec453 100644 --- a/ui/packages/consul-ui/server/index.js +++ b/ui/packages/consul-ui/server/index.js @@ -29,7 +29,7 @@ module.exports = function(app, options) { // sets the base CSP policy for the UI app.use(function(request, response, next) { response.set({ - 'Content-Security-Policy': `default-src 'self' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`, + 'Content-Security-Policy': `default-src 'self' 'unsafe-inline' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`, }); next(); }); diff --git a/ui/packages/consul-ui/tests/integration/adapters/acl-test.js b/ui/packages/consul-ui/tests/integration/adapters/acl-test.js deleted file mode 100644 index 398292bff..000000000 --- a/ui/packages/consul-ui/tests/integration/adapters/acl-test.js +++ /dev/null @@ -1,106 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -module('Integration | Adapter | acl', function(hooks) { - setupTest(hooks); - const dc = 'dc-1'; - const id = 'token-name'; - test('requestForQuery returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `GET /v1/acl/list?dc=${dc}`; - const actual = adapter.requestForQuery(request, { - dc: dc, - }); - assert.equal(actual, expected); - }); - test('requestForQueryRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `GET /v1/acl/info/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(request, { - dc: dc, - id: id, - }); - assert.equal(actual, expected); - }); - test("requestForQueryRecord throws if you don't specify an id", function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - assert.throws(function() { - adapter.requestForQueryRecord(request, { - dc: dc, - }); - }); - }); - test('requestForCreateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `PUT /v1/acl/create?dc=${dc}`; - const actual = adapter - .requestForCreateRecord( - request, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `PUT /v1/acl/update?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - request, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `PUT /v1/acl/destroy/${id}?dc=${dc}`; - const actual = adapter - .requestForDeleteRecord( - request, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('/n')[0]; - assert.equal(actual, expected); - }); - test('requestForCloneRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:acl'); - const client = this.owner.lookup('service:client/http'); - const request = client.url.bind(client); - const expected = `PUT /v1/acl/clone/${id}?dc=${dc}`; - const actual = adapter - .requestForCloneRecord( - request, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/adapters/acl-test.js b/ui/packages/consul-ui/tests/unit/adapters/acl-test.js deleted file mode 100644 index faccc4787..000000000 --- a/ui/packages/consul-ui/tests/unit/adapters/acl-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Adapter | acl', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let adapter = this.owner.lookup('adapter:acl'); - assert.ok(adapter); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/controllers/dc/acls/create-test.js b/ui/packages/consul-ui/tests/unit/controllers/dc/acls/create-test.js deleted file mode 100644 index adb0e65b7..000000000 --- a/ui/packages/consul-ui/tests/unit/controllers/dc/acls/create-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | dc/acls/create', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:dc/acls/create'); - assert.ok(controller); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/controllers/dc/acls/edit-test.js b/ui/packages/consul-ui/tests/unit/controllers/dc/acls/edit-test.js deleted file mode 100644 index cd0135ee6..000000000 --- a/ui/packages/consul-ui/tests/unit/controllers/dc/acls/edit-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Controller | dc/acls/edit', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let controller = this.owner.lookup('controller:dc/acls/edit'); - assert.ok(controller); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/mixins/acl/with-actions-test.js b/ui/packages/consul-ui/tests/unit/mixins/acl/with-actions-test.js deleted file mode 100644 index 137e79083..000000000 --- a/ui/packages/consul-ui/tests/unit/mixins/acl/with-actions-test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { module } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import test from 'ember-sinon-qunit/test-support/test'; -import Service from '@ember/service'; -import Route from 'consul-ui/routes/dc/acls/index'; - -import Mixin from 'consul-ui/mixins/acl/with-actions'; - -module('Unit | Mixin | acl/with actions', function(hooks) { - setupTest(hooks); - - hooks.beforeEach(function() { - this.subject = function() { - const MixedIn = Route.extend(Mixin); - this.owner.register('test-container:acl/with-actions-object', MixedIn); - return this.owner.lookup('test-container:acl/with-actions-object'); - }; - }); - - // Replace this with your real tests. - test('it works', function(assert) { - const subject = this.subject(); - assert.ok(subject); - }); - test('use persists the token', function(assert) { - assert.expect(2); - const item = { ID: 'id' }; - const expected = { Namespace: 'default', AccessorID: null, SecretID: item.ID }; - this.owner.register( - 'service:settings', - Service.extend({ - persist: function(actual) { - assert.deepEqual(actual.token, expected); - return Promise.resolve(actual); - }, - }) - ); - const subject = this.subject(); - return subject.actions.use - .bind(subject)(item) - .then(function(actual) { - assert.deepEqual(actual.token, expected); - }); - }); - test('clone clones the token and calls afterDelete correctly', function(assert) { - assert.expect(4); - this.owner.register( - 'service:feedback', - Service.extend({ - execute: function(cb, name) { - assert.equal(name, 'clone'); - return cb(); - }, - }) - ); - const expected = { ID: 'id' }; - this.owner.register( - 'service:repository/acl', - Service.extend({ - clone: function(actual) { - assert.deepEqual(actual, expected); - return Promise.resolve(actual); - }, - }) - ); - const subject = this.subject(); - const afterDelete = this.stub(subject, 'afterDelete').returnsArg(0); - return subject.actions.clone - .bind(subject)(expected) - .then(function(actual) { - assert.ok(afterDelete.calledOnce); - assert.equal(actual, expected); - }); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/models/acl-test.js b/ui/packages/consul-ui/tests/unit/models/acl-test.js deleted file mode 100644 index 24df7d51e..000000000 --- a/ui/packages/consul-ui/tests/unit/models/acl-test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import { run } from '@ember/runloop'; - -module('Unit | Model | acl', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let store = this.owner.lookup('service:store'); - let model = run(() => store.createRecord('acl', {})); - assert.ok(model); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/routes/dc/acls-test.js b/ui/packages/consul-ui/tests/unit/routes/dc/acls-test.js deleted file mode 100644 index a455e74a2..000000000 --- a/ui/packages/consul-ui/tests/unit/routes/dc/acls-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | dc/acls', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:dc/acls'); - assert.ok(route); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/routes/dc/acls/create-test.js b/ui/packages/consul-ui/tests/unit/routes/dc/acls/create-test.js deleted file mode 100644 index a2159a044..000000000 --- a/ui/packages/consul-ui/tests/unit/routes/dc/acls/create-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | dc/acls/create', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:dc/acls/create'); - assert.ok(route); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/routes/dc/acls/edit-test.js b/ui/packages/consul-ui/tests/unit/routes/dc/acls/edit-test.js deleted file mode 100644 index 024529fd9..000000000 --- a/ui/packages/consul-ui/tests/unit/routes/dc/acls/edit-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | dc/acls/edit', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:dc/acls/edit'); - assert.ok(route); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/routes/dc/acls/index-test.js b/ui/packages/consul-ui/tests/unit/routes/dc/acls/index-test.js deleted file mode 100644 index 9bd559be0..000000000 --- a/ui/packages/consul-ui/tests/unit/routes/dc/acls/index-test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Route | dc/acls/index', function(hooks) { - setupTest(hooks); - - test('it exists', function(assert) { - let route = this.owner.lookup('route:dc/acls/index'); - assert.ok(route); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js deleted file mode 100644 index dcadd5cae..000000000 --- a/ui/packages/consul-ui/tests/unit/search/predicates/acl-test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { module, test } from 'qunit'; - -import ExactSearch from 'consul-ui/utils/search/exact'; -import predicates from 'consul-ui/search/predicates/acl'; - -module('Unit | Search | Predicate | acl', function() { - test('items are found by properties', function(assert) { - const actual = new ExactSearch( - [ - { - ID: 'HIT-id', - Name: 'name', - }, - { - ID: 'id', - Name: 'name', - }, - { - ID: 'id', - Name: 'name-HIT', - }, - ], - { - finders: predicates, - } - ).search('hit'); - assert.equal(actual.length, 2); - }); - test('items are not found', function(assert) { - const actual = new ExactSearch( - [ - { - ID: 'id', - Name: 'name', - }, - ], - { - finders: predicates, - } - ).search('hit'); - assert.equal(actual.length, 0); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/serializers/acl-test.js b/ui/packages/consul-ui/tests/unit/serializers/acl-test.js deleted file mode 100644 index ee548017f..000000000 --- a/ui/packages/consul-ui/tests/unit/serializers/acl-test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; -import { run } from '@ember/runloop'; - -module('Unit | Serializer | acl', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let store = this.owner.lookup('service:store'); - let serializer = store.serializerFor('acl'); - - assert.ok(serializer); - }); - - test('it serializes records', function(assert) { - let store = this.owner.lookup('service:store'); - let record = run(() => store.createRecord('acl', {})); - - let serializedRecord = record.serialize(); - - assert.ok(serializedRecord); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/services/repository/acl-test.js b/ui/packages/consul-ui/tests/unit/services/repository/acl-test.js deleted file mode 100644 index 470764ec0..000000000 --- a/ui/packages/consul-ui/tests/unit/services/repository/acl-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Service | acl', function(hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function(assert) { - let service = this.owner.lookup('service:repository/acl'); - assert.ok(service); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/utils/acls-status-test.js b/ui/packages/consul-ui/tests/unit/utils/acls-status-test.js deleted file mode 100644 index 2034b5d69..000000000 --- a/ui/packages/consul-ui/tests/unit/utils/acls-status-test.js +++ /dev/null @@ -1,89 +0,0 @@ -import { module } from 'qunit'; -import test from 'ember-sinon-qunit/test-support/test'; -import aclsStatus from 'consul-ui/utils/acls-status'; - -module('Unit | Utility | acls status', function() { - test('it rejects and nothing is enabled or authorized', function(assert) { - const isValidServerError = this.stub().returns(false); - const status = aclsStatus(isValidServerError); - [ - this.stub().rejects(), - this.stub().rejects({ errors: [] }), - this.stub().rejects({ errors: [{ status: '404' }] }), - ].forEach(function(reject) { - const actual = status({ - response: reject(), - }); - assert.rejects(actual.response); - ['isAuthorized', 'isEnabled'].forEach(function(prop) { - actual[prop].then(function(actual) { - assert.notOk(actual); - }); - }); - }); - }); - test('with a 401 it resolves with an empty array and nothing is enabled or authorized', function(assert) { - assert.expect(3); - const isValidServerError = this.stub().returns(false); - const status = aclsStatus(isValidServerError); - const actual = status({ - response: this.stub().rejects({ errors: [{ status: '401' }] })(), - }); - actual.response.then(function(actual) { - assert.deepEqual(actual, []); - }); - ['isAuthorized', 'isEnabled'].forEach(function(prop) { - actual[prop].then(function(actual) { - assert.notOk(actual); - }); - }); - }); - test("with a 403 it resolves with an empty array and it's enabled but not authorized", function(assert) { - assert.expect(3); - const isValidServerError = this.stub().returns(false); - const status = aclsStatus(isValidServerError); - const actual = status({ - response: this.stub().rejects({ errors: [{ status: '403' }] })(), - }); - actual.response.then(function(actual) { - assert.deepEqual(actual, []); - }); - actual.isEnabled.then(function(actual) { - assert.ok(actual); - }); - actual.isAuthorized.then(function(actual) { - assert.notOk(actual); - }); - }); - test("with a 500 (but not a 'valid' error) it rejects and nothing is enabled or authorized", function(assert) { - assert.expect(3); - const isValidServerError = this.stub().returns(false); - const status = aclsStatus(isValidServerError); - const actual = status({ - response: this.stub().rejects({ errors: [{ status: '500' }] })(), - }); - assert.rejects(actual.response); - ['isAuthorized', 'isEnabled'].forEach(function(prop) { - actual[prop].then(function(actual) { - assert.notOk(actual); - }); - }); - }); - test("with a 500 and a 'valid' error, it resolves with an empty array and it's enabled but not authorized", function(assert) { - assert.expect(3); - const isValidServerError = this.stub().returns(true); - const status = aclsStatus(isValidServerError); - const actual = status({ - response: this.stub().rejects({ errors: [{ status: '500' }] })(), - }); - actual.response.then(function(actual) { - assert.deepEqual(actual, []); - }); - actual.isEnabled.then(function(actual) { - assert.ok(actual); - }); - actual.isAuthorized.then(function(actual) { - assert.notOk(actual); - }); - }); -}); diff --git a/ui/packages/consul-ui/tests/unit/utils/http/acl/is-valid-server-error-test.js b/ui/packages/consul-ui/tests/unit/utils/http/acl/is-valid-server-error-test.js deleted file mode 100644 index c1297598c..000000000 --- a/ui/packages/consul-ui/tests/unit/utils/http/acl/is-valid-server-error-test.js +++ /dev/null @@ -1,49 +0,0 @@ -import createIsValidServerError from 'consul-ui/utils/http/acl/is-valid-server-error'; -import { module, test } from 'qunit'; - -module('Unit | Utility | http/acl/is valid server error', function() { - const createEmberDataError = function(response) { - return { - errors: [ - { - detail: response, - }, - ], - }; - }; - test('it returns a function', function(assert) { - const isValidServerError = createIsValidServerError(); - assert.ok(typeof isValidServerError === 'function'); - }); - test("it returns false if there is no 'correctly' formatted error", function(assert) { - const isValidServerError = createIsValidServerError(); - assert.notOk(isValidServerError()); - assert.notOk(isValidServerError({})); - assert.notOk(isValidServerError({ errors: {} })); - assert.notOk(isValidServerError({ errors: [{}] })); - assert.notOk(isValidServerError({ errors: [{ notDetail: '' }] })); - }); - // don't go too crazy with these, just enough for sanity check, we are essentially testing indexOf - test("it returns false if the response doesn't contain the exact error response", function(assert) { - const isValidServerError = createIsValidServerError(); - [ - "pc error making call: rpc: can't find method ACL", - "rpc error making call: rpc: can't find method", - "rpc rror making call: rpc: can't find method ACL", - ].forEach(function(response) { - const e = createEmberDataError(response); - assert.notOk(isValidServerError(e)); - }); - }); - test('it returns true if the response contains the exact error response', function(assert) { - const isValidServerError = createIsValidServerError(); - [ - "rpc error making call: rpc: can't find method ACL", - " rpc error making call: rpc: can't find method ACL", - "rpc error making call: rpc: rpc error making call: rpc: rpc error making call: rpc: can't find method ACL", - ].forEach(function(response) { - const e = createEmberDataError(response); - assert.ok(isValidServerError(e)); - }); - }); -}); diff --git a/ui/packages/consul-ui/vendor/acls/routes.js b/ui/packages/consul-ui/vendor/acls/routes.js new file mode 100644 index 000000000..b553c9e93 --- /dev/null +++ b/ui/packages/consul-ui/vendor/acls/routes.js @@ -0,0 +1,15 @@ +(function(appNameJS = 'consulUi', doc = document) { + const scripts = doc.getElementsByTagName('script'); + const script = scripts[scripts.length - 1]; + script.dataset[`${appNameJS}Routes`] = JSON.stringify({ + dc: { + acls: { + tokens: { + _options: { + abilities: ['read tokens'], + }, + }, + }, + }, + }); +})(); diff --git a/ui/yarn.lock b/ui/yarn.lock index 7b5c435a6..75c5968df 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -5102,6 +5102,11 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"