Merge pull request #1249 from ae-utbm/membership-set-old

prevent csrf on `MembershipSetOldView`
This commit is contained in:
thomas girod
2025-11-19 13:04:51 +01:00
committed by GitHub
8 changed files with 90 additions and 45 deletions

View File

@@ -7,7 +7,7 @@ from django.conf import settings
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Max from django.db.models import Max
from django.test import TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import localdate, localtime, now from django.utils.timezone import localdate, localtime, now
from model_bakery import baker from model_bakery import baker
@@ -532,6 +532,35 @@ class TestMembership(TestClub):
assert new_board == initial_board assert new_board == initial_board
@pytest.mark.django_db
def test_membership_set_old(client: Client):
membership = baker.make(Membership, end_date=None, user=(subscriber_user.make()))
client.force_login(membership.user)
response = client.post(
reverse("club:membership_set_old", kwargs={"membership_id": membership.id})
)
assertRedirects(
response, reverse("core:user_clubs", kwargs={"user_id": membership.user_id})
)
membership.refresh_from_db()
assert membership.end_date == localdate()
@pytest.mark.django_db
def test_membership_delete(client: Client):
user = baker.make(User, is_superuser=True)
membership = baker.make(Membership)
client.force_login(user)
url = reverse("club:membership_delete", kwargs={"membership_id": membership.id})
response = client.get(url)
assert response.status_code == 200
response = client.post(url)
assertRedirects(
response, reverse("core:user_clubs", kwargs={"user_id": membership.user_id})
)
assert not Membership.objects.filter(id=membership.id).exists()
@pytest.mark.django_db @pytest.mark.django_db
class TestJoinClub: class TestJoinClub:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)

View File

@@ -34,7 +34,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
from django.core.paginator import InvalidPage, Paginator from django.core.paginator import InvalidPage, Paginator
from django.db.models import F, Q, Sum from django.db.models import F, Q, Sum
from django.http import Http404, HttpResponseRedirect, StreamingHttpResponse from django.http import Http404, StreamingHttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
@@ -43,6 +43,7 @@ from django.utils.timezone import now
from django.utils.translation import gettext from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, View from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.forms import ( from club.forms import (
@@ -544,33 +545,17 @@ class ClubCreateView(PermissionRequiredMixin, CreateView):
permission_required = "club.add_club" permission_required = "club.add_club"
class MembershipSetOldView(CanEditMixin, DetailView): class MembershipSetOldView(CanEditMixin, SingleObjectMixin, View):
"""Set a membership as beeing old.""" """Set a membership as being old."""
model = Membership model = Membership
pk_url_kwarg = "membership_id" pk_url_kwarg = "membership_id"
def get(self, request, *args, **kwargs): def post(self, *_args, **_kwargs):
self.object = self.get_object() self.object = self.get_object()
self.object.end_date = timezone.now() self.object.end_date = timezone.now()
self.object.save() self.object.save()
return HttpResponseRedirect( return redirect("core:user_clubs", user_id=self.object.user_id)
reverse(
"club:club_members",
args=self.args,
kwargs={"club_id": self.object.club.id},
)
)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return HttpResponseRedirect(
reverse(
"club:club_members",
args=self.args,
kwargs={"club_id": self.object.club.id},
)
)
class MembershipDeleteView(PermissionRequiredMixin, DeleteView): class MembershipDeleteView(PermissionRequiredMixin, DeleteView):
@@ -582,7 +567,7 @@ class MembershipDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = "club.delete_membership" permission_required = "club.delete_membership"
def get_success_url(self): def get_success_url(self):
return reverse_lazy("core:user_clubs", kwargs={"user_id": self.object.user.id}) return reverse_lazy("core:user_clubs", kwargs={"user_id": self.object.user_id})
class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):

View File

@@ -21,6 +21,8 @@ $secondary-neutral-dark-color: hsl(40, 57.6%, 17%);
$white-color: hsl(219.6, 20.8%, 98%); $white-color: hsl(219.6, 20.8%, 98%);
$black-color: hsl(0, 0%, 17%); $black-color: hsl(0, 0%, 17%);
$red-text-color: #eb2f06;
$hovered-red-text-color: #ff4d4d;
$faceblue: hsl(221, 44%, 41%); $faceblue: hsl(221, 44%, 41%);
$twitblue: hsl(206, 82%, 63%); $twitblue: hsl(206, 82%, 63%);

View File

@@ -745,4 +745,32 @@ form {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: var(--nf-input-size); background-size: var(--nf-input-size);
} }
&.no-margin {
margin:0;
}
// a submit input that should look like a regular <a>
input[type="submit"], button {
&.link-like {
color: $primary-dark-color;
&:hover {
color: $primary-light-color;
}
&.link-red {
color: $red-text-color;
&:hover {
color: $hovered-red-text-color;
}
}
font-weight: normal;
font-size: 100%;
margin: auto;
background: none;
border: none;
cursor: pointer;
padding: 0;
}
}
} }

View File

@@ -5,9 +5,6 @@ $text-color: white;
$background-color-hovered: #283747; $background-color-hovered: #283747;
$red-text-color: #eb2f06;
$hovered-red-text-color: #ff4d4d;
.header { .header {
box-sizing: border-box; box-sizing: border-box;
background-color: $deepblue; background-color: $deepblue;
@@ -251,12 +248,15 @@ $hovered-red-text-color: #ff4d4d;
justify-content: flex-start; justify-content: flex-start;
} }
a {
color: $text-color;
}
a, a,
button { button {
font-size: 100%; font-size: 100%;
margin: 0; margin: 0;
text-align: right; text-align: right;
color: $text-color;
margin-top: auto; margin-top: auto;
&:hover { &:hover {
@@ -268,19 +268,6 @@ $hovered-red-text-color: #ff4d4d;
margin: 0; margin: 0;
display: inline; display: inline;
} }
#logout-form button {
color: $red-text-color;
&:hover {
color: $hovered-red-text-color;
}
background: none;
border: none;
cursor: pointer;
padding: 0;
}
} }
} }
} }

View File

@@ -519,7 +519,6 @@ th {
td { td {
margin: 5px; margin: 5px;
border-collapse: collapse; border-collapse: collapse;
vertical-align: top;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@@ -61,7 +61,9 @@
<a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a> <a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a>
<form id="logout-form" method="post" action="{{ url("core:logout") }}"> <form id="logout-form" method="post" action="{{ url("core:logout") }}">
{% csrf_token %} {% csrf_token %}
<button type="submit">{% trans %}Logout{% endtrans %}</button> <button type="submit" class="link-like link-red">
{% trans %}Logout{% endtrans %}
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -17,7 +17,9 @@
<td>{% trans %}Description{% endtrans %}</td> <td>{% trans %}Description{% endtrans %}</td>
<td>{% trans %}Since{% endtrans %}</td> <td>{% trans %}Since{% endtrans %}</td>
<td></td> <td></td>
<td></td> {% if user.has_perm("club.delete_membership") %}
<td></td>
{% endif %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -28,7 +30,16 @@
<td>{{ m.description }}</td> <td>{{ m.description }}</td>
<td>{{ m.start_date }}</td> <td>{{ m.start_date }}</td>
{% if m.can_be_edited_by(user) %} {% if m.can_be_edited_by(user) %}
<td><a href="{{ url('club:membership_set_old', membership_id=m.id) }}">{% trans %}Mark as old{% endtrans %}</a></td> <td>
<form
method="post"
action="{{ url('club:membership_set_old', membership_id=m.id) }}"
class="no-margin"
>
{% csrf_token %}
<input type="submit" class="link-like" value="{% trans %}Mark as old{% endtrans %}" />
</form>
</td>
{% endif %} {% endif %}
{% if user.has_perm("club.delete_membership") %} {% if user.has_perm("club.delete_membership") %}
<td><a href="{{ url('club:membership_delete', membership_id=m.id) }}">{% trans %}Delete{% endtrans %}</a></td> <td><a href="{{ url('club:membership_delete', membership_id=m.id) }}">{% trans %}Delete{% endtrans %}</a></td>
@@ -48,7 +59,9 @@
<td>{% trans %}Description{% endtrans %}</td> <td>{% trans %}Description{% endtrans %}</td>
<td>{% trans %}From{% endtrans %}</td> <td>{% trans %}From{% endtrans %}</td>
<td>{% trans %}To{% endtrans %}</td> <td>{% trans %}To{% endtrans %}</td>
{% if user.has_perm("club.delete_membership") %}
<td></td>
{% endif %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>