From 2d0ed84b2b57a4fd003ed5455bad344c9fbd2feb Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@cccv.de> Date: Sat, 4 Sep 2021 14:25:15 +0200 Subject: [PATCH] Dedicated error page for permission errors Prior to this change permission errors (i.e. the user is logged in but does not have a required group) were reported with flash('Access denied') and a redirect to the selfservice index page. This causes two problems: The error is reported with HTTP status 301/200 which is difficult to check for in tests. This can also cause redirect loops as soon as the selfservice uses more differentiated permission checks (see #104). With this change a dedicated error page is displayed in place the requested page and the HTTP status 403 is returned. This is implemented with flask's errorhandler concept for 403. --- tests/test_invite.py | 5 +- tests/test_role.py | 14 ++- tests/test_rolemod.py | 15 ++- uffd/__init__.py | 8 +- uffd/invite/views.py | 5 +- uffd/mail/views.py | 7 +- uffd/mfa/views.py | 3 +- uffd/oauth2/views.py | 14 +-- uffd/role/views.py | 7 +- uffd/rolemod/views.py | 16 +-- uffd/session/views.py | 3 +- uffd/templates/403.html | 23 ++++ uffd/translations/de/LC_MESSAGES/messages.mo | Bin 31076 -> 31369 bytes uffd/translations/de/LC_MESSAGES/messages.po | 111 ++++++++++--------- uffd/user/views_group.py | 9 +- uffd/user/views_user.py | 7 +- 16 files changed, 135 insertions(+), 112 deletions(-) create mode 100644 uffd/templates/403.html diff --git a/tests/test_invite.py b/tests/test_invite.py index 7c6bf2fb..f3d652ac 100644 --- a/tests/test_invite.py +++ b/tests/test_invite.py @@ -326,8 +326,7 @@ class TestInviteAdminViews(UffdTestCase): self.login_as('user') r = self.client.get(path=url_for('invite.index'), follow_redirects=True) dump('invite_index_noaccess', r) - self.assertEqual(r.status_code, 200) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) def test_index_signupperm(self): current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access' @@ -345,7 +344,6 @@ class TestInviteAdminViews(UffdTestCase): self.login_as('user') r = self.client.get(path=url_for('invite.index'), follow_redirects=True) self.assertEqual(r.status_code, 200) - self.assertNotIn('Access denied'.encode(), r.data) self.assertNotIn(token1.encode(), r.data) self.assertIn(token2.encode(), r.data) self.assertNotIn(token3.encode(), r.data) @@ -362,7 +360,6 @@ class TestInviteAdminViews(UffdTestCase): self.login_as('user') r = self.client.get(path=url_for('invite.index'), follow_redirects=True) self.assertEqual(r.status_code, 200) - self.assertNotIn('Access denied'.encode(), r.data) self.assertNotIn('testrole1'.encode(), r.data) self.assertIn('testrole2'.encode(), r.data) diff --git a/tests/test_role.py b/tests/test_role.py index 6fef9547..0adbde99 100644 --- a/tests/test_role.py +++ b/tests/test_role.py @@ -251,6 +251,7 @@ class TestRoleViews(UffdTestCase): ldap.session.commit() role = Role(name='test') db.session.add(role) + role.groups[self.get_admin_group()] = RoleGroup() user1 = self.get_user() user2 = self.get_admin() service_user = User.query.get('uid=service,{}'.format(self.app.config['LDAP_USER_SEARCH_BASE'])) @@ -281,26 +282,31 @@ class TestRoleViews(UffdTestCase): ldap.session.delete(user) ldap.session.add(User(loginname='service', is_service_user=True, mail='service@example.com', displayname='Service')) ldap.session.commit() + admin_role = Role(name='admin', is_default=True) + db.session.add(admin_role) + admin_role.groups[self.get_admin_group()] = RoleGroup() role = Role(name='test', is_default=True) db.session.add(role) user1 = self.get_user() user2 = self.get_admin() service_user = User.query.get('uid=service,{}'.format(self.app.config['LDAP_USER_SEARCH_BASE'])) role.members.add(service_user) - self.assertSetEqual(set(user1.roles_effective), {role}) - self.assertSetEqual(set(user2.roles_effective), {role}) + self.assertSetEqual(set(user1.roles_effective), {role, admin_role}) + self.assertSetEqual(set(user2.roles_effective), {role, admin_role}) self.assertSetEqual(set(service_user.roles_effective), {role}) db.session.commit() role_id = role.id + admin_role_id = admin_role.id self.assertSetEqual(set(role.members), {service_user}) r = self.client.get(path=url_for('role.unset_default', roleid=role.id), follow_redirects=True) dump('role_unset_default', r) self.assertEqual(r.status_code, 200) role = Role.query.get(role_id) + admin_role = Role.query.get(admin_role_id) service_user = User.query.get('uid=service,{}'.format(self.app.config['LDAP_USER_SEARCH_BASE'])) self.assertSetEqual(set(role.members), {service_user}) - self.assertSetEqual(set(user1.roles_effective), set()) - self.assertSetEqual(set(user2.roles_effective), set()) + self.assertSetEqual(set(user1.roles_effective), {admin_role}) + self.assertSetEqual(set(user2.roles_effective), {admin_role}) ldap.session.delete(service_user) ldap.session.commit() diff --git a/tests/test_rolemod.py b/tests/test_rolemod.py index d94fce37..81a39c97 100644 --- a/tests/test_rolemod.py +++ b/tests/test_rolemod.py @@ -36,8 +36,7 @@ class TestRolemodViews(UffdTestCase): db.session.commit() r = self.client.get(path=url_for('rolemod.index'), follow_redirects=True) dump('rolemod_acl_notmod', r) - self.assertEqual(r.status_code, 200) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) def test_show(self): role = Role(name='test', moderator_group=self.get_access_group()) @@ -64,7 +63,7 @@ class TestRolemodViews(UffdTestCase): db.session.commit() r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True) dump('rolemod_show_noperm', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) def test_show_nomod(self): # Make sure we pass the blueprint-wide acl check @@ -74,7 +73,7 @@ class TestRolemodViews(UffdTestCase): db.session.commit() r = self.client.get(path=url_for('rolemod.show', role_id=role.id), follow_redirects=True) dump('rolemod_show_nomod', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) def test_update(self): role = Role(name='test', description='old_description', moderator_group=self.get_access_group()) @@ -102,7 +101,7 @@ class TestRolemodViews(UffdTestCase): db.session.commit() r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'new_description'}, follow_redirects=True) dump('rolemod_update_noperm', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) self.assertEqual(Role.query.get(role.id).description, 'old_description') def test_update_nomod(self): @@ -113,7 +112,7 @@ class TestRolemodViews(UffdTestCase): db.session.commit() r = self.client.post(path=url_for('rolemod.update', role_id=role.id), data={'description': 'new_description'}, follow_redirects=True) dump('rolemod_update_nomod', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) self.assertEqual(Role.query.get(role.id).description, 'old_description') def test_delete_member(self): @@ -160,7 +159,7 @@ class TestRolemodViews(UffdTestCase): self.assertTrue(user in role.members) r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_dn=user.dn), follow_redirects=True) dump('rolemod_delete_member_noperm', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) user_updated = self.get_admin() role = Role.query.get(role.id) self.assertTrue(user_updated in role.members) @@ -177,7 +176,7 @@ class TestRolemodViews(UffdTestCase): self.assertTrue(user in role.members) r = self.client.get(path=url_for('rolemod.delete_member', role_id=role.id, member_dn=user.dn), follow_redirects=True) dump('rolemod_delete_member_nomod', r) - self.assertIn('Access denied'.encode(), r.data) + self.assertEqual(r.status_code, 403) user_updated = self.get_admin() role = Role.query.get(role.id) self.assertTrue(user_updated in role.members) diff --git a/uffd/__init__.py b/uffd/__init__.py index 1a958569..90feeafd 100644 --- a/uffd/__init__.py +++ b/uffd/__init__.py @@ -2,12 +2,12 @@ import os import secrets import sys -from flask import Flask, redirect, url_for, request +from flask import Flask, redirect, url_for, request, render_template from flask_babel import Babel from werkzeug.routing import IntegerConverter from werkzeug.serving import make_ssl_devcert from werkzeug.contrib.profiler import ProfilerMiddleware -from werkzeug.exceptions import InternalServerError +from werkzeug.exceptions import InternalServerError, Forbidden from flask_migrate import Migrate import uffd.ldap @@ -94,6 +94,10 @@ def create_app(test_config=None): # pylint: disable=too-many-locals app.test_request_context().push() # LDAP ORM requires request context return {'db': db, 'ldap': uffd.ldap.ldap, 'User': User, 'Group': Group, 'Role': Role, 'Mail': Mail} + @app.errorhandler(403) + def handle_403(error): + return render_template('403.html', description=error.description if error.description != Forbidden.description else None), 403 + @app.route("/") def index(): #pylint: disable=unused-variable return redirect(url_for('selfservice.index')) diff --git a/uffd/invite/views.py b/uffd/invite/views.py index d93dd892..65f4c51c 100644 --- a/uffd/invite/views.py +++ b/uffd/invite/views.py @@ -1,7 +1,7 @@ import datetime import functools -from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify, abort from flask_babel import gettext as _, lazy_gettext import sqlalchemy @@ -35,8 +35,7 @@ def invite_acl_required(func): @login_required() def decorator(*args, **kwargs): if not invite_acl(): - flash('Access denied') - return redirect(url_for('index')) + abort(403) return func(*args, **kwargs) return decorator diff --git a/uffd/mail/views.py b/uffd/mail/views.py index 9cc7e585..2e11905b 100644 --- a/uffd/mail/views.py +++ b/uffd/mail/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app +from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app, abort from flask_babel import gettext as _, lazy_gettext from uffd.navbar import register_navbar @@ -11,10 +11,9 @@ from uffd.mail.models import Mail bp = Blueprint("mail", __name__, template_folder='templates', url_prefix='/mail/') @bp.before_request @login_required() -def mail_acl(): #pylint: disable=inconsistent-return-statements +def mail_acl(): if not mail_acl_check(): - flash('Access denied') - return redirect(url_for('index')) + abort(403) def mail_acl_check(): return request.user and request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']) diff --git a/uffd/mfa/views.py b/uffd/mfa/views.py index 287b4ab0..69b5cc94 100644 --- a/uffd/mfa/views.py +++ b/uffd/mfa/views.py @@ -44,8 +44,7 @@ def admin_disable(uid): # Group cannot be checked with login_required kwarg, because the config # variable is not available when the decorator is processed if not request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']): - flash('Access denied') - return redirect(url_for('index')) + abort(403) user = User.query.filter_by(uid=uid).one() MFAMethod.query.filter_by(dn=user.dn).delete() db.session.commit() diff --git a/uffd/oauth2/views.py b/uffd/oauth2/views.py index 898d3d9b..e3300929 100644 --- a/uffd/oauth2/views.py +++ b/uffd/oauth2/views.py @@ -122,17 +122,11 @@ validator = UffdRequestValidator() server = oauthlib.oauth2.WebApplicationServer(validator) bp = Blueprint('oauth2', __name__, url_prefix='/oauth2/', template_folder='templates') -def display_oauth_errors(func): - @functools.wraps(func) - def decorator(*args, **kwargs): - try: - return func(*args, **kwargs) - except oauthlib.oauth2.rfc6749.errors.OAuth2Error as ex: - return render_template('oauth2/error.html', error=type(ex).__name__, error_description=ex.description), 400 - return decorator +@bp.errorhandler(oauthlib.oauth2.rfc6749.errors.OAuth2Error) +def handle_oauth2error(error): + return render_template('oauth2/error.html', error=type(error).__name__, error_description=error.description), 400 @bp.route('/authorize', methods=['GET', 'POST']) -@display_oauth_errors def authorize(): scopes, credentials = server.validate_authorization_request(request.url, request.method, request.form, request.headers) client = OAuth2Client.from_id(credentials['client_id']) @@ -177,7 +171,7 @@ def authorize(): # service access to his data. Since we only have trusted services (the # clients defined in the server config), we don't ask for consent. if not client.access_allowed(credentials['user']): - raise oauthlib.oauth2.rfc6749.errors.AccessDeniedError('User is not permitted to authenticate with this service.') + abort(403, description=_("You don't have the permission to access the service <b>%(service_name)s</b>.", service_name=client.client_id)) session['oauth2-clients'] = session.get('oauth2-clients', []) if client.client_id not in session['oauth2-clients']: session['oauth2-clients'].append(client.client_id) diff --git a/uffd/role/views.py b/uffd/role/views.py index 4c10a8b5..0b3e827f 100644 --- a/uffd/role/views.py +++ b/uffd/role/views.py @@ -1,6 +1,6 @@ import sys -from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app +from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app, abort from flask_babel import gettext as _, lazy_gettext import click @@ -39,10 +39,9 @@ def add_cli_commands(state): @bp.before_request @login_required() -def role_acl(): #pylint: disable=inconsistent-return-statements +def role_acl(): if not role_acl_check(): - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) def role_acl_check(): return request.user and request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']) diff --git a/uffd/rolemod/views.py b/uffd/rolemod/views.py index be60ee34..58849101 100644 --- a/uffd/rolemod/views.py +++ b/uffd/rolemod/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request, url_for, redirect, flash +from flask import Blueprint, render_template, request, url_for, redirect, flash, abort from flask_babel import gettext as _, lazy_gettext from uffd.navbar import register_navbar @@ -16,10 +16,9 @@ def user_is_rolemod(): @bp.before_request @login_required() -def acl_check(): #pylint: disable=inconsistent-return-statements +def acl_check(): if not user_is_rolemod(): - flash('Access denied') - return redirect(url_for('index')) + abort(403) @bp.route("/") @register_navbar(12, lazy_gettext('Moderation'), icon='user-lock', blueprint=bp, visible=user_is_rolemod) @@ -33,8 +32,7 @@ def show(role_id): User.query.all() role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) return render_template('rolemod/show.html', role=role) @bp.route("/<int:role_id>", methods=['POST']) @@ -42,8 +40,7 @@ def show(role_id): def update(role_id): role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) if request.form['description'] != role.description: if len(request.form['description']) > 256: flash(_('Description too long')) @@ -57,8 +54,7 @@ def update(role_id): def delete_member(role_id, member_dn): role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) member = User.query.get_or_404(member_dn) role.members.discard(member) member.update_groups() diff --git a/uffd/session/views.py b/uffd/session/views.py index c674648b..5e8433b0 100644 --- a/uffd/session/views.py +++ b/uffd/session/views.py @@ -129,8 +129,7 @@ def login_required(group=None): if not request.user: return redirect(url_for('mfa.auth', ref=request.full_path)) if not request.user.is_in_group(group): - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) return func(*args, **kwargs) return decorator return wrapper diff --git a/uffd/templates/403.html b/uffd/templates/403.html new file mode 100644 index 00000000..5a19dc98 --- /dev/null +++ b/uffd/templates/403.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block body %} +<div class="row mt-2 justify-content-center"> + <div class="col-lg-6 col-md-10" style="background: #f7f7f7; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); padding: 30px;"> + <div class="text-center"> + <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > + </div> + <div class="col-12"> + <h2 class="text-center">{{ _('Access Denied') }}</h2> + </div> + <div class="form-group col-12"> + <p> + {% if description %} + {{ description|safe }} + {% else %} + {{ _("You don't have the permission to access this page.") }} + {% endif %} + </p> + </div> + </div> +</div> +{% endblock %} diff --git a/uffd/translations/de/LC_MESSAGES/messages.mo b/uffd/translations/de/LC_MESSAGES/messages.mo index 8732d8a30f74045b143df675da5ec4687b98356f..89920cdc366a7daa31d3fb1afc9fceef4be3562d 100644 GIT binary patch delta 5196 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 Bml*&6 delta 4936 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 diff --git a/uffd/translations/de/LC_MESSAGES/messages.po b/uffd/translations/de/LC_MESSAGES/messages.po index 89805c8f..70e5f135 100644 --- a/uffd/translations/de/LC_MESSAGES/messages.po +++ b/uffd/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-09-04 21:18+0200\n" +"POT-Creation-Date: 2021-09-04 21:53+0200\n" "PO-Revision-Date: 2021-05-25 21:18+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language: de\n" @@ -44,54 +44,54 @@ msgstr "eine Stunde" msgid "%(hours)d hours" msgstr "%(hours)d Stunden\"" -#: uffd/invite/views.py:56 +#: uffd/invite/views.py:55 msgid "Invites" msgstr "Einladungslinks" -#: uffd/invite/views.py:85 +#: uffd/invite/views.py:84 msgid "The \"Expires After\" date is too far in the future" msgstr "Das Ablaufdatum liegt zu weit in der Zukunft" -#: uffd/invite/views.py:88 +#: uffd/invite/views.py:87 msgid "You are not allowed to create invite links with these permissions" msgstr "Dir fehlen Berechtigungen um diesen Einladungslink zu erstellen" -#: uffd/invite/views.py:91 +#: uffd/invite/views.py:90 msgid "Invite link must either allow signup or grant at least one role" msgstr "" "Einladungslink muss entweder Account-Registrierung erlauben oder Rollen " "vergeben" -#: uffd/invite/views.py:119 uffd/invite/views.py:148 +#: uffd/invite/views.py:118 uffd/invite/views.py:147 msgid "Invalid invite link" msgstr "Ungültiger Einladungslink" -#: uffd/invite/views.py:136 +#: uffd/invite/views.py:135 msgid "Roles successfully updated" msgstr "Rollen erfolgreich geändert" -#: uffd/invite/views.py:151 +#: uffd/invite/views.py:150 msgid "Invite link does not allow signup" msgstr "Einladungslink erlaubt keine Account-Registrierung" -#: uffd/invite/views.py:173 uffd/selfservice/views.py:50 +#: uffd/invite/views.py:172 uffd/selfservice/views.py:50 #: uffd/signup/views.py:49 msgid "Passwords do not match" msgstr "Die Passwörter stimmen nicht überein" -#: uffd/invite/views.py:178 uffd/signup/views.py:54 +#: uffd/invite/views.py:177 uffd/signup/views.py:54 #, python-format msgid "Too many signup requests with this mail address! Please wait %(delay)s." msgstr "" "Zu viele Account-Registrierungen mit dieser E-Mail-Adresse! Bitte warte " "%(delay)s." -#: uffd/invite/views.py:180 uffd/signup/views.py:56 uffd/signup/views.py:92 +#: uffd/invite/views.py:179 uffd/signup/views.py:56 uffd/signup/views.py:92 #, python-format msgid "Too many requests! Please wait %(delay)s." msgstr "Zu viele Anfragen! Bitte warte %(delay)s." -#: uffd/invite/views.py:193 uffd/signup/views.py:68 +#: uffd/invite/views.py:192 uffd/signup/views.py:68 msgid "Cound not send mail" msgstr "Mailversand fehlgeschlagen" @@ -346,15 +346,15 @@ msgstr "Neuen Account registrieren" msgid "Login and add the roles to your account" msgstr "Anmelden und die Rollen zu deinem Account hinzufügen" -#: uffd/mail/views.py:23 +#: uffd/mail/views.py:22 msgid "Forwardings" msgstr "Weiterleitungen" -#: uffd/mail/views.py:47 +#: uffd/mail/views.py:46 msgid "Mail mapping updated." msgstr "Mailweiterleitung geändert." -#: uffd/mail/views.py:56 +#: uffd/mail/views.py:55 msgid "Deleted mail mapping." msgstr "Mailweiterleitung gelöscht." @@ -383,19 +383,19 @@ msgstr "Speichern" msgid "Delete" msgstr "Löschen" -#: uffd/mfa/views.py:54 +#: uffd/mfa/views.py:53 msgid "Two-factor authentication was reset" msgstr "Zwei-Faktor-Authentifizierung wurde zurückgesetzt" -#: uffd/mfa/views.py:83 +#: uffd/mfa/views.py:82 msgid "Generate recovery codes first!" msgstr "Generiere zuerst die Wiederherstellungscodes!" -#: uffd/mfa/views.py:92 +#: uffd/mfa/views.py:91 msgid "Code is invalid" msgstr "Wiederherstellungscode ist ungültig" -#: uffd/mfa/views.py:116 +#: uffd/mfa/views.py:115 #, python-format msgid "" "2FA WebAuthn support disabled because import of the fido2 module failed " @@ -404,16 +404,16 @@ msgstr "" "2FA WebAuthn Unterstützung deaktiviert, da das fido2 Modul nicht geladen " "werden konnte (%s)" -#: uffd/mfa/views.py:225 +#: uffd/mfa/views.py:224 #, python-format msgid "We received too many invalid attempts! Please wait at least %s." msgstr "Wir haben zu viele fehlgeschlagene Versuche! Bitte warte mindestens %s." -#: uffd/mfa/views.py:239 +#: uffd/mfa/views.py:238 msgid "You have exhausted your recovery codes. Please generate new ones now!" msgstr "Du hast keine Wiederherstellungscode mehr. Bitte generiere diese jetzt!" -#: uffd/mfa/views.py:242 +#: uffd/mfa/views.py:241 msgid "" "You only have a few recovery codes remaining. Make sure to generate new " "ones before they run out." @@ -421,7 +421,7 @@ msgstr "" "Du hast nur noch wenige Wiederherstellungscodes übrig. Bitte generiere " "diese erneut bevor keine mehr übrig sind." -#: uffd/mfa/views.py:246 +#: uffd/mfa/views.py:245 msgid "Two-factor authentication failed" msgstr "Zwei-Faktor-Authentifizierung fehlgeschlagen" @@ -704,7 +704,7 @@ msgstr "Verifiziere und beende das Setup" msgid "You need to login to access this service" msgstr "Du musst dich anmelden, um auf diesen Dienst zugreifen zu können" -#: uffd/oauth2/views.py:146 uffd/selfservice/views.py:76 +#: uffd/oauth2/views.py:140 uffd/selfservice/views.py:76 #: uffd/session/views.py:95 #, python-format msgid "" @@ -714,14 +714,23 @@ msgstr "" "Wir haben zu viele Anfragen von deiner IP-Adresses bzw. aus deinem " "Netzwerk empfangen! Bitte warte mindestens %(delay)s." -#: uffd/oauth2/views.py:154 +#: uffd/oauth2/views.py:148 msgid "Device login is currently not available. Try again later!" msgstr "Geräte-Login ist gerade nicht verfügbar. Versuche es später nochmal!" -#: uffd/oauth2/views.py:167 +#: uffd/oauth2/views.py:161 msgid "Device login failed" msgstr "Gerätelogin fehlgeschlagen" +#: uffd/oauth2/views.py:174 +#, python-format +msgid "" +"You don't have the permission to access the service " +"<b>%(service_name)s</b>." +msgstr "" +"Du bist nicht berechtigt, auf den Dienst <b>%(service_name)s</b> " +"zuzugreifen." + #: uffd/oauth2/templates/oauth2/logout.html:10 uffd/templates/base.html:99 msgid "Logout" msgstr "Abmelden" @@ -764,19 +773,13 @@ msgstr "" "Automatisches Abmelden bei einigen Diensten fehlgeschlagen. Nochmal " "versuchen?" -#: uffd/role/views.py:44 uffd/rolemod/views.py:36 uffd/rolemod/views.py:45 -#: uffd/rolemod/views.py:60 uffd/session/views.py:132 -#: uffd/user/views_group.py:14 uffd/user/views_user.py:25 -msgid "Access denied" -msgstr "Zugriff verweigert" - -#: uffd/role/views.py:51 uffd/selfservice/templates/selfservice/self.html:86 +#: uffd/role/views.py:50 uffd/selfservice/templates/selfservice/self.html:86 #: uffd/user/templates/user/list.html:20 uffd/user/templates/user/show.html:21 #: uffd/user/templates/user/show.html:90 msgid "Roles" msgstr "Rollen" -#: uffd/role/views.py:101 +#: uffd/role/views.py:100 msgid "Locked roles cannot be deleted" msgstr "Gesperrte Rollen können nicht gelöscht werden" @@ -868,15 +871,15 @@ msgstr "derzeit enthaltene Gruppen" msgid "2FA required" msgstr "2FA erforderlich" -#: uffd/rolemod/views.py:25 +#: uffd/rolemod/views.py:24 msgid "Moderation" msgstr "Moderation" -#: uffd/rolemod/views.py:49 +#: uffd/rolemod/views.py:46 msgid "Description too long" msgstr "Beschreibung zu lang" -#: uffd/rolemod/views.py:67 +#: uffd/rolemod/views.py:63 msgid "Member removed" msgstr "Mitglied entfernt" @@ -1192,15 +1195,15 @@ msgstr "Du hast keinen Zugriff auf diesen Service" msgid "You need to login first" msgstr "Du musst dich erst anmelden" -#: uffd/session/views.py:149 uffd/session/views.py:159 +#: uffd/session/views.py:148 uffd/session/views.py:158 msgid "Initiation code is no longer valid" msgstr "Startcode ist nicht mehr gültig" -#: uffd/session/views.py:163 +#: uffd/session/views.py:162 msgid "Invalid confirmation code" msgstr "Ungültiger Bestätigungscode" -#: uffd/session/views.py:175 uffd/session/views.py:186 +#: uffd/session/views.py:174 uffd/session/views.py:185 msgid "Invalid initiation code" msgstr "Ungültiger Startcode" @@ -1403,6 +1406,14 @@ msgstr "" " Gründen keine Bestätigungsmail erhalten hast, kannst du den Prozess " "einfach von Vorne beginnen." +#: uffd/templates/403.html:10 +msgid "Access Denied" +msgstr "Zugriff verweigert" + +#: uffd/templates/403.html:17 +msgid "You don't have the permission to access this page." +msgstr "Du bist nicht berechtigt, auf diese Seite zuzugreifen." + #: uffd/templates/base.html:84 msgid "Change" msgstr "Ändern" @@ -1422,45 +1433,45 @@ msgstr "" "und manche Symbole (<code>%(symbols)s</code>), keine Umlaute. Bitte " "verwende einen Passwort-Manager." -#: uffd/user/views_group.py:21 +#: uffd/user/views_group.py:20 msgid "Groups" msgstr "Gruppen" -#: uffd/user/views_user.py:32 +#: uffd/user/views_user.py:31 msgid "Users" msgstr "Accounts" -#: uffd/user/views_user.py:52 +#: uffd/user/views_user.py:51 msgid "Login name does not meet requirements" msgstr "Anmeldename entspricht nicht den Anforderungen" -#: uffd/user/views_user.py:57 +#: uffd/user/views_user.py:56 msgid "Mail is invalid" msgstr "E-Mail-Adresse nicht valide" -#: uffd/user/views_user.py:61 +#: uffd/user/views_user.py:60 msgid "Display name does not meet requirements" msgstr "Anzeigename entspricht nicht den Anforderungen" -#: uffd/user/views_user.py:66 +#: uffd/user/views_user.py:65 msgid "Password is invalid" msgstr "Passwort ist ungültig" -#: uffd/user/views_user.py:80 +#: uffd/user/views_user.py:79 msgid "Service user created" msgstr "Service-Account erstellt" -#: uffd/user/views_user.py:83 +#: uffd/user/views_user.py:82 msgid "User created. We sent the user a password reset link by mail" msgstr "" "Account erstellt. E-Mail mit einem Link zum Setzen des Passworts wurde " "versendet." -#: uffd/user/views_user.py:85 +#: uffd/user/views_user.py:84 msgid "User updated" msgstr "Account aktualisiert" -#: uffd/user/views_user.py:96 +#: uffd/user/views_user.py:95 msgid "Deleted user" msgstr "Account gelöscht" diff --git a/uffd/user/views_group.py b/uffd/user/views_group.py index 480a7b59..1526d102 100644 --- a/uffd/user/views_group.py +++ b/uffd/user/views_group.py @@ -1,5 +1,5 @@ -from flask import Blueprint, render_template, url_for, redirect, flash, current_app, request -from flask_babel import gettext as _, lazy_gettext +from flask import Blueprint, render_template, current_app, request, abort +from flask_babel import lazy_gettext from uffd.navbar import register_navbar from uffd.session import login_required @@ -9,10 +9,9 @@ from .models import Group bp = Blueprint("group", __name__, template_folder='templates', url_prefix='/group/') @bp.before_request @login_required() -def group_acl(): #pylint: disable=inconsistent-return-statements +def group_acl(): if not group_acl_check(): - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) def group_acl_check(): return request.user and request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']) diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py index 2b273946..2b4497dd 100644 --- a/uffd/user/views_user.py +++ b/uffd/user/views_user.py @@ -1,7 +1,7 @@ import csv import io -from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app +from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app, abort from flask_babel import gettext as _, lazy_gettext from uffd.navbar import register_navbar @@ -20,10 +20,9 @@ bp.add_app_template_global(User, 'User') @bp.before_request @login_required() -def user_acl(): #pylint: disable=inconsistent-return-statements +def user_acl(): if not user_acl_check(): - flash(_('Access denied')) - return redirect(url_for('index')) + abort(403) def user_acl_check(): return request.user and request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']) -- GitLab