diff --git a/tests/test_invite.py b/tests/test_invite.py index 7c6bf2fb762c447d38d8fc5f0088ceca51c470df..f3d652ac7f5dc7ab66ab126a9947e0eded1bad20 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 6fef9547df589eca9aa3fc382bac471d9bdf7b11..0adbde99e98f640e50e689b53f22a7f7fa56eda7 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 d94fce37bda958fddcf99165bb76ac1c4c17d65f..81a39c97c6ea0e398bdf75db1b68a79b1e503852 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 1a9585691875ff4886f3d6a48e1fee5166907862..90feeafd1185b87bb1f38797da8f37aab62a8a82 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 d93dd892bddd3f65ec1041215cba5cd841a4e8b1..65f4c51c0358e8c1f999c623b417ac34b638d7d7 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 9cc7e585ed9f7cf0f9762bd8a2646eaa57c1f2f3..2e11905b0dce159ba1ee4c3d60329735c9817ce4 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 287b4ab03d34d74e905a297428461f03a79490f3..69b5cc94a75144ca266c0f581094cac928723484 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 898d3d9be15130a4bee49a709e20cc82d62855cf..e330092945d9815254294e7ad9e57c2d24a4cd22 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 4c10a8b593f4cd9c03b78b1013d71f780a354d69..0b3e827f2a65d16cef7031fc98153d7769cb1a90 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 be60ee34207ddf864a379e621f4eac652d4ca941..5884910176a32e9a9f8f235d5fd5079c3ceb2cdc 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 c674648b19a42631b1e9965c6d92c7f7f3cba35e..5e8433b038fbfbb746976c37f15ee8046f091e4b 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 0000000000000000000000000000000000000000..5a19dc9820bc1a6edabeb85c2856b48e4b4b5dcb --- /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 Binary files a/uffd/translations/de/LC_MESSAGES/messages.mo and b/uffd/translations/de/LC_MESSAGES/messages.mo differ diff --git a/uffd/translations/de/LC_MESSAGES/messages.po b/uffd/translations/de/LC_MESSAGES/messages.po index 89805c8fcbe12c13ec3e510b2da4d456802b979e..70e5f135a05d4ce7a2db7914eaf2ad24b9f2927e 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 480a7b59bbccac2b449249b7d7d23faaccba5734..1526d10298c27d1b20d39d5ddf078be2f8f144a5 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 2b2739465cc9e376afb73bc57d34e1fde790caa0..2b4497dd2a963bfbe0c034563cd2fcec05b91f65 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'])