Skip to content

Commit aebd852

Browse files
committed
[IMP] auth_oauth_multi_token: make it compatible with odoo.sh "login as"
1 parent f52fedb commit aebd852

File tree

8 files changed

+116
-43
lines changed

8 files changed

+116
-43
lines changed

auth_oauth_multi_token/README.rst

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,21 @@ Authors
6666
Contributors
6767
------------
6868

69-
- Florent de Labarre <[email protected]>
70-
- Simone Orsi <[email protected]>
71-
- `Tecnativa <https://www.tecnativa.com/>`__:
69+
- Florent de Labarre <[email protected]>
70+
- Simone Orsi <[email protected]>
71+
- `Tecnativa <https://www.tecnativa.com/>`__:
7272

73-
- Jairo Llopis
74-
- Sergio Teruel
73+
- Jairo Llopis
74+
- Sergio Teruel
7575

76-
- Stéphane Bidoul <[email protected]>
77-
- Dan Tillinghast
78-
- Miku Laitinen
79-
- `Kencove <https://www.kencove.com/>`__:
76+
- Stéphane Bidoul <[email protected]>
77+
- Dan Tillinghast
78+
- Miku Laitinen
79+
- `Kencove <https://www.kencove.com/>`__:
8080

81-
- Mohamed Alkobrosli
81+
- Mohamed Alkobrosli
82+
83+
- Christopher Rogos
8284

8385
Maintainers
8486
-----------
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def migrate(cr, version):
2+
cr.execute(
3+
"""
4+
UPDATE
5+
res_users
6+
SET
7+
oauth_access_token = oauth_master_uuid;
8+
"""
9+
)

auth_oauth_multi_token/models/auth_oauth_multi_token.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ def _oauth_validate_multi_token(self):
4646
user_tokens = self._oauth_user_tokens(token.user_id.id)
4747
max_token = token.user_id.oauth_access_max_token
4848
if user_tokens and len(user_tokens) > max_token:
49-
# clear last token
50-
user_tokens[max_token - 1]._oauth_clear_token()
49+
# clear tokens beyond the max
50+
user_tokens[max_token:]._oauth_clear_token()
5151

5252
def _oauth_clear_token(self):
5353
"""Disable current token records."""

auth_oauth_multi_token/models/res_users.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55

66
from odoo import api, exceptions, fields, models
77

8-
from odoo.addons import base
9-
10-
base.models.res_users.USER_PRIVATE_FIELDS.append("oauth_master_uuid")
11-
128

139
class ResUsers(models.Model):
1410
_inherit = "res.users"
@@ -27,10 +23,9 @@ def _generate_oauth_master_uuid(self):
2723
oauth_access_max_token = fields.Integer(
2824
string="Max Number of Simultaneous Connections", default=10, required=True
2925
)
30-
oauth_master_uuid = fields.Char(
31-
string="Master UUID",
32-
copy=False,
33-
readonly=True,
26+
27+
# use the oauth_access_token field as oauth_master_uuid
28+
oauth_access_token = fields.Char(
3429
required=True,
3530
default=lambda self: self._generate_oauth_master_uuid(),
3631
)
@@ -39,45 +34,62 @@ def _generate_oauth_master_uuid(self):
3934
def multi_token_model(self):
4035
return self.env["auth.oauth.multi.token"]
4136

37+
@api.model
38+
def _generate_signup_values(self, provider, validation, params):
39+
"""Because access_token was replaced in
40+
_auth_oauth_signin we need to replace it here."""
41+
res = super()._generate_signup_values(provider, validation, params)
42+
res["oauth_access_token"] = params["access_token_multi"]
43+
return res
44+
4245
@api.model
4346
def _auth_oauth_signin(self, provider, validation, params):
4447
"""Override to handle sign-in with multi token."""
45-
res = super()._auth_oauth_signin(provider, validation, params)
48+
params["access_token_multi"] = params["access_token"]
4649

47-
oauth_uid = validation["user_id"]
4850
# Lookup for user by oauth uid and provider
51+
oauth_uid = validation["user_id"]
4952
user = self.search(
5053
[("oauth_uid", "=", oauth_uid), ("oauth_provider_id", "=", provider)]
5154
)
55+
56+
# Because access_token is automatically written to the user, we need to replace
57+
# this by the existing oauth_access_token which acts as oauth_master_uuid
58+
params["access_token"] = user.oauth_access_token
59+
res = super()._auth_oauth_signin(provider, validation, params)
60+
5261
if not user:
5362
raise exceptions.AccessDenied()
5463
user.ensure_one()
5564
# user found and unique: create a token
5665
self.multi_token_model.create(
57-
{"user_id": user.id, "oauth_access_token": params["access_token"]}
66+
{"user_id": user.id, "oauth_access_token": params["access_token_multi"]}
5867
)
5968
return res
6069

6170
def action_oauth_clear_token(self):
6271
"""Inactivate current user tokens."""
6372
self.mapped("oauth_access_token_ids")._oauth_clear_token()
6473
for res in self:
65-
res.oauth_access_token = False
66-
res.oauth_master_uuid = self._generate_oauth_master_uuid()
74+
res.oauth_access_token = self._generate_oauth_master_uuid()
6775

6876
@api.model
69-
def _check_credentials(self, password, env):
77+
def _check_credentials(self, credential, env):
7078
"""Override to check credentials against multi tokens."""
7179
try:
72-
return super()._check_credentials(password, env)
80+
return super()._check_credentials(credential, env)
7381
except exceptions.AccessDenied:
74-
res = self.multi_token_model.sudo().search(
75-
[("user_id", "=", self.env.uid), ("oauth_access_token", "=", password)]
82+
passwd_allowed = (
83+
env["interactive"] or not self.env.user._rpc_api_keys_only()
7684
)
77-
if not res:
78-
raise
85+
if passwd_allowed and self.env.user.active:
86+
res = self.multi_token_model.sudo().search(
87+
[
88+
("user_id", "=", self.env.uid),
89+
("oauth_access_token", "=", credential["token"]),
90+
]
91+
)
92+
if res:
93+
return
7994

80-
def _get_session_token_fields(self):
81-
res = super()._get_session_token_fields()
82-
res.remove("oauth_access_token")
83-
return res | {"oauth_master_uuid"}
95+
raise

auth_oauth_multi_token/readme/CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
- Miku Laitinen
99
- [Kencove](https://www.kencove.com/):
1010
- Mohamed Alkobrosli
11+
- Christopher Rogos

auth_oauth_multi_token/static/description/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ <h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
425425
<li>Mohamed Alkobrosli</li>
426426
</ul>
427427
</li>
428+
<li>Christopher Rogos</li>
428429
</ul>
429430
</div>
430431
<div class="section" id="maintainers">

auth_oauth_multi_token/tests/test_multi_token.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
import base64
55
import json
6+
import uuid
67

78
from odoo import exceptions
9+
from odoo.tests import users
810
from odoo.tests.common import TransactionCase
911

1012

@@ -27,9 +29,8 @@ def setUpClass(cls):
2729
)
2830

2931
def _fake_params(self, **kw):
30-
# in version 18.0 tokens should follow version check
31-
# based on verify_hash_signed method
32-
fake_token = b"\x01" + b"FAKE_TOKEN"
32+
random = uuid.uuid4().bytes
33+
fake_token = b"\x01" + b"FAKE_TOKEN" + random
3334
encoded_token = base64.urlsafe_b64encode(fake_token).rstrip(b"=").decode()
3435
params = {
3536
"state": json.dumps({"t": encoded_token}),
@@ -53,8 +54,10 @@ def _test_one_token(self):
5354
"user_id": "oauth_uid_johndoe",
5455
}
5556
params = self._fake_params()
56-
login = self.user_model._auth_oauth_signin(
57-
self.provider_google.id, validation, params
57+
login = (
58+
self.env["res.users"]
59+
.sudo()
60+
._auth_oauth_signin(self.provider_google.id, validation, params)
5861
)
5962
self.assertEqual(login, "johndoe")
6063

@@ -85,10 +88,55 @@ def test_access_multi_token(self):
8588
len(self.user.oauth_access_token_ids), self.user.oauth_access_max_token
8689
)
8790

88-
def test_remove_oauth_access_token(self):
91+
@users("johndoe")
92+
def test_access_multi_token_first_removed(self):
93+
# no token yet
94+
self.assertFalse(self.user.oauth_access_token_ids)
95+
96+
# login the first token
97+
validation = {
98+
"user_id": "oauth_uid_johndoe",
99+
}
100+
params = self._fake_params()
101+
login = (
102+
self.env["res.users"]
103+
.sudo()
104+
._auth_oauth_signin(self.provider_google.id, validation, params)
105+
)
106+
self.assertEqual(login, "johndoe")
107+
108+
# login is working
109+
credential = {
110+
"type": "oauth_token",
111+
"password": "",
112+
"token": params["access_token_multi"],
113+
}
114+
self.env["res.users"]._check_credentials(credential, {"interactive": False})
115+
116+
# use as many token as max allowed
117+
for token_count in range(2, self.user.oauth_access_max_token + 1):
118+
self._test_one_token()
119+
self.assertEqual(len(self.user.oauth_access_token_ids), token_count)
120+
self.assertEqual(
121+
len(self.token_model._oauth_user_tokens(self.user.id)), token_count
122+
)
123+
124+
# exceed the number, token removed and login blocked
125+
self._test_one_token()
126+
with self.assertRaises(exceptions.AccessDenied):
127+
self.env["res.users"]._check_credentials(credential, {"interactive": False})
128+
129+
# token count does not exceed max number
130+
self.assertEqual(
131+
len(self.user.oauth_access_token_ids), self.user.oauth_access_max_token
132+
)
133+
134+
def test_oauth_access_token_odoo_sh(self):
135+
# do not change the _get_session_token_fields result to stay compatible
136+
# with odoo.sh
89137
res = self.user._get_session_token_fields()
90-
self.assertFalse("oauth_access_token" in res)
91-
self.assertTrue("oauth_master_uuid" in res)
138+
self.assertTrue("oauth_access_token" in res)
139+
self.assertFalse("oauth_master_uuid" in res)
92140

93141
def test_action_oauth_clear_token(self):
94142
self.user.action_oauth_clear_token()

0 commit comments

Comments
 (0)