From 6a22c82c022ccf7b8c4e8b0355590db8e4f34cef Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 20 Nov 2022 10:17:19 +0100 Subject: [PATCH 01/11] Fix run_dev --- core/admin/run_dev.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/run_dev.sh b/core/admin/run_dev.sh index cf05fba3..05bf6548 100755 --- a/core/admin/run_dev.sh +++ b/core/admin/run_dev.sh @@ -87,7 +87,7 @@ EOF # build chmod -R u+rwX,go+rX . -"${docker}" build --tag "${DEV_NAME}:latest" . +"${docker}" build --build-arg TARGETPLATFORM=linux/amd64 --tag "${DEV_NAME}:latest" . # gather volumes to map into container volumes=() From 38507b2e1be890f2d7603b5401d245f64613bfed Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 20 Nov 2022 10:19:28 +0100 Subject: [PATCH 02/11] Close #2372: Implement a GUI for WILDCARD_SENDERS --- core/admin/mailu/internal/views/postfix.py | 3 ++- core/admin/mailu/models.py | 1 + core/admin/mailu/ui/forms.py | 1 + .../admin/mailu/ui/templates/user/create.html | 1 + .../migrations/versions/7ac252f2bbbf_.py | 22 +++++++++++++++++++ docs/webadministration.rst | 4 +++- towncrier/newsfragments/2372.feature | 1 + 7 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 core/admin/migrations/versions/7ac252f2bbbf_.py create mode 100644 towncrier/newsfragments/2372.feature diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index 8188270c..c0a17319 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -145,8 +145,9 @@ def postfix_sender_login(sender): localpart = localpart[:next((i for i, ch in enumerate(localpart) if ch in flask.current_app.config.get('RECIPIENT_DELIMITER')), None)] destinations = models.Email.resolve_destination(localpart, domain_name, True) or [] destinations.extend(wildcard_senders) + destinations.extend(i[0] for i in models.User.query.filter_by(allow_spoofing=True).with_entities(models.User.email).all()) if destinations: - return flask.jsonify(",".join(idna_encode(destinations))) + return flask.jsonify(",".join(idna_encode(list(set(destinations))))) return flask.abort(404) @internal.route("/postfix/sender/rate/") diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 48ce8b33..1c57c8be 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -501,6 +501,7 @@ class User(Base, Email): # Features enable_imap = db.Column(db.Boolean, nullable=False, default=True) enable_pop = db.Column(db.Boolean, nullable=False, default=True) + allow_spoofing = db.Column(db.Boolean, nullable=False, default=False) # Filters forward_enabled = db.Column(db.Boolean, nullable=False, default=False) diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index beb44092..3882064d 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -84,6 +84,7 @@ class UserForm(flask_wtf.FlaskForm): quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9) enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True) enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True) + allow_spoofing = fields.BooleanField(_('Allow the user to spoof the sender (send email as anyone)'), default=False) displayed_name = fields.StringField(_('Displayed name')) comment = fields.StringField(_('Comment')) enabled = fields.BooleanField(_('Enabled'), default=True) diff --git a/core/admin/mailu/ui/templates/user/create.html b/core/admin/mailu/ui/templates/user/create.html index 9a32243d..7e1c9122 100644 --- a/core/admin/mailu/ui/templates/user/create.html +++ b/core/admin/mailu/ui/templates/user/create.html @@ -25,6 +25,7 @@ prepend=' GB') }} {{ macros.form_field(form.enable_imap) }} {{ macros.form_field(form.enable_pop) }} + {{ macros.form_field(form.allow_spoofing) }} {%- endcall %} {{ macros.form_field(form.submit) }} diff --git a/core/admin/migrations/versions/7ac252f2bbbf_.py b/core/admin/migrations/versions/7ac252f2bbbf_.py new file mode 100644 index 00000000..0be19d88 --- /dev/null +++ b/core/admin/migrations/versions/7ac252f2bbbf_.py @@ -0,0 +1,22 @@ +"""empty message + +Revision ID: 7ac252f2bbbf +Revises: 8f9ea78776f4 +Create Date: 2022-11-20 08:57:16.879152 + +""" + +# revision identifiers, used by Alembic. +revision = '7ac252f2bbbf' +down_revision = '8f9ea78776f4' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('user', sa.Column('allow_spoofing', sa.Boolean(), nullable=False)) + + +def downgrade(): + op.drop_column('user', 'allow_spoofing') diff --git a/docs/webadministration.rst b/docs/webadministration.rst index e17d12f0..2e0de745 100644 --- a/docs/webadministration.rst +++ b/docs/webadministration.rst @@ -321,7 +321,7 @@ This page also shows an overview of the following settings of an user: * Email. The email address of the user. -* Features. Shows if IMAP or POP3 access is enabled. +* Features. Shows if IMAP or POP3 access is enabled and whether the user should be allowed to spoof emails. * Storage quota. Shows how much assigned storage has been consumed. @@ -357,6 +357,8 @@ For adding a new user the following options can be configured. * Allow POP3 access. When ticked, allows email retrieval via the POP3 protocol. +* Allow the user to spoof the sender. When ticked, allows the user to send email as anyone. + Aliases ``````` diff --git a/towncrier/newsfragments/2372.feature b/towncrier/newsfragments/2372.feature new file mode 100644 index 00000000..ec2c5bef --- /dev/null +++ b/towncrier/newsfragments/2372.feature @@ -0,0 +1 @@ +Create a GUI for WILDCARD_SENDERS From ef9cc3c866c64be67391e6c85090cb79dedfcbc5 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 20 Nov 2022 11:09:04 +0100 Subject: [PATCH 03/11] Show spoofing on /admin/user/list too --- core/admin/mailu/ui/templates/user/list.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index 14626212..ac74a675 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -39,9 +39,10 @@   {{ user }} - + {% if user.enable_imap %}imap{% endif %} {% if user.enable_pop %}pop3{% endif %} + {% if user.allow_spoofing %}allow-spoofing{% endif %} {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} {{ user.comment or '-' }} From d5ac9199a08f5bfbff81c7268c5c133b5994cf0c Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 20 Nov 2022 14:59:06 +0100 Subject: [PATCH 04/11] Update 7ac252f2bbbf_.py --- core/admin/migrations/versions/7ac252f2bbbf_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/migrations/versions/7ac252f2bbbf_.py b/core/admin/migrations/versions/7ac252f2bbbf_.py index 0be19d88..11a30453 100644 --- a/core/admin/migrations/versions/7ac252f2bbbf_.py +++ b/core/admin/migrations/versions/7ac252f2bbbf_.py @@ -1,4 +1,4 @@ -"""empty message +""" Add user.allow_spoofing Revision ID: 7ac252f2bbbf Revises: 8f9ea78776f4 From ee512112fb10b62afd9d1861d4da90b6f73c4b85 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 23 Nov 2022 17:07:19 +0100 Subject: [PATCH 05/11] fix flask db history --- core/admin/migrations/versions/7ac252f2bbbf_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/migrations/versions/7ac252f2bbbf_.py b/core/admin/migrations/versions/7ac252f2bbbf_.py index 11a30453..c37bf554 100644 --- a/core/admin/migrations/versions/7ac252f2bbbf_.py +++ b/core/admin/migrations/versions/7ac252f2bbbf_.py @@ -8,7 +8,7 @@ Create Date: 2022-11-20 08:57:16.879152 # revision identifiers, used by Alembic. revision = '7ac252f2bbbf' -down_revision = '8f9ea78776f4' +down_revision = 'f4f0f89e0047' from alembic import op import sqlalchemy as sa From 4d8bd210c542c1859776b9640e55f41da8195af5 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 23 Nov 2022 17:07:48 +0100 Subject: [PATCH 06/11] Update run_dev.sh --- core/admin/run_dev.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/run_dev.sh b/core/admin/run_dev.sh index 05bf6548..cf05fba3 100755 --- a/core/admin/run_dev.sh +++ b/core/admin/run_dev.sh @@ -87,7 +87,7 @@ EOF # build chmod -R u+rwX,go+rX . -"${docker}" build --build-arg TARGETPLATFORM=linux/amd64 --tag "${DEV_NAME}:latest" . +"${docker}" build --tag "${DEV_NAME}:latest" . # gather volumes to map into container volumes=() From c1144612be1d1fd689ad19b28869a5f2837843cd Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 23 Nov 2022 17:13:15 +0100 Subject: [PATCH 07/11] fix sorting --- core/admin/mailu/ui/templates/user/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index ac74a675..84c0cd27 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -39,7 +39,7 @@   {{ user }} - + {% if user.enable_imap %}imap{% endif %} {% if user.enable_pop %}pop3{% endif %} {% if user.allow_spoofing %}allow-spoofing{% endif %} From 19bd9362d3c0d2c113a894ea3d43a12f725fabbe Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 24 Nov 2022 14:56:26 +0100 Subject: [PATCH 08/11] As suggested by ghost --- core/admin/mailu/internal/views/postfix.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index c0a17319..62b400d3 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -143,11 +143,11 @@ def postfix_sender_login(sender): if localpart is None: return flask.jsonify(",".join(wildcard_senders)) if wildcard_senders else flask.abort(404) localpart = localpart[:next((i for i, ch in enumerate(localpart) if ch in flask.current_app.config.get('RECIPIENT_DELIMITER')), None)] - destinations = models.Email.resolve_destination(localpart, domain_name, True) or [] - destinations.extend(wildcard_senders) - destinations.extend(i[0] for i in models.User.query.filter_by(allow_spoofing=True).with_entities(models.User.email).all()) + destinations = set(models.Email.resolve_destination(localpart, domain_name, True) or []) + destinations.update(wildcard_senders) + destinations.update(i[0] for i in models.User.query.filter_by(allow_spoofing=True).with_entities(models.User.email).all()) if destinations: - return flask.jsonify(",".join(idna_encode(list(set(destinations))))) + return flask.jsonify(",".join(idna_encode(destinations))) return flask.abort(404) @internal.route("/postfix/sender/rate/") From a5eeab37e1210ccf31f6f850790262d9d52d00e6 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Fri, 25 Nov 2022 10:43:00 +0100 Subject: [PATCH 09/11] Add default for column allow_spoofing --- core/admin/migrations/versions/7ac252f2bbbf_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/migrations/versions/7ac252f2bbbf_.py b/core/admin/migrations/versions/7ac252f2bbbf_.py index c37bf554..039ab8b6 100644 --- a/core/admin/migrations/versions/7ac252f2bbbf_.py +++ b/core/admin/migrations/versions/7ac252f2bbbf_.py @@ -15,7 +15,7 @@ import sqlalchemy as sa def upgrade(): - op.add_column('user', sa.Column('allow_spoofing', sa.Boolean(), nullable=False)) + op.add_column('user', sa.Column('allow_spoofing', sa.Boolean(), nullable=False, server_default=sa.sql.expression.false())) def downgrade(): From 53720876b481b4c26b20fd58706e53ef24d8ab5f Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Fri, 25 Nov 2022 10:47:49 +0100 Subject: [PATCH 10/11] Colorize feature badges --- core/admin/mailu/ui/templates/user/list.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index 84c0cd27..1c845062 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -40,9 +40,9 @@ {{ user }} - {% if user.enable_imap %}imap{% endif %} - {% if user.enable_pop %}pop3{% endif %} - {% if user.allow_spoofing %}allow-spoofing{% endif %} + {% if user.enable_imap %}imap{% endif %} + {% if user.enable_pop %}pop3{% endif %} + {% if user.allow_spoofing %}allow-spoofing{% endif %} {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} {{ user.comment or '-' }} From b0990460a47035cfbf10051c0d9bcd3f5e4d104f Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Fri, 25 Nov 2022 11:32:21 +0100 Subject: [PATCH 11/11] Fix error display --- core/admin/mailu/ui/templates/macros.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/ui/templates/macros.html b/core/admin/mailu/ui/templates/macros.html index 90084246..31efd0e4 100644 --- a/core/admin/mailu/ui/templates/macros.html +++ b/core/admin/mailu/ui/templates/macros.html @@ -3,7 +3,7 @@ {%- for fieldname, errors in form.errors.items() %} {%- if bootstrap_is_hidden_field(form[fieldname]) %} {%- for error in errors %} -

{{error}}

+

{{error}}

{%- endfor %} {%- endif %} {%- endfor %} @@ -13,7 +13,7 @@ {%- macro form_field_errors(field) %} {%- if field.errors %} {%- for error in field.errors %} -

{{ error }}

+

{{ error }}

{%- endfor %} {%- endif %} {%- endmacro %} @@ -23,7 +23,7 @@
{%- for field in fields %} -
+
{%- if field.__class__.__name__ == 'list' %} {%- for subfield in field %} {{ form_individual_field(subfield, prepend=prepend, append=append, label=label, **kwargs) }} @@ -38,12 +38,13 @@ {%- endmacro %} {%- macro form_individual_field(field, prepend='', append='', label=True, class_="") %} + {%- set fieldclass=" ".join(["form-control"] + ([class_] if class_ else []) + (["is-invalid"] if field.errors else [])) %} {%- if field.type == "BooleanField" %} {{ field(**kwargs) }}  {{ field.label if label else '' }} {%- else %} {{ field.label if label else '' }}{{ form_field_errors(field) }} {%- if prepend %}
{%- elif append %}
{%- endif %} - {{ prepend|safe }}{{ field(class_=("form-control " + class_) if class_ else "form-control", **kwargs) }}{{ append|safe }} + {{ prepend|safe }}{{ field(class_=fieldclass, **kwargs) }}{{ append|safe }} {%- if prepend or append %}
{%- endif %} {%- endif %} {%- endmacro %}