From 3b7873302b4ba56ceafac631933ac079103776fb Mon Sep 17 00:00:00 2001 From: Priya Patel Date: Fri, 25 Jul 2025 10:57:56 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20vault=20ldap=20cred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../generated/models/credential-library.js | 4 +- addons/api/addon/models/credential-library.js | 13 ++- .../addon/serializers/credential-library.js | 51 ++++++++---- .../mirage/factories/credential-library.js | 8 ++ .../api/mirage/factories/credential-store.js | 2 +- .../unit/models/credential-library-test.js | 16 +++- .../serializers/credential-library-test.js | 80 +++++++++++++++++++ addons/core/translations/resources/en-us.yaml | 1 + 8 files changed, 154 insertions(+), 21 deletions(-) diff --git a/addons/api/addon/generated/models/credential-library.js b/addons/api/addon/generated/models/credential-library.js index 3d8ba617e..9838bc80a 100644 --- a/addons/api/addon/generated/models/credential-library.js +++ b/addons/api/addon/generated/models/credential-library.js @@ -76,13 +76,13 @@ export default class GeneratedCredentialLibraryModel extends BaseModel { path; @attr('string', { - for: 'vault-generic', + for: ['vault-generic', 'vault-ldap'], description: 'It indicates the type of credential the library returns.', }) credential_type; @attr('object-as-array', { - for: 'vault-generic', + for: ['vault-generic', 'vault-ldap'], description: 'It indicates the credential mapping overrides.', }) credential_mapping_overrides; diff --git a/addons/api/addon/models/credential-library.js b/addons/api/addon/models/credential-library.js index 7f7a6c9c3..8bb68bd52 100644 --- a/addons/api/addon/models/credential-library.js +++ b/addons/api/addon/models/credential-library.js @@ -38,6 +38,7 @@ export const options = { export const TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC = 'vault-generic'; export const TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE = 'vault-ssh-certificate'; +export const TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP = 'vault-ldap'; /** * Supported Credential Library types. @@ -45,6 +46,7 @@ export const TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE = export const TYPES_CREDENTIAL_LIBRARY = Object.freeze([ TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, ]); export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel { @@ -65,7 +67,8 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo get isVault() { return ( this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC || - this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE + this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE || + this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP ); } @@ -84,4 +87,12 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo get isVaultSSHCertificate() { return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE; } + + /** + * True if credential is a vault ldap type. + * @type {boolean} + */ + get isVaultLDAP() { + return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP; + } } diff --git a/addons/api/addon/serializers/credential-library.js b/addons/api/addon/serializers/credential-library.js index b5cc40bff..bc96b26a6 100644 --- a/addons/api/addon/serializers/credential-library.js +++ b/addons/api/addon/serializers/credential-library.js @@ -7,6 +7,7 @@ import ApplicationSerializer from './application'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, options, } from '../models/credential-library'; @@ -29,6 +30,8 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer { return this.serializeVaultGeneric(...arguments); case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE: return this.serializeVaultSshCertificate(...arguments); + case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP: + return this.serializeVaultLdap(...arguments); default: return super.serialize(...arguments); } @@ -44,21 +47,7 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer { if (!serialized.attributes?.http_method?.match(/post/i)) { serialized.attributes.http_request_body = null; } - const { credential_type, credential_mapping_overrides } = serialized; - - // API expects to send null to fields if it is undefined or deleted - if (credential_mapping_overrides && !isNew) { - serialized.credential_mapping_overrides = options.mapping_overrides[ - credential_type - ].reduce((obj, key) => { - if (credential_mapping_overrides[key]) { - obj[key] = credential_mapping_overrides[key]; - } else { - obj[key] = null; - } - return obj; - }, {}); - } + this.handleCredentialMappingOverrides(serialized, isNew); return serialized; } } @@ -74,4 +63,36 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer { return serialized; } } + + serializeVaultLdap(snapshot) { + const { isNew } = snapshot; + const serialized = super.serialize(...arguments); + if (serialized.attributes) { + this.handleCredentialMappingOverrides(serialized, isNew); + return serialized; + } + } + + /** + * Handle credential mapping overrides for serialization. + * @param {Object} serialized - The serialized object. + * @param {boolean} isNew - Indicates if the record is new. + */ + handleCredentialMappingOverrides(serialized, isNew) { + const { credential_type, credential_mapping_overrides } = serialized; + + // API expects to send null to fields if it is undefined or deleted + if (credential_mapping_overrides && !isNew) { + serialized.credential_mapping_overrides = options.mapping_overrides[ + credential_type + ].reduce((obj, key) => { + if (credential_mapping_overrides[key]) { + obj[key] = credential_mapping_overrides[key]; + } else { + obj[key] = null; + } + return obj; + }, {}); + } + } } diff --git a/addons/api/mirage/factories/credential-library.js b/addons/api/mirage/factories/credential-library.js index 5c3d02470..05f7ef878 100644 --- a/addons/api/mirage/factories/credential-library.js +++ b/addons/api/mirage/factories/credential-library.js @@ -10,6 +10,7 @@ import generateId from '../helpers/id'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, TYPES_CREDENTIAL_LIBRARY, options, } from 'api/models/credential-library'; @@ -22,6 +23,9 @@ export default factory.extend({ if (this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC) { return options.credential_types[i % options.credential_types.length]; } + if (this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP) { + return options.credential_types[2]; // Only supports username, password & domain credential type + } }, authorized_actions: () => permissions.authorizedActionsFor('credential-library') || [ @@ -45,6 +49,10 @@ export default factory.extend({ key_bits: faker.number.int(999), path: faker.system.directoryPath(), }; + case TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP: + return { + path: faker.system.directoryPath(), + }; } }, }); diff --git a/addons/api/mirage/factories/credential-store.js b/addons/api/mirage/factories/credential-store.js index 8a2150bbf..c6941f688 100644 --- a/addons/api/mirage/factories/credential-store.js +++ b/addons/api/mirage/factories/credential-store.js @@ -58,7 +58,7 @@ export default factory.extend({ const { scope, type } = credentialStore; switch (type) { case 'vault': - server.createList('credential-library', 2, { + server.createList('credential-library', 3, { scope, credentialStore, }); diff --git a/addons/api/tests/unit/models/credential-library-test.js b/addons/api/tests/unit/models/credential-library-test.js index dc59bd598..612cd0411 100644 --- a/addons/api/tests/unit/models/credential-library-test.js +++ b/addons/api/tests/unit/models/credential-library-test.js @@ -5,7 +5,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library'; +import { + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, +} from 'api/models/credential-library'; module('Unit | Model | credential library', function (hooks) { setupTest(hooks); @@ -22,10 +26,18 @@ module('Unit | Model | credential library', function (hooks) { type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, }); const modelB = store.createRecord('credential-library', { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + }); + const modelC = store.createRecord('credential-library', { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }); + const modelD = store.createRecord('credential-library', { type: 'unknown', }); assert.false(modelA.isUnknown); - assert.true(modelB.isUnknown); + assert.false(modelB.isUnknown); + assert.false(modelC.isUnknown); + assert.true(modelD.isUnknown); }); }); diff --git a/addons/api/tests/unit/serializers/credential-library-test.js b/addons/api/tests/unit/serializers/credential-library-test.js index 94a645870..da7106a02 100644 --- a/addons/api/tests/unit/serializers/credential-library-test.js +++ b/addons/api/tests/unit/serializers/credential-library-test.js @@ -8,6 +8,7 @@ import { setupTest } from 'ember-qunit'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, } from 'api/models/credential-library'; module('Unit | Serializer | credential library', function (hooks) { @@ -109,6 +110,7 @@ module('Unit | Serializer | credential library', function (hooks) { let serializedVaultGenericRecord = vaultGenericRecord.serialize(); let serializedVaultSSHCertificateRecord = vaultSSHCertificateRecord.serialize(); + assert.deepEqual(serializedVaultGenericRecord, { attributes: { path: null, @@ -279,4 +281,82 @@ module('Unit | Serializer | credential library', function (hooks) { version: 1, }); }); + + test('it serializes vault-ldap correctly on create', function (assert) { + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('credential-library'); + const record = store.createRecord('credential-library', { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + name: 'Name', + description: 'Description', + path: '/vault/path', + version: 1, + credential_type: 'username_password_domain', + credential_mapping_overrides: [ + { key: 'username_attribute', value: 'user' }, + { key: 'domain_attribute', value: 'domain' }, + ], + }); + const snapshot = record._createSnapshot(); + const serializedRecord = serializer.serialize(snapshot); + + assert.deepEqual(serializedRecord, { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + credential_store_id: null, + name: 'Name', + description: 'Description', + version: 1, + credential_type: 'username_password_domain', + credential_mapping_overrides: { + username_attribute: 'user', + domain_attribute: 'domain', + }, + attributes: { + path: '/vault/path', + }, + }); + }); + + test('it serializes vault-ldap correctly on update', function (assert) { + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('credential-library'); + store.push({ + data: { + id: '1', + type: 'credential-library', + attributes: { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + name: 'Name', + description: 'Description', + path: '/vault/path', + version: 1, + credential_type: 'username_password_domain', + credential_mapping_overrides: [ + { key: 'username_attribute', value: 'user' }, + { key: 'domain_attribute', value: 'domain' }, + ], + }, + }, + }); + const record = store.peekRecord('credential-library', '1'); + const snapshot = record._createSnapshot(); + const serializedRecord = serializer.serialize(snapshot); + + assert.deepEqual(serializedRecord, { + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + credential_store_id: null, + name: 'Name', + description: 'Description', + version: 1, + credential_type: 'username_password_domain', + credential_mapping_overrides: { + username_attribute: 'user', + domain_attribute: 'domain', + password_attribute: null, + }, + attributes: { + path: '/vault/path', + }, + }); + }); }); diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 597784975..c0d78cb93 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -966,6 +966,7 @@ credential-library: types: vault-generic: Generic Secrets vault-ssh-certificate: SSH Certificates + vault-ldap: LDAP help: vault-generic: Authorize sessions using credentials brokered by Vault vault-ssh-certificate: Authorize sessions using the SSH Secrets Engine in Vault