Skip to content

Commit 57bc71b

Browse files
[MIG] auth_saml: Migration to 19.0
1 parent ddc1c48 commit 57bc71b

12 files changed

Lines changed: 110 additions & 102 deletions

File tree

auth_saml/README.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ SAML2 Authentication
2121
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
2222
:alt: License: AGPL-3
2323
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
24-
:target: https://github.com/OCA/server-auth/tree/18.0/auth_saml
24+
:target: https://github.com/OCA/server-auth/tree/19.0/auth_saml
2525
:alt: OCA/server-auth
2626
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27-
:target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_saml
27+
:target: https://translation.odoo-community.org/projects/server-auth-19-0/server-auth-19-0-auth_saml
2828
:alt: Translate me on Weblate
2929
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
30-
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0
30+
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=19.0
3131
:alt: Try me on Runboat
3232

3333
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -133,7 +133,7 @@ Bug Tracker
133133
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_.
134134
In case of trouble, please check there if your issue has already been reported.
135135
If you spotted it first, help us to smash it by providing a detailed and welcomed
136-
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20auth_saml%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
136+
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20auth_saml%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
137137

138138
Do not contact contributors directly about support or help with technical issues.
139139

@@ -192,6 +192,6 @@ Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
192192

193193
|maintainer-vincent-hatakeyama|
194194

195-
This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/18.0/auth_saml>`_ project on GitHub.
195+
This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/19.0/auth_saml>`_ project on GitHub.
196196

197197
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

auth_saml/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from . import controllers, models
1+
from . import controllers, models, wizards

auth_saml/__manifest__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# Copyright (C) 2020 GlodoUK <https://www.glodo.uk/>
2-
# Copyright (C) 2010-2016, 2022 XCG Consulting <http://odoo.consulting>
2+
# Copyright (C) 2010-2016, 2022 XCG Consulting <https://orbeet.io/>
33
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
44

55
{
66
"name": "SAML2 Authentication",
7-
"version": "18.0.1.1.0",
7+
"version": "19.0.1.0.0",
88
"category": "Tools",
99
"author": "XCG Consulting, Odoo Community Association (OCA)",
1010
"maintainers": ["vincent-hatakeyama"],
1111
"website": "https://github.com/OCA/server-auth",
1212
"license": "AGPL-3",
1313
"depends": ["base_setup", "web"],
1414
"external_dependencies": {
15-
"python": ["pysaml2"],
15+
"python": ["pysaml2", "responses"],
1616
"bin": ["xmlsec1"],
1717
# special definition used by OCA to install packages
1818
"deb": ["xmlsec1"],

auth_saml/controllers/main.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212

1313
from odoo import (
1414
SUPERUSER_ID,
15-
_,
1615
api,
1716
exceptions,
1817
http,
1918
models,
2019
modules,
2120
)
22-
from odoo.http import request
21+
from odoo.http import Response, request
2322
from odoo.tools.misc import clean_context
2423

2524
from odoo.addons.web.controllers.home import Home
@@ -35,19 +34,23 @@
3534

3635
def fragment_to_query_string(func):
3736
@functools.wraps(func)
38-
def wrapper(self, **kw):
37+
def wrapper(self, *a, **kw):
38+
kw.pop("debug", False)
3939
if not kw:
40-
return """<html><head><script>
40+
return Response("""<html><head><script>
4141
var l = window.location;
4242
var q = l.hash.substring(1);
43-
var r = '/' + l.search;
43+
var r = l.pathname + l.search;
4444
if(q.length !== 0) {
4545
var s = l.search ? (l.search === '?' ? '' : '&') : '?';
4646
r = l.pathname + l.search + s + q;
4747
}
48+
if (r == l.pathname) {
49+
r = '/';
50+
}
4851
window.location = r;
49-
</script></head><body></body></html>"""
50-
return func(self, **kw)
52+
</script></head><body></body></html>""")
53+
return func(self, *a, **kw)
5154

5255
return wrapper
5356

@@ -59,7 +62,7 @@ def wrapper(self, **kw):
5962

6063
class SAMLLogin(Home):
6164
# Disable pylint self use as the method is meant to be reused in other modules
62-
def _list_saml_providers_domain(self): # pylint: disable=no-self-use
65+
def _list_saml_providers_domain(self):
6366
return []
6467

6568
def list_saml_providers(self, with_autoredirect: bool = False) -> models.Model:
@@ -131,11 +134,11 @@ def web_login(self, *args, **kw):
131134
if response.is_qweb:
132135
error = request.params.get("saml_error")
133136
if error == "no-signup":
134-
error = _("Sign up is not allowed on this database.")
137+
error = request.env._("Sign up is not allowed on this database.")
135138
elif error == "access-denied":
136-
error = _("Access Denied")
139+
error = request.env._("Access Denied")
137140
elif error == "expired":
138-
error = _(
141+
error = request.env._(
139142
"You do not have access to this database. Please contact support."
140143
)
141144
else:
@@ -179,8 +182,10 @@ def get_auth_request(self, pid):
179182
)
180183
if not redirect_url:
181184
raise Exception(
182-
"Failed to get auth request from provider. "
183-
"Either misconfigured SAML provider or unknown provider."
185+
request.env._(
186+
"Failed to get auth request from provider. "
187+
"Either misconfigured SAML provider or unknown provider."
188+
)
184189
)
185190

186191
redirect = werkzeug.utils.redirect(redirect_url, 303)
@@ -227,25 +232,28 @@ def signin(self, **kw):
227232
request.httprequest.url_root.rstrip("/"),
228233
)
229234
)
235+
# The response needs to be saved otherwise login does not work.
236+
# auth_oauth has a similar code
237+
request.env.cr.commit()
230238
action = state.get("a")
231239
menu = state.get("m")
232240
redirect = (
233241
werkzeug.urls.url_unquote_plus(state["r"]) if state.get("r") else False
234242
)
235-
url = "/web"
243+
url = "/odoo"
236244
if redirect:
237245
url = redirect
238246
elif action:
239-
url = f"/#action={action}"
247+
url = f"/odoo/action-{action}"
240248
elif menu:
241-
url = f"/#menu_id={menu}"
249+
url = f"/odoo?menu_id={menu}"
242250

243251
credentials_dict = {
244252
"login": credentials[1],
245253
"token": credentials[2],
246254
"type": "saml_token",
247255
}
248-
auth_info = request.session.authenticate(dbname, credentials_dict)
256+
auth_info = request.session.authenticate(request.env, credentials_dict)
249257
resp = request.redirect(_get_login_redirect_url(auth_info["uid"], url), 303)
250258
resp.autocorrect_location_header = False
251259
return resp
@@ -277,15 +285,15 @@ def saml_metadata(self, **kw):
277285

278286
if not dbname or not provider:
279287
_logger.debug("Metadata page asked without database name or provider id")
280-
raise request.not_found(_("Missing parameters"))
288+
raise request.not_found(request.env._("Missing parameters"))
281289

282290
provider = int(provider)
283291

284292
with modules.registry.Registry(dbname).cursor() as cr:
285293
env = api.Environment(cr, SUPERUSER_ID, {})
286294
client = env["auth.saml.provider"].sudo().browse(provider)
287295
if not client.exists():
288-
raise request.not_found(_("Unknown provider"))
296+
raise request.not_found(request.env._("Unknown provider"))
289297

290298
return request.make_response(
291299
client._metadata_string(

auth_saml/models/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from . import (
44
auth_saml_attribute_mapping,
55
auth_saml_provider,
6-
auth_saml_request,
76
ir_config_parameter,
87
res_config_settings,
98
res_users,

auth_saml/models/auth_saml_provider.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
import urllib
1111

1212
import requests
13-
14-
# dependency name is pysaml2 # pylint: disable=W7936
1513
import saml2
1614
import saml2.xmldsig as ds
1715
from saml2.client import Saml2Client
@@ -165,7 +163,7 @@ def _compute_sp_metadata_url(self):
165163
)
166164

167165
for record in self:
168-
if isinstance(record.id, models.NewId):
166+
if not record.id:
169167
record.sp_metadata_url = False
170168
continue
171169

@@ -335,8 +333,12 @@ def _validate_auth_response(self, token: str, base_url: str = None):
335333

336334
if not matching_value:
337335
raise Exception(
338-
f"Matching attribute {self.matching_attribute} not found "
339-
f"in user attrs: {attrs}"
336+
self.env._(
337+
"Matching attribute %(matching_attribute)s"
338+
" not found in user attrs: %(attrs)s",
339+
matching_attribute=self.matching_attribute,
340+
attrs=attrs,
341+
)
340342
)
341343

342344
if matching_value and isinstance(matching_value, list):
@@ -417,8 +419,12 @@ def action_refresh_metadata_from_url(self):
417419
document = requests.get(provider.idp_metadata_url, timeout=5)
418420
if document.status_code != 200:
419421
raise UserError(
420-
"Unable to download the metadata for "
421-
f"{provider.name}: {document.reason}"
422+
self.env._(
423+
"Unable to download the metadata for"
424+
" %(provider_name)s: %(reason)s",
425+
provider_name=provider.name,
426+
reason=document.reason,
427+
)
422428
)
423429
if document.text != provider.idp_metadata:
424430
providers_to_update[provider.id] = document.text

auth_saml/models/res_users.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import passlib
99

10-
from odoo import SUPERUSER_ID, _, api, fields, models, modules, tools
10+
from odoo import SUPERUSER_ID, api, fields, models, modules, tools
1111
from odoo.exceptions import AccessDenied, ValidationError
1212

1313
from .ir_config_parameter import ALLOW_SAML_UID_AND_PASSWORD
@@ -153,21 +153,22 @@ def _set_password(self):
153153
# And also to be able to set password to a blank value
154154
if not self.allow_saml_and_password():
155155
saml_users = self.filtered(
156-
lambda user: user.sudo().saml_ids
157-
and user.id not in self._saml_allowed_user_ids()
158-
and user.password
156+
lambda user: (
157+
user.sudo().saml_ids
158+
and user.id not in self._saml_allowed_user_ids()
159+
and user.password
160+
)
159161
)
160162
if saml_users:
161163
# same error as an api.constrains because it is a constraint.
162164
# a standard constrains would require the use of SQL as the password
163165
# field is obfuscated by the base module.
164166
raise ValidationError(
165-
_(
166-
"This database disallows users to "
167-
"have both passwords and SAML IDs. "
168-
"Error for logins %s"
167+
self.env._(
168+
"This database disallows users to have both "
169+
"passwords and SAML IDs. Error for logins %s",
170+
saml_users.mapped("login"),
169171
)
170-
% saml_users.mapped("login")
171172
)
172173
# handle setting password to NULL
173174
blank_password_users = self.filtered(lambda user: user.password is False)

auth_saml/tests/test_pysaml.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os.path as osp
66
import urllib
77
from copy import deepcopy
8+
from unittest import SkipTest
89
from unittest.mock import patch
910

1011
import responses
@@ -49,7 +50,7 @@ def setUp(self):
4950
}
5051
)
5152
self.url_saml_request = (
52-
"/auth_saml/get_auth_request?pid=%d" % self.saml_provider.id
53+
f"/auth_saml/get_auth_request?pid={self.saml_provider.id}"
5354
)
5455

5556
self.idp = FakeIDP([self.saml_provider._metadata_string()])
@@ -102,8 +103,7 @@ def test_ensure_provider_appears_on_login(self):
102103
def test_ensure_provider_appears_on_login_with_redirect_param(self):
103104
"""Test that SAML provider is listed in the login page keeping the redirect"""
104105
response = self.url_open(
105-
"/web/login?redirect=%2Fweb%23action%3D37%26model%3Dir.module.module%26view"
106-
"_type%3Dkanban%26menu_id%3D5"
106+
"/web/login?redirect=%2Fweb%23action%3D37%26model%3Dir.module.module%26view_type%3Dkanban%26menu_id%3D5"
107107
)
108108
self.assertIn("Login with Authentic", response.text)
109109
self.assertIn(
@@ -184,16 +184,15 @@ def test_get_config_for_provider(self):
184184

185185
def test_ensure_metadata_present(self):
186186
response = self.url_open(
187-
"/auth_saml/metadata?p=%d&d=%s"
188-
% (self.saml_provider.id, self.env.cr.dbname)
187+
f"/auth_saml/metadata?p={self.saml_provider.id}&d={self.env.cr.dbname}"
189188
)
190189

191190
self.assertTrue(response.ok)
192191
self.assertTrue("xml" in response.headers.get("Content-Type"))
193192

194193
def test_ensure_get_auth_request_redirects(self):
195194
response = self.url_open(
196-
"/auth_saml/get_auth_request?pid=%d" % self.saml_provider.id,
195+
f"/auth_saml/get_auth_request?pid={self.saml_provider.id}",
197196
allow_redirects=False,
198197
)
199198
self.assertTrue(response.ok)
@@ -460,17 +459,20 @@ def test_disallow_user_password_when_changing_settings(self):
460459
def test_fragment_to_query_string_no_kw(self):
461460
"""Test the case where no keyword arguments are passed."""
462461
response = self.decorated_function(self)
463-
expected_html = """<html><head><script>
462+
expected_html = b"""<html><head><script>
464463
var l = window.location;
465464
var q = l.hash.substring(1);
466-
var r = '/' + l.search;
465+
var r = l.pathname + l.search;
467466
if(q.length !== 0) {
468467
var s = l.search ? (l.search === '?' ? '' : '&') : '?';
469468
r = l.pathname + l.search + s + q;
470469
}
470+
if (r == l.pathname) {
471+
r = '/';
472+
}
471473
window.location = r;
472474
</script></head><body></body></html>"""
473-
self.assertEqual(response.strip(), expected_html.strip())
475+
self.assertEqual(response.data, expected_html)
474476

475477
def test_fragment_to_query_string_with_kw(self):
476478
"""Test the case where keyword arguments are passed."""
@@ -648,9 +650,13 @@ def test_signin_no_relaystate_redirect(self):
648650

649651
def test_signin_redirect_mfa(self):
650652
"""Test redirect to mfa url"""
653+
if not self.env["ir.module.module"].search(
654+
[("name", "=", "auth_totp"), ("state", "=", "installed")]
655+
):
656+
raise SkipTest("auth_totp not installed")
651657
self.add_provider_to_user()
652658

653-
redirect_url = self.saml_provider._get_auth_request({"a": "action"})
659+
redirect_url = self.saml_provider._get_auth_request({"a": "42"})
654660
response = self.idp.fake_login(redirect_url)
655661
unpacked_response = response._unpack()
656662

@@ -668,7 +674,7 @@ def test_signin_redirect_mfa(self):
668674
self.assertTrue(response.ok)
669675
self.assertEqual(
670676
response.url,
671-
self.base_url() + "/web/login/totp?redirect=%2F%23action%3Daction",
677+
self.base_url() + "/web/login/totp?redirect=%2Fodoo%2Faction-42",
672678
)
673679

674680
def test_action_redirect(self):
@@ -690,7 +696,7 @@ def test_action_redirect(self):
690696
self.assertTrue(response.ok)
691697
self.assertEqual(
692698
response.url,
693-
self.base_url() + "/odoo#action=action",
699+
self.base_url() + "/odoo/action-action",
694700
)
695701

696702
def test_menu_redirect(self):
@@ -712,7 +718,7 @@ def test_menu_redirect(self):
712718
self.assertTrue(response.ok)
713719
self.assertEqual(
714720
response.url,
715-
self.base_url() + "/odoo#menu_id=12",
721+
self.base_url() + "/odoo?menu_id=12",
716722
)
717723

718724
@responses.activate

0 commit comments

Comments
 (0)