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:
parent
ae9b34b1d9
commit
cdc6c5208e
3 changed files with 84 additions and 29 deletions
|
@ -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]
|
||||||
|
|
|
@ -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)):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue