Login

Safe template decorator

Author:
eternicode
Posted:
May 16, 2012
Language:
Python
Version:
1.3
Tags:
template clean safe restrict
Score:
1 (after 1 ratings)

A decorator that restricts the tags and filters available to template loading and parsing within a function.

This is mainly meant to be used when granting users the power of the DTL. You obviously don't want users to be able to do things that could be potentially malicious.

The {% ssi %} tag, for example, could be used to display sensitive data if improperly configured.

{% load %} gives them access to all the unlimited python code you wrote in your templatetags. {% load sudo %}{% sudo rm -rf / %} o_0

Note that the "load" tag (among others) is not listed in the default tag whitelist. If you parse a template (however indirectly) in a function decorated with this, unlisted builtin tags will behave like undefined tags (ie, they will result in a TemplateSyntaxError).

Since {% load %} is not whitelisted, you may want to include some custom tags or filters as "builtins" for convenience. Simply put the module paths to the libraries to include in the extra kwarg or the extra_libraries list. Generally, this is not recommended, as these libraries need to be carefully and defensively programmed.

NOTE: This does not do anything about cleaning your rendering context! That's completely up to you! This merely restricts what tags and filters are allowed in the templates.

Examples:

from django.template.loader import get_template
safe_get_template = use_safe_templates(get_template)
tmpl = safe_get_template('myapp/some_template.html')

from django.template import Template
use_safe_templates(Template)('{% load sudo %}')
# TemplateSyntaxError: Invalid block tag 'load'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from django.utils.functional import wraps
from django import template

# Default tag whitelist
whitelist_tags = [
  'comment', 'csrf_token', 'cycle', 'filter', 'firstof', 'for', 'if',
  'ifchanged', 'now', 'regroup', 'spaceless', 'templatetag', 'url',
  'widthratio', 'with'
]

# Default filter whitelist
whitelist_filters = [
  'add', 'addslashes', 'capfirst', 'center', 'cut', 'date', 'default',
  'default_if_none', 'dictsort', 'dictsortreversed', 'divisibleby', 'escape',
  'escapejs', 'filesizeformat', 'first', 'fix_ampersands', 'floatformat',
  'force_escape', 'get_digit', 'iriencode', 'join', 'last', 'length', 'length_is',
  'linebreaks', 'linebreaksbr', 'linenumbers', 'ljust', 'lower', 'make_list',
  'phone2numeric', 'pluralize', 'pprint', 'random', 'removetags', 'rjust', 'safe',
  'safeseq', 'slice', 'slugify', 'stringformat', 'striptags', 'time', 'timesince',
  'timeuntil', 'title', 'truncatewords', 'truncatewords_html', 'unordered_list',
  'upper', 'urlencode', 'urlize', 'urlizetrunc', 'wordcount', 'wordwrap', 'yesno'
]

# Custom libraries to add to builtins
extra_libraries = [
    'myproject.myapp.templatetags.extra_tags',
    'myproject.myapp.templatetags.extra_filters',
]

def use_safe_templates(tags=None, filters=None, extra=None):
  """
  Cleans the builtin template libraries before running the function (restoring
  the builtins afterwards).

  Removes any builtin tags and filters that are not enumerated in `tags` and
  `filters`, and adds the extra library modules in `extra` to the builtins.

  Does not catch any template exceptions (notably, TemplateSyntaxError and
  TemplateDoesNotExist may be raised).
  """
  def decorate(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
      # Clean out default libraries
      # Note: template.builtins is merely a convenience import, we have to work
      # with template.base.builtins for this to work right.
      template.base.builtins, default_libs = [], template.base.builtins
      try:
        # Construct new builtin with only whitelisted tags/filters
        whitelist = template.Library()
        for lib in default_libs:
          for tag in lib.tags:
            if tag in tags:
              whitelist.tags[tag] = lib.tags[tag]
          for filter in lib.filters:
            if filter in filters:
              whitelist.filters[filter] = lib.filters[filter]

        # Install whitelist library and extras as builtins
        template.base.builtins.append(whitelist)
        [template.add_to_builtins(e) for e in extra]

        return func(*args, **kwargs)
      finally:
        # Restore the builtins to their former defaults
        template.base.builtins = default_libs
    return wrapped

  if callable(tags):
    # @use_safe_templates
    func = tags
    tags = whitelist_tags
    filters = whitelist_filters
    extra = extra_libraries
    return decorate(func)
  else:
    # @use_safe_templates(...)
    tags = tags or whitelist_tags
    filters = filters or whitelist_filters
    extra = extra or extra_libraries
    return decorate

More like this

  1. FloatField with safe expression parsing by joelegner 5 years, 3 months ago
  2. Template class to test custom tag libraries by a.v.khodyrev 5 years, 11 months ago
  3. Smart {% if %} template tag by SmileyChris 6 years, 4 months ago
  4. "for" template tag with support for "else" if array is empty by jezdez 7 years, 5 months ago
  5. Jinja2 integration + application specific functions/filters/tests by hasenj 6 years, 9 months ago

Comments

Please login first before commenting.