2546: Implement a GUI for WILDCARD_SENDERS r=mergify[bot] a=nextgens

## What type of PR?

Feature

## What does this PR do?

- Implement a GUI for WILDCARD_SENDERS

### Related issue(s)
- closes #2372

## Prerequisites
Before we can consider review and merge, please make sure the following list is done and checked.
If an entry in not applicable, you can check it or remove it from the list.

- [x] In case of feature or enhancement: documentation updated accordingly
- [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file.


Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
Co-authored-by: Florent Daigniere <nextgens@users.noreply.github.com>
Co-authored-by: Alexander Graf <ghostwheel42@users.noreply.github.com>
main
bors[bot] 2 years ago committed by GitHub
commit e0d42cadc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -143,8 +143,9 @@ def postfix_sender_login(sender):
if localpart is None: if localpart is None:
return flask.jsonify(",".join(wildcard_senders)) if wildcard_senders else flask.abort(404) 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)] 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 = set(models.Email.resolve_destination(localpart, domain_name, True) or [])
destinations.extend(wildcard_senders) 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: if destinations:
return flask.jsonify(",".join(idna_encode(destinations))) return flask.jsonify(",".join(idna_encode(destinations)))
return flask.abort(404) return flask.abort(404)

@ -505,6 +505,7 @@ class User(Base, Email):
# Features # Features
enable_imap = db.Column(db.Boolean, nullable=False, default=True) enable_imap = db.Column(db.Boolean, nullable=False, default=True)
enable_pop = 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 # Filters
forward_enabled = db.Column(db.Boolean, nullable=False, default=False) forward_enabled = db.Column(db.Boolean, nullable=False, default=False)

@ -94,6 +94,7 @@ class UserForm(flask_wtf.FlaskForm):
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9) quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9)
enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True) enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True)
enable_pop = fields.BooleanField(_('Allow POP3 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')) displayed_name = fields.StringField(_('Displayed name'))
comment = fields.StringField(_('Comment')) comment = fields.StringField(_('Comment'))
enabled = fields.BooleanField(_('Enabled'), default=True) enabled = fields.BooleanField(_('Enabled'), default=True)

@ -3,7 +3,7 @@
{%- for fieldname, errors in form.errors.items() %} {%- for fieldname, errors in form.errors.items() %}
{%- if bootstrap_is_hidden_field(form[fieldname]) %} {%- if bootstrap_is_hidden_field(form[fieldname]) %}
{%- for error in errors %} {%- for error in errors %}
<p class="error">{{error}}</p> <p class="form-text text-danger">{{error}}</p>
{%- endfor %} {%- endfor %}
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
@ -13,7 +13,7 @@
{%- macro form_field_errors(field) %} {%- macro form_field_errors(field) %}
{%- if field.errors %} {%- if field.errors %}
{%- for error in field.errors %} {%- for error in field.errors %}
<p class="help-block inline">{{ error }}</p> <p class="form-text text-danger">{{ error }}</p>
{%- endfor %} {%- endfor %}
{%- endif %} {%- endif %}
{%- endmacro %} {%- endmacro %}
@ -23,7 +23,7 @@
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
{%- for field in fields %} {%- for field in fields %}
<div class="col-lg-{{ width }} col-xs-12 {{ 'has-error' if field.errors else '' }}"> <div class="col-lg-{{ width }} col-xs-12">
{%- if field.__class__.__name__ == 'list' %} {%- if field.__class__.__name__ == 'list' %}
{%- for subfield in field %} {%- for subfield in field %}
{{ form_individual_field(subfield, prepend=prepend, append=append, label=label, **kwargs) }} {{ form_individual_field(subfield, prepend=prepend, append=append, label=label, **kwargs) }}
@ -38,12 +38,13 @@
{%- endmacro %} {%- endmacro %}
{%- macro form_individual_field(field, prepend='', append='', label=True, class_="") %} {%- 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" %} {%- if field.type == "BooleanField" %}
{{ field(**kwargs) }}<span>&nbsp;&nbsp;</span>{{ field.label if label else '' }} {{ field(**kwargs) }}<span>&nbsp;&nbsp;</span>{{ field.label if label else '' }}
{%- else %} {%- else %}
{{ field.label if label else '' }}{{ form_field_errors(field) }} {{ field.label if label else '' }}{{ form_field_errors(field) }}
{%- if prepend %}<div class="input-group-prepend">{%- elif append %}<div class="input-group-append">{%- endif %} {%- if prepend %}<div class="input-group-prepend">{%- elif append %}<div class="input-group-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 %}</div>{%- endif %} {%- if prepend or append %}</div>{%- endif %}
{%- endif %} {%- endif %}
{%- endmacro %} {%- endmacro %}

@ -25,6 +25,7 @@
prepend='<span class="input-group-text"><span id="quota_bytes_value"></span>&nbsp;GB</span>') }} prepend='<span class="input-group-text"><span id="quota_bytes_value"></span>&nbsp;GB</span>') }}
{{ macros.form_field(form.enable_imap) }} {{ macros.form_field(form.enable_imap) }}
{{ macros.form_field(form.enable_pop) }} {{ macros.form_field(form.enable_pop) }}
{{ macros.form_field(form.allow_spoofing) }}
{%- endcall %} {%- endcall %}
{{ macros.form_field(form.submit) }} {{ macros.form_field(form.submit) }}

@ -39,9 +39,10 @@
<a href="{{ url_for('.fetch_list', user_email=user.email) }}" title="{% trans %}Fetched accounts{% endtrans %}"><i class="fa fa-download"></i></a>&nbsp; <a href="{{ url_for('.fetch_list', user_email=user.email) }}" title="{% trans %}Fetched accounts{% endtrans %}"><i class="fa fa-download"></i></a>&nbsp;
</td> </td>
<td>{{ user }}</td> <td>{{ user }}</td>
<td data-sort="{{ user.enable_imap*2 + user.enable_pop }}"> <td data-sort="{{ user.allow_spoofing*4 + user.enable_imap*2 + user.enable_pop }}">
{% if user.enable_imap %}<span class="badge bg-info">imap</span>{% endif %} {% if user.enable_imap %}<span class="badge bg-primary">imap</span>{% endif %}
{% if user.enable_pop %}<span class="badge bg-info">pop3</span>{% endif %} {% if user.enable_pop %}<span class="badge bg-secondary">pop3</span>{% endif %}
{% if user.allow_spoofing %}<span class="badge bg-danger">allow-spoofing</span>{% endif %}
</td> </td>
<td data-sort="{{ user.quota_bytes_used }}">{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td> <td data-sort="{{ user.quota_bytes_used }}">{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td>
<td>{{ user.comment or '-' }}</td> <td>{{ user.comment or '-' }}</td>

@ -0,0 +1,22 @@
""" Add user.allow_spoofing
Revision ID: 7ac252f2bbbf
Revises: 8f9ea78776f4
Create Date: 2022-11-20 08:57:16.879152
"""
# revision identifiers, used by Alembic.
revision = '7ac252f2bbbf'
down_revision = 'f4f0f89e0047'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('user', sa.Column('allow_spoofing', sa.Boolean(), nullable=False, server_default=sa.sql.expression.false()))
def downgrade():
op.drop_column('user', 'allow_spoofing')

@ -325,7 +325,7 @@ This page also shows an overview of the following settings of an user:
* Email. The email address of the 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. * Storage quota. Shows how much assigned storage has been consumed.
@ -361,6 +361,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 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 Aliases
``````` ```````

@ -0,0 +1 @@
Create a GUI for WILDCARD_SENDERS
Loading…
Cancel
Save