Skip to content

Commit

Permalink
[IMP] auth_jwt: Add validator.next_validator_id to allow validator ch…
Browse files Browse the repository at this point in the history
…aining
  • Loading branch information
paradoxxxzero committed Feb 17, 2022
1 parent 3c0888d commit 92b6f28
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 87 deletions.
14 changes: 14 additions & 0 deletions auth_jwt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,17 @@ class UnauthorizedInvalidToken(Unauthorized):

class UnauthorizedPartnerNotFound(Unauthorized):
pass


class CompositeJwtError(Unauthorized):
"""Indicate that multiple errors occurred during JWT chain validation."""

def __init__(self, errors):
self.errors = errors
super().__init__(
"Multiple errors occurred during JWT chain validation:\n"
+ "\n".join(
"{}: {}".format(validator_name, error)
for validator_name, error in self.errors.items()
)
)
22 changes: 22 additions & 0 deletions auth_jwt/models/auth_jwt_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class AuthJwtValidator(models.Model):
partner_id_strategy = fields.Selection([("email", "From email claim")])
partner_id_required = fields.Boolean()

next_validator_id = fields.Many2one(
"auth.jwt.validator",
domain="[('id', '!=', id)]",
help="Next validator to try if this one fails",
)

_sql_constraints = [
("name_uniq", "unique(name)", "JWT validator names must be unique !"),
]
Expand All @@ -79,6 +85,22 @@ def _check_name(self):
_("Name %r is not a valid python identifier.") % (rec.name,)
)

@api.constrains("next_validator_id")
def _check_next_validator_id(self):
# Prevent circular references
for rec in self:
validator = rec
chain = [validator.name]
while validator:
validator = validator.next_validator_id
chain.append(validator.name)
if rec == validator:
raise ValidationError(
_("Validators mustn't make a closed chain: {}.").format(
" -> ".join(chain)
)
)

@api.model
def _get_validator_by_name_domain(self, validator_name):
if validator_name:
Expand Down
36 changes: 25 additions & 11 deletions auth_jwt/models/ir_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import logging
import re

from odoo import SUPERUSER_ID, api, models, registry as registry_get
from odoo import SUPERUSER_ID, api, models
from odoo.http import request

from ..exceptions import (
CompositeJwtError,
UnauthorizedMalformedAuthorizationHeader,
UnauthorizedMissingAuthorizationHeader,
UnauthorizedSessionMismatch,
Expand Down Expand Up @@ -55,20 +56,33 @@ def _authenticate(cls, endpoint):

@classmethod
def _auth_method_jwt(cls, validator_name=None):
assert request.db
assert not request.uid
assert not request.session.uid
token = cls._get_bearer_token()
assert token
registry = registry_get(request.db)
with registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
validator = env["auth.jwt.validator"]._get_validator_by_name(validator_name)
assert len(validator) == 1
payload = validator._decode(token)
uid = validator._get_and_check_uid(payload)
assert uid
partner_id = validator._get_and_check_partner_id(payload)
# # Use request cursor to allow partner creation strategy in validator
env = api.Environment(request.cr, SUPERUSER_ID, {})
validator = env["auth.jwt.validator"]._get_validator_by_name(validator_name)
assert len(validator) == 1

payload = None
exceptions = {}
while validator:
try:
payload = validator._decode(token)
break
except Exception as e:
exceptions[validator.name] = e
validator = validator.next_validator_id

if not payload:
if len(exceptions) == 1:
raise list(exceptions.values())[0]
raise CompositeJwtError(exceptions)

uid = validator._get_and_check_uid(payload)
assert uid
partner_id = validator._get_and_check_partner_id(payload)
request.uid = uid # this resets request.env
request.jwt_payload = payload
request.jwt_partner_id = partner_id
Expand Down
Loading

0 comments on commit 92b6f28

Please sign in to comment.