diff --git a/templates/member/admin/memberip.html b/templates/member/admin/memberip.html index 4532788f7a..2d2040132c 100644 --- a/templates/member/admin/memberip.html +++ b/templates/member/admin/memberip.html @@ -25,18 +25,29 @@ {% block content %}
+ {% captureas city %}{% if ip_location %} ({{ ip_location }}){% endif %}{% endcaptureas %}
{% blocktrans %}
- Liste des membres dont la dernière IP connue est {{ ip }}
+ Liste des membres dont la dernière IP connue est {{ ip }}
{{ city }}
{% endblocktrans %}
{% trans "Membre" %} | +{% trans "Inscription" %} | +{% trans "Dernière connexion" %} | + + {% for member in members %} -
---|---|---|
{% include "misc/member_item.part.html" with member=member avatar=True %} | +{{ member.user.date_joined|format_date:True }} | +{{ last_visit }} | +
{% trans "Membre" %} | +{% trans "Inscription" %} | +{% trans "Dernière connexion" %} | + + {% for member in network_members %} -
---|---|---|
{% include "misc/member_item.part.html" with member=member avatar=True %} | +{{ member.user.date_joined|format_date:True }} | +{{ last_visit }} | +
- 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. + {% blocktrans %} + 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. + {% endblocktrans %}
{% endif %} {% endblock %} diff --git a/zds/member/models.py b/zds/member/models.py index 9d16f1a2d8..2c8b50a049 100644 --- a/zds/member/models.py +++ b/zds/member/models.py @@ -1,25 +1,23 @@ -import logging from datetime import datetime -from geoip2.errors import AddressNotFoundError from hashlib import md5 +import logging from django.conf import settings from django.contrib.auth.models import User -from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception -from django.urls import reverse from django.db import models from django.dispatch import receiver +from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from zds.forum.models import Post, Topic -from zds.notification.models import TopicAnswerSubscription +from zds.forum.models import Forum, Post, Topic from zds.member import NEW_PROVIDER_USES from zds.member.managers import ProfileManager +from zds.member.utils import get_geo_location_from_ip +from zds.notification.models import TopicAnswerSubscription from zds.tutorialv2.models.database import PublishableContent from zds.utils import old_slugify from zds.utils.models import Alert, Licence, Hat -from zds.forum.models import Forum import homoglyphs as hg @@ -91,30 +89,16 @@ def get_absolute_url(self): def get_city(self): """ - Uses geo-localization to get physical localization of a profile through its last IP address. - This works relatively well with IPv4 addresses (~city level), but is very imprecise with IPv6 or exotic internet - providers. - The result is cached on an instance level because this method is called a lot in the profile. + Uses geo-localization to get physical localization of a profile through + its last IP address. + The result is cached on an instance level because this method is called + a lot in the profile. :return: The city and the country name of this profile. """ if self._cached_city is not None and self._cached_city[0] == self.last_ip_address: return self._cached_city[1] - try: - geo = GeoIP2().city(self.last_ip_address) - except AddressNotFoundError: - geo_location = "" - except GeoIP2Exception as e: - geo_location = "" - logging.getLogger(__name__).warning( - f"GeoIP2 failed with the following message: '{e}'. " - "The Geolite2 database might not be installed or configured correctly. " - "Check the documentation for guidance on how to install it properly." - ) - else: - city = geo["city"] - country = geo["country_name"] - geo_location = ", ".join(i for i in [city, country] if i) + geo_location = get_geo_location_from_ip(self.last_ip_address) self._cached_city = (self.last_ip_address, geo_location) diff --git a/zds/member/utils.py b/zds/member/utils.py index bfc676d02b..d456916424 100644 --- a/zds/member/utils.py +++ b/zds/member/utils.py @@ -1,10 +1,13 @@ +from geoip2.errors import AddressNotFoundError +import logging + from django.conf import settings -from django.contrib.auth.models import User -from social_django.middleware import SocialAuthExceptionMiddleware from django.contrib import messages -from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import User +from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception from django.urls import reverse -import logging +from django.utils.translation import gettext_lazy as _ +from social_django.middleware import SocialAuthExceptionMiddleware logger = logging.getLogger(__name__) @@ -49,3 +52,27 @@ def get_anonymous_account() -> User: Used for example as a replacement for unregistered users. """ return User.objects.get(username=settings.ZDS_APP["member"]["anonymous_account"]) + + +def get_geo_location_from_ip(ip: str) -> str: + """ + Uses geo-localization to get physical localization of an IP address. + This works relatively well with IPv4 addresses (~city level), but is very + imprecise with IPv6 or exotic internet providers. + :return: The city and the country name of this IP. + """ + try: + geo = GeoIP2().city(ip) + except AddressNotFoundError: + return "" + except GeoIP2Exception as e: + logger.warning( + f"GeoIP2 failed with the following message: '{e}'. " + "The Geolite2 database might not be installed or configured correctly. " + "Check the documentation for guidance on how to install it properly." + ) + return "" + else: + city = geo["city"] + country = geo["country_name"] + return ", ".join(i for i in [city, country] if i) diff --git a/zds/member/views/moderation.py b/zds/member/views/moderation.py index a742a52a4a..81d12305da 100644 --- a/zds/member/views/moderation.py +++ b/zds/member/views/moderation.py @@ -1,14 +1,14 @@ import ipaddress +import logging from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required from django.core.exceptions import PermissionDenied from django.db import transaction -from django.http import HttpResponseBadRequest -from django.urls import reverse -from django.http import Http404 +from django.http import Http404, HttpResponseBadRequest from django.shortcuts import redirect, render, get_object_or_404 +from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_POST @@ -23,7 +23,7 @@ from zds.member.decorator import can_write_and_read_now from zds.member.forms import MiniProfileForm from zds.member.models import Profile, KarmaNote -import logging +from zds.member.utils import get_geo_location_from_ip @login_required @@ -32,17 +32,21 @@ def member_from_ip(request, ip_address): """List users connected from a particular IP, and an IPV6 subnetwork.""" members = Profile.objects.filter(last_ip_address=ip_address).order_by("-last_visit") - members_and_ip = {"members": members, "ip": ip_address} + context_data = { + "members": members, + "ip": ip_address, + "ip_location": get_geo_location_from_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 + context_data["network_members"] = network_members + context_data["network_ip"] = network_ip - return render(request, "member/admin/memberip.html", members_and_ip) + return render(request, "member/admin/memberip.html", context_data) @login_required