Repository: Mailu/Mailu

Issue:
PROXY_AUTH_WHITELIST is validating address in REAL_IP_HEADER instead of proxy IP address
<!--

Thank you for opening an issue with Mailu. Please understand that issues are meant for bugs and enhancement-requests.
For **user-support questions**, reach out to us  on [matrix](https://matrix.to/#/#mailu:tedomum.net).

To be able to help you best, we need some more information.

Before you open your issue
- Check if no issue or pull-request for this already exists.
- Check [documentation](https://mailu.io/master/) and [FAQ](https://mailu.io/master/faq.html). (Tip, use the search function on the documentation page)
- You understand `Mailu` is made by volunteers in their **free time** — be concise, civil and accept that delays can occur.
- The title of the issue should be short and simple. It should contain specific terms related to the actual issue. Be specific while writing the title.

Please put your text outside of the comment blocks to be visible. You can use the button "Preview" above to check.

-->

## Environment & Version

### Environment

- [x] docker compose
- [ ] kubernetes
- [ ] docker swarm

### Version

- Version: `master`

<!--
To find your version, get the image name of a mailu container and read  the version from the tag (example for version 1.7).

$> docker ps -a | grep mailu
140b09d4b09c    mailu/roundcube:1.7    "docker-php-entrypoi…"    2 weeks ago    Up 2 days (healthy)    80/tcp
$> grep MAILU_VERSION docker-compose.yml mailu.env
-->

## Description
<!--
Further explain the bug in a few words. It should be clear what the unexpected behaviour is.  Share it in an easy-to-understand language.
-->
When using a proxy for authentication, we have the option to set proxy IPs/CIDRs to trust for authentication with `PROXY_AUTH_WHITELIST` setting. Whenever we are using a proxy for authentication we are also supposed to set `REAL_IP_HEADER` and `REAL_IP_FROM` so mailu can determine where the request came from when the proxy intercepted it. However, when `REAL_IP_HEADER` and `REAL_IP_FROM` are set, the mailu frontend is validating the IP address in the header from the proxy, rather than the proxy IP itself, for `PROXY_AUTH_WHITELIST`.

I would guess that this has something to do with how nginx is configured for the frontend, as this only happens when BOTH `REAL_IP_FROM` and `PROXY_AUTH_WHITELIST` are configured. If only `PROXY_AUTH_WHITELIST` is set, then the user is permitted to login and access webmail (at increased security risk of IP spoofing).

## Replication Steps
<!--
Steps for replicating your issue
-->
1. Deploy mailu with a reverse proxy to perform authentication. I am using traefik2 for proxy and authentik for IdP.
2. Configure a user in IdP that will authenticate to mailu. Optionally enable PROXY_AUTH_CREATE in mailu config.
3. Set mailu configuration `PROXY_AUTH_WHITELIST` to use the proxy's IP address. Leave `REAL_IP_HEADER` and `REAL_IP_FROM` unset. Restart/redeploy mailu to pick up the new configuration.
4. Confirm authentication through proxy works by navigating to mailu through the proxy, using a different host / source IP address than the one used by the proxy. A successful login will load Webmail.
5. Set mailu configuration `REAL_IP_FROM` to use the proxy's IP address. Set `REAL_IP_HEADER` to a header that the proxy will set, such as `X-Real-IP`. Restart/redeploy mailu to pick up the new configuration.
6. Navigate to mailu through the proxy, using a different host / source IP address than the one used by the proxy. Complete the login if prompted.

## Observed behaviour
<!--
Explain or paste the result you received.
-->
Mailu returns with HTTP 500 error, where `X.X.X.X` is the IP address of the host running the browser, found from the header:
```
Internal Server Error
X.X.X.X is not on PROXY_AUTH_WHITELIST
```

## Expected behaviour
<!--
Explain what results you expected - be as specific as possible.
Just saying "it doesn’t work as expected" is not useful. It's also helpful to describe what you actually experienced.
-->
User should be permitted to login without encountering this error.

## Logs
<!--
Often it is very useful to include log fragments of the involved component.
You can get the logs via `docker logs <container name> --tail 1000`.
For example for the admin container: `docker logs mailu_admin_1 --tail 1000`
or using docker compose `docker compose -f /mailu/docker-compose.yml logs --tail 1000 admin`

If you can find the relevant section, please share only the parts that seem relevant. If you have any logs, please enclose them in code tags, like so:

```
Your logs here!
```
-->
Logs from the frontend:
```
<host IP> - - [17/Mar/2023:19:50:25 -0500] "GET /webmail HTTP/1.1" 301 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
<host IP> - - [17/Mar/2023:19:50:25 -0500] "GET /webmail/ HTTP/1.1" 302 138 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
<host IP> - - [17/Mar/2023:19:50:25 -0500] "GET /sso/login?url=/webmail/ HTTP/1.1" 500 155 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
```


Discussion:
It looks like here is the problem
https://github.com/Mailu/Mailu/blob/86ad4c93a901233226a9492279d2d2cc677344c9/core/admin/mailu/sso/views/base.py#L95

Resolving patch:
diff --git a/core/admin/mailu/sso/views/base.py b/core/admin/mailu/sso/views/base.py
--- a/core/admin/mailu/sso/views/base.py
+++ b/core/admin/mailu/sso/views/base.py
@@ -78,8 +78,8 @@ def logout():
 Redirect to the url passed in parameter if any; Ensure that this is not an open-redirect too...
 https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
 """
-def _has_usable_redirect():
-    if 'homepage' in flask.request.url:
+def _has_usable_redirect(is_proxied=False):
+    if 'homepage' in flask.request.url and not is_proxied:
         return None
     if url := flask.request.args.get('url'):
         url = url_unquote(url)
@@ -92,15 +92,16 @@ def _has_usable_redirect():
 https://mailu.io/master/configuration.html#header-authentication-using-an-external-proxy
 """
 def _proxy():
-    ip = ipaddress.ip_address(flask.request.remote_addr)
+    proxy_ip = flask.request.headers.get('X-Forwarded-By', flask.request.remote_addr)
+    ip = ipaddress.ip_address(proxy_ip)
     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' % proxy_ip)
 
     email = flask.request.headers.get(app.config['PROXY_AUTH_HEADER'])
     if not email:
         return flask.abort(500, 'No %s header' % app.config['PROXY_AUTH_HEADER'])
 
-    url = _has_usable_redirect() or app.config['WEB_ADMIN']
+    url = _has_usable_redirect(True) or app.config['WEB_ADMIN']
 
     user = models.User.get(email)
     if user:


Test patch: