We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
fix keyError WEBMAIL_ADDRESS
## What type of PR?
bugfix WEBMAIL_ADDRESS not initialized in admin/mailu/configuration.py, leading to lot of errors in log.

## What does this PR do?
Initialize 'WEBMAIL_ADDRESS' to None in the admin configuration

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

## Prerequisites
None

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/admin/mailu/limiter.py`
Content:
```
1 from mailu import utils
2 from flask import current_app as app
3 import base64
4 import limits
5 import limits.storage
6 import limits.strategies
7 
8 import hmac
9 import secrets
10 
11 class LimitWrapper(object):
12     """ Wraps a limit by providing the storage, item and identifiers
13     """
14 
15     def __init__(self, limiter, limit, *identifiers):
16         self.limiter = limiter
17         self.limit = limit
18         self.base_identifiers = identifiers
19 
20     def test(self, *args):
21         return self.limiter.test(self.limit, *(self.base_identifiers + args))
22 
23     def hit(self, *args):
24         return self.limiter.hit(self.limit, *(self.base_identifiers + args))
25 
26     def get_window_stats(self, *args):
27         return self.limiter.get_window_stats(self.limit, *(self.base_identifiers + args))
28 
29 
30 class LimitWraperFactory(object):
31     """ Global limiter, to be used as a factory
32     """
33 
34     def init_app(self, app):
35         self.storage = limits.storage.storage_from_string(app.config["RATELIMIT_STORAGE_URL"])
36         self.limiter = limits.strategies.MovingWindowRateLimiter(self.storage)
37 
38     def get_limiter(self, limit, *args):
39         return LimitWrapper(self.limiter, limits.parse(limit), *args)
40 
41     def is_subject_to_rate_limits(self, ip):
42         return False if utils.is_exempt_from_ratelimits(ip) else not (self.storage.get(f'exempt-{ip}') > 0)
43 
44     def exempt_ip_from_ratelimits(self, ip):
45         self.storage.incr(f'exempt-{ip}', app.config["AUTH_RATELIMIT_EXEMPTION_LENGTH"], True)
46 
47     def should_rate_limit_ip(self, ip):
48         limiter = self.get_limiter(app.config["AUTH_RATELIMIT_IP"], 'auth-ip')
49         client_network = utils.extract_network_from_ip(ip)
50         is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(client_network)
51         if is_rate_limited:
52             app.logger.warn(f'Authentication attempt from {ip} has been rate-limited.')
53         return is_rate_limited
54 
55     def rate_limit_ip(self, ip):
56         if ip != app.config['WEBMAIL_ADDRESS']:
57             limiter = self.get_limiter(app.config["AUTH_RATELIMIT_IP"], 'auth-ip')
58             client_network = utils.extract_network_from_ip(ip)
59             if self.is_subject_to_rate_limits(ip):
60                 limiter.hit(client_network)
61 
62     def should_rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):
63         limiter = self.get_limiter(app.config["AUTH_RATELIMIT_USER"], 'auth-user')
64         is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(device_cookie if device_cookie_name == username else username)
65         if is_rate_limited:
66             app.logger.warn(f'Authentication attempt from {ip} for {username} has been rate-limited.')
67         return is_rate_limited
68 
69     def rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):
70         limiter = self.get_limiter(app.config["AUTH_RATELIMIT_USER"], 'auth-user')
71         if self.is_subject_to_rate_limits(ip):
72             limiter.hit(device_cookie if device_cookie_name == username else username)
73 
74     """ Device cookies as described on:
75     https://owasp.org/www-community/Slow_Down_Online_Guessing_Attacks_with_Device_Cookies
76     """
77     def parse_device_cookie(self, cookie):
78         try:
79             login, nonce, _ = cookie.split('$')
80             if hmac.compare_digest(cookie, self.device_cookie(login, nonce)):
81                 return nonce, login
82         except:
83             pass
84         return None, None
85 
86     """ Device cookies don't require strong crypto:
87         72bits of nonce, 96bits of signature is more than enough
88         and these values avoid padding in most cases
89     """
90     def device_cookie(self, username, nonce=None):
91         if not nonce:
92             nonce = secrets.token_urlsafe(9)
93         sig = str(base64.urlsafe_b64encode(hmac.new(app.device_cookie_key, bytearray(f'device_cookie|{username}|{nonce}', 'utf-8'), 'sha256').digest()[20:]), 'utf-8')
94         return f'{username}${nonce}${sig}'
95
```

Path: `core/admin/mailu/internal/views/auth.py`
Content:
```
1 from mailu import models, utils
2 from mailu.internal import internal, nginx
3 from flask import current_app as app
4 
5 import flask
6 import flask_login
7 import base64
8 
9 @internal.route("/auth/email")
10 def nginx_authentication():
11     """ Main authentication endpoint for Nginx email server
12     """
13     client_ip = flask.request.headers["Client-Ip"]
14     headers = flask.request.headers
15     if headers["Auth-Port"] == '25' and headers['Auth-Method'] == 'plain':
16         response = flask.Response()
17         response.headers['Auth-Status'] = 'AUTH not supported'
18         response.headers['Auth-Error-Code'] = '502 5.5.1'
19         utils.limiter.rate_limit_ip(client_ip)
20         return response
21     if utils.limiter.should_rate_limit_ip(client_ip):
22         status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')
23         response = flask.Response()
24         response.headers['Auth-Status'] = status
25         response.headers['Auth-Error-Code'] = code
26         if int(flask.request.headers['Auth-Login-Attempt']) < 10:
27             response.headers['Auth-Wait'] = '3'
28         return response
29     headers = nginx.handle_authentication(flask.request.headers)
30     response = flask.Response()
31     for key, value in headers.items():
32         response.headers[key] = str(value)
33     is_valid_user = False
34     if response.headers.get("Auth-User-Exists"):
35         username = response.headers["Auth-User"]
36         if utils.limiter.should_rate_limit_user(username, client_ip):
37             # FIXME could be done before handle_authentication()
38             status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')
39             response = flask.Response()
40             response.headers['Auth-Status'] = status
41             response.headers['Auth-Error-Code'] = code
42             if int(flask.request.headers['Auth-Login-Attempt']) < 10:
43                 response.headers['Auth-Wait'] = '3'
44             return response
45         is_valid_user = True
46     if headers.get("Auth-Status") == "OK":
47         utils.limiter.exempt_ip_from_ratelimits(client_ip)
48     elif is_valid_user:
49         utils.limiter.rate_limit_user(username, client_ip)
50     else:
51         utils.limiter.rate_limit_ip(client_ip)
52     return response
53 
54 @internal.route("/auth/admin")
55 def admin_authentication():
56     """ Fails if the user is not an authenticated admin.
57     """
58     if (not flask_login.current_user.is_anonymous
59         and flask_login.current_user.global_admin
60         and flask_login.current_user.enabled):
61         return ""
62     return flask.abort(403)
63 
64 @internal.route("/auth/user")
65 def user_authentication():
66     """ Fails if the user is not authenticated.
67     """
68     if (not flask_login.current_user.is_anonymous
69         and flask_login.current_user.enabled):
70         response = flask.Response()
71         email = flask_login.current_user.get_id()
72         response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, email, "")
73         response.headers["X-User-Token"] = utils.gen_temp_token(email, flask.session)
74         return response
75     return flask.abort(403)
76 
77 
78 @internal.route("/auth/basic")
79 def basic_authentication():
80     """ Tries to authenticate using the Authorization header.
81     """
82     client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
83     if utils.limiter.should_rate_limit_ip(client_ip):
84         response = flask.Response(status=401)
85         response.headers["WWW-Authenticate"] = 'Basic realm="Authentication rate limit from one source exceeded"'
86         response.headers['Retry-After'] = '60'
87         return response
88     authorization = flask.request.headers.get("Authorization")
89     if authorization and authorization.startswith("Basic "):
90         encoded = authorization.replace("Basic ", "")
91         user_email, password = base64.b64decode(encoded).split(b":", 1)
92         user_email = user_email.decode("utf8")
93         if utils.limiter.should_rate_limit_user(user_email, client_ip):
94             response = flask.Response(status=401)
95             response.headers["WWW-Authenticate"] = 'Basic realm="Authentication rate limit for this username exceeded"'
96             response.headers['Retry-After'] = '60'
97             return response
98         user = models.User.query.get(user_email)
99         if user and nginx.check_credentials(user, password.decode('utf-8'), client_ip, "web"):
100             response = flask.Response()
101             response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, user.email, "")
102             utils.limiter.exempt_ip_from_ratelimits(client_ip)
103             return response
104         utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip)
105     response = flask.Response(status=401)
106     response.headers["WWW-Authenticate"] = 'Basic realm="Login Required"'
107     return response
108
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py
--- a/core/admin/mailu/internal/views/auth.py
+++ b/core/admin/mailu/internal/views/auth.py
@@ -31,6 +31,7 @@
     for key, value in headers.items():
         response.headers[key] = str(value)
     is_valid_user = False
+    is_from_webmail = headers['Auth-Port'] in ['10143', '10025']
     if response.headers.get("Auth-User-Exists"):
         username = response.headers["Auth-User"]
         if utils.limiter.should_rate_limit_user(username, client_ip):
@@ -47,7 +48,7 @@
         utils.limiter.exempt_ip_from_ratelimits(client_ip)
     elif is_valid_user:
         utils.limiter.rate_limit_user(username, client_ip)
-    else:
+    elif not is_from_webmail:
         utils.limiter.rate_limit_ip(client_ip)
     return response
 
diff --git a/core/admin/mailu/limiter.py b/core/admin/mailu/limiter.py
--- a/core/admin/mailu/limiter.py
+++ b/core/admin/mailu/limiter.py
@@ -53,11 +53,10 @@
         return is_rate_limited
 
     def rate_limit_ip(self, ip):
-        if ip != app.config['WEBMAIL_ADDRESS']:
-            limiter = self.get_limiter(app.config["AUTH_RATELIMIT_IP"], 'auth-ip')
-            client_network = utils.extract_network_from_ip(ip)
-            if self.is_subject_to_rate_limits(ip):
-                limiter.hit(client_network)
+        limiter = self.get_limiter(app.config["AUTH_RATELIMIT_IP"], 'auth-ip')
+        client_network = utils.extract_network_from_ip(ip)
+        if self.is_subject_to_rate_limits(ip):
+            limiter.hit(client_network)
 
     def should_rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):
         limiter = self.get_limiter(app.config["AUTH_RATELIMIT_USER"], 'auth-user')

{"golden_diff": "diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py\n--- a/core/admin/mailu/internal/views/auth.py\n+++ b/core/admin/mailu/internal/views/auth.py\n@@ -31,6 +31,7 @@\n     for key, value in headers.items():\n         response.headers[key] = str(value)\n     is_valid_user = False\n+    is_from_webmail = headers['Auth-Port'] in ['10143', '10025']\n     if response.headers.get(\"Auth-User-Exists\"):\n         username = response.headers[\"Auth-User\"]\n         if utils.limiter.should_rate_limit_user(username, client_ip):\n@@ -47,7 +48,7 @@\n         utils.limiter.exempt_ip_from_ratelimits(client_ip)\n     elif is_valid_user:\n         utils.limiter.rate_limit_user(username, client_ip)\n-    else:\n+    elif not is_from_webmail:\n         utils.limiter.rate_limit_ip(client_ip)\n     return response\n \ndiff --git a/core/admin/mailu/limiter.py b/core/admin/mailu/limiter.py\n--- a/core/admin/mailu/limiter.py\n+++ b/core/admin/mailu/limiter.py\n@@ -53,11 +53,10 @@\n         return is_rate_limited\n \n     def rate_limit_ip(self, ip):\n-        if ip != app.config['WEBMAIL_ADDRESS']:\n-            limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n-            client_network = utils.extract_network_from_ip(ip)\n-            if self.is_subject_to_rate_limits(ip):\n-                limiter.hit(client_network)\n+        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n+        client_network = utils.extract_network_from_ip(ip)\n+        if self.is_subject_to_rate_limits(ip):\n+            limiter.hit(client_network)\n \n     def should_rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):\n         limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_USER\"], 'auth-user')\n", "issue": "fix keyError WEBMAIL_ADDRESS\n## What type of PR?\r\nbugfix WEBMAIL_ADDRESS not initialized in admin/mailu/configuration.py, leading to lot of errors in log.\r\n\r\n## What does this PR do?\r\nInitialize 'WEBMAIL_ADDRESS' to None in the admin configuration\r\n\r\n### Related issue(s)\r\n- closes #2125\r\n\r\n## Prerequisites\r\nNone\n", "before_files": [{"content": "from mailu import utils\nfrom flask import current_app as app\nimport base64\nimport limits\nimport limits.storage\nimport limits.strategies\n\nimport hmac\nimport secrets\n\nclass LimitWrapper(object):\n    \"\"\" Wraps a limit by providing the storage, item and identifiers\n    \"\"\"\n\n    def __init__(self, limiter, limit, *identifiers):\n        self.limiter = limiter\n        self.limit = limit\n        self.base_identifiers = identifiers\n\n    def test(self, *args):\n        return self.limiter.test(self.limit, *(self.base_identifiers + args))\n\n    def hit(self, *args):\n        return self.limiter.hit(self.limit, *(self.base_identifiers + args))\n\n    def get_window_stats(self, *args):\n        return self.limiter.get_window_stats(self.limit, *(self.base_identifiers + args))\n\n\nclass LimitWraperFactory(object):\n    \"\"\" Global limiter, to be used as a factory\n    \"\"\"\n\n    def init_app(self, app):\n        self.storage = limits.storage.storage_from_string(app.config[\"RATELIMIT_STORAGE_URL\"])\n        self.limiter = limits.strategies.MovingWindowRateLimiter(self.storage)\n\n    def get_limiter(self, limit, *args):\n        return LimitWrapper(self.limiter, limits.parse(limit), *args)\n\n    def is_subject_to_rate_limits(self, ip):\n        return False if utils.is_exempt_from_ratelimits(ip) else not (self.storage.get(f'exempt-{ip}') > 0)\n\n    def exempt_ip_from_ratelimits(self, ip):\n        self.storage.incr(f'exempt-{ip}', app.config[\"AUTH_RATELIMIT_EXEMPTION_LENGTH\"], True)\n\n    def should_rate_limit_ip(self, ip):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n        client_network = utils.extract_network_from_ip(ip)\n        is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(client_network)\n        if is_rate_limited:\n            app.logger.warn(f'Authentication attempt from {ip} has been rate-limited.')\n        return is_rate_limited\n\n    def rate_limit_ip(self, ip):\n        if ip != app.config['WEBMAIL_ADDRESS']:\n            limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n            client_network = utils.extract_network_from_ip(ip)\n            if self.is_subject_to_rate_limits(ip):\n                limiter.hit(client_network)\n\n    def should_rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_USER\"], 'auth-user')\n        is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(device_cookie if device_cookie_name == username else username)\n        if is_rate_limited:\n            app.logger.warn(f'Authentication attempt from {ip} for {username} has been rate-limited.')\n        return is_rate_limited\n\n    def rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_USER\"], 'auth-user')\n        if self.is_subject_to_rate_limits(ip):\n            limiter.hit(device_cookie if device_cookie_name == username else username)\n\n    \"\"\" Device cookies as described on:\n    https://owasp.org/www-community/Slow_Down_Online_Guessing_Attacks_with_Device_Cookies\n    \"\"\"\n    def parse_device_cookie(self, cookie):\n        try:\n            login, nonce, _ = cookie.split('$')\n            if hmac.compare_digest(cookie, self.device_cookie(login, nonce)):\n                return nonce, login\n        except:\n            pass\n        return None, None\n\n    \"\"\" Device cookies don't require strong crypto:\n        72bits of nonce, 96bits of signature is more than enough\n        and these values avoid padding in most cases\n    \"\"\"\n    def device_cookie(self, username, nonce=None):\n        if not nonce:\n            nonce = secrets.token_urlsafe(9)\n        sig = str(base64.urlsafe_b64encode(hmac.new(app.device_cookie_key, bytearray(f'device_cookie|{username}|{nonce}', 'utf-8'), 'sha256').digest()[20:]), 'utf-8')\n        return f'{username}${nonce}${sig}'\n", "path": "core/admin/mailu/limiter.py"}, {"content": "from mailu import models, utils\nfrom mailu.internal import internal, nginx\nfrom flask import current_app as app\n\nimport flask\nimport flask_login\nimport base64\n\n@internal.route(\"/auth/email\")\ndef nginx_authentication():\n    \"\"\" Main authentication endpoint for Nginx email server\n    \"\"\"\n    client_ip = flask.request.headers[\"Client-Ip\"]\n    headers = flask.request.headers\n    if headers[\"Auth-Port\"] == '25' and headers['Auth-Method'] == 'plain':\n        response = flask.Response()\n        response.headers['Auth-Status'] = 'AUTH not supported'\n        response.headers['Auth-Error-Code'] = '502 5.5.1'\n        utils.limiter.rate_limit_ip(client_ip)\n        return response\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n        response = flask.Response()\n        response.headers['Auth-Status'] = status\n        response.headers['Auth-Error-Code'] = code\n        if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n            response.headers['Auth-Wait'] = '3'\n        return response\n    headers = nginx.handle_authentication(flask.request.headers)\n    response = flask.Response()\n    for key, value in headers.items():\n        response.headers[key] = str(value)\n    is_valid_user = False\n    if response.headers.get(\"Auth-User-Exists\"):\n        username = response.headers[\"Auth-User\"]\n        if utils.limiter.should_rate_limit_user(username, client_ip):\n            # FIXME could be done before handle_authentication()\n            status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n            response = flask.Response()\n            response.headers['Auth-Status'] = status\n            response.headers['Auth-Error-Code'] = code\n            if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n                response.headers['Auth-Wait'] = '3'\n            return response\n        is_valid_user = True\n    if headers.get(\"Auth-Status\") == \"OK\":\n        utils.limiter.exempt_ip_from_ratelimits(client_ip)\n    elif is_valid_user:\n        utils.limiter.rate_limit_user(username, client_ip)\n    else:\n        utils.limiter.rate_limit_ip(client_ip)\n    return response\n\n@internal.route(\"/auth/admin\")\ndef admin_authentication():\n    \"\"\" Fails if the user is not an authenticated admin.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.global_admin\n        and flask_login.current_user.enabled):\n        return \"\"\n    return flask.abort(403)\n\n@internal.route(\"/auth/user\")\ndef user_authentication():\n    \"\"\" Fails if the user is not authenticated.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.enabled):\n        response = flask.Response()\n        email = flask_login.current_user.get_id()\n        response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, email, \"\")\n        response.headers[\"X-User-Token\"] = utils.gen_temp_token(email, flask.session)\n        return response\n    return flask.abort(403)\n\n\n@internal.route(\"/auth/basic\")\ndef basic_authentication():\n    \"\"\" Tries to authenticate using the Authorization header.\n    \"\"\"\n    client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        response = flask.Response(status=401)\n        response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit from one source exceeded\"'\n        response.headers['Retry-After'] = '60'\n        return response\n    authorization = flask.request.headers.get(\"Authorization\")\n    if authorization and authorization.startswith(\"Basic \"):\n        encoded = authorization.replace(\"Basic \", \"\")\n        user_email, password = base64.b64decode(encoded).split(b\":\", 1)\n        user_email = user_email.decode(\"utf8\")\n        if utils.limiter.should_rate_limit_user(user_email, client_ip):\n            response = flask.Response(status=401)\n            response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit for this username exceeded\"'\n            response.headers['Retry-After'] = '60'\n            return response\n        user = models.User.query.get(user_email)\n        if user and nginx.check_credentials(user, password.decode('utf-8'), client_ip, \"web\"):\n            response = flask.Response()\n            response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, user.email, \"\")\n            utils.limiter.exempt_ip_from_ratelimits(client_ip)\n            return response\n        utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip)\n    response = flask.Response(status=401)\n    response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Login Required\"'\n    return response\n", "path": "core/admin/mailu/internal/views/auth.py"}], "after_files": [{"content": "from mailu import utils\nfrom flask import current_app as app\nimport base64\nimport limits\nimport limits.storage\nimport limits.strategies\n\nimport hmac\nimport secrets\n\nclass LimitWrapper(object):\n    \"\"\" Wraps a limit by providing the storage, item and identifiers\n    \"\"\"\n\n    def __init__(self, limiter, limit, *identifiers):\n        self.limiter = limiter\n        self.limit = limit\n        self.base_identifiers = identifiers\n\n    def test(self, *args):\n        return self.limiter.test(self.limit, *(self.base_identifiers + args))\n\n    def hit(self, *args):\n        return self.limiter.hit(self.limit, *(self.base_identifiers + args))\n\n    def get_window_stats(self, *args):\n        return self.limiter.get_window_stats(self.limit, *(self.base_identifiers + args))\n\n\nclass LimitWraperFactory(object):\n    \"\"\" Global limiter, to be used as a factory\n    \"\"\"\n\n    def init_app(self, app):\n        self.storage = limits.storage.storage_from_string(app.config[\"RATELIMIT_STORAGE_URL\"])\n        self.limiter = limits.strategies.MovingWindowRateLimiter(self.storage)\n\n    def get_limiter(self, limit, *args):\n        return LimitWrapper(self.limiter, limits.parse(limit), *args)\n\n    def is_subject_to_rate_limits(self, ip):\n        return False if utils.is_exempt_from_ratelimits(ip) else not (self.storage.get(f'exempt-{ip}') > 0)\n\n    def exempt_ip_from_ratelimits(self, ip):\n        self.storage.incr(f'exempt-{ip}', app.config[\"AUTH_RATELIMIT_EXEMPTION_LENGTH\"], True)\n\n    def should_rate_limit_ip(self, ip):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n        client_network = utils.extract_network_from_ip(ip)\n        is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(client_network)\n        if is_rate_limited:\n            app.logger.warn(f'Authentication attempt from {ip} has been rate-limited.')\n        return is_rate_limited\n\n    def rate_limit_ip(self, ip):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_IP\"], 'auth-ip')\n        client_network = utils.extract_network_from_ip(ip)\n        if self.is_subject_to_rate_limits(ip):\n            limiter.hit(client_network)\n\n    def should_rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_USER\"], 'auth-user')\n        is_rate_limited = self.is_subject_to_rate_limits(ip) and not limiter.test(device_cookie if device_cookie_name == username else username)\n        if is_rate_limited:\n            app.logger.warn(f'Authentication attempt from {ip} for {username} has been rate-limited.')\n        return is_rate_limited\n\n    def rate_limit_user(self, username, ip, device_cookie=None, device_cookie_name=None):\n        limiter = self.get_limiter(app.config[\"AUTH_RATELIMIT_USER\"], 'auth-user')\n        if self.is_subject_to_rate_limits(ip):\n            limiter.hit(device_cookie if device_cookie_name == username else username)\n\n    \"\"\" Device cookies as described on:\n    https://owasp.org/www-community/Slow_Down_Online_Guessing_Attacks_with_Device_Cookies\n    \"\"\"\n    def parse_device_cookie(self, cookie):\n        try:\n            login, nonce, _ = cookie.split('$')\n            if hmac.compare_digest(cookie, self.device_cookie(login, nonce)):\n                return nonce, login\n        except:\n            pass\n        return None, None\n\n    \"\"\" Device cookies don't require strong crypto:\n        72bits of nonce, 96bits of signature is more than enough\n        and these values avoid padding in most cases\n    \"\"\"\n    def device_cookie(self, username, nonce=None):\n        if not nonce:\n            nonce = secrets.token_urlsafe(9)\n        sig = str(base64.urlsafe_b64encode(hmac.new(app.device_cookie_key, bytearray(f'device_cookie|{username}|{nonce}', 'utf-8'), 'sha256').digest()[20:]), 'utf-8')\n        return f'{username}${nonce}${sig}'\n", "path": "core/admin/mailu/limiter.py"}, {"content": "from mailu import models, utils\nfrom mailu.internal import internal, nginx\nfrom flask import current_app as app\n\nimport flask\nimport flask_login\nimport base64\n\n@internal.route(\"/auth/email\")\ndef nginx_authentication():\n    \"\"\" Main authentication endpoint for Nginx email server\n    \"\"\"\n    client_ip = flask.request.headers[\"Client-Ip\"]\n    headers = flask.request.headers\n    if headers[\"Auth-Port\"] == '25' and headers['Auth-Method'] == 'plain':\n        response = flask.Response()\n        response.headers['Auth-Status'] = 'AUTH not supported'\n        response.headers['Auth-Error-Code'] = '502 5.5.1'\n        utils.limiter.rate_limit_ip(client_ip)\n        return response\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n        response = flask.Response()\n        response.headers['Auth-Status'] = status\n        response.headers['Auth-Error-Code'] = code\n        if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n            response.headers['Auth-Wait'] = '3'\n        return response\n    headers = nginx.handle_authentication(flask.request.headers)\n    response = flask.Response()\n    for key, value in headers.items():\n        response.headers[key] = str(value)\n    is_valid_user = False\n    is_from_webmail = headers['Auth-Port'] in ['10143', '10025']\n    if response.headers.get(\"Auth-User-Exists\"):\n        username = response.headers[\"Auth-User\"]\n        if utils.limiter.should_rate_limit_user(username, client_ip):\n            # FIXME could be done before handle_authentication()\n            status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n            response = flask.Response()\n            response.headers['Auth-Status'] = status\n            response.headers['Auth-Error-Code'] = code\n            if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n                response.headers['Auth-Wait'] = '3'\n            return response\n        is_valid_user = True\n    if headers.get(\"Auth-Status\") == \"OK\":\n        utils.limiter.exempt_ip_from_ratelimits(client_ip)\n    elif is_valid_user:\n        utils.limiter.rate_limit_user(username, client_ip)\n    elif not is_from_webmail:\n        utils.limiter.rate_limit_ip(client_ip)\n    return response\n\n@internal.route(\"/auth/admin\")\ndef admin_authentication():\n    \"\"\" Fails if the user is not an authenticated admin.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.global_admin\n        and flask_login.current_user.enabled):\n        return \"\"\n    return flask.abort(403)\n\n@internal.route(\"/auth/user\")\ndef user_authentication():\n    \"\"\" Fails if the user is not authenticated.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.enabled):\n        response = flask.Response()\n        email = flask_login.current_user.get_id()\n        response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, email, \"\")\n        response.headers[\"X-User-Token\"] = utils.gen_temp_token(email, flask.session)\n        return response\n    return flask.abort(403)\n\n\n@internal.route(\"/auth/basic\")\ndef basic_authentication():\n    \"\"\" Tries to authenticate using the Authorization header.\n    \"\"\"\n    client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        response = flask.Response(status=401)\n        response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit from one source exceeded\"'\n        response.headers['Retry-After'] = '60'\n        return response\n    authorization = flask.request.headers.get(\"Authorization\")\n    if authorization and authorization.startswith(\"Basic \"):\n        encoded = authorization.replace(\"Basic \", \"\")\n        user_email, password = base64.b64decode(encoded).split(b\":\", 1)\n        user_email = user_email.decode(\"utf8\")\n        if utils.limiter.should_rate_limit_user(user_email, client_ip):\n            response = flask.Response(status=401)\n            response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit for this username exceeded\"'\n            response.headers['Retry-After'] = '60'\n            return response\n        user = models.User.query.get(user_email)\n        if user and nginx.check_credentials(user, password.decode('utf-8'), client_ip, \"web\"):\n            response = flask.Response()\n            response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, user.email, \"\")\n            utils.limiter.exempt_ip_from_ratelimits(client_ip)\n            return response\n        utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip)\n    response = flask.Response(status=401)\n    response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Login Required\"'\n    return response\n", "path": "core/admin/mailu/internal/views/auth.py"}]}