1
Author: James Cammarata <jimi@sngx.net>
2
Description: Add CSRF protection to cobbler-web
3
Bug-Ubuntu: https://launchpad.net/bugs/858878
4
Origin: backport, http://git.fedorahosted.org/git/?p=cobbler;a=commit;h=18eb1c06779b37d89dfb2962a08236dd1bab24a6
6
This also includes previous CSRF work from http://git.fedorahosted.org/git?p=cobbler;a=commit;h=4bee30b4086a8d845bea5d39d6f2cba1f4a396aa
8
--- a/web/cobbler_web/templates/generic_edit.tmpl
9
+++ b/web/cobbler_web/templates/generic_edit.tmpl
11
<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a {{ what|capfirst }}{%ifequal editmode 'edit' %}: {{ name }}{% endifequal %}</h1>
13
<form method="post" action="/cobbler_web/{{ what }}/save">
15
<input type="hidden" name="editmode" value="{{ editmode }}" />
16
<input type="hidden" name="subobject" value="{{ subobject }}" />
18
--- a/web/cobbler_web/templates/generic_list.tmpl
19
+++ b/web/cobbler_web/templates/generic_list.tmpl
24
+<form id="action" method="POST" action="">{% csrf_token %}</form>
25
<script type="text/javascript">
26
function items_check_all(){
27
var checkall = document.getElementById("itemsall").checked
29
function obj_rename(old) {
30
var newname = window.prompt("Change {{ what }} name to?",old);
31
if (newname != null) {
32
- window.location = "/cobbler_web/{{ what }}/rename/" + old + "/" + newname;
33
+ document.forms["action"].action = "/cobbler_web/{{ what }}/rename/" + old + "/" + newname;
34
+ document.forms["action"].submit();
37
function obj_copy(old) {
38
var newname = window.prompt("Name for the new {{ what }}?",old);
39
if (newname != null) {
40
- window.location = "/cobbler_web/{{ what }}/copy/" + old + "/" + newname;
41
+ document.forms["action"].action = "/cobbler_web/{{ what }}/copy/" + old + "/" + newname;
42
+ document.forms["action"].submit();
45
function obj_delete(old) {
46
if (confirm("Delete {{ what }} (" + old + ") and all child objects?")) {
47
- window.location = "/cobbler_web/{{ what }}/delete/" + old;
48
+ document.forms["action"].action = "/cobbler_web/{{ what }}/delete/" + old;
49
+ document.forms["action"].submit();
54
document.location = "/cobbler_web/" + what + "/" + action
57
+function action_sort(value) {
58
+ document.forms["action"].action = '/cobbler_web/{{ what }}/modifylist/sort/' + value;
59
+ document.forms["action"].submit();
62
function action_multi(otype) {
63
var values = items_checked_values()
68
<form name="myform" method="post" action="/cobbler_web/{{ what }}/action">
70
<table id="listitems" cellspacing="0">
75
{% for value in columns %}
77
- <a href="/cobbler_web/{{ what }}/modifylist/sort/{{ value.0 }}">{{ value.0|title }}</a>
78
+ <a href="javascript:action_sort('{{ value.0 }}');">{{ value.0|title }}</a>
79
{% ifequal value.1 "asc" %}
82
--- a/web/cobbler_web/templates/import.tmpl
83
+++ b/web/cobbler_web/templates/import.tmpl
87
<form method="post" action="/cobbler_web/import/run">
89
<div class="sectionbody">
92
--- a/web/cobbler_web/templates/ksfile_edit.tmpl
93
+++ b/web/cobbler_web/templates/ksfile_edit.tmpl
95
<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a Kickstart Template</h1>
97
<form id="ksform" method="post" action="/cobbler_web/ksfile/save">
101
<label for="ksdata">{% if ksfile_name %}Editing: {{ ksfile_name }}{% else %}Filename:{% endif %}</label>
102
--- a/web/cobbler_web/templates/login.tmpl
103
+++ b/web/cobbler_web/templates/login.tmpl
105
<p class="error">Sorry, that's not a valid username or password</p>
107
<form action="/cobbler_web/do_login" method="post">
109
{% if next %}<input type="hidden" name="next" value="{{ next|escape }}" />{% endif %}
111
<label for="username">Username: </label>
112
--- a/web/cobbler_web/templates/snippet_edit.tmpl
113
+++ b/web/cobbler_web/templates/snippet_edit.tmpl
115
<h1>{% ifequal editmode 'edit' %}Editing{% else %}Adding{% endifequal %} a Snippet</h1>
117
<form id="snippetform" method="post" action="/cobbler_web/snippet/save">
121
<label for="snippetdata">{% if snippet_name %}Snippet: {{ snippet_name }}{% else %}Filename:{% endif %}</label>
122
--- a/web/cobbler_web/views.py
123
+++ b/web/cobbler_web/views.py
125
from django.http import HttpResponse
126
from django.http import HttpResponseRedirect
127
from django.shortcuts import render_to_response
128
+from django.views.decorators.http import require_POST
131
+ from django.views.decorators.csrf import csrf_protect
133
+ # Old Django, fudge the @csrf_protect decorator to be a pass-through
134
+ # that does nothing. Django decorator shell based on this page:
135
+ # http://passingcuriosity.com/2009/writing-view-decorators-for-django/
136
+ def csrf_protect(f):
137
+ def _dec(view_func):
138
+ def _view(request,*args,**kwargs):
139
+ return view_func(request,*args,**kwargs)
140
+ _view.__name__ = view_func.__name__
141
+ _view.__dict__ = view_func.__dict__
142
+ _view.__doc__ = view_func.__doc__
152
if not test_user_authenticated(request): return login(request,next="/cobbler_web")
154
t = get_template('index.tmpl')
155
- html = t.render(Context({
156
+ html = t.render(RequestContext(request,{
157
'version' : remote.version(request.session['token']),
158
'username': username,
162
if not test_user_authenticated(request): return login(request)
163
t = get_template("task_created.tmpl")
164
- html = t.render(Context({
165
+ html = t.render(RequestContext(request,{
166
'version' : remote.version(request.session['token']),
167
'username' : username
170
t = get_template('error_page.tmpl')
171
message = message.replace("<Fault 1: \"<class 'cobbler.cexceptions.CX'>:'","Remote exception: ")
172
message = message.replace("'\">","")
173
- html = t.render(Context({
174
+ html = t.render(RequestContext(request,{
175
'version' : remote.version(request.session['token']),
180
return HttpResponse(html)
185
def modify_list(request, what, pref, value=None):
187
This function is used in the generic list view
190
# ======================================================================
194
def generic_rename(request, what, obj_name=None, obj_newname=None):
199
# ======================================================================
203
def generic_copy(request, what, obj_name=None, obj_newname=None):
208
# ======================================================================
212
def generic_delete(request, what, obj_name=None):
217
# ======================================================================
221
def generic_domulti(request, what, multi_mode=None, multi_arg=None):
225
def import_prompt(request):
226
if not test_user_authenticated(request): return login(request)
227
t = get_template('import.tmpl')
228
- html = t.render(Context({
229
+ html = t.render(RequestContext(request,{
230
'version' : remote.version(request.session['token']),
231
'username' : username,
234
if not test_user_authenticated(request): return login(request)
235
results = remote.check(request.session['token'])
236
t = get_template('check.tmpl')
237
- html = t.render(Context({
238
+ html = t.render(RequestContext(request,{
239
'version': remote.version(request.session['token']),
240
'username' : username,
244
# ======================================================================
248
def buildiso(request):
249
if not test_user_authenticated(request): return login(request)
250
remote.background_buildiso({},request.session['token'])
253
# ======================================================================
257
def import_run(request):
258
if not test_user_authenticated(request): return login(request)
261
ksfile_list.append((ksfile,ksfile,None))
263
t = get_template('ksfile_list.tmpl')
264
- html = t.render(Context({
265
+ html = t.render(RequestContext(request,{
267
'ksfiles': ksfile_list,
268
'version': remote.version(request.session['token']),
271
# ======================================================================
275
def ksfile_edit(request, ksfile_name=None, editmode='edit'):
277
This is the page where a kickstart file is edited.
279
ksdata = remote.read_or_write_kickstart_template(ksfile_name, True, "", request.session['token'])
281
t = get_template('ksfile_edit.tmpl')
282
- html = t.render(Context({
283
+ html = t.render(RequestContext(request,{
284
'ksfile_name' : ksfile_name,
285
'deleteable' : deleteable,
289
# ======================================================================
293
def ksfile_save(request):
295
This page processes and saves edits to a kickstart file.
297
snippet_list.append((snippet,snippet,None))
299
t = get_template('snippet_list.tmpl')
300
- html = t.render(Context({
301
+ html = t.render(RequestContext(request,{
303
'snippets' : snippet_list,
304
'version' : remote.version(request.session['token']),
307
# ======================================================================
310
def snippet_edit(request, snippet_name=None, editmode='edit'):
312
This page edits a specific snippet.
314
snippetdata = remote.read_or_write_snippet(snippet_name, True, "", request.session['token'])
316
t = get_template('snippet_edit.tmpl')
317
- html = t.render(Context({
318
+ html = t.render(RequestContext(request,{
319
'snippet_name' : snippet_name,
320
'deleteable' : deleteable,
321
'snippetdata' : snippetdata,
324
# ======================================================================
328
def snippet_save(request):
330
This snippet saves a snippet once edited.
332
results.append([k,settings[k]])
334
t = get_template('settings.tmpl')
335
- html = t.render(Context({
336
+ html = t.render(RequestContext(request,{
337
'settings' : results,
338
'version' : remote.version(request.session['token']),
339
'username' : username,
343
t = get_template('events.tmpl')
344
- html = t.render(Context({
345
+ html = t.render(RequestContext(request,{
347
'version' : remote.version(request.session['token']),
348
'username' : username
350
'version' : remote.version(request.session['token']),
351
'username' : username
353
- html = t.render(Context(vars))
354
+ html = t.render(RequestContext(request,vars))
355
return HttpResponse(html)
357
# ======================================================================
360
# ======================================================================
366
Runs 'cobbler sync' from the API when the user presses the sync button.
369
# ======================================================================
373
def reposync(request):
375
Syncs all repos that are configured to be synced.
378
# ======================================================================
382
def hardlink(request):
384
Hardlinks files between repos and install trees to save space.
387
# ======================================================================
391
def replicate(request):
393
Replicate configuration from the central cobbler server, configured
396
# ======================================================================
399
def generic_edit(request, what=None, obj_name=None, editmode="new"):
403
t = get_template('generic_edit.tmpl')
404
inames = interfaces.keys()
406
- html = t.render(Context({
407
+ html = t.render(RequestContext(request,{
413
# ======================================================================
417
def generic_save(request,what):
420
@@ -1124,9 +1173,12 @@
425
def login(request, next=None):
426
- return render_to_response('login.tmpl', {'next':next})
427
+ return render_to_response('login.tmpl', RequestContext(request,{'next':next}))
431
def do_login(request):
434
@@ -1156,6 +1208,8 @@
436
return login(request,nextsite)
440
def do_logout(request):
441
request.session['username'] = ""
442
request.session['token'] = ""
443
--- a/web/settings.py
444
+++ b/web/settings.py
446
# Django settings for cobbler-web project.
450
TEMPLATE_DEBUG = DEBUG
453
MIDDLEWARE_CLASSES = (
454
'django.middleware.common.CommonMiddleware',
455
+ 'django.middleware.csrf.CsrfViewMiddleware',
456
'django.contrib.sessions.middleware.SessionMiddleware',
457
'django.contrib.auth.middleware.AuthenticationMiddleware',
459
--- a/web/cobbler_web/templates/filter.tmpl
460
+++ b/web/cobbler_web/templates/filter.tmpl
463
<script type="text/javascript">
464
+// these functions depend on the "action" form defined in generic_list
465
function add_filter() {
466
field_name = document.getElementById("filter_field");
467
field_value = document.getElementById("filter_value");
469
} else if (field_value.value == "") {
470
alert("You must select a filter value.");
472
- location = '/cobbler_web/{{ what }}/modifylist/addfilter/'+field_name.value+':'+field_value.value;
473
+ document.forms["action"].action = '/cobbler_web/{{ what }}/modifylist/addfilter/'+field_name.value+':'+field_value.value;
474
+ document.forms["action"].submit();
477
+function del_filter(filter) {
478
+ document.forms["action"].action = '/cobbler_web/{{ what }}/modifylist/removefilter/' + filter
479
+ document.forms["action"].submit();
482
<ul id="filter-adder">
486
<ul id="filter-remover">
487
{% for key,value in filters.items %}
488
- <li><a href="/cobbler_web/{{ what }}/modifylist/removefilter/{{ key }}" title="remove">✖</a> {{ key }} = {{ value }}</li>
489
+ <li><a href="javascript:del_filter('{{ key }}');" title="remove">✖</a> {{ key }} = {{ value }}</li>
493
--- a/web/cobbler_web/templates/master.tmpl
494
+++ b/web/cobbler_web/templates/master.tmpl
498
<input type="hidden" name="username" id="username" value="{{ username }}" />
499
- Logged in: <b>{{ username }}</b> <a class="action" href="/cobbler_web/logout">Logout</a>
500
+ Logged in: <b>{{ username }}</b> <a class="action" href="javascript:menuaction('/cobbler_web/logout');">Logout</a>
503
<big><b>Orchestra</b></big><br/><i>powered by <a href="https://fedorahosted.org/cobbler/">Cobbler</a></i><br/><br/>
504
+ <form id="menuaction" method="POST" action="">{% csrf_token %}</form>
505
+ <script type="text/javascript">
506
+ function menuaction(action) {
507
+ document.forms["menuaction"].action = action
508
+ document.forms["menuaction"].submit();
511
<h1>Configuration</h1>
513
<li><a href="/cobbler_web/distro/list" class="edit">Distros</a></li>
517
<li><a href="/cobbler_web/import/prompt">Import DVD</a></li>
518
- <li><a href="/cobbler_web/sync">Sync</a> ☼</li>
519
- <li><a href="/cobbler_web/reposync">Reposync</a> ☼ </li>
520
- <li><a href="/cobbler_web/hardlink">Hardlink</a> ☼ </li>
521
- <!-- <li><a href="/cobbler_web/replicate">Replicate</a> ☼ </li> -->
522
- <li><a href="/cobbler_web/buildiso">Build ISO</a> ☼ </li>
523
+ <li><a href="javascript:menuaction('/cobbler_web/sync');">Sync</a> ☼</li>
524
+ <li><a href="javascript:menuaction('/cobbler_web/reposync');"">Reposync</a> ☼ </li>
525
+ <li><a href="javascript:menuaction('/cobbler_web/hardlink');"">Hardlink</a> ☼ </li>
526
+ <!-- <li><a href="javascript:menuaction('/cobbler_web/replicate');"">Replicate</a> ☼ </li> -->
527
+ <li><a href="javascript:menuaction('/cobbler_web/buildiso');">Build ISO</a> ☼ </li>
531
--- a/web/cobbler_web/templates/paginate.tmpl
532
+++ b/web/cobbler_web/templates/paginate.tmpl
535
+<script type="text/javascript">
536
+// All of these functions depend on the form "action" defined in generic_list
537
+function change_limit(value) {
538
+ document.forms["action"].action = '/cobbler_web/{{ what }}/modifylist/limit/' + value
539
+ document.forms["action"].submit();
541
+function change_page(value) {
542
+ document.forms["action"].action = '/cobbler_web/{{ what }}/modifylist/page/' + value
543
+ document.forms["action"].submit();
547
<li class="paginate"><label for="limit">Items/page:</label>
548
- <select name="limit" id="limit" onchange="javascript:location='/cobbler_web/{{ what }}/modifylist/limit/'+this.value">
549
+ <select name="limit" id="limit" onchange="javascript:change_limit(this.value)">
550
{% for p in pageinfo.items_per_page_list %}
551
<option value="{{ p }}"{% ifequal pageinfo.items_per_page p %} selected="selected"{% endifequal %}>{{ p }}</option>
554
{% ifnotequal pageinfo.prev_page "~" %}
555
- <a href="/cobbler_web/{{ what }}/modifylist/page/{{ pageinfo.prev_page }}"><span class="lpointers">⇐</span></a>
556
+ <a href="javascript:change_page('{{ pageinfo.prev_page }}')"><span class="lpointers">⇐</span></a>
558
<span class="lpointers">⇐</span>
560
- <select name="page" id="page" onchange="javascript:location='/cobbler_web/{{ what }}/modifylist/page/'+this.value">
561
+ <select name="page" id="page" onchange="javascript:change_page(this.value)">
562
{% for p in pageinfo.pages %}<option value="{{ p }}"{% ifequal pageinfo.page p %} selected="selected"{% endifequal %}>Page {{ p }}</option>{% endfor %}
564
{% ifnotequal pageinfo.next_page "~" %}
565
- <a href="/cobbler_web/{{ what }}/modifylist/page/{{ pageinfo.next_page }}"><span class="rpointers">⇒</span></a>
566
+ <a href="javascript:change_page('{{ pageinfo.next_page }}')"><span class="rpointers">⇒</span></a>
568
<span class="rpointers">⇒</span>