diff --git a/tests/models/test_services.py b/tests/models/test_services.py index 82808db46dfd0bd71ebeb7f5462093de5cdf0208..d6836b5f7d3d8dfbb329d639b29415e7491ee374 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 1e57d8be565b242f1a96ba1474e0d2ee3888381e..a40e447f07702651163a20429bf80d2ef20a082c 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 7b01157fb27e75cf264e21413a7f268c6c1f91c7..3ac6ee2160a78049bc7b465c7af6e78ebf01519a 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 0000000000000000000000000000000000000000..10c617ee70e3f14d9be935cc4171724f36b5a666 --- /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 c5e5cbcce58674c5c85be9b7b8e0b754d9952880..2143e6740fd119d39698bd465438ead46e23fb6d 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 6c6fca94f8121b6e06ad228355d995e7c17e54d6..5277fa41751cfbc4fce96f2174ad05767ff652d5 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 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 52d52102f02e5d68cc23fe92e0f42937b072bc92..5738094ae9a4f4e90d9b1e17bf21e6891a065aa0 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 7f5b36e03aaeeda3e2fb211751895f91bac74fe1..37fa1f67bedf10adab5c505cec021b117ed85737 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))