Commit 49850f94 authored by Serge S. Koval's avatar Serge S. Koval

Fixed #442. Do not allow redirects to external sites from create/edit/delete model views

parent 6cf9b937
from re import sub from re import sub
from urlparse import urlparse, urljoin
from jinja2 import contextfunction from jinja2 import contextfunction
from flask import g, request from flask import g, request
from wtforms.validators import DataRequired, InputRequired from wtforms.validators import DataRequired, InputRequired
from ._compat import string_types from ._compat import string_types
...@@ -96,3 +98,17 @@ def prettify_class_name(name): ...@@ -96,3 +98,17 @@ def prettify_class_name(name):
String to split String to split
""" """
return sub(r'(?<=.)([A-Z])', r' \1', name) return sub(r'(?<=.)([A-Z])', r' \1', name)
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return (test_url.scheme in ('http', 'https') and
ref_url.netloc == test_url.netloc)
def get_redirect_target(param_name='url'):
target = request.values.get(param_name)
if target and is_safe_url(target):
return target
...@@ -10,7 +10,7 @@ from flask.ext.admin.base import BaseView, expose ...@@ -10,7 +10,7 @@ from flask.ext.admin.base import BaseView, expose
from flask.ext.admin.form import BaseForm, FormOpts, rules from flask.ext.admin.form import BaseForm, FormOpts, rules
from flask.ext.admin.model import filters, typefmt from flask.ext.admin.model import filters, typefmt
from flask.ext.admin.actions import ActionsMixin from flask.ext.admin.actions import ActionsMixin
from flask.ext.admin.helpers import get_form_data, validate_form_on_submit from flask.ext.admin.helpers import get_form_data, validate_form_on_submit, get_redirect_target
from flask.ext.admin.tools import rec_getattr from flask.ext.admin.tools import rec_getattr
from flask.ext.admin._backwards import ObsoleteAttr from flask.ext.admin._backwards import ObsoleteAttr
from flask.ext.admin._compat import iteritems, as_unicode from flask.ext.admin._compat import iteritems, as_unicode
...@@ -1200,7 +1200,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1200,7 +1200,7 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
Create model view Create model view
""" """
return_url = request.args.get('url') or url_for('.index_view') return_url = get_redirect_target() or url_for('.index_view')
if not self.can_create: if not self.can_create:
return redirect(return_url) return redirect(return_url)
...@@ -1228,7 +1228,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1228,7 +1228,7 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
Edit model view Edit model view
""" """
return_url = request.args.get('url') or url_for('.index_view') return_url = get_redirect_target() or url_for('.index_view')
if not self.can_edit: if not self.can_edit:
return redirect(return_url) return redirect(return_url)
...@@ -1266,7 +1266,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1266,7 +1266,7 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
Delete model view. Only POST method is allowed. Delete model view. Only POST method is allowed.
""" """
return_url = request.args.get('url') or url_for('.index_view') return_url = get_redirect_target() or url_for('.index_view')
# TODO: Use post # TODO: Use post
if not self.can_delete: if not self.can_delete:
......
...@@ -840,3 +840,26 @@ def test_ajax_fk_multi(): ...@@ -840,3 +840,26 @@ def test_ajax_fk_multi():
ok_(mdl is not None) ok_(mdl is not None)
ok_(mdl.model1 is not None) ok_(mdl.model1 is not None)
eq_(len(mdl.model1), 1) eq_(len(mdl.model1), 1)
def test_safe_redirect():
app, db, admin = setup()
Model1, _ = create_models(db)
db.create_all()
view = CustomModelView(Model1, db.session)
admin.add_view(view)
client = app.test_client()
rv = client.post('/admin/model1view/new/?url=http://localhost/admin/model2view/',
data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model2view/')
rv = client.post('/admin/model1view/new/?url=http://google.com/evil/',
data=dict(test1='test1large', test2='test2'))
eq_(rv.status_code, 302)
eq_(rv.location, 'http://localhost/admin/model1view/')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment