Improve permission checking on detail views

This commit is contained in:
Scot Hacker 2018-02-11 00:38:13 -08:00
parent 4d0801313c
commit 3d93e176e8
2 changed files with 158 additions and 133 deletions

View file

@ -1,114 +1,125 @@
{% extends "todo/base.html" %} {% extends "todo/base.html" %}
{% block title %}Task: {{ task.title }}{% endblock %} {% block title %}Task:
{{ task.title }}
{% endblock %}
{% block content %} {% block content %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function () {
// Initially hide the TaskEdit form // Initially hide the TaskEdit form
$('#TaskEdit').hide(); $('#TaskEdit').hide();
// toggle slide to show the Add Task form when link clicked // toggle slide to show the Add Task form when link clicked
$('#slideToggle').click(function(){ $('#slideToggle').click(function () {
$(this).siblings('#TaskEdit').slideToggle(); $(this).siblings('#TaskEdit').slideToggle();
}); });
}); });
</script> </script>
{% if auth_ok %} <h2>{{ task }}</h2>
<h2>{{ task }}</h2> <form action="" method="POST">
{% csrf_token %}
<form action="" method="POST"> <p id="slideToggle">
{% csrf_token %} <strong>&rarr; Click to edit details &larr;</strong>
</p>
<p id="slideToggle" ><strong>&rarr; Click to edit details &larr;</strong></p> <p>
<strong>In list:</strong>
<a href="{% url 'todo:list_detail' task.task_list.id task.task_list.slug %}">
{{ task.task_list }}
</a><br/>
<p> <strong>Assigned to:</strong>
<strong>In list:</strong> {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}
<a href="{% url 'todo:list_detail' task.task_list.id task.task_list.slug %}"> {% else %}Anyone{% endif %}<br/>
{{ task.task_list }}
</a><br />
<strong>Assigned to:</strong> <strong>Created by:</strong>
{% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}<br /> {{ task.created_by.first_name }}
{{ task.created_by.last_name }}<br/>
<strong>Created by:</strong> <strong>Due date:</strong>
{{ task.created_by.first_name }} {{ task.created_by.last_name }}<br /> {{ task.due_date }}<br/>
<strong>Due date:</strong> <strong>Completed:</strong>
{{ task.due_date }}<br /> {{ form.completed }}<br/>
</p>
<strong>Completed:</strong>
{{ form.completed }}<br />
</p>
{% if task.note %}
<div class="task_note"><strong>Note:</strong> {{ task.note|safe|urlize|linebreaks }}</div>
{% endif %}
<div id="TaskEdit">
<h3>Edit Task</h3>
<table>
<tr>
<td>Title:</td>
<td>{{ form.title }} </td>
</tr>
<tr>
<td>List:</td>
<td>{{ form.task_list }} </td>
</tr>
<tr>
<td>Due:</td>
<td>{{ form.due_date }} </td>
</tr>
<tr>
<td>Assigned to:</td>
<td>{{ form.assigned_to }} </td>
</tr>
<tr>
<td valign="top">Note:</td>
<td>{{ form.note }} </td>
</tr>
<tr>
<td>Priority:</td>
<td>{{ form.priority }} </td>
</tr>
</table>
<p><input type="submit" class="todo-button" name="edit_task" value="Edit task"></p>
</div>
<hr />
<h3>Add comment</h3>
<textarea name="comment-body"></textarea>
<p><input class="todo-button"type="submit" value="Submit"></p>
</form>
<h3>Comments on this task</h3>
<div class="task_comments">
{% for comment in comment_list %}
<p>
<strong>{{ comment.author.first_name }} {{ comment.author.last_name }},
{{ comment.date|date:"F d Y P" }}
</strong>
</p>
{{ comment.body|safe|urlize|linebreaks }}
{% empty %}
<p>No Comments</p>
{% endfor %}
</div>
{% if task.note %}
<div class="task_note">
<strong>Note:</strong>
{{ task.note|safe|urlize|linebreaks }}</div>
{% endif %} {% endif %}
<div id="TaskEdit">
<h3>Edit Task</h3>
<table>
<tr>
<td>Title:</td>
<td>{{ form.title }}
</td>
</tr>
<tr>
<td>List:</td>
<td>{{ form.task_list }}
</td>
</tr>
<tr>
<td>Due:</td>
<td>{{ form.due_date }}
</td>
</tr>
<tr>
<td>Assigned to:</td>
<td>{{ form.assigned_to }}
</td>
</tr>
<tr>
<td valign="top">Note:</td>
<td>{{ form.note }}
</td>
</tr>
<tr>
<td>Priority:</td>
<td>{{ form.priority }}
</td>
</tr>
</table>
<p><input type="submit" class="todo-button" name="edit_task" value="Edit task"></p>
</div>
<hr/>
<h3>Add comment</h3>
<textarea name="comment-body"></textarea>
<p><input class="todo-button" type="submit" value="Submit"></p>
</form>
<h3>Comments on this task</h3>
<div class="task_comments">
{% for comment in comment_list %}
<p>
<strong>{{ comment.author.first_name }}
{{ comment.author.last_name }},
{{ comment.date|date:"F d Y P" }}
</strong>
</p>
{{ comment.body|safe|urlize|linebreaks }}
{% empty %}
<p>No Comments</p>
{% endfor %}
</div>
{% endblock %} {% endblock %}

