diff --git a/changelogs/fragments/ajson-nested-decode.yaml b/changelogs/fragments/ajson-nested-decode.yaml new file mode 100644 index 00000000000..15feb133fe2 --- /dev/null +++ b/changelogs/fragments/ajson-nested-decode.yaml @@ -0,0 +1,2 @@ +bugfixes: +- Ansible JSON Decoder - Switch from decode to object_hook to support nested use of __ansible_vault and __ansible_unsafe (https://github.com/ansible/ansible/pull/45514) diff --git a/lib/ansible/parsing/ajson.py b/lib/ansible/parsing/ajson.py index 7444a9f4035..36c693dead8 100644 --- a/lib/ansible/parsing/ajson.py +++ b/lib/ansible/parsing/ajson.py @@ -20,33 +20,27 @@ class AnsibleJSONDecoder(json.JSONDecoder): _vaults = {} + def __init__(self, *args, **kwargs): + kwargs['object_hook'] = self.object_hook + super(AnsibleJSONDecoder, self).__init__(*args, **kwargs) + @classmethod def set_secrets(cls, secrets): cls._vaults['default'] = VaultLib(secrets=secrets) - def _decode_map(self, value): - - if value.get('__ansible_unsafe', False): - value = wrap_var(value.get('__ansible_unsafe')) - elif value.get('__ansible_vault', False): - value = AnsibleVaultEncryptedUnicode(value.get('__ansible_vault')) - if self._vaults: - value.vault = self._vaults['default'] - else: - for k in value: - if isinstance(value[k], Mapping): - value[k] = self._decode_map(value[k]) - return value + def object_hook(self, pairs): + for key in pairs: + value = pairs[key] - def decode(self, obj): - ''' use basic json decoding except for specific ansible objects unsafe and vault ''' + if key == '__ansible_vault': + value = AnsibleVaultEncryptedUnicode(value) + if self._vaults: + value.vault = self._vaults['default'] + return value + elif key == '__ansible_unsafe': + return wrap_var(value.get('__ansible_unsafe')) - value = super(AnsibleJSONDecoder, self).decode(obj) - - if isinstance(value, Mapping): - value = self._decode_map(value) - - return value + return pairs # TODO: find way to integrate with the encoding modules do in module_utils diff --git a/test/units/parsing/fixtures/ajson.json b/test/units/parsing/fixtures/ajson.json new file mode 100644 index 00000000000..d42b7dc070a --- /dev/null +++ b/test/units/parsing/fixtures/ajson.json @@ -0,0 +1,19 @@ +{ + "password": { + "__ansible_vault": "$ANSIBLE_VAULT;1.1;AES256\n34646264306632313333393636316562356435376162633631326264383934326565333633366238\n3863373264326461623132613931346165636465346337310a326434313830316337393263616439\n64653937313463396366633861363266633465663730303633323534363331316164623237363831\n3536333561393238370a313330316263373938326162386433313336613532653538376662306435\n3339\n" + }, + "bar": { + "baz": [ + { + "password": { + "__ansible_vault": "$ANSIBLE_VAULT;1.1;AES256\n34646264306632313333393636316562356435376162633631326264383934326565333633366238\n3863373264326461623132613931346165636465346337310a326434313830316337393263616439\n64653937313463396366633861363266633465663730303633323534363331316164623237363831\n3536333561393238370a313330316263373938326162386433313336613532653538376662306435\n3339\n" + } + } + ] + }, + "foo": { + "password": { + "__ansible_vault": "$ANSIBLE_VAULT;1.1;AES256\n34646264306632313333393636316562356435376162633631326264383934326565333633366238\n3863373264326461623132613931346165636465346337310a326434313830316337393263616439\n64653937313463396366633861363266633465663730303633323534363331316164623237363831\n3536333561393238370a313330316263373938326162386433313336613532653538376662306435\n3339\n" + } + } +} diff --git a/test/units/parsing/test_ajson.py b/test/units/parsing/test_ajson.py new file mode 100644 index 00000000000..6fb54b3775a --- /dev/null +++ b/test/units/parsing/test_ajson.py @@ -0,0 +1,22 @@ +# Copyright 2018, Matt Martz +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import os +import json + +import pytest + +from ansible.parsing.ajson import AnsibleJSONDecoder +from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode + + +def test_AnsibleJSONDecoder_vault(): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/ajson.json')) as f: + data = json.load(f, cls=AnsibleJSONDecoder) + + assert isinstance(data['password'], AnsibleVaultEncryptedUnicode) + assert isinstance(data['bar']['baz'][0]['password'], AnsibleVaultEncryptedUnicode) + assert isinstance(data['foo']['password'], AnsibleVaultEncryptedUnicode)