diff options
| author | Vladislav Poluhin | 2013-04-23 14:17:55 +0800 | 
|---|---|---|
| committer | Vladislav Poluhin | 2013-04-23 14:17:55 +0800 | 
| commit | 918519f8540cca98fee96eec0f3e2f9afb2e3073 (patch) | |
| tree | f5e24e574b7b83f0e50af78d7759b447e862b2ee | |
| parent | 92e2dc9c81508239c11f9cae5295715e64abd9b5 (diff) | |
| download | django-debug-toolbar-918519f8540cca98fee96eec0f3e2f9afb2e3073.tar.bz2 | |
Form for SQL validation
| -rw-r--r-- | debug_toolbar/forms.py | 92 | ||||
| -rw-r--r-- | debug_toolbar/panels/sql.py | 5 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/sql.html | 23 | ||||
| -rw-r--r-- | debug_toolbar/utils/tracking/db.py | 3 | ||||
| -rw-r--r-- | debug_toolbar/views.py | 110 | 
5 files changed, 140 insertions, 93 deletions
| diff --git a/debug_toolbar/forms.py b/debug_toolbar/forms.py new file mode 100644 index 0000000..d795c17 --- /dev/null +++ b/debug_toolbar/forms.py @@ -0,0 +1,92 @@ +from django import forms +from django.conf import settings +from django.core.exceptions import ValidationError +from django.utils.functional import cached_property + +try: +    import json +except ImportError: +    from django.utils import simplejson as json + +try: +    from hashlib import sha1 +except ImportError: +    from django.utils.hashcompat import sha_constructor as sha1 + +from debug_toolbar.utils.compat.db import connections + + +class SQLSelectForm(forms.Form): +    """ +    Validate params + +        sql: urlencoded sql with positional arguments +        params: JSON encoded parameter values +        duration: time for SQL to execute passed in from toolbar just for redisplay +        hash: the hash of (secret + sql + params) for tamper checking +    """ +    sql = forms.CharField() +    params = forms.CharField() +    alias = forms.CharField(required=False, initial='default') +    duration = forms.FloatField() +    hash = forms.CharField() + +    def __init__(self, *args, **kwargs): +        initial = kwargs.get('initial', None) + +        if initial is not None: +            initial['hash'] = self.make_hash(initial) + +        super(SQLSelectForm, self).__init__(*args, **kwargs) + +        for name in self.fields: +            self.fields[name].widget = forms.HiddenInput() + +    def clean_sql(self): +        value = self.cleaned_data['sql'] + +        if not value.lower().strip().startswith('select'): +            raise ValidationError("Only 'select' queries are allowed.") + +        return value + +    def clean_params(self): +        value = self.cleaned_data['params'] + +        try: +            return json.loads(value) +        except ValueError: +            raise ValidationError('Is not valid JSON') + +    def clean_alias(self): +        value = self.cleaned_data['alias'] + +        if value not in connections: +            raise ValidationError("Database alias '%s' not found" % value) + +        return value + +    def clean_hash(self): +        hash = self.cleaned_data['hash'] + +        if hash != self.make_hash(self.data): +            raise ValidationError('Tamper alert') + +        return hash + +    def reformat_sql(self): +        from debug_toolbar.panels.sql import reformat_sql +        sql, params = self.cleaned_data['sql'], self.cleaned_data['params'] +        return reformat_sql(self.cursor.db.ops.last_executed_query(self.cursor, sql, params)) + +    def make_hash(self, data): +        params = settings.SECRET_KEY + data['sql'] + data['params'] +        return sha1(params).hexdigest() + +    @property +    def connection(self): +        return connections[self.cleaned_data['alias']] + +    @cached_property +    def cursor(self): +        return self.connection.cursor() diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index a492888..e434014 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -1,10 +1,12 @@  import re  import uuid +from copy import copy  from django.db.backends import BaseDatabaseWrapper  from django.utils.html import escape  from django.utils.translation import ugettext_lazy as _, ungettext_lazy as __ +from debug_toolbar.forms import SQLSelectForm  from debug_toolbar.utils.compat.db import connections  from debug_toolbar.middleware import DebugToolbarMiddleware  from debug_toolbar.panels import DebugPanel @@ -170,6 +172,9 @@ class SQLDebugPanel(DebugPanel):                      query['iso_level'] = get_isolation_level_display(query['engine'], query['iso_level'])                  if 'trans_status' in query:                      query['trans_status'] = get_transaction_status_display(query['engine'], query['trans_status']) + +                query['form'] = SQLSelectForm(auto_id=None, initial=copy(query)) +                  if query['sql']:                      query['sql'] = reformat_sql(query['sql'])                  query['rgb_color'] = self._databases[alias]['rgb_color'] diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html index 6cdda34..087b50e 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -44,21 +44,18 @@  					{% if query.params %}  						{% if query.is_select %} +							<form method="post"> +								{% for field in query.form.hidden_fields %} +									{{ field }} +								{% endfor %} -                            <form method="post"> -                                <input type="hidden" name="sql" value="{{ query.raw_sql }}" /> -                                <input type="hidden" name="params" value="{{ query.params }}" /> -                                <input type="hidden" name="duration" value="{{ query.duration|floatformat:"2" }}" /> -                                <input type="hidden" name="hash" value="{{ query.hash }}" /> -                                <input type="hidden" name="alias" value="{{ query.alias }}" /> +								<button formaction="/__debug__/sql_select/" class="remoteCall">Sel</button> +								<button formaction="/__debug__/sql_explain/" class="remoteCall">Expl</button> -                                <button formaction="/__debug__/sql_select/" class="remoteCall">Sel</button> -                                <button formaction="/__debug__/sql_explain/" class="remoteCall">Expl</button> - -                                {% ifequal query.engine 'mysql' %} -                                    <button formaction="/__debug__/sql_profile/" class="remoteCall">Prof</button> -                                {% endifequal %} -                            </form> +								{% ifequal query.engine 'mysql' %} +									<button formaction="/__debug__/sql_profile/" class="remoteCall">Prof</button> +								{% endifequal %} +							</form>  						{% endif %}  					{% endif %}  					</td> diff --git a/debug_toolbar/utils/tracking/db.py b/debug_toolbar/utils/tracking/db.py index 0dc22a6..68408c3 100644 --- a/debug_toolbar/utils/tracking/db.py +++ b/debug_toolbar/utils/tracking/db.py @@ -139,9 +139,6 @@ class NormalCursorWrapper(object):                  'duration': duration,                  'raw_sql': sql,                  'params': _params, -                'hash': sha1(settings.SECRET_KEY \ -                                        + smart_str(sql) \ -                                        + _params).hexdigest(),                  'stacktrace': stacktrace,                  'start_time': start,                  'stop_time': stop, diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index b1a045e..22b03d9 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -4,87 +4,55 @@ debug toolbar is displayed, and typically can do Bad Things, so hooking up these  views in any other way is generally not advised.  """ -from django.conf import settings  from django.http import HttpResponseBadRequest  from django.shortcuts import render_to_response  from django.utils import simplejson  from django.views.decorators.csrf import csrf_exempt -from debug_toolbar.utils.compat.db import connections  try:      from hashlib import sha1  except ImportError:      from django.utils.hashcompat import sha_constructor as sha1 - -class InvalidSQLError(Exception): -    def __init__(self, value): -        self.value = value - -    def __str__(self): -        return repr(self.value) +from debug_toolbar.forms import SQLSelectForm  @csrf_exempt  def sql_select(request): -    """ -    Returns the output of the SQL SELECT statement. +    """Returns the output of the SQL SELECT statement""" +    form = SQLSelectForm(request.POST or None) -    Expected GET variables: -        sql: urlencoded sql with positional arguments -        params: JSON encoded parameter values -        duration: time for SQL to execute passed in from toolbar just for redisplay -        hash: the hash of (secret + sql + params) for tamper checking -    """ -    from debug_toolbar.panels.sql import reformat_sql -    sql = request.REQUEST.get('sql', '') -    params = request.REQUEST.get('params', '') -    alias = request.REQUEST.get('alias', 'default') -    hash = sha1(settings.SECRET_KEY + sql + params).hexdigest() -    if hash != request.REQUEST.get('hash', ''): -        return HttpResponseBadRequest('Tamper alert')  # SQL Tampering alert -    if sql.lower().strip().startswith('select'): -        params = simplejson.loads(params) -        cursor = connections[alias].cursor() +    if form.is_valid(): +        sql = form.cleaned_data['sql'] +        params = form.cleaned_data['params'] +        cursor = form.cursor          cursor.execute(sql, params)          headers = [d[0] for d in cursor.description]          result = cursor.fetchall()          cursor.close()          context = {              'result': result, -            'sql': reformat_sql(cursor.db.ops.last_executed_query(cursor, sql, params)), -            'duration': request.REQUEST.get('duration', 0.0), +            'sql': form.reformat_sql(), +            'duration': form.cleaned_data['duration'],              'headers': headers, -            'alias': alias, +            'alias': form.cleaned_data['alias'],          }          return render_to_response('debug_toolbar/panels/sql_select.html', context) -    raise InvalidSQLError("Only 'select' queries are allowed.") +    return HttpResponseBadRequest('Form errors')  @csrf_exempt  def sql_explain(request): -    """ -    Returns the output of the SQL EXPLAIN on the given query. +    """Returns the output of the SQL EXPLAIN on the given query""" +    form = SQLSelectForm(request.POST or None) -    Expected GET variables: -        sql: urlencoded sql with positional arguments -        params: JSON encoded parameter values -        duration: time for SQL to execute passed in from toolbar just for redisplay -        hash: the hash of (secret + sql + params) for tamper checking -    """ -    from debug_toolbar.panels.sql import reformat_sql -    sql = request.REQUEST.get('sql', '') -    params = request.REQUEST.get('params', '') -    alias = request.REQUEST.get('alias', 'default') -    hash = sha1(settings.SECRET_KEY + sql + params).hexdigest() -    if hash != request.REQUEST.get('hash', ''): -        return HttpResponseBadRequest('Tamper alert')  # SQL Tampering alert -    if sql.lower().strip().startswith('select'): -        params = simplejson.loads(params) -        cursor = connections[alias].cursor() - -        conn = connections[alias].connection +    if form.is_valid(): +        sql = form.cleaned_data['sql'] +        params = form.cleaned_data['params'] +        cursor = form.cursor + +        conn = form.connection          engine = conn.__class__.__module__.split('.', 1)[0]          if engine == "sqlite3": @@ -100,36 +68,24 @@ def sql_explain(request):          cursor.close()          context = {              'result': result, -            'sql': reformat_sql(cursor.db.ops.last_executed_query(cursor, sql, params)), -            'duration': request.REQUEST.get('duration', 0.0), +            'sql': form.reformat_sql(), +            'duration': form.cleaned_data['duration'],              'headers': headers, -            'alias': alias, +            'alias': form.cleaned_data['alias'],          }          return render_to_response('debug_toolbar/panels/sql_explain.html', context) -    raise InvalidSQLError("Only 'select' queries are allowed.") +    return HttpResponseBadRequest('Form errors')  @csrf_exempt  def sql_profile(request): -    """ -    Returns the output of running the SQL and getting the profiling statistics. +    """Returns the output of running the SQL and getting the profiling statistics""" +    form = SQLSelectForm(request.POST or None) -    Expected GET variables: -        sql: urlencoded sql with positional arguments -        params: JSON encoded parameter values -        duration: time for SQL to execute passed in from toolbar just for redisplay -        hash: the hash of (secret + sql + params) for tamper checking -    """ -    from debug_toolbar.panels.sql import reformat_sql -    sql = request.REQUEST.get('sql', '') -    params = request.REQUEST.get('params', '') -    alias = request.REQUEST.get('alias', 'default') -    hash = sha1(settings.SECRET_KEY + sql + params).hexdigest() -    if hash != request.REQUEST.get('hash', ''): -        return HttpResponseBadRequest('Tamper alert')  # SQL Tampering alert -    if sql.lower().strip().startswith('select'): -        params = simplejson.loads(params) -        cursor = connections[alias].cursor() +    if form.is_valid(): +        sql = form.cleaned_data['sql'] +        params = form.cleaned_data['params'] +        cursor = form.cursor          result = None          headers = None          result_error = None @@ -147,13 +103,13 @@ def sql_profile(request):          context = {              'result': result,              'result_error': result_error, -            'sql': reformat_sql(cursor.db.ops.last_executed_query(cursor, sql, params)), -            'duration': request.REQUEST.get('duration', 0.0), +            'sql': form.reformat_sql(), +            'duration': form.cleaned_data['duration'],              'headers': headers, -            'alias': alias, +            'alias': form.cleaned_data['alias'],          }          return render_to_response('debug_toolbar/panels/sql_profile.html', context) -    raise InvalidSQLError("Only 'select' queries are allowed.") +    return HttpResponseBadRequest('Form errors')  def template_source(request): | 
