Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Decode database connection_url #29114

Merged
merged 8 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/29114.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Decode database url to fix editing failures for an oracle connection
```
12 changes: 0 additions & 12 deletions ui/app/helpers/decode-uri.js

This file was deleted.

10 changes: 10 additions & 0 deletions ui/app/serializers/database/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export default RESTSerializer.extend({
...payload.data,
...payload.data.connection_details,
};

// connection_details are spread above into the main body of response so we can remove redundant data
delete response.connection_details;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was some cleanup, but i can remove if folks are worried about regressions. I searched globally for any use of connection_details or connectionDetails related to databases and didn't come across anything.

if (response?.connection_url) {
// this url can include interpolated data, such as: "{{username}}/{{password}}@localhost:1521/OraDoc.localhost"
// these curly brackets are returned by the API encoded: "%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@localhost:1521/OraDoc.localhost"
// we decode here so the UI displays and submits the url in the correct format
response.connection_url = decodeURI(response.connection_url);
}

if (payload.data.root_credentials_rotate_statements) {
response.root_rotation_statements = payload.data.root_credentials_rotate_statements;
}
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/database-connection.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
@defaultShown={{defaultDisplay}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{if (eq attr.name "connection_url") (decode-uri (get @model attr.name)) (get @model attr.name)}}
@value={{get @model attr.name}}
/>
{{/if}}
{{/let}}
Expand Down
61 changes: 55 additions & 6 deletions ui/tests/acceptance/secrets/backend/database/secret-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { currentURL, settled, click, visit, fillIn, typeIn, waitFor } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { create } from 'ember-cli-page-object';
import { selectChoose } from 'ember-power-select/test-support';
import { clickTrigger } from 'ember-power-select/test-support/helpers';
Expand Down Expand Up @@ -226,6 +227,7 @@ const connectionTests = [

module('Acceptance | secrets/database/*', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);

hooks.beforeEach(async function () {
this.backend = `database-testing`;
Expand Down Expand Up @@ -337,9 +339,11 @@ module('Acceptance | secrets/database/*', function (hooks) {
await visit('/vault/secrets');
});
}
test('database connection create and edit: vault-plugin-database-oracle', async function (assert) {

// keep oracle as separate test because it relies on an external plugin that isn't rolled into the vault binary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great comment

// https://github.com/hashicorp/vault-plugin-database-oracle
test('database connection create: vault-plugin-database-oracle', async function (assert) {
assert.expect(11);
// keep oracle as separate test because it behaves differently than the others
const testCase = {
name: 'oracle-connection',
plugin: 'vault-plugin-database-oracle',
Expand Down Expand Up @@ -380,7 +384,52 @@ module('Acceptance | secrets/database/*', function (hooks) {
await connectionPage.connectionUrl(testCase.url);
testCase.requiredFields(assert, testCase.plugin);
// Cannot save without plugin mounted
// TODO: add fake server response for fuller test coverage
// Edit tested separately with mocked server response
});

test('database connection edit: vault-plugin-database-oracle', async function (assert) {
assert.expect(2);
const connectionName = 'oracle-connection';
// mock API so we can test edit (without mounting external oracle plugin)
this.server.get(`/${this.backend}/config/${connectionName}`, () => {
return {
request_id: 'f869f23e-15c0-389b-82ac-84035a2b6079',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
allowed_roles: ['*'],
connection_details: {
backend: 'database',
connection_url: '%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@//localhost:1521/ORCLPDB1',
max_connection_lifetime: '0s',
max_idle_connections: 0,
max_open_connections: 3,
username: 'VAULTADMIN',
},
password_policy: '',
plugin_name: 'vault-plugin-database-oracle',
plugin_version: '',
root_credentials_rotate_statements: [],
verify_connection: true,
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: 'database',
};
});

await visit(`/vault/secrets/${this.backend}/show/${connectionName}`);
const decoded = '{{username}}/{{password}}@//localhost:1521/ORCLPDB1';
assert
.dom('[data-test-row-value="Connection URL"]')
.hasText(decoded, 'connection_url is decoded in display');

await connectionPage.edit();
assert
.dom('[data-test-input="connection_url"]')
.hasValue(decoded, 'connection_url is decoded when editing');
});

test('Can create and delete a connection', async function (assert) {
Expand Down Expand Up @@ -504,17 +553,17 @@ module('Acceptance | secrets/database/*', function (hooks) {
await visit('/vault/secrets');
});

test('connection_url must be decoded', async function (assert) {
test('connection_url is decoded', async function (assert) {
const backend = this.backend;
const connection = await newConnection(
backend,
'mongodb-database-plugin',
'{{username}}/{{password}}@oracle-xe:1521/XEPDB1'
'{{username}}/{{password}}@mongo:1521/XEPDB1'
);
await navToConnection(backend, connection);
assert
.dom('[data-test-row-value="Connection URL"]')
.hasText('{{username}}/{{password}}@oracle-xe:1521/XEPDB1');
.hasText('{{username}}/{{password}}@mongo:1521/XEPDB1');
});

test('Role create form', async function (assert) {
Expand Down
113 changes: 107 additions & 6 deletions ui/tests/unit/serializers/database/connection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,6 @@ module('Unit | Serializer | database/connection', function (hooks) {
const expectedResult = {
allowed_roles: ['readonly'],
backend: 'database',
connection_details: {
backend: 'database',
insecure: false,
url: 'https://localhost:9200',
username: 'root',
},
id: 'elastic-test',
insecure: false,
name: 'elastic-test',
Expand All @@ -98,4 +92,111 @@ module('Unit | Serializer | database/connection', function (hooks) {
};
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
});

test('it should normalize values for the database type (oracle)', function (assert) {
const serializer = this.owner.lookup('serializer:database/connection');
const normalized = serializer.normalizeSecrets({
request_id: 'request-id',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
allowed_roles: ['*'],
connection_details: {
backend: 'database',
connection_url: '%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@//localhost:1521/ORCLPDB1',
max_connection_lifetime: '0s',
max_idle_connections: 0,
max_open_connections: 3,
username: 'VAULTADMIN',
},
password_policy: '',
plugin_name: 'vault-plugin-database-oracle',
plugin_version: '',
root_credentials_rotate_statements: [],
verify_connection: true,
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: 'database',
backend: 'database',
id: 'oracle-test',
});
const expectedResult = {
allowed_roles: ['*'],
backend: 'database',
connection_url: '{{username}}/{{password}}@//localhost:1521/ORCLPDB1',
id: 'oracle-test',
max_connection_lifetime: '0s',
max_idle_connections: 0,
max_open_connections: 3,
name: 'oracle-test',
password_policy: '',
plugin_name: 'vault-plugin-database-oracle',
plugin_version: '',
root_credentials_rotate_statements: [],
root_rotation_statements: [],
username: 'VAULTADMIN',
verify_connection: true,
};
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
});

test('it should normalize values if some params do not exist', function (assert) {
const serializer = this.owner.lookup('serializer:database/connection');
const normalized = serializer.normalizeSecrets({
request_id: 'request-id',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
allowed_roles: ['*'],
connection_details: { backend: 'database' }, // no connection_url param intentionally
plugin_name: 'vault-postgres-db',
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: 'database',
backend: 'database',
id: 'db-test',
});
const expectedResult = {
allowed_roles: ['*'],
backend: 'database',
id: 'db-test',
name: 'db-test',
plugin_name: 'vault-postgres-db',
};
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
});

test('it should fail gracefully if no connection_details', function (assert) {
const serializer = this.owner.lookup('serializer:database/connection');
const normalized = serializer.normalizeSecrets({
request_id: 'request-id',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
allowed_roles: ['*'],
plugin_name: 'vault-postgres-db',
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: 'database',
backend: 'database',
id: 'db-test',
});
const expectedResult = {
allowed_roles: ['*'],
backend: 'database',
id: 'db-test',
name: 'db-test',
plugin_name: 'vault-postgres-db',
};
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
});
});
Loading