Skip to content

Commit

Permalink
Merge branch 'dev' into empeche-comparaisons-identiques
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud-D authored Nov 28, 2021
2 parents 2fd8c70 + 9e28a62 commit b02cafc
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 15 deletions.
12 changes: 6 additions & 6 deletions doc/source/back-end/gallery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Il est ensuite possible d'uploader des images via le menu de gauche :

Liens permettant d'uploader des images

Via celui-ci, on peut importer des archives contenant des images (au format ZIP) ou des images seules. Dans ce dernier cas, le formulaire d'*upload* est le suivant :
Via celui-ci, on peut importer des archives (au format ZIP) contenant des images ou des images seules. Dans ce dernier cas, le formulaire d'*upload* est le suivant :

.. figure:: ../images/gallery/nouvelle-image.png
:align: center
Expand All @@ -33,21 +33,21 @@ Via celui-ci, on peut importer des archives contenant des images (au format ZIP)

Comme on peut le voir, chaque image doit posséder au minimum un titre et peut posséder une légende, qui sera employée par la suite. Il est donc conseillé de remplir également ce second champ, bien que ce ne soit pas obligatoire. Quant à l'image elle-même, sa taille ne peut pas excéder 1024 Kio.

.. attention::
Le titre de l'image n'entre pas en compte dans le nommage de l'image une fois cette dernière téléchargée. Afin de réduire le risque de rencontrer des conflits de noms de fichiers, ces derniers sont hashés.

Une fois l'image uploadée, il est possible d'y effectuer différentes actions sur la page qui lui est spécifique :

.. figure:: ../images/gallery/gestion-image.png
:align: center

Gestion d'une image

Autrement dit,
Autrement dit :

+ En modifier le titre, la légende ou encore l'image en elle-même. À noter que le titre et la légende peuvent être modifiés **sans qu'il ne soit nécessaire** d'uploader une nouvelle image.
+ En modifier le titre, la légende ou encore l'image en elle-même. À noter que le titre et la légende peuvent être modifiés **sans qu'il ne soit nécessaire** d'uploader une nouvelle image. Si une nouvelle version de l'image est uploadée, l'ancienne version de l'image n'est pas supprimée du serveur et reste accessible depuis son URL ; un nouvel identifiant (et donc une nouvelle URL) sera attribué à la nouvelle version de l'image. Cela signifie notamment que mettre à jour une image ne changera pas l'image là où elle a déjà été utilisée (tutoriel, article, message, ...). Ce comportement permet d'éviter que les images utilisées dans des contenus validés soient changées sans repasser par une validation.
+ Obtenir le code à insérer dans un champ de texte acceptant le Markdown pour l'image en elle-même, sa miniature ou encore la miniature accompagnée du lien vers l'image en taille réelle.

.. attention::
Le titre de l'image n'entre pas en compte dans le nommage de l'image une fois cette dernière téléchargée. Afin de réduire le risque de rencontrer des conflits de noms de fichiers, ces derniers sont hashés.

Les utilisateurs et leurs droits
--------------------------------

Expand Down
9 changes: 4 additions & 5 deletions doc/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ Contribuer à Zeste De Savoir
1. Créez une branche pour contenir votre travail.
2. Faites vos modifications.
3. Ajoutez un test pour votre modification. Seules les modifications de documentation et les réusinages n'ont pas besoin de nouveaux tests.
4. Assurez-vous que vos tests passent en utilisant la commande ``python manage.py test`` (`voir la documentation <https://docs.djangoproject.com/fr/1.10/topics/testing/overview/#running-tests>`_). Lancer la commande sur tous les tests du site risque de prendre un certain temps et n'est pas nécessaire : les tests seront de toute manière lancés de manière automatisée sur votre *pull request*.
5. Si vous avez fait des modifications du _frontend_, jouez les tests associés : ``yarn test``.
6. Si vous modifiez les modèles (les fichiers ``models.py``), n'oubliez pas de créer les fichiers de migration : ``python manage.py makemigrations``.
7. Poussez votre travail et faites une *pull request*.
8. Si votre travail nécessite des actions spécifiques lors du déploiement, précisez-les dans le corps de votre *pull request*. Elles seront ajoutées au *changelog* par le mainteneur qui effectuera le *merge*.
4. Assurez-vous que vos tests passent en utilisant la commande ``make test-back`` (voyez la `page dédiée <./guides/backend-tests.html>`_ pour plus de détails). Lancer la commande sur tous les tests du site risque de prendre un certain temps et n'est pas nécessaire : les tests seront de toute manière lancés de manière automatisée sur votre *pull request*.
5. Si vous avez modifié les modèles (les fichiers ``models.py``), n'oubliez pas de créer les fichiers de migration : ``python manage.py makemigrations``.
6. Poussez votre travail et faites une *pull request*.
7. Si votre travail nécessite des actions spécifiques lors du déploiement, précisez-les dans le corps de votre *pull request*. Elles seront ajoutées au *changelog* par le mainteneur qui effectuera le *merge*.

Quelques bonnes pratiques
-------------------------
Expand Down
14 changes: 13 additions & 1 deletion doc/source/install/install-linux.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,23 @@ Une fois que c'est fait, vous pouvez directement lancer votre instance à l'aide
make zmd-start # démarrer zmarkdown
make run-back # démarer le serveur django


Stoppez le serveur à l'aide de ctrl+c. Pour sortir de votre environnement, tapez ``deactivate``.

Vous pouvez également `indiquer à Git de ne pas effectuer de commit s'il y a des erreurs de formatage dans le code <../utils/git-pre-hook.html>`__.

Si vous utilisez un shell autre que bash, et que vous avez l’erreur suivante quand vous activez ``zdsenv`` :

.. sourcecode:: bash

…/zds-site/zdsenv/bin/activate:67: command not found: nvm

Alors c’est très probablement dû au script d’installation de nvm qui ne gère que Bash. Pour corriger ce problème, ouvrez votre fichier ``.bashrc`` et copiez les lignes concernant nvm dans le fichier de configuration de votre shell. Ces fichiers se trouvent dans votre répertoire utilisateur, par exemple :

.. sourcecode:: bash

~/.bashrc
~/.zshrc

Plus d'informations
-------------------

Expand Down
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 %}
3 changes: 3 additions & 0 deletions zds/gallery/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.fields["physical"].required = False
self.fields["physical"].label = _(
"Changer l'image (attention : cela ne changera pas l'image là où vous l'avez déjà utilisée ; un nouvel identifiant sera attribué à la nouvelle image)"
)

self.helper = FormHelper()
self.helper.form_class = "clearfix"
Expand Down
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 b02cafc

Please sign in to comment.