mirror of
https://github.com/bringout/oca-server-auth.git
synced 2026-04-18 13:32:08 +02:00
Initial commit: OCA Server Auth packages (29 packages)
This commit is contained in:
commit
3ed80311c4
1325 changed files with 127292 additions and 0 deletions
|
|
@ -0,0 +1 @@
|
|||
from . import test_auth_jwt
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
# Copyright 2021 ACSONE SA/NV
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
import contextlib
|
||||
import time
|
||||
from unittest.mock import Mock
|
||||
|
||||
import jwt
|
||||
|
||||
import odoo.http
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools.misc import DotDict
|
||||
|
||||
from ..exceptions import (
|
||||
AmbiguousJwtValidator,
|
||||
JwtValidatorNotFound,
|
||||
UnauthorizedCompositeJwtError,
|
||||
UnauthorizedInvalidToken,
|
||||
UnauthorizedMalformedAuthorizationHeader,
|
||||
UnauthorizedMissingAuthorizationHeader,
|
||||
UnauthorizedPartnerNotFound,
|
||||
)
|
||||
|
||||
|
||||
class TestAuthMethod(TransactionCase):
|
||||
@contextlib.contextmanager
|
||||
def _mock_request(self, authorization):
|
||||
environ = {}
|
||||
if authorization:
|
||||
environ["HTTP_AUTHORIZATION"] = authorization
|
||||
request = Mock(
|
||||
context={},
|
||||
db=self.env.cr.dbname,
|
||||
uid=None,
|
||||
httprequest=Mock(environ=environ),
|
||||
session=DotDict(),
|
||||
env=self.env,
|
||||
cr=self.env.cr,
|
||||
)
|
||||
# These attributes are added upon successful auth, so make sure
|
||||
# calling hasattr on the mock when they are not yet set returns False.
|
||||
del request.jwt_payload
|
||||
del request.jwt_partner_id
|
||||
|
||||
with contextlib.ExitStack() as s:
|
||||
odoo.http._request_stack.push(request)
|
||||
s.callback(odoo.http._request_stack.pop)
|
||||
yield request
|
||||
|
||||
def _create_token(
|
||||
self,
|
||||
key="thesecret",
|
||||
audience="me",
|
||||
issuer="http://the.issuer",
|
||||
exp_delta=100,
|
||||
nbf=None,
|
||||
email=None,
|
||||
):
|
||||
payload = dict(aud=audience, iss=issuer, exp=time.time() + exp_delta)
|
||||
if email:
|
||||
payload["email"] = email
|
||||
if nbf:
|
||||
payload["nbf"] = nbf
|
||||
return jwt.encode(payload, key=key, algorithm="HS256")
|
||||
|
||||
def _create_validator(
|
||||
self,
|
||||
name,
|
||||
audience="me",
|
||||
issuer="http://the.issuer",
|
||||
secret_key="thesecret",
|
||||
partner_id_required=False,
|
||||
static_user_id=1,
|
||||
):
|
||||
return self.env["auth.jwt.validator"].create(
|
||||
dict(
|
||||
name=name,
|
||||
signature_type="secret",
|
||||
secret_algorithm="HS256",
|
||||
secret_key=secret_key,
|
||||
audience=audience,
|
||||
issuer=issuer,
|
||||
user_id_strategy="static",
|
||||
static_user_id=static_user_id,
|
||||
partner_id_strategy="email",
|
||||
partner_id_required=partner_id_required,
|
||||
)
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
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.",
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
def test_get_validator(self):
|
||||
AuthJwtValidator = self.env["auth.jwt.validator"]
|
||||
AuthJwtValidator.search([]).unlink()
|
||||
with self.assertRaises(JwtValidatorNotFound), mute_logger(
|
||||
"odoo.addons.auth_jwt.models.auth_jwt_validator"
|
||||
):
|
||||
AuthJwtValidator._get_validator_by_name(None)
|
||||
with self.assertRaises(JwtValidatorNotFound), 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
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
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):
|
||||
validator._decode(token)
|
||||
|
||||
def test_auth_method_registration_on_create(self):
|
||||
IrHttp = self.env["ir.http"]
|
||||
self.assertFalse(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
|
||||
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")
|
||||
)
|
||||
|
||||
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()
|
||||
self.assertFalse(hasattr(IrHttp.__class__, "_auth_method_jwt_validator1"))
|
||||
self.assertFalse(
|
||||
hasattr(IrHttp.__class__, "_auth_method_public_or_jwt_validator1")
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
def test_name_check(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self._create_validator(name="not an identifier")
|
||||
|
||||
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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue