Ensure that data within a tuple is marked as unsafe (#65918)

* Use is_sequence, and Mapping throughout, add support for tuples. Fixes #65722

* Address tests

* Remove unused import

* Add changelog

* Add docstring for clarity

* Argh, linting fix

* Not chasing this rabbit

* wrap_var doesn't return a ref to the original item

* no ref tests

* Remove unused import
This commit is contained in:
Matt Martz 2020-01-07 08:41:37 -06:00 committed by GitHub
parent f9e315671a
commit f8654de851
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 46 additions and 20 deletions

View file

@ -0,0 +1,3 @@
bugfixes:
- unsafe_proxy - Ensure that data within a tuple is marked as unsafe
(https://github.com/ansible/ansible/issues/65722)

View file

@ -662,7 +662,7 @@ class TaskExecutor:
if not isidentifier(self._task.register): if not isidentifier(self._task.register):
raise AnsibleError("Invalid variable name in 'register' specified: '%s'" % self._task.register) raise AnsibleError("Invalid variable name in 'register' specified: '%s'" % self._task.register)
vars_copy[self._task.register] = wrap_var(result) vars_copy[self._task.register] = result = wrap_var(result)
if self._task.async_val > 0: if self._task.async_val > 0:
if self._task.poll > 0 and not result.get('skipped') and not result.get('failed'): if self._task.poll > 0 and not result.get('skipped') and not result.get('failed'):
@ -720,7 +720,7 @@ class TaskExecutor:
# This gives changed/failed_when access to additional recently modified # This gives changed/failed_when access to additional recently modified
# attributes of result # attributes of result
if self._task.register: if self._task.register:
vars_copy[self._task.register] = wrap_var(result) vars_copy[self._task.register] = result = wrap_var(result)
# if we didn't skip this task, use the helpers to evaluate the changed/ # if we didn't skip this task, use the helpers to evaluate the changed/
# failed_when properties # failed_when properties
@ -751,7 +751,7 @@ class TaskExecutor:
# do the final update of the local variables here, for both registered # do the final update of the local variables here, for both registered
# values and any facts which may have been created # values and any facts which may have been created
if self._task.register: if self._task.register:
variables[self._task.register] = wrap_var(result) variables[self._task.register] = result = wrap_var(result)
if 'ansible_facts' in result: if 'ansible_facts' in result:
if self._task.action in ('set_fact', 'include_vars'): if self._task.action in ('set_fact', 'include_vars'):

View file

@ -44,6 +44,7 @@ from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVar
from ansible.module_utils.six import iteritems, string_types, text_type from ansible.module_utils.six import iteritems, string_types, text_type
from ansible.module_utils._text import to_native, to_text, to_bytes from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible.module_utils.common._collections_compat import Sequence, Mapping, MutableMapping from ansible.module_utils.common._collections_compat import Sequence, Mapping, MutableMapping
from ansible.module_utils.common.collections import is_sequence
from ansible.plugins.loader import filter_loader, lookup_loader, test_loader from ansible.plugins.loader import filter_loader, lookup_loader, test_loader
from ansible.template.safe_eval import safe_eval from ansible.template.safe_eval import safe_eval
from ansible.template.template import AnsibleJ2Template from ansible.template.template import AnsibleJ2Template
@ -632,7 +633,7 @@ class Templar:
return result return result
elif isinstance(variable, (list, tuple)): elif is_sequence(variable):
return [self.template( return [self.template(
v, v,
preserve_trailing_newlines=preserve_trailing_newlines, preserve_trailing_newlines=preserve_trailing_newlines,
@ -640,7 +641,7 @@ class Templar:
overrides=overrides, overrides=overrides,
disable_lookups=disable_lookups, disable_lookups=disable_lookups,
) for v in variable] ) for v in variable]
elif isinstance(variable, (dict, Mapping)): elif isinstance(variable, Mapping):
d = {} d = {}
# we don't use iteritems() here to avoid problems if the underlying dict # we don't use iteritems() here to avoid problems if the underlying dict
# changes sizes due to the templating, which can happen with hostvars # changes sizes due to the templating, which can happen with hostvars

View file

@ -54,7 +54,8 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common._collections_compat import Mapping, MutableSequence, Set from ansible.module_utils.common._collections_compat import Mapping, Set
from ansible.module_utils.common.collections import is_sequence
from ansible.module_utils.six import string_types, binary_type, text_type from ansible.module_utils.six import string_types, binary_type, text_type
@ -97,21 +98,19 @@ class UnsafeProxy(object):
def _wrap_dict(v): def _wrap_dict(v):
for k in v.keys(): return dict((wrap_var(k), wrap_var(item)) for k, item in v.items())
if v[k] is not None:
v[wrap_var(k)] = wrap_var(v[k])
return v
def _wrap_list(v): def _wrap_sequence(v):
for idx, item in enumerate(v): """Wraps a sequence with unsafe, not meant for strings, primarily
if item is not None: ``tuple`` and ``list``
v[idx] = wrap_var(item) """
return v v_type = type(v)
return v_type(wrap_var(item) for item in v)
def _wrap_set(v): def _wrap_set(v):
return set(item if item is None else wrap_var(item) for item in v) return set(wrap_var(item) for item in v)
def wrap_var(v): def wrap_var(v):
@ -120,10 +119,10 @@ def wrap_var(v):
if isinstance(v, Mapping): if isinstance(v, Mapping):
v = _wrap_dict(v) v = _wrap_dict(v)
elif isinstance(v, MutableSequence):
v = _wrap_list(v)
elif isinstance(v, Set): elif isinstance(v, Set):
v = _wrap_set(v) v = _wrap_set(v)
elif is_sequence(v):
v = _wrap_sequence(v)
elif isinstance(v, binary_type): elif isinstance(v, binary_type):
v = AnsibleUnsafeBytes(v) v = AnsibleUnsafeBytes(v)
elif isinstance(v, text_type): elif isinstance(v, text_type):

View file

@ -62,8 +62,12 @@ def test_wrap_var_set_None():
def test_wrap_var_tuple(): def test_wrap_var_tuple():
assert isinstance(wrap_var(('foo',)), tuple) assert isinstance(wrap_var(('foo',)), tuple)
assert not isinstance(wrap_var(('foo',)), AnsibleUnsafe) assert not isinstance(wrap_var(('foo',)), AnsibleUnsafe)
assert isinstance(wrap_var(('foo',))[0], type('')) assert isinstance(wrap_var(('foo',))[0], AnsibleUnsafe)
assert not isinstance(wrap_var(('foo',))[0], AnsibleUnsafe)
def test_wrap_var_tuple_None():
assert wrap_var((None,))[0] is None
assert not isinstance(wrap_var((None,))[0], AnsibleUnsafe)
def test_wrap_var_None(): def test_wrap_var_None():
@ -79,6 +83,25 @@ def test_wrap_var_unsafe_bytes():
assert isinstance(wrap_var(AnsibleUnsafeBytes(b'foo')), AnsibleUnsafeBytes) assert isinstance(wrap_var(AnsibleUnsafeBytes(b'foo')), AnsibleUnsafeBytes)
def test_wrap_var_no_ref():
thing = {
'foo': {
'bar': 'baz'
},
'bar': ['baz', 'qux'],
'baz': ('qux',),
'none': None,
'text': 'text',
}
wrapped_thing = wrap_var(thing)
thing is not wrapped_thing
thing['foo'] is not wrapped_thing['foo']
thing['bar'][0] is not wrapped_thing['bar'][0]
thing['baz'][0] is not wrapped_thing['baz'][0]
thing['none'] is not wrapped_thing['none']
thing['text'] is not wrapped_thing['text']
def test_AnsibleUnsafeText(): def test_AnsibleUnsafeText():
assert isinstance(AnsibleUnsafeText(u'foo'), AnsibleUnsafe) assert isinstance(AnsibleUnsafeText(u'foo'), AnsibleUnsafe)