View file

@ -5,6 +5,7 @@ from django.contrib.auth.decorators import user_passes_test, login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
@ -21,8 +22,10 @@ from todo.utils import toggle_done, toggle_deleted, send_notify_mail
def check_user_allowed(user): def check_user_allowed(user):
""" """
Conditions for user_passes_test decorator. Verifies user is logged in, and in staff if that setting is enabled.
Per-object permission checks (e.g. to view a particular list) must be in the views that handle those objects.
""" """
if settings.STAFF_ONLY: if settings.STAFF_ONLY:
return user.is_authenticated and user.is_staff return user.is_authenticated and user.is_staff
else: else:
@ -64,6 +67,11 @@ def del_list(request, list_id, list_slug):
""" """
task_list = get_object_or_404(TaskList, slug=list_slug) task_list = get_object_or_404(TaskList, slug=list_slug)
# Ensure user has permission to delete list. Admins can delete all lists.
# Get the group this list belongs to, and check whether current user is a member of that group.
if task_list.group not in request.user.groups.all() or not request.user.is_staff:
raise PermissionDenied
if request.method == 'POST': if request.method == 'POST':
TaskList.objects.get(id=task_list.id).delete() TaskList.objects.get(id=task_list.id).delete()
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name)) messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
@ -80,6 +88,13 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
"""Display and manage items in a todo list. """Display and manage items in a todo list.
""" """
task_list = get_object_or_404(TaskList, id=list_id, slug=list_slug)
# Ensure user has permission to view list. Admins can view all lists.
# Get the group this task_list belongs to, and check whether current user is a member of that group.
if task_list.group not in request.user.groups.all() and not request.user.is_staff:
raise PermissionDenied
if request.POST: if request.POST:
# Process completed and deleted requests on each POST # Process completed and deleted requests on each POST
toggle_done(request, request.POST.getlist('toggle_done_tasks')) toggle_done(request, request.POST.getlist('toggle_done_tasks'))
@ -134,50 +149,49 @@ def task_detail(request, task_id):
task = get_object_or_404(Item, pk=task_id) task = get_object_or_404(Item, pk=task_id)
comment_list = Comment.objects.filter(task=task_id) comment_list = Comment.objects.filter(task=task_id)
# Ensure user has permission to view item. Admins can edit all tasks. # Ensure user has permission to view item. Admins can view all tasks.
# Get the group this task belongs to, and check whether current user is a member of that group. # Get the group this task belongs to, and check whether current user is a member of that group.
if task.task_list.group not in request.user.groups.all() and not request.user.is_staff:
raise PermissionDenied
if task.task_list.group in request.user.groups.all() or request.user.is_staff: if request.POST:
auth_ok = True form = EditItemForm(request.POST, instance=task)
if request.POST: if form.is_valid():
form = EditItemForm(request.POST, instance=task) form.save()
if form.is_valid(): # Also save submitted comment, if non-empty
form.save() if request.POST['comment-body']:
c = Comment(
author=request.user,
task=task,
body=request.POST['comment-body'],
)
c.save()
# Also save submitted comment, if non-empty # And email comment to all people who have participated in this thread.
if request.POST['comment-body']: current_site = Site.objects.get_current()
c = Comment( email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task})
author=request.user, email_body = render_to_string(
task=task, "todo/email/newcomment_body.txt",
body=request.POST['comment-body'], {'task': task, 'body': request.POST['comment-body'], 'site': current_site, 'user': request.user}
) )
c.save()
# And email comment to all people who have participated in this thread. # Get list of all thread participants - everyone who has commented on it plus task creator.
current_site = Site.objects.get_current() commenters = Comment.objects.filter(task=task)
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task}) recip_list = [c.author.email for c in commenters]
email_body = render_to_string( recip_list.append(task.created_by.email)
"todo/email/newcomment_body.txt", recip_list = list(set(recip_list)) # Eliminate duplicates
{'task': task, 'body': request.POST['comment-body'], 'site': current_site, 'user': request.user}
)
# Get list of all thread participants - everyone who has commented on it plus task creator. try:
commenters = Comment.objects.filter(task=task) send_mail(email_subject, email_body, task.created_by.email, recip_list, fail_silently=False)
recip_list = [c.author.email for c in commenters] messages.success(request, "Comment sent to thread participants.")
recip_list.append(task.created_by.email) except ConnectionRefusedError:
recip_list = list(set(recip_list)) # Eliminate duplicates messages.error(request, "Comment saved but mail not sent. Contact your administrator.")
try: messages.success(request, "The task has been edited.")
send_mail(email_subject, email_body, task.created_by.email, recip_list, fail_silently=False)
messages.success(request, "Comment sent to thread participants.")
except ConnectionRefusedError:
messages.error(request, "Comment saved but mail not sent. Contact your administrator.")
messages.success(request, "The task has been edited.") return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
else: else:
form = EditItemForm(instance=task) form = EditItemForm(instance=task)
if task.due_date: if task.due_date: