1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

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.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