WIP: public: change permission denied handling (#8122) #218
|
@ -0,0 +1,23 @@
|
|||
{% extends "combo/page_template.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block combo-content %}
|
||||
<div>
|
||||
<h2>{% trans "You do not have access to this page" %}</h2>
|
||||
{% url 'auth_login' as login_url %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<p>
|
||||
{% trans "Your user do not have the permission to see this page. You can try to" %}
|
||||
<a href="{% url 'auth_logout' %}?next={{ login_url | urlencode }}{{ '?next=' | urlencode }}{{ request.build_absolute_uri | urlencode }}">
|
||||
{% trans "authenticate yourself using another account" %}
|
||||
</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
<p>
|
||||
{% trans "This page is not publicly accessible. You can try to" %}
|
||||
<a href="{{ login_url }}?next={{ request.build_absolute_uri | urlencode }}">
|
||||
{% trans "authenticate yourself" %}</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -24,6 +24,7 @@ from django.contrib import messages
|
|||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core import signing
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import transaction
|
||||
|
@ -545,10 +546,31 @@ def publish_page(request, page, status=200, template_name=None):
|
|||
|
||||
if not page.is_visible(request.user):
|
||||
if not request.user.is_authenticated:
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
if Page.objects.exists() and all(
|
||||
not p.is_visible(request.user) for p in Page.objects.filter(parent_id__isnull=True)
|
||||
):
|
||||
# if user is not connected and none of the first-level pages can
|
||||
yweber marked this conversation as resolved
Outdated
lguerin
commented
ici je mettrais un RequestContext plutôt qu'un Context, sinon tu risques d'avoir plus tard un ticket qui dit qu'on n'a pas accès à la variable site_base, par exemple ici je mettrais un RequestContext plutôt qu'un Context, sinon tu risques d'avoir plus tard un ticket qui dit qu'on n'a pas accès à la variable site_base, par exemple
|
||||
# be viewed, display generic django Permission Denied error
|
||||
raise PermissionDenied()
|
||||
|
||||
return redirect_to_login(request.build_absolute_uri())
|
||||
fpeters
commented
Si j'arrive à suivre correctement, ça ne me semble pas approprié, ça voudrait dire qu'un premier accès sur le portail agent afficherait une page d'erreur, plutôt qu'envoyer l'usager se connecter. J'ai l'impression qu'en fait tout est confus entre les différentes directions, la description un peu courte du ticket, le ticket lié toulouse qui demande une page 403 différente, mes commentaires, etc. Il faut peut-être prendre du recul et refaire une description fonctionnelle un peu exhaustive de ce qu'on veut atteindre, avant de continuer sur le code. Si j'arrive à suivre correctement, ça ne me semble pas approprié, ça voudrait dire qu'un premier accès sur le portail agent afficherait une page d'erreur, plutôt qu'envoyer l'usager se connecter.
J'ai l'impression qu'en fait tout est confus entre les différentes directions, la description un peu courte du ticket, le ticket lié toulouse qui demande une page 403 différente, mes commentaires, etc.
Il faut peut-être prendre du recul et refaire une description fonctionnelle un peu exhaustive de ce qu'on veut atteindre, avant de continuer sur le code.
yweber
commented
Dans le cas ou aucune page n'est accessible oui, j'avais (mal) compris que c'était ce qui était attendu ;)
Je pense qu'on peut fermer cette PR en attendant une description fonctionnelle plus détaillée de ce qui est attendu/voulu ? > Si j'arrive à suivre correctement, ça ne me semble pas approprié, ça voudrait dire qu'un premier accès sur le portail agent afficherait une page d'erreur, plutôt qu'envoyer l'usager se connecter.
Dans le cas ou aucune page n'est accessible oui, j'avais (mal) compris que c'était ce qui était attendu ;)
> J'ai l'impression qu'en fait tout est confus entre les différentes directions, la description un peu courte du ticket, le ticket lié toulouse qui demande une page 403 différente, mes commentaires, etc.
> Il faut peut-être prendre du recul et refaire une description fonctionnelle un peu exhaustive de ce qu'on veut atteindre, avant de continuer sur le code.
Je pense qu'on peut fermer cette PR en attendant une description fonctionnelle plus détaillée de ce qui est attendu/voulu ?
|
||||
raise PermissionDenied()
|
||||
try:
|
||||
yweber marked this conversation as resolved
Outdated
yweber
commented
Je ne sais pas si c'est une bonne idée de faire que la fausse page essaye d'utiliser le même thème que la page non-accessible ? Je ne sais pas si c'est une bonne idée de faire que la fausse page essaye d'utiliser le même thème que la page non-accessible ?
vdeniaud
commented
Je ne suis pas sûr, ici on touche à mes limites de compréhension de combo :) je pense que tu peux mettre Je ne suis pas sûr, ici on touche à mes limites de compréhension de combo :) je pense que tu peux mettre `template_name='combo/private-page-error.html'`, l'hypothèse étant que c'est sûrement plus safe de ne pas mixer deux template différents
fpeters
commented
Je serais à dire de regarder ce qui est fait dans error404 et de faire pareil. Je serais à dire de regarder ce qui est fait dans error404 et de faire pareil.
yweber
commented
Merci à vous deux ! Ok, je force l'utilisation du template 'standard' Merci à vous deux !
Ok, je force l'utilisation du template 'standard'
fpeters
commented
Ça n'est pas tout ce que error404 fait; il y a 1/ vérifier qu'il y a des pages du site accessible et si ça n'est pas le cas retourner un gabarit minimal, pour les mêmes raisons que sur la page 404 (c'est suite à un audit de sécurité, cf #41514); puis 2/ prendre une page qui aurait comme slug "403" et si elle n'existe pas, partir sur la base de qui est à l'accueil (cf #43851, c'est pour avoir le pied de page). > Ok, je force l'utilisation du template 'standard'
Ça n'est pas tout ce que error404 fait; il y a 1/ vérifier qu'il y a des pages du site accessible et si ça n'est pas le cas retourner un gabarit minimal, pour les mêmes raisons que sur la page 404 (c'est suite à un audit de sécurité, cf #41514); puis 2/ prendre une page qui aurait comme slug "403" et si elle n'existe pas, partir sur la base de qui est à l'accueil (cf #43851, c'est pour avoir le pied de page).
yweber
commented
J'ai l'impression qu'on comprend le ticket de deux manières différentes :
C'est bien ça ? J'ai l'impression qu'on comprend le ticket de deux manières différentes :
- j'avais compris que le but du ticket était d'avoir la possibilité d'ajouter un message (par page) à afficher si la page n'est pas accessible
- je crois comprendre que tu me guide vers le fait d'implémenter une page d'erreur PermissionDenied configurable pour tout le portail (le message personnalisable serait une page avec "403" comme slug)
C'est bien ça ?
fpeters
commented
Il me semble en effet que la direction prise répète des erreurs et questions déjà traitées pour la page 404; le point 1/ est important pour ne pas prochain audit se taper le même commentaire, le point 2/ relève davantage de l'anticipation des besoins, ne pas devoir défaire/refaire quand quelqu'un viendra souhaiter du gras ou un lien dans le texte personnalisé. Il me semble en effet que la direction prise répète des erreurs et questions déjà traitées pour la page 404; le point 1/ est important pour ne pas prochain audit se taper le même commentaire, le point 2/ relève davantage de l'anticipation des besoins, ne pas devoir défaire/refaire quand quelqu'un viendra souhaiter du gras ou un lien dans le texte personnalisé.
fpeters
commented
Alors oui je m'éloigne ainsi du ticket, mais il ne donne pas de cas d'usage qui justifierait que le message soit différent par page et Benjamin y a lié le ticket #84346 qui correspond davantage à ma description. Alors oui je m'éloigne ainsi du ticket, mais il ne donne pas de cas d'usage qui justifierait que le message soit différent par page et Benjamin y a lié le ticket #84346 qui correspond davantage à ma description.
yweber
commented
Super, merci :) Je pars sur cette implémentation 404 like. Super, merci :) Je pars sur cette implémentation 404 like.
|
||||
page = Page.objects.get(slug='403')
|
||||
template_name = None
|
||||
if not page.is_visible(request.user):
|
||||
raise Page.DoesNotExist
|
||||
except Page.DoesNotExist:
|
||||
if not request.user.is_authenticated:
|
||||
# No custom 403 page, user not logged in : redirect to login
|
||||
return redirect_to_login(request.build_absolute_uri())
|
||||
# User is logged in but do not have suffisent permissions
|
||||
yweber marked this conversation as resolved
Outdated
lguerin
commented
les params de la chaîne à traduire sont à mettre à l'extérieur du _() les params de la chaîne à traduire sont à mettre à l'extérieur du _()
|
||||
# displaying a small template inviting to logout/login
|
||||
page = Page.objects.filter(slug='index', parent=None).first() or Page()
|
||||
page.redirect_url = None
|
||||
page.public = True
|
||||
page.title = _('Permission denied')
|
||||
page.template_name = 'standard'
|
||||
template_name = 'combo/403.html'
|
||||
return publish_page(request, page, status=403, template_name=template_name)
|
||||
|
||||
if page.redirect_url:
|
||||
yweber marked this conversation as resolved
Outdated
vdeniaud
commented
La logique du code est bien mais je suggère de l'écrire tout autrement, en passant par un template dédié :
N'hésite pas à me dire si tu trouves cette idée mauvaise ou qu'il y a une complexité que j'ai raté 😄 La logique du code est bien mais je suggère de l'écrire tout autrement, en passant par un template dédié :
* Après rendu du message d'erreur, le code ici devient juste `return render(request, 'combo/private-page-error.html', context={'error_message': error_msg}, status=403)`
* Dans le template, gérer le cas non connecté et afficher le lien de connexion grâce un bloc `{% if` (ça devrait rendre mieux que de construire du html dans la vue)
* À propos, la construction de l'URL de login peut se permettre d'être méga triviale, genre `href="{% url "auth_login" %}?next={{ request.build_absolute_uri }}"` (le gain en lisibilité outrepasse large le gain en robustesse de passer par urlparse, et le "next" est déjà hardcodé à plein endroits, pas besoin d'aller le chercher dans REDIRECT_FIELD_NAME)
N'hésite pas à me dire si tu trouves cette idée mauvaise ou qu'il y a une complexité que j'ai raté 😄
|
||||
context = {'request': request}
|
||||
|
|
|
@ -337,10 +337,20 @@ def test_page_templated_redirect(app):
|
|||
|
||||
def test_page_private_unlogged(app):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard', public=False)
|
||||
page.save()
|
||||
resp = app.get('/', status=302)
|
||||
assert resp.location.endswith('/login/?next=http%3A//testserver/')
|
||||
Page.objects.create(title='Home', slug='index', template_name='standard', public=True)
|
||||
Page.objects.create(title='private', slug='priv', template_name='standard', public=False)
|
||||
resp = app.get('/priv/', status=302)
|
||||
assert resp.location.endswith('/login/?next=http%3A//testserver/priv/')
|
||||
|
||||
# custom error page : returns 403
|
||||
error_page = Page.objects.create(title='demo', slug='403', public=True)
|
||||
resp = app.get('/priv/', status=403)
|
||||
|
||||
# custom error page not visible : redirect to login
|
||||
yweber marked this conversation as resolved
Outdated
vdeniaud
commented
Ça vaut le coup de taper un resp.click sur le lien de login, et vite fait vérifier qu'on tombe bien sur la page de connexion Ça vaut le coup de taper un resp.click sur le lien de login, et vite fait vérifier qu'on tombe bien sur la page de connexion
|
||||
error_page.public = False
|
||||
error_page.save()
|
||||
resp = app.get('/priv/', status=302)
|
||||
assert resp.location.endswith('/login/?next=http%3A//testserver/priv/')
|
||||
|
||||
|
||||
def test_page_private_logged_in(app, admin_user):
|
||||
|
@ -351,6 +361,44 @@ def test_page_private_logged_in(app, admin_user):
|
|||
app.get('/', status=200)
|
||||
|
||||
|
||||
def test_page_private_all_private(app):
|
||||
Page.objects.all().delete()
|
||||
Page.objects.create(title='index', slug='index', public=False)
|
||||
p2 = Page.objects.create(title='index2', slug='index2', public=False)
|
||||
|
||||
resp = app.get('/', status=403)
|
||||
assert 'You do not have access to this page' not in resp
|
||||
|
||||
# even with a public 403 page, the error should remain the same
|
||||
Page.objects.create(title='Title of the custom 403 page', slug='403', public=True, parent=p2)
|
||||
resp = app.get('/', status=403)
|
||||
assert 'Title of the custom 403 page' not in resp
|
||||
|
||||
|
||||
def test_page_private_logged_in_no_perm(app, normal_user):
|
||||
group_ok = Group.objects.create(name='g1')
|
||||
Page.objects.all().delete()
|
||||
Page.objects.create(title='index', slug='index', public=True)
|
||||
|
||||
priv = Page.objects.create(title='index2', slug='priv', public=False)
|
||||
priv.groups.set([group_ok])
|
||||
normal_user.groups.set([])
|
||||
|
||||
app = login(app, username='normal-user', password='normal-user')
|
||||
|
||||
# Falling on default small 403 template proposing to switch account
|
||||
resp = app.get('/priv/', status=403)
|
||||
assert 'You do not have access to this page' in resp
|
||||
assert 'authenticate yourself using another account' in resp
|
||||
|
||||
# using a custom empty page as 403 error page
|
||||
Page.objects.create(title='Title of the custom 403 page', slug='403', public=True)
|
||||
resp = app.get('/priv/', status=403)
|
||||
assert 'You do not have access to this page' not in resp
|
||||
assert 'authenticate yourself using another account' not in resp
|
||||
assert 'Title of the custom 403 page' in resp
|
||||
|
||||
|
||||
def test_page_skeleton(app):
|
||||
Page.objects.all().delete()
|
||||
page = Page(
|
||||
|
|
On ne devrait jamais tomber dans ce "else", c'est utile de le laisser ?