3

I have a Django SaaS app with users who create their own Jinja2 templates (in an extraordinarily sandboxed environment, for those who just cringed), which are saved to a database. I have a template_type field, noting whether or not a given template is an "include" or a "full template" (a full template can of course include "includes"). The problem is that a user could put {% include "foo" %} into a template called "bar", and {% include "bar" %} into the "foo" template, causing a RuntimeError: maximum recursion depth exceeded type of situation, which would not be good for performance.

Is there a nice way to handle this situation that doesn't include a validation regexp (e.g., r'\{%\s+include') check for includes during user template creation time ("Make sure a recursive import never gets in the database, or your server will asplode" doesn't quite jive with me).

My failed attempt

I started by using a custom loader which only contains a user's "includes"::

def get_source(self, environment, template):
    """
    Returns the source of the template or raises ``TemplateNotFound``.
    This loader is for use with an environment which intends to load each
    template from a string, since only includes are loaded from here.
    """
    try:
        template = self.snippets[template]
    except KeyError:
        raise jinja2.TemplateNotFound(
            template, message=u'Snippet "{s}" was not found'.format(s=template)
        )
    else:
        source = template['source']
        return (source, None, lambda: True)

The problem with doing this is that I've basically blocked myself from being able to take advantage of Jinja2's Bytecode Cache, which would clearly require that all the templates be available for a load(... call, which in turn calls get_source(....

Community
  • 1
  • 1
orokusaki
  • 55,146
  • 59
  • 179
  • 257
  • 1
    Can you execute a new template at creation time with test data, catch the RumtimeError and then throw an appropriate error message at the user? And temporarily set the recursion limit to a low value (see http://stackoverflow.com/questions/3323001/maximum-recursion-depth)? That doesn't catch conditional includes, though. – Simon Nov 13 '12 at 09:37
  • Have you run across any good guides to the Sandbox features of Jinja2 ? I'm about to start doing the same exact thing as you , and all I've found are the official docs and 3 questions on Stack Overflow. – Jonathan Vanasco Mar 02 '13 at 19:31
  • @Jon - I just dredged through it. What you need to do is use the most limited sandbox class, and then override the "is attribute safe" and "is safe callable" methods (don't remember actual method names) to restrict attempted access to any attributes whose values aren't built in types (or certain expected types), as well as restrict access to calling methods that don't have some "is safe" attribute set to true. This, combined with ensuring that none of your exposed objects have `@property`s on them with dangerous side effects, will protect your app. – Johnny 5 Mar 04 '13 at 00:43
  • Sounds good. Thanks. I'm going to dig through it this week. If you've ever seen the Zephyr templating language from SailThru, that's pretty much what I want to implement. I'm pretty sure every object going into sandbox will just be cast into a JSON-like object, and then I'll allow for a limited subset of functions via a utility object. Though I'm not sure how to do that yet. – Jonathan Vanasco Mar 04 '13 at 00:50

1 Answers1

3

To parse templates and check includes, i use this code:

    ast = env.parse(template_text)
    for each in meta.find_referenced_templates(ast) :        # find the (% includes %}
voscausa
  • 11,253
  • 2
  • 39
  • 67
  • Thanks! Btw, how does using `parse` differ from the `from_string`, or are they just from different versions? Does parse not perform the actual includes? – orokusaki Nov 13 '12 at 13:04
  • 1
    Maybe I understand your question. from_string loads the template. Parse gives you an abstract sytax tree (AST). In my code: each gives you the include tag value. I use the include tags to dynamicly include images, pdfs, urls, css, js, html and txt templates from a CMS and the parser to verify if the referenced file exists, when saving the template. – voscausa Nov 13 '12 at 14:49