Skip to content

Commit

Permalink
Affichage des comptes sur un même réseau IPV6 dans la page de multi-c…
Browse files Browse the repository at this point in the history
…omptes (#6124)

Modification de la fonction member_from_ip afin qu'elle retourne tous les membres sur un même bloc IPV6. Adaption du template associé. Ajout de tests fonctionnels et d'autorisation. Voir issue #4103.
  • Loading branch information
PetitMote authored Nov 28, 2021
1 parent 8fad392 commit 387a6b0
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 3 deletions.
23 changes: 22 additions & 1 deletion templates/member/admin/memberip.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
{% block content %}
<p>
{% blocktrans %}
Liste des membres dont la dernière IP connue est {{ ip }}
Liste des membres dont la dernière IP connue est <code>{{ ip }}</code>
{% endblocktrans %}
</p>

Expand All @@ -37,4 +37,25 @@
{% endfor %}
</ul>
</div>

{# Checks if it's an IPV6 to show the members from the same IPV6 network #}
{% if ":" in ip %}
<p>
{% blocktrans %}
Liste des membres dont la dernière IP connue fait partie du bloc <code>{{ network_ip }}</code>
{% endblocktrans %}
</p>

<div class="members">
<ul>
{% for member in network_members %}
<li>{% include "misc/member_item.part.html" with member=member info=member.last_visit|format_date:True avatar=True %}</li>
{% endfor %}
</ul>
</div>

<p>
En IPv6, les adresses sont attribuées par bloc d'IP. Un bot de spam peut donc facilement changer d'adresse IP au sein de ce bloc. Sont affichés ici tous les membres dont l'IPv6 fait partie du même bloc que l'IP demandée.
</p>
{% endif %}
{% endblock %}
95 changes: 95 additions & 0 deletions zds/member/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _

from zds.member.views import member_from_ip
from zds.notification.models import TopicAnswerSubscription
from zds.member.factories import (
ProfileFactory,
Expand Down Expand Up @@ -1723,3 +1724,97 @@ def send_messages(messages):
)
self.assertEqual(result.status_code, 200)
self.assertIn(escape("Impossible d'envoyer l'email."), result.content.decode("utf-8"))


class IpListingsTests(TestCase):
"""Test the member_from_ip function : listing users from a same IPV4/IPV6 address or same IPV6 network."""

def setUp(self) -> None:
self.staff = StaffProfileFactory().user
self.regular_user = ProfileFactory()

self.user_ipv4_same_ip_1 = ProfileFactory(last_ip_address="155.128.92.54")
self.user_ipv4_same_ip_1.user.username = "user_ipv4_same_ip_1"
self.user_ipv4_same_ip_1.user.save()

self.user_ipv4_same_ip_2 = ProfileFactory(last_ip_address="155.128.92.54")
self.user_ipv4_same_ip_2.user.username = "user_ipv4_same_ip_2"
self.user_ipv4_same_ip_2.user.save()

self.user_ipv4_different_ip = ProfileFactory(last_ip_address="155.128.92.55")
self.user_ipv4_different_ip.user.username = "user_ipv4_different_ip"
self.user_ipv4_different_ip.user.save()

self.user_ipv6_same_ip_1 = ProfileFactory(last_ip_address="2001:8f8:1425:60a0:7981:9852:1493:3721")
self.user_ipv6_same_ip_1.user.username = "user_ipv6_same_ip_1"
self.user_ipv6_same_ip_1.user.save()

self.user_ipv6_same_ip_2 = ProfileFactory(last_ip_address="2001:8f8:1425:60a0:7981:9852:1493:3721")
self.user_ipv6_same_ip_2.user.username = "user_ipv6_same_ip_2"
self.user_ipv6_same_ip_2.user.save()

self.user_ipv6_same_network = ProfileFactory(last_ip_address="2001:8f8:1425:60a0:9852:7981:3721:1493")
self.user_ipv6_same_network.user.username = "user_ipv6_same_network"
self.user_ipv6_same_network.user.save()

self.user_ipv6_different_network = ProfileFactory(last_ip_address="8f8:60a0:3721:1425:7981:1493:2001:9852")
self.user_ipv6_different_network.user.username = "user_ipv6_different_network"
self.user_ipv6_different_network.user.save()

def test_same_ipv4(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=[self.user_ipv4_same_ip_1.last_ip_address]))
self.assertContains(response, self.user_ipv4_same_ip_1.user.username)
self.assertContains(response, self.user_ipv4_same_ip_2.user.username)
self.assertContains(response, self.user_ipv4_same_ip_1.last_ip_address)
self.assertNotContains(response, self.user_ipv4_different_ip.user.username)

def test_different_ipv4(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=[self.user_ipv4_different_ip.last_ip_address]))
self.assertContains(response, self.user_ipv4_different_ip.user.username)
self.assertContains(response, self.user_ipv4_different_ip.last_ip_address)
self.assertNotContains(response, self.user_ipv6_same_ip_1.user.username)

def test_same_ipv6_and_same_ipv6_network(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=[self.user_ipv6_same_ip_1.last_ip_address]))
self.assertContains(response, self.user_ipv6_same_ip_1.user.username)
self.assertContains(response, self.user_ipv6_same_ip_2.user.username)
self.assertContains(response, self.user_ipv6_same_network.user.username)
self.assertNotContains(response, self.user_ipv6_different_network.user.username)

def test_same_ipv6_network_but_different_ip(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=[self.user_ipv6_same_network.last_ip_address]))
self.assertContains(response, self.user_ipv6_same_network.user.username)
self.assertContains(response, self.user_ipv6_same_ip_1.user.username)
self.assertContains(response, self.user_ipv6_same_ip_2.user.username)
self.assertNotContains(response, self.user_ipv6_different_network.user.username)

def test_different_ipv6_network(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=[self.user_ipv6_different_network.last_ip_address]))
self.assertContains(response, self.user_ipv6_different_network.user.username)
self.assertNotContains(response, self.user_ipv6_same_ip_1.user.username)
self.assertNotContains(response, self.user_ipv6_same_ip_2.user.username)
self.assertNotContains(response, self.user_ipv6_same_network.user.username)

def test_access_rights_to_ip_page_as_regular_user(self) -> None:
self.client.force_login(self.regular_user.user)
response = self.client.get(reverse(member_from_ip, args=["0.0.0.0"]))
self.assertEqual(response.status_code, 403)

def test_access_rights_to_ip_page_as_anonymous(self) -> None:
response = self.client.get(reverse(member_from_ip, args=["0.0.0.0"]))
self.assertEqual(response.status_code, 302)

def test_access_rights_to_ip_page_as_staff(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=["0.0.0.0"]))
self.assertEqual(response.status_code, 200)

def test_template_used_by_ip_page(self) -> None:
self.client.force_login(self.staff)
response = self.client.get(reverse(member_from_ip, args=["0.0.0.0"]))
self.assertTemplateUsed(response, "member/admin/memberip.html")
15 changes: 13 additions & 2 deletions zds/member/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ipaddress
import uuid
from datetime import datetime, timedelta
from urllib.parse import unquote
Expand Down Expand Up @@ -1457,10 +1458,20 @@ def settings_promote(request, user_pk):
@login_required
@permission_required("member.change_profile", raise_exception=True)
def member_from_ip(request, ip_address):
"""List users connected from a particular IP."""
"""List users connected from a particular IP, and an IPV6 subnetwork."""

members = Profile.objects.filter(last_ip_address=ip_address).order_by("-last_visit")
return render(request, "member/admin/memberip.html", {"members": members, "ip": ip_address})
members_and_ip = {"members": members, "ip": ip_address}

if ":" in ip_address: # Check if it's an IPV6
network_ip = ipaddress.ip_network(ip_address + "/64", strict=False).network_address # Get the network / block
# Remove the additional ":" at the end of the network adresse, so we can filter the IP adresses on this network
network_ip = str(network_ip)[:-1]
network_members = Profile.objects.filter(last_ip_address__startswith=network_ip).order_by("-last_visit")
members_and_ip["network_members"] = network_members
members_and_ip["network_ip"] = network_ip

return render(request, "member/admin/memberip.html", members_and_ip)


@login_required
Expand Down

0 comments on commit 387a6b0

Please sign in to comment.