Skip to content

Commit 2ec9325

Browse files
author
Raphael Vicini
committed
[MIG] auth_device: Migration to 19.0
1 parent 3dcfa30 commit 2ec9325

11 files changed

Lines changed: 546 additions & 88 deletions

File tree

auth_device/__manifest__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"name": "Auth Device",
66
"summary": "Allows users to log in through an external device.",
7-
"version": "14.0.1.0.0",
7+
"version": "19.0.1.0.0",
88
"license": "AGPL-3",
99
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
1010
"maintainers": ["FrancoMaxime"],
@@ -13,8 +13,12 @@
1313
"web",
1414
],
1515
"data": [
16-
"views/assets_frontend.xml",
1716
"views/auth_device_connection.xml",
1817
"views/res_users_views.xml",
1918
],
19+
"assets": {
20+
"web.assets_frontend": [
21+
"auth_device/static/src/interactions/*",
22+
],
23+
},
2024
}

auth_device/controllers/main.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
import werkzeug.utils
66

7-
from odoo import _, http
7+
from odoo import http
88
from odoo.exceptions import AccessDenied
99
from odoo.http import request
1010

1111
from odoo.addons.portal.controllers.web import Home
12-
from odoo.addons.web.controllers.main import ensure_db, login_and_redirect
12+
from odoo.addons.web.controllers.utils import ensure_db
1313

1414

1515
class DeviceController(Home):
@@ -28,11 +28,11 @@ def web_login(self, *args, **kw):
2828
if response.is_qweb:
2929
error = request.params.get("auth_device_error")
3030
if error == "1":
31-
error = _("Access Denied")
31+
error = self.env._("Access Denied")
3232
elif error == "2":
33-
error = _("Missing Device Code")
33+
error = self.env._("Missing Device Code")
3434
elif error == "3":
35-
error = _("Internal Error")
35+
error = self.env._("Internal Error")
3636
else:
3737
error = None
3838
if error:
@@ -42,7 +42,7 @@ def web_login(self, *args, **kw):
4242

4343

4444
class AuthDeviceController(http.Controller):
45-
@http.route("/auth_device/login", type="http", auth="none")
45+
@http.route("/auth_device/login", type="http", auth="none", readonly=False)
4646
def device_login(self, redirect="/web", **kw):
4747
ensure_db()
4848
if request.httprequest.method == "GET" or request.session.uid:
@@ -70,12 +70,13 @@ def device_login(self, redirect="/web", **kw):
7070
url = "/web/login?auth_device_error=3"
7171
elif user and request.httprequest.method == "POST":
7272
try:
73-
return login_and_redirect(
74-
db=request.session.db,
75-
login=user.login,
76-
key=request.params["device_code"],
77-
redirect_url=redirect,
78-
)
73+
credential = {
74+
"login": user.login,
75+
"password": request.params["device_code"],
76+
"type": "password",
77+
}
78+
request.session.authenticate(request.env, credential)
79+
return request.redirect(redirect or "/web")
7980
except AccessDenied:
8081
url = "/web/login?auth_device_error=1"
8182
return werkzeug.utils.redirect(url, 303)

auth_device/i18n/auth_device.pot

Lines changed: 217 additions & 0 deletions
Large diffs are not rendered by default.

auth_device/i18n/fr.po

Lines changed: 217 additions & 0 deletions
Large diffs are not rendered by default.

auth_device/models/res_users.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,22 @@
66

77
class ResUsers(models.Model):
88
_inherit = "res.users"
9-
_sql_constraints = [
10-
(
11-
"device_code_uniq",
12-
"UNIQUE(device_code)",
13-
"The device code should be unique.",
14-
)
15-
]
169

17-
device_code = fields.Char("Device Code", copy=False)
10+
_device_code_uniq = models.Constraint(
11+
"UNIQUE(device_code)",
12+
"The device code should be unique.",
13+
)
14+
15+
device_code = fields.Char(copy=False)
1816

1917
is_allowed_to_connect_with_device = fields.Boolean(
2018
string="Is allowed to connect with the external device?"
2119
)
2220

2321
# pylint: disable=missing-return
24-
def _check_credentials(self, password, env):
22+
def _check_credentials(self, credential, env):
2523
try:
26-
super()._check_credentials(password, env)
24+
return super()._check_credentials(credential, env)
2725

2826
except exceptions.AccessDenied:
2927
# Just be sure that parent methods aren't wrong
@@ -32,10 +30,16 @@ def _check_credentials(self, password, env):
3230
.sudo()
3331
.search(
3432
[
35-
("device_code", "=", password),
33+
("device_code", "=", credential["password"]),
3634
("is_allowed_to_connect_with_device", "=", True),
3735
]
3836
)
3937
)
4038
if not user or len(user) > 1:
4139
raise
40+
41+
return {
42+
"uid": self.env.user.id,
43+
"auth_method": "passkey",
44+
"mfa": "skip",
45+
}

auth_device/static/js/device_connection.js

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {Interaction} from "@web/public/interaction";
2+
import {registry} from "@web/core/registry";
3+
4+
export class DeviceLogin extends Interaction {
5+
static selector = ".o_auth_device_login";
6+
7+
dynamicContent = {
8+
_root: {"t-on-click.prevent": this.onClick},
9+
};
10+
11+
onClick() {
12+
const modalEl = document.querySelector("#loginDevice");
13+
if (!modalEl) {
14+
return;
15+
}
16+
17+
window.Modal.getOrCreateInstance(modalEl).show();
18+
19+
modalEl.addEventListener(
20+
"shown.bs.modal",
21+
() => modalEl.querySelector("#device_code_input")?.focus(),
22+
{once: true}
23+
);
24+
}
25+
}
26+
27+
registry.category("public.interactions").add("auth_device.device_login", DeviceLogin);

auth_device/tests/test_auth_device.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
from odoo import exceptions
88
from odoo.tests import tagged
9-
from odoo.tests.common import SavepointCase
9+
from odoo.tests.common import TransactionCase
1010

1111

1212
@tagged("post_install", "-at_install")
13-
class TestAuthDevice(SavepointCase):
13+
class TestAuthDevice(TransactionCase):
1414
@classmethod
1515
def setUpClass(cls):
1616
super().setUpClass()
@@ -37,16 +37,29 @@ def setUpClass(cls):
3737
)
3838
cls.user = cls.user.with_user(cls.user)
3939

40+
def _device_credential(self, device_code):
41+
return {
42+
"login": self.user.login,
43+
"password": device_code,
44+
"type": "password",
45+
}
46+
4047
def test_01_normal_login_succeed(self):
41-
self.user._check_credentials(self.user_device_code, {"interactive": True})
48+
self.user._check_credentials(
49+
self._device_credential(self.user_device_code), {"interactive": True}
50+
)
4251

4352
def test_02_normal_login_fail(self):
4453
with self.assertRaises(exceptions.AccessDenied):
45-
self.user._check_credentials(self.bad_device_code, {"interactive": True})
54+
self.user._check_credentials(
55+
self._device_credential(self.bad_device_code), {"interactive": True}
56+
)
4657

4758
def test_03_missing_device_code(self):
48-
with self.assertRaises(AssertionError):
49-
self.user._check_credentials("", {"interactive": True})
59+
with self.assertRaises(exceptions.AccessDenied):
60+
self.user._check_credentials(
61+
self._device_credential(""), {"interactive": True}
62+
)
5063

5164
def test_04_duplicate_device_code(self):
5265
partner_2 = self.partner_user = self.ResPartner.create(
@@ -71,4 +84,6 @@ def test_04_duplicate_device_code(self):
7184
def test_05_not_allowed_to_connect(self):
7285
self.user.sudo().is_allowed_to_connect_with_device = False
7386
with self.assertRaises(exceptions.AccessDenied):
74-
self.user._check_credentials(self.user_device_code, {"interactive": True})
87+
self.user._check_credentials(
88+
self._device_credential(self.user_device_code), {"interactive": True}
89+
)

auth_device/tests/test_ui.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

44
from lxml import html
5-
from werkzeug.test import Client
6-
from werkzeug.wrappers import BaseResponse
75

8-
from odoo.service import wsgi_server
96
from odoo.tests import common, tagged
107

118

@@ -46,33 +43,29 @@ def setUp(self):
4643

4744
self.dbname = env.cr.dbname
4845

49-
self.werkzeug_environ = {"REMOTE_ADDR": "127.0.0.1"}
50-
self.test_client = Client(wsgi_server.application, BaseResponse)
51-
self.test_client.get("/web/session/logout")
46+
self.url_open("/web/session/logout")
5247

5348
def html_doc(self, response):
5449
"""Get an HTML LXML document."""
55-
return html.fromstring(response.data)
50+
return html.fromstring(response.content)
5651

5752
def get_request(self, url, data=None):
58-
return self.test_client.get(url, query_string=data, follow_redirects=True)
53+
return self.url_open(url, params=data, allow_redirects=True)
5954

6055
def csrf_token(self, response):
6156
"""Get a valid CSRF token."""
6257
doc = self.html_doc(response)
6358
return doc.xpath("//input[@name='csrf_token']")[1].get("value")
6459

6560
def post_request(self, url, data=None):
66-
return self.test_client.post(
67-
url, data=data, follow_redirects=True, environ_base=self.werkzeug_environ
68-
)
61+
return self.url_open(url, data=data, allow_redirects=True)
6962

7063
def test_01_ui_normal_login_succeed(self):
7164
# Our user wants to go to backoffice part of Odoo
7265
response = self.get_request("/web/", data={"db": self.dbname})
7366

7467
# He notices that his redirected to login page as not authenticated
75-
self.assertIn("oe_login_device_form", response.data.decode("utf8"))
68+
self.assertIn("oe_login_device_form", response.text)
7669

7770
# He needs to enter his credentials and submit the form
7871
data = {
@@ -83,14 +76,14 @@ def test_01_ui_normal_login_succeed(self):
8376
response = self.post_request("/auth_device/login", data=data)
8477

8578
# He notices that his redirected to backoffice
86-
self.assertNotIn("oe_login_device_form", response.data.decode("utf8"))
79+
self.assertNotIn("oe_login_device_form", response.text)
8780

8881
def test_02_normal_login_fail(self):
8982
# Our user wants to go to backoffice part of Odoo
9083
response = self.get_request("/web/", data={"db": self.dbname})
9184

9285
# He notices that he's redirected to login page as not authenticated
93-
self.assertIn("oe_login_device_form", response.data.decode("utf8"))
86+
self.assertIn("oe_login_device_form", response.text)
9487

9588
# He needs to enter his credentials and submit the form
9689
data = {
@@ -101,14 +94,14 @@ def test_02_normal_login_fail(self):
10194
response = self.post_request("/auth_device/login", data=data)
10295

10396
# He mistyped his password so he's redirected to login page again
104-
self.assertIn("oe_login_device_form", response.data.decode("utf8"))
97+
self.assertIn("oe_login_device_form", response.text)
10598

10699
def test_03_no_login(self):
107100
# Our user wants to go to backoffice part of Odoo
108101
response = self.get_request("/web/", data={"db": self.dbname})
109102

110103
# He notices that he's redirected to login page as not authenticated
111-
self.assertIn("oe_login_device_form", response.data.decode("utf8"))
104+
self.assertIn("oe_login_device_form", response.text)
112105

113106
# He forgot to enter his credentials and submit the form
114107
data = {
@@ -118,4 +111,4 @@ def test_03_no_login(self):
118111
response = self.post_request("/auth_device/login", data=data)
119112

120113
# He forgot to complete the form so he's redirected to login page again
121-
self.assertIn("oe_login_device_form", response.data.decode("utf8"))
114+
self.assertIn("oe_login_device_form", response.text)

auth_device/views/assets_frontend.xml

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)