Repository: Mailu/Mailu

Issue:
Accidentally specify a complete mail in the alias field ⇒ oops
When accidentally writing a complete email-address, like `foo@bar.baz` into the mail-domain⇒alias⇒create alias⇒alias field, `admin` explodes like this, and the user is presented with a `500`:

```
admin_1_ef8e5e126497 | [2018-12-17 20:08:12,886] ERROR in app: Exception on /ui/alias/create/ram.en.it [POST]
admin_1_ef8e5e126497 | Traceback (most recent call last):
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1193, in _execute_context
admin_1_ef8e5e126497 |     context)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 509, in do_execute
admin_1_ef8e5e126497 |     cursor.execute(statement, parameters)
admin_1_ef8e5e126497 | sqlite3.IntegrityError: NOT NULL constraint failed: alias.email
admin_1_ef8e5e126497 | 
admin_1_ef8e5e126497 | The above exception was the direct cause of the following exception:
admin_1_ef8e5e126497 | 
admin_1_ef8e5e126497 | Traceback (most recent call last):
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
admin_1_ef8e5e126497 |     response = self.full_dispatch_request()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
admin_1_ef8e5e126497 |     rv = self.handle_user_exception(e)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
admin_1_ef8e5e126497 |     reraise(exc_type, exc_value, tb)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
admin_1_ef8e5e126497 |     raise value
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
admin_1_ef8e5e126497 |     rv = self.dispatch_request()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
admin_1_ef8e5e126497 |     return self.view_functions[rule.endpoint](**req.view_args)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/flask_login/utils.py", line 261, in decorated_view
admin_1_ef8e5e126497 |     return func(*args, **kwargs)
admin_1_ef8e5e126497 |   File "/app/mailu/ui/access.py", line 28, in wrapper
admin_1_ef8e5e126497 |     return callback(function, args, kwargs, dargs, dkwargs)
admin_1_ef8e5e126497 |   File "/app/mailu/ui/access.py", line 19, in callback
admin_1_ef8e5e126497 |     return function(*args, **kwargs)
admin_1_ef8e5e126497 |   File "/app/mailu/ui/views/aliases.py", line 31, in alias_create
admin_1_ef8e5e126497 |     models.db.session.commit()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/scoping.py", line 153, in do
admin_1_ef8e5e126497 |     return getattr(self.registry(), name)(*args, **kwargs)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 943, in commit
admin_1_ef8e5e126497 |     self.transaction.commit()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 467, in commit
admin_1_ef8e5e126497 |     self._prepare_impl()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 447, in _prepare_impl
admin_1_ef8e5e126497 |     self.session.flush()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2254, in flush
admin_1_ef8e5e126497 |     self._flush(objects)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2381, in _flush
admin_1_ef8e5e126497 |     transaction.rollback(_capture_exception=True)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
admin_1_ef8e5e126497 |     compat.reraise(exc_type, exc_value, exc_tb)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 249, in reraise
admin_1_ef8e5e126497 |     raise value
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 2345, in _flush
admin_1_ef8e5e126497 |     flush_context.execute()
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py", line 395, in execute
admin_1_ef8e5e126497 |     rec.execute(self)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/unitofwork.py", line 560, in execute
admin_1_ef8e5e126497 |     uow
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py", line 181, in save_obj
admin_1_ef8e5e126497 |     mapper, table, insert)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/orm/persistence.py", line 866, in _emit_insert_statements
admin_1_ef8e5e126497 |     execute(statement, params)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 948, in execute
admin_1_ef8e5e126497 |     return meth(self, multiparams, params)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/sql/elements.py", line 269, in _execute_on_connection
admin_1_ef8e5e126497 |     return connection._execute_clauseelement(self, multiparams, params)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1060, in _execute_clauseelement
admin_1_ef8e5e126497 |     compiled_sql, distilled_params
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1200, in _execute_context
admin_1_ef8e5e126497 |     context)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1413, in _handle_dbapi_exception
admin_1_ef8e5e126497 |     exc_info
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 265, in raise_from_cause
admin_1_ef8e5e126497 |     reraise(type(exception), exception, tb=exc_tb, cause=cause)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 248, in reraise
admin_1_ef8e5e126497 |     raise value.with_traceback(tb)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1193, in _execute_context
admin_1_ef8e5e126497 |     context)
admin_1_ef8e5e126497 |   File "/usr/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 509, in do_execute
admin_1_ef8e5e126497 |     cursor.execute(statement, parameters)
admin_1_ef8e5e126497 | sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: alias.email [SQL: 'INSERT INTO alias (created_at, updated_at, comment, localpart, wildcard, destination, domain_name, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'] [parameters: ('2018-12-17', None, '', 'root@ram.en.it', 0, 'testuser@ram.en.it', 'ram.en.it', None)] (Background on this error at: http://sqlalche.me/e/gkpj)
admin_1_ef8e5e126497 | 172.31.0.13 - - [17/Dec/2018:20:08:12 +0000] "POST /ui/alias/create/ram.en.it HTTP/1.0" 500 291 "https://mail.ram.en.it/admin/ui/alias/create/ram.en.it" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"
```

This is not bad, as no invalid/broken entry seems to be created, but i think it’s an easy validation check so the user could be presented with a nicer and informative error ….

Thanks everyone ^_^.


Discussion:

Resolving patch:
diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py
--- a/core/admin/mailu/ui/forms.py
+++ b/core/admin/mailu/ui/forms.py
@@ -136,7 +136,7 @@ class TokenForm(flask_wtf.FlaskForm):
 
 
 class AliasForm(flask_wtf.FlaskForm):
-    localpart = fields.StringField(_('Alias'), [validators.DataRequired()])
+    localpart = fields.StringField(_('Alias'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
     wildcard = fields.BooleanField(
         _('Use SQL LIKE Syntax (e.g. for catch-all aliases)'))
     destination = DestinationField(_('Destination'))


Test patch: