[UI] Ember Data Migration - API Property Casing (#31325)

* updates api client vars to snake_case for custom messages

* updates api client vars to snake_case for tools

* updates api client vars to snake_case for sync

* updates api client vars to snake_case for secrets engine

* updates api client vars to snake_case for auth

* updates api client vars to snake_case for usage

* updates api client dep to point to gh repo

* fixes custom-messages service unit tests

* fixes configure-ssh test

* fixes configure-ssh test...again
This commit is contained in:
Jordan Reimer 2025-07-18 09:32:01 -06:00 committed by GitHub
parent 9190485ef6
commit 8700becc45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 907 additions and 844 deletions

View File

@ -121,8 +121,7 @@ export default ApplicationAdapter.extend({
});
},
mfaValidate(mfaRequirement) {
const { mfaRequestId: mfa_request_id, mfaConstraints: mfa_constraints } = mfaRequirement;
mfaValidate({ mfa_request_id, mfa_constraints }) {
const options = {
data: {
mfa_request_id,

View File

@ -19,16 +19,16 @@ export default class AuthMethodConfigurationComponent extends Component<Args> {
'description',
'accessor',
'local',
'sealWrap',
'config.listingVisibility',
'config.defaultLeaseTtl',
'config.maxLeaseTtl',
'config.tokenType',
'config.auditNonHmacRequestKeys',
'config.auditNonHmacResponseKeys',
'config.passthroughRequestHeaders',
'config.allowedResponseHeaders',
'config.pluginVersion',
'seal_wrap',
'config.listing_visibility',
'config.default_lease_ttl',
'config.max_lease_ttl',
'config.token_type',
'config.audit_non_hmac_request_keys',
'config.audit_non_hmac_response_keys',
'config.passthrough_request_headers',
'config.allowed_response_headers',
'config.plugin_version',
];
label = (field: string) => {
@ -37,24 +37,24 @@ export default class AuthMethodConfigurationComponent extends Component<Args> {
// map specific fields to custom labels
return (
{
listingVisibility: 'Use as preferred UI login method',
defaultLeaseTtl: 'Default Lease TTL',
maxLeaseTtl: 'Max Lease TTL',
auditNonHmacRequestKeys: 'Request keys excluded from HMACing in audit',
auditNonHmacResponseKeys: 'Response keys excluded from HMACing in audit',
passthroughRequestHeaders: 'Allowed passthrough request headers',
listing_visibility: 'Use as preferred UI login method',
default_lease_ttl: 'Default Lease TTL',
max_lease_ttl: 'Max Lease TTL',
audit_non_hmac_request_keys: 'Request keys excluded from HMACing in audit',
audit_non_hmac_response_keys: 'Response keys excluded from HMACing in audit',
passthrough_request_headers: 'Allowed passthrough request headers',
}[key] || label
);
};
value = (field: string) => {
const { method } = this.args;
if (field === 'config.listingVisibility') {
return method.config.listingVisibility === 'unauth';
if (field === 'config.listing_visibility') {
return method.config.listing_visibility === 'unauth';
}
return get(method, field);
};
isTtl = (field: string) => {
return ['config.defaultLeaseTtl', 'config.maxLeaseTtl'].includes(field);
return ['config.default_lease_ttl', 'config.max_lease_ttl'].includes(field);
};
}

View File

@ -13,12 +13,13 @@ import { POSSIBLE_FIELDS } from 'vault/utils/auth-form-helpers';
import { ResponseError } from '@hashicorp/vault-client-typescript';
import type { HTMLElementEvent } from 'vault/forms';
import type { LoginFields, NormalizedAuthData, NormalizeAuthResponseKeys } from 'vault/vault/auth/form';
import type { AuthResponseAuthKey, AuthResponseDataKey } from 'vault/vault/auth/methods';
import type { LoginFields, NormalizedAuthData, NormalizeAuthResponseKeys } from 'vault/auth/form';
import type { AuthResponseAuthKey, AuthResponseDataKey } from 'vault/auth/methods';
import type ApiService from 'vault/services/api';
import type ClusterModel from 'vault/models/cluster';
import type FlagsService from 'vault/services/flags';
import type VersionService from 'vault/services/version';
import type AuthService from 'vault/services/auth';
/**
* @module Auth::Base
@ -43,6 +44,7 @@ export default abstract class AuthBase extends Component<Args> {
@service declare readonly api: ApiService;
@service declare readonly flags: FlagsService;
@service declare readonly version: VersionService;
@service declare readonly auth: AuthService;
@action
onSubmit(event: HTMLElementEvent<HTMLFormElement>) {
@ -131,14 +133,13 @@ export default abstract class AuthBase extends Component<Args> {
authResponse: AuthResponseAuthKey | AuthResponseDataKey,
{ authMountPath, displayName, token, ttl }: NormalizeAuthResponseKeys
) => {
return {
// authResponse will include enforcement data in the `mfaRequirement` key - if MFA is configured.
...authResponse,
// authResponse will include enforcement data in the `mfa_requirement` key - if MFA is configured.
return this.auth.normalizeAuthData(authResponse, {
authMethodType: this.args.authType,
authMountPath,
displayName,
token,
ttl,
};
});
};
}

View File

@ -28,8 +28,8 @@ export default class AuthFormGithub extends AuthBase {
return this.normalizeAuthResponse(auth, {
authMountPath: path,
displayName,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
}

View File

@ -25,8 +25,8 @@ export default class AuthFormLdap extends AuthBase {
return this.normalizeAuthResponse(auth, {
authMountPath: path,
displayName: auth?.metadata?.username,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
}

View File

@ -107,7 +107,7 @@ export default class AuthFormOidcJwt extends AuthBase {
if (wait) await timeout(wait);
const { namespace = '', path = '', role = '' } = this.parseFormData(this._formData);
const redirectUri = this.generateRedirectUri(namespace, path);
const redirect_uri = this.generateRedirectUri(namespace, path);
// reset state
this.authUrl = null;
@ -116,9 +116,9 @@ export default class AuthFormOidcJwt extends AuthBase {
try {
const { data } = (await this.api.auth.jwtOidcRequestAuthorizationUrl(path, {
role,
redirectUri,
redirect_uri,
})) as JwtOidcAuthUrlResponse;
this.authUrl = data.authUrl;
this.authUrl = data.auth_url;
this.isOIDC = true;
} catch (e) {
const { status, message } = await this.api.parseError(e);
@ -158,8 +158,8 @@ export default class AuthFormOidcJwt extends AuthBase {
// displayName is not returned by auth response and is set in persistAuthData
return this.normalizeAuthResponse(auth, {
authMountPath: path,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
@ -175,8 +175,8 @@ export default class AuthFormOidcJwt extends AuthBase {
// displayName is not returned by auth response and is set in persistAuthData
return this.normalizeAuthResponse(auth, {
authMountPath: path,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
} finally {
this.closeWindow(oidcWindow);

View File

@ -42,8 +42,8 @@ export default class AuthFormOkta extends AuthBase {
return this.normalizeAuthResponse(auth, {
authMountPath: path,
displayName: auth?.metadata?.username,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
@ -67,7 +67,7 @@ export default class AuthFormOkta extends AuthBase {
async requestOktaVerify(nonce: string, mountPath: string) {
try {
const { data } = (await this.api.auth.oktaVerify(nonce, mountPath)) as OktaVerifyApiResponse;
return data.correctAnswer;
return data.correct_answer;
} catch (e) {
const { status, message } = await this.api.parseError(e);
if (status === 404) {

View File

@ -25,8 +25,8 @@ export default class AuthFormRadius extends AuthBase {
return this.normalizeAuthResponse(auth, {
authMountPath: path,
displayName: auth?.metadata?.username,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
}

View File

@ -20,9 +20,9 @@ import type { SamlLoginApiResponse, SamlSsoServiceUrlApiResponse } from 'vault/v
*/
interface SamlRole {
ssoServiceUrl: string;
tokenPollId: string;
clientVerifier: string;
sso_service_url: string;
token_poll_id: string;
client_verifier: string;
}
export default class AuthFormSaml extends AuthBase {
loginFields = [
@ -48,7 +48,7 @@ export default class AuthFormSaml extends AuthBase {
// either the default of auth type, or the custom inputted path
const { namespace, path, role } = formData;
const fetchedRole = await this.fetchSamlRole({ namespace, path, role });
const samlWindow = await this.startSAMLAuth(fetchedRole.ssoServiceUrl);
const samlWindow = await this.startSAMLAuth(fetchedRole.sso_service_url);
if (samlWindow) {
try {
// start watching the popup window and the current one
@ -60,8 +60,8 @@ export default class AuthFormSaml extends AuthBase {
// displayName is not included in auth response - it is set in persistAuthData
return this.normalizeAuthResponse(auth, {
authMountPath: path,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
} finally {
this.closeWindow(samlWindow);
@ -79,22 +79,22 @@ export default class AuthFormSaml extends AuthBase {
async fetchSamlRole({ namespace = '', path = '', role = '' }): Promise<SamlRole> {
// Create the client verifier and challenge
const verifier = uuid();
const clientChallenge = await this.generateClientChallenge(verifier);
const acsUrl = this.generateAcsUrl(path, namespace);
const clientType = SamlWriteSsoServiceUrlRequestClientTypeEnum.BROWSER; // 'browser'
const client_challenge = await this.generateClientChallenge(verifier);
const acs_url = this.generateAcsUrl(path, namespace);
const client_type = SamlWriteSsoServiceUrlRequestClientTypeEnum.BROWSER; // 'browser'
// Kick off the authentication flow by generating the SSO service URL
// It requires the client challenge generated from the verifier. We'll
// later provide the verifier to match up with the challenge on the server
// when we poll for the Vault token by its returned token poll ID.
const { data } = (await this.api.auth.samlWriteSsoServiceUrl(path, {
acsUrl,
clientChallenge,
clientType,
acs_url,
client_challenge,
client_type,
role,
})) as SamlSsoServiceUrlApiResponse;
return {
...data,
clientVerifier: verifier,
client_verifier: verifier,
};
}
@ -123,11 +123,11 @@ export default class AuthFormSaml extends AuthBase {
await timeout(WAIT_TIME);
try {
const { clientVerifier, tokenPollId } = fetchedRole;
const { client_verifier, token_poll_id } = fetchedRole;
// Exit loop if there's a response
return (await this.api.auth.samlWriteToken(path, {
clientVerifier,
tokenPollId,
client_verifier,
token_poll_id,
})) as SamlLoginApiResponse;
} catch (e) {
const { message, status } = await this.api.parseError(e);

View File

@ -26,8 +26,8 @@ export default class AuthFormUserpass extends AuthBase {
return this.normalizeAuthResponse(auth, {
authMountPath: path,
displayName: auth?.metadata?.username,
token: auth.clientToken,
ttl: auth.leaseDuration,
token: auth.client_token,
ttl: auth.lease_duration,
});
}
}

View File

@ -10,17 +10,17 @@
<:body as |B|>
<B.Tr>
<B.Td>API_ADDR</B.Td>
<B.Td data-test-vault-config-details="api_addr">{{or @vaultConfiguration.apiAddr "None"}}</B.Td>
<B.Td data-test-vault-config-details="api_addr">{{or @vaultConfiguration.api_addr "None"}}</B.Td>
</B.Tr>
<B.Tr>
<B.Td>Default lease TTL</B.Td>
<B.Td data-test-vault-config-details="default_lease_ttl">{{format-duration
@vaultConfiguration.defaultLeaseTtl
@vaultConfiguration.default_lease_ttl
}}</B.Td>
</B.Tr>
<B.Tr>
<B.Td>Max lease TTL</B.Td>
<B.Td data-test-vault-config-details="max_lease_ttl">{{format-duration @vaultConfiguration.maxLeaseTtl}}</B.Td>
<B.Td data-test-vault-config-details="max_lease_ttl">{{format-duration @vaultConfiguration.max_lease_ttl}}</B.Td>
</B.Tr>
<B.Tr>
<B.Td>TLS</B.Td>
@ -28,11 +28,11 @@
</B.Tr>
<B.Tr>
<B.Td>Log format</B.Td>
<B.Td data-test-vault-config-details="log_format">{{or @vaultConfiguration.logFormat "None"}}</B.Td>
<B.Td data-test-vault-config-details="log_format">{{or @vaultConfiguration.log_format "None"}}</B.Td>
</B.Tr>
<B.Tr>
<B.Td>Log level</B.Td>
<B.Td data-test-vault-config-details="log_level">{{@vaultConfiguration.logLevel}}</B.Td>
<B.Td data-test-vault-config-details="log_level">{{@vaultConfiguration.log_level}}</B.Td>
</B.Tr>
<B.Tr>
<B.Td>Storage type</B.Td>

View File

@ -18,11 +18,11 @@ import Component from '@glimmer/component';
export default class DashboardSecretsEnginesCard extends Component {
get tls() {
// since the default for tlsDisable is false it may not be in the config
// consider tls enabled if tlsDisable is undefined or false AND both tlsCertFile and tlsKeyFile are defined
// since the default for tls_disable is false it may not be in the config
// consider tls enabled if tls_disable is undefined or false AND both tls_cert_file and tls_key_file are defined
const tlsListener = this.args.vaultConfiguration?.listeners.find((listener) => {
const { tlsDisable, tlsCertFile, tlsKeyFile } = listener.config || {};
return !tlsDisable && tlsCertFile && tlsKeyFile;
const { tls_disable, tls_cert_file, tls_key_file } = listener.config || {};
return !tls_disable && tls_cert_file && tls_key_file;
});
return tlsListener ? 'Enabled' : 'Disabled';

View File

@ -47,7 +47,7 @@ export default class MfaForm extends Component {
}
get constraints() {
return this.args.authData.mfaRequirement.mfaConstraints;
return this.args.authData.mfaRequirement.mfa_constraints;
}
get multiConstraint() {
return this.constraints.length > 1;

View File

@ -89,7 +89,7 @@ export default class MountBackendForm extends Component<Args> {
typeChangeSideEffect(type: string) {
if (this.args.mountCategory !== 'secret') return;
// If type PKI, set max lease to ~10years
this.args.mountModel.config.maxLeaseTtl = type === 'pki' ? '3650d' : 0;
this.args.mountModel.config.max_lease_ttl = type === 'pki' ? '3650d' : 0;
}
checkModelValidity(model: MountModel) {
@ -114,16 +114,16 @@ export default class MountBackendForm extends Component<Args> {
}
async saveKvConfig(path: string, formData: SecretsEngineForm['data']) {
const { options, kvConfig = {} } = formData;
const { maxVersions, casRequired, deleteVersionAfter } = kvConfig;
const { options, kv_config = {} } = formData;
const { max_versions, cas_required, delete_version_after } = kv_config;
const isKvV2 = options?.version === 2 && ['kv', 'generic'].includes(this.args.mountModel.engineType);
const hasConfig = maxVersions || casRequired || deleteVersionAfter;
const hasConfig = max_versions || cas_required || delete_version_after;
if (isKvV2 && hasConfig) {
try {
const { canUpdate } = await this.capabilities.for('kvConfig', { path });
if (canUpdate) {
await this.api.secrets.kvV2Configure(path, kvConfig);
await this.api.secrets.kvV2Configure(path, kv_config);
} else {
this.flashMessages.warning(
'You do not have access to the config endpoint. The secret engine was mounted, but the configuration settings were not saved.'
@ -209,6 +209,6 @@ export default class MountBackendForm extends Component<Args> {
@action
handleIdentityTokenKeyChange(value: string[] | string): void {
// if array, it's coming from the search-select component, otherwise it hit the fallback component and will come in as a string.
this.args.mountModel.config.identityTokenKey = Array.isArray(value) ? value[0] : value;
this.args.mountModel.config.identity_token_key = Array.isArray(value) ? value[0] : value;
}
}

View File

@ -6,9 +6,9 @@
{{#if @config}}
{{#each this.displayFields as |field|}}
{{! public key while not sensitive when editing/creating, should be hidden by default on viewing }}
{{#if (eq field "publicKey")}}
<InfoTableRow @label="Public key" @value={{@config.publicKey}}>
<MaskedInput @value={{@config.publicKey}} @name={{field}} @displayOnly={{true}} @allowCopy={{true}} />
{{#if (eq field "public_key")}}
<InfoTableRow @label="Public key" @value={{@config.public_key}}>
<MaskedInput @value={{@config.public_key}} @name={{field}} @displayOnly={{true}} @allowCopy={{true}} />
</InfoTableRow>
{{else}}
<InfoTableRow

View File

@ -15,33 +15,40 @@ type Args = {
export default class ConfigurationDetails extends Component<Args> {
awsFields = [
'roleArn',
'identityTokenAudience',
'identityTokenTtl',
'accessKey',
'role_arn',
'identity_token_audience',
'identity_token_ttl',
'access_key',
'region',
'iamEndpoint',
'stsEndpoint',
'maxRetries',
'iam_endpoint',
'sts_endpoint',
'max_retries',
'lease',
'leaseMax',
'lease_max',
'issuer',
];
azureFields = [
'subscriptionId',
'tenantId',
'clientId',
'identityTokenAudience',
'identityTokenTtl',
'rootPasswordTtl',
'subscription_id',
'tenant_id',
'client_id',
'identity_token_audience',
'identity_token_ttl',
'root_password_ttl',
'environment',
'issuer',
];
gcpFields = ['serviceAccountEmail', 'ttl', 'maxTtl', 'identityTokenAudience', 'identityTokenTtl', 'issuer'];
gcpFields = [
'service_account_email',
'ttl',
'max_ttl',
'identity_token_audience',
'identity_token_ttl',
'issuer',
];
sshFields = ['publicKey', 'generateSigningKey'];
sshFields = ['public_key', 'generate_signing_key'];
get displayFields() {
switch (this.args.typeDisplay) {
@ -72,13 +79,15 @@ export default class ConfigurationDetails extends Component<Args> {
return (
{
lease: 'Default Lease TTL',
leaseMax: 'Max Lease TTL',
lease_max: 'Max Lease TTL',
ttl: 'Config TTL',
}[field] || formattedLabel
);
};
isDuration = (field: string) => {
return ['identityTokenTtl', 'rootPasswordTtl', 'lease', 'leaseMax', 'ttl', 'maxTtl'].includes(field);
return ['identity_token_ttl', 'root_password_ttl', 'lease', 'lease_max', 'ttl', 'max_ttl'].includes(
field
);
};
}

View File

@ -47,9 +47,9 @@
</label>
<div class="control">
<MaskedInput
@name="publickey"
@name="publicKey"
@id="publicKey"
@value={{@configForm.publicKey}}
@value={{@configForm.public_key}}
@displayOnly={{true}}
@allowCopy={{true}}
data-test-input="public-key"
@ -59,7 +59,7 @@
<Hds::ButtonSet>
<Hds::Copy::Button
@text="Copy"
@textToCopy={{@configForm.publicKey}}
@textToCopy={{@configForm.public_key}}
@onError={{fn (set-flash-message "Clipboard copy failed. The Clipboard API requires a secure context." "danger")}}
class="primary"
/>

View File

@ -151,8 +151,8 @@ export default class ConfigureWif extends Component<Args> {
if (type === 'aws') {
await this.api.secrets.awsConfigureRootIamCredentials(backendPath, data);
try {
const { lease, leaseMax } = data as { lease: string; leaseMax: string };
await this.api.secrets.awsConfigureLease(backendPath, { lease, leaseMax });
const { lease, lease_max } = data as { lease: string; lease_max: string };
await this.api.secrets.awsConfigureLease(backendPath, { lease, lease_max });
} catch (e) {
const { message } = await this.api.parseError(e);
this.flashMessages.danger(`Error saving lease configuration: ${message}`);
@ -182,14 +182,14 @@ export default class ConfigureWif extends Component<Args> {
if (accessType === 'account') {
// reset all "wif" attributes that are mutually exclusive with "account" attributes
// these attributes are the same for each engine
configForm.data.identityTokenAudience = configForm.data.identityTokenTtl = undefined;
configForm.data.identity_token_audience = configForm.data.identity_token_ttl = undefined;
} else if (accessType === 'wif') {
// reset all "account" attributes that are mutually exclusive with "wif" attributes
// these attributes are different for each engine
if (type === 'azure') {
(configForm as AzureConfigForm).data.clientSecret = undefined;
(configForm as AzureConfigForm).data.client_secret = undefined;
} else if (type === 'aws') {
(configForm as AwsConfigForm).data.accessKey = undefined;
(configForm as AwsConfigForm).data.access_key = undefined;
}
}
}

View File

@ -83,7 +83,7 @@
</div>
<code class="has-text-grey is-size-8" data-test-engine-accessor>
{{backend.accessor}}
{{backend.runningPluginVersion}}
{{backend.running_plugin_version}}
</code>
{{#if backend.description}}
<ReadMore>

View File

@ -14,7 +14,7 @@
{{#if this.lookupData}}
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
{{#each-in this.lookupData as |key value|}}
{{#let (if (eq key "creationTtl") "Creation TTL" (to-label key)) as |label|}}
{{#let (if (eq key "creation_ttl") "Creation TTL" (to-label key)) as |label|}}
<InfoTableRow @label={{label}} @value={{value}} />
{{/let}}
{{/each-in}}

View File

@ -38,10 +38,10 @@ export default class ToolsLookup extends Component {
get expirationDate() {
if (this.lookupData) {
const { creationTime, creationTtl } = this.lookupData;
if (creationTime && creationTtl) {
const { creation_time, creation_ttl } = this.lookupData;
if (creation_time && creation_ttl) {
// returns new Date with seconds added.
return addSeconds(creationTime, Number(creationTtl));
return addSeconds(creation_time, Number(creation_ttl));
}
}
return null;

View File

@ -47,8 +47,8 @@ export default class ToolsRandom extends Component {
evt.preventDefault();
const data = { bytes: Number(this.bytes), format: this.format };
try {
const { randomBytes } = await this.api.sys.generateRandom(data);
this.randomBytes = randomBytes || '';
const { random_bytes } = await this.api.sys.generateRandom(data);
this.randomBytes = random_bytes || '';
this.flashMessages.success('Generated random bytes successfully.');
} catch (error) {
const { message } = await this.api.parseError(error);

View File

@ -41,8 +41,8 @@ export default class ToolsRewrap extends Component {
const data = { token: this.originalToken.trim() };
try {
const { wrapInfo } = await this.api.sys.rewrap(data);
this.rewrappedToken = wrapInfo?.token || '';
const { wrap_info } = await this.api.sys.rewrap(data);
this.rewrappedToken = wrap_info?.token || '';
this.flashMessages.success('Rewrap was successful.');
} catch (error) {
const { message } = await this.api.parseError(error);

View File

@ -46,10 +46,10 @@ export default class ToolsUnwrap extends Component {
const resp = await this.api.sys.unwrap(data);
this.unwrapData = (resp && resp.data) || resp.auth;
this.unwrapDetails = {
'Request ID': resp.requestId,
'Lease ID': resp.leaseId || 'None',
'Request ID': resp.request_id,
'Lease ID': resp.lease_id || 'None',
Renewable: resp.renewable,
'Lease Duration': resp.leaseDuration || 'None',
'Lease Duration': resp.lease_duration || 'None',
};
this.flashMessages.success('Unwrap was successful.');
} catch (error) {

View File

@ -82,8 +82,8 @@ export default class ToolsWrap extends Component {
const wrap = this.wrapTTL || '';
try {
const { wrapInfo } = await this.api.sys.wrap(data, this.api.buildHeaders({ wrap }));
this.token = wrapInfo?.token || '';
const { wrap_info } = await this.api.sys.wrap(data, this.api.buildHeaders({ wrap }));
this.token = wrap_info?.token || '';
this.flashMessages.success('Wrap was successful.');
} catch (error) {
const { message } = await this.api.parseError(error);

View File

@ -9,6 +9,7 @@ import { service } from '@ember/service';
import type FlagsService from 'vault/services/flags';
import type ApiService from 'vault/services/api';
import type { getUsageDataFunction, UsageDashboardData } from '@hashicorp/vault-reporting/types/index';
import type { UtilizationReport } from 'vault/usage';
/**
* @module UsagePage
@ -35,41 +36,38 @@ export default class UsagePage extends Component {
* We should get typescript errors if top level interfaces in the API client or
* the vault-reporting addon change.
*/
const response = await this.api.sys.generateUtilizationReport();
const leaseCountQuotas = response.leaseCountQuotas as UsageDashboardData['leaseCountQuotas'];
const replicationStatus = response.replicationStatus as UsageDashboardData['replicationStatus'];
const pki = response.pki as UsageDashboardData['pki'];
const secretSync = response.secretSync as UsageDashboardData['secretSync'];
const response = (await this.api.sys.generateUtilizationReport()) as UtilizationReport;
const { lease_count_quotas, replication_status, pki, secret_sync } = response;
const data: UsageDashboardData = {
authMethods: (response.authMethods as Record<string, number>) || {},
secretEngines: (response.secretEngines as Record<string, number>) || {},
leasesByAuthMethod: (response.leasesByAuthMethod as Record<string, number>) || {},
authMethods: (response.auth_methods as Record<string, number>) || {},
secretEngines: (response.secret_engines as Record<string, number>) || {},
leasesByAuthMethod: (response.leases_by_auth_method as Record<string, number>) || {},
leaseCountQuotas: {
globalLeaseCountQuota: {
capacity: leaseCountQuotas?.globalLeaseCountQuota?.capacity || 0,
count: leaseCountQuotas?.globalLeaseCountQuota?.count || 0,
name: leaseCountQuotas?.globalLeaseCountQuota?.name || '',
capacity: lease_count_quotas?.global_lease_count_quota?.capacity || 0,
count: lease_count_quotas?.global_lease_count_quota?.count || 0,
name: lease_count_quotas?.global_lease_count_quota?.name || '',
},
totalLeaseCountQuotas: leaseCountQuotas?.totalLeaseCountQuotas || 0,
totalLeaseCountQuotas: lease_count_quotas?.total_lease_count_quotas || 0,
},
replicationStatus: {
drState: replicationStatus?.drState || 'disabled',
prState: replicationStatus?.prState || 'disabled',
drPrimary: replicationStatus?.drPrimary ?? false,
prPrimary: replicationStatus?.prPrimary ?? false,
drState: replication_status?.dr_state || 'disabled',
prState: replication_status?.pr_state || 'disabled',
drPrimary: replication_status?.dr_primary ?? false,
prPrimary: replication_status?.pr_primary ?? false,
},
kvv1Secrets: response.kvv1Secrets || 0,
kvv2Secrets: response.kvv2Secrets || 0,
kvv1Secrets: response.kvv1_secrets || 0,
kvv2Secrets: response.kvv2_secrets || 0,
namespaces: response.namespaces || 0,
pki: {
totalIssuers: pki?.totalIssuers || 0,
totalRoles: pki?.totalRoles || 0,
totalIssuers: pki?.total_issuers || 0,
totalRoles: pki?.total_roles || 0,
},
secretSync: {
totalDestinations: secretSync?.totalDestinations || 0,
totalDestinations: secret_sync?.total_destinations || 0,
},
};
return data as UsageDashboardData;
return data;
};
}

View File

@ -10,24 +10,24 @@ import engineDisplayData from 'vault/helpers/engines-display-data';
export default class SecretsBackendConfigurationController extends Controller {
get displayFields() {
const { engineType } = this.model.secretsEngine;
const fields = ['type', 'path', 'description', 'accessor', 'local', 'sealWrap'];
const fields = ['type', 'path', 'description', 'accessor', 'local', 'seal_wrap'];
// no ttl options for keymgmt
if (engineType !== 'keymgmt') {
fields.push('config.defaultLeaseTtl', 'config.maxLeaseTtl');
fields.push('config.default_lease_ttl', 'config.max_lease_ttl');
}
fields.push(
'config.allowedManagedKeys',
'config.auditNonHmacRequestKeys',
'config.auditNonHmacResponseKeys',
'config.passthroughRequestHeaders',
'config.allowedResponseHeaders'
'config.allowed_managed_keys',
'config.audit_non_hmac_request_keys',
'config.audit_non_hmac_response_keys',
'config.passthrough_request_headers',
'config.allowed_response_headers'
);
if (engineType === 'kv' || engineType === 'generic') {
fields.push('version');
}
// For WIF Secret engines, allow users to set the identity token key when mounting the engine.
if (engineDisplayData(engineType)?.isWIF) {
fields.push('config.identityTokenKey');
fields.push('config.identity_token_key');
}
return fields;
}
@ -38,11 +38,11 @@ export default class SecretsBackendConfigurationController extends Controller {
// map specific fields to custom labels
return (
{
defaultLeaseTtl: 'Default Lease TTL',
maxLeaseTtl: 'Max Lease TTL',
auditNonHmacRequestKeys: 'Request keys excluded from HMACing in audit',
auditNonHmacResponseKeys: 'Response keys excluded from HMACing in audit',
passthroughRequestHeaders: 'Allowed passthrough request headers',
default_lease_ttl: 'Default Lease TTL',
max_lease_ttl: 'Max Lease TTL',
audit_non_hmac_request_keys: 'Request keys excluded from HMACing in audit',
audit_non_hmac_response_keys: 'Response keys excluded from HMACing in audit',
passthrough_request_headers: 'Allowed passthrough request headers',
}[key] || label
);
};

View File

@ -65,13 +65,13 @@ export default class CustomMessageForm extends Form<CustomMessageFormData> {
allowWhiteSpace: true,
}),
new FormField('startTime', 'dateTimeLocal', {
new FormField('start_time', 'dateTimeLocal', {
editType: 'dateTimeLocal',
label: 'Message starts',
subText: 'Defaults to 12:00 a.m. the following day (local timezone).',
}),
new FormField('endTime', 'dateTimeLocal', {
new FormField('end_time', 'dateTimeLocal', {
editType: 'yield',
label: 'Message expires',
}),
@ -91,20 +91,20 @@ export default class CustomMessageForm extends Form<CustomMessageFormData> {
message: 'Link title and url are required.',
},
],
startTime: [
start_time: [
{
validator({ startTime, endTime }: CustomMessageFormData) {
if (!startTime || !endTime) return true;
return isBefore(new Date(startTime), new Date(endTime));
validator({ start_time, end_time }: CustomMessageFormData) {
if (!start_time || !end_time) return true;
return isBefore(new Date(start_time), new Date(end_time));
},
message: 'Start time is after end time.',
},
],
endTime: [
end_time: [
{
validator({ startTime, endTime }: CustomMessageFormData) {
if (!startTime || !endTime) return true;
return isAfter(new Date(endTime), new Date(startTime));
validator({ start_time, end_time }: CustomMessageFormData) {
if (!start_time || !end_time) return true;
return isAfter(new Date(end_time), new Date(start_time));
},
message: 'End time is before start time.',
},
@ -114,10 +114,10 @@ export default class CustomMessageForm extends Form<CustomMessageFormData> {
toJSON() {
// overriding to do some date serialization
// form sets dates as strings but client expects Date objects
const startTime = this.data.startTime ? new Date(this.data.startTime as unknown as string) : undefined;
const endTime = this.data.endTime ? new Date(this.data.endTime as unknown as string) : undefined;
const start_time = this.data.start_time ? new Date(this.data.start_time as unknown as string) : undefined;
const end_time = this.data.end_time ? new Date(this.data.end_time as unknown as string) : undefined;
// encode message to base64
const message = this.data.message ? encodeString(this.data.message) : undefined;
return super.toJSON({ ...this.data, startTime, endTime, message });
return super.toJSON({ ...this.data, start_time, end_time, message });
}
}

View File

@ -16,8 +16,8 @@ export default class AwsConfigForm extends WifConfigForm<AwsConfigFormData> {
lease: [
{
validator(data: AwsConfigForm['data']) {
const { lease, leaseMax } = data;
return (lease && leaseMax) || (!lease && !leaseMax) ? true : false;
const { lease, lease_max } = data;
return (lease && lease_max) || (!lease && !lease_max) ? true : false;
},
message: 'Lease TTL and Max Lease TTL are both required if one of them is set.',
},
@ -25,17 +25,17 @@ export default class AwsConfigForm extends WifConfigForm<AwsConfigFormData> {
};
get isAccountPluginConfigured() {
return !!this.data.accessKey;
return !!this.data.access_key;
}
get isWifPluginConfigured() {
const { identityTokenAudience, identityTokenTtl, roleArn } = this.data;
return !!identityTokenAudience || !!identityTokenTtl || !!roleArn;
const { identity_token_audience, identity_token_ttl, role_arn } = this.data;
return !!identity_token_audience || !!identity_token_ttl || !!role_arn;
}
accountFields = [
new FormField('accessKey', 'string'),
new FormField('secretKey', 'string', { sensitive: true }),
new FormField('access_key', 'string'),
new FormField('secret_key', 'string', { sensitive: true }),
];
optionFields = [
@ -44,21 +44,21 @@ export default class AwsConfigForm extends WifConfigForm<AwsConfigFormData> {
subText:
'Specifies the AWS region. If not set it will use the AWS_REGION env var, AWS_DEFAULT_REGION env var, or us-east-1 in that order.',
}),
new FormField('iamEndpoint', 'string', { label: 'IAM endpoint' }),
new FormField('stsEndpoint', 'string', { label: 'STS endpoint' }),
new FormField('maxRetries', 'number', {
new FormField('iam_endpoint', 'string', { label: 'IAM endpoint' }),
new FormField('sts_endpoint', 'string', { label: 'STS endpoint' }),
new FormField('max_retries', 'number', {
subText: 'Number of max retries the client should use for recoverable errors. Default is -1.',
}),
];
wifFields = [
this.commonWifFields.issuer,
new FormField('roleArn', 'string', {
new FormField('role_arn', 'string', {
label: 'Role ARN',
subText: 'Role ARN to assume for plugin workload identity federation.',
}),
this.commonWifFields.identityTokenAudience,
this.commonWifFields.identityTokenTtl,
this.commonWifFields.identity_token_audience,
this.commonWifFields.identity_token_ttl,
];
// formFieldGroups will render the default and root config option fields
@ -77,7 +77,7 @@ export default class AwsConfigForm extends WifConfigForm<AwsConfigFormData> {
label: 'Default Lease TTL',
editType: 'ttl',
}),
new FormField('leaseMax', 'string', {
new FormField('lease_max', 'string', {
label: 'Max Lease TTL',
editType: 'ttl',
}),

View File

@ -15,15 +15,15 @@ export default class AzureConfigForm extends WifConfigForm<AzureConfigFormData>
isAccountPluginConfigured = false;
get isWifPluginConfigured() {
const { identityTokenAudience, identityTokenTtl } = this.data;
return !!identityTokenAudience || !!identityTokenTtl;
const { identity_token_audience, identity_token_ttl } = this.data;
return !!identity_token_audience || !!identity_token_ttl;
}
accountFields = [
new FormField('subscriptionId', 'string', { label: 'Subscription ID' }),
new FormField('tenantId', 'string', { label: 'Tenant ID' }),
new FormField('clientId', 'string', { label: 'Client ID' }),
new FormField('clientSecret', 'string', { sensitive: true }),
new FormField('subscription_id', 'string', { label: 'Subscription ID' }),
new FormField('tenant_id', 'string', { label: 'Tenant ID' }),
new FormField('client_id', 'string', { label: 'Client ID' }),
new FormField('client_secret', 'string', { sensitive: true }),
];
optionFields = [
@ -31,7 +31,7 @@ export default class AzureConfigForm extends WifConfigForm<AzureConfigFormData>
subText:
'This value can also be provided with the AZURE_ENVIRONMENT environment variable. If not specified, Vault will use Azure Public Cloud.',
}),
new FormField('rootPasswordTtl', 'string', {
new FormField('root_password_ttl', 'string', {
label: 'Root password TTL',
editType: 'ttl',
// default is 15768000 sec. The api docs say 182 days, but this should be updated to 182.5 days.
@ -46,8 +46,8 @@ export default class AzureConfigForm extends WifConfigForm<AzureConfigFormData>
this.accountFields[0] as FormField,
this.accountFields[1] as FormField,
this.accountFields[2] as FormField,
this.commonWifFields.identityTokenAudience,
this.commonWifFields.identityTokenTtl,
this.commonWifFields.identity_token_audience,
this.commonWifFields.identity_token_ttl,
];
get formFieldGroups() {

View File

@ -25,7 +25,7 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
level: 'warn',
},
],
'kvConfig.maxVersions': [
'kv_config.max_versions': [
{ type: 'number', message: 'Maximum versions must be a number.' },
{ type: 'length', options: { min: 1, max: 16 }, message: 'You cannot go over 16 characters.' },
],
@ -34,7 +34,7 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
get coreOptionFields() {
return [
new FormField('description', 'string', { editType: 'textarea' }),
new FormField('config.listingVisibility', 'boolean', {
new FormField('config.listing_visibility', 'boolean', {
label: 'Use as preferred UI login method',
editType: 'toggleButton',
helperTextEnabled:
@ -46,7 +46,7 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
helpText:
'When Replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
}),
new FormField('sealWrap', 'boolean', {
new FormField('seal_wrap', 'boolean', {
helpText:
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For KV mounts, all values will be seal wrapped.) This can only be specified at mount time.',
}),
@ -55,9 +55,9 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
get leaseConfigFields() {
return [
new FormField('config.defaultLeaseTtl', 'string', { label: 'Default Lease TTL', editType: 'ttl' }),
new FormField('config.maxLeaseTtl', 'string', { label: 'Max Lease TTL', editType: 'ttl' }),
new FormField('config.allowedManagedKeys', 'string', {
new FormField('config.default_lease_ttl', 'string', { label: 'Default Lease TTL', editType: 'ttl' }),
new FormField('config.max_lease_ttl', 'string', { label: 'Max Lease TTL', editType: 'ttl' }),
new FormField('config.allowed_managed_keys', 'string', {
label: 'Allowed managed keys',
editType: 'stringArray',
}),
@ -66,22 +66,22 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
get standardConfigFields() {
return [
new FormField('config.auditNonHmacRequestKeys', 'string', {
new FormField('config.audit_non_hmac_request_keys', 'string', {
label: 'Request keys excluded from HMACing in audit',
editType: 'stringArray',
helpText: "Keys that will not be HMAC'd by audit devices in the request data object.",
}),
new FormField('config.auditNonHmacResponseKeys', 'string', {
new FormField('config.audit_non_hmac_response_keys', 'string', {
label: 'Response keys excluded from HMACing in audit',
editType: 'stringArray',
helpText: "Keys that will not be HMAC'd by audit devices in the response data object.",
}),
new FormField('config.passthroughRequestHeaders', 'string', {
new FormField('config.passthrough_request_headers', 'string', {
label: 'Allowed passthrough request headers',
helpText: 'Headers to allow and pass from the request to the backend',
editType: 'stringArray',
}),
new FormField('config.allowedResponseHeaders', 'string', {
new FormField('config.allowed_response_headers', 'string', {
label: 'Allowed response headers',
helpText: 'Headers to allow, allowing a plugin to include them in the response.',
editType: 'stringArray',
@ -97,17 +97,17 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
const fields = [new FormField('path', 'string')];
if (this.engineType === 'kv') {
fields.push(
new FormField('kvConfig.maxVersions', 'number', {
new FormField('kv_config.max_versions', 'number', {
label: 'Maximum number of versions',
subText:
'The number of versions to keep per key. Once the number of keys exceeds the maximum number set here, the oldest version will be permanently deleted. This value applies to all keys, but a keys metadata settings can overwrite this value. When 0 is used or the value is unset, Vault will keep 10 versions.',
}),
new FormField('kvConfig.casRequired', 'boolean', {
new FormField('kv_config.cas_required', 'boolean', {
label: 'Require Check and Set',
subText:
'If checked, all keys will require the cas parameter to be set on all write requests. A keys metadata settings can overwrite this value.',
}),
new FormField('kvConfig.deleteVersionAfter', 'string', {
new FormField('kv_config.delete_version_after', 'string', {
editType: 'ttl',
label: 'Automate secret deletion',
helperTextDisabled: 'A secrets version must be manually deleted.',
@ -139,7 +139,7 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
...this.coreOptionFields,
defaultTtl,
maxTtl,
new FormField('config.identityTokenKey', undefined, {
new FormField('config.identity_token_key', undefined, {
label: 'Identity token key',
subText: `A named key to sign tokens. If not provided, this will default to Vault's OIDC default key.`,
editType: 'yield',
@ -178,8 +178,8 @@ export default class SecretsEngineForm extends Form<SecretsEngineFormData> {
...this.data,
config: {
...(config || {}),
forceNoCache: config?.forceNoCache ?? false,
listingVisibility: config?.listingVisibility ? 'unauth' : 'hidden',
force_no_cache: config?.force_no_cache ?? false,
listing_visibility: config?.listing_visibility ? 'unauth' : 'hidden',
},
};
// options are only relevant for kv/generic engines

View File

@ -16,8 +16,8 @@ export default class AzureConfigForm extends WifConfigForm<GcpConfigFormData> {
isAccountPluginConfigured = false;
get isWifPluginConfigured() {
const { identityTokenAudience, identityTokenTtl, serviceAccountEmail } = this.data;
return !!identityTokenAudience || !!identityTokenTtl || !!serviceAccountEmail;
const { identity_token_audience, identity_token_ttl, service_account_email } = this.data;
return !!identity_token_audience || !!identity_token_ttl || !!service_account_email;
}
accountFields = [
@ -38,7 +38,7 @@ export default class AzureConfigForm extends WifConfigForm<GcpConfigFormData> {
helperTextEnabled:
'The default config TTL (time-to-live) for long-lived credentials (i.e. service account keys).',
}),
new FormField('maxTtl', 'string', {
new FormField('max_ttl', 'string', {
label: 'Max TTL',
editType: 'ttl',
helperTextDisabled:
@ -50,11 +50,11 @@ export default class AzureConfigForm extends WifConfigForm<GcpConfigFormData> {
wifFields = [
this.commonWifFields.issuer,
this.commonWifFields.identityTokenAudience,
new FormField('serviceAccountEmail', 'string', {
this.commonWifFields.identity_token_audience,
new FormField('service_account_email', 'string', {
subText: 'Email ID for the Service Account to impersonate for Workload Identity Federation.',
}),
this.commonWifFields.identityTokenTtl,
this.commonWifFields.identity_token_ttl,
];
get formFieldGroups() {

View File

@ -11,12 +11,12 @@ import type { SshConfigureCaRequest } from '@hashicorp/vault-client-typescript';
export default class SshConfigForm extends Form<SshConfigureCaRequest> {
validations: Validations = {
generateSigningKey: [
generate_signing_key: [
{
validator(data: SshConfigForm['data']) {
const { publicKey, privateKey, generateSigningKey } = data;
const { public_key, private_key, generate_signing_key } = data;
// if generateSigningKey is false, both public and private keys are required
if (!generateSigningKey && (!publicKey || !privateKey)) {
if (!generate_signing_key && (!public_key || !private_key)) {
return false;
}
return true;
@ -24,12 +24,12 @@ export default class SshConfigForm extends Form<SshConfigureCaRequest> {
message: 'Provide a Public and Private key or set "Generate Signing Key" to true.',
},
],
publicKey: [
public_key: [
{
validator(data: SshConfigForm['data']) {
const { publicKey, privateKey } = data;
const { public_key, private_key } = data;
// regardless of generateSigningKey, if one key is set they both need to be set.
return publicKey || privateKey ? !!(publicKey && privateKey) : true;
return public_key || private_key ? !!(public_key && private_key) : true;
},
message: 'You must provide a Public and Private keys or leave both unset.',
},
@ -37,8 +37,8 @@ export default class SshConfigForm extends Form<SshConfigureCaRequest> {
};
formFields = [
new FormField('privateKey', 'string', { sensitive: true }),
new FormField('publicKey', 'string', { sensitive: true }),
new FormField('generateSigningKey', 'boolean'),
new FormField('private_key', 'string', { sensitive: true }),
new FormField('public_key', 'string', { sensitive: true }),
new FormField('generate_signing_key', 'boolean'),
];
}

View File

@ -22,12 +22,12 @@ export default class WifConfigForm<T extends object> extends Form<T> {
placeholder: 'https://vault-test.com',
}),
identityTokenAudience: new FormField('identityTokenAudience', 'string', {
identity_token_audience: new FormField('identity_token_audience', 'string', {
subText:
'The audience claim value for plugin identity tokens. Must match an allowed audience configured for the target IAM OIDC identity provider.',
}),
identityTokenTtl: new FormField('identityTokenTtl', 'string', {
identity_token_ttl: new FormField('identity_token_ttl', 'string', {
label: 'Identity token TTL',
helperTextDisabled:
'The TTL of generated tokens. Defaults to 1 hour, turn on the toggle to specify a different value.',
@ -35,7 +35,7 @@ export default class WifConfigForm<T extends object> extends Form<T> {
editType: 'ttl',
}),
serviceAccountEmail: new FormField('serviceAccountEmail', 'string', {
service_account_email: new FormField('service_account_email', 'string', {
subText: 'Email ID for the Service Account to impersonate for Workload Identity Federation.',
}),
};

View File

@ -23,26 +23,26 @@ export default class AwsSmForm extends Form<AwsSmFormData> {
'For AWS secrets manager, the name of the region must be supplied, something like “us-west-1.” If empty, Vault will use the AWS_REGION environment variable if configured.',
editDisabled: true,
}),
new FormField('roleArn', 'string', {
new FormField('role_arn', 'string', {
label: 'Role ARN',
subText:
'Specifies a role to assume when connecting to AWS. When assuming a role, Vault uses temporary STS credentials to authenticate.',
}),
new FormField('externalId', 'string', {
new FormField('external_id', 'string', {
label: 'External ID',
subText:
'Optional extra protection that must match the trust policy granting access to the AWS IAM role ARN. We recommend using a different random UUID per destination.',
}),
]),
new FormFieldGroup('Credentials', [
new FormField('accessKeyId', 'string', {
new FormField('access_key_id', 'string', {
label: 'Access key ID',
subText:
'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.',
sensitive: true,
noCopy: true,
}),
new FormField('secretAccessKey', 'string', {
new FormField('secret_access_key', 'string', {
label: 'Secret access key',
subText:
'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.',

View File

@ -18,13 +18,13 @@ export default class AzureKvForm extends Form<AzureKvFormData> {
formFieldGroups = [
new FormFieldGroup('default', [
commonFields.name,
new FormField('keyVaultUri', 'string', {
new FormField('key_vault_uri', 'string', {
label: 'Key Vault URI',
subText:
'URI of an existing Azure Key Vault instance. If empty, Vault will use the KEY_VAULT_URI environment variable if configured.',
editDisabled: true,
}),
new FormField('tenantId', 'string', {
new FormField('tenant_id', 'string', {
label: 'Tenant ID',
subText:
'ID of the target Azure tenant. If empty, Vault will use the AZURE_TENANT_ID environment variable if configured.',
@ -34,14 +34,14 @@ export default class AzureKvForm extends Form<AzureKvFormData> {
subText: 'Specifies a cloud for the client. The default is Azure Public Cloud.',
editDisabled: true,
}),
new FormField('clientId', 'string', {
new FormField('client_id', 'string', {
label: 'Client ID',
subText:
'Client ID of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_ID environment variable if configured.',
}),
]),
new FormFieldGroup('Credentials', [
new FormField('clientSecret', 'string', {
new FormField('client_secret', 'string', {
subText:
'Client secret of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_SECRET environment variable if configured.',
sensitive: true,

View File

@ -18,7 +18,7 @@ export default class GcpSmForm extends Form<GcpSmFormData> {
formFieldGroups = [
new FormFieldGroup('default', [
commonFields.name,
new FormField('projectId', 'string', {
new FormField('project_id', 'string', {
label: 'Project ID',
subText:
'The target project to manage secrets in. If set, overrides the project derived from the service account JSON credentials or application default credentials.',

View File

@ -18,19 +18,19 @@ export default class GcpSmForm extends Form<GhFormData> {
formFieldGroups = [
new FormFieldGroup('default', [
commonFields.name,
new FormField('repositoryOwner', 'string', {
new FormField('repository_owner', 'string', {
subText:
'Github organization or username that owns the repository. If empty, Vault will use the GITHUB_REPOSITORY_OWNER environment variable if configured.',
editDisabled: true,
}),
new FormField('repositoryName', 'string', {
new FormField('repository_name', 'string', {
subText:
'The name of the Github repository to connect to. If empty, Vault will use the GITHUB_REPOSITORY_NAME environment variable if configured.',
editDisabled: true,
}),
]),
new FormFieldGroup('Credentials', [
new FormField('accessToken', 'string', {
new FormField('access_token', 'string', {
subText:
'Personal access token to authenticate to the GitHub repository. If empty, Vault will use the GITHUB_ACCESS_TOKEN environment variable if configured.',
sensitive: true,

View File

@ -36,16 +36,16 @@ export default function destinationFormResolver(type: DestinationType, data = {}
return new GhForm(data, options, validations);
}
if (type === 'vercel-project') {
const teamId = (data as VercelProjectForm['data'])['teamId'];
validations['teamId'] = [
const teamId = (data as VercelProjectForm['data'])['team_id'];
validations['team_id'] = [
{
validator: (formData: VercelProjectForm['data']) =>
!options?.isNew && formData['teamId'] !== teamId ? false : true,
!options?.isNew && formData['team_id'] !== teamId ? false : true,
message: 'Team ID should only be updated if the project was transferred to another account.',
level: 'warn',
},
];
validations['deploymentEnvironments'] = [
validations['deployment_environments'] = [
{ type: 'presence', message: 'At least one environment is required.' },
];
return new VercelProjectForm(data, options, validations);

View File

@ -14,7 +14,7 @@ export const commonFields = {
editDisabled: true,
}),
secretNameTemplate: new FormField('secretNameTemplate', 'string', {
secretNameTemplate: new FormField('secret_name_template', 'string', {
subText:
'Go-template string that indicates how to format the secret name at the destination. The default template varies by destination type but is generally in the form of "vault-{{ .MountAccessor }}-{{ .SecretPath }}" e.g. "vault-kv_9a8f68ad-my-secret-1". Optional.',
}),
@ -38,7 +38,7 @@ export const commonFields = {
],
}),
customTags: new FormField('customTags', 'object', {
customTags: new FormField('custom_tags', 'object', {
subText:
'An optional set of informational key-value pairs added as additional metadata on secrets synced to this destination. Custom tags are merged with built-in tags.',
editType: 'kv',

View File

@ -18,23 +18,23 @@ export default class VercelProjectForm extends Form<VercelProjectFormData> {
formFieldGroups = [
new FormFieldGroup('default', [
commonFields.name,
new FormField('projectId', 'string', {
new FormField('project_id', 'string', {
label: 'Project ID',
subText: 'Project ID where to manage environment variables.',
editDisabled: true,
}),
new FormField('teamId', 'string', {
new FormField('team_id', 'string', {
label: 'Team ID',
subText: 'Team ID the project belongs to. Optional.',
}),
new FormField('deploymentEnvironments', 'string', {
new FormField('deployment_environments', 'string', {
subText: 'Deployment environments where the environment variables are available.',
editType: 'checkboxList',
possibleValues: ['development', 'preview', 'production'],
}),
]),
new FormFieldGroup('Credentials', [
new FormField('accessToken', 'string', {
new FormField('access_token', 'string', {
subText: 'Vercel API access token with the permissions to manage environment variables.',
sensitive: true,
noCopy: true,

View File

@ -80,13 +80,7 @@ export default class AuthRoute extends ClusterRouteBase {
async unwrapToken(token, clusterId) {
try {
const { auth } = await this.api.sys.unwrap({}, this.api.buildHeaders({ token }));
const authData = {
...auth,
authMethodType: 'token',
authMountPath: '',
token: auth.clientToken,
ttl: auth.leaseDuration,
};
const authData = this.auth.normalizeAuthData(auth, { authMethodType: 'token', authMountPath: '' });
return await this.auth.authSuccess(clusterId, authData);
} catch (e) {
const { message } = await this.api.parseError(e);

View File

@ -55,7 +55,7 @@ export default class SecretsBackendConfigurationRoute extends Route {
.catch(handleError);
const { data: configLease } = await this.api.secrets.awsReadLeaseConfiguration(path).catch(handleError);
const WIF_FIELDS = ['roleArn', 'identityTokenAudience', 'identityTokenTtl'];
const WIF_FIELDS = ['role_arn', 'identity_token_audience', 'identity_token_ttl'];
const issuer = await this.checkIssuer(configRoot, WIF_FIELDS);
if (configRoot) {
@ -66,7 +66,7 @@ export default class SecretsBackendConfigurationRoute extends Route {
async fetchAzureConfig(path) {
try {
const { data: azureConfig } = await this.api.secrets.azureReadConfiguration(path);
const WIF_FIELDS = ['identityTokenAudience', 'identityTokenTtl'];
const WIF_FIELDS = ['identity_token_audience', 'identity_token_ttl'];
const issuer = await this.checkIssuer(azureConfig, WIF_FIELDS);
// azure config endpoint returns 200 with default values if engine has not been configured yet
// all values happen to be falsy so we can just check if any are truthy
@ -87,7 +87,7 @@ export default class SecretsBackendConfigurationRoute extends Route {
async fetchGcpConfig(path) {
try {
const { data: gcpConfig } = await this.api.secrets.googleCloudReadConfiguration(path);
const WIF_FIELDS = ['identityTokenAudience', 'identityTokenTtl', 'serviceAccountEmail'];
const WIF_FIELDS = ['identity_token_audience', 'identity_token_ttl', 'service_account_email'];
const issuer = await this.checkIssuer(gcpConfig, WIF_FIELDS);
if (gcpConfig) {

View File

@ -43,7 +43,7 @@ export default class SecretsBackendConfigurationEdit extends Route {
}[type];
const defaults = {
ssh: { generateSigningKey: true, issuer: '' },
ssh: { generate_signing_key: true, issuer: '' },
}[type] || { issuer: '' };
// if the engine type is not configurable or a form class does not exist for the type return a 404.

View File

@ -17,11 +17,11 @@ export default class VaultClusterSettingsMountSecretBackendRoute extends Route {
model() {
const defaults = {
config: { listingVisibility: false },
kvConfig: {
maxVersions: 0,
casRequired: false,
deleteVersionAfter: undefined,
config: { listing_visibility: false },
kv_config: {
max_versions: 0,
cas_required: false,
delete_version_after: undefined,
},
options: { version: 2 },
};

View File

@ -421,13 +421,13 @@ export default Service.extend({
},
parseMfaResponse(mfaRequirement) {
// mfaRequirement response comes back in a shape that is not easy to work with
// mfa_requirement response comes back in a shape that is not easy to work with
// convert to array of objects and add necessary properties to satisfy the view
if (mfaRequirement) {
const { mfaRequestId, mfaConstraints } = mfaRequirement;
const { mfa_request_id, mfa_constraints } = mfaRequirement;
const constraints = [];
for (const key in mfaConstraints) {
const methods = mfaConstraints[key].any;
for (const key in mfa_constraints) {
const methods = mfa_constraints[key].any;
const isMulti = methods.length > 1;
// friendly label for display in MfaForm
@ -441,7 +441,7 @@ export default Service.extend({
selectedMethod: isMulti ? null : methods[0],
});
}
return { mfaRequestId, mfaConstraints: constraints };
return { mfa_request_id, mfa_constraints: constraints };
}
return {};
},
@ -484,7 +484,7 @@ export default Service.extend({
// Depending on where auth happens (mfa/validate, renew-self or the method's login) the auth data
// varies slightly (i.e. "ttl" vs "lease_duration"). Normalize it so stored authData contains consistent keys.
// (Also, the API service returns camel cased keys and raw ajax requests return snake cased params.)
normalizeAuthData(authData, { authMethodType, authMountPath, displayName }) {
normalizeAuthData(authData, { authMethodType, authMountPath, displayName, token, ttl }) {
const displayNameFromMetadata = (metadata) =>
metadata
? ['org', 'username']
@ -498,10 +498,11 @@ export default Service.extend({
authMountPath,
entityId: authData?.entity_id,
expireTime: authData?.expire_time,
token: authData?.client_token,
token: token || authData?.client_token,
renewable: authData?.renewable,
ttl: authData?.lease_duration,
ttl: ttl || authData?.lease_duration,
policies: authData?.policies,
mfaRequirement: authData?.mfa_requirement,
// not all methods return a display name or metadata, if this is still empty it will be gleaned from lookup-self
displayName: displayName || displayNameFromMetadata(authData?.metadata),
};

View File

@ -34,10 +34,10 @@ export default class CustomMessagesService extends Service {
try {
const type = this.auth.currentToken ? 'Authenticated' : 'Unauthenticated';
const method = `internalUiRead${type}ActiveCustomMessages`;
const { keys = [], keyInfo } = await this.api.sys[method]();
const { keys = [], key_info } = await this.api.sys[method]();
this.messages = keys.map((key) => {
const data = keyInfo[key];
const data = key_info[key];
return {
id: key,
...data,

View File

@ -31,7 +31,7 @@
{{#each this.displayFields as |field|}}
<InfoTableRow
@alwaysRender={{and (not (is-empty-value (get this.model.secretsEngine field))) (not-eq field "version")}}
@formatTtl={{includes field (array "config.defaultLeaseTtl" "config.maxLeaseTtl")}}
@formatTtl={{includes field (array "config.default_lease_ttl" "config.max_lease_ttl")}}
@label={{this.label field}}
@value={{get this.model.secretsEngine field}}
/>

View File

@ -10,7 +10,7 @@
id="never"
value="never"
@value="never"
@onChange={{fn (mut @message.endTime) ""}}
@onChange={{fn (mut @message.end_time) ""}}
@groupValue={{this.groupValue}}
/>
<label for="never" class="has-left-margin-xs has-text-black is-size-7">
@ -45,8 +45,8 @@
@type="datetime-local"
@value={{if this.messageEndTime (date-format this.messageEndTime this.datetimeLocalStringFormat) ""}}
class="input has-top-margin-xs is-auto-width {{if this.validationError 'has-error-border'}}"
name="endTime"
data-test-input="endTime"
name="end_time"
data-test-input="end_time"
{{on "focusout" this.onFocusOut}}
/>
</div>

View File

@ -26,9 +26,9 @@ export default class MessageExpirationDateForm extends Component {
constructor() {
super(...arguments);
if (this.args.message.endTime) {
if (this.args.message.end_time) {
this.groupValue = 'specificDate';
this.messageEndTime = this.args.message.endTime;
this.messageEndTime = this.args.message.end_time;
}
}
@ -41,13 +41,13 @@ export default class MessageExpirationDateForm extends Component {
@action
specificDateChange() {
this.groupValue = 'specificDate';
this.args.message.endTime = this.messageEndTime;
this.args.message.end_time = this.messageEndTime;
}
@action
onFocusOut(e) {
this.messageEndTime = e.target.value;
this.args.message.endTime = this.messageEndTime;
this.args.message.end_time = this.messageEndTime;
this.groupValue = 'specificDate';
}
}

View File

@ -46,8 +46,8 @@ export default class MessagesList extends Component {
get hasExpiredModalMessages() {
const modalMessages = this.args.messages?.filter((message) => message.type === 'modal') || [];
return modalMessages.every((message) => {
if (!message.endTime) return false;
return isAfter(timestamp.now(), new Date(message.endTime));
if (!message.end_time) return false;
return isAfter(timestamp.now(), new Date(message.end_time));
});
}

View File

@ -32,12 +32,12 @@
</Toolbar>
{{#each this.displayFields as |field|}}
{{#if (or (eq field "endTime") (eq field "startTime"))}}
{{! if the attr is an endTime and is falsy, we want to show a 'Never' text value }}
{{#if (or (eq field "end_time") (eq field "start_time"))}}
{{! if the attr is an end_time and is falsy, we want to show a 'Never' text value }}
<InfoTableRow
@label={{capitalize (humanize (dasherize field))}}
@value={{if
(and (eq field "endTime") (not (get @message field)))
(and (eq field "end_time") (not (get @message field)))
"Never"
(date-format (get @message field) "MMM d, yyyy hh:mm aaa" withTimeZone=true)
}}

View File

@ -25,7 +25,7 @@ export default class MessageDetails extends Component {
@service pagination;
@service api;
displayFields = ['active', 'type', 'authenticated', 'title', 'message', 'startTime', 'endTime', 'link'];
displayFields = ['active', 'type', 'authenticated', 'title', 'message', 'start_time', 'end_time', 'link'];
@action
async deleteMessage() {

View File

@ -35,7 +35,7 @@ export default class MessagesList extends Component {
@tracked messageToDelete = null;
isStartTimeAfterToday = (message) => {
return isAfter(message.startTime, timestamp.now());
return isAfter(message.start_time, timestamp.now());
};
get formattedMessages() {
@ -44,8 +44,8 @@ export default class MessagesList extends Component {
let badgeColor = 'neutral';
if (message.active) {
if (message.endTime) {
badgeDisplayText = `Active until ${dateFormat([message.endTime, 'MMM d, yyyy hh:mm aaa'], {
if (message.end_time) {
badgeDisplayText = `Active until ${dateFormat([message.end_time, 'MMM d, yyyy hh:mm aaa'], {
withTimeZone: true,
})}`;
} else {
@ -54,12 +54,12 @@ export default class MessagesList extends Component {
badgeColor = 'success';
} else {
if (this.isStartTimeAfterToday(message)) {
badgeDisplayText = `Scheduled: ${dateFormat([message.startTime, 'MMM d, yyyy hh:mm aaa'], {
badgeDisplayText = `Scheduled: ${dateFormat([message.start_time, 'MMM d, yyyy hh:mm aaa'], {
withTimeZone: true,
})}`;
badgeColor = 'highlight';
} else {
badgeDisplayText = `Inactive: ${dateFormat([message.startTime, 'MMM d, yyyy hh:mm aaa'], {
badgeDisplayText = `Inactive: ${dateFormat([message.start_time, 'MMM d, yyyy hh:mm aaa'], {
withTimeZone: true,
})}`;
badgeColor = 'neutral';

View File

@ -20,8 +20,8 @@ export default class MessagesCreateRoute extends Route {
async getMessages(authenticated) {
try {
const { keyInfo } = await this.api.sys.uiConfigListCustomMessages(true, undefined, authenticated);
return Object.values(keyInfo);
const { key_info } = await this.api.sys.uiConfigListCustomMessages(true, undefined, authenticated);
return Object.values(key_info);
} catch {
return [];
}
@ -33,7 +33,7 @@ export default class MessagesCreateRoute extends Route {
{
authenticated,
type: 'banner',
startTime: addDays(startOfDay(timestamp.now()), 1).toISOString(),
start_time: addDays(startOfDay(timestamp.now()), 1).toISOString(),
},
{ isNew: true }
);

View File

@ -37,22 +37,22 @@ export default class MessagesRoute extends Route {
}[status];
try {
const { keyInfo, keys } = await this.api.sys.uiConfigListCustomMessages(
const { key_info, keys } = await this.api.sys.uiConfigListCustomMessages(
true,
active,
authenticated,
type
);
// ids are in the keys array and can be mapped to the object in keyInfo
// map and set id property on keyInfo object
// ids are in the keys array and can be mapped to the object in key_info
// map and set id property on key_info object
const data = keys.map((id) => {
const { startTime, endTime, ...message } = keyInfo[id];
const { start_time, end_time, ...message } = key_info[id];
// dates returned from list endpoint are strings -- convert to date
return {
id,
...message,
startTime: startTime ? new Date(startTime) : startTime,
endTime: endTime ? new Date(endTime) : endTime,
start_time: start_time ? new Date(start_time) : start_time,
end_time: end_time ? new Date(end_time) : end_time,
};
});
const messages = paginate(data, {

View File

@ -14,14 +14,14 @@ export default class MessagesMessageEditRoute extends Route {
async model() {
const { id } = this.paramsFor('messages.message');
const data = await this.api.sys.uiConfigReadCustomMessage(id);
const { keyInfo, keys } = await this.api.sys.uiConfigListCustomMessages(
const { key_info, keys } = await this.api.sys.uiConfigListCustomMessages(
true,
undefined,
data.authenticated
);
return {
message: new CustomMessage({ ...data, message: decodeString(data.message) }),
messages: keys.map((id) => ({ ...keyInfo[id], id })),
messages: keys.map((id) => ({ ...key_info[id], id })),
};
}

View File

@ -66,7 +66,7 @@
@modelValidations={{@modelValidations}}
@showHelpText={{@showHelpText}}
>
{{#if (and (has-block "identityTokenKey") (eq attr.name "config.identityTokenKey"))}}
{{#if (and (has-block "identityTokenKey") (eq attr.name "config.identity_token_key"))}}
{{yield to="identityTokenKey"}}
{{/if}}
</FormField>

View File

@ -37,10 +37,10 @@ export default class SecretsEngineMountConfig extends Component<Args> {
{ label: 'Path', value: secretsEngine.path },
{ label: 'Accessor', value: secretsEngine.accessor },
{ label: 'Local', value: secretsEngine.local },
{ label: 'Seal wrap', value: secretsEngine.sealWrap },
{ label: 'Default Lease TTL', value: duration([secretsEngine.config.defaultLeaseTtl]) },
{ label: 'Max Lease TTL', value: duration([secretsEngine.config.maxLeaseTtl]) },
{ label: 'Identity token key', value: secretsEngine.config.identityTokenKey },
{ label: 'Seal wrap', value: secretsEngine.seal_wrap },
{ label: 'Default Lease TTL', value: duration([secretsEngine.config.default_lease_ttl]) },
{ label: 'Max Lease TTL', value: duration([secretsEngine.config.max_lease_ttl]) },
{ label: 'Identity token key', value: secretsEngine.config.identity_token_key },
];
}
}

View File

@ -19,7 +19,7 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
type: 'aws-sm',
icon: 'aws-color',
category: 'cloud',
maskedParams: ['accessKeyId', 'secretAccessKey'],
maskedParams: ['access_key_id', 'secret_access_key'],
readonlyParams: ['name', 'region'],
defaultValues: {
granularity: 'secret-path',
@ -30,8 +30,8 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
type: 'azure-kv',
icon: 'azure-color',
category: 'cloud',
maskedParams: ['clientSecret'],
readonlyParams: ['name', 'keyVaultUri', 'tenantId', 'cloud'],
maskedParams: ['client_secret'],
readonlyParams: ['name', 'key_vault_uri', 'tenant_id', 'cloud'],
defaultValues: {
granularity: 'secret-path',
},
@ -52,8 +52,8 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
type: 'gh',
icon: 'github-color',
category: 'dev-tools',
maskedParams: ['accessToken'],
readonlyParams: ['name', 'repositoryOwner', 'repositoryName'],
maskedParams: ['access_token'],
readonlyParams: ['name', 'repository_owner', 'repository_name'],
defaultValues: {
granularity: 'secret-key',
},
@ -63,11 +63,11 @@ const SYNC_DESTINATIONS: Array<SyncDestination> = [
type: 'vercel-project',
icon: 'vercel-color',
category: 'dev-tools',
maskedParams: ['accessToken'],
readonlyParams: ['name', 'projectId'],
maskedParams: ['access_token'],
readonlyParams: ['name', 'project_id'],
defaultValues: {
granularity: 'secret-key',
deploymentEnvironments: [],
deployment_environments: [],
},
},
];

View File

@ -13,29 +13,29 @@
}}
/>
{{#if @destination.purgeInitiatedAt}}
{{#if @destination.purge_initiated_at}}
<Hds::Alert
data-test-delete-status-banner
@type="inline"
class="has-bottom-margin-m"
@color={{if @destination.purgeError "critical" "neutral"}}
@icon={{unless @destination.purgeError "loading-static"}}
@color={{if @destination.purge_error "critical" "neutral"}}
@icon={{unless @destination.purge_error "loading-static"}}
as |A|
>
{{#if @destination.purgeError}}
{{#if @destination.purge_error}}
<A.Title>Deletion failed</A.Title>
<A.Description>
There was a problem with the delete purge initiated at
{{date-format @destination.purgeInitiatedAt "MMM dd, yyyy 'at' hh:mm:ss aaa"}}.
{{date-format @destination.purge_initiated_at "MMM dd, yyyy 'at' hh:mm:ss aaa"}}.
</A.Description>
<A.Description>
{{@destination.purgeError}}
{{@destination.purge_error}}
</A.Description>
{{else}}
<A.Title>Deletion in progress</A.Title>
<A.Description>
Purge initiated on
{{date-format @destination.purgeInitiatedAt "MMM dd, yyyy 'at' hh:mm:ss aaa"}}. This process may take some time
{{date-format @destination.purge_initiated_at "MMM dd, yyyy 'at' hh:mm:ss aaa"}}. This process may take some time
depending on how many secrets must be un-synced from this destination.
</A.Description>
{{/if}}

View File

@ -31,13 +31,13 @@ export default class DestinationsTabsToolbar extends Component<Args> {
get showSyncBtn() {
const { destination, capabilities } = this.args;
const path = this.capabilities.pathFor('syncSetAssociation', destination);
return capabilities[path]?.canUpdate && !destination.purgeInitiatedAt;
return capabilities[path]?.canUpdate && !destination.purge_initiated_at;
}
get showEditBtn() {
const { destination, capabilities } = this.args;
const path = this.capabilities.pathFor('syncDestination', destination);
return capabilities[path]?.canUpdate && !destination.purgeInitiatedAt;
return capabilities[path]?.canUpdate && !destination.purge_initiated_at;
}
@action

View File

@ -64,7 +64,7 @@
</span>
</div>
<code class="has-text-grey is-size-8" data-test-destination-type={{index}}>
{{destination.typeDisplayName}}
{{destination.type_display_name}}
</code>
</Item.content>

View File

@ -39,10 +39,10 @@ export default class DestinationsCreateForm extends Component<Args> {
super(owner, args);
// cache initial custom tags value to compare against updates
// tags that are removed when editing need to be added to the payload
// cast type here since not all types have customTags
const { customTags } = args.form.data as unknown as Record<string, unknown>;
if (customTags) {
this.initialCustomTags = { ...customTags };
// cast type here since not all types have custom_tags
const { custom_tags } = args.form.data as unknown as Record<string, unknown>;
if (custom_tags) {
this.initialCustomTags = { ...custom_tags };
}
}
@ -92,13 +92,15 @@ export default class DestinationsCreateForm extends Component<Args> {
diffCustomTags(payload: Record<string, unknown>) {
// if tags were removed we need to add them to the payload
const { isNew } = this.args.form;
const { customTags } = payload;
if (!isNew && customTags && this.initialCustomTags) {
// compare the new and old keys of customTags object to determine which need to be removed
const oldKeys = Object.keys(this.initialCustomTags).filter((k) => !Object.keys(customTags).includes(k));
// add tagsToRemove to the payload if there is a diff
const { custom_tags } = payload;
if (!isNew && custom_tags && this.initialCustomTags) {
// compare the new and old keys of custom_tags object to determine which need to be removed
const oldKeys = Object.keys(this.initialCustomTags).filter(
(k) => !Object.keys(custom_tags).includes(k)
);
// add tags_to_remove to the payload if there is a diff
if (oldKeys.length > 0) {
payload['tagsToRemove'] = oldKeys;
payload['tags_to_remove'] = oldKeys;
}
}
}

View File

@ -10,7 +10,7 @@
<InfoTableRow @label={{this.fieldLabel field}}>
<Hds::Badge @text={{this.credentialValue fieldValue}} @icon="check-circle" @color="success" />
</InfoTableRow>
{{else if (eq field "options.customTags")}}
{{else if (eq field "options.custom_tags")}}
{{#unless (is-empty-value fieldValue)}}
<Hds::Text::Display @tag="h3" @size="300" @weight="semibold" class="has-top-margin-l" data-test-section-header>
Custom tags

View File

@ -17,45 +17,50 @@ interface Args {
export default class DestinationDetailsPage extends Component<Args> {
connectionDetailsMap = {
'aws-sm': ['region', 'accessKeyId', 'secretAccessKey', 'roleArn', 'externalId'],
'azure-kv': ['keyVaultUri', 'tenantId', 'cloud', 'clientId', 'clientSecret'],
'gcp-sm': ['projectId', 'credentials'],
gh: ['repositoryOwner', 'repositoryName', 'accessToken'],
'vercel-project': ['accessToken', 'projectId', 'teamId', 'deploymentEnvironments'],
'aws-sm': ['region', 'access_key_id', 'secret_access_key', 'role_arn', 'external_id'],
'azure-kv': ['key_vault_uri', 'tenant_id', 'cloud', 'client_id', 'client_secret'],
'gcp-sm': ['project_id', 'credentials'],
gh: ['repository_owner', 'repository_name', 'access_token'],
'vercel-project': ['access_token', 'project_id', 'team_id', 'deployment_environments'],
};
get displayFields() {
const { destination } = this.args;
const type = destination.type as keyof typeof this.connectionDetailsMap;
const connectionDetails = this.connectionDetailsMap[type].map((field) => `connectionDetails.${field}`);
const fields = ['name', ...connectionDetails, 'options.granularityLevel', 'options.secretNameTemplate'];
const connectionDetails = this.connectionDetailsMap[type].map((field) => `connection_details.${field}`);
const fields = [
'name',
...connectionDetails,
'options.granularity_level',
'options.secret_name_template',
];
if (!['gh', 'vercel-project'].includes(type)) {
fields.push('options.customTags');
fields.push('options.custom_tags');
}
return fields;
}
// remove connectionDetails or options from the field name
// remove connection_details or options from the field name
fieldName(field: string) {
return field.replace(/(connectionDetails|options)\./, '');
return field.replace(/(connection_details|options)\./, '');
}
fieldLabel = (field: string) => {
const fieldName = this.fieldName(field);
// some fields have a specific label that cannot be converted from key name
const customLabel = {
granularityLevel: 'Secret sync granularity',
accessKeyId: 'Access key ID',
roleArn: 'Role ARN',
externalId: 'External ID',
keyVaultUri: 'Key Vault URI',
clientId: 'Client ID',
tenantId: 'Tenant ID',
projectId: 'Project ID',
granularity_level: 'Secret sync granularity',
access_key_id: 'Access key ID',
role_arn: 'Role ARN',
external_id: 'External ID',
key_vault_uri: 'Key Vault URI',
client_id: 'Client ID',
tenant_id: 'Tenant ID',
project_id: 'Project ID',
credentials: 'JSON credentials',
teamId: 'Team ID',
team_id: 'Team ID',
}[fieldName];
return customLabel || toLabel([fieldName]);

View File

@ -22,18 +22,18 @@
data-test-association-name={{index}}
class="has-text-black has-text-weight-semibold"
@route="kvSecretOverview"
@models={{array association.mount association.secretName}}
@models={{array association.mount association.secret_name}}
>
{{association.secretName}}
{{association.secret_name}}
</LinkToExternal>
{{#if association.subKey}}
<Hds::Badge @text="secret key: {{association.subKey}}/" />
{{#if association.sub_key}}
<Hds::Badge @text="secret key: {{association.sub_key}}/" />
{{/if}}
<div>
<SyncStatusBadge @status={{association.syncStatus}} data-test-association-status={{index}} />
<SyncStatusBadge @status={{association.sync_status}} data-test-association-status={{index}} />
<code class="has-text-grey is-size-8" data-test-association-updated={{index}}>
last updated on
{{date-format association.updatedAt "MMMM do yyyy, h:mm:ss a"}}
{{date-format association.updated_at "MMMM do yyyy, h:mm:ss a"}}
</code>
</div>
</div>
@ -47,9 +47,9 @@
@hasChevron={{false}}
data-test-popup-menu-trigger
/>
{{#if (eq @destination.options.granularityLevel "secret-key")}}
{{#if (eq @destination.options.granularity_level "secret-key")}}
<dd.Description
@text='Sync or unsync actions will apply to the secret "{{association.secretName}}" and not this individual key.'
@text='Sync or unsync actions will apply to the secret "{{association.secret_name}}" and not this individual key.'
/>
<dd.Separator />
{{/if}}
@ -62,7 +62,7 @@
data-test-association-action="view"
@route="kvSecretOverview"
@isRouteExternal={{true}}
@models={{array association.mount association.secretName}}
@models={{array association.mount association.secret_name}}
>
View secret
</dd.Interactive>
@ -107,7 +107,7 @@
{{#if this.secretToUnsync}}
<ConfirmModal
@color="critical"
@confirmMessage='The secret "{{this.secretToUnsync.secretName}}" will be unsynced from this destination.'
@confirmMessage='The secret "{{this.secretToUnsync.secret_name}}" will be unsynced from this destination.'
@onClose={{fn (mut this.secretToUnsync) null}}
@onConfirm={{fn this.update this.secretToUnsync "remove"}}
/>

View File

@ -51,8 +51,8 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
async update(association: AssociatedSecret, operation: string) {
try {
const { name, type } = this.args.destination;
const { mount, secretName } = association;
const body = { mount, secretName };
const { mount, secret_name } = association;
const body = { mount, secret_name };
if (operation === 'set') {
await this.api.sys.systemWriteSyncDestinationsTypeNameAssociationsSet(name, type, body);

View File

@ -36,7 +36,7 @@
<p class="is-label">Which secrets would you like us to sync?</p>
<p class="sub-text">
Select a KV engine mount and path to sync a secret to the
{{@destination.typeDisplayName}}
{{@destination.type_display_name}}
destination. Selecting a previously synced secret will re-sync that secret.
{{#if (eq @destination.granularity "secret-key")}}
This destination is configured to sync with

View File

@ -87,7 +87,7 @@ export default class DestinationSyncPageComponent extends Component<Args> {
this.syncedSecret = '';
const { name, type } = this.args.destination;
const mount = keyIsFolder(this.mountPath) ? this.mountPath.slice(0, -1) : this.mountPath; // strip trailing slash from mount path
const payload = { mount, secretName: this.secretPath };
const payload = { mount, secret_name: this.secretPath };
await this.api.sys.systemWriteSyncDestinationsTypeNameAssociationsSet(name, type, payload);
this.syncedSecret = this.secretPath;
// reset the secret path to help make it clear that the sync was successful

View File

@ -67,9 +67,9 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
responses: SystemReadSyncDestinationsTypeNameAssociationsResponse[]
): DestinationMetrics[] {
return responses.map((response) => {
const { storeName, storeType, associatedSecrets } = response;
const type = storeType as DestinationType;
const secrets = associatedSecrets as Record<string, AssociatedSecret>;
const { store_name, store_type, associated_secrets } = response;
const type = store_type as DestinationType;
const secrets = associated_secrets as Record<string, AssociatedSecret>;
const unsynced = [];
let lastUpdated;
@ -77,11 +77,11 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
const association = secrets[key];
// for display purposes, any status other than SYNCED is considered unsynced
if (association) {
if (association.syncStatus !== 'SYNCED') {
unsynced.push(association.syncStatus);
if (association.sync_status !== 'SYNCED') {
unsynced.push(association.sync_status);
}
// use the most recent updated_at value as the last synced date
const updated = new Date(association.updatedAt);
const updated = new Date(association.updated_at);
if (!lastUpdated || updated > lastUpdated) {
lastUpdated = updated;
}
@ -91,7 +91,7 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
const associationCount = Object.entries(secrets).length;
return {
icon: findDestination(type).icon,
name: storeName,
name: store_name,
type,
associationCount,
status: associationCount ? (unsynced.length ? `${unsynced.length} Unsynced` : 'All synced') : null,

View File

@ -58,7 +58,7 @@ export default class SyncSecretsDestinationsDestinationRoute extends Route {
const baseRoute = 'vault.cluster.sync.secrets.destinations.destination';
const routes = [`${baseRoute}.edit`, `${baseRoute}.sync`];
const toRoute = transition.to?.name;
if (toRoute && routes.includes(toRoute) && destination.purgeInitiatedAt) {
if (toRoute && routes.includes(toRoute) && destination.purge_initiated_at) {
const action = transition.to?.localName === 'edit' ? 'Editing a destination' : 'Syncing secrets';
this.flashMessages.info(`${action} is not permitted once a purge has been initiated.`);
this.router.replaceWith('vault.cluster.sync.secrets.destinations.destination.secrets');

View File

@ -14,17 +14,17 @@ import type { DestinationRouteModel } from '../destination';
export default class SyncSecretsDestinationsDestinationEditRoute extends Route {
model() {
const { destination } = this.modelFor('secrets.destinations.destination') as DestinationRouteModel;
const { type, name, connectionDetails, options } = destination;
const { type, name, connection_details, options } = destination;
// granularity is returned as granularityLevel in the response but expected as granularity in the request
const { granularityLevel, ...partialOptions } = options;
const { granularity_level, ...partialOptions } = options;
return {
type,
form: formResolver(type, {
name,
...connectionDetails,
...connection_details,
...partialOptions,
granularity: granularityLevel,
granularity: granularity_level,
}),
};
}

View File

@ -30,14 +30,14 @@ export default class SyncDestinationSecretsRoute extends Route {
) as DestinationRouteModel;
const {
associatedSecrets = {},
storeName,
storeType,
associated_secrets = {},
store_name,
store_type,
} = await this.api.sys.systemReadSyncDestinationsTypeNameAssociations(destination.name, destination.type);
const associations = Object.values(associatedSecrets).map((association) => ({
destinationName: storeName,
destinationType: storeType,
const associations = Object.values(associated_secrets).map((association) => ({
destination_name: store_name,
destination_type: store_type,
...association,
}));

View File

@ -40,7 +40,7 @@ export default class SyncSecretsOverviewRoute extends Route {
]
: [capabilitiesReq, [], []];
const [{ canCreate, canUpdate }, { totalSecrets }, destinations] = (await Promise.all(requests)) as [
const [{ canCreate, canUpdate }, { total_secrets }, destinations] = (await Promise.all(requests)) as [
Capabilities,
SystemListSyncAssociationsResponse,
SystemListSyncDestinationsResponse,
@ -48,7 +48,7 @@ export default class SyncSecretsOverviewRoute extends Route {
return {
canActivateSecretsSync: canCreate || canUpdate,
totalSecrets,
total_secrets,
destinations: listDestinationsTransform(destinations),
};
}

View File

@ -17,20 +17,20 @@ export const listDestinationsTransform = (
nameFilter?: string,
typeFilter?: string
) => {
const { keyInfo } = response;
const { key_info } = response;
const destinations: ListDestination[] = [];
// build ListDestination objects from keyInfo
for (const key in keyInfo) {
for (const key in key_info) {
// iterate through each type's destination names
const names = (keyInfo as Record<string, string[]>)[key];
const names = (key_info as Record<string, string[]>)[key];
// remove trailing slash from key
const type = key.replace(/\/$/, '') as DestinationType;
names?.forEach((name: string) => {
const id = `${type}/${name}`;
const { icon, name: typeDisplayName } = findDestination(type);
const { icon, name: type_display_name } = findDestination(type);
// create object with destination's id and attributes
destinations.push({ id, name, type, icon, typeDisplayName });
destinations.push({ id, name, type, icon, type_display_name });
});
}

View File

@ -217,7 +217,7 @@
"dependencies": {
"@babel/core": "7.26.10",
"@hashicorp/design-system-components": "4.18.2",
"@hashicorp/vault-client-typescript": "portal:./api-client",
"@hashicorp/vault-client-typescript": "hashicorp/vault-client-typescript",
"@hashicorp/vault-reporting": "portal:./vault-reporting",
"ember-auto-import": "2.10.0",
"handlebars": "4.7.8",

View File

@ -107,13 +107,13 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
);
await fillIn(
CUSTOM_MESSAGES.input('startTime'),
CUSTOM_MESSAGES.input('start_time'),
format(addDays(startOfDay(new Date('2023-12-12')), 1), datetimeLocalStringFormat)
);
if (end_time) {
await click('#specificDate');
await fillIn(
CUSTOM_MESSAGES.input('endTime'),
CUSTOM_MESSAGES.input('end_time'),
format(addDays(startOfDay(new Date('2023-12-12')), 10), datetimeLocalStringFormat)
);
}

View File

@ -138,7 +138,7 @@ module('Acceptance | auth custom messages auth tests', function (hooks) {
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
);
await fillIn(
CUSTOM_MESSAGES.input('startTime'),
CUSTOM_MESSAGES.input('start_time'),
format(addDays(startOfDay(new Date('2023-12-12')), 1), datetimeLocalStringFormat)
);
await fillIn('[data-test-kv-key="0"]', 'Learn more');

View File

@ -121,46 +121,46 @@ module('Acceptance | landing page dashboard', function (hooks) {
module('configuration details card', function (hooks) {
hooks.beforeEach(async function () {
this.data = {
apiAddr: 'http://127.0.0.1:8200',
cacheSize: 0,
clusterAddr: 'https://127.0.0.1:8201',
clusterCipherSuites: '',
clusterName: '',
defaultLeaseTtl: 0,
defaultMaxRequestDuration: 0,
detectDeadlocks: '',
disableCache: false,
disableClustering: false,
disableIndexing: false,
disableMlock: true,
disablePerformanceStandby: false,
disablePrintableCheck: false,
disableSealwrap: false,
disableSentinelTrace: false,
enableResponseHeaderHostname: false,
enableResponseHeaderRaftNodeId: false,
enableUi: true,
api_addr: 'http://127.0.0.1:8200',
cache_size: 0,
cluster_addr: 'https://127.0.0.1:8201',
cluster_cipher_suites: '',
cluster_name: '',
default_lease_ttl: 0,
default_max_request_duration: 0,
detect_deadlocks: '',
disable_cache: false,
disable_clustering: false,
disable_indexing: false,
disable_mlock: true,
disable_performance_standby: false,
disable_printable_check: false,
disable_sealwrap: false,
disable_sentinel_trace: false,
enable_response_header_hostname: false,
enable_response_header_raft_node_id: false,
enable_ui: true,
experiments: null,
introspectionEndpoint: false,
introspection_endpoint: false,
listeners: [
{
config: {
address: '0.0.0.0:8200',
clusterAddress: '0.0.0.0:8201',
tlsDisable: true,
cluster_address: '0.0.0.0:8201',
tls_disable: true,
},
type: 'tcp',
},
],
logFormat: '',
logLevel: 'debug',
logRequestsLevel: '',
maxLeaseTtl: '48h',
pidFile: '',
pluginDirectory: '',
pluginFilePermissions: 0,
pluginFileUid: 0,
rawStorageEndpoint: true,
log_format: '',
log_level: 'debug',
log_requests_level: '',
max_lease_ttl: '48h',
pid_file: '',
plugin_directory: '',
plugin_file_permissions: 0,
plugin_file_uid: 0,
raw_storage_endpoint: true,
seals: [
{
disabled: false,
@ -168,44 +168,44 @@ module('Acceptance | landing page dashboard', function (hooks) {
},
],
storage: {
clusterAddr: 'https://127.0.0.1:8201',
disableClustering: false,
cluster_addr: 'https://127.0.0.1:8201',
disable_clustering: false,
raft: {
maxEntrySize: '',
max_entry_size: '',
},
redirectAddr: 'http://127.0.0.1:8200',
redirect_addr: 'http://127.0.0.1:8200',
type: 'raft',
},
telemetry: {
addLeaseMetricsNamespaceLabels: false,
circonusApiApp: '',
circonusApiToken: '',
circonusApiUrl: '',
circonusBrokerId: '',
circonusBrokerSelectTag: '',
circonusCheckDisplayName: '',
circonusCheckForceMetricActivation: '',
circonusCheckId: '',
circonusCheckInstanceId: '',
circonusCheckSearchTag: '',
circonusCheckTags: '',
circonusSubmissionInterval: '',
circonusSubmissionUrl: '',
disableHostname: true,
dogstatsdAddr: '',
dogstatsdTags: null,
leaseMetricsEpsilon: 3600000000000,
maximumGaugeCardinality: 500,
metricsPrefix: '',
numLeaseMetricsBuckets: 168,
prometheusRetentionTime: 86400000000000,
stackdriverDebugLogs: false,
stackdriverLocation: '',
stackdriverNamespace: '',
stackdriverProjectId: '',
statsdAddress: '',
statsiteAddress: '',
usageGaugePeriod: 5000000000,
add_lease_metrics_namespace_labels: false,
circonus_api_app: '',
circonus_api_token: '',
circonus_api_url: '',
circonus_broker_id: '',
circonus_broker_select_tag: '',
circonus_check_display_name: '',
circonus_check_force_metric_activation: '',
circonus_check_id: '',
circonus_check_instance_id: '',
circonus_check_search_tag: '',
circonus_check_tags: '',
circonus_submission_interval: '',
circonus_submission_url: '',
disable_hostname: true,
dogstatsd_addr: '',
dogstatsd_tags: null,
lease_metrics_epsilon: 3600000000000,
maximum_gauge_cardinality: 500,
metrics_prefix: '',
num_lease_metrics_buckets: 168,
prometheus_retention_time: 86400000000000,
stackdriver_debug_logs: false,
stackdriver_location: '',
stackdriver_namespace: '',
stackdriver_project_id: '',
statsd_address: '',
statsite_address: '',
usage_gauge_period: 5000000000,
},
};
@ -252,9 +252,9 @@ module('Acceptance | landing page dashboard', function (hooks) {
test('it should show tls as enabled if tls_disable, tls_cert_file and tls_key_file are in the config', async function (assert) {
assert.expect(1);
this.data.listeners[0].config.tlsDisable = false;
this.data.listeners[0].config.tlsCertFile = './cert.pem';
this.data.listeners[0].config.tlsKeyFile = './key.pem';
this.data.listeners[0].config.tls_disable = false;
this.data.listeners[0].config.tls_cert_file = './cert.pem';
this.data.listeners[0].config.tls_key_file = './key.pem';
await login();
await visit('/vault/dashboard');
@ -263,9 +263,9 @@ module('Acceptance | landing page dashboard', function (hooks) {
test('it should show tls as enabled if only cert and key exist in config', async function (assert) {
assert.expect(1);
delete this.data.listeners[0].config.tlsDisable;
this.data.listeners[0].config.tlsCertFile = './cert.pem';
this.data.listeners[0].config.tlsKeyFile = './key.pem';
delete this.data.listeners[0].config.tls_disable;
this.data.listeners[0].config.tls_cert_file = './cert.pem';
this.data.listeners[0].config.tls_key_file = './key.pem';
await login();
await visit('/vault/dashboard');
assert.dom(DASHBOARD.vaultConfigurationCard.configDetailsField('tls')).hasText('Enabled');

View File

@ -173,17 +173,17 @@ module('Acceptance | aws | configuration', function (hooks) {
await runCmd(`delete sys/mounts/${path}`);
});
test('it should show identityTokenTtl or maxRetries even if they have not been set', async function (assert) {
test('it should show identity_token_ttl or maxRetries even if they have not been set', async function (assert) {
// documenting the intention that we show fields that have not been set but are returned by the api due to defaults
const path = `aws-${this.uid}`;
await enablePage.enable('aws', path);
await click(SES.configTab);
await click(SES.configure);
// manually fill in attrs without using helper so we can exclude identityTokenTtl and maxRetries.
// manually fill in attrs without using helper so we can exclude identity_token_ttl and max_retries.
await click(SES.wif.accessType('wif')); // toggle to wif
await fillIn(GENERAL.inputByAttr('roleArn'), 'foo-role');
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'foo-audience');
await fillIn(GENERAL.inputByAttr('role_arn'), 'foo-role');
await fillIn(GENERAL.inputByAttr('identity_token_audience'), 'foo-audience');
// manually fill in non-access type specific fields on root config so we can exclude Max Retries.
await click(GENERAL.button('Root config options'));
await fillIn(GENERAL.inputByAttr('region'), 'eu-central-1');
@ -231,7 +231,7 @@ module('Acceptance | aws | configuration', function (hooks) {
const path = `aws-${this.uid}`;
const type = 'aws';
await enablePage.enable(type, path);
// create accessKey with value foo and confirm it shows up in the details page.
// create access_key with value foo and confirm it shows up in the details page.
await click(SES.configTab);
await click(SES.configure);
await fillInAwsConfig('withAccess');
@ -243,7 +243,7 @@ module('Acceptance | aws | configuration', function (hooks) {
// edit root config details and lease config details and confirm the configuration.index page is updated.
await click(SES.configure);
// edit root config details
await fillIn(GENERAL.inputByAttr('accessKey'), 'not-foo');
await fillIn(GENERAL.inputByAttr('access_key'), 'not-foo');
await click(GENERAL.button('Root config options'));
await fillIn(GENERAL.inputByAttr('region'), 'ap-southeast-2');
// add lease config details

View File

@ -176,8 +176,8 @@ module('Acceptance | Azure | configuration', function (hooks) {
'subscription_id is included with updated value in the payload'
);
});
await fillIn(GENERAL.inputByAttr('subscriptionId'), 'subscription-id-updated');
await click(GENERAL.enableField('clientSecret'));
await fillIn(GENERAL.inputByAttr('subscription_id'), 'subscription-id-updated');
await click(GENERAL.enableField('client_secret'));
await click(GENERAL.submitButton);
// cleanup
await runCmd(`delete sys/mounts/${path}`);
@ -201,10 +201,10 @@ module('Acceptance | Azure | configuration', function (hooks) {
'subscription_id is included with updated value in the payload'
);
});
await fillIn(GENERAL.inputByAttr('subscriptionId'), 'subscription-id-updated-again');
await click(GENERAL.enableField('clientSecret'));
await fillIn(GENERAL.inputByAttr('subscription_id'), 'subscription-id-updated-again');
await click(GENERAL.enableField('client_secret'));
await click(GENERAL.button('toggle-masked'));
await fillIn(GENERAL.inputByAttr('clientSecret'), 'client-secret-updated');
await fillIn(GENERAL.inputByAttr('client_secret'), 'client-secret-updated');
await click(GENERAL.submitButton);
// cleanup
await runCmd(`delete sys/mounts/${path}`);
@ -449,7 +449,7 @@ module('Acceptance | Azure | configuration', function (hooks) {
.dom(GENERAL.infoRowValue('Identity token audience'))
.hasText('azure-audience', `value for identity token audience shows on the config details view.`);
await click(SES.configure);
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'new-audience');
await fillIn(GENERAL.inputByAttr('identity_token_audience'), 'new-audience');
await click(GENERAL.submitButton);
assert
.dom(GENERAL.infoRowValue('Identity token audience'))

View File

@ -50,8 +50,8 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
await mountSecrets.visit();
await click(MOUNT_BACKEND_FORM.mountType('kv'));
await fillIn(GENERAL.inputByAttr('path'), enginePath);
await fillIn('[data-test-input="kvConfig.maxVersions"]', maxVersion);
await click('[data-test-input="kvConfig.casRequired"]');
await fillIn('[data-test-input="kv_config.max_versions"]', maxVersion);
await click('[data-test-input="kv_config.cas_required"]');
await click('[data-test-toggle-label="Automate secret deletion"]');
await fillIn('[data-test-select="ttl-unit"]', 's');
await fillIn('[data-test-ttl-value="Automate secret deletion"]', '1');

View File

@ -84,9 +84,9 @@ module('Acceptance | ssh | configuration', function (hooks) {
`/vault/secrets/${sshPath}/configuration/edit`,
'after deleting public key stays on edit page'
);
assert.dom(GENERAL.inputByAttr('privateKey')).hasNoText('Private key is empty and reset');
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
assert.dom(GENERAL.inputByAttr('generateSigningKey')).isChecked('Generate signing key is checked');
assert.dom(GENERAL.inputByAttr('private_key')).hasNoText('Private key is empty and reset');
assert.dom(GENERAL.inputByAttr('public_key')).hasNoText('Public key is empty and reset');
assert.dom(GENERAL.inputByAttr('generate_signing_key')).isChecked('Generate signing key is checked');
await click(SES.viewBackend);
await click(SES.configTab);
assert
@ -101,11 +101,13 @@ module('Acceptance | ssh | configuration', function (hooks) {
await enablePage.enable('ssh', path);
await click(SES.configTab);
await click(SES.configure);
assert.dom(GENERAL.inputByAttr('generateSigningKey')).isChecked('generate_signing_key defaults to true');
await click(GENERAL.inputByAttr('generateSigningKey'));
assert
.dom(GENERAL.inputByAttr('generate_signing_key'))
.isChecked('generate_signing_key defaults to true');
await click(GENERAL.inputByAttr('generate_signing_key'));
await click(GENERAL.submitButton);
assert
.dom(GENERAL.validationErrorByAttr('generateSigningKey'))
.dom(GENERAL.validationErrorByAttr('generate_signing_key'))
.hasText('Provide a Public and Private key or set "Generate Signing Key" to true.');
// visit the details page and confirm the public key is not shown
await visit(`/vault/secrets/${path}/configuration`);

View File

@ -101,23 +101,23 @@ module('Acceptance | settings/mount-secret-backend', function (hooks) {
await page.visit();
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.mount-secret-backend');
await click(MOUNT_BACKEND_FORM.mountType('pki'));
assert.dom('[data-test-input="config.maxLeaseTtl"]').exists();
assert.dom('[data-test-input="config.max_lease_ttl"]').exists();
assert
.dom('[data-test-input="config.maxLeaseTtl"] [data-test-ttl-toggle]')
.dom('[data-test-input="config.max_lease_ttl"] [data-test-ttl-toggle]')
.isChecked('Toggle is checked by default');
assert.dom('[data-test-input="config.maxLeaseTtl"] [data-test-ttl-value]').hasValue('3650');
assert.dom('[data-test-input="config.maxLeaseTtl"] [data-test-select="ttl-unit"]').hasValue('d');
assert.dom('[data-test-input="config.max_lease_ttl"] [data-test-ttl-value]').hasValue('3650');
assert.dom('[data-test-input="config.max_lease_ttl"] [data-test-select="ttl-unit"]').hasValue('d');
// Go back and choose a different type
await click(GENERAL.backButton);
await click(MOUNT_BACKEND_FORM.mountType('database'));
assert.dom('[data-test-input="config.maxLeaseTtl"]').exists('3650');
assert.dom('[data-test-input="config.max_lease_ttl"]').exists('3650');
assert
.dom('[data-test-input="config.maxLeaseTtl"] [data-test-ttl-toggle]')
.dom('[data-test-input="config.max_lease_ttl"] [data-test-ttl-toggle]')
.isNotChecked('Toggle is unchecked by default');
await click(GENERAL.toggleInput('Max Lease TTL'));
assert.dom('[data-test-input="config.maxLeaseTtl"] [data-test-ttl-value]').hasValue('');
assert.dom('[data-test-input="config.maxLeaseTtl"] [data-test-select="ttl-unit"]').hasValue('s');
assert.dom('[data-test-input="config.max_lease_ttl"] [data-test-ttl-value]').hasValue('');
assert.dom('[data-test-input="config.max_lease_ttl"] [data-test-select="ttl-unit"]').hasValue('s');
});
test('it throws error if setting duplicate path name', async function (assert) {

View File

@ -82,16 +82,16 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
apiStub.resolves(response);
await visit('vault/sync/secrets/destinations/vercel-project/destination-vercel/edit');
await fillIn(GENERAL.inputByAttr('teamId'), 'team-id');
await fillIn(GENERAL.inputByAttr('team_id'), 'team-id');
await click(GENERAL.submitButton);
assert.false('accessToken' in apiStub.lastCall.args[1], 'access_token not sent in request');
assert.false('access_token' in apiStub.lastCall.args[1], 'access_token not sent in request');
await click(ts.toolbar('Edit destination'));
await click(ts.enableField('accessToken'));
await fillIn(GENERAL.inputByAttr('accessToken'), 'foobar');
await click(ts.enableField('access_token'));
await fillIn(GENERAL.inputByAttr('access_token'), 'foobar');
await click(GENERAL.submitButton);
assert.strictEqual(
apiStub.lastCall.args[1].accessToken,
apiStub.lastCall.args[1].access_token,
'foobar',
'Updated access token sent in patch request'
);

View File

@ -8,7 +8,7 @@ import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { toolsActions } from 'vault/helpers/tools-actions';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import { capitalize, camelize } from '@ember/string';
import { capitalize } from '@ember/string';
import codemirror from 'vault/tests/helpers/codemirror';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
@ -166,11 +166,11 @@ module('Acceptance | tools', function (hooks) {
await click(GENERAL.submitButton);
await waitUntil(() => find('.CodeMirror'));
const expected = Object.keys(AUTH_RESPONSE.auth).reduce((obj, auth) => {
obj[camelize(auth)] = AUTH_RESPONSE.auth[auth];
return obj;
}, {});
assert.deepEqual(expected, JSON.parse(codemirror().getValue()), 'unwrapped data equals input data');
assert.deepEqual(
AUTH_RESPONSE.auth,
JSON.parse(codemirror().getValue()),
'unwrapped data equals input data'
);
});
});

View File

@ -6,7 +6,6 @@
import { click, fillIn, find } from '@ember/test-helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
import { stringArrayToCamelCase } from 'vault/helpers/string-array-to-camel';
import { v4 as uuidv4 } from 'uuid';
import SecretsEngineResource from 'vault/resources/secrets/engine';
@ -58,9 +57,9 @@ export function configUrl(type, backend) {
const createAwsRootConfig = (accessType = 'iam') => {
if (accessType === 'wif') {
return {
roleArn: '123-role',
identityTokenAudience: '123-audience',
identityTokenTtl: 7200,
role_arn: '123-role',
identity_token_audience: '123-audience',
identity_token_ttl: 7200,
};
} else if (accessType === 'no-access') {
// set root config options that are not associated with accessType 'wif' or 'iam'
@ -70,10 +69,10 @@ const createAwsRootConfig = (accessType = 'iam') => {
} else {
return {
region: 'us-west-2',
accessKey: '123-key',
iamEndpoint: 'iam-endpoint',
stsEndpoint: 'sts-endpoint',
maxRetries: 1,
access_key: '123-key',
iam_endpoint: 'iam-endpoint',
sts_endpoint: 'sts-endpoint',
max_retries: 1,
};
}
};
@ -81,14 +80,14 @@ const createAwsRootConfig = (accessType = 'iam') => {
const createAwsLeaseConfig = () => {
return {
lease: '50s',
leaseMax: '55s',
lease_max: '55s',
};
};
const createSshCaConfig = () => {
return {
publicKey: 'public-key',
generateSigningKey: true,
public_key: 'public-key',
generate_signing_key: true,
};
};
@ -96,30 +95,30 @@ const createAzureConfig = (accessType = 'generic') => {
// note: allowed "environment" params for testing https://github.com/hashicorp/vault-plugin-secrets-azure/blob/main/client.go#L35-L37
if (accessType === 'azure') {
return {
clientSecret: 'client-secret',
subscriptionId: 'subscription-id',
tenantId: 'tenant-id',
clientId: 'client-id',
rootPasswordTtl: '1800000s',
client_secret: 'client-secret',
subscription_id: 'subscription-id',
tenant_id: 'tenant-id',
client_id: 'client-id',
root_password_ttl: '1800000s',
environment: 'AZUREPUBLICCLOUD',
};
} else if (accessType === 'wif') {
return {
subscriptionId: 'subscription-id',
tenantId: 'tenant-id',
clientId: 'client-id',
identityTokenAudience: 'audience',
identityTokenTtl: 7200,
rootPasswordTtl: '1800000s',
subscription_id: 'subscription-id',
tenant_id: 'tenant-id',
client_id: 'client-id',
identity_token_audience: 'audience',
identity_token_ttl: 7200,
root_password_ttl: '1800000s',
environment: 'AZUREPUBLICCLOUD',
};
} else {
return {
subscriptionId: 'subscription-id-2',
tenantId: 'tenant-id-2',
clientId: 'client-id-2',
subscription_id: 'subscription-id-2',
tenant_id: 'tenant-id-2',
client_id: 'client-id-2',
environment: 'AZUREPUBLICCLOUD',
rootPasswordTtl: '1800000s',
root_password_ttl: '1800000s',
};
}
};
@ -127,15 +126,15 @@ const createAzureConfig = (accessType = 'generic') => {
const createGcpConfig = (accessType = 'gcp') => {
if (accessType === 'wif') {
return {
serviceAccountEmail: 'service-email',
identityTokenAudience: 'audience',
identityTokenTtl: 7200,
service_account_email: 'service-email',
identity_token_audience: 'audience',
identity_token_ttl: 7200,
};
} else {
return {
credentials: '{"some-key":"some-value"}',
ttl: '100s',
maxTtl: '101s',
max_ttl: '101s',
};
}
};
@ -169,15 +168,15 @@ export const createConfig = (type) => {
/* Manually create the configuration by filling in the configuration form */
export const fillInAwsConfig = async (situation = 'withAccess') => {
if (situation === 'withAccess') {
await fillIn(GENERAL.inputByAttr('accessKey'), 'foo');
await fillIn(GENERAL.inputByAttr('secretKey'), 'bar');
await fillIn(GENERAL.inputByAttr('access_key'), 'foo');
await fillIn(GENERAL.inputByAttr('secret_key'), 'bar');
}
if (situation === 'withAccessOptions') {
await click(GENERAL.button('Root config options'));
await fillIn(GENERAL.inputByAttr('region'), 'ca-central-1');
await fillIn(GENERAL.inputByAttr('iamEndpoint'), 'iam-endpoint');
await fillIn(GENERAL.inputByAttr('stsEndpoint'), 'sts-endpoint');
await fillIn(GENERAL.inputByAttr('maxRetries'), '3');
await fillIn(GENERAL.inputByAttr('iam_endpoint'), 'iam-endpoint');
await fillIn(GENERAL.inputByAttr('sts_endpoint'), 'sts-endpoint');
await fillIn(GENERAL.inputByAttr('max_retries'), '3');
}
if (situation === 'withLease') {
await click(GENERAL.ttl.toggle('Default Lease TTL'));
@ -188,17 +187,17 @@ export const fillInAwsConfig = async (situation = 'withAccess') => {
if (situation === 'withWif') {
await click(SES.wif.accessType('wif')); // toggle to wif
await fillIn(GENERAL.inputByAttr('issuer'), `http://bar.${uuidv4()}`); // make random because global setting
await fillIn(GENERAL.inputByAttr('roleArn'), 'foo-role');
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'foo-audience');
await fillIn(GENERAL.inputByAttr('role_arn'), 'foo-role');
await fillIn(GENERAL.inputByAttr('identity_token_audience'), 'foo-audience');
await click(GENERAL.ttl.toggle('Identity token TTL'));
await fillIn(GENERAL.ttl.input('Identity token TTL'), '7200');
}
};
export const fillInAzureConfig = async (withWif = false) => {
await fillIn(GENERAL.inputByAttr('subscriptionId'), 'subscription-id');
await fillIn(GENERAL.inputByAttr('tenantId'), 'tenant-id');
await fillIn(GENERAL.inputByAttr('clientId'), 'client-id');
await fillIn(GENERAL.inputByAttr('subscription_id'), 'subscription-id');
await fillIn(GENERAL.inputByAttr('tenant_id'), 'tenant-id');
await fillIn(GENERAL.inputByAttr('client_id'), 'client-id');
// options may already be toggled so check before clicking
if (!find(GENERAL.inputByAttr('environment'))) {
await click(GENERAL.button('More options'));
@ -212,21 +211,21 @@ export const fillInAzureConfig = async (withWif = false) => {
if (withWif) {
await click(SES.wif.accessType('wif')); // toggle to wif
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'azure-audience');
await fillIn(GENERAL.inputByAttr('identity_token_audience'), 'azure-audience');
await click(GENERAL.ttl.toggle('Identity token TTL'));
await fillIn(GENERAL.ttl.input('Identity token TTL'), '7200');
} else {
await fillIn(GENERAL.inputByAttr('clientSecret'), 'client-secret');
await fillIn(GENERAL.inputByAttr('client_secret'), 'client-secret');
}
};
export const fillInGcpConfig = async (withWif = false) => {
if (withWif) {
await click(SES.wif.accessType('wif')); // toggle to wif
await fillIn(GENERAL.inputByAttr('identityTokenAudience'), 'azure-audience');
await fillIn(GENERAL.inputByAttr('identity_token_audience'), 'azure-audience');
await click(GENERAL.ttl.toggle('Identity token TTL'));
await fillIn(GENERAL.ttl.input('Identity token TTL'), '7200');
await fillIn(GENERAL.inputByAttr('serviceAccountEmail'), 'some@email.com');
await fillIn(GENERAL.inputByAttr('service_account_email'), 'some@email.com');
} else {
await click(GENERAL.button('More options'));
await click(GENERAL.ttl.toggle('Config TTL'));
@ -259,24 +258,26 @@ const gcpWifKeys = [...genericWifKeys, 'Service account email'];
// SSH specific keys
const sshKeys = ['Private key', 'Public key', 'Generate signing key'];
export const expectedConfigKeys = (type, camelCase = false) => {
export const expectedConfigKeys = (type, snake_case = false) => {
const getKeys = (keys) => (snake_case ? keys.map((str) => str.replace(/\s+/g, '_').toLowerCase()) : keys);
switch (type) {
case 'aws':
return camelCase ? stringArrayToCamelCase(awsKeys) : awsKeys;
return getKeys(awsKeys);
case 'aws-wif':
return camelCase ? stringArrayToCamelCase(awsWifKeys) : awsWifKeys;
return getKeys(awsWifKeys);
case 'aws-lease':
return camelCase ? stringArrayToCamelCase(awsLeaseKeys) : awsLeaseKeys;
return getKeys(awsLeaseKeys);
case 'azure':
return camelCase ? stringArrayToCamelCase(azureKeys) : azureKeys;
return getKeys(azureKeys);
case 'azure-wif':
return camelCase ? stringArrayToCamelCase(azureWifKeys) : azureWifKeys;
return getKeys(azureWifKeys);
case 'gcp':
return camelCase ? stringArrayToCamelCase(gcpKeys) : gcpKeys;
return getKeys(gcpKeys);
case 'gcp-wif':
return camelCase ? stringArrayToCamelCase(gcpWifKeys) : gcpWifKeys;
return getKeys(gcpWifKeys);
case 'ssh':
return camelCase ? stringArrayToCamelCase(sshKeys) : sshKeys;
return getKeys(sshKeys);
}
};

View File

@ -2,7 +2,6 @@
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import camelizeKeys from 'vault/utils/camelize-object-keys';
// creates destination and association model for use in sync integration tests
// ensure that setupMirage is used prior to setupModels since this.server is used
@ -26,14 +25,14 @@ export function setupDataStubs(hooks) {
this.destination = {
name,
type,
connectionDetails: camelizeKeys(connection_details),
connection_details,
options: {
granularityLevel: granularity,
secretNameTemplate: secret_name_template,
customTags: custom_tags,
granularity_level: granularity,
secret_name_template,
custom_tags,
},
purgeInitiatedAt: purge_initiated_at,
purgeError: purge_error,
purge_initiated_at,
purge_error,
};
this.destinations = [this.destination];
@ -52,9 +51,9 @@ export function setupDataStubs(hooks) {
updated_at: '2023-09-20T10:51:53.961861096', // removed tz offset so time is consistently displayed
});
this.association = {
...camelizeKeys(association),
destinationType: this.destination.type,
destinationName: this.destination.name,
...association,
destination_type: this.destination.type,
destination_name: this.destination.name,
};
this.associations = [this.association];
this.associations.meta = {

View File

@ -93,13 +93,13 @@ export const PAGE = {
case 'credentials':
await click(GENERAL.textToggle);
return fillIn(GENERAL.maskedInput, value);
case 'customTags':
case 'custom_tags':
await fillIn('[data-test-kv-key="0"]', 'foo');
return fillIn('[data-test-kv-value="0"]', value);
case 'deploymentEnvironments':
await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#development`);
await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#preview`);
return await click(`${GENERAL.inputGroupByAttr('deploymentEnvironments')} input#production`);
case 'deployment_environments':
await click(`${GENERAL.inputGroupByAttr('deployment_environments')} input#development`);
await click(`${GENERAL.inputGroupByAttr('deployment_environments')} input#preview`);
return await click(`${GENERAL.inputGroupByAttr('deployment_environments')} input#production`);
default:
return fillIn(`[data-test-input="${attr}"]`, value);
}

View File

@ -16,7 +16,7 @@ module('Integration | Component | auth-method/configuration', function (hooks) {
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.createMethod = (path, type) => {
this.method = new AuthMethodResource({ path, type, config: { listingVisibility: 'hidden' } }, this);
this.method = new AuthMethodResource({ path, type, config: { listing_visibility: 'hidden' } }, this);
};
this.renderComponent = () => render(hbs`<AuthMethod::Configuration @method={{this.method}} />`);
});

View File

@ -35,7 +35,7 @@ const authUrlRequestTests = (test) => {
const { role } = JSON.parse(req.requestBody);
assert.true(true, 'it makes request to auth_url');
assert.strictEqual(role, '', 'role is empty');
return { data: { authUrl: '123-example.com' } };
return { data: { auth_url: '123-example.com' } };
});
await this.renderComponent();
});
@ -100,7 +100,7 @@ const oidcLoginTests = (test) => {
// true success has to be asserted in acceptance tests because it's not possible to mock a trusted message event
test('it opens the popup window on submit', async function (assert) {
this.server.post(`/auth/${this.authType}/oidc/auth_url`, () => {
return { data: { authUrl: '123-example.com' } };
return { data: { auth_url: '123-example.com' } };
});
sinon.replaceGetter(window, 'screen', () => ({ height: 600, width: 500 }));
await this.renderComponent();
@ -142,7 +142,7 @@ const oidcLoginTests = (test) => {
});
test('it fires onError callback on submit when auth_url request is successful but missing auth_url', async function (assert) {
this.server.post('/auth/:path/oidc/auth_url', () => ({ data: { authUrl: '' } }));
this.server.post('/auth/:path/oidc/auth_url', () => ({ data: { auth_url: '' } }));
await this.renderComponent();
await click(GENERAL.submitButton);

View File

@ -52,12 +52,12 @@ module('Integration | Component | auth | form | saml', function (hooks) {
});
this.assertSubmit = (assert, loginRequestArgs, loginData) => {
const [path, { clientVerifier, tokenPollId }] = loginRequestArgs;
const [path, { client_verifier, token_poll_id }] = loginRequestArgs;
// if path is included in loginData, a custom path was submitted
const expectedPath = loginData?.path || this.authType;
assert.strictEqual(path, expectedPath, 'it calls samlWriteToken with expected path');
assert.strictEqual(clientVerifier, this.verifier, 'it calls samlWriteToken with verifier');
assert.strictEqual(tokenPollId, this.tokenPollId, 'it calls samlWriteToken with tokenPollId');
assert.strictEqual(client_verifier, this.verifier, 'it calls samlWriteToken with verifier');
assert.strictEqual(token_poll_id, this.tokenPollId, 'it calls samlWriteToken with tokenPollId');
};
this.renderComponent = ({ yieldBlock = false } = {}) => {

View File

@ -30,7 +30,7 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
{
authenticated: true,
type: 'banner',
startTime: addDays(startOfDay(timestamp.now()), 1).toISOString(),
start_time: addDays(startOfDay(timestamp.now()), 1).toISOString(),
},
{ isNew: true }
);
@ -60,14 +60,14 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
assert.dom('[data-test-kv-key="0"]').exists();
assert.dom('[data-test-kv-value="0"]').exists();
assert
.dom(CUSTOM_MESSAGES.input('startTime'))
.dom(CUSTOM_MESSAGES.input('start_time'))
.hasValue(
format(addDays(startOfDay(timestamp.now()), 1), datetimeLocalStringFormat),
`message startTime defaults to midnight of following day. test context startTime: ${
this.message.startTime
`message start_time defaults to midnight of following day. test context start_time: ${
this.message.start_time
}, now: ${timestamp.now().toISOString()}`
);
assert.dom(CUSTOM_MESSAGES.input('endTime')).hasValue('');
assert.dom(CUSTOM_MESSAGES.input('end_time')).hasValue('');
});
test('it should display validation errors for invalid form fields', async function (assert) {
@ -75,8 +75,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
await this.renderComponent();
await fillIn(CUSTOM_MESSAGES.input('startTime'), '2024-01-20T00:00');
await fillIn(CUSTOM_MESSAGES.input('endTime'), '2024-01-01T00:00');
await fillIn(CUSTOM_MESSAGES.input('start_time'), '2024-01-20T00:00');
await fillIn(CUSTOM_MESSAGES.input('end_time'), '2024-01-01T00:00');
await click(GENERAL.submitButton);
assert
.dom(GENERAL.validationErrorByAttr('title'))
@ -87,12 +87,12 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
.exists('Validation error for field `message` renders')
.hasText('Message is required.');
assert
.dom(GENERAL.validationErrorByAttr('startTime'))
.exists('Validation error for field `startTime` renders')
.dom(GENERAL.validationErrorByAttr('start_time'))
.exists('Validation error for field `start_time` renders')
.hasText('Start time is after end time.');
assert
.dom(GENERAL.validationErrorByAttr('endTime'))
.exists('Validation error for field `endTime` renders')
.dom(GENERAL.validationErrorByAttr('end_time'))
.exists('Validation error for field `end_time` renders')
.hasText('End time is before start time.');
});
@ -111,12 +111,12 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
);
await fillIn(
CUSTOM_MESSAGES.input('startTime'),
CUSTOM_MESSAGES.input('start_time'),
format(addDays(startOfDay(new Date('2023-12-12')), 1), datetimeLocalStringFormat)
);
await click('#specificDate');
await fillIn(
CUSTOM_MESSAGES.input('endTime'),
CUSTOM_MESSAGES.input('end_time'),
format(addDays(startOfDay(new Date('2023-12-12')), 10), datetimeLocalStringFormat)
);
await fillIn('[data-test-kv-key="0"]', 'Learn more');
@ -143,8 +143,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
authenticated: false,
title: 'Hello world',
message: 'Blah blah blah. Some super long message.',
startTime: new Date('2023-12-12T08:00:00.000Z'),
endTime: new Date('2023-12-21T08:00:00.000Z'),
start_time: new Date('2023-12-12T08:00:00.000Z'),
end_time: new Date('2023-12-21T08:00:00.000Z'),
link: { 'Learn more': 'www.learnmore.com' },
});
@ -163,11 +163,11 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
assert.dom('[data-test-kv-value="0"]').hasValue('www.learnmore.com');
await click('#specificDate');
assert
.dom(CUSTOM_MESSAGES.input('startTime'))
.hasValue(format(new Date(this.message.startTime), datetimeLocalStringFormat));
.dom(CUSTOM_MESSAGES.input('start_time'))
.hasValue(format(new Date(this.message.start_time), datetimeLocalStringFormat));
assert
.dom(CUSTOM_MESSAGES.input('endTime'))
.hasValue(format(new Date(this.message.endTime), datetimeLocalStringFormat));
.dom(CUSTOM_MESSAGES.input('end_time'))
.hasValue(format(new Date(this.message.end_time), datetimeLocalStringFormat));
});
test('it should show a preview image modal when preview is clicked', async function (assert) {
@ -220,8 +220,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
title: 'Message title 1',
message: 'Some long long long message',
link: { here: 'www.example.com' },
startTime: new Date('2021-08-01T00:00:00Z'),
endTime: '',
start_time: new Date('2021-08-01T00:00:00Z'),
end_time: '',
},
{
id: '01234567-89ab-vvvv-0123-456789abcdef',
@ -231,8 +231,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
title: 'Message title 2',
message: 'Some long long long message',
link: { here: 'www.example.com' },
startTime: new Date('2021-08-01T00:00:00Z'),
endTime: new Date('2090-08-01T00:00:00Z'),
start_time: new Date('2021-08-01T00:00:00Z'),
end_time: new Date('2090-08-01T00:00:00Z'),
},
];

View File

@ -18,8 +18,8 @@ const allFields = [
{ label: 'Authenticated', key: 'authenticated' },
{ label: 'Title', key: 'title' },
{ label: 'Message', key: 'message' },
{ label: 'Start time', key: 'startTime' },
{ label: 'End time', key: 'endTime' },
{ label: 'Start time', key: 'start_time' },
{ label: 'End time', key: 'end_time' },
{ label: 'Link', key: 'link' },
];
@ -45,9 +45,8 @@ module('Integration | Component | messages/page/details', function (hooks) {
title: 'Message title 1',
message: 'Some long long long message',
link: { here: 'www.example.com' },
startTime: new Date('2021-08-01T00:00:00Z'),
endTime: undefined,
canEditCustomMessages: true,
start_time: new Date('2021-08-01T00:00:00Z'),
end_time: undefined,
};
this.capabilities = { canDelete: true, canUpdate: true };
});
@ -64,7 +63,7 @@ module('Integration | Component | messages/page/details', function (hooks) {
allFields.forEach((field) => {
assert.dom(GENERAL.infoRowLabel(field.label)).hasText(field.label, `${field.label} label renders`);
if (field.key === 'startTime' || field.key === 'endTime') {
if (field.key === 'start_time' || field.key === 'end_time') {
const formattedDate = dateFormat([this.message[field.key], 'MMM d, yyyy hh:mm aaa'], {
withTimeZone: true,
});

View File

@ -38,8 +38,8 @@ module('Integration | Component | messages/page/list', function (hooks) {
title: 'Message title 1',
message: 'Some long long long message',
link: { title: 'here', href: 'www.example.com' },
startTime: new Date('2021-08-01T00:00:00Z'),
endTime: undefined,
start_time: new Date('2021-08-01T00:00:00Z'),
end_time: undefined,
},
{
id: '1',
@ -49,8 +49,8 @@ module('Integration | Component | messages/page/list', function (hooks) {
title: 'Message title 2',
message: 'Some long long long message blah blah blah',
link: { title: 'here', href: 'www.example2.com' },
startTime: new Date('2023-07-01T00:00:00Z'),
endTime: new Date('2023-08-01T00:00:00Z'),
start_time: new Date('2023-07-01T00:00:00Z'),
end_time: new Date('2023-08-01T00:00:00Z'),
},
{
id: '2',
@ -111,7 +111,7 @@ module('Integration | Component | messages/page/list', function (hooks) {
title: `Message title ${i}`,
message: 'Some long long long message',
link: { title: 'here', href: 'www.example.com' },
startTime: new Date('2021-08-01T00:00:00Z'),
start_time: new Date('2021-08-01T00:00:00Z'),
});
}
this.messages.meta.total = this.messages.length;

View File

@ -29,8 +29,8 @@ module('Integration | Component | mfa-form', function (hooks) {
// override in tests that require different scenarios
this.totpConstraint = this.server.create('mfa-method', { type: 'totp' });
const mfaRequirement = this.authService.parseMfaResponse({
mfaRequestId: 'test-mfa-id',
mfaConstraints: { test_mfa: { any: [this.totpConstraint] } },
mfa_request_id: 'test-mfa-id',
mfa_constraints: { test_mfa: { any: [this.totpConstraint] } },
});
this.mfaAuthData.mfaRequirement = mfaRequirement;
});
@ -41,8 +41,8 @@ module('Integration | Component | mfa-form', function (hooks) {
const duoConstraint = this.server.create('mfa-method', { type: 'duo' });
this.mfaAuthData.mfaRequirement = this.authService.parseMfaResponse({
mfaRequestId: 'test-mfa-id',
mfaConstraints: { test_mfa_1: { any: [totpConstraint] } },
mfa_request_id: 'test-mfa-id',
mfa_constraints: { test_mfa_1: { any: [totpConstraint] } },
});
await render(
@ -61,8 +61,8 @@ module('Integration | Component | mfa-form', function (hooks) {
);
this.mfaAuthData.mfaRequirement = this.authService.parseMfaResponse({
mfaRequestId: 'test-mfa-id',
mfaConstraints: { test_mfa_1: { any: [duoConstraint, oktaConstraint] } },
mfa_request_id: 'test-mfa-id',
mfa_constraints: { test_mfa_1: { any: [duoConstraint, oktaConstraint] } },
});
await render(
@ -81,8 +81,8 @@ module('Integration | Component | mfa-form', function (hooks) {
);
this.mfaAuthData.mfaRequirement = this.authService.parseMfaResponse({
mfaRequestId: 'test-mfa-id',
mfaConstraints: { test_mfa_1: { any: [oktaConstraint] }, test_mfa_2: { any: [duoConstraint] } },
mfa_request_id: 'test-mfa-id',
mfa_constraints: { test_mfa_1: { any: [oktaConstraint] }, test_mfa_2: { any: [duoConstraint] } },
});
await render(
@ -117,8 +117,8 @@ module('Integration | Component | mfa-form', function (hooks) {
const oktaConstraint = this.server.create('mfa-method', { type: 'okta' });
const pingidConstraint = this.server.create('mfa-method', { type: 'pingid' });
const mfaRequirement = this.authService.parseMfaResponse({
mfaRequestId: 'test-mfa-id',
mfaConstraints: {
mfa_request_id: 'test-mfa-id',
mfa_constraints: {
test_mfa_1: {
any: [pingidConstraint, oktaConstraint],
},

View File

@ -137,11 +137,11 @@ module('Integration | Component | mount backend form', function (hooks) {
module('secrets engine', function (hooks) {
hooks.beforeEach(function () {
const defaults = {
config: { listingVisibility: false },
kvConfig: {
maxVersions: 0,
casRequired: false,
deleteVersionAfter: 0,
config: { listing_visibility: false },
kv_config: {
max_versions: 0,
cas_required: false,
delete_version_after: 0,
},
options: { version: 2 },
};
@ -218,7 +218,7 @@ module('Integration | Component | mount backend form', function (hooks) {
});
module('WIF secret engines', function () {
test('it shows identityTokenKey when type is a WIF engine and hides when its not', async function (assert) {
test('it shows identity_token_key when type is a WIF engine and hides when its not', async function (assert) {
await render(
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
@ -226,7 +226,7 @@ module('Integration | Component | mount backend form', function (hooks) {
await click(MOUNT_BACKEND_FORM.mountType(engine));
await click(GENERAL.button('Method Options'));
assert
.dom(GENERAL.fieldByAttr('config.identityTokenKey'))
.dom(GENERAL.fieldByAttr('config.identity_token_key'))
.exists(`Identity token key field shows when type=${this.model.type}`);
await click(GENERAL.backButton);
}
@ -238,20 +238,20 @@ module('Integration | Component | mount backend form', function (hooks) {
await click(MOUNT_BACKEND_FORM.mountType(engine.type));
await click(GENERAL.button('Method Options'));
assert
.dom(GENERAL.fieldByAttr('config.identityTokenKey'))
.dom(GENERAL.fieldByAttr('config.identity_token_key'))
.doesNotExist(`Identity token key field hidden when type=${this.model.type}`);
await click(GENERAL.backButton);
}
});
test('it updates identityTokenKey if user has changed it', async function (assert) {
test('it updates identity_token_key if user has changed it', async function (assert) {
await render(
hbs`<MountBackendForm @mountCategory="secret" @mountModel={{this.model}} @onMountSuccess={{this.onMountSuccess}} />`
);
assert.strictEqual(
this.model.config.identityTokenKey,
this.model.config.identity_token_key,
undefined,
`On init identityTokenKey is not set on the model`
`On init identity_token_key is not set on the model`
);
for (const engine of WIF_ENGINES) {
await click(MOUNT_BACKEND_FORM.mountType(engine));
@ -259,9 +259,9 @@ module('Integration | Component | mount backend form', function (hooks) {
await typeIn(GENERAL.inputSearch('key'), `${engine}+specialKey`); // set to something else
assert.strictEqual(
this.model.config.identityTokenKey,
this.model.config.identity_token_key,
`${engine}+specialKey`,
`updates ${engine} model with custom identityTokenKey`
`updates ${engine} model with custom identity_token_key`
);
await click(GENERAL.backButton);
}

View File

@ -22,27 +22,27 @@ module('Integration | Component | SecretEngine::ConfigurationDetails', function
this.configs = {
aws: {
region: 'us-west-2',
accessKey: '123-key',
iamEndpoint: 'iam-endpoint',
stsEndpoint: 'sts-endpoint',
maxRetries: 1,
access_key: '123-key',
iam_endpoint: 'iam-endpoint',
sts_endpoint: 'sts-endpoint',
max_retries: 1,
},
azure: {
clientSecret: 'client-secret',
subscriptionId: 'subscription-id',
tenantId: 'tenant-id',
clientId: 'client-id',
rootPasswordTtl: '1800000s',
client_secret: 'client-secret',
subscription_id: 'subscription-id',
tenant_id: 'tenant-id',
client_id: 'client-id',
root_password_ttl: '1800000s',
environment: 'AZUREPUBLICCLOUD',
},
gcp: {
credentials: '{"some-key":"some-value"}',
ttl: '100s',
maxTtl: '101s',
max_ttl: '101s',
},
ssh: {
publicKey: 'public-key',
generateSigningKey: true,
public_key: 'public-key',
generate_signing_key: true,
},
};
});

View File

@ -20,7 +20,7 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
hooks.beforeEach(function () {
const router = this.owner.lookup('service:router');
this.id = 'ssh-test';
this.form = new SshConfigForm({ generateSigningKey: true }, { isNew: true });
this.form = new SshConfigForm({ generate_signing_key: true }, { isNew: true });
this.transitionStub = sinon.stub(router, 'transitionTo');
this.refreshStub = sinon.stub(router, 'refresh');
});
@ -32,10 +32,10 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
@id={{this.id}}
/>
`);
assert.dom(GENERAL.inputByAttr('privateKey')).hasNoText('Private key is empty and reset');
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
assert.dom(GENERAL.inputByAttr('private_key')).hasNoText('Private key is empty and reset');
assert.dom(GENERAL.inputByAttr('public_key')).hasNoText('Public key is empty and reset');
assert
.dom(GENERAL.inputByAttr('generateSigningKey'))
.dom(GENERAL.inputByAttr('generate_signing_key'))
.isChecked('Generate signing key is checked by default');
});
@ -62,19 +62,19 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
@id={{this.id}}
/>
`);
await fillIn(GENERAL.inputByAttr('publicKey'), 'hello');
await fillIn(GENERAL.inputByAttr('public_key'), 'hello');
await click(GENERAL.submitButton);
assert
.dom(GENERAL.validationErrorByAttr('publicKey'))
.dom(GENERAL.validationErrorByAttr('public_key'))
.hasText(
'You must provide a Public and Private keys or leave both unset.',
'Public key validation error renders.'
);
await click(GENERAL.inputByAttr('generateSigningKey'));
await click(GENERAL.inputByAttr('generate_signing_key'));
await click(GENERAL.submitButton);
assert
.dom(GENERAL.validationErrorByAttr('generateSigningKey'))
.dom(GENERAL.validationErrorByAttr('generate_signing_key'))
.hasText(
'Provide a Public and Private key or set "Generate Signing Key" to true.',
'Generate signing key validation message shows.'
@ -107,10 +107,10 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
module('editing', function (hooks) {
hooks.beforeEach(function () {
this.editId = 'ssh-edit-me';
this.publicKey = 'public-key';
this.public_key = 'public-key';
this.form = new SshConfigForm({
publicKey: this.publicKey,
generateSigningKey: true,
public_key: this.public_key,
generate_signing_key: true,
});
});
test('it populates fields when editing', async function (assert) {
@ -127,7 +127,7 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
await click(GENERAL.button('toggle-masked'));
assert
.dom(GENERAL.inputByAttr('public-key'))
.hasText(this.publicKey, 'public key is unmasked and shows the actual value');
.hasText(this.public_key, 'public key is unmasked and shows the actual value');
});
test('it allows you to delete a public key', async function (assert) {

Some files were not shown because too many files have changed in this diff Show More