Merge pull request #12227 from ansible/vaultcredentialsbug

Prevent edit of  vault ID once credential is created.
This commit is contained in:
djyasin 2022-05-19 15:13:41 -04:00 committed by GitHub
commit 2041665880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 0 deletions

View File

@ -2664,6 +2664,13 @@ class CredentialSerializer(BaseSerializer):
return credential_type
def validate_inputs(self, inputs):
if self.instance and self.instance.credential_type.kind == "vault":
if 'vault_id' in inputs and inputs['vault_id'] != self.instance.inputs['vault_id']:
raise ValidationError(_('Vault IDs cannot be changed once they have been created.'))
return inputs
class CredentialSerializerCreate(CredentialSerializer):

View File

@ -532,6 +532,49 @@ def test_vault_password_required(post, organization, admin):
assert 'required fields (vault_password)' in j.job_explanation
@pytest.mark.django_db
def test_vault_id_immutable(post, patch, organization, admin):
vault = CredentialType.defaults['vault']()
vault.save()
response = post(
reverse('api:credential_list'),
{
'credential_type': vault.pk,
'organization': organization.id,
'name': 'Best credential ever',
'inputs': {'vault_id': 'password', 'vault_password': 'password'},
},
admin,
)
assert response.status_code == 201
assert Credential.objects.count() == 1
response = patch(
reverse('api:credential_detail', kwargs={'pk': response.data['id']}), {'inputs': {'vault_id': 'password2', 'vault_password': 'password'}}, admin
)
assert response.status_code == 400
assert response.data['inputs'][0] == 'Vault IDs cannot be changed once they have been created.'
@pytest.mark.django_db
def test_patch_without_vault_id_valid(post, patch, organization, admin):
vault = CredentialType.defaults['vault']()
vault.save()
response = post(
reverse('api:credential_list'),
{
'credential_type': vault.pk,
'organization': organization.id,
'name': 'Best credential ever',
'inputs': {'vault_id': 'password', 'vault_password': 'password'},
},
admin,
)
assert response.status_code == 201
assert Credential.objects.count() == 1
response = patch(reverse('api:credential_detail', kwargs={'pk': response.data['id']}), {'name': 'worst_credential_ever'}, admin)
assert response.status_code == 200
#
# Net Credentials
#

View File

@ -1,5 +1,6 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useField, useFormikContext } from 'formik';
import { shape, string } from 'prop-types';
import styled from 'styled-components';
@ -31,6 +32,7 @@ function CredentialInput({
fieldOptions,
isFieldGroupValid,
credentialKind,
isVaultIdDisabled,
...rest
}) {
const [fileName, setFileName] = useState('');
@ -148,6 +150,7 @@ function CredentialInput({
onChange={(value, event) => {
subFormField.onChange(event);
}}
isDisabled={isVaultIdDisabled}
validated={isValid ? 'default' : 'error'}
/>
);
@ -167,6 +170,7 @@ CredentialInput.defaultProps = {
function CredentialField({ credentialType, fieldOptions }) {
const { values: formikValues } = useFormikContext();
const location = useLocation();
const requiredFields = credentialType?.inputs?.required || [];
const isRequired = requiredFields.includes(fieldOptions.id);
const validateField = () => {
@ -242,6 +246,15 @@ function CredentialField({ credentialType, fieldOptions }) {
<BecomeMethodField fieldOptions={fieldOptions} isRequired={isRequired} />
);
}
let disabled = false;
if (
credentialType.kind === 'vault' &&
location.pathname.endsWith('edit') &&
fieldOptions.id === 'vault_id'
) {
disabled = true;
}
return (
<CredentialPluginField
fieldOptions={fieldOptions}
@ -251,6 +264,7 @@ function CredentialField({ credentialType, fieldOptions }) {
<CredentialInput
isFieldGroupValid={isValid}
fieldOptions={fieldOptions}
isVaultIdDisabled={disabled}
/>
</CredentialPluginField>
);

View File

@ -13,6 +13,12 @@ const fieldOptions = {
secret: true,
};
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({
pathname: '/credentials/3/edit',
}),
}));
describe('<CredentialField />', () => {
let wrapper;
test('renders correctly without initial value', () => {
@ -113,4 +119,33 @@ describe('<CredentialField />', () => {
expect(wrapper.find('TextInput').props().value).toBe('');
expect(wrapper.find('TextInput').props().placeholder).toBe('ENCRYPTED');
});
test('Should check to see if the ability to edit vault ID is disabled after creation.', () => {
const vaultCredential = credentialTypes.find((type) => type.id === 3);
const vaultFieldOptions = {
id: 'vault_id',
label: 'Vault Identifier',
type: 'string',
secret: true,
};
wrapper = mountWithContexts(
<Formik
initialValues={{
passwordPrompts: {},
inputs: {
password: 'password',
vault_id: 'vault_id',
},
}}
>
{() => (
<CredentialField
fieldOptions={vaultFieldOptions}
credentialType={vaultCredential}
/>
)}
</Formik>
);
expect(wrapper.find('CredentialInput').props().isDisabled).toBe(true);
expect(wrapper.find('KeyIcon').length).toBe(1);
});
});