Skip to content

Commit 5ba4fb3

Browse files
UI: Decode Oracle database connection_url (#29114)
* decode url in the serializer for oracle connection_url * add serializer test * add test for oracle * add test back, remove decode-url helper * update comment and test * link jiras VAULT-32830 VAULT-29785 * add changelog * add test
1 parent 59489a8 commit 5ba4fb3

File tree

6 files changed

+176
-25
lines changed

6 files changed

+176
-25
lines changed

Diff for: changelog/29114.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
ui: Decode database url to fix editing failures for an oracle connection
3+
```

Diff for: ui/app/helpers/decode-uri.js

-12
This file was deleted.

Diff for: ui/app/serializers/database/connection.js

+10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export default RESTSerializer.extend({
2929
...payload.data,
3030
...payload.data.connection_details,
3131
};
32+
33+
// connection_details are spread above into the main body of response so we can remove redundant data
34+
delete response.connection_details;
35+
if (response?.connection_url) {
36+
// this url can include interpolated data, such as: "{{username}}/{{password}}@localhost:1521/OraDoc.localhost"
37+
// these curly brackets are returned by the API encoded: "%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@localhost:1521/OraDoc.localhost"
38+
// we decode here so the UI displays and submits the url in the correct format
39+
response.connection_url = decodeURI(response.connection_url);
40+
}
41+
3242
if (payload.data.root_credentials_rotate_statements) {
3343
response.root_rotation_statements = payload.data.root_credentials_rotate_statements;
3444
}

Diff for: ui/app/templates/components/database-connection.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@
353353
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
354354
@defaultShown={{defaultDisplay}}
355355
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
356-
@value={{if (eq attr.name "connection_url") (decode-uri (get @model attr.name)) (get @model attr.name)}}
356+
@value={{get @model attr.name}}
357357
/>
358358
{{/if}}
359359
{{/let}}

Diff for: ui/tests/acceptance/secrets/backend/database/secret-test.js

+55-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { module, test } from 'qunit';
77
import { setupApplicationTest } from 'ember-qunit';
88
import { currentURL, settled, click, visit, fillIn, typeIn, waitFor } from '@ember/test-helpers';
9+
import { setupMirage } from 'ember-cli-mirage/test-support';
910
import { create } from 'ember-cli-page-object';
1011
import { selectChoose } from 'ember-power-select/test-support';
1112
import { clickTrigger } from 'ember-power-select/test-support/helpers';
@@ -226,6 +227,7 @@ const connectionTests = [
226227

227228
module('Acceptance | secrets/database/*', function (hooks) {
228229
setupApplicationTest(hooks);
230+
setupMirage(hooks);
229231

230232
hooks.beforeEach(async function () {
231233
this.backend = `database-testing`;
@@ -337,9 +339,11 @@ module('Acceptance | secrets/database/*', function (hooks) {
337339
await visit('/vault/secrets');
338340
});
339341
}
340-
test('database connection create and edit: vault-plugin-database-oracle', async function (assert) {
342+
343+
// keep oracle as separate test because it relies on an external plugin that isn't rolled into the vault binary
344+
// https://github.com/hashicorp/vault-plugin-database-oracle
345+
test('database connection create: vault-plugin-database-oracle', async function (assert) {
341346
assert.expect(11);
342-
// keep oracle as separate test because it behaves differently than the others
343347
const testCase = {
344348
name: 'oracle-connection',
345349
plugin: 'vault-plugin-database-oracle',
@@ -380,7 +384,52 @@ module('Acceptance | secrets/database/*', function (hooks) {
380384
await connectionPage.connectionUrl(testCase.url);
381385
testCase.requiredFields(assert, testCase.plugin);
382386
// Cannot save without plugin mounted
383-
// TODO: add fake server response for fuller test coverage
387+
// Edit tested separately with mocked server response
388+
});
389+
390+
test('database connection edit: vault-plugin-database-oracle', async function (assert) {
391+
assert.expect(2);
392+
const connectionName = 'oracle-connection';
393+
// mock API so we can test edit (without mounting external oracle plugin)
394+
this.server.get(`/${this.backend}/config/${connectionName}`, () => {
395+
return {
396+
request_id: 'f869f23e-15c0-389b-82ac-84035a2b6079',
397+
lease_id: '',
398+
renewable: false,
399+
lease_duration: 0,
400+
data: {
401+
allowed_roles: ['*'],
402+
connection_details: {
403+
backend: 'database',
404+
connection_url: '%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@//localhost:1521/ORCLPDB1',
405+
max_connection_lifetime: '0s',
406+
max_idle_connections: 0,
407+
max_open_connections: 3,
408+
username: 'VAULTADMIN',
409+
},
410+
password_policy: '',
411+
plugin_name: 'vault-plugin-database-oracle',
412+
plugin_version: '',
413+
root_credentials_rotate_statements: [],
414+
verify_connection: true,
415+
},
416+
wrap_info: null,
417+
warnings: null,
418+
auth: null,
419+
mount_type: 'database',
420+
};
421+
});
422+
423+
await visit(`/vault/secrets/${this.backend}/show/${connectionName}`);
424+
const decoded = '{{username}}/{{password}}@//localhost:1521/ORCLPDB1';
425+
assert
426+
.dom('[data-test-row-value="Connection URL"]')
427+
.hasText(decoded, 'connection_url is decoded in display');
428+
429+
await connectionPage.edit();
430+
assert
431+
.dom('[data-test-input="connection_url"]')
432+
.hasValue(decoded, 'connection_url is decoded when editing');
384433
});
385434

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

507-
test('connection_url must be decoded', async function (assert) {
556+
test('connection_url is decoded', async function (assert) {
508557
const backend = this.backend;
509558
const connection = await newConnection(
510559
backend,
511560
'mongodb-database-plugin',
512-
'{{username}}/{{password}}@oracle-xe:1521/XEPDB1'
561+
'{{username}}/{{password}}@mongo:1521/XEPDB1'
513562
);
514563
await navToConnection(backend, connection);
515564
assert
516565
.dom('[data-test-row-value="Connection URL"]')
517-
.hasText('{{username}}/{{password}}@oracle-xe:1521/XEPDB1');
566+
.hasText('{{username}}/{{password}}@mongo:1521/XEPDB1');
518567
});
519568

520569
test('Role create form', async function (assert) {

Diff for: ui/tests/unit/serializers/database/connection-test.js

+107-6
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,6 @@ module('Unit | Serializer | database/connection', function (hooks) {
7979
const expectedResult = {
8080
allowed_roles: ['readonly'],
8181
backend: 'database',
82-
connection_details: {
83-
backend: 'database',
84-
insecure: false,
85-
url: 'https://localhost:9200',
86-
username: 'root',
87-
},
8882
id: 'elastic-test',
8983
insecure: false,
9084
name: 'elastic-test',
@@ -98,4 +92,111 @@ module('Unit | Serializer | database/connection', function (hooks) {
9892
};
9993
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
10094
});
95+
96+
test('it should normalize values for the database type (oracle)', function (assert) {
97+
const serializer = this.owner.lookup('serializer:database/connection');
98+
const normalized = serializer.normalizeSecrets({
99+
request_id: 'request-id',
100+
lease_id: '',
101+
renewable: false,
102+
lease_duration: 0,
103+
data: {
104+
allowed_roles: ['*'],
105+
connection_details: {
106+
backend: 'database',
107+
connection_url: '%7B%7Busername%7D%7D/%7B%7Bpassword%7D%7D@//localhost:1521/ORCLPDB1',
108+
max_connection_lifetime: '0s',
109+
max_idle_connections: 0,
110+
max_open_connections: 3,
111+
username: 'VAULTADMIN',
112+
},
113+
password_policy: '',
114+
plugin_name: 'vault-plugin-database-oracle',
115+
plugin_version: '',
116+
root_credentials_rotate_statements: [],
117+
verify_connection: true,
118+
},
119+
wrap_info: null,
120+
warnings: null,
121+
auth: null,
122+
mount_type: 'database',
123+
backend: 'database',
124+
id: 'oracle-test',
125+
});
126+
const expectedResult = {
127+
allowed_roles: ['*'],
128+
backend: 'database',
129+
connection_url: '{{username}}/{{password}}@//localhost:1521/ORCLPDB1',
130+
id: 'oracle-test',
131+
max_connection_lifetime: '0s',
132+
max_idle_connections: 0,
133+
max_open_connections: 3,
134+
name: 'oracle-test',
135+
password_policy: '',
136+
plugin_name: 'vault-plugin-database-oracle',
137+
plugin_version: '',
138+
root_credentials_rotate_statements: [],
139+
root_rotation_statements: [],
140+
username: 'VAULTADMIN',
141+
verify_connection: true,
142+
};
143+
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
144+
});
145+
146+
test('it should normalize values if some params do not exist', function (assert) {
147+
const serializer = this.owner.lookup('serializer:database/connection');
148+
const normalized = serializer.normalizeSecrets({
149+
request_id: 'request-id',
150+
lease_id: '',
151+
renewable: false,
152+
lease_duration: 0,
153+
data: {
154+
allowed_roles: ['*'],
155+
connection_details: { backend: 'database' }, // no connection_url param intentionally
156+
plugin_name: 'vault-postgres-db',
157+
},
158+
wrap_info: null,
159+
warnings: null,
160+
auth: null,
161+
mount_type: 'database',
162+
backend: 'database',
163+
id: 'db-test',
164+
});
165+
const expectedResult = {
166+
allowed_roles: ['*'],
167+
backend: 'database',
168+
id: 'db-test',
169+
name: 'db-test',
170+
plugin_name: 'vault-postgres-db',
171+
};
172+
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
173+
});
174+
175+
test('it should fail gracefully if no connection_details', function (assert) {
176+
const serializer = this.owner.lookup('serializer:database/connection');
177+
const normalized = serializer.normalizeSecrets({
178+
request_id: 'request-id',
179+
lease_id: '',
180+
renewable: false,
181+
lease_duration: 0,
182+
data: {
183+
allowed_roles: ['*'],
184+
plugin_name: 'vault-postgres-db',
185+
},
186+
wrap_info: null,
187+
warnings: null,
188+
auth: null,
189+
mount_type: 'database',
190+
backend: 'database',
191+
id: 'db-test',
192+
});
193+
const expectedResult = {
194+
allowed_roles: ['*'],
195+
backend: 'database',
196+
id: 'db-test',
197+
name: 'db-test',
198+
plugin_name: 'vault-postgres-db',
199+
};
200+
assert.deepEqual(normalized, expectedResult, `Normalizes and flattens database response`);
201+
});
101202
});

0 commit comments

Comments
 (0)