Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from django.template import Template
- class SafeTemplate(Template):
- """
- Subclass of the regular django Template but disallows rendering anything
- that will call a method on a class thus making it safe for use as a user
- editable object
- Examples:
- # this will return the template as expected
- >> template = SafeTemplate('{{ server.hostname }}', server=server)
- >> template.render(context)
- 'reina-1'
- # but since this calls a method on server, it will return an empty
- # string
- >> template = SafeTemplate('{{ server.power_off }}', server=server)
- >> template.render(context)
- ''
- Discussion:
- Django offers a way of specifying if a method should not be called
- during template rendering. See alters_data in the django docs which is
- an attr that can be set on any class method. However, django defaults
- this attr to False for all methods. If we were to use this route of
- solving the problem at hand, we would have to set this attr all over
- the place. That's not reasonable.
- Allowed: model fields, custom fields, and properties on a server
- Disallowed: all methods on django's RelatedManager objects, all things
- that return true for inspect.ismethod = True
- Eventually it would be nice to include a white/blacklist to speed this
- process up
- """
- def render(self, context, autoescape=True):
- """
- Rebuild the template.nodelist to filter out any Nodes we don't like
- This accepts any TextNode types, conditionally accepts any VariableNode
- types, and rejects all other node types (ex: comments, forloops, etc)
- """
- final_nodelist = DebugNodeList()
- for node in self.nodelist:
- if isinstance(node, VariableNode):
- # this is some sort of variable call so we need to confirm that
- # its allowable
- lookups = node.filter_expression.var.lookups
- if not self.alters_data(lookups, context):
- final_nodelist.append(node)
- elif isinstance(node, TextNode):
- # this is just text so we'll allow it
- final_nodelist.append(node)
- self.nodelist = final_nodelist
- rendered_data = super(SafeTemplate, self).render(context)
- if not autoescape:
- # When rendering a template, django autoescapes by default. This
- # means that all quotation marks, greaterthan/lessthan signs, etc
- # will be escaped. In some cases we may not want this (ex. if this
- # is a script to be run on a server).
- rendered_data = HTMLParser().unescape(rendered_data)
- return rendered_data
- def alters_data(self, var_lookups, context):
- """
- Determine if the list of var_lookups would produce a method call,
- return True if so
- `var_lookups` is a tuple of strings as returned by
- node.filter_expression.var.lookups (see render() above). This list is
- basically the template node's string value split at '.'
- Examples:
- When var_lookups = ('server', 'hostname'), the django template
- rendering would call server.hostname which we will allow
- When var_lookups = ('server', 'environment', 'server_set', 'all',
- 'delete') the user is trying to delete all servers from this
- environment. Since calling `all` is a method, it is disallowed
- """
- # The first lookup will be an object found in the context dictionary
- this_lookup = context[var_lookups[0]]
- for lookup in var_lookups[1:]:
- try:
- attr = this_lookup.__getattribute__(lookup)
- except AttributeError:
- # If this is a custom field on a server, then __getattribute__
- # throws an AttributeError. Therefore, we must call
- # get_value_for_custom_field directly.
- attr = this_lookup.get_value_for_custom_field(lookup)
- if inspect.ismethod(attr):
- return True
- this_lookup = attr
- return False
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement