|
7 | 7 | import logging |
8 | 8 |
|
9 | 9 | import werkzeug.utils |
| 10 | +from saml2.validate import ResponseLifetimeExceed |
10 | 11 | from werkzeug.exceptions import BadRequest |
11 | 12 | from werkzeug.urls import url_quote_plus |
12 | 13 |
|
@@ -54,6 +55,18 @@ def wrapper(self, **kw): |
54 | 55 | return wrapper |
55 | 56 |
|
56 | 57 |
|
| 58 | +def _error_message(error: str) -> str | None: |
| 59 | + if error == "no-signup": |
| 60 | + return _("Sign up is not allowed on this database.") |
| 61 | + if error == "access-denied": |
| 62 | + return _("Access Denied") |
| 63 | + if error == "response-lifetime-exceed": |
| 64 | + return _("Response Lifetime Exceeded") |
| 65 | + if error == "expired": |
| 66 | + return _("You do not have access to this database. Please contact support.") |
| 67 | + return None |
| 68 | + |
| 69 | + |
57 | 70 | # ---------------------------------------------------------- |
58 | 71 | # Controller |
59 | 72 | # ---------------------------------------------------------- |
@@ -131,19 +144,7 @@ def web_login(self, *args, **kw): |
131 | 144 |
|
132 | 145 | response = super().web_login(*args, **kw) |
133 | 146 | if response.is_qweb: |
134 | | - error = request.params.get("saml_error") |
135 | | - if error == "no-signup": |
136 | | - error = _("Sign up is not allowed on this database.") |
137 | | - elif error == "access-denied": |
138 | | - error = _("Access Denied") |
139 | | - elif error == "expired": |
140 | | - error = _( |
141 | | - "You do not have access to this database. Please contact" |
142 | | - " support." |
143 | | - ) |
144 | | - else: |
145 | | - error = None |
146 | | - |
| 147 | + error = _error_message(request.params.get("saml_error")) |
147 | 148 | response.qcontext["saml_providers"] = providers |
148 | 149 |
|
149 | 150 | if error: |
@@ -173,6 +174,17 @@ def _get_saml_extra_relaystate(self): |
173 | 174 | } |
174 | 175 | return state |
175 | 176 |
|
| 177 | + def _get_saml_error_url(self, saml_error: str) -> str: |
| 178 | + """Return the URL of the SAML error page. |
| 179 | + This module provides a configuration option to use another page. |
| 180 | + """ |
| 181 | + base = ( |
| 182 | + request.env["ir.config_parameter"] |
| 183 | + .sudo() |
| 184 | + .get_param("auth_saml.saml_error_page", "/web/login") |
| 185 | + ) |
| 186 | + return f"{base}?saml_error={saml_error}" |
| 187 | + |
176 | 188 | @http.route("/auth_saml/get_auth_request", type="http", auth="none") |
177 | 189 | def get_auth_request(self, pid): |
178 | 190 | provider_id = int(pid) |
@@ -251,15 +263,17 @@ def signin(self, **kw): |
251 | 263 | # user could be on a temporary session |
252 | 264 | _logger.info("SAML2: access denied") |
253 | 265 |
|
254 | | - url = "/web/login?saml_error=expired" |
255 | | - redirect = werkzeug.utils.redirect(url, 303) |
| 266 | + redirect = werkzeug.utils.redirect(self._get_saml_error_url("expired"), 303) |
256 | 267 | redirect.autocorrect_location_header = False |
257 | 268 | return redirect |
| 269 | + except ResponseLifetimeExceed as e: |
| 270 | + _logger.debug("Response Lifetime Exceed - %s", str(e)) |
| 271 | + url = self._get_saml_error_url("response-lifetime-exceed") |
258 | 272 |
|
259 | 273 | except Exception as e: |
260 | 274 | # signup error |
261 | 275 | _logger.exception("SAML2: failure - %s", str(e)) |
262 | | - url = "/web/login?saml_error=access-denied" |
| 276 | + url = self._get_saml_error_url("access-denied") |
263 | 277 |
|
264 | 278 | redirect = request.redirect(url, 303) |
265 | 279 | redirect.autocorrect_location_header = False |
@@ -291,3 +305,15 @@ def saml_metadata(self, **kw): |
291 | 305 | ), |
292 | 306 | [("Content-Type", "text/xml")], |
293 | 307 | ) |
| 308 | + |
| 309 | + @http.route("/web/login/saml_error", type="http", auth="none", csrf=False) |
| 310 | + def saml_error(self, redirect=None, **kw): |
| 311 | + saml_error = request.params.get("saml_error") |
| 312 | + error = _error_message(saml_error) |
| 313 | + if not error: |
| 314 | + return request.redirect(redirect or "/") |
| 315 | + response = request.render("auth_saml.login_error", {"error": error}) |
| 316 | + response.headers["Cache-Control"] = "no-cache" |
| 317 | + response.headers["X-Frame-Options"] = "SAMEORIGIN" |
| 318 | + response.headers["Content-Security-Policy"] = "frame-ancestors 'self'" |
| 319 | + return response |
0 commit comments