diff --git a/.copier-answers.yml b/.copier-answers.yml
index bb275c6d83..f888986b75 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Do NOT update manually; changes here will be overwritten by Copier
-_commit: v1.35
+_commit: v1.38
_src_path: git+https://github.com/OCA/oca-addons-repo-template
additional_ruff_rules: []
ci: GitHub
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 5eb021ef15..852a07469b 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -17,6 +17,7 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
+ cache: 'pip'
- name: Get python version
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
- uses: actions/cache@v4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5851b71598..a3e2701930 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -76,6 +76,13 @@ jobs:
run: oca_init_test_database
- name: Run tests
run: oca_run_tests
+ - name: Upload screenshots from JS tests
+ uses: actions/upload-artifact@v4
+ if: ${{ failure() }}
+ with:
+ name: Screenshots of failed JS tests - ${{ matrix.name }}${{ join(matrix.include) }}
+ path: /tmp/odoo_tests/${{ env.PGDATABASE }}
+ if-no-files-found: ignore
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.pylintrc b/.pylintrc
index d103ffcd9a..197cb67370 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -99,22 +99,22 @@ enable=anomalous-backslash-in-string,
translation-positional-used,
website-manifest-key-not-valid-uri,
external-request-timeout,
- # messages that do not cause the lint step to fail
- consider-merging-classes-inherited,
+ missing-manifest-dependency,
+ too-complex,,
create-user-wo-reset-password,
dangerous-filter-wo-user,
- deprecated-module,
file-not-used,
- invalid-commit,
- missing-manifest-dependency,
missing-newline-extrafiles,
- missing-readme,
no-utf8-coding-comment,
- odoo-addons-relative-import,
old-api7-method-defined,
- redefined-builtin,
- too-complex,
unnecessary-utf8-coding-comment,
+ # messages that do not cause the lint step to fail
+ consider-merging-classes-inherited,
+ deprecated-module,
+ invalid-commit,
+ missing-readme,
+ odoo-addons-relative-import,
+ redefined-builtin,
manifest-external-assets
diff --git a/README.md b/README.md
index c3ed52d72d..6801bbff58 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
+[](https://odoo-community.org/get-involved?utm_source=repo-readme)
+
+# server-auth
[](https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0)
[](https://github.com/OCA/server-auth/actions/workflows/pre-commit.yml?query=branch%3A18.0)
[](https://github.com/OCA/server-auth/actions/workflows/test.yml?query=branch%3A18.0)
@@ -7,8 +10,6 @@
-# server-auth
-
server-auth
@@ -25,9 +26,10 @@ addon | version | maintainers | summary
[auth_api_key](auth_api_key/) | 18.0.1.0.1 | | Authenticate http requests from an API key
[auth_api_key_group](auth_api_key_group/) | 18.0.1.0.0 |
| Allow grouping API keys together. Grouping per se does nothing. This feature is supposed to be used by other modules to limit access to services or records based on groups of keys.
[auth_api_key_server_env](auth_api_key_server_env/) | 18.0.1.0.0 | | Configure api keys via server env. This can be very useful to avoid mixing your keys between your various environments when restoring databases. All you have to do is to add a new section to your configuration file according to the following convention:
-[auth_jwt](auth_jwt/) | 18.0.1.0.0 |
| JWT bearer token authentication.
-[auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.0 |
| Test/demo module for auth_jwt.
+[auth_jwt](auth_jwt/) | 18.0.1.0.2 |
| JWT bearer token authentication.
+[auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.1 |
| Test/demo module for auth_jwt.
[auth_oauth_filter_by_domain](auth_oauth_filter_by_domain/) | 18.0.1.0.0 |
| Filter OAuth providers by domain
+[auth_oauth_login_field](auth_oauth_login_field/) | 18.0.1.0.0 |
| Handle the login field in OAuth signup
[auth_oauth_multi_token](auth_oauth_multi_token/) | 18.0.2.0.0 | | Allow multiple connection with the same OAuth account
[auth_oidc](auth_oidc/) | 18.0.1.1.0 |
| Allow users to login through OpenID Connect Provider
[auth_oidc_environment](auth_oidc_environment/) | 18.0.1.0.0 | | This module allows to use server env for OIDC configuration
@@ -41,7 +43,7 @@ addon | version | maintainers | summary
[password_security](password_security/) | 18.0.1.0.0 | | Allow admin to set password security requirements.
[user_log_view](user_log_view/) | 18.0.1.0.0 |
| Allow to see user's actions log
[users_ldap_mail](users_ldap_mail/) | 18.0.1.0.0 |
| LDAP mapping for user name and e-mail
-[vault](vault/) | 18.0.1.0.1 | | Password vault integration in Odoo
+[vault](vault/) | 18.0.1.0.2 | | Password vault integration in Odoo
[vault_share](vault_share/) | 18.0.1.0.0 | | Implementation of a mechanism to share secrets
[//]: # (end addons)
diff --git a/auth_jwt/README.rst b/auth_jwt/README.rst
index c63b8c4476..4446364614 100644
--- a/auth_jwt/README.rst
+++ b/auth_jwt/README.rst
@@ -11,7 +11,7 @@ Auth JWT
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:0257cb75b9a02ab9b3f1aeebe8e0c5aee0b983f8b5ac1692132897dfb1986d02
+ !! source digest: sha256:0f9850eea2daf7003512ee2b445ac6de73eb66b8264c0c463354414a8bc61236
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/auth_jwt/__manifest__.py b/auth_jwt/__manifest__.py
index 8a311e5bdf..3791933393 100644
--- a/auth_jwt/__manifest__.py
+++ b/auth_jwt/__manifest__.py
@@ -5,7 +5,7 @@
"name": "Auth JWT",
"summary": """
JWT bearer token authentication.""",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.0.2",
"license": "LGPL-3",
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"maintainers": ["sbidoul"],
diff --git a/auth_jwt/models/auth_jwt_validator.py b/auth_jwt/models/auth_jwt_validator.py
index 13649adad2..73bc8a393b 100644
--- a/auth_jwt/models/auth_jwt_validator.py
+++ b/auth_jwt/models/auth_jwt_validator.py
@@ -13,6 +13,7 @@
from odoo import _, api, fields, models, tools
from odoo.exceptions import ValidationError
+from odoo.tools import mute_logger
from ..exceptions import (
AmbiguousJwtValidator,
@@ -251,19 +252,21 @@ def _register_hook(self):
def _register_auth_method(self):
IrHttp = self.env["ir.http"]
- for rec in self:
- setattr(
- IrHttp.__class__,
- f"_auth_method_jwt_{rec.name}",
- partial(IrHttp.__class__._auth_method_jwt, validator_name=rec.name),
- )
- setattr(
- IrHttp.__class__,
- f"_auth_method_public_or_jwt_{rec.name}",
- partial(
- IrHttp.__class__._auth_method_public_or_jwt, validator_name=rec.name
- ),
- )
+ with mute_logger("odoo.tests.common"): # Mute patch checker during tests
+ for rec in self:
+ setattr(
+ IrHttp.__class__,
+ f"_auth_method_jwt_{rec.name}",
+ partial(IrHttp.__class__._auth_method_jwt, validator_name=rec.name),
+ )
+ setattr(
+ IrHttp.__class__,
+ f"_auth_method_public_or_jwt_{rec.name}",
+ partial(
+ IrHttp.__class__._auth_method_public_or_jwt,
+ validator_name=rec.name,
+ ),
+ )
def _unregister_auth_method(self):
IrHttp = self.env["ir.http"]
diff --git a/auth_jwt/static/description/index.html b/auth_jwt/static/description/index.html
index 8813f94463..8b4f8e4c5a 100644
--- a/auth_jwt/static/description/index.html
+++ b/auth_jwt/static/description/index.html
@@ -372,7 +372,7 @@
Auth JWT
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:0257cb75b9a02ab9b3f1aeebe8e0c5aee0b983f8b5ac1692132897dfb1986d02
+!! source digest: sha256:0f9850eea2daf7003512ee2b445ac6de73eb66b8264c0c463354414a8bc61236
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

JWT bearer token authentication.
diff --git a/auth_jwt/tests/test_auth_jwt.py b/auth_jwt/tests/test_auth_jwt.py
index 6a87e87cbc..5175566869 100644
--- a/auth_jwt/tests/test_auth_jwt.py
+++ b/auth_jwt/tests/test_auth_jwt.py
@@ -51,7 +51,7 @@ def _mock_request(self, authorization):
def _create_token(
self,
- key="thesecret",
+ key="thesecret012345678901234567890123456789",
audience="me",
issuer="http://the.issuer",
exp_delta=100,
@@ -65,16 +65,17 @@ def _create_token(
payload["nbf"] = nbf
return jwt.encode(payload, key=key, algorithm="HS256")
+ @contextlib.contextmanager
def _create_validator(
self,
name,
audience="me",
issuer="http://the.issuer",
- secret_key="thesecret",
+ secret_key="thesecret012345678901234567890123456789",
partner_id_required=False,
static_user_id=1,
):
- return self.env["auth.jwt.validator"].create(
+ v = self.env["auth.jwt.validator"].create(
dict(
name=name,
signature_type="secret",
@@ -88,202 +89,237 @@ def _create_validator(
partner_id_required=partner_id_required,
)
)
+ try:
+ yield v
+ finally:
+ v.unlink()
def test_missing_authorization_header(self):
- self._create_validator("validator")
- with self._mock_request(authorization=None):
- with self.assertRaises(UnauthorizedMissingAuthorizationHeader):
- self.env["ir.http"]._auth_method_jwt(validator_name="validator")
+ with self._create_validator("validator"):
+ with self._mock_request(authorization=None):
+ with self.assertRaises(UnauthorizedMissingAuthorizationHeader):
+ self.env["ir.http"]._auth_method_jwt(validator_name="validator")
def test_malformed_authorization_header(self):
- self._create_validator("validator")
- for authorization in (
- "a",
- "Bearer",
- "Bearer ",
- "Bearer x y",
- "Bearer token ",
- "bearer token",
- ):
- with self._mock_request(authorization=authorization):
- with self.assertRaises(UnauthorizedMalformedAuthorizationHeader):
- self.env["ir.http"]._auth_method_jwt(validator_name="validator")
+ with self._create_validator("validator"):
+ for authorization in (
+ "a",
+ "Bearer",
+ "Bearer ",
+ "Bearer x y",
+ "Bearer token ",
+ "bearer token",
+ ):
+ with self._mock_request(authorization=authorization):
+ with self.assertRaises(UnauthorizedMalformedAuthorizationHeader):
+ self.env["ir.http"]._auth_method_jwt(validator_name="validator")
def test_auth_method_valid_token(self):
- self._create_validator("validator")
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- self.env["ir.http"]._auth_method_jwt_validator()
+ with self._create_validator("validator"):
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ self.env["ir.http"]._auth_method_jwt_validator()
def test_auth_method_valid_token_two_validators_one_bad_issuer(self):
- self._create_validator("validator2", issuer="http://other.issuer")
- self._create_validator("validator3")
-
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- # first validator rejects the token because of invalid audience
- with self.assertRaises(UnauthorizedInvalidToken):
- self.env["ir.http"]._auth_method_jwt_validator2()
- # second validator accepts the token
- self.env["ir.http"]._auth_method_jwt_validator3()
+ with (
+ self._create_validator("validator2", issuer="http://other.issuer"),
+ self._create_validator("validator3"),
+ ):
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ # first validator rejects the token because of invalid audience
+ with self.assertRaises(UnauthorizedInvalidToken):
+ self.env["ir.http"]._auth_method_jwt_validator2()
+ # second validator accepts the token
+ self.env["ir.http"]._auth_method_jwt_validator3()
def test_auth_method_valid_token_two_validators_one_bad_issuer_chained(self):
- validator2 = self._create_validator("validator2", issuer="http://other.issuer")
- validator3 = self._create_validator("validator3")
- validator2.next_validator_id = validator3
+ with (
+ self._create_validator(
+ "validator2", issuer="http://other.issuer"
+ ) as validator2,
+ self._create_validator("validator3") as validator3,
+ ):
+ validator2.next_validator_id = validator3
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- # Validator2 rejects the token because of invalid issuer but chain
- # on validator3 which accepts it
- self.env["ir.http"]._auth_method_jwt_validator2()
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ # Validator2 rejects the token because of invalid issuer but chain
+ # on validator3 which accepts it
+ self.env["ir.http"]._auth_method_jwt_validator2()
def test_auth_method_valid_token_two_validators_one_bad_audience(self):
- self._create_validator("validator2", audience="bad")
- self._create_validator("validator3")
-
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- # first validator rejects the token because of invalid audience
- with self.assertRaises(UnauthorizedInvalidToken):
- self.env["ir.http"]._auth_method_jwt_validator2()
- # second validator accepts the token
- self.env["ir.http"]._auth_method_jwt_validator3()
+ with (
+ self._create_validator("validator2", audience="bad"),
+ self._create_validator("validator3"),
+ ):
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ # first validator rejects the token because of invalid audience
+ with self.assertRaises(UnauthorizedInvalidToken):
+ self.env["ir.http"]._auth_method_jwt_validator2()
+ # second validator accepts the token
+ self.env["ir.http"]._auth_method_jwt_validator3()
def test_auth_method_valid_token_two_validators_one_bad_audience_chained(self):
- validator2 = self._create_validator("validator2", audience="bad")
- validator3 = self._create_validator("validator3")
-
- validator2.next_validator_id = validator3
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- self.env["ir.http"]._auth_method_jwt_validator2()
+ with (
+ self._create_validator("validator2", audience="bad") as validator2,
+ self._create_validator("validator3") as validator3,
+ ):
+ validator2.next_validator_id = validator3
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ self.env["ir.http"]._auth_method_jwt_validator2()
def test_auth_method_invalid_token(self):
# Test invalid token via _auth_method_jwt
# Other types of invalid tokens are unit tested elswhere.
- self._create_validator("validator4")
- authorization = "Bearer " + self._create_token(audience="bad")
- with self._mock_request(authorization=authorization):
- with self.assertRaises(UnauthorizedInvalidToken):
- self.env["ir.http"]._auth_method_jwt_validator4()
+ with self._create_validator("validator4"):
+ authorization = "Bearer " + self._create_token(audience="bad")
+ with self._mock_request(authorization=authorization):
+ with self.assertRaises(UnauthorizedInvalidToken):
+ self.env["ir.http"]._auth_method_jwt_validator4()
def test_auth_method_invalid_token_on_chain(self):
- validator1 = self._create_validator("validator", issuer="http://other.issuer")
- validator2 = self._create_validator("validator2", audience="bad audience")
- validator3 = self._create_validator("validator3", secret_key="bad key")
- validator4 = self._create_validator(
- "validator4", issuer="http://other.issuer", audience="bad audience"
- )
- validator5 = self._create_validator(
- "validator5", issuer="http://other.issuer", secret_key="bad key"
- )
- validator6 = self._create_validator(
- "validator6", audience="bad audience", secret_key="bad key"
- )
- validator7 = self._create_validator(
- "validator7",
- issuer="http://other.issuer",
- audience="bad audience",
- secret_key="bad key",
- )
- validator1.next_validator_id = validator2
- validator2.next_validator_id = validator3
- validator3.next_validator_id = validator4
- validator4.next_validator_id = validator5
- validator5.next_validator_id = validator6
- validator6.next_validator_id = validator7
-
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization):
- with self.assertRaises(UnauthorizedCompositeJwtError) as composite_error:
- self.env["ir.http"]._auth_method_jwt_validator()
- self.assertEqual(
- str(composite_error.exception),
- "401 Unauthorized: "
- "Multiple errors occurred during JWT chain validation:\n"
- "validator: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator2: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator3: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator4: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator5: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator6: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.\n"
- "validator7: 401 Unauthorized: "
- "The server could not verify that you are authorized to "
- "access the URL requested. You either supplied the wrong "
- "credentials (e.g. a bad password), or your browser doesn't "
- "understand how to supply the credentials required.",
- )
+ with (
+ self._create_validator(
+ "validator",
+ issuer="http://other.issuer",
+ ) as validator1,
+ self._create_validator(
+ "validator2",
+ audience="bad audience",
+ ) as validator2,
+ self._create_validator(
+ "validator3",
+ secret_key="bad key 012345678901234567890123456789",
+ ) as validator3,
+ self._create_validator(
+ "validator4",
+ issuer="http://other.issuer",
+ audience="bad audience",
+ ) as validator4,
+ self._create_validator(
+ "validator5",
+ issuer="http://other.issuer",
+ secret_key="bad key 012345678901234567890123456789",
+ ) as validator5,
+ self._create_validator(
+ "validator6",
+ audience="bad audience",
+ secret_key="bad key 012345678901234567890123456789",
+ ) as validator6,
+ self._create_validator(
+ "validator7",
+ issuer="http://other.issuer",
+ audience="bad audience",
+ secret_key="bad key 012345678901234567890123456789",
+ ) as validator7,
+ ):
+ validator1.next_validator_id = validator2
+ validator2.next_validator_id = validator3
+ validator3.next_validator_id = validator4
+ validator4.next_validator_id = validator5
+ validator5.next_validator_id = validator6
+ validator6.next_validator_id = validator7
+
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization):
+ with self.assertRaises(
+ UnauthorizedCompositeJwtError
+ ) as composite_error:
+ self.env["ir.http"]._auth_method_jwt_validator()
+ self.assertEqual(
+ str(composite_error.exception),
+ "401 Unauthorized: "
+ "Multiple errors occurred during JWT chain validation:\n"
+ "validator: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator2: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator3: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator4: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator5: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator6: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.\n"
+ "validator7: 401 Unauthorized: "
+ "The server could not verify that you are authorized to "
+ "access the URL requested. You either supplied the wrong "
+ "credentials (e.g. a bad password), or your browser doesn't "
+ "understand how to supply the credentials required.",
+ )
def test_invalid_validation_chain(self):
- validator1 = self._create_validator("validator")
- validator2 = self._create_validator("validator2")
- validator3 = self._create_validator("validator3")
-
- validator1.next_validator_id = validator2
- validator2.next_validator_id = validator3
- with self.assertRaises(ValidationError) as error:
- validator3.next_validator_id = validator1
- self.assertEqual(
- str(error.exception),
- "Validators mustn't make a closed chain: "
- "validator3 -> validator -> validator2 -> validator3.",
- )
+ with (
+ self._create_validator("validator") as validator1,
+ self._create_validator("validator2") as validator2,
+ self._create_validator("validator3") as validator3,
+ ):
+ validator1.next_validator_id = validator2
+ validator2.next_validator_id = validator3
+ with self.assertRaises(ValidationError) as error:
+ validator3.next_validator_id = validator1
+ self.assertEqual(
+ str(error.exception),
+ "Validators mustn't make a closed chain: "
+ "validator3 -> validator -> validator2 -> validator3.",
+ )
def test_invalid_validation_auto_chain(self):
- validator = self._create_validator("validator")
- with self.assertRaises(ValidationError) as error:
- validator.next_validator_id = validator
- self.assertEqual(
- str(error.exception),
- "Validators mustn't make a closed chain: " "validator -> validator.",
- )
+ with self._create_validator("validator") as validator:
+ with self.assertRaises(ValidationError) as error:
+ validator.next_validator_id = validator
+ self.assertEqual(
+ str(error.exception),
+ "Validators mustn't make a closed chain: validator -> validator.",
+ )
def test_partner_id_strategy_email_found(self):
partner = self.env["res.partner"].search([("email", "!=", False)])[0]
- self._create_validator("validator6")
- authorization = "Bearer " + self._create_token(email=partner.email)
- with self._mock_request(authorization=authorization) as request:
- self.env["ir.http"]._auth_method_jwt_validator6()
- self.assertEqual(request.jwt_partner_id, partner.id)
+ with self._create_validator("validator6"):
+ authorization = "Bearer " + self._create_token(email=partner.email)
+ with self._mock_request(authorization=authorization) as request:
+ self.env["ir.http"]._auth_method_jwt_validator6()
+ self.assertEqual(request.jwt_partner_id, partner.id)
def test_partner_id_strategy_email_not_found(self):
- self._create_validator("validator6")
- authorization = "Bearer " + self._create_token(email="notanemail@example.com")
- with self._mock_request(authorization=authorization) as request:
- self.env["ir.http"]._auth_method_jwt_validator6()
- self.assertFalse(request.jwt_partner_id)
+ with self._create_validator("validator6"):
+ authorization = "Bearer " + self._create_token(
+ email="notanemail@example.com"
+ )
+ with self._mock_request(authorization=authorization) as request:
+ self.env["ir.http"]._auth_method_jwt_validator6()
+ self.assertFalse(request.jwt_partner_id)
def test_partner_id_strategy_email_not_found_partner_required(self):
- self._create_validator("validator6", partner_id_required=True)
- authorization = "Bearer " + self._create_token(email="notanemail@example.com")
- with self._mock_request(authorization=authorization):
- with self.assertRaises(UnauthorizedPartnerNotFound):
- self.env["ir.http"]._auth_method_jwt_validator6()
+ with self._create_validator("validator6", partner_id_required=True):
+ authorization = "Bearer " + self._create_token(
+ email="notanemail@example.com"
+ )
+ with self._mock_request(authorization=authorization):
+ with self.assertRaises(UnauthorizedPartnerNotFound):
+ self.env["ir.http"]._auth_method_jwt_validator6()
def test_get_validator(self):
AuthJwtValidator = self.env["auth.jwt.validator"]
@@ -298,59 +334,59 @@ def test_get_validator(self):
mute_logger("odoo.addons.auth_jwt.models.auth_jwt_validator"),
):
AuthJwtValidator._get_validator_by_name("notavalidator")
- validator1 = self._create_validator(name="validator1")
- with (
- self.assertRaises(JwtValidatorNotFound),
- mute_logger("odoo.addons.auth_jwt.models.auth_jwt_validator"),
- ):
- AuthJwtValidator._get_validator_by_name("notavalidator")
- self.assertEqual(AuthJwtValidator._get_validator_by_name(None), validator1)
- self.assertEqual(
- AuthJwtValidator._get_validator_by_name("validator1"), validator1
- )
- # create a second validator
- validator2 = self._create_validator(name="validator2")
- with (
- self.assertRaises(AmbiguousJwtValidator),
- mute_logger("odoo.addons.auth_jwt.models.auth_jwt_validator"),
- ):
- AuthJwtValidator._get_validator_by_name(None)
- self.assertEqual(
- AuthJwtValidator._get_validator_by_name("validator2"), validator2
- )
+ with self._create_validator(name="validator1") as validator1:
+ with (
+ self.assertRaises(JwtValidatorNotFound),
+ mute_logger("odoo.addons.auth_jwt.models.auth_jwt_validator"),
+ ):
+ AuthJwtValidator._get_validator_by_name("notavalidator")
+ self.assertEqual(AuthJwtValidator._get_validator_by_name(None), validator1)
+ self.assertEqual(
+ AuthJwtValidator._get_validator_by_name("validator1"), validator1
+ )
+ # create a second validator
+ with self._create_validator(name="validator2") as validator2:
+ with (
+ self.assertRaises(AmbiguousJwtValidator),
+ mute_logger("odoo.addons.auth_jwt.models.auth_jwt_validator"),
+ ):
+ AuthJwtValidator._get_validator_by_name(None)
+ self.assertEqual(
+ AuthJwtValidator._get_validator_by_name("validator2"), validator2
+ )
def test_bad_tokens(self):
- validator = self._create_validator("validator")
- token = self._create_token(key="badsecret")
- with self.assertRaises(UnauthorizedInvalidToken):
- validator._decode(token)
- token = self._create_token(audience="badaudience")
- with self.assertRaises(UnauthorizedInvalidToken):
- validator._decode(token)
- token = self._create_token(issuer="badissuer")
- with self.assertRaises(UnauthorizedInvalidToken):
- validator._decode(token)
- token = self._create_token(exp_delta=-100)
- with self.assertRaises(UnauthorizedInvalidToken):
- validator._decode(token)
+ with self._create_validator("validator") as validator:
+ token = self._create_token(key="badsecret 012345678901234567890123456789")
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
+ token = self._create_token(audience="badaudience")
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
+ token = self._create_token(issuer="badissuer")
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
+ token = self._create_token(exp_delta=-100)
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
def test_multiple_aud(self):
- validator = self._create_validator("validator", audience="a1,a2")
- token = self._create_token(audience="a1")
- validator._decode(token)
- token = self._create_token(audience="a2")
- validator._decode(token)
- token = self._create_token(audience="a3")
- with self.assertRaises(UnauthorizedInvalidToken):
+ with self._create_validator("validator", audience="a1,a2") as validator:
+ token = self._create_token(audience="a1")
+ validator._decode(token)
+ token = self._create_token(audience="a2")
validator._decode(token)
+ token = self._create_token(audience="a3")
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
def test_nbf(self):
- validator = self._create_validator("validator")
- token = self._create_token(nbf=time.time() - 60)
- validator._decode(token)
- token = self._create_token(nbf=time.time() + 60)
- with self.assertRaises(UnauthorizedInvalidToken):
+ with self._create_validator("validator") as validator:
+ token = self._create_token(nbf=time.time() - 60)
validator._decode(token)
+ token = self._create_token(nbf=time.time() + 60)
+ with self.assertRaises(UnauthorizedInvalidToken):
+ validator._decode(token)
def test_auth_method_registration_on_create(self):
IrHttp = self.env["ir.http"]
@@ -358,20 +394,19 @@ def test_auth_method_registration_on_create(self):
self.assertFalse(
hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
)
- self._create_validator("validator1")
- self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
- self.assertTrue(
- hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
- )
+ with self._create_validator("validator1"):
+ self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
+ self.assertTrue(
+ hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
+ )
def test_auth_method_unregistration_on_unlink(self):
IrHttp = self.env["ir.http"]
- validator = self._create_validator("validator1")
- self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
- self.assertTrue(
- hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
- )
- validator.unlink()
+ with self._create_validator("validator1"):
+ self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
+ self.assertTrue(
+ hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
+ )
self.assertFalse(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
self.assertFalse(
hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
@@ -379,28 +414,29 @@ def test_auth_method_unregistration_on_unlink(self):
def test_auth_method_registration_on_rename(self):
IrHttp = self.env["ir.http"]
- validator = self._create_validator("validator1")
- self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
- self.assertTrue(
- hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
- )
- validator.name = "validator2"
- self.assertFalse(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
- self.assertFalse(
- hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
- )
- self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator2"))
- self.assertTrue(
- hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator2")
- )
+ with self._create_validator("validator1") as validator:
+ self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
+ self.assertTrue(
+ hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
+ )
+ validator.name = "validator2"
+ self.assertFalse(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
+ self.assertFalse(
+ hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
+ )
+ self.assertTrue(hasattr(IrHttp.__class__, "_auth_method_jwt_validator2"))
+ self.assertTrue(
+ hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator2")
+ )
def test_name_check(self):
with self.assertRaises(ValidationError):
- self._create_validator(name="not an identifier")
+ with self._create_validator(name="not an identifier"):
+ pass
def test_public_or_jwt_valid_token(self):
- self._create_validator("validator")
- authorization = "Bearer " + self._create_token()
- with self._mock_request(authorization=authorization) as request:
- self.env["ir.http"]._auth_method_public_or_jwt_validator()
- assert request.jwt_payload["aud"] == "me"
+ with self._create_validator("validator"):
+ authorization = "Bearer " + self._create_token()
+ with self._mock_request(authorization=authorization) as request:
+ self.env["ir.http"]._auth_method_public_or_jwt_validator()
+ assert request.jwt_payload["aud"] == "me"
diff --git a/auth_jwt_demo/README.rst b/auth_jwt_demo/README.rst
index 4069d6660b..bb809ceb54 100644
--- a/auth_jwt_demo/README.rst
+++ b/auth_jwt_demo/README.rst
@@ -11,7 +11,7 @@ Auth JWT Test
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:eceb81c0fb5eea794389966cbdf7730462b1e9ed52ba64d67da57d7b1f400e6b
+ !! source digest: sha256:9107748d0e8fe356b5dbf0e6a8af05342460d05d3d8f5b123ea96eb8db7b26ae
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/auth_jwt_demo/__manifest__.py b/auth_jwt_demo/__manifest__.py
index ae32a32037..7c503371c4 100644
--- a/auth_jwt_demo/__manifest__.py
+++ b/auth_jwt_demo/__manifest__.py
@@ -5,7 +5,7 @@
"name": "Auth JWT Test",
"summary": """
Test/demo module for auth_jwt.""",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.0.1",
"license": "LGPL-3",
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"maintainers": ["sbidoul"],
diff --git a/auth_jwt_demo/demo/auth_jwt_validator.xml b/auth_jwt_demo/demo/auth_jwt_validator.xml
index 6bba454a67..31593e7a32 100644
--- a/auth_jwt_demo/demo/auth_jwt_validator.xml
+++ b/auth_jwt_demo/demo/auth_jwt_validator.xml
@@ -5,7 +5,7 @@
theissuer
secret
HS256
- thesecret
+ thesecret 012345678901234567890123456789
static
email
@@ -17,7 +17,7 @@
theissuer
secret
HS256
- thesecret
+ thesecret 012345678901234567890123456789
static
email
diff --git a/auth_jwt_demo/static/description/index.html b/auth_jwt_demo/static/description/index.html
index 12fdcf3766..47d900bcf3 100644
--- a/auth_jwt_demo/static/description/index.html
+++ b/auth_jwt_demo/static/description/index.html
@@ -372,7 +372,7 @@ Auth JWT Test
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:eceb81c0fb5eea794389966cbdf7730462b1e9ed52ba64d67da57d7b1f400e6b
+!! source digest: sha256:9107748d0e8fe356b5dbf0e6a8af05342460d05d3d8f5b123ea96eb8db7b26ae
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

A test/demo module for auth_jwt.
diff --git a/auth_oauth_login_field/README.rst b/auth_oauth_login_field/README.rst
new file mode 100644
index 0000000000..2f9a3eee19
--- /dev/null
+++ b/auth_oauth_login_field/README.rst
@@ -0,0 +1,93 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+======================
+Auth Oauth Login Field
+======================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:0cd90cc579e7c20557c8e1f820f160d40c991eb2d9976c9a964510bc2571485c
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
+ :target: https://github.com/OCA/server-auth/tree/18.0/auth_oauth_login_field
+ :alt: OCA/server-auth
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_oauth_login_field
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Handle the ``login`` field from the JWT token in OAuth signup. This is
+useful when you need to create users where the ``login`` field is
+different from the ``email`` field.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+If a ``login`` field is present in the token, it will be used as
+``login`` field on user signup. When using the ``auth_oidc`` module, the
+Token Map can be populated like this, for instance:
+``preferred_username:login``.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* ACSONE SA/NV
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-sbidoul| image:: https://github.com/sbidoul.png?size=40px
+ :target: https://github.com/sbidoul
+ :alt: sbidoul
+
+Current `maintainer `__:
+
+|maintainer-sbidoul|
+
+This module is part of the `OCA/server-auth `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/auth_oauth_login_field/__init__.py b/auth_oauth_login_field/__init__.py
new file mode 100644
index 0000000000..0650744f6b
--- /dev/null
+++ b/auth_oauth_login_field/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/auth_oauth_login_field/__manifest__.py b/auth_oauth_login_field/__manifest__.py
new file mode 100644
index 0000000000..fcebc8b6ad
--- /dev/null
+++ b/auth_oauth_login_field/__manifest__.py
@@ -0,0 +1,17 @@
+# Copyright 2025 ACSONE SA/NV
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+{
+ "name": "Auth Oauth Login Field",
+ "summary": """Handle the login field in OAuth signup""",
+ "version": "18.0.1.0.0",
+ "license": "AGPL-3",
+ "author": "ACSONE SA/NV,Odoo Community Association (OCA)",
+ "maintainers": ["sbidoul"],
+ "website": "https://github.com/OCA/server-auth",
+ "depends": [
+ "auth_oauth",
+ ],
+ "data": [],
+ "demo": [],
+}
diff --git a/auth_oauth_login_field/i18n/auth_oauth_login_field.pot b/auth_oauth_login_field/i18n/auth_oauth_login_field.pot
new file mode 100644
index 0000000000..4fab517885
--- /dev/null
+++ b/auth_oauth_login_field/i18n/auth_oauth_login_field.pot
@@ -0,0 +1,19 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * auth_oauth_login_field
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: auth_oauth_login_field
+#: model:ir.model,name:auth_oauth_login_field.model_res_users
+msgid "User"
+msgstr ""
diff --git a/auth_oauth_login_field/i18n/it.po b/auth_oauth_login_field/i18n/it.po
new file mode 100644
index 0000000000..ab3e4ec559
--- /dev/null
+++ b/auth_oauth_login_field/i18n/it.po
@@ -0,0 +1,22 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * auth_oauth_login_field
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2026-02-21 12:10+0000\n"
+"Last-Translator: mymage \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 5.15.2\n"
+
+#. module: auth_oauth_login_field
+#: model:ir.model,name:auth_oauth_login_field.model_res_users
+msgid "User"
+msgstr "Utente"
diff --git a/auth_oauth_login_field/models/__init__.py b/auth_oauth_login_field/models/__init__.py
new file mode 100644
index 0000000000..8835165330
--- /dev/null
+++ b/auth_oauth_login_field/models/__init__.py
@@ -0,0 +1 @@
+from . import res_users
diff --git a/auth_oauth_login_field/models/res_users.py b/auth_oauth_login_field/models/res_users.py
new file mode 100644
index 0000000000..9c22216cfa
--- /dev/null
+++ b/auth_oauth_login_field/models/res_users.py
@@ -0,0 +1,14 @@
+# Copyright 2021 ACSONE SA/NV
+
+from odoo import api, models
+
+
+class ResUsers(models.Model):
+ _inherit = "res.users"
+
+ @api.model
+ def _generate_signup_values(self, provider, validation, params):
+ res = super()._generate_signup_values(provider, validation, params)
+ if "login" in validation:
+ res["login"] = validation["login"]
+ return res
diff --git a/auth_oauth_login_field/pyproject.toml b/auth_oauth_login_field/pyproject.toml
new file mode 100644
index 0000000000..4231d0cccb
--- /dev/null
+++ b/auth_oauth_login_field/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/auth_oauth_login_field/readme/DESCRIPTION.md b/auth_oauth_login_field/readme/DESCRIPTION.md
new file mode 100644
index 0000000000..f3b772ec2b
--- /dev/null
+++ b/auth_oauth_login_field/readme/DESCRIPTION.md
@@ -0,0 +1,3 @@
+Handle the `login` field from the JWT token in OAuth signup. This is useful when
+you need to create users where the `login` field is different from the `email`
+field.
diff --git a/auth_oauth_login_field/readme/USAGE.md b/auth_oauth_login_field/readme/USAGE.md
new file mode 100644
index 0000000000..2a16487265
--- /dev/null
+++ b/auth_oauth_login_field/readme/USAGE.md
@@ -0,0 +1,3 @@
+If a `login` field is present in the token, it will be used as `login` field on
+user signup. When using the ``auth_oidc`` module, the Token Map can be populated
+like this, for instance: ``preferred_username:login``.
diff --git a/auth_oauth_login_field/static/description/icon.png b/auth_oauth_login_field/static/description/icon.png
new file mode 100644
index 0000000000..1dcc49c24f
Binary files /dev/null and b/auth_oauth_login_field/static/description/icon.png differ
diff --git a/auth_oauth_login_field/static/description/index.html b/auth_oauth_login_field/static/description/index.html
new file mode 100644
index 0000000000..2fa3001f0c
--- /dev/null
+++ b/auth_oauth_login_field/static/description/index.html
@@ -0,0 +1,434 @@
+
+
+
+
+
+README.rst
+
+
+
+
+
+
+
+
+
+
+
Auth Oauth Login Field
+
+

+
Handle the login field from the JWT token in OAuth signup. This is
+useful when you need to create users where the login field is
+different from the email field.
+
Table of contents
+
+
+
+
If a login field is present in the token, it will be used as
+login field on user signup. When using the auth_oidc module, the
+Token Map can be populated like this, for instance:
+preferred_username:login.
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
This module is maintained by the OCA.
+
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer:
+

+
This module is part of the OCA/server-auth project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+
+
+
+
+
+
diff --git a/auth_oauth_login_field/tests/__init__.py b/auth_oauth_login_field/tests/__init__.py
new file mode 100644
index 0000000000..f03c5aecd0
--- /dev/null
+++ b/auth_oauth_login_field/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_generate_signup_values
diff --git a/auth_oauth_login_field/tests/test_generate_signup_values.py b/auth_oauth_login_field/tests/test_generate_signup_values.py
new file mode 100644
index 0000000000..69a84879f9
--- /dev/null
+++ b/auth_oauth_login_field/tests/test_generate_signup_values.py
@@ -0,0 +1,48 @@
+from odoo.tests.common import TransactionCase
+
+
+class TestGenerateSignupValues(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.user = cls.env["res.users"].create(
+ {"name": "Test user", "login": "testuser"}
+ )
+
+ def test_generate_signup_values_no_login(self):
+ provider_id = 1
+ name = "Toto Le Héro"
+ email = "toto@example.com"
+ validation = {
+ "user_id": "1234567890",
+ "name": name,
+ "email": email,
+ }
+ params = {
+ "access_token": "thetoken",
+ }
+ res = self.user._generate_signup_values(provider_id, validation, params)
+ self.assertEqual(res["name"], name)
+ # Odoo's auth_oauth module sets login = email
+ self.assertTrue(res["login"] == res["email"])
+
+ def test_generate_signup_values_login(self):
+ provider_id = 1
+ name = "Toto Le Héro"
+ email = "toto.externe@example.com"
+ login = "toto@example.com"
+ validation = {
+ "user_id": "1234567890",
+ "name": name,
+ "email": email,
+ "login": login,
+ }
+ params = {
+ "access_token": "thetoken",
+ }
+ res = self.user._generate_signup_values(provider_id, validation, params)
+ self.assertEqual(res["name"], name)
+ # With this module we can have a different login than email
+ self.assertTrue(res["login"] != res["email"])
+ self.assertEqual(res["login"], login)
+ self.assertEqual(res["email"], email)
diff --git a/checklog-odoo.cfg b/checklog-odoo.cfg
index 0b55b7bf66..9cc94b30dd 100644
--- a/checklog-odoo.cfg
+++ b/checklog-odoo.cfg
@@ -1,3 +1,4 @@
[checklog-odoo]
ignore=
WARNING.* 0 failed, 0 error\(s\).*
+ WARNING.* Missing widget: res_partner_many2one for field of type many2one.*
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml
index 815ed04f61..ac7f6ec2fe 100644
--- a/setup/_metapackage/pyproject.toml
+++ b/setup/_metapackage/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "odoo-addons-oca-server-auth"
-version = "18.0.20251217.0"
+version = "18.0.20260219.0"
dependencies = [
"odoo-addon-auth_admin_passkey==18.0.*",
"odoo-addon-auth_api_key==18.0.*",
@@ -9,6 +9,7 @@ dependencies = [
"odoo-addon-auth_jwt==18.0.*",
"odoo-addon-auth_jwt_demo==18.0.*",
"odoo-addon-auth_oauth_filter_by_domain==18.0.*",
+ "odoo-addon-auth_oauth_login_field==18.0.*",
"odoo-addon-auth_oauth_multi_token==18.0.*",
"odoo-addon-auth_oidc==18.0.*",
"odoo-addon-auth_oidc_environment==18.0.*",
diff --git a/vault/README.rst b/vault/README.rst
index 482e293d58..f16526d2c6 100644
--- a/vault/README.rst
+++ b/vault/README.rst
@@ -11,7 +11,7 @@ Vault
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:4f91a8f4641c642226369994ad7a6532d1600c33246c36bf698c4fc846ba18d0
+ !! source digest: sha256:9925e324c4689cb3c4bcb95fbe72d46f826b24e2ab8f40c32bab64f54dba6add
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/vault/__manifest__.py b/vault/__manifest__.py
index 8f8f90d36d..6e2a495e6f 100644
--- a/vault/__manifest__.py
+++ b/vault/__manifest__.py
@@ -5,7 +5,7 @@
"name": "Vault",
"summary": "Password vault integration in Odoo",
"license": "AGPL-3",
- "version": "18.0.1.0.1",
+ "version": "18.0.1.0.2",
"website": "https://github.com/OCA/server-auth",
"application": True,
"author": "initOS GmbH, Odoo Community Association (OCA)",
diff --git a/vault/static/description/index.html b/vault/static/description/index.html
index cc2487ef98..887b8cfa8d 100644
--- a/vault/static/description/index.html
+++ b/vault/static/description/index.html
@@ -372,7 +372,7 @@ Vault
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:4f91a8f4641c642226369994ad7a6532d1600c33246c36bf698c4fc846ba18d0
+!! source digest: sha256:9925e324c4689cb3c4bcb95fbe72d46f826b24e2ab8f40c32bab64f54dba6add
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

This module implements a vault for secrets and files using
diff --git a/vault/static/src/backend/controller.esm.js b/vault/static/src/backend/controller.esm.js
index b3e61657e2..65259305c9 100644
--- a/vault/static/src/backend/controller.esm.js
+++ b/vault/static/src/backend/controller.esm.js
@@ -159,7 +159,7 @@ patch(FormController.prototype, {
);
for (const rec of records) {
- const val = await this.vault_utils.sym_decrypt(
+ const val = await self.vault_utils.sym_decrypt(
current_key,
rec.value,
rec.iv
@@ -176,8 +176,8 @@ patch(FormController.prototype, {
continue;
}
- const iv = this.vault_utils.generate_iv_base64();
- const encrypted = await this.vault_utils.sym_encrypt(
+ const iv = self.vault_utils.generate_iv_base64();
+ const encrypted = await self.vault_utils.sym_encrypt(
master_key,
val,
iv