From 9dede6eed23f7f470ed0bc5426425059521b46fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Thu, 19 Feb 2026 13:55:01 +0100 Subject: [PATCH 01/14] [FIX] auth_jwt: fix tests Odoo test runner now complains if new methods/attributes have been added to a model during a test and not cleaned-up. We fix that with a context manager that does the cleanup. --- auth_jwt/tests/test_auth_jwt.py | 518 +++++++++++++++++--------------- 1 file changed, 277 insertions(+), 241 deletions(-) diff --git a/auth_jwt/tests/test_auth_jwt.py b/auth_jwt/tests/test_auth_jwt.py index 6a87e87cbc..71d5b7a093 100644 --- a/auth_jwt/tests/test_auth_jwt.py +++ b/auth_jwt/tests/test_auth_jwt.py @@ -65,6 +65,7 @@ def _create_token( payload["nbf"] = nbf return jwt.encode(payload, key=key, algorithm="HS256") + @contextlib.contextmanager def _create_validator( self, name, @@ -74,7 +75,7 @@ def _create_validator( 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", + ) 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", + ) as validator5, + self._create_validator( + "validator6", + audience="bad audience", + secret_key="bad key", + ) as validator6, + self._create_validator( + "validator7", + issuer="http://other.issuer", + audience="bad audience", + secret_key="bad key", + ) 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") + 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" From d9bfe6b9b2d8b37c9647fd8b9d87a80a5d0c9bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Thu, 19 Feb 2026 13:55:37 +0100 Subject: [PATCH 02/14] Update dotfiles --- .copier-answers.yml | 2 +- .github/workflows/pre-commit.yml | 1 + .github/workflows/test.yml | 7 +++++++ .pylintrc | 18 +++++++++--------- README.md | 5 +++-- checklog-odoo.cfg | 1 + 6 files changed, 22 insertions(+), 12 deletions(-) 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..cce7996e03 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ +[![Support the OCA](https://odoo-community.org/readme-banner-image)](https://odoo-community.org/get-involved?utm_source=repo-readme) + +# server-auth [![Runboat](https://img.shields.io/badge/runboat-Try%20me-875A7B.png)](https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0) [![Pre-commit Status](https://github.com/OCA/server-auth/actions/workflows/pre-commit.yml/badge.svg?branch=18.0)](https://github.com/OCA/server-auth/actions/workflows/pre-commit.yml?query=branch%3A18.0) [![Build Status](https://github.com/OCA/server-auth/actions/workflows/test.yml/badge.svg?branch=18.0)](https://github.com/OCA/server-auth/actions/workflows/test.yml?query=branch%3A18.0) @@ -7,8 +10,6 @@ -# server-auth - server-auth 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.* From 822142e2e43891f3f64357de16e81b4d6fe3d284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Thu, 19 Feb 2026 14:03:44 +0100 Subject: [PATCH 03/14] [FIX] auth_jwt: silence jwt warning about short HMAC secrets used in tests --- auth_jwt/tests/test_auth_jwt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/auth_jwt/tests/test_auth_jwt.py b/auth_jwt/tests/test_auth_jwt.py index 71d5b7a093..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, @@ -71,7 +71,7 @@ def _create_validator( name, audience="me", issuer="http://the.issuer", - secret_key="thesecret", + secret_key="thesecret012345678901234567890123456789", partner_id_required=False, static_user_id=1, ): @@ -192,7 +192,7 @@ def test_auth_method_invalid_token_on_chain(self): ) as validator2, self._create_validator( "validator3", - secret_key="bad key", + secret_key="bad key 012345678901234567890123456789", ) as validator3, self._create_validator( "validator4", @@ -202,18 +202,18 @@ def test_auth_method_invalid_token_on_chain(self): self._create_validator( "validator5", issuer="http://other.issuer", - secret_key="bad key", + secret_key="bad key 012345678901234567890123456789", ) as validator5, self._create_validator( "validator6", audience="bad audience", - secret_key="bad key", + secret_key="bad key 012345678901234567890123456789", ) as validator6, self._create_validator( "validator7", issuer="http://other.issuer", audience="bad audience", - secret_key="bad key", + secret_key="bad key 012345678901234567890123456789", ) as validator7, ): validator1.next_validator_id = validator2 @@ -357,7 +357,7 @@ def test_get_validator(self): def test_bad_tokens(self): with self._create_validator("validator") as validator: - token = self._create_token(key="badsecret") + token = self._create_token(key="badsecret 012345678901234567890123456789") with self.assertRaises(UnauthorizedInvalidToken): validator._decode(token) token = self._create_token(audience="badaudience") From 452bd61ceef25eb3b040b05e0840652dc0639f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Thu, 19 Feb 2026 14:10:39 +0100 Subject: [PATCH 04/14] [FIX] auth_jwt_demo: silence jwt warning about short HMAC secrets used in tests --- auth_jwt_demo/demo/auth_jwt_validator.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From cba0bd13b360058c2d89d3e16ee885e9c50edf7f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 19 Feb 2026 13:17:13 +0000 Subject: [PATCH 05/14] [BOT] post-merge updates --- README.md | 4 ++-- auth_jwt/README.rst | 2 +- auth_jwt/__manifest__.py | 2 +- auth_jwt/static/description/index.html | 2 +- auth_jwt_demo/README.rst | 2 +- auth_jwt_demo/__manifest__.py | 2 +- auth_jwt_demo/static/description/index.html | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cce7996e03..85e0afca57 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ 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 | simahawk | 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 | sbidoul | JWT bearer token authentication. -[auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.0 | sbidoul | Test/demo module for auth_jwt. +[auth_jwt](auth_jwt/) | 18.0.1.0.1 | sbidoul | JWT bearer token authentication. +[auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.1 | sbidoul | Test/demo module for auth_jwt. [auth_oauth_filter_by_domain](auth_oauth_filter_by_domain/) | 18.0.1.0.0 | natuan9 | Filter OAuth providers by domain [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 | sbidoul | Allow users to login through OpenID Connect Provider diff --git a/auth_jwt/README.rst b/auth_jwt/README.rst index c63b8c4476..b35526edf2 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:30777e8ab742b08820f2d6989bf2ad5d3e7e869085397571480cfdcd78f810ea !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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..e2d214e6a1 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.1", "license": "LGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "maintainers": ["sbidoul"], diff --git a/auth_jwt/static/description/index.html b/auth_jwt/static/description/index.html index 8813f94463..ffe3ae8b8a 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:30777e8ab742b08820f2d6989bf2ad5d3e7e869085397571480cfdcd78f810ea !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

JWT bearer token authentication.

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/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 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

A test/demo module for auth_jwt.

From 3d1190b70307860ac9100017deed93e1695d721e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Thu, 9 Oct 2025 10:06:28 +0200 Subject: [PATCH 06/14] [ADD] auth_oauth_login_field --- auth_oauth_login_field/README.rst | 73 +++ auth_oauth_login_field/__init__.py | 1 + auth_oauth_login_field/__manifest__.py | 17 + auth_oauth_login_field/models/__init__.py | 1 + auth_oauth_login_field/models/res_users.py | 14 + auth_oauth_login_field/pyproject.toml | 3 + auth_oauth_login_field/readme/DESCRIPTION.md | 3 + auth_oauth_login_field/readme/USAGE.md | 3 + .../static/description/index.html | 418 ++++++++++++++++++ auth_oauth_login_field/tests/__init__.py | 1 + .../tests/test_generate_signup_values.py | 48 ++ 11 files changed, 582 insertions(+) create mode 100644 auth_oauth_login_field/README.rst create mode 100644 auth_oauth_login_field/__init__.py create mode 100644 auth_oauth_login_field/__manifest__.py create mode 100644 auth_oauth_login_field/models/__init__.py create mode 100644 auth_oauth_login_field/models/res_users.py create mode 100644 auth_oauth_login_field/pyproject.toml create mode 100644 auth_oauth_login_field/readme/DESCRIPTION.md create mode 100644 auth_oauth_login_field/readme/USAGE.md create mode 100644 auth_oauth_login_field/static/description/index.html create mode 100644 auth_oauth_login_field/tests/__init__.py create mode 100644 auth_oauth_login_field/tests/test_generate_signup_values.py diff --git a/auth_oauth_login_field/README.rst b/auth_oauth_login_field/README.rst new file mode 100644 index 0000000000..d79e3ff21f --- /dev/null +++ b/auth_oauth_login_field/README.rst @@ -0,0 +1,73 @@ +====================== +Auth Oauth Login Field +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:fc5382a243d153a8cb6f66f54f2153c2655b737645f64385e65015f42a5e4d60 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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: + +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. + +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/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/index.html b/auth_oauth_login_field/static/description/index.html new file mode 100644 index 0000000000..155bdae37b --- /dev/null +++ b/auth_oauth_login_field/static/description/index.html @@ -0,0 +1,418 @@ + + + + + +Auth Oauth Login Field + + + +
+

Auth Oauth Login Field

+ + +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

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

+ +
+

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.

+ +Odoo Community Association + +

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.

+

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) From e64e4deac38bf50f4589503fdb2cbad702e6b1e0 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 19 Feb 2026 13:21:00 +0000 Subject: [PATCH 07/14] [UPD] Update auth_oauth_login_field.pot --- .../i18n/auth_oauth_login_field.pot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 auth_oauth_login_field/i18n/auth_oauth_login_field.pot 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 "" From 46f87ca74b2affec98b2ea8aaaf532bcbb90ada0 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 19 Feb 2026 13:24:04 +0000 Subject: [PATCH 08/14] [BOT] post-merge updates --- README.md | 1 + auth_oauth_login_field/README.rst | 24 +++++++++- .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 42 ++++++++++++------ setup/_metapackage/pyproject.toml | 3 +- 5 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 auth_oauth_login_field/static/description/icon.png diff --git a/README.md b/README.md index 85e0afca57..ab46ebb5e6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ addon | version | maintainers | summary [auth_jwt](auth_jwt/) | 18.0.1.0.1 | sbidoul | JWT bearer token authentication. [auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.1 | sbidoul | Test/demo module for auth_jwt. [auth_oauth_filter_by_domain](auth_oauth_filter_by_domain/) | 18.0.1.0.0 | natuan9 | Filter OAuth providers by domain +[auth_oauth_login_field](auth_oauth_login_field/) | 18.0.1.0.0 | sbidoul | 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 | sbidoul | 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 diff --git a/auth_oauth_login_field/README.rst b/auth_oauth_login_field/README.rst index d79e3ff21f..2f9a3eee19 100644 --- a/auth_oauth_login_field/README.rst +++ b/auth_oauth_login_field/README.rst @@ -1,3 +1,7 @@ +.. 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 ====================== @@ -7,13 +11,13 @@ Auth Oauth Login Field !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:fc5382a243d153a8cb6f66f54f2153c2655b737645f64385e65015f42a5e4d60 + !! 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/licence-AGPL--3-blue.png +.. |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 @@ -37,6 +41,14 @@ different from the ``email`` field. .. 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 =========== @@ -68,6 +80,14 @@ 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/static/description/icon.png b/auth_oauth_login_field/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 -Auth Oauth Login Field +README.rst -
-

Auth Oauth Login Field

+
+ + +Odoo Community Association + +
+

Auth Oauth Login Field

-

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

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

+
+

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

+

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 @@ -393,15 +406,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ACSONE SA/NV
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -409,10 +422,13 @@

Maintainers

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:

+

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/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.*", From fb0eea0d08b05120f6e50ba7df8ab9dd5ea58be5 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Fri, 20 Feb 2026 11:47:22 +0100 Subject: [PATCH 09/14] [FIX] auth_jwt: mute noisy Odoo test patch checker See https://github.com/odoo/odoo/pull/247151 --- auth_jwt/models/auth_jwt_validator.py | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) 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"] From a12f4c81a5db3400c64e347880c8e38a7a0d7c0a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 20 Feb 2026 12:34:58 +0000 Subject: [PATCH 10/14] [BOT] post-merge updates --- README.md | 2 +- auth_jwt/README.rst | 2 +- auth_jwt/__manifest__.py | 2 +- auth_jwt/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab46ebb5e6..7134de0d09 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ 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 | simahawk | 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.1 | sbidoul | JWT bearer token authentication. +[auth_jwt](auth_jwt/) | 18.0.1.0.2 | sbidoul | JWT bearer token authentication. [auth_jwt_demo](auth_jwt_demo/) | 18.0.1.0.1 | sbidoul | Test/demo module for auth_jwt. [auth_oauth_filter_by_domain](auth_oauth_filter_by_domain/) | 18.0.1.0.0 | natuan9 | Filter OAuth providers by domain [auth_oauth_login_field](auth_oauth_login_field/) | 18.0.1.0.0 | sbidoul | Handle the login field in OAuth signup diff --git a/auth_jwt/README.rst b/auth_jwt/README.rst index b35526edf2..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:30777e8ab742b08820f2d6989bf2ad5d3e7e869085397571480cfdcd78f810ea + !! 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 e2d214e6a1..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.1", + "version": "18.0.1.0.2", "license": "LGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "maintainers": ["sbidoul"], diff --git a/auth_jwt/static/description/index.html b/auth_jwt/static/description/index.html index ffe3ae8b8a..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:30777e8ab742b08820f2d6989bf2ad5d3e7e869085397571480cfdcd78f810ea +!! source digest: sha256:0f9850eea2daf7003512ee2b445ac6de73eb66b8264c0c463354414a8bc61236 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

JWT bearer token authentication.

From 9d7522852a8dad5d37be6bd7bd31ea4a5cbc52a3 Mon Sep 17 00:00:00 2001 From: mymage Date: Sat, 21 Feb 2026 09:07:10 +0000 Subject: [PATCH 11/14] Added translation using Weblate (Italian) --- auth_oauth_login_field/i18n/it.po | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 auth_oauth_login_field/i18n/it.po diff --git a/auth_oauth_login_field/i18n/it.po b/auth_oauth_login_field/i18n/it.po new file mode 100644 index 0000000000..61746a13f5 --- /dev/null +++ b/auth_oauth_login_field/i18n/it.po @@ -0,0 +1,20 @@ +# 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: Automatically generated\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" + +#. module: auth_oauth_login_field +#: model:ir.model,name:auth_oauth_login_field.model_res_users +msgid "User" +msgstr "" From 96cdc92f63c1781246b4073c80fb7c48dd875cd1 Mon Sep 17 00:00:00 2001 From: mymage Date: Sat, 21 Feb 2026 10:02:17 +0000 Subject: [PATCH 12/14] Translated using Weblate (Italian) Currently translated at 100.0% (1 of 1 strings) Translation: server-auth-18.0/server-auth-18.0-auth_oauth_login_field Translate-URL: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_oauth_login_field/it/ --- auth_oauth_login_field/i18n/it.po | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/auth_oauth_login_field/i18n/it.po b/auth_oauth_login_field/i18n/it.po index 61746a13f5..ab3e4ec559 100644 --- a/auth_oauth_login_field/i18n/it.po +++ b/auth_oauth_login_field/i18n/it.po @@ -6,15 +6,17 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\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 "" +msgstr "Utente" From 486ae8bdb951200e72c2c3803fb0de7987320929 Mon Sep 17 00:00:00 2001 From: Carlos Roca Date: Wed, 25 Feb 2026 10:31:43 +0100 Subject: [PATCH 13/14] [FIX] vault: Error when user clicks on reencrypt Before this changes when a user clicks on reencrypt button of a vault, an error is thrown. With this changes, the problem does not occur anymore, and it works normally. --- vault/static/src/backend/controller.esm.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 530ca74c8a9c492a6dedcbd1aa1038df43cf48a7 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 25 Feb 2026 09:48:28 +0000 Subject: [PATCH 14/14] [BOT] post-merge updates --- README.md | 2 +- vault/README.rst | 2 +- vault/__manifest__.py | 2 +- vault/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7134de0d09..6801bbff58 100644 --- a/README.md +++ b/README.md @@ -43,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 | trojikman | Allow to see user's actions log [users_ldap_mail](users_ldap_mail/) | 18.0.1.0.0 | joao-p-marques | 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/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 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module implements a vault for secrets and files using