Clean string data run through the template engine

Also strip UnsafeProxy off of low level srings and objects to ensure
they don't cause issues later down the road

Fixes #12513
This commit is contained in:
James Cammarata 2015-09-25 14:54:20 -04:00
parent ae9b34b1d9
commit cdc6c5208e
3 changed files with 84 additions and 29 deletions

View file

@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile
from ansible.playbook.role import hash_params from ansible.playbook.role import hash_params
from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader
from ansible.template import Templar from ansible.template import Templar
from ansible.vars.unsafe_proxy import UnsafeProxy from ansible.vars.unsafe_proxy import wrap_var
try: try:
from __main__ import display from __main__ import display
@ -244,31 +244,8 @@ class StrategyBase:
# the variable goes in the fact_cache # the variable goes in the fact_cache
host = result[1] host = result[1]
var_name = result[2] var_name = result[2]
var_value = result[3] var_value = wrap_var(result[3])
def _wrap_var(v):
if isinstance(v, dict):
v = _wrap_dict(v)
elif isinstance(v, list):
v = _wrap_list(v)
else:
if v is not None and not isinstance(v, UnsafeProxy):
v = UnsafeProxy(v)
return v
def _wrap_dict(v):
for k in v.keys():
if v[k] is not None and not isinstance(v[k], UnsafeProxy):
v[k] = _wrap_var(v[k])
return v
def _wrap_list(v):
for idx, item in enumerate(v):
if item is not None and not isinstance(item, UnsafeProxy):
v[idx] = _wrap_var(item)
return v
var_value = _wrap_var(var_value)
self._variable_manager.set_nonpersistent_facts(host, {var_name: var_value}) self._variable_manager.set_nonpersistent_facts(host, {var_name: var_value})
elif result[0] in ('set_host_var', 'set_host_facts'): elif result[0] in ('set_host_var', 'set_host_facts'):
@ -291,7 +268,8 @@ class StrategyBase:
if result[0] == 'set_host_var': if result[0] == 'set_host_var':
var_name = result[4] var_name = result[4]
var_value = result[5] var_value = wrap_var(result[5])
self._variable_manager.set_host_variable(target_host, var_name, var_value) self._variable_manager.set_host_variable(target_host, var_name, var_value)
elif result[0] == 'set_host_facts': elif result[0] == 'set_host_facts':
facts = result[4] facts = result[4]

View file

@ -19,7 +19,10 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import StringIO
import ast import ast
import contextlib
import os
import re import re
from six import string_types, text_type, binary_type from six import string_types, text_type, binary_type
@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number )
JINJA2_OVERRIDE = '#jinja2:' JINJA2_OVERRIDE = '#jinja2:'
def _escape_backslashes(data, jinja_env): def _escape_backslashes(data, jinja_env):
"""Double backslashes within jinja2 expressions """Double backslashes within jinja2 expressions
@ -108,6 +112,7 @@ def _count_newlines_from_end(in_str):
# Uncommon cases: zero length string and string containing only newlines # Uncommon cases: zero length string and string containing only newlines
return i return i
class Templar: class Templar:
''' '''
The main class for templating, with the main entry-point of template(). The main class for templating, with the main entry-point of template().
@ -148,6 +153,12 @@ class Templar:
self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string)) self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string))
self.block_start = self.environment.block_start_string
self.block_end = self.environment.block_end_string
self.variable_start = self.environment.variable_start_string
self.variable_end = self.environment.variable_end_string
self._clean_regex = re.compile(r'(?:%s[%s%s]|[%s%s]%s)' % (self.variable_start[0], self.variable_start[1], self.block_start[1], self.block_end[0], self.variable_end[0], self.variable_end[1]))
def _get_filters(self): def _get_filters(self):
''' '''
Returns filter plugins, after loading and caching them if need be Returns filter plugins, after loading and caching them if need be
@ -197,6 +208,47 @@ class Templar:
return jinja_exts return jinja_exts
def _clean_data(self, orig_data):
''' remove jinja2 template tags from a string '''
if not isinstance(orig_data, string_types):
return orig_data
with contextlib.closing(StringIO.StringIO(orig_data)) as data:
# these variables keep track of opening block locations, as we only
# want to replace matched pairs of print/block tags
print_openings = []
block_openings = []
for mo in self._clean_regex.finditer(orig_data):
token = mo.group(0)
token_start = mo.start(0)
if token[0] == self.variable_start[0]:
if token == self.block_start:
block_openings.append(token_start)
elif token == self.variable_start:
print_openings.append(token_start)
elif token[1] == self.variable_end[1]:
prev_idx = None
if token == '%}' and block_openings:
prev_idx = block_openings.pop()
elif token == '}}' and print_openings:
prev_idx = print_openings.pop()
if prev_idx is not None:
# replace the opening
data.seek(prev_idx, os.SEEK_SET)
data.write('{#')
# replace the closing
data.seek(token_start, os.SEEK_SET)
data.write('#}')
else:
raise AnsibleError("Error while cleaning data for safety: unhandled regex match")
return data.getvalue()
def set_available_variables(self, variables): def set_available_variables(self, variables):
''' '''
Sets the list of template variables this Templar instance will use Sets the list of template variables this Templar instance will use
@ -218,11 +270,11 @@ class Templar:
# their constituent type. # their constituent type.
if hasattr(variable, '__UNSAFE__'): if hasattr(variable, '__UNSAFE__'):
if isinstance(variable, text_type): if isinstance(variable, text_type):
return text_type(variable) return self._clean_data(text_type(variable))
elif isinstance(variable, binary_type): elif isinstance(variable, binary_type):
return bytes(variable) return self._clean_data(bytes(variable))
else: else:
return variable return self._clean_data(variable._obj)
try: try:
if convert_bare: if convert_bare:
@ -258,6 +310,7 @@ class Templar:
# FIXME: if the safe_eval raised an error, should we do something with it? # FIXME: if the safe_eval raised an error, should we do something with it?
pass pass
#return self._clean_data(result)
return result return result
elif isinstance(variable, (list, tuple)): elif isinstance(variable, (list, tuple)):

View file

@ -50,6 +50,8 @@
# http://code.activestate.com/recipes/496741-object-proxying/ # http://code.activestate.com/recipes/496741-object-proxying/
# Author: Tomer Filiba # Author: Tomer Filiba
__all__ = ['UnsafeProxy', 'wrap_var']
class UnsafeProxy(object): class UnsafeProxy(object):
__slots__ = ["_obj", "__weakref__"] __slots__ = ["_obj", "__weakref__"]
def __init__(self, obj): def __init__(self, obj):
@ -149,3 +151,25 @@ class UnsafeProxy(object):
ins = object.__new__(theclass) ins = object.__new__(theclass)
return ins return ins
def _wrap_dict(v):
for k in v.keys():
if v[k] is not None and not isinstance(v[k], UnsafeProxy):
v[k] = _wrap_var(v[k])
return v
def _wrap_list(v):
for idx, item in enumerate(v):
if item is not None and not isinstance(item, UnsafeProxy):
v[idx] = _wrap_var(item)
return v
def wrap_var(v):
if isinstance(v, dict):
v = _wrap_dict(v)
elif isinstance(v, list):
v = _wrap_list(v)
else:
if v is not None and not isinstance(v, UnsafeProxy):
v = UnsafeProxy(v)
return v