2697: Make the login page guess where to redirect r=mergify[bot] a=nextgens

## What type of PR?

enhancement

## What does this PR do?

Make the login page guess where to redirect.

If you access /admin/ and get redirected to /sso/login, it's only fair that it redirects you back to /admin afterwards.

This is also changing the interface for external proxy authentication, making it simpler to configure.

### Related issue(s)
- close #2692
- #1972

## 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.

- [ ] 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: Dimitri Huisman <diman@huisman.xyz>
Co-authored-by: Dimitri Huisman <52963853+Diman0@users.noreply.github.com>
main
bors[bot] 2 years ago committed by GitHub
commit 86ad4c93a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -86,6 +86,7 @@ DEFAULT_CONFIG = {
'PROXY_AUTH_WHITELIST': '', 'PROXY_AUTH_WHITELIST': '',
'PROXY_AUTH_HEADER': 'X-Auth-Email', 'PROXY_AUTH_HEADER': 'X-Auth-Email',
'PROXY_AUTH_CREATE': False, 'PROXY_AUTH_CREATE': False,
'PROXY_AUTH_LOGOUT_URL': None,
'SUBNET': '192.168.203.0/24', 'SUBNET': '192.168.203.0/24',
'SUBNET6': None, 'SUBNET6': None,
} }

@ -8,15 +8,24 @@ import flask
import flask_login import flask_login
import secrets import secrets
import ipaddress import ipaddress
from urllib.parse import urlparse, urljoin
from werkzeug.urls import url_unquote
@sso.route('/login', methods=['GET', 'POST']) @sso.route('/login', methods=['GET', 'POST'])
def login(): def login():
if flask.request.headers.get(app.config['PROXY_AUTH_HEADER']) and not 'noproxyauth' in flask.request.url:
return _proxy()
client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr) client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
form = forms.LoginForm() form = forms.LoginForm()
form.submitAdmin.label.text = form.submitAdmin.label.text + ' Admin'
form.submitWebmail.label.text = form.submitWebmail.label.text + ' Webmail'
fields = [] fields = []
if 'url' in flask.request.args and not 'homepage' in flask.request.url:
fields.append(form.submitAdmin)
else:
form.submitAdmin.label.text = form.submitAdmin.label.text + ' Admin'
form.submitWebmail.label.text = form.submitWebmail.label.text + ' Webmail'
if str(app.config["WEBMAIL"]).upper() != "NONE": if str(app.config["WEBMAIL"]).upper() != "NONE":
fields.append(form.submitWebmail) fields.append(form.submitWebmail)
if str(app.config["ADMIN"]).upper() != "FALSE": if str(app.config["ADMIN"]).upper() != "FALSE":
@ -24,6 +33,9 @@ def login():
fields = [fields] fields = [fields]
if form.validate_on_submit(): if form.validate_on_submit():
if destination := _has_usable_redirect():
pass
else:
if form.submitAdmin.data: if form.submitAdmin.data:
destination = app.config['WEB_ADMIN'] destination = app.config['WEB_ADMIN']
elif form.submitWebmail.data: elif form.submitWebmail.data:
@ -57,14 +69,29 @@ def login():
def logout(): def logout():
flask_login.logout_user() flask_login.logout_user()
flask.session.destroy() flask.session.destroy()
response = flask.redirect(flask.url_for('.login')) response = flask.redirect(app.config['PROXY_AUTH_LOGOUT_URL'] or flask.url_for('.login'))
for cookie in ['roundcube_sessauth', 'roundcube_sessid', 'smsession']: for cookie in ['roundcube_sessauth', 'roundcube_sessid', 'smsession']:
response.set_cookie(cookie, 'empty', expires=0) response.set_cookie(cookie, 'empty', expires=0)
return response return response
@sso.route('/proxy', methods=['GET']) """
@sso.route('/proxy/<target>', methods=['GET']) Redirect to the url passed in parameter if any; Ensure that this is not an open-redirect too...
def proxy(target='webmail'): https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
"""
def _has_usable_redirect():
if 'homepage' in flask.request.url:
return None
if url := flask.request.args.get('url'):
url = url_unquote(url)
target = urlparse(urljoin(flask.request.url, url))
if target.netloc == urlparse(flask.request.url).netloc:
return target.geturl()
return None
"""
https://mailu.io/master/configuration.html#header-authentication-using-an-external-proxy
"""
def _proxy():
ip = ipaddress.ip_address(flask.request.remote_addr) ip = ipaddress.ip_address(flask.request.remote_addr)
if not any(ip in cidr for cidr in app.config['PROXY_AUTH_WHITELIST']): if not any(ip in cidr for cidr in app.config['PROXY_AUTH_WHITELIST']):
return flask.abort(500, '%s is not on PROXY_AUTH_WHITELIST' % flask.request.remote_addr) return flask.abort(500, '%s is not on PROXY_AUTH_WHITELIST' % flask.request.remote_addr)
@ -73,11 +100,13 @@ def proxy(target='webmail'):
if not email: if not email:
return flask.abort(500, 'No %s header' % app.config['PROXY_AUTH_HEADER']) return flask.abort(500, 'No %s header' % app.config['PROXY_AUTH_HEADER'])
url = _has_usable_redirect() or app.config['WEB_ADMIN']
user = models.User.get(email) user = models.User.get(email)
if user: if user:
flask.session.regenerate() flask.session.regenerate()
flask_login.login_user(user) flask_login.login_user(user)
return flask.redirect(app.config['WEB_ADMIN'] if target=='admin' else app.config['WEB_WEBMAIL']) return flask.redirect(url)
if not app.config['PROXY_AUTH_CREATE']: if not app.config['PROXY_AUTH_CREATE']:
return flask.abort(500, 'You don\'t exist. Go away! (%s)' % email) return flask.abort(500, 'You don\'t exist. Go away! (%s)' % email)
@ -100,4 +129,4 @@ def proxy(target='webmail'):
flask_login.login_user(user) flask_login.login_user(user)
user.send_welcome() user.send_welcome()
flask.current_app.logger.info(f'Login succeeded by proxy created user: {user} from {client_ip} through {flask.request.remote_addr}.') flask.current_app.logger.info(f'Login succeeded by proxy created user: {user} from {client_ip} through {flask.request.remote_addr}.')
return flask.redirect(app.config['WEB_ADMIN'] if target=='admin' else app.config['WEB_WEBMAIL']) return flask.redirect(url)

@ -42,7 +42,7 @@ login.login_view = "sso.login"
def handle_needs_login(): def handle_needs_login():
""" redirect unauthorized requests to login page """ """ redirect unauthorized requests to login page """
return flask.redirect( return flask.redirect(
flask.url_for('sso.login') flask.url_for('sso.login', url=flask.request.url)
) )
# DNS stub configured to do DNSSEC enabled queries # DNS stub configured to do DNSSEC enabled queries

@ -173,11 +173,15 @@ http {
} }
{% endif %} {% endif %}
location @sso_login {
return 302 /sso/login?url=$request_uri;
}
{% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %} {% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %}
location / { location / {
expires $expires; expires $expires;
{% if WEBROOT_REDIRECT %} {% if WEBROOT_REDIRECT %}
try_files $uri {{ WEBROOT_REDIRECT }}; try_files $uri {{ WEBROOT_REDIRECT }}?homepage;
{% else %} {% else %}
try_files $uri =404; try_files $uri =404;
{% endif %} {% endif %}
@ -192,7 +196,7 @@ http {
{% endif %} {% endif %}
include /etc/nginx/proxy.conf; include /etc/nginx/proxy.conf;
auth_request /internal/auth/user; auth_request /internal/auth/user;
error_page 403 @webmail_login; error_page 403 @sso_login;
proxy_pass http://$webmail; proxy_pass http://$webmail;
} }
@ -211,13 +215,9 @@ http {
auth_request_set $token $upstream_http_x_user_token; auth_request_set $token $upstream_http_x_user_token;
proxy_set_header X-Remote-User $user; proxy_set_header X-Remote-User $user;
proxy_set_header X-Remote-User-Token $token; proxy_set_header X-Remote-User-Token $token;
error_page 403 @webmail_login; error_page 403 @sso_login;
proxy_pass http://$webmail; proxy_pass http://$webmail;
} }
location @webmail_login {
return 302 /sso/login;
}
{% endif %} {% endif %}
{% if ADMIN %} {% if ADMIN %}
location {{ WEB_ADMIN }} { location {{ WEB_ADMIN }} {
@ -232,6 +232,7 @@ http {
proxy_set_header X-Real-IP ""; proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-For ""; proxy_set_header X-Forwarded-For "";
proxy_pass http://$antispam; proxy_pass http://$antispam;
error_page 403 @sso_login;
} }
{% endif %} {% endif %}

@ -375,6 +375,15 @@ The ``PROXY_AUTH_WHITELIST`` (default: unset/disabled) option allows you to conf
Use ``PROXY_AUTH_HEADER`` (default: 'X-Auth-Email') to customize which HTTP header the email address of the user to authenticate as should be and ``PROXY_AUTH_CREATE`` (default: False) to control whether non-existing accounts should be auto-created. Please note that Mailu doesn't currently support creating new users for non-existing domains; you do need to create all the domains that may be used manually. Use ``PROXY_AUTH_HEADER`` (default: 'X-Auth-Email') to customize which HTTP header the email address of the user to authenticate as should be and ``PROXY_AUTH_CREATE`` (default: False) to control whether non-existing accounts should be auto-created. Please note that Mailu doesn't currently support creating new users for non-existing domains; you do need to create all the domains that may be used manually.
Once configured, any request to /sso/proxy will be redirected to the webmail and /sso/proxy/admin to the admin panel. Please check issue `1972` for more details. Once configured, any request to /sso/login with the correct headers will be authenticated unless the "noproxyauth" parameter is passed, in which case the "standard" login form will be displayed. Please check issues `1972`_ and `2692`_ for more details.
Requests to:
- "/sso/login" results the user being redirected to the web administration interface after authentication.
- "/admin" (``WEB_ADMIN=/admin``) results the user being redirected to the web administration interface after authentication.
- "/webmail" (``WEB_WEBMAIL=/webmail``) results the user being redirected to the web administration interface after authentication.
Use ``PROXY_AUTH_LOGOUT_URL`` (default: unset) to redirect users to a specific URL after they have been logged out.
.. _`1972`: https://github.com/Mailu/Mailu/issues/1972 .. _`1972`: https://github.com/Mailu/Mailu/issues/1972
.. _`2692`: https://github.com/Mailu/Mailu/issues/2692

@ -0,0 +1,7 @@
Make the login page "guess" where the user wants to land.
This means that requests for /admin result in a login page that always redirects to admin.
Requests for /webmail results in a login page that redirects the user being logged in to webmail.
You can still access / (https://mydomain/) or /sso/login, to access the login page with both login buttons.
Introduce AUTH_PROXY_LOGOUT_URL to redirect users to a specific URL after they have been logged-out
Retire /sso/proxy and merge it in /sso/login
Loading…
Cancel
Save