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 
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
lib/ansible
plugins/strategy
template
vars

View file

@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile
from ansible.playbook.role import hash_params
from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader
from ansible.template import Templar
from ansible.vars.unsafe_proxy import UnsafeProxy
from ansible.vars.unsafe_proxy import wrap_var
try:
from __main__ import display
@ -244,31 +244,8 @@ class StrategyBase:
# the variable goes in the fact_cache
host = result[1]
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})
elif result[0] in ('set_host_var', 'set_host_facts'):
@ -291,7 +268,8 @@ class StrategyBase:
if result[0] == 'set_host_var':
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)
elif result[0] == 'set_host_facts':
facts = result[4]

View file

@ -19,7 +19,10 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import StringIO
import ast
import contextlib
import os
import re
from six import string_types, text_type, binary_type
@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number )
JINJA2_OVERRIDE = '#jinja2:'
def _escape_backslashes(data, jinja_env):
"""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
return i
class Templar:
'''
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.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):
'''
Returns filter plugins, after loading and caching them if need be
@ -197,6 +208,47 @@ class Templar:
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):
'''
Sets the list of template variables this Templar instance will use
@ -218,11 +270,11 @@ class Templar:
# their constituent type.
if hasattr(variable, '__UNSAFE__'):
if isinstance(variable, text_type):
return text_type(variable)
return self._clean_data(text_type(variable))
elif isinstance(variable, binary_type):
return bytes(variable)
return self._clean_data(bytes(variable))
else:
return variable
return self._clean_data(variable._obj)
try:
if convert_bare:
@ -258,6 +310,7 @@ class Templar:
# FIXME: if the safe_eval raised an error, should we do something with it?
pass
#return self._clean_data(result)
return result
elif isinstance(variable, (list, tuple)):

View file

@ -50,6 +50,8 @@
# http://code.activestate.com/recipes/496741-object-proxying/
# Author: Tomer Filiba
__all__ = ['UnsafeProxy', 'wrap_var']
class UnsafeProxy(object):
__slots__ = ["_obj", "__weakref__"]
def __init__(self, obj):
@ -149,3 +151,25 @@ class UnsafeProxy(object):
ins = object.__new__(theclass)
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