| /** |
| * Copyright (c) HashiCorp, Inc. |
| * SPDX-License-Identifier: MPL-2.0 |
| */ |
| |
| import { set } from '@ember/object'; |
| import Service from '@ember/service'; |
| import { module, test } from 'qunit'; |
| import { setupTest } from 'ember-qunit'; |
| import sinon from 'sinon'; |
| |
| import { storageKey, CONTROL_GROUP_PREFIX, TOKEN_SEPARATOR } from 'vault/services/control-group'; |
| |
| const versionStub = Service.extend(); |
| |
| function storage() { |
| return { |
| items: {}, |
| getItem(key) { |
| var item = this.items[key]; |
| return item && JSON.parse(item); |
| }, |
| |
| setItem(key, val) { |
| return (this.items[key] = JSON.stringify(val)); |
| }, |
| |
| removeItem(key) { |
| delete this.items[key]; |
| }, |
| |
| keys() { |
| return Object.keys(this.items); |
| }, |
| }; |
| } |
| |
| module('Unit | Service | control group', function (hooks) { |
| setupTest(hooks); |
| |
| hooks.beforeEach(function () { |
| this.owner.register('service:version', versionStub); |
| this.version = this.owner.lookup('service:version'); |
| this.router = this.owner.lookup('service:router'); |
| this.router.reopen({ |
| transitionTo: sinon.stub(), |
| urlFor: sinon.stub().returns('/ui/vault/foo'), |
| currentURL: '/vault/secrets/kv/show/foo', |
| }); |
| }); |
| |
| hooks.afterEach(function () {}); |
| |
| const isOSS = (context) => set(context, 'version.isOSS', true); |
| const isEnt = (context) => set(context, 'version.isOSS', false); |
| const resolvesArgs = (assert, result, expectedArgs) => { |
| return result.then((...args) => { |
| return assert.deepEqual(args, expectedArgs, 'resolves with the passed args'); |
| }); |
| }; |
| |
| [ |
| [ |
| 'it resolves isOSS:true, wrapTTL: true, response: has wrap_info', |
| isOSS, |
| [[{ one: 'two', three: 'four' }], { wrap_info: { token: 'foo', accessor: 'bar' } }, true], |
| (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]), |
| ], |
| [ |
| 'it resolves isOSS:true, wrapTTL: false, response: has no wrap_info', |
| isOSS, |
| [[{ one: 'two', three: 'four' }], { wrap_info: null }, false], |
| (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]), |
| ], |
| [ |
| 'it resolves isOSS: false and wrapTTL:true response: has wrap_info', |
| isEnt, |
| [[{ one: 'two', three: 'four' }], { wrap_info: { token: 'foo', accessor: 'bar' } }, true], |
| (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]), |
| ], |
| [ |
| 'it resolves isOSS: false and wrapTTL:false response: has no wrap_info', |
| isEnt, |
| [[{ one: 'two', three: 'four' }], { wrap_info: null }, false], |
| (assert, result) => resolvesArgs(assert, result, [{ one: 'two', three: 'four' }]), |
| ], |
| [ |
| 'it rejects isOSS: false, wrapTTL:false, response: has wrap_info', |
| isEnt, |
| [ |
| [{ one: 'two', three: 'four' }], |
| { foo: 'bar', wrap_info: { token: 'secret', accessor: 'lookup' } }, |
| false, |
| ], |
| (assert, result) => { |
| // ensure failure if we ever don't reject |
| assert.expect(2); |
| |
| return result.then( |
| () => {}, |
| (err) => { |
| assert.strictEqual(err.token, 'secret'); |
| assert.strictEqual(err.accessor, 'lookup'); |
| } |
| ); |
| }, |
| ], |
| ].forEach(function ([name, setup, args, expectation]) { |
| test(`checkForControlGroup: ${name}`, function (assert) { |
| const assertCount = name === 'it rejects isOSS: false, wrapTTL:false, response: has wrap_info' ? 2 : 1; |
| assert.expect(assertCount); |
| if (setup) { |
| setup(this); |
| } |
| const service = this.owner.lookup('service:control-group'); |
| const result = service.checkForControlGroup(...args); |
| return expectation(assert, result); |
| }); |
| }); |
| |
| test(`handleError: transitions to accessor and stores control group token`, function (assert) { |
| const error = { |
| accessor: '12345', |
| token: 'token', |
| creation_path: 'kv/', |
| creation_time: '2022-03-17T20:00:25.594Z', |
| ttl: 400, |
| }; |
| const expected = { ...error, uiParams: { url: '/vault/secrets/kv/show/foo' } }; |
| const service = this.owner.factoryFor('service:control-group').create({ |
| storeControlGroupToken: sinon.spy(), |
| }); |
| service.handleError(error); |
| assert.ok(service.storeControlGroupToken.calledWith(expected), 'calls storeControlGroupToken'); |
| assert.ok( |
| this.router.transitionTo.calledWith('vault.cluster.access.control-group-accessor', '12345'), |
| 'calls router transitionTo' |
| ); |
| }); |
| |
| test(`logFromError: returns correct content string`, function (assert) { |
| const error = { |
| accessor: '12345', |
| token: 'token', |
| creation_path: 'kv/', |
| creation_time: '2022-03-17T20:00:25.594Z', |
| ttl: 400, |
| }; |
| const service = this.owner.factoryFor('service:control-group').create({ |
| storeControlGroupToken: sinon.spy(), |
| }); |
| const contentString = service.logFromError(error); |
| assert.ok( |
| this.router.urlFor.calledWith('vault.cluster.access.control-group-accessor', '12345'), |
| 'calls urlFor with accessor' |
| ); |
| assert.ok(service.storeControlGroupToken.calledWith(error), 'calls storeControlGroupToken'); |
| assert.ok(contentString.content.includes('12345'), 'contains accessor'); |
| assert.ok(contentString.content.includes('kv/'), 'contains creation path'); |
| assert.ok(contentString.content.includes('token'), 'contains token'); |
| }); |
| |
| test('storageKey', function (assert) { |
| const accessor = '12345'; |
| const path = 'kv/foo/bar'; |
| const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`; |
| assert.strictEqual(storageKey(accessor, path), expectedKey, 'uses expected key'); |
| }); |
| |
| test('keyFromAccessor', function (assert) { |
| const store = storage(); |
| const accessor = '12345'; |
| const path = 'kv/foo/bar'; |
| const data = { foo: 'bar' }; |
| const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`; |
| const subject = this.owner.factoryFor('service:control-group').create({ |
| storage() { |
| return store; |
| }, |
| }); |
| |
| store.setItem(expectedKey, data); |
| store.setItem(`${CONTROL_GROUP_PREFIX}2345${TOKEN_SEPARATOR}${path}`, 'ok'); |
| |
| assert.strictEqual(subject.keyFromAccessor(accessor), expectedKey, 'finds key given the accessor'); |
| assert.strictEqual(subject.keyFromAccessor('foo'), null, 'returns null if no key was found'); |
| }); |
| |
| test('storeControlGroupToken', function (assert) { |
| const store = storage(); |
| const subject = this.owner.factoryFor('service:control-group').create({ |
| storage() { |
| return store; |
| }, |
| }); |
| const info = { |
| accessor: '12345', |
| creation_path: 'foo/', |
| creation_time: '2022-03-17T20:00:25.594Z', |
| ttl: 300, |
| }; |
| const key = `${CONTROL_GROUP_PREFIX}${info.accessor}${TOKEN_SEPARATOR}${info.creation_path}`; |
| |
| subject.storeControlGroupToken(info); |
| assert.deepEqual(store.items[key], JSON.stringify(info), 'stores the whole info object'); |
| }); |
| |
| test('deleteControlGroupToken', function (assert) { |
| const store = storage(); |
| const subject = this.owner.factoryFor('service:control-group').create({ |
| storage() { |
| return store; |
| }, |
| }); |
| const accessor = 'foo'; |
| const path = 'kv/one'; |
| |
| const expectedKey = `${CONTROL_GROUP_PREFIX}${accessor}${TOKEN_SEPARATOR}${path}`; |
| store.setItem(expectedKey, { one: '2' }); |
| subject.deleteControlGroupToken(accessor); |
| assert.strictEqual(Object.keys(store.items).length, 0, 'there are no keys stored in storage'); |
| }); |
| |
| test('deleteTokens', function (assert) { |
| const store = storage(); |
| const subject = this.owner.factoryFor('service:control-group').create({ |
| storage() { |
| return store; |
| }, |
| }); |
| |
| const keyOne = `${CONTROL_GROUP_PREFIX}foo`; |
| const keyTwo = `${CONTROL_GROUP_PREFIX}bar`; |
| store.setItem(keyOne, { one: '2' }); |
| store.setItem(keyTwo, { two: '2' }); |
| store.setItem('value', 'one'); |
| assert.strictEqual(Object.keys(store.items).length, 3, 'stores 3 values'); |
| subject.deleteTokens(); |
| assert.strictEqual(Object.keys(store.items).length, 1, 'removes tokens with control group prefix'); |
| assert.strictEqual(store.getItem('value'), 'one', 'keeps the non-prefixed value'); |
| }); |
| |
| test('wrapInfoForAccessor', function (assert) { |
| const store = storage(); |
| const subject = this.owner.factoryFor('service:control-group').create({ |
| storage() { |
| return store; |
| }, |
| }); |
| |
| const keyOne = `${CONTROL_GROUP_PREFIX}foo`; |
| store.setItem(keyOne, { one: '2' }); |
| assert.deepEqual(subject.wrapInfoForAccessor('foo'), { one: '2' }); |
| }); |
| }); |