Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
1e6eefc7
Commit
1e6eefc7
authored
Jan 27, 2017
by
Paul Brown
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add csrf token validation to actions
parent
2ce8dbc0
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
100 additions
and
16 deletions
+100
-16
actions.py
flask_admin/actions.py
+14
-8
__init__.py
flask_admin/contrib/fileadmin/__init__.py
+30
-0
base.py
flask_admin/model/base.py
+28
-1
actions.html
flask_admin/templates/bootstrap2/admin/actions.html
+5
-3
actions.html
flask_admin/templates/bootstrap3/admin/actions.html
+5
-3
test_model.py
flask_admin/tests/test_model.py
+18
-1
No files found.
flask_admin/actions.py
View file @
1e6eefc7
...
@@ -3,7 +3,7 @@ from flask import request, redirect
...
@@ -3,7 +3,7 @@ from flask import request, redirect
from
flask_admin
import
tools
from
flask_admin
import
tools
from
flask_admin._compat
import
text_type
from
flask_admin._compat
import
text_type
from
flask_admin.helpers
import
get_redirect_target
from
flask_admin.helpers
import
get_redirect_target
,
flash_errors
def
action
(
name
,
text
,
confirmation
=
None
):
def
action
(
name
,
text
,
confirmation
=
None
):
...
@@ -104,8 +104,12 @@ class ActionsMixin(object):
...
@@ -104,8 +104,12 @@ class ActionsMixin(object):
If not provided, will return user to the return url in the form
If not provided, will return user to the return url in the form
or the list view.
or the list view.
"""
"""
action
=
request
.
form
.
get
(
'action'
)
form
=
self
.
action_form
()
if
self
.
validate_form
(
form
):
# using getlist instead of FieldList for backward compatibility
ids
=
request
.
form
.
getlist
(
'rowid'
)
ids
=
request
.
form
.
getlist
(
'rowid'
)
action
=
form
.
action
.
data
handler
=
self
.
_actions_data
.
get
(
action
)
handler
=
self
.
_actions_data
.
get
(
action
)
...
@@ -114,6 +118,8 @@ class ActionsMixin(object):
...
@@ -114,6 +118,8 @@ class ActionsMixin(object):
if
response
is
not
None
:
if
response
is
not
None
:
return
response
return
response
else
:
flash_errors
(
form
,
message
=
'Failed to perform action.
%(error)
s'
)
if
return_view
:
if
return_view
:
url
=
self
.
get_url
(
'.'
+
return_view
)
url
=
self
.
get_url
(
'.'
+
return_view
)
...
...
flask_admin/contrib/fileadmin/__init__.py
View file @
1e6eefc7
...
@@ -413,6 +413,19 @@ class BaseFileAdmin(BaseView, ActionsMixin):
...
@@ -413,6 +413,19 @@ class BaseFileAdmin(BaseView, ActionsMixin):
return
DeleteForm
return
DeleteForm
def
get_action_form
(
self
):
"""
Create form class for model action.
Override to implement customized behavior.
"""
class
ActionForm
(
self
.
form_base_class
):
action
=
fields
.
HiddenField
()
url
=
fields
.
HiddenField
()
# rowid is retrieved using getlist, for backward compatibility
return
ActionForm
def
upload_form
(
self
):
def
upload_form
(
self
):
"""
"""
Instantiate file upload form and return it.
Instantiate file upload form and return it.
...
@@ -471,6 +484,18 @@ class BaseFileAdmin(BaseView, ActionsMixin):
...
@@ -471,6 +484,18 @@ class BaseFileAdmin(BaseView, ActionsMixin):
else
:
else
:
return
delete_form_class
()
return
delete_form_class
()
def
action_form
(
self
):
"""
Instantiate action form and return it.
Override to implement custom behavior.
"""
action_form_class
=
self
.
get_action_form
()
if
request
.
form
:
return
action_form_class
(
request
.
form
)
else
:
return
action_form_class
()
def
is_file_allowed
(
self
,
filename
):
def
is_file_allowed
(
self
,
filename
):
"""
"""
Verify if file can be uploaded.
Verify if file can be uploaded.
...
@@ -812,6 +837,10 @@ class BaseFileAdmin(BaseView, ActionsMixin):
...
@@ -812,6 +837,10 @@ class BaseFileAdmin(BaseView, ActionsMixin):
# Actions
# Actions
actions
,
actions_confirmation
=
self
.
get_actions_list
()
actions
,
actions_confirmation
=
self
.
get_actions_list
()
if
actions
:
action_form
=
self
.
action_form
()
else
:
action_form
=
None
def
sort_url
(
column
,
invert
=
False
):
def
sort_url
(
column
,
invert
=
False
):
desc
=
None
desc
=
None
...
@@ -829,6 +858,7 @@ class BaseFileAdmin(BaseView, ActionsMixin):
...
@@ -829,6 +858,7 @@ class BaseFileAdmin(BaseView, ActionsMixin):
items
=
items
,
items
=
items
,
actions
=
actions
,
actions
=
actions
,
actions_confirmation
=
actions_confirmation
,
actions_confirmation
=
actions_confirmation
,
action_form
=
action_form
,
delete_form
=
delete_form
,
delete_form
=
delete_form
,
sort_column
=
sort_column
,
sort_column
=
sort_column
,
sort_desc
=
sort_desc
,
sort_desc
=
sort_desc
,
...
...
flask_admin/model/base.py
View file @
1e6eefc7
...
@@ -795,6 +795,7 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -795,6 +795,7 @@ class BaseModelView(BaseView, ActionsMixin):
self
.
_create_form_class
=
self
.
get_create_form
()
self
.
_create_form_class
=
self
.
get_create_form
()
self
.
_edit_form_class
=
self
.
get_edit_form
()
self
.
_edit_form_class
=
self
.
get_edit_form
()
self
.
_delete_form_class
=
self
.
get_delete_form
()
self
.
_delete_form_class
=
self
.
get_delete_form
()
self
.
_action_form_class
=
self
.
get_action_form
()
# List View In-Line Editing
# List View In-Line Editing
if
self
.
column_editable_list
:
if
self
.
column_editable_list
:
...
@@ -1254,6 +1255,19 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1254,6 +1255,19 @@ class BaseModelView(BaseView, ActionsMixin):
return
DeleteForm
return
DeleteForm
def
get_action_form
(
self
):
"""
Create form class for a model action.
Override to implement customized behavior.
"""
class
ActionForm
(
self
.
form_base_class
):
action
=
HiddenField
()
url
=
HiddenField
()
# rowid is retrieved using getlist, for backward compatibility
return
ActionForm
def
create_form
(
self
,
obj
=
None
):
def
create_form
(
self
,
obj
=
None
):
"""
"""
Instantiate model creation form and return it.
Instantiate model creation form and return it.
...
@@ -1295,6 +1309,14 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1295,6 +1309,14 @@ class BaseModelView(BaseView, ActionsMixin):
"""
"""
return
self
.
_list_form_class
(
get_form_data
(),
obj
=
obj
)
return
self
.
_list_form_class
(
get_form_data
(),
obj
=
obj
)
def
action_form
(
self
,
obj
=
None
):
"""
Instantiate model action form and return it.
Override to implement custom behavior.
"""
return
self
.
_action_form_class
(
get_form_data
(),
obj
=
obj
)
def
validate_form
(
self
,
form
):
def
validate_form
(
self
,
form
):
"""
"""
Validate the form on submit.
Validate the form on submit.
...
@@ -1540,7 +1562,7 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1540,7 +1562,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
"""
pass
pass
def
on_form_prefill
(
self
,
form
,
id
):
def
on_form_prefill
(
self
,
form
,
id
):
"""
"""
Perform additional actions to pre-fill the edit form.
Perform additional actions to pre-fill the edit form.
...
@@ -1874,6 +1896,10 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1874,6 +1896,10 @@ class BaseModelView(BaseView, ActionsMixin):
# Actions
# Actions
actions
,
actions_confirmation
=
self
.
get_actions_list
()
actions
,
actions_confirmation
=
self
.
get_actions_list
()
if
actions
:
action_form
=
self
.
action_form
()
else
:
action_form
=
None
clear_search_url
=
self
.
_get_list_url
(
view_args
.
clone
(
page
=
0
,
clear_search_url
=
self
.
_get_list_url
(
view_args
.
clone
(
page
=
0
,
sort
=
view_args
.
sort
,
sort
=
view_args
.
sort
,
...
@@ -1886,6 +1912,7 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1886,6 +1912,7 @@ class BaseModelView(BaseView, ActionsMixin):
data
=
data
,
data
=
data
,
list_forms
=
list_forms
,
list_forms
=
list_forms
,
delete_form
=
delete_form
,
delete_form
=
delete_form
,
action_form
=
action_form
,
# List
# List
list_columns
=
self
.
_list_columns
,
list_columns
=
self
.
_list_columns
,
...
...
flask_admin/templates/bootstrap2/admin/actions.html
View file @
1e6eefc7
...
@@ -14,11 +14,13 @@
...
@@ -14,11 +14,13 @@
{% macro form(actions, url) %}
{% macro form(actions, url) %}
{% if actions %}
{% if actions %}
<form
id=
"action_form"
action=
"{{ url }}"
method=
"POST"
style=
"display: none"
>
<form
id=
"action_form"
action=
"{{ url }}"
method=
"POST"
style=
"display: none"
>
{% if csrf_token %}
{% if action_form.csrf_token %}
{{ action_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
{% endif %}
<input
type=
"hidden"
name=
"url"
value=
"{{ return_url }}"
>
{{ action_form.url(value=return_url) }}
<input
type=
"hidden"
id=
"action"
name=
"action"
/>
{{ action_form.action() }}
</form>
</form>
{% endif %}
{% endif %}
{% endmacro %}
{% endmacro %}
...
...
flask_admin/templates/bootstrap3/admin/actions.html
View file @
1e6eefc7
...
@@ -14,11 +14,13 @@
...
@@ -14,11 +14,13 @@
{% macro form(actions, url) %}
{% macro form(actions, url) %}
{% if actions %}
{% if actions %}
<form
id=
"action_form"
action=
"{{ url }}"
method=
"POST"
style=
"display: none"
>
<form
id=
"action_form"
action=
"{{ url }}"
method=
"POST"
style=
"display: none"
>
{% if csrf_token %}
{% if action_form.csrf_token %}
{{ action_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
{% endif %}
<input
type=
"hidden"
name=
"url"
value=
"{{ return_url }}"
>
{{ action_form.url(value=return_url) }}
<input
type=
"hidden"
id=
"action"
name=
"action"
/>
{{ action_form.action() }}
</form>
</form>
{% endif %}
{% endif %}
{% endmacro %}
{% endmacro %}
...
...
flask_admin/tests/test_model.py
View file @
1e6eefc7
...
@@ -424,6 +424,23 @@ def test_csrf():
...
@@ -424,6 +424,23 @@ def test_csrf():
eq_
(
rv
.
status_code
,
200
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'Record was successfully deleted.'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
u'Record was successfully deleted.'
in
rv
.
data
.
decode
(
'utf-8'
))
################
# actions
################
rv
=
client
.
get
(
'/admin/secure/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'name="csrf_token"'
in
rv
.
data
.
decode
(
'utf-8'
))
csrf_token
=
get_csrf_token
(
rv
.
data
.
decode
(
'utf-8'
))
# Delete without CSRF token, test validation errors
rv
=
client
.
post
(
'/admin/secure/action/'
,
data
=
dict
(
rowid
=
'1'
,
url
=
'/admin/secure/'
,
action
=
'delete'
),
follow_redirects
=
True
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
u'Record was successfully deleted.'
not
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
u'Failed to perform action.'
in
rv
.
data
.
decode
(
'utf-8'
))
def
test_custom_form
():
def
test_custom_form
():
app
,
admin
=
setup
()
app
,
admin
=
setup
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment