2016-03-19 21:37:48 +02:00
|
|
|
from wtforms import validators, fields, widgets
|
|
|
|
from wtforms_components import fields as fields_
|
2016-10-02 14:37:06 +02:00
|
|
|
from flask_babel import lazy_gettext as _
|
2016-08-11 13:33:04 +02:00
|
|
|
|
2016-08-13 20:51:54 +02:00
|
|
|
import flask_login
|
2016-10-02 09:33:07 +02:00
|
|
|
import flask_wtf
|
2016-08-11 13:33:04 +02:00
|
|
|
import re
|
2023-06-08 13:26:41 +02:00
|
|
|
import ipaddress
|
2016-08-11 13:33:04 +02:00
|
|
|
|
2019-01-04 19:01:46 +02:00
|
|
|
LOCALPART_REGEX = "^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*$"
|
2016-08-11 13:33:04 +02:00
|
|
|
|
|
|
|
class DestinationField(fields.SelectMultipleField):
|
|
|
|
""" Allow for multiple emails selection from current user choices and
|
|
|
|
additional email addresses.
|
|
|
|
"""
|
|
|
|
|
|
|
|
validator = re.compile(r'^.+@([^.@][^@]+)$', re.IGNORECASE)
|
|
|
|
|
|
|
|
def iter_choices(self):
|
|
|
|
managed = [
|
|
|
|
str(email)
|
|
|
|
for email in flask_login.current_user.get_managed_emails()
|
|
|
|
]
|
|
|
|
for email in managed:
|
|
|
|
selected = self.data is not None and self.coerce(email) in self.data
|
|
|
|
yield (email, email, selected)
|
2016-08-11 14:27:01 +02:00
|
|
|
for email in self.data or ():
|
2016-08-11 13:33:04 +02:00
|
|
|
if email not in managed:
|
|
|
|
yield (email, email, True)
|
|
|
|
|
|
|
|
def pre_validate(self, form):
|
|
|
|
for item in self.data:
|
|
|
|
if not self.validator.match(item):
|
2016-10-02 14:37:06 +02:00
|
|
|
raise validators.ValidationError(_('Invalid email address.'))
|
2016-03-19 21:37:48 +02:00
|
|
|
|
2018-12-28 22:03:57 +02:00
|
|
|
class MultipleEmailAddressesVerify(object):
|
|
|
|
def __init__(self,message=_('Invalid email address.')):
|
|
|
|
self.message = message
|
|
|
|
|
|
|
|
def __call__(self, form, field):
|
2023-02-05 18:01:34 +02:00
|
|
|
pattern = re.compile(r'^([_a-z0-9\-\+]+)(\.[_a-z0-9\-\+]+)*@([a-z0-9\-]{1,}\.)*([a-z]{1,})(,([_a-z0-9\-\+]+)(\.[_a-z0-9\-\+]+)*@([a-z0-9\-]{1,}\.)*([a-z]{2,}))*$')
|
2018-12-28 22:03:57 +02:00
|
|
|
if not pattern.match(field.data.replace(" ", "")):
|
|
|
|
raise validators.ValidationError(self.message)
|
2016-03-19 21:37:48 +02:00
|
|
|
|
2022-11-13 18:15:50 +02:00
|
|
|
class MultipleFoldersVerify(object):
|
2022-11-21 11:38:44 +02:00
|
|
|
""" Ensure that we have CSV formated data """
|
2022-11-13 18:15:50 +02:00
|
|
|
def __init__(self,message=_('Invalid list of folders.')):
|
|
|
|
self.message = message
|
|
|
|
|
|
|
|
def __call__(self, form, field):
|
2022-12-19 11:33:05 +02:00
|
|
|
pattern = re.compile(r'^[^,]+(,[^,]+)*$')
|
2022-11-13 18:15:50 +02:00
|
|
|
if not pattern.match(field.data.replace(" ", "")):
|
|
|
|
raise validators.ValidationError(self.message)
|
|
|
|
|
2016-10-02 14:53:01 +02:00
|
|
|
class ConfirmationForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Confirm'))
|
2016-08-19 10:36:13 +02:00
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class DomainForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
name = fields.StringField(_('Domain name'), [validators.DataRequired()])
|
2019-01-05 14:45:55 +02:00
|
|
|
max_users = fields_.IntegerField(_('Maximum user count'), [validators.NumberRange(min=-1)], default=10)
|
|
|
|
max_aliases = fields_.IntegerField(_('Maximum alias count'), [validators.NumberRange(min=-1)], default=10)
|
2017-02-02 23:29:33 +02:00
|
|
|
max_quota_bytes = fields_.IntegerSliderField(_('Maximum user quota'), default=0)
|
2017-12-03 13:01:25 +02:00
|
|
|
signup_enabled = fields.BooleanField(_('Enable sign-up'), default=False)
|
2016-10-02 14:37:06 +02:00
|
|
|
comment = fields.StringField(_('Comment'))
|
2018-08-01 21:23:50 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2016-03-19 21:37:48 +02:00
|
|
|
|
|
|
|
|
2018-04-18 20:31:32 +02:00
|
|
|
class DomainSignupForm(flask_wtf.FlaskForm):
|
|
|
|
name = fields.StringField(_('Domain name'), [validators.DataRequired()])
|
|
|
|
localpart = fields.StringField(_('Initial admin'), [validators.DataRequired()])
|
|
|
|
pw = fields.PasswordField(_('Admin password'), [validators.DataRequired()])
|
|
|
|
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
2022-11-04 14:35:13 +02:00
|
|
|
pwned = fields.HiddenField(label='', default=-1)
|
2018-04-18 20:31:32 +02:00
|
|
|
captcha = flask_wtf.RecaptchaField()
|
|
|
|
submit = fields.SubmitField(_('Create'))
|
|
|
|
|
|
|
|
|
2017-09-03 18:30:00 +02:00
|
|
|
class AlternativeForm(flask_wtf.FlaskForm):
|
|
|
|
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
|
2018-08-01 21:23:50 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2017-09-03 18:30:00 +02:00
|
|
|
|
|
|
|
|
2017-09-10 20:48:39 +02:00
|
|
|
class RelayForm(flask_wtf.FlaskForm):
|
|
|
|
name = fields.StringField(_('Relayed domain name'), [validators.DataRequired()])
|
|
|
|
smtp = fields.StringField(_('Remote host'))
|
|
|
|
comment = fields.StringField(_('Comment'))
|
2018-08-01 21:23:50 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2017-09-10 20:48:39 +02:00
|
|
|
|
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class UserForm(flask_wtf.FlaskForm):
|
2018-06-10 17:15:36 +02:00
|
|
|
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
2019-01-09 13:03:47 +02:00
|
|
|
pw = fields.PasswordField(_('Password'))
|
2016-10-02 14:37:06 +02:00
|
|
|
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
2022-11-04 14:35:13 +02:00
|
|
|
pwned = fields.HiddenField(label='', default=-1)
|
2021-09-02 22:49:36 +02:00
|
|
|
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9)
|
2016-10-02 14:37:06 +02:00
|
|
|
enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True)
|
|
|
|
enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True)
|
2022-11-20 11:19:28 +02:00
|
|
|
allow_spoofing = fields.BooleanField(_('Allow the user to spoof the sender (send email as anyone)'), default=False)
|
2018-12-30 21:29:41 +02:00
|
|
|
displayed_name = fields.StringField(_('Displayed name'))
|
2016-10-02 14:37:06 +02:00
|
|
|
comment = fields.StringField(_('Comment'))
|
2018-04-15 11:35:37 +02:00
|
|
|
enabled = fields.BooleanField(_('Enabled'), default=True)
|
2023-08-10 12:06:15 +02:00
|
|
|
change_pw_next_login = fields.BooleanField(_('Force password change at next login'), default=True)
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2016-03-19 21:37:48 +02:00
|
|
|
|
|
|
|
|
2017-12-03 13:01:25 +02:00
|
|
|
class UserSignupForm(flask_wtf.FlaskForm):
|
2018-06-10 17:15:36 +02:00
|
|
|
localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
2017-12-03 13:01:25 +02:00
|
|
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
|
|
|
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
2022-11-04 14:35:13 +02:00
|
|
|
pwned = fields.HiddenField(label='', default=-1)
|
2017-12-03 13:01:25 +02:00
|
|
|
submit = fields.SubmitField(_('Sign up'))
|
|
|
|
|
2018-11-07 09:58:49 +02:00
|
|
|
class UserSignupFormCaptcha(UserSignupForm):
|
|
|
|
captcha = flask_wtf.RecaptchaField()
|
2017-12-03 13:01:25 +02:00
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class UserSettingsForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
displayed_name = fields.StringField(_('Displayed name'))
|
|
|
|
spam_enabled = fields.BooleanField(_('Enable spam filter'))
|
2022-03-11 15:58:50 +02:00
|
|
|
spam_mark_as_read = fields.BooleanField(_('Enable marking spam mails as read'))
|
2017-11-05 18:17:51 +02:00
|
|
|
spam_threshold = fields_.IntegerSliderField(_('Spam filter tolerance'))
|
2018-04-11 21:28:36 +02:00
|
|
|
forward_enabled = fields.BooleanField(_('Enable forwarding'))
|
|
|
|
forward_keep = fields.BooleanField(_('Keep a copy of the emails'))
|
2018-12-28 22:03:57 +02:00
|
|
|
forward_destination = fields.StringField(_('Destination'), [validators.Optional(), MultipleEmailAddressesVerify()])
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Save settings'))
|
2016-03-20 12:09:06 +02:00
|
|
|
|
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class UserPasswordForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
|
|
|
pw2 = fields.PasswordField(_('Password check'), [validators.DataRequired()])
|
2022-11-04 14:35:13 +02:00
|
|
|
pwned = fields.HiddenField(label='', default=-1)
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Update password'))
|
2016-03-19 21:37:48 +02:00
|
|
|
|
2023-04-02 16:35:15 +02:00
|
|
|
class UserPasswordChangeForm(flask_wtf.FlaskForm):
|
|
|
|
current_pw = fields.PasswordField(_('Current password'), [validators.DataRequired()])
|
|
|
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
|
|
|
pw2 = fields.PasswordField(_('Password check'), [validators.DataRequired()])
|
|
|
|
pwned = fields.HiddenField(label='', default=-1)
|
|
|
|
submit = fields.SubmitField(_('Update password'))
|
2016-03-19 21:37:48 +02:00
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class UserReplyForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
reply_enabled = fields.BooleanField(_('Enable automatic reply'))
|
|
|
|
reply_subject = fields.StringField(_('Reply subject'))
|
|
|
|
reply_body = fields.StringField(_('Reply body'),
|
|
|
|
widget=widgets.TextArea())
|
2022-11-02 18:52:38 +02:00
|
|
|
reply_startdate = fields.DateField(_('Start of vacation'))
|
|
|
|
reply_enddate = fields.DateField(_('End of vacation'))
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Update'))
|
2016-03-20 12:00:01 +02:00
|
|
|
|
|
|
|
|
2017-10-29 15:49:07 +02:00
|
|
|
class TokenForm(flask_wtf.FlaskForm):
|
2017-10-29 16:39:01 +02:00
|
|
|
displayed_password = fields.StringField(
|
2017-10-29 15:49:07 +02:00
|
|
|
_('Your token (write it down, as it will never be displayed again)')
|
|
|
|
)
|
2017-10-29 16:39:01 +02:00
|
|
|
raw_password = fields.HiddenField([validators.DataRequired()])
|
2017-10-29 15:49:07 +02:00
|
|
|
comment = fields.StringField(_('Comment'))
|
|
|
|
ip = fields.StringField(
|
2023-06-08 13:26:41 +02:00
|
|
|
_('Authorized IP'), [validators.Optional()]
|
2017-10-29 15:49:07 +02:00
|
|
|
)
|
2018-08-01 21:23:50 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2017-10-29 15:49:07 +02:00
|
|
|
|
2023-06-08 13:26:41 +02:00
|
|
|
def validate_ip(form, field):
|
|
|
|
if not field.data:
|
|
|
|
return True
|
|
|
|
try:
|
|
|
|
for candidate in field.data.replace(' ','').split(','):
|
|
|
|
ipaddress.ip_network(candidate, False)
|
|
|
|
except:
|
|
|
|
raise validators.ValidationError('Not a valid list of CIDRs')
|
2017-10-29 15:49:07 +02:00
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class AliasForm(flask_wtf.FlaskForm):
|
2018-12-18 17:06:39 +02:00
|
|
|
localpart = fields.StringField(_('Alias'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
2016-08-15 21:01:53 +02:00
|
|
|
wildcard = fields.BooleanField(
|
2016-10-02 14:37:06 +02:00
|
|
|
_('Use SQL LIKE Syntax (e.g. for catch-all aliases)'))
|
|
|
|
destination = DestinationField(_('Destination'))
|
|
|
|
comment = fields.StringField(_('Comment'))
|
2018-08-01 21:23:50 +02:00
|
|
|
submit = fields.SubmitField(_('Save'))
|
2016-04-24 19:17:40 +02:00
|
|
|
|
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class AdminForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
admin = fields.SelectField(_('Admin email'), choices=[])
|
|
|
|
submit = fields.SubmitField(_('Submit'))
|
2016-04-24 19:17:40 +02:00
|
|
|
|
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class ManagerForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
manager = fields.SelectField(_('Manager email'))
|
|
|
|
submit = fields.SubmitField(_('Submit'))
|
2016-04-28 20:07:38 +02:00
|
|
|
|
|
|
|
|
2016-10-02 09:33:07 +02:00
|
|
|
class FetchForm(flask_wtf.FlaskForm):
|
2016-10-02 14:37:06 +02:00
|
|
|
protocol = fields.SelectField(_('Protocol'), choices=[
|
2016-04-28 20:07:38 +02:00
|
|
|
('imap', 'IMAP'), ('pop3', 'POP3')
|
|
|
|
])
|
2019-01-08 20:29:34 +02:00
|
|
|
host = fields.StringField(_('Hostname or IP'), [validators.DataRequired()])
|
2022-11-13 18:15:50 +02:00
|
|
|
port = fields.IntegerField(_('TCP port'), [validators.DataRequired(), validators.NumberRange(min=0, max=65535)], default=993)
|
|
|
|
tls = fields.BooleanField(_('Enable TLS'), default=True)
|
2019-01-08 20:29:34 +02:00
|
|
|
username = fields.StringField(_('Username'), [validators.DataRequired()])
|
2019-01-09 13:52:05 +02:00
|
|
|
password = fields.PasswordField(_('Password'))
|
2017-02-02 23:45:43 +02:00
|
|
|
keep = fields.BooleanField(_('Keep emails on the server'))
|
2022-11-13 18:15:50 +02:00
|
|
|
scan = fields.BooleanField(_('Rescan emails locally'))
|
|
|
|
folders = fields.StringField(_('Folders to fetch on the server'), [validators.Optional(), MultipleFoldersVerify()], default='INBOX,Junk')
|
2016-10-02 14:37:06 +02:00
|
|
|
submit = fields.SubmitField(_('Submit'))
|
2016-11-10 11:48:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
class AnnouncementForm(flask_wtf.FlaskForm):
|
|
|
|
announcement_subject = fields.StringField(_('Announcement subject'),
|
|
|
|
[validators.DataRequired()])
|
|
|
|
announcement_body = fields.StringField(_('Announcement body'),
|
|
|
|
[validators.DataRequired()], widget=widgets.TextArea())
|
|
|
|
submit = fields.SubmitField(_('Send'))
|