fix a few major permission holes
This commit is contained in:
@@ -608,6 +608,10 @@ class QuestionClone(AnatomyQuestionCreateBase):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
# print(self.request)
|
# print(self.request)
|
||||||
old_object = get_object_or_404(AnatomyQuestion, pk=self.kwargs["pk"])
|
old_object = get_object_or_404(AnatomyQuestion, pk=self.kwargs["pk"])
|
||||||
|
|
||||||
|
if self.request.user not in old_object.get_author_objects() and not self.request.user.is_superuser:
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
initial_data = model_to_dict(old_object, exclude=["id"])
|
initial_data = model_to_dict(old_object, exclude=["id"])
|
||||||
|
|
||||||
# We don't want to clone the exam details
|
# We don't want to clone the exam details
|
||||||
|
|||||||
+5
-1
@@ -37,9 +37,13 @@ def user_is_author_or_atlas_series_checker(function):
|
|||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
def user_is_author_or_atlas_editor_or_atlas_marker(function):
|
def user_has_case_view_access(function):
|
||||||
def wrap(request, *args, **kwargs):
|
def wrap(request, *args, **kwargs):
|
||||||
atlas = Case.objects.get(pk=kwargs["pk"])
|
atlas = Case.objects.get(pk=kwargs["pk"])
|
||||||
|
|
||||||
|
# If open access everyone can view
|
||||||
|
if atlas.open_access:
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
if (
|
if (
|
||||||
request.user in atlas.author.all()
|
request.user in atlas.author.all()
|
||||||
or request.user.groups.filter(name="atlas_editor").exists()
|
or request.user.groups.filter(name="atlas_editor").exists()
|
||||||
|
|||||||
+2
-1
@@ -12,6 +12,7 @@ from .models import (
|
|||||||
Subspecialty,
|
Subspecialty,
|
||||||
)
|
)
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
def get_authors(request):
|
def get_authors(request):
|
||||||
@@ -118,7 +119,7 @@ class CaseFilter(django_filters.FilterSet):
|
|||||||
):
|
):
|
||||||
if not request.user.groups.filter(name="atlas_editor").exists():
|
if not request.user.groups.filter(name="atlas_editor").exists():
|
||||||
queryset = queryset.prefetch_related("author").filter(
|
queryset = queryset.prefetch_related("author").filter(
|
||||||
author__id=request.user.id
|
(Q(author__id=request.user.id) | Q(open_access=True))
|
||||||
)
|
)
|
||||||
super(CaseFilter, self).__init__(
|
super(CaseFilter, self).__init__(
|
||||||
data=data, queryset=queryset, prefix=prefix, request=request
|
data=data, queryset=queryset, prefix=prefix, request=request
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{% extends 'atlas/base.html' %}
|
|
||||||
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% block css %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div id="view-filter-options">
|
|
||||||
<h3>Filter Long </h3>
|
|
||||||
<form action="" method="get">
|
|
||||||
{{ filter.form }}
|
|
||||||
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% render_table table %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Add/Edit Condition</h2>
|
<h2>Add/Edit Condition</h2>
|
||||||
<p>Use this form to create or edit a condition.</p>
|
<p>Use this form to create or edit a condition.</p>
|
||||||
<p>Please check if it already exists before doing so!</p>
|
<p>Please check if it already <a href='{% url "atlas:condition_view" %}'>exists</a> before doing so!</p>
|
||||||
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
|
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<h2>Atlas</h2>
|
<h2>Atlas</h2>
|
||||||
|
|
||||||
<a href="{% url 'atlas:user_collections' %}">Available collections</a><br/>
|
<a href="{% url 'atlas:user_collections' %}">Available collections</a><br/>
|
||||||
|
View my <a href='{% url "atlas:case_view" %}?author={{request.user.id}}'>cases</a>.<br/>
|
||||||
<a href="{% url 'atlas:user_uploads' %}">My uploads</a>
|
<a href="{% url 'atlas:user_uploads' %}">My uploads</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
View my <a href='{% url "atlas:case_view" %}?author={{request.user.id}}'>cases</a>.
|
||||||
</details>
|
</details>
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# from django.contrib.auth.models import User
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from rich.pretty import pprint
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+30
-10
@@ -99,12 +99,11 @@ from autocomplete import HTMXAutoComplete
|
|||||||
|
|
||||||
from .decorators import (
|
from .decorators import (
|
||||||
user_is_author_or_atlas_editor,
|
user_is_author_or_atlas_editor,
|
||||||
user_is_author_or_atlas_editor_or_atlas_marker,
|
user_has_case_view_access,
|
||||||
user_is_author_or_atlas_series_checker_or_atlas_marker,
|
user_is_author_or_atlas_series_checker_or_atlas_marker,
|
||||||
user_is_atlas_editor,
|
user_is_atlas_editor,
|
||||||
user_is_author_or_atlas_series_checker,
|
user_is_author_or_atlas_series_checker,
|
||||||
user_is_atlas_marker,
|
user_is_atlas_marker,
|
||||||
user_is_author_or_atlas_editor_or_atlas_marker,
|
|
||||||
user_is_collection_author_or_atlas_editor,
|
user_is_collection_author_or_atlas_editor,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,9 +147,18 @@ class AuthorOrCheckerRequiredMixin(object):
|
|||||||
raise PermissionDenied() # or Http404
|
raise PermissionDenied() # or Http404
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
class AtlasEditorRequiredMixin(object):
|
||||||
|
def get_object(self, *args, **kwargs):
|
||||||
|
obj = super().get_object(*args, **kwargs)
|
||||||
|
if (
|
||||||
|
self.request.user.groups.filter(name="atlas_editor").exists()
|
||||||
|
or self.request.user.is_superuser
|
||||||
|
):
|
||||||
|
return obj
|
||||||
|
raise PermissionDenied("You must be an atlas editor to do this.") # or Http404
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_is_author_or_atlas_editor_or_atlas_marker
|
@user_has_case_view_access
|
||||||
def case_detail(request, pk):
|
def case_detail(request, pk):
|
||||||
case = get_object_or_404(Case, pk=pk)
|
case = get_object_or_404(Case, pk=pk)
|
||||||
|
|
||||||
@@ -417,31 +425,31 @@ class SeriesFindingDelete(RevisionMixin, PermissionRequiredMixin, DeleteView):
|
|||||||
return reverse("atlas:series_detail", kwargs={"pk": series_id})
|
return reverse("atlas:series_detail", kwargs={"pk": series_id})
|
||||||
|
|
||||||
|
|
||||||
class SeriesDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class SeriesDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
|
||||||
model = Series
|
model = Series
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
success_url = reverse_lazy("atlas:series_view")
|
success_url = reverse_lazy("atlas:series_view")
|
||||||
|
|
||||||
|
|
||||||
class CaseCollectionDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class CaseCollectionDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
|
||||||
model = CaseCollection
|
model = CaseCollection
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
success_url = reverse_lazy("atlas:collection_view")
|
success_url = reverse_lazy("atlas:collection_view")
|
||||||
|
|
||||||
|
|
||||||
class ConditionDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class ConditionDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
|
||||||
model = Condition
|
model = Condition
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
success_url = reverse_lazy("atlas:condition_view")
|
success_url = reverse_lazy("atlas:condition_view")
|
||||||
|
|
||||||
|
|
||||||
class FindingDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class FindingDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
|
||||||
model = Finding
|
model = Finding
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
success_url = reverse_lazy("atlas:finding_view")
|
success_url = reverse_lazy("atlas:finding_view")
|
||||||
|
|
||||||
|
|
||||||
class StructureDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
class StructureDelete(RevisionMixin, AtlasEditorRequiredMixin, DeleteView):
|
||||||
model = Structure
|
model = Structure
|
||||||
template_name = "confirm_delete.html"
|
template_name = "confirm_delete.html"
|
||||||
success_url = reverse_lazy("atlas:structure_view")
|
success_url = reverse_lazy("atlas:structure_view")
|
||||||
@@ -867,7 +875,7 @@ def add_collection_to_case_form(request, case_id):
|
|||||||
# return reverse("atlas:case_detail", kwargs={"pk": pk})
|
# return reverse("atlas:case_detail", kwargs={"pk": pk})
|
||||||
|
|
||||||
|
|
||||||
class AtlasClone(AtlasCreateBase, CreateView):
|
class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
||||||
"""Clones a existing atlas"""
|
"""Clones a existing atlas"""
|
||||||
|
|
||||||
# fields = '__all__'
|
# fields = '__all__'
|
||||||
@@ -877,11 +885,23 @@ class AtlasClone(AtlasCreateBase, CreateView):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
# print(self.request)
|
# print(self.request)
|
||||||
old_object = get_object_or_404(Case, pk=self.kwargs["pk"])
|
old_object = get_object_or_404(Case, pk=self.kwargs["pk"])
|
||||||
initial_data = model_to_dict(old_object, exclude=["id"])
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.request.user.groups.filter(name="atlas_editor").exists()
|
||||||
|
or self.request.user.is_superuser
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.request.user not in old_object.get_author_objects():
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
initial_data = model_to_dict(old_object, exclude=["id"])
|
||||||
return initial_data
|
return initial_data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_is_author_or_atlas_editor
|
@user_is_author_or_atlas_editor
|
||||||
def atlas_scrap(request, pk):
|
def atlas_scrap(request, pk):
|
||||||
|
|||||||
@@ -2327,6 +2327,10 @@ class GenericViewBase:
|
|||||||
class ExamCloneMixin:
|
class ExamCloneMixin:
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
|
old_object = get_object_or_404(self.model, pk=self.kwargs["exam_id"])
|
||||||
|
|
||||||
|
if self.request.user not in old_object.get_author_objects() and not self.request.user.is_superuser:
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
initial_data = model_to_dict(old_object, exclude=["id"])
|
initial_data = model_to_dict(old_object, exclude=["id"])
|
||||||
|
|
||||||
# We manually transfer the forign keys / m2m relationships
|
# We manually transfer the forign keys / m2m relationships
|
||||||
|
|||||||
@@ -271,6 +271,9 @@ def long_clone(request, pk):
|
|||||||
new_item.pk = None # autogen a new pk (item_id)
|
new_item.pk = None # autogen a new pk (item_id)
|
||||||
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
||||||
|
|
||||||
|
if request.user not in new_item.get_author_objects() and not request.user.is_superuser:
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
form = LongForm(request.POST or None, instance=new_item)
|
form = LongForm(request.POST or None, instance=new_item)
|
||||||
|
|
||||||
series_formset = SeriesFormSet()
|
series_formset = SeriesFormSet()
|
||||||
|
|||||||
+2
-1
@@ -519,7 +519,8 @@ def page_not_found(request, exception):
|
|||||||
|
|
||||||
|
|
||||||
def page_forbidden(request, exception):
|
def page_forbidden(request, exception):
|
||||||
response = render(request, "403.html")
|
|
||||||
|
response = render(request, "403.html", context={"exception": exception})
|
||||||
|
|
||||||
response.status_code = 403
|
response.status_code = 403
|
||||||
|
|
||||||
|
|||||||
@@ -212,8 +212,12 @@ def rapid_clone(request, pk):
|
|||||||
new_item.pk = None # autogen a new pk (item_id)
|
new_item.pk = None # autogen a new pk (item_id)
|
||||||
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
# new_item.name = "Copy of " + new_item.name #need to change uniques
|
||||||
|
|
||||||
|
if request.user not in new_item.get_author_objects() and not request.user.is_superuser:
|
||||||
|
raise PermissionDenied() # or Http404
|
||||||
|
|
||||||
form = RapidForm(request.POST or None, instance=new_item)
|
form = RapidForm(request.POST or None, instance=new_item)
|
||||||
|
|
||||||
|
|
||||||
image_formset = ImageFormSet()
|
image_formset = ImageFormSet()
|
||||||
answer_formset = AnswerFormSet()
|
answer_formset = AnswerFormSet()
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -4,7 +4,11 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="">
|
<div class="">
|
||||||
<h2>403 error</h2>
|
<h2>403 error</h2>
|
||||||
|
{% if exception and not exception.strip %}
|
||||||
|
<p>{{ exception }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Forbidden</p>
|
||||||
|
{% endif %}
|
||||||
<img src="{% static 'img/x-ray-radiation-no-unauthorised.png' %}" width="500px">
|
<img src="{% static 'img/x-ray-radiation-no-unauthorised.png' %}" width="500px">
|
||||||
Forbidden
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user