diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 0a4210b2ee..c790e2a2e4 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3449,7 +3449,8 @@ class JobLaunchSerializer(BaseSerializer): dict( id=cred.id, name=cred.name, - credential_type=cred.credential_type.pk + credential_type=cred.credential_type.pk, + passwords_needed=cred.passwords_needed ) for cred in obj.credentials.all() ] diff --git a/awx/api/views.py b/awx/api/views.py index 67a0241d87..9b8558cc64 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -626,9 +626,9 @@ class LaunchConfigCredentialsBase(SubListAttachDetachAPIView): if not getattr(parent, ask_field_name): return {"msg": _("Related template is not configured to accept credentials on launch.")} - elif sub.kind != 'vault' and parent.credentials.filter(credential_type__kind=sub.kind).exists(): + elif sub.unique_hash() in [cred.unique_hash() for cred in parent.credentials.all()]: return {"msg": _("This launch configuration already provides a {credential_type} credential.".format( - credential_type=sub.kind))} + credential_type=sub.unique_hash(display=True)))} elif sub.pk in parent.unified_job_template.credentials.values_list('pk', flat=True): return {"msg": _("Related template already uses {credential_type} credential.".format( credential_type=sub.name))} @@ -3061,9 +3061,9 @@ class JobTemplateCredentialsList(SubListCreateAttachDetachAPIView): return sublist_qs def is_valid_relation(self, parent, sub, created=False): - current_extra_types = [cred.credential_type.pk for cred in parent.credentials.all()] - if sub.credential_type.pk in current_extra_types: - return {'error': _('Cannot assign multiple %s credentials.' % sub.credential_type.name)} + if sub.unique_hash() in [cred.unique_hash() for cred in parent.credentials.all()]: + return {"msg": _("Cannot assign multiple {credential_type} credentials.".format( + credential_type=sub.unique_hash(display=True)))} return super(JobTemplateCredentialsList, self).is_valid_relation(parent, sub, created) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index b9b2dd4942..786405e81e 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -326,9 +326,14 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): @property def passwords_needed(self): needed = [] - for field in ('ssh_password', 'become_password', 'ssh_key_unlock', 'vault_password'): + for field in ('ssh_password', 'become_password', 'ssh_key_unlock'): if getattr(self, 'needs_%s' % field): needed.append(field) + if self.needs_vault_password: + if self.inputs.get('vault_id'): + needed.append('vault_password.{}'.format(self.inputs.get('vault_id'))) + else: + needed.append('vault_password') return needed def _password_field_allows_ask(self, field): @@ -369,15 +374,23 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): field_val[k] = '$encrypted$' return field_val - def unique_hash(self): + def unique_hash(self, display=False): ''' Credential exclusivity is not defined solely by the related credential type (due to vault), so this produces a hash that can be used to evaluate exclusivity ''' - if self.kind == 'vault' and self.inputs.get('id', None): - return '{}_{}'.format(self.credential_type_id, self.inputs.get('id')) - return str(self.credential_type_id) + if display: + type_alias = self.kind + else: + type_alias = self.credential_type_id + if self.kind == 'vault' and self.inputs.get('vault_id', None): + if display: + fmt_str = '{} (id={})' + else: + fmt_str = '{}_{}' + return fmt_str.format(type_alias, self.inputs.get('vault_id')) + return str(type_alias) @staticmethod def unique_dict(cred_qs): diff --git a/awx/main/tests/functional/api/test_deprecated_credential_assignment.py b/awx/main/tests/functional/api/test_deprecated_credential_assignment.py index e9090630e2..b84865d052 100644 --- a/awx/main/tests/functional/api/test_deprecated_credential_assignment.py +++ b/awx/main/tests/functional/api/test_deprecated_credential_assignment.py @@ -131,7 +131,7 @@ def test_prevent_multiple_machine_creds(get, post, job_template, admin, machine_ assert get(url, admin).data['count'] == 1 resp = post(url, _new_cred('Second Cred'), admin, expect=400) - assert 'Cannot assign multiple Machine credentials.' in resp.content + assert 'Cannot assign multiple ssh credentials.' in resp.content @pytest.mark.django_db @@ -167,7 +167,7 @@ def test_extra_credentials_unique_by_kind(get, post, job_template, admin, assert get(url, admin).data['count'] == 1 resp = post(url, _new_cred('Second Cred'), admin, expect=400) - assert 'Cannot assign multiple Amazon Web Services credentials.' in resp.content + assert 'Cannot assign multiple aws credentials.' in resp.content @pytest.mark.django_db