/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import { assign } from '@ember/polyfills'; import { copy } from 'ember-copy'; import { assert } from '@ember/debug'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; import { set, get, computed } from '@ember/object'; import { encodeString } from 'vault/utils/b64'; const TRANSIT_PARAMS = { hash_algorithm: 'sha2-256', algorithm: 'sha2-256', signature_algorithm: 'pss', bits: 256, bytes: 32, ciphertext: null, context: null, format: 'base64', hmac: null, input: null, key_version: 0, keys: null, nonce: null, param: 'wrapped', prehashed: false, plaintext: null, random_bytes: null, signature: null, sum: null, encodedBase64: false, exportKeyType: null, exportKeyVersion: null, wrappedToken: null, valid: null, plaintextOriginal: null, didDecode: false, verification: 'Signature', }; const PARAMS_FOR_ACTION = { sign: ['input', 'hash_algorithm', 'key_version', 'prehashed', 'signature_algorithm'], verify: ['input', 'hmac', 'signature', 'hash_algorithm', 'prehashed'], hmac: ['input', 'algorithm', 'key_version'], encrypt: ['plaintext', 'context', 'nonce', 'key_version'], decrypt: ['ciphertext', 'context', 'nonce'], rewrap: ['ciphertext', 'context', 'nonce', 'key_version'], }; const SUCCESS_MESSAGE_FOR_ACTION = { sign: 'Signed your data', // the verify action doesn't trigger a success message hmac: 'Created your hash output', encrypt: 'Created a wrapped token for your data', decrypt: 'Decrypted the data from your token', rewrap: 'Created a new token for your data', datakey: 'Generated your key', export: 'Exported your key', }; export default Component.extend(TRANSIT_PARAMS, { store: service(), flashMessages: service(), // public attrs selectedAction: null, key: null, isModalActive: false, onRefresh() {}, init() { this._super(...arguments); // TODO figure out why get is needed here Ember Upgrade // eslint-disable-next-line ember/no-get if (this.selectedAction) { return; } // eslint-disable-next-line ember/no-get set(this, 'selectedAction', get(this, 'key.supportedActions.firstObject')); assert('`key` is required for `' + this.toString() + '`.', this.getModelInfo()); }, didReceiveAttrs() { this._super(...arguments); this.checkAction(); if (this.selectedAction === 'export') { this.setExportKeyDefaults(); } }, setExportKeyDefaults() { const exportKeyType = this.key.exportKeyTypes.firstObject; const exportKeyVersion = this.key.validKeyVersions.lastObject; this.setProperties({ exportKeyType, exportKeyVersion, }); }, keyIsRSA: computed('key.type', function () { const type = this.key.type; return type === 'rsa-2048' || type === 'rsa-3072' || type === 'rsa-4096'; }), getModelInfo() { const model = this.key || this.backend; if (!model) { return null; } const backend = model.backend || model.id; const id = model.id; return { backend, id, }; }, checkAction() { const currentAction = this.selectedAction; const oldAction = this.oldSelectedAction; this.resetParams(oldAction, currentAction); set(this, 'oldSelectedAction', currentAction); }, resetParams(oldAction, action) { const params = copy(TRANSIT_PARAMS); let paramsToKeep; const clearWithoutCheck = !oldAction || // don't save values from datakey oldAction === 'datakey' || // can rewrap signatures — using that as a ciphertext later would be problematic (oldAction === 'rewrap' && !this.key.supportsEncryption); if (!clearWithoutCheck && action) { paramsToKeep = PARAMS_FOR_ACTION[action]; } if (paramsToKeep) { paramsToKeep.forEach((param) => delete params[param]); } //resets params still left in the object to defaults this.clearErrors(); this.setProperties(params); if (action === 'export') { this.setExportKeyDefaults(); } }, handleError(e) { this.set('errors', e.errors); }, clearErrors() { this.set('errors', null); }, triggerSuccessMessage(action) { const message = SUCCESS_MESSAGE_FOR_ACTION[action]; if (!message) return; this.flashMessages.success(message); }, handleSuccess(resp, options, action) { let props = {}; if (resp && resp.data) { if (action === 'export' && resp.data.keys) { const { keys, type, name } = resp.data; resp.data.keys = { keys, type, name }; } props = assign({}, props, resp.data); } if (options.wrapTTL) { props = assign({}, props, { wrappedToken: resp.wrap_info.token }); } if (!this.isDestroyed && !this.isDestroying) { this.toggleProperty('isModalActive'); this.setProperties(props); } if (action === 'rotate') { this.onRefresh(); } this.triggerSuccessMessage(action); }, compactData(data) { const type = this.key.type; const isRSA = type === 'rsa-2048' || type === 'rsa-3072' || type === 'rsa-4096'; return Object.keys(data).reduce((result, key) => { if (key === 'signature_algorithm' && !isRSA) { return result; } if (data[key]) { result[key] = data[key]; } return result; }, {}); }, actions: { onActionChange(action) { set(this, 'selectedAction', action); this.checkAction(); }, onClear() { this.resetParams(null, this.selectedAction); }, clearParams(params) { const arr = Array.isArray(params) ? params : [params]; arr.forEach((param) => this.set(param, null)); }, toggleModal(successMessage) { if (!!successMessage && typeof successMessage === 'string') { this.flashMessages.success(successMessage); } this.toggleProperty('isModalActive'); }, doSubmit(data, options = {}, maybeEvent) { const event = options.type === 'submit' ? options : maybeEvent; if (event) { event.preventDefault(); } const { backend, id } = this.getModelInfo(); const action = this.selectedAction; const { encodedBase64, ...formData } = data || {}; if (!encodedBase64) { if (action === 'encrypt' && !!formData.plaintext) { formData.plaintext = encodeString(formData.plaintext); } if ((action === 'hmac' || action === 'verify' || action === 'sign') && !!formData.input) { formData.input = encodeString(formData.input); } } const payload = formData ? this.compactData(formData) : null; this.setProperties({ errors: null, result: null, }); this.store .adapterFor('transit-key') .keyAction(action, { backend, id, payload }, options) .then( (resp) => this.handleSuccess(resp, options, action), (...errArgs) => this.handleError(...errArgs) ); }, }, });