From 05f68ec88101cf184b23c3c99e4298a3e922de82 Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@cccv.de> Date: Sun, 6 Nov 2022 02:10:23 +0100 Subject: [PATCH] Add per-service setting for testing remailer This setting is more flexible than the existing REMAILER_LIMIT_TO_USERS config option. The config option is therefore deprecated and will be removed in the next major version. --- tests/models/test_services.py | 186 ++++++-------- tests/views/test_services.py | 232 ++++++++++++++++++ uffd/default_config.cfg | 3 +- .../e249233e2a31_remailer_mode_overwrite.py | 44 ++++ uffd/models/service.py | 46 ++-- uffd/templates/service/show.html | 26 +- uffd/translations/de/LC_MESSAGES/messages.mo | Bin 40147 -> 40588 bytes uffd/translations/de/LC_MESSAGES/messages.po | 40 ++- uffd/views/service.py | 31 ++- 9 files changed, 461 insertions(+), 147 deletions(-) create mode 100644 uffd/migrations/versions/e249233e2a31_remailer_mode_overwrite.py diff --git a/tests/models/test_services.py b/tests/models/test_services.py index 82808db4..d6836b5f 100644 --- a/tests/models/test_services.py +++ b/tests/models/test_services.py @@ -1,3 +1,5 @@ +import itertools + from uffd.remailer import remailer from uffd.tasks import cleanup_task from uffd.database import db @@ -39,6 +41,24 @@ class TestServiceUser(UffdTestCase): db.session.commit() self.assertEqual(ServiceUser.query.count(), service_count * user_count) + def test_effective_remailer_mode(self): + self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com' + user = self.get_user() + service = Service.query.filter_by(name='service1').first() + service.remailer_mode = RemailerMode.ENABLED_V2 + service_user = ServiceUser.query.get((service.id, user.id)) + self.assertEqual(service_user.effective_remailer_mode, RemailerMode.ENABLED_V2) + self.app.config['REMAILER_LIMIT_TO_USERS'] = ['testadmin'] + self.assertEqual(service_user.effective_remailer_mode, RemailerMode.DISABLED) + self.app.config['REMAILER_LIMIT_TO_USERS'] = ['testuser'] + self.assertEqual(service_user.effective_remailer_mode, RemailerMode.ENABLED_V2) + self.app.config['REMAILER_LIMIT_TO_USERS'] = None + service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V1 + service.remailer_mode = RemailerMode.DISABLED + self.assertEqual(service_user.effective_remailer_mode, RemailerMode.ENABLED_V1) + self.app.config['REMAILER_DOMAIN'] = '' + self.assertEqual(service_user.effective_remailer_mode, RemailerMode.DISABLED) + def test_service_email(self): user = self.get_user() service = Service.query.filter_by(name='service1').first() @@ -99,119 +119,59 @@ class TestServiceUser(UffdTestCase): # 5. remailer setup + remailer enabled + REMAILER_LIMIT_TO_USERS includes user self.app.config['REMAILER_LIMIT_TO_USERS'] = ['testuser'] self.assertEqual(service_user.email, remailer_email) + # 6. remailer setup + remailer disabled + user overwrite + self.app.config['REMAILER_LIMIT_TO_USERS'] = None + service.remailer_mode = RemailerMode.DISABLED + service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V1 + self.assertEqual(service_user.email, remailer_email) + # 7. remailer setup + remailer enabled + user overwrite + self.app.config['REMAILER_LIMIT_TO_USERS'] = None + service.remailer_mode = RemailerMode.ENABLED_V1 + service_user.remailer_overwrite_mode = RemailerMode.DISABLED + self.assertEqual(service_user.email, user.primary_email.address) def test_filter_query_by_email(self): - def run_query(value): - return {(su.service_id, su.user_id) for su in ServiceUser.filter_query_by_email(ServiceUser.query, value)} - - user1 = self.get_user() - user2 = User(loginname='user2', primary_email_address=user1.primary_email.address, displayname='User 2') - db.session.add(user2) - db.session.commit() - service1 = Service.query.filter_by(name='service1').first() # remailer disabled - service2 = Service.query.filter_by(name='service2').first() # remailer enabled - self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com' - remailer_email1_1 = remailer.build_v1_address(service1.id, user1.id) - remailer_email2_1 = remailer.build_v1_address(service2.id, user1.id) - remailer_email1_2 = remailer.build_v1_address(service1.id, user2.id) - remailer_email2_2 = remailer.build_v1_address(service2.id, user2.id) - - # 1. remailer disabled - self.app.config['REMAILER_DOMAIN'] = '' - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), (service1.id, user2.id), - (service2.id, user1.id), (service2.id, user2.id), - }) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), set()) - self.assertEqual(run_query('invalid'), set()) - - # 2. remailer enabled + REMAILER_LIMIT_TO_USERS unset - self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com' - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), (service1.id, user2.id), - }) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), { - (service2.id, user1.id), - }) - self.assertEqual(run_query(remailer_email2_1 + ' '), set()) - self.assertEqual(run_query('invalid'), set()) - - # 3. remailer enabled + REMAILER_LIMIT_TO_USERS includes testuser - self.app.config['REMAILER_LIMIT_TO_USERS'] = ['testuser'] - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), (service1.id, user2.id), - (service2.id, user2.id), - }) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), { - (service2.id, user1.id), - }) - self.assertEqual(run_query(remailer_email2_1 + ' '), set()) - self.assertEqual(run_query(remailer_email1_2), set()) - self.assertEqual(run_query(remailer_email2_2), set()) - self.assertEqual(run_query('invalid'), set()) - - # 4. remailer enabled + REMAILER_LIMIT_TO_USERS does not include user (should behave the same as 1.) - self.app.config['REMAILER_LIMIT_TO_USERS'] = ['testadmin'] - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), (service1.id, user2.id), - (service2.id, user1.id), (service2.id, user2.id), - }) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), set()) - self.assertEqual(run_query('invalid'), set()) - - def test_filter_query_by_email_prefs(self): - def run_query(value): - return {(su.service_id, su.user_id) for su in ServiceUser.filter_query_by_email(ServiceUser.query, value)} - - user1 = self.get_user() - service1 = Service.query.filter_by(name='service1').first() # remailer disabled - service2 = Service.query.filter_by(name='service2').first() # remailer enabled - self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com' - remailer_email1_1 = remailer.build_v1_address(service1.id, user1.id) - remailer_email2_1 = remailer.build_v1_address(service2.id, user1.id) - - self.app.config['REMAILER_DOMAIN'] = '' - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), - (service2.id, user1.id), - }) - self.assertEqual(run_query('addr1-1@example.com'), set()) - self.assertEqual(run_query('addr2-1@example.com'), set()) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), set()) - - ServiceUser.query.get((service1.id, user1.id)).service_email = UserEmail(user=user1, verified=True, address='addr1-1@example.com') - ServiceUser.query.get((service2.id, user1.id)).service_email = UserEmail(user=user1, verified=True, address='addr2-1@example.com') - self.assertEqual(run_query(user1.primary_email.address), { - (service1.id, user1.id), - (service2.id, user1.id), - }) - self.assertEqual(run_query('addr1-1@example.com'), set()) - self.assertEqual(run_query('addr2-1@example.com'), set()) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), set()) - service1.enable_email_preferences = True - service2.enable_email_preferences = True - self.assertEqual(run_query(user1.primary_email.address), set()) - self.assertEqual(run_query('addr1-1@example.com'), { - (service1.id, user1.id), - }) - self.assertEqual(run_query('addr2-1@example.com'), { - (service2.id, user1.id), - }) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), set()) + service = Service.query.filter_by(name='service1').first() + user = self.get_user() self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com' - self.assertEqual(run_query(user1.primary_email.address), set()) - self.assertEqual(run_query('addr1-1@example.com'), { - (service1.id, user1.id), - }) - self.assertEqual(run_query('addr2-1@example.com'), set()) - self.assertEqual(run_query(remailer_email1_1), set()) - self.assertEqual(run_query(remailer_email2_1), { - (service2.id, user1.id), - }) + remailer_email_v1 = remailer.build_v1_address(service.id, user.id) + remailer_email_v2 = remailer.build_v2_address(service.id, user.id) + email1 = user.primary_email + email2 = UserEmail(user=user, address='test2@example.com', verified=True) + db.session.add(email2) + service_user = ServiceUser.query.get((service.id, user.id)) + all_service_users = ServiceUser.query.all() + cases = itertools.product( + # Input values + [ + 'test@example.com', + 'test2@example.com', + 'other@example.com', + remailer_email_v1, + remailer_email_v2, + ], + # REMAILER_DOMAIN config + [None, 'remailer.example.com'], + # REMAILER_LIMIT config + [None, ['testuser', 'otheruser'], ['testadmin', 'otheruser']], + # service.remailer_mode + [RemailerMode.DISABLED, RemailerMode.ENABLED_V1, RemailerMode.ENABLED_V2], + # service.enable_email_preferences + [True, False], + # service_user.service_email + [None, email1, email2], + # service_user.remailer_overwrite_mode + [None, RemailerMode.DISABLED, RemailerMode.ENABLED_V1, RemailerMode.ENABLED_V2], + ) + for options in cases: + value = options[0] + self.app.config['REMAILER_DOMAIN'] = options[1] + self.app.config['REMAILER_LIMIT_TO_USERS'] = options[2] + service.remailer_mode = options[3] + service.enable_email_preferences = options[4] + service_user.service_email = options[5] + service_user.remailer_overwrite_mode = options[6] + a = {result for result in all_service_users if result.email == value} + b = set(ServiceUser.filter_query_by_email(ServiceUser.query, value).all()) + if a != b: + self.fail(f'{a} != {b} with ' + repr(options)) diff --git a/tests/views/test_services.py b/tests/views/test_services.py index 1e57d8be..a40e447f 100644 --- a/tests/views/test_services.py +++ b/tests/views/test_services.py @@ -1,5 +1,7 @@ from flask import url_for +from uffd.database import db +from uffd.models import Service, ServiceUser, OAuth2Client, APIClient, RemailerMode from tests.utils import dump, UffdTestCase class TestServices(UffdTestCase): @@ -96,3 +98,233 @@ class TestServices(UffdTestCase): r = self.client.get(path=url_for('service.overview'), follow_redirects=True) dump('service_overview_public_admin', r) self.assertEqual(r.status_code, 200) + +class TestServiceAdminViews(UffdTestCase): + def setUpDB(self): + db.session.add(Service( + name='test1', + oauth2_clients=[OAuth2Client(client_id='test1_oauth2_client1', client_secret='test'), OAuth2Client(client_id='test1_oauth2_client2', client_secret='test')], + api_clients=[APIClient(auth_username='test1_api_client1', auth_password='test'), APIClient(auth_username='test1_api_client2', auth_password='test')], + )) + db.session.add(Service(name='test2')) + db.session.add(Service(name='test3')) + db.session.commit() + self.service_id = Service.query.filter_by(name='test1').one().id + + def test_index(self): + self.login_as('admin') + r = self.client.get(path=url_for('service.index'), follow_redirects=True) + dump('service_index', r) + self.assertEqual(r.status_code, 200) + + def test_show(self): + self.login_as('admin') + r = self.client.get(path=url_for('service.show', id=self.service_id), follow_redirects=True) + dump('service_show', r) + self.assertEqual(r.status_code, 200) + + def test_new(self): + self.login_as('admin') + r = self.client.get(path=url_for('service.show'), follow_redirects=True) + dump('service_new', r) + self.assertEqual(r.status_code, 200) + + def test_new_submit(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit'), + follow_redirects=True, + data={ + 'name': 'new-service', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + dump('service_new_submit', r) + self.assertEqual(r.status_code, 200) + service = Service.query.filter_by(name='new-service').one_or_none() + self.assertIsNotNone(service) + self.assertEqual(service.limit_access, True) + self.assertEqual(service.access_group, None) + self.assertEqual(service.remailer_mode, RemailerMode.DISABLED) + self.assertEqual(service.enable_email_preferences, False) + + def test_edit(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'new-name', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + dump('service_edit_submit', r) + self.assertEqual(r.status_code, 200) + service = Service.query.get(self.service_id) + self.assertEqual(service.name, 'new-name') + self.assertEqual(service.limit_access, True) + self.assertEqual(service.access_group, None) + self.assertEqual(service.remailer_mode, RemailerMode.DISABLED) + self.assertEqual(service.enable_email_preferences, False) + + def test_edit_access_all(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': 'all', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + self.assertEqual(r.status_code, 200) + service = Service.query.get(self.service_id) + self.assertEqual(service.limit_access, False) + self.assertEqual(service.access_group, None) + + def test_edit_access_group(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': str(self.get_users_group().id), + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + self.assertEqual(r.status_code, 200) + service = Service.query.get(self.service_id) + self.assertEqual(service.limit_access, True) + self.assertEqual(service.access_group, self.get_users_group()) + + def test_edit_email_preferences(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + 'enable_email_preferences': '1', + }, + ) + self.assertEqual(r.status_code, 200) + service = Service.query.get(self.service_id) + self.assertEqual(service.enable_email_preferences, True) + + def test_edit_remailer_mode(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': '', + 'remailer-mode': 'ENABLED_V2', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + self.assertEqual(r.status_code, 200) + service = Service.query.get(self.service_id) + self.assertEqual(service.remailer_mode, RemailerMode.ENABLED_V2) + + def test_edit_remailer_overwrite_enable(self): + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': 'testuser, testadmin', + }, + ) + self.assertEqual(r.status_code, 200) + service_user1 = ServiceUser.query.get((self.service_id, self.get_user().id)) + service_user2 = ServiceUser.query.get((self.service_id, self.get_admin().id)) + self.assertEqual(service_user1.remailer_overwrite_mode, RemailerMode.ENABLED_V2) + self.assertEqual(service_user2.remailer_overwrite_mode, RemailerMode.ENABLED_V2) + self.assertEqual( + set(ServiceUser.query.filter( + ServiceUser.service_id == self.service_id, + ServiceUser.remailer_overwrite_mode != None + ).all()), + {service_user1, service_user2} + ) + + def test_edit_remailer_overwrite_change(self): + service_user = ServiceUser.query.get((self.service_id, self.get_user().id)) + service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V2 + db.session.commit() + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V1', + 'remailer-overwrite-users': ', testadmin', + }, + ) + self.assertEqual(r.status_code, 200) + service_user = ServiceUser.query.get((self.service_id, self.get_admin().id)) + self.assertEqual(service_user.remailer_overwrite_mode, RemailerMode.ENABLED_V1) + self.assertEqual( + ServiceUser.query.filter( + ServiceUser.service_id == self.service_id, + ServiceUser.remailer_overwrite_mode != None + ).all(), + [service_user] + ) + + def test_edit_remailer_overwrite_disable(self): + service_user = ServiceUser.query.get((self.service_id, self.get_user().id)) + service_user.remailer_overwrite_mode = RemailerMode.ENABLED_V2 + db.session.commit() + self.login_as('admin') + r = self.client.post( + path=url_for('service.edit_submit', id=self.service_id), + follow_redirects=True, + data={ + 'name': 'test1', + 'access-group': '', + 'remailer-mode': 'DISABLED', + 'remailer-overwrite-mode': 'ENABLED_V2', + 'remailer-overwrite-users': '', + }, + ) + self.assertEqual(r.status_code, 200) + self.assertEqual( + ServiceUser.query.filter( + ServiceUser.service_id == self.service_id, + ServiceUser.remailer_overwrite_mode != None + ).all(), + [] + ) + + def test_delete(self): + self.login_as('admin') + r = self.client.get(path=url_for('service.delete', id=self.service_id), follow_redirects=True) + dump('service_delete', r) + self.assertEqual(r.status_code, 200) + self.assertIsNone(Service.query.get(self.service_id)) diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg index 7b01157f..3ac6ee21 100644 --- a/uffd/default_config.cfg +++ b/uffd/default_config.cfg @@ -53,7 +53,8 @@ REMAILER_OLD_DOMAINS = [] REMAILER_SECRET_KEY = None # Set to list of user loginnames to limit remailer to a small list of users. # Useful for debugging. If None remailer is active for all users (if -# configured and enabled for a service). +# configured and enabled for a service). This option is deprecated. Use the +# per-service setting in the web interface instead. REMAILER_LIMIT_TO_USERS = None # Do not enable this on a public service! There is no spam protection implemented at the moment. diff --git a/uffd/migrations/versions/e249233e2a31_remailer_mode_overwrite.py b/uffd/migrations/versions/e249233e2a31_remailer_mode_overwrite.py new file mode 100644 index 00000000..10c617ee --- /dev/null +++ b/uffd/migrations/versions/e249233e2a31_remailer_mode_overwrite.py @@ -0,0 +1,44 @@ +"""Remailer mode overwrite + +Revision ID: e249233e2a31 +Revises: aeb07202a6c8 +Create Date: 2022-11-05 03:42:38.036623 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'e249233e2a31' +down_revision = 'aeb07202a6c8' +branch_labels = None +depends_on = None + +def upgrade(): + meta = sa.MetaData(bind=op.get_bind()) + service_user = sa.Table('service_user', meta, + sa.Column('service_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('service_email_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['service_email_id'], ['user_email.id'], name=op.f('fk_service_user_service_email_id_user_email'), onupdate='CASCADE', ondelete='SET NULL'), + sa.ForeignKeyConstraint(['service_id'], ['service.id'], name=op.f('fk_service_user_service_id_service'), onupdate='CASCADE', ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('fk_service_user_user_id_user'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('service_id', 'user_id', name=op.f('pk_service_user')) + ) + with op.batch_alter_table('service_user', copy_from=service_user) as batch_op: + batch_op.add_column(sa.Column('remailer_overwrite_mode', sa.Enum('DISABLED', 'ENABLED_V1', 'ENABLED_V2', name='remailermode'), nullable=True)) + +def downgrade(): + meta = sa.MetaData(bind=op.get_bind()) + service_user = sa.Table('service_user', meta, + sa.Column('service_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('remailer_overwrite_mode', sa.Enum('DISABLED', 'ENABLED_V1', 'ENABLED_V2', name='remailermode'), nullable=True), + sa.Column('service_email_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['service_email_id'], ['user_email.id'], name=op.f('fk_service_user_service_email_id_user_email'), onupdate='CASCADE', ondelete='SET NULL'), + sa.ForeignKeyConstraint(['service_id'], ['service.id'], name=op.f('fk_service_user_service_id_service'), onupdate='CASCADE', ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('fk_service_user_user_id_user'), onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('service_id', 'user_id', name=op.f('pk_service_user')) + ) + with op.batch_alter_table('service_user', copy_from=service_user) as batch_op: + batch_op.drop_column('remailer_overwrite_mode') diff --git a/uffd/models/service.py b/uffd/models/service.py index c5e5cbcc..2143e674 100644 --- a/uffd/models/service.py +++ b/uffd/models/service.py @@ -58,6 +58,19 @@ class ServiceUser(db.Model): def has_access(self): return not self.service.limit_access or self.service.access_group in self.user.groups + remailer_overwrite_mode = Column(Enum(RemailerMode), default=None, nullable=True) + + @property + def effective_remailer_mode(self): + if not remailer.configured: + return RemailerMode.DISABLED + if current_app.config['REMAILER_LIMIT_TO_USERS'] is not None: + if self.user.loginname not in current_app.config['REMAILER_LIMIT_TO_USERS']: + return RemailerMode.DISABLED + if self.remailer_overwrite_mode is not None: + return self.remailer_overwrite_mode + return self.service.remailer_mode + service_email_id = Column(Integer(), ForeignKey('user_email.id', onupdate='CASCADE', ondelete='SET NULL')) service_email = relationship('UserEmail') @@ -92,13 +105,9 @@ class ServiceUser(db.Model): # E-Mail address as seen by the service @property def email(self): - if current_app.config['REMAILER_LIMIT_TO_USERS'] is None: - use_remailer = remailer.configured - else: - use_remailer = remailer.configured and self.user.loginname in current_app.config['REMAILER_LIMIT_TO_USERS'] - if use_remailer and self.service.remailer_mode == RemailerMode.ENABLED_V1: + if self.effective_remailer_mode == RemailerMode.ENABLED_V1: return remailer.build_v1_address(self.service_id, self.user_id) - if use_remailer and self.service.remailer_mode == RemailerMode.ENABLED_V2: + if self.effective_remailer_mode == RemailerMode.ENABLED_V2: return remailer.build_v2_address(self.service_id, self.user_id) return self.real_email @@ -106,7 +115,7 @@ class ServiceUser(db.Model): def filter_query_by_email(cls, query, email): '''Filter query of ServiceUser by ServiceUser.email''' # pylint completely fails to understand SQLAlchemy's query functions - # pylint: disable=no-member,invalid-name + # pylint: disable=no-member,invalid-name,singleton-comparison service_user = cls.get_by_remailer_email(email) if service_user and service_user.email == email: return query.filter(cls.user_id == service_user.user_id, cls.service_id == service_user.service_id) @@ -121,23 +130,26 @@ class ServiceUser(db.Model): query = query.outerjoin(cls.service_email.of_type(AliasedServiceEmail)) query = query.join(cls.service.of_type(AliasedService)) - remailer_enabled_expr = db.and_( - AliasedService.remailer_mode != RemailerMode.DISABLED, - remailer.configured + remailer_enabled = db.case( + whens=[ + (db.not_(remailer.configured), False), + ( + db.not_(AliasedUser.loginname.in_(current_app.config['REMAILER_LIMIT_TO_USERS'])) + if current_app.config['REMAILER_LIMIT_TO_USERS'] is not None else db.and_(False), + False + ), + (cls.remailer_overwrite_mode != None, cls.remailer_overwrite_mode != RemailerMode.DISABLED) + ], + else_=(AliasedService.remailer_mode != RemailerMode.DISABLED) ) - if current_app.config['REMAILER_LIMIT_TO_USERS'] is not None: - remailer_enabled_expr = db.and_( - remailer_enabled_expr, - AliasedUser.loginname.in_(current_app.config['REMAILER_LIMIT_TO_USERS']), - ) - real_email_matches_expr = db.case( + real_email_matches = db.case( whens=[ # pylint: disable=singleton-comparison (db.and_(AliasedService.enable_email_preferences, cls.service_email != None), AliasedServiceEmail.address == email), ], else_=(AliasedPrimaryEmail.address == email) ) - return query.filter(db.and_(db.not_(remailer_enabled_expr), real_email_matches_expr)) + return query.filter(db.and_(db.not_(remailer_enabled), real_email_matches)) @db.event.listens_for(db.Session, 'after_flush') # pylint: disable=no-member def create_service_users(session, flush_context): # pylint: disable=unused-argument diff --git a/uffd/templates/service/show.html b/uffd/templates/service/show.html index 6c6fca94..5277fa41 100644 --- a/uffd/templates/service/show.html +++ b/uffd/templates/service/show.html @@ -21,7 +21,7 @@ <input type="text" class="form-control" id="service-name" name="name" value="{{ service.name or '' }}" required> </div> <div class="form-group col"> - <label for="moderator-group">{{ _('Access Restriction') }}</label> + <label for="access-group">{{ _('Access Restriction') }}</label> <select class="form-control" id="access-group" name="access-group"> <option value="" class="text-muted">{{ _('No user has access') }}</option> <option value="all" class="text-muted" {{ 'selected' if not service.limit_access }}>{{ _('All users have access (legacy)') }}</option> @@ -54,6 +54,30 @@ </small> </div> + <div class="form-group col"> + <p class="mb-2"> + {{ _('Overwrite remailer setting for specific users') }} + </p> + <div class="input-group" id="remailer-mode-overwrite"> + <input class="form-control" name="remailer-overwrite-users" placeholder="{{ _('Login names') }}" value="{{ remailer_overwrites|map(attribute='user')|map(attribute='loginname')|sort|join(', ') }}"> + <select class="form-control" name="remailer-overwrite-mode"> + {% set remailer_overwrite_mode = remailer_overwrites|map(attribute='remailer_overwrite_mode')|first or RemailerMode.ENABLED_V2 %} + <option value="{{ RemailerMode.DISABLED.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.DISABLED }}> + {{ _('Remailer disabled') }} + </option> + <option value="{{ RemailerMode.ENABLED_V2.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.ENABLED_V2 }}> + {{ _('Remailer enabled') }} + </option> + <option value="{{ RemailerMode.ENABLED_V1.name }}" {{ 'selected' if remailer_overwrite_mode == RemailerMode.ENABLED_V1 }}> + {{ _('Remailer enabled (deprecated, case-sensitive format)') }} + </option> + </select> + </div> + <small class="form-text text-muted"> + {{ _('Useful for testing remailer before enabling it for all users. Specify users as a comma-seperated list of login names.') }} + </small> + </div> + <div class="form-group col"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="service-enable-email-preferences" name="enable_email_preferences" value="1" aria-label="enabled" {{ 'checked' if service.enable_email_preferences }}> diff --git a/uffd/translations/de/LC_MESSAGES/messages.mo b/uffd/translations/de/LC_MESSAGES/messages.mo index f7010b8fa6548e32d0a8e5ebeb97cc081ca8d9c0..0897f2eba472b656935ffde9314eb66162e0fef2 100644 GIT binary patch delta 7026 zcmcb-lc{GfQ~f<5mZ=O33=Gp485m?37#Pm*fOrU8Bgw$P&%nS?E6Knh%)r3VE6KpX z#=yX^P?CYchk=1%g(L$5NXu7A1_mw$1_mZ62%lGqfkBIbfk8?NBJL{1z`)DEzz`tC zz#zoHzz`<|Hm{zc5W-++m11C!VqjpHDaF9R&cML1MT&tzgn@x!j}!v~Hv<F1MJWb` zY6b>|yHX4ciVO@4k<t)#)zT1k&C(1EN(>APGo%?9<QNzjc1trb*fTIN+>&NsumU+m zhJm4wfq@}Z1|ol4hJnG6p`L-^nGD2b8nO%w(F_a>X0i+n%NZCLCdo1|xHB*?SjjOk z)G{zIG|53MV322E5NBXuP=?ah@(c`O3=9mx@(c_n3=9le@(_>AmuFybV_;y|CC|Vh z$-uzCr~pwXtpEv8BLxNqi+TnI1}6nb5LGKcg0@Y8fk6cn)KLB=sD_;i5FedXU|?Wn zU|`@>WMGJ8U|`@^WMJTAU|`5qgm|P}5hCBJ$iTqDz`)S0$iQI8z`!s;5n}NnMFs|0 z1_p*piVO_(q6`cS9~2oFq!}0(*pwI;1Q{3@l%TYU5(5JV0|SGb62#^HN)UBvO5nI- zXi{Qe-~*){B}f!4Rf2eABUIlWB?fR>IjRJ4*cl}T29SNXlpty3gAzkMIBx$cLE?~8 z86qJErL~nIL1&@Nz@W*%z!0PivA9N=fx!~wBV~vMr=aRDDnru7J!Oc)LBR-0oSZ5U zi^WtJ7}OaU7<5!1Q5d394~eTBsDgGCh>zw%=`|`03<99UqykB`XH+0TcVC5p!J2`A z;jaoLWGq!7K^&k8ad49=#DY1h5c9XFGBD^fFfg1~h3NmU%D|w&z`!6|uLg-LJ2i-b zE^3fCZcu}y?gMHJ44DiJ3@4y`BXx++tkfYscUFfu&`TW>x6$g5sK{1lU|?ooU?^8- zV9;e?V5nAyMBPetNa{bT4zaKPi8>^16f__P>uEsv=1|&I0}}MX8W0O>H6S6>qQStR z%)r1fS%ZPWfq{Wxy9NWpRR#uz9~uk{6$}gv=QJ4@CNeNExNAWyzNrP#|5Xd3?~fMP z!SxIZ+7JT`w7~)l_S%qa=C94bu$h5@p$ICjrUMBPGaX0}+vz}pI6?=KCJJ;QX{SX8 z5+W0IAP!%o11;E~>R#$VqU<kNyq<wUL>J;RLtRMFS?EFxbkT)4EJ7EOsM2*A7&I6d z7&>(!*>jUF#GvcCkSKen3vr;H9>hE|Jq8AC1_lOOJxCO1>Orz&z8)mImgq4sD1h>R zogM>&Dgy(<d_9Pdj_N_;=&l|Eg9`%#!yi3Js&&+d1YM>+#K2yCNOqg750Rgz4>9MM zJ|wlj(Pv=L0~N^z44^WL!O#HW!9W8@2*w&PFw}#J+;Rhmizh)9%rt=bbg2O(m7X(z z1nqkRh{1mh7#OB7Ffa%iGB9*7Ffc4PgrsT%BZzuGBS^^Q8$m*(&IpnS+l(L%-eLra z>itFx_25e5yb;7@w~Qb`^UMg6T|O8=f=t1ffgzNEfkD+6VnDSqBz3nLL$YO;F~os$ zjUnc&GKQpqZBY5+#*mP^WehR@pD`pvI85pxaj9VfG04{hqA|e)R5>s(6q+zFG%_$S zY&L-uP^P93pL>}yFl=OCV7PAz$u$$rAU>RB2H~GDgP3#C43d@}nL!fcH#10@Q80(_ z&Fjq}E>1Lu_#oFDl9;N@85q(*m4`VbsFW-q8jUO<>OCzWK^tlTu^`q066A#zkTld_ z!N8CYstqj|7}^*Z7&t5;*}5M}&$Wa&w0^ZE#NaJZiPM%418-U~FxWCMFub*7U@&4} zV9>T=V2}XS+g6Y|qTCAN!%izmtvAaGl7`M(K^%O|3KG=MtRN2iU<FCk|E(Y;qMS9j zeNfL}X$?tiDb|oE=!Mb?tRV($wuZQTFO+}U8j||2T0`o9+tv^RMQtGF7}!A4Or#CO zL2)(=3=s?r45>DdklSYi@yIC~P>9zvFgyk`7#Kd-K;rnX4J2rUY#~0CwuSgW*A}FK zfx*U>fnf#%1B0h6MBO7>hy!2SLPF%HEyNryJ4ndN+d&*+W(V=0tsNv02Y}>3`9InY z(v&K)gA|?n?I13;uxDV{&A`Ag(Vl@}5(5K+uLH!O2M&<9e-5SJJ3uV_?f|KTcpMoR zS{WD^>>L>wCNMBC9C3u?_fRKD)aE-eFf3+ZU}$z?V5kpZU|>*pW?-;kU|^_lW?(R2 zU|`tl3`w2OoFPFh-~wq0dAUFgn&bk>=WARbx#X-1qz>SBg=Dh;S4b3;xI&_;(iKu} z)Vne;yk%funCi;FU=DJC8$&&~C9>NMlAmw7L4xqE8^oe_ZjhkoaEJI@+?|0TkAZ<f z&Ygk51JputXJGJRU|_iB4)K|y2LnSX0|SGp2PA}6doVEAfwHd$B<enSKpgZRO0#;_ zLlOnIC!~xQ_k{Ss(i4(eV?7~pT;mBz3$30IhfMc`IB=dP#AiF8;^#aeaeUPil8s+@ zGB89jFfeF%LE4thQ2M?XBucI8y%`u%LDi)<1A`d@1A~bV149f014FhCr1Ci7!@zKi zfq~(R55(uYeIboXEk6c^5(Wl_LO%uuV+IC>XMT{7lJbW{iKaiq0oneL+H|!)1A`jK z9)Cz$`|1zL*7Z&S3=FOe3=E|Kkji6s03^sB20-d<g+NG<+5|$<#HK(<0dpr1lHI-p zLP|c_AO?m=1_lQ2AO?m+1_p+uP?|j$Vs2(I1H%yp28RA%NTW3*1e|N?88(GLg7QEJ zBr0x&KoZZh5J=F!4uNEsFCh#JObiSR|3e^g%M=QsxuCRAC`6r9C?u{`Lm>r`Q78jL zFCznkQz#?~FN8tT%A+ul`SlD8-@+h4{yz+o_+r8#4#^D%yO5zWoPoiVfq|ht9Ad%V za7Y}V2#18k?QlqL_#6)Lh)4t^sP!Tsi7hk&l18E<AQl%!Ffg29U|?vCU|`S!<^P~a zh)-%GAqra~A#p!D5)yaEBOyWiDiRX+E>R2&aiDg66eMV0MKLh=F)%O)L_^Af_-F=( zWCjL?l4u5oH4F?4H=-f>T4NX(ia=ew7)b4E9}BJj^J5_;RaY#eT3i?lX&fGmg*2W1 z#xgJjgSt?0kTj7O2XSau93&(b#6d!6a~vduUdBO+?k{nW#Q84{;z6T$NC;cSLmcEC z&rlEUJWh&dU<d=XfZ`z<zr{m>o*@BZAYTF`O-Lp{Qng$Hq}q;4fP_#plwOtqsS8df zK;rsI0wl=4B|x$*S0cn*!9<9D`9x^@-z5<mbcv8`R05@&6Cn<oln6>J3=DG;AwF1| z2nm^Oi4cn}Cqfe4Q>ZzgpnUcuh`2}+#9@j_kP_A=2@;aI^+}MTwKEA4L>H4F2Hu1! ze4PYIj55g(S|b?}RVK-hww-q}B+*qRLz-Yyk|9yJDH-DPOUV!qJxhk<hOfyGkFusf z5^cRy3MBDZr$DO3t`tZRFHV6N{5S>T^1msN5>X-*(w#O>h470~85ndxl~gJui1(#J z3ZSE@kTk=d2FV4{X^>uYVj3h$@27!7rk>$d8pP+{(;zPXp9V=(!s!qftEEHg2D5ZX zYPU*<WUqvDNWs&d&cF}?s<hG}A;*;gu|OsRqF+4&Vy<}x1A_&qfXaZ_*OCE>%6bNd zo(xFyd2R*+gEIpI!_f=|h998rbtVHtC8+tF#lWzdfq|hW3sSvmWJ97NDjQN{r)5JN zJU1KSfDPG@G;=5$62xb+85j<MT2k2%2W`lK$REgIV2}mn|5G`T?DQfB64d{4KrUlo zkj#Y`td$E1V%uCu9B1W19NYn=H{?PR)s0+8Tyy3@Xo)<CdFpwP5OB(al=)$K;HDQt zXC4DXJ!qg|OCBUO-^^oR0JR6c<w0EPo)7V9cs`^pn4AxB`NDiic3hng$rT&&A=z+e zKEz>npnR4BNI|1s04cD-3m|E2TLC0wSPL2I!2=9xg%FGM3n2xGbs;2m7ZySi;etX) zrL?OM5)wZP!HI`Ktq2k##zm0q6j%gtP;L=KzN`phPID2&A)AUIA@isR;^B`)^$ZLb z85kJ&iWwO8F)%PZD2B8H=9fSc*TE7<oW3uCBr3*INNbm;6vB5bg~<DqLb6?4Da2=W zrH~+>UkWKnH<v;}?0qSuuK7|5iQ2&WGDzx9DT8RpD1*4LrVQeP?lOqN8D)^lW_1~) z+C2=Fe^Ul2$$pkW94uE3$;LkA5PhlT3=H9*rd>G$LmdMH!-aB4`Qcx|z_5~mfuTOF zf`Q=xsPd?U_~>FK#Km_iAwelo1u;mm3Zl`Z3KFE=Rgi30RRwALEv$k#{81Go?R>0) zG|?EVAq^F;YDl8qUk&ld1qi>M;YBs1;P?p<U|^_$C=jfHM1^Jz149oe-_|fNtOM0% zHIOpAy%rMmQ)?k1wY(OR+PBm~9CE%E636#y85o=x7#M!nLh`>|9VAVa*MUmHdIpAx zbr2set7BmBXJBC1SqDkwoS?A*1_lP<dWc0D^^g#-s)tn1f%T9$udRo)p6Av>TD7;J z;zA9OL}}Cj30c1eNFtAf@>3fa7}!Ahzn}pU<mC+z7jJHWB(e()5QnffLTLU*28LV) z1_sGSh(Ya*kP>lnBP0ZNHbR2_SR*72Tx*1sbnhD>9{diaxtbvAWSbZm>Q^x^Fz7Zx zg7iicC`cF>o;5)X_}v5vDv@SL{?==T6jXuD5R22BAtBS-3`vCZn<1(DNHYV&ECvP! z#TH1Jzo&(P;WKC?wH4BH(rbf6`SCV}dhl59xi*N8uC+lN@Td)v&%d`ZFwA6NU{Gm? zG?(|aLmU#=0g2<14oK13+yUwBx^+V0dRr%C<m6{3q|s{E1u5Dyx)>OO7#JAVbV16E z|6TPEi)6bQ7`z!67`(a}7(78En%$71@<BHwdkXhJ@~?OgBm`u77#Ktt85oRuAO(|q zFC<&G^+MFI?}b>fxffDPUhIYF`_K!CDz-ibhHP-!>Vp_i-v_aHUmqkDbM!MX^n(VO z`yq*Eb3X$^AOi!#zJ5pu2~2=wPt^&KIM$m02|=?75Fa~DfMnC036MlM8!Eqj0;HTd zJOSd6`s))QCDxM(kW|e&5n`d?L`Z(MnFwj1_)dg09;Z%(3`lZJf^=4kCNVH<U|?W) zJqc3A&zTHq+x?!*z)%Gm+@1odZ1zrJVEDwq!0=)Uq(5<cD!6A<&k#5bGKjQh8YI=5 zPKP9_tm%-3NX>Lek7dtvNbcaA0r8>Q3<d^Y(1gMaNHsif2E?IPXFy6q*_jai-ZLRl z(LEEAmR8P$)RxY(Kp|7lz)&&^(qfq~3u5r4SrDK8nFXo;^Jhc&FK0s>5Hkmonk(lp zFr<No-RD9ad}S^q>YmMo1TEt{NSfiB2Z<VCC@l{a*Ph3~zz!OlHJAs9OAC+y0|SHO zJV?-c&V!VMQS%_#uxQ?9FUh-Nta*vKsl}TW4D8r+{mW8|%8N2fQWc6)a}zUjQi~Ld zQ%g!R^U@X4@{1IT3sRFa(=wA4N{dsAiZ{oa6$;j;mF9ruN>Yo#>fjoaQq%H_QWaA3 z5|eU324t3i)g|WSK(y;A1cQyMgm4uSixm<TlJj$O6LpJI3sQ>`OHxx5ax#lc6!Oy) za`MwN^Ax}?)nf=PPAyXKbOF<wue-nJVK-ASFtjo;+k7^pQijJdFE=$OB^9JNZ?kLR zDt7C`bCObvijy;nQZtiM6@s8%({;_vD=taR$teYS@9>@?g~Zb0^wjdhOEPjwQWgA4 zOR7?fHovRj5v}*g%t<Rs%}mZvs4C4>2uUq2NzGG8NiBw(Rjg2!U!;(dnycWLU6NUr znOc;Zr;r9VGbaaTmYzarF4S0%RaK>6t5WlHz}`$%$jm58RX}#VLT+Y>f_HwdKyIQ! WdTL2gYF=K6LSku}-sYdZxA*|Ez3h(w delta 6582 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 I`)~3A069xPnE(I) diff --git a/uffd/translations/de/LC_MESSAGES/messages.po b/uffd/translations/de/LC_MESSAGES/messages.po index 52d52102..5738094a 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: 2022-11-01 00:38+0100\n" +"POT-Creation-Date: 2022-11-06 01:46+0100\n" "PO-Revision-Date: 2021-05-25 21:18+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language: de\n" @@ -98,7 +98,7 @@ msgstr "Falsches Passwort" msgid "Login name or e-mail address is already in use" msgstr "Der Anmeldename oder die E-Mail-Adresse wird bereits verwendet" -#: uffd/models/user.py:46 +#: uffd/models/user.py:119 #, python-format msgid "" "At least %(minlen)d and at most %(maxlen)d characters. Only letters, " @@ -144,8 +144,8 @@ msgstr "Über uffd" #: uffd/templates/group/list.html:8 uffd/templates/invite/list.html:6 #: uffd/templates/mail/list.html:8 uffd/templates/role/list.html:8 -#: uffd/templates/service/index.html:8 uffd/templates/service/show.html:75 -#: uffd/templates/service/show.html:103 uffd/templates/user/list.html:8 +#: uffd/templates/service/index.html:8 uffd/templates/service/show.html:99 +#: uffd/templates/service/show.html:127 uffd/templates/user/list.html:8 msgid "New" msgstr "Neu" @@ -162,7 +162,7 @@ msgstr "GID" #: uffd/templates/rolemod/list.html:9 uffd/templates/rolemod/show.html:44 #: uffd/templates/selfservice/self.html:189 #: uffd/templates/service/index.html:14 uffd/templates/service/show.html:20 -#: uffd/templates/service/show.html:109 uffd/templates/user/show.html:193 +#: uffd/templates/service/show.html:133 uffd/templates/user/show.html:193 #: uffd/templates/user/show.html:225 msgid "Name" msgstr "Name" @@ -238,7 +238,7 @@ msgid "Created by" msgstr "Erstellt durch" #: uffd/templates/invite/list.html:14 uffd/templates/service/api.html:34 -#: uffd/templates/service/show.html:110 +#: uffd/templates/service/show.html:134 msgid "Permissions" msgstr "Berechtigungen" @@ -1239,7 +1239,7 @@ msgstr "Diese Option hat keine Auswirkung: Remailer ist nicht konfiguriert" msgid "Access uffd metrics" msgstr "Zugriff auf uffd-Metriken" -#: uffd/templates/service/oauth2.html:20 uffd/templates/service/show.html:81 +#: uffd/templates/service/oauth2.html:20 uffd/templates/service/show.html:105 msgid "Client ID" msgstr "Client-ID" @@ -1312,15 +1312,15 @@ msgstr "Mitglieder der Gruppe \"%(group_name)s\" haben Zugriff" msgid "Hide e-mail addresses with remailer" msgstr "E-Mail-Adressen mit Remailer verstecken" -#: uffd/templates/service/show.html:43 +#: uffd/templates/service/show.html:43 uffd/templates/service/show.html:66 msgid "Remailer disabled" msgstr "Remailer deaktiviert" -#: uffd/templates/service/show.html:46 +#: uffd/templates/service/show.html:46 uffd/templates/service/show.html:69 msgid "Remailer enabled" msgstr "Remailer aktiviert" -#: uffd/templates/service/show.html:49 +#: uffd/templates/service/show.html:49 uffd/templates/service/show.html:72 msgid "Remailer enabled (deprecated, case-sensitive format)" msgstr "" "Remailer aktiviert (veraltetes, Groß-/Kleinschreibung-unterscheidendes " @@ -1337,13 +1337,29 @@ msgstr "" "-Mail-Adressen aller Nutzer aus und kann zu massenhaftem Versand von " "Benachrichtigungs-E-Mails führen." -#: uffd/templates/service/show.html:60 +#: uffd/templates/service/show.html:59 +msgid "Overwrite remailer setting for specific users" +msgstr "Überschreibe Remailer-Einstellung für ausgewählte Nutzer" + +#: uffd/templates/service/show.html:62 +msgid "Login names" +msgstr "Anmeldenamen" + +#: uffd/templates/service/show.html:77 +msgid "" +"Useful for testing remailer before enabling it for all users. Specify " +"users as a comma-seperated list of login names." +msgstr "" +"Hilfreich zum Testen des Remailers vor dem Aktivieren für alle Nutzer. Um" +" Nutzer auszuwählen, liste ihre Anmeldenamen mit Komma getrennt auf." + +#: uffd/templates/service/show.html:84 msgid "Allow users to select a different e-mail address for this service" msgstr "" "Ermögliche Nutzern für diesen Dienst eine andere E-Mail-Adresse " "auszuwählen" -#: uffd/templates/service/show.html:62 +#: uffd/templates/service/show.html:86 msgid "If disabled, the service always uses the primary e-mail address." msgstr "Wenn deaktiviert, wird immer die primäre E-Mail-Adresse verwendet." diff --git a/uffd/views/service.py b/uffd/views/service.py index 7f5b36e0..37fa1f67 100644 --- a/uffd/views/service.py +++ b/uffd/views/service.py @@ -6,7 +6,7 @@ from flask_babel import lazy_gettext from uffd.navbar import register_navbar from uffd.csrf import csrf_protect from uffd.database import db -from uffd.models import Service, get_services, Group, OAuth2Client, OAuth2LogoutURI, APIClient, RemailerMode +from uffd.models import User, Service, ServiceUser, get_services, Group, OAuth2Client, OAuth2LogoutURI, APIClient, RemailerMode from .session import login_required @@ -50,8 +50,15 @@ def index(): @login_required(admin_acl) def show(id=None): service = Service() if id is None else Service.query.get_or_404(id) + remailer_overwrites = [] + if id is not None: + # pylint: disable=singleton-comparison + remailer_overwrites = ServiceUser.query.filter( + ServiceUser.service_id == id, + ServiceUser.remailer_overwrite_mode != None + ).all() all_groups = Group.query.all() - return render_template('service/show.html', service=service, all_groups=all_groups) + return render_template('service/show.html', service=service, all_groups=all_groups, remailer_overwrites=remailer_overwrites) @bp.route('/service/new', methods=['POST']) @bp.route('/service/<int:id>', methods=['POST']) @@ -73,8 +80,26 @@ def edit_submit(id=None): else: service.limit_access = True service.access_group = Group.query.get(request.form['access-group']) - service.remailer_mode = RemailerMode[request.form['remailer-mode']] service.enable_email_preferences = request.form.get('enable_email_preferences') == '1' + service.remailer_mode = RemailerMode[request.form['remailer-mode']] + remailer_overwrite_mode = RemailerMode[request.form['remailer-overwrite-mode']] + remailer_overwrite_user_ids = [ + User.query.filter_by(loginname=loginname.strip()).one().id + for loginname in request.form['remailer-overwrite-users'].split(',') if loginname.strip() + ] + # pylint: disable=singleton-comparison + service_users = ServiceUser.query.filter( + ServiceUser.service == service, + db.or_( + ServiceUser.user_id.in_(remailer_overwrite_user_ids), + ServiceUser.remailer_overwrite_mode != None, + ) + ) + for service_user in service_users: + if service_user.user_id in remailer_overwrite_user_ids: + service_user.remailer_overwrite_mode = remailer_overwrite_mode + else: + service_user.remailer_overwrite_mode = None db.session.commit() return redirect(url_for('service.show', id=service.id)) -- GitLab