Initial commit: OCA Server Auth packages (29 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:06 +02:00
commit 3ed80311c4
1325 changed files with 127292 additions and 0 deletions

View file

@ -0,0 +1,157 @@
========
Auth JWT
========
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d22309ac82ef1eb8879974683b10d4be288eb330fd7e250927f1a8d602dc3988
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
:target: https://github.com/OCA/server-auth/tree/16.0/auth_jwt
: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-16-0/server-auth-16-0-auth_jwt
: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=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
JWT bearer token authentication.
**Table of contents**
.. contents::
:local:
Installation
============
This module requires the ``pyjwt`` library to be installed.
Usage
=====
This module lets developpers add a new ``jwt`` authentication method on Odoo
controller routes.
To use it, you must:
* Create an ``auth.jwt.validator`` record to configure how the JWT token will
be validated.
* Add an ``auth="jwt_{validator-name}"`` or ``auth="public_or_jwt_{validator-name}"``
attribute to the routes you want to protect where ``{validator-name}`` corresponds to
the name attribute of the JWT validator record.
The ``auth_jwt_demo`` module provides examples.
The JWT validator can be configured with the following properties:
* ``name``: the validator name, to match the ``auth="jwt_{validator-name}"``
route property.
* ``audience``: a comma-separated list of allowed audiences, used to validate
the ``aud`` claim.
* ``issuer``: used to validate the ``iss`` claim.
* Signature type (secret or public key), algorithm, secret and JWK URI
are used to validate the token signature.
In addition, the ``exp`` claim is validated to reject expired tokens.
If the ``Authorization`` HTTP header is missing, malformed, or contains
an invalid token, the request is rejected with a 401 (Unauthorized) code,
unless the cookie mode is enabled (see below).
If the token is valid, the request executes with the configured user id. By
default the user id selection strategy is ``static`` (i.e. the same for all
requests) and the selected user is configured on the JWT validator. Additional
strategies can be provided by overriding the ``_get_uid()`` method and
extending the ``user_id_strategy`` selection field.
The selected user is *not* stored in the session. It is only available in
``request.uid`` (and thus it is the one used in ``request.env``). To avoid any
confusion and mismatches between the bearer token and the session, this module
rejects requests made with an authenticated user session.
Additionally, if a ``partner_id_strategy`` is configured, a partner is searched
and if found, its id is stored in the ``request.jwt_partner_id`` attribute. If
``partner_id_required`` is set, a 401 (Unauthorized) is returned if no partner
was found. Otherwise ``request.jwt_partner_id`` is left falsy. Additional
strategies can be provided by overriding the ``_get_partner_id()`` method
and extending the ``partner_id_strategy`` selection field.
The decoded JWT payload is stored in ``request.jwt_payload``.
The ``public_auth_jwt`` method delegates authentication to the standard Odoo ``public``
method when the Authorization header is not set. If it is set, the regular JWT
authentication is performed as described above. This method is useful for public
endpoints that need to work for anonymous users, but can be enhanced when an
authenticated user is know. A typical use case is a "add to cart" endpoint that can work
for anonymous users, but can be enhanced by binding the cart to a known customer when
the authenticated user is known.
You can enable a cookie mode on JWT validators. In this case, the JWT payload obtained
from the ``Authorization`` header is returned as a Http-Only cookie. This mode is
sometimes simpler for front-end applications which do not then need to store and protect
the JWT token across requests and can simply rely on the cookie management mechanisms of
browsers. When both the ``Authorization`` header and a cookie are provided, the cookie
is ignored in order to let clients authenticate with a different user by providing a new
JWT token.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/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 <https://github.com/OCA/server-auth/issues/new?body=module:%20auth_jwt%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ACSONE SA/NV
Contributors
~~~~~~~~~~~~
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
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 <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-sbidoul|
This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/16.0/auth_jwt>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,17 @@
# Copyright 2021 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "Auth JWT",
"summary": """
JWT bearer token authentication.""",
"version": "16.0.1.1.0",
"license": "LGPL-3",
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"maintainers": ["sbidoul"],
"website": "https://github.com/OCA/server-auth",
"depends": [],
"external_dependencies": {"python": ["pyjwt", "cryptography"]},
"data": ["security/ir.model.access.csv", "views/auth_jwt_validator_views.xml"],
"demo": [],
}

View file

@ -0,0 +1,54 @@
# Copyright 2021 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)
from werkzeug.exceptions import InternalServerError, Unauthorized
class UnauthorizedMissingAuthorizationHeader(Unauthorized):
pass
class UnauthorizedMissingCookie(Unauthorized):
pass
class UnauthorizedMalformedAuthorizationHeader(Unauthorized):
pass
class UnauthorizedSessionMismatch(Unauthorized):
pass
class AmbiguousJwtValidator(InternalServerError):
pass
class JwtValidatorNotFound(InternalServerError):
pass
class UnauthorizedInvalidToken(Unauthorized):
pass
class UnauthorizedPartnerNotFound(Unauthorized):
pass
class UnauthorizedCompositeJwtError(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()
)
)
class ConfigurationError(InternalServerError):
pass

View file

@ -0,0 +1,346 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_jwt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.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_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid ""
"A cookie name must be provided on JWT validator %s because it has cookie "
"mode enabled."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__audience
msgid "Audience"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__audience
msgid "Comma separated list of audiences, to validate aud."
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid ""
"Convert the JWT token into an HttpOnly Secure cookie. When both an "
"Authorization header and the cookie are present in the request, the cookie "
"is ignored."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Cookie"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid "Cookie Enabled"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Cookie Max Age"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_name
msgid "Cookie Name"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_path
msgid "Cookie Path"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Cookie Secure"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_uid
msgid "Created by"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_date
msgid "Created on"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__display_name
msgid "Display Name"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256
msgid "ES256 - ECDSA using SHA-256"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256k
msgid "ES256K - ECDSA with secp256k1 curve using SHA-256"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es384
msgid "ES384 - ECDSA using SHA-384"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es512
msgid "ES512 - ECDSA using SHA-512"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__partner_id_strategy__email
msgid "From email claim"
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "General"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs256
msgid "HS256 - HMAC using SHA-256 hash algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs384
msgid "HS384 - HMAC using SHA-384 hash algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs512
msgid "HS512 - HMAC using SHA-512 hash algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_ir_http
msgid "HTTP Routing"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__id
msgid "ID"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__issuer
msgid "Issuer"
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "JWK URI"
msgstr ""
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_auth_jwt_validator
msgid "JWT Validator Configuration"
msgstr ""
#. module: auth_jwt
#: model:ir.actions.act_window,name:auth_jwt.action_auth_jwt_validator
#: model:ir.ui.menu,name:auth_jwt.menu_auth_jwt_validator
msgid "JWT Validators"
msgstr ""
#. module: auth_jwt
#: model:ir.model.constraint,message:auth_jwt.constraint_auth_jwt_validator_name_uniq
msgid "JWT validator names must be unique !"
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Key"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator____last_update
msgid "Last Modified on"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_uid
msgid "Last Updated by"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_date
msgid "Last Updated on"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__name
msgid "Name"
msgstr ""
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Name %r is not a valid python identifier."
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next Validator"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next validator to try if this one fails"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Number of seconds until the cookie expires (Max-Age)."
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps256
msgid "PS256 - RSASSA-PSS using SHA-256 and MGF1 padding with SHA-256"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps384
msgid "PS384 - RSASSA-PSS using SHA-384 and MGF1 padding with SHA-384"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps512
msgid "PS512 - RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512"
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Partner"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_required
msgid "Partner Id Required"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_strategy
msgid "Partner Id Strategy"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_algorithm
msgid "Public Key Algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_jwk_uri
msgid "Public Key Jwk Uri"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__public_key
msgid "Public key"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs256
msgid "RS256 - RSASSA-PKCS1-v1_5 using SHA-256"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs384
msgid "RS384 - RSASSA-PKCS1-v1_5 using SHA-384"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs512
msgid "RS512 - RSASSA-PKCS1-v1_5 using SHA-512"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__secret
msgid "Secret"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_algorithm
msgid "Secret Algorithm"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_key
msgid "Secret Key"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Set to false only for development without https."
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__signature_type
msgid "Signature Type"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__user_id_strategy__static
msgid "Static"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__static_user_id
msgid "Static User"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__issuer
msgid "To validate iss."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Token validation"
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "User"
msgstr ""
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__user_id_strategy
msgid "User Id Strategy"
msgstr ""
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Validators mustn't make a closed chain: {}."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "arch"
msgstr ""

View file

@ -0,0 +1,346 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_jwt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.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_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid ""
"A cookie name must be provided on JWT validator %s because it has cookie "
"mode enabled."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Algorithm"
msgstr "Algoritam"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__audience
msgid "Audience"
msgstr "Publika"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__audience
msgid "Comma separated list of audiences, to validate aud."
msgstr "Zarez odvojena lista publika, za validaciju aud."
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid ""
"Convert the JWT token into an HttpOnly Secure cookie. When both an "
"Authorization header and the cookie are present in the request, the cookie "
"is ignored."
msgstr ""
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Cookie"
msgstr "Cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid "Cookie Enabled"
msgstr "Cookie omogućen"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Cookie Max Age"
msgstr "Cookie maksimalno vrijeme"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_name
msgid "Cookie Name"
msgstr "Naziv cookie-ja"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_path
msgid "Cookie Path"
msgstr "Putanja cookie-ja"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Cookie Secure"
msgstr "Cookie siguran"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256
msgid "ES256 - ECDSA using SHA-256"
msgstr "ES256 - ECDSA koristeći SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256k
msgid "ES256K - ECDSA with secp256k1 curve using SHA-256"
msgstr "ES256K - ECDSA sa secp256k1 krivom koristeći SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es384
msgid "ES384 - ECDSA using SHA-384"
msgstr "ES384 - ECDSA koristeći SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es512
msgid "ES512 - ECDSA using SHA-512"
msgstr "ES512 - ECDSA koristeći SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__partner_id_strategy__email
msgid "From email claim"
msgstr "Iz email tvrdnje"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "General"
msgstr "Opšte"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs256
msgid "HS256 - HMAC using SHA-256 hash algorithm"
msgstr "HS256 - HMAC koristeći SHA-256 hash algoritam"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs384
msgid "HS384 - HMAC using SHA-384 hash algorithm"
msgstr "HS384 - HMAC koristeći SHA-384 hash algoritam"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs512
msgid "HS512 - HMAC using SHA-512 hash algorithm"
msgstr "HS512 - HMAC koristeći SHA-512 hash algoritam"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_ir_http
msgid "HTTP Routing"
msgstr "HTTP usmjeravanje"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__id
msgid "ID"
msgstr "ID"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__issuer
msgid "Issuer"
msgstr "Izdavac"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "JWK URI"
msgstr "JWK URI"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_auth_jwt_validator
msgid "JWT Validator Configuration"
msgstr "Konfiguracija JWT validatora"
#. module: auth_jwt
#: model:ir.actions.act_window,name:auth_jwt.action_auth_jwt_validator
#: model:ir.ui.menu,name:auth_jwt.menu_auth_jwt_validator
msgid "JWT Validators"
msgstr "JWT validatori"
#. module: auth_jwt
#: model:ir.model.constraint,message:auth_jwt.constraint_auth_jwt_validator_name_uniq
msgid "JWT validator names must be unique !"
msgstr "Nazivi JWT validatora moraju biti jedinstveni!"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Key"
msgstr "Ključ"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__name
msgid "Name"
msgstr "Naziv:"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Name %r is not a valid python identifier."
msgstr "Naziv %r nije valjani python identifikator."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next Validator"
msgstr "Sljedeći validator"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next validator to try if this one fails"
msgstr "Sljedeći validator za pokušaj ako ovaj ne uspije"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Number of seconds until the cookie expires (Max-Age)."
msgstr "Broj sekundi do isteka cookie-ja (Max-Age)."
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps256
msgid "PS256 - RSASSA-PSS using SHA-256 and MGF1 padding with SHA-256"
msgstr "PS256 - RSASSA-PSS koristeći SHA-256 i MGF1 padding sa SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps384
msgid "PS384 - RSASSA-PSS using SHA-384 and MGF1 padding with SHA-384"
msgstr "PS384 - RSASSA-PSS koristeći SHA-384 i MGF1 padding sa SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps512
msgid "PS512 - RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512"
msgstr "PS512 - RSASSA-PSS koristeći SHA-512 i MGF1 padding sa SHA-512"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Partner"
msgstr "Partner"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_required
msgid "Partner Id Required"
msgstr "Partner ID potreban"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_strategy
msgid "Partner Id Strategy"
msgstr "Strategija Partner ID-a"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_algorithm
msgid "Public Key Algorithm"
msgstr "Algoritam javnog ključa"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_jwk_uri
msgid "Public Key Jwk Uri"
msgstr "Javni ključ JWK URI"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__public_key
msgid "Public key"
msgstr "Javni ključ"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs256
msgid "RS256 - RSASSA-PKCS1-v1_5 using SHA-256"
msgstr "RS256 - RSASSA-PKCS1-v1_5 koristeći SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs384
msgid "RS384 - RSASSA-PKCS1-v1_5 using SHA-384"
msgstr "RS384 - RSASSA-PKCS1-v1_5 koristeći SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs512
msgid "RS512 - RSASSA-PKCS1-v1_5 using SHA-512"
msgstr "RS512 - RSASSA-PKCS1-v1_5 koristeći SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__secret
msgid "Secret"
msgstr "Tajna"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_algorithm
msgid "Secret Algorithm"
msgstr "Algoritam tajnog ključa"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_key
msgid "Secret Key"
msgstr "Tajni ključ"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Set to false only for development without https."
msgstr "Postavite na false samo za razvoj bez https."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__signature_type
msgid "Signature Type"
msgstr "Tip potpisa"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__user_id_strategy__static
msgid "Static"
msgstr "Statičan"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__static_user_id
msgid "Static User"
msgstr "Statičan korisnik"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__issuer
msgid "To validate iss."
msgstr "Za validaciju iss."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Token validation"
msgstr "Validacija tokena"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "User"
msgstr "Korisnik"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__user_id_strategy
msgid "User Id Strategy"
msgstr "Strategija ID korisnika"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Validators mustn't make a closed chain: {}."
msgstr "Validatori ne smiju praviti zatvoreni lanac: {}."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "arch"
msgstr "arhitektura"

View file

@ -0,0 +1,357 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_jwt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-09-02 19:25+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\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 4.17\n"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid ""
"A cookie name must be provided on JWT validator %s because it has cookie "
"mode enabled."
msgstr ""
"Se debe proporcionar un nombre de cookie en el validador JWT %s porque tiene "
"habilitado el modo cookie."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Algorithm"
msgstr "Algoritmo"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__audience
msgid "Audience"
msgstr "Audiencia"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__audience
msgid "Comma separated list of audiences, to validate aud."
msgstr "Lista de audiencias separada por comas, para validar aud."
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid ""
"Convert the JWT token into an HttpOnly Secure cookie. When both an "
"Authorization header and the cookie are present in the request, the cookie "
"is ignored."
msgstr ""
"Convierte el código JWT en una cookie HttpOnly Secure. Cuando tanto la "
"cabecera de autorización como la cookie están presentes en la solicitud, se "
"ignora la cookie."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Cookie"
msgstr ""
"Paquete de datos que un programa recibe y reenvía sin cambiarlos y que "
"normalmente se emplea para indicar que ha ocurrido un evento o situación "
"especial"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid "Cookie Enabled"
msgstr "Cookie habilitada"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Cookie Max Age"
msgstr "Cookie Edad Máxima"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_name
msgid "Cookie Name"
msgstr "Nombre de la cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_path
msgid "Cookie Path"
msgstr "Ruta de Cookies"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Cookie Secure"
msgstr "Cookie segura"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_date
msgid "Created on"
msgstr "Creado el"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__display_name
msgid "Display Name"
msgstr "Mostrar Nombre"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256
msgid "ES256 - ECDSA using SHA-256"
msgstr "ES256 - ECDSA utilizando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256k
msgid "ES256K - ECDSA with secp256k1 curve using SHA-256"
msgstr "ES256K - ECDSA con curva secp256k1 utilizando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es384
msgid "ES384 - ECDSA using SHA-384"
msgstr "ES384 - ECDSA utilizando SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es512
msgid "ES512 - ECDSA using SHA-512"
msgstr "ES512 - ECDSA utilizando SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__partner_id_strategy__email
msgid "From email claim"
msgstr "De la reclamación por correo electrónico"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "General"
msgstr "General"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs256
msgid "HS256 - HMAC using SHA-256 hash algorithm"
msgstr "HS256 - HMAC utilizando el algoritmo hash SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs384
msgid "HS384 - HMAC using SHA-384 hash algorithm"
msgstr "HS384 - HMAC utilizando el algoritmo hash SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs512
msgid "HS512 - HMAC using SHA-512 hash algorithm"
msgstr "HS512 - HMAC utilizando el algoritmo hash SHA-512"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_ir_http
msgid "HTTP Routing"
msgstr "Enrutamiento HTTP"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__id
msgid "ID"
msgstr "ID (identificación)"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__issuer
msgid "Issuer"
msgstr "Emisor"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "JWK URI"
msgstr "URI DE JWK"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_auth_jwt_validator
msgid "JWT Validator Configuration"
msgstr "Configuración del validador JWT"
#. module: auth_jwt
#: model:ir.actions.act_window,name:auth_jwt.action_auth_jwt_validator
#: model:ir.ui.menu,name:auth_jwt.menu_auth_jwt_validator
msgid "JWT Validators"
msgstr "Validadores JWT"
#. module: auth_jwt
#: model:ir.model.constraint,message:auth_jwt.constraint_auth_jwt_validator_name_uniq
msgid "JWT validator names must be unique !"
msgstr "¡Los nombres de los validadores JWT deben ser únicos!"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Key"
msgstr "Clave"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__name
msgid "Name"
msgstr "Nombre"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Name %r is not a valid python identifier."
msgstr "El nombre %r no es un identificador python válido."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next Validator"
msgstr "Siguiente Validador"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next validator to try if this one fails"
msgstr "Siguiente validador a probar si éste falla"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Number of seconds until the cookie expires (Max-Age)."
msgstr "Número de segundos hasta que expira la cookie (Max-Age)."
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps256
msgid "PS256 - RSASSA-PSS using SHA-256 and MGF1 padding with SHA-256"
msgstr "PS256 - RSASSA-PSS utilizando SHA-256 y relleno MGF1 con SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps384
msgid "PS384 - RSASSA-PSS using SHA-384 and MGF1 padding with SHA-384"
msgstr "PS384 - RSASSA-PSS utilizando SHA-384 y relleno MGF1 con SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps512
msgid "PS512 - RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512"
msgstr "PS512 - RSASSA-PSS utilizando SHA-512 y relleno MGF1 con SHA-512"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Partner"
msgstr "Socio"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_required
msgid "Partner Id Required"
msgstr "Id de socio Obligatorio"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_strategy
msgid "Partner Id Strategy"
msgstr "Estrategia de ID de socio"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_algorithm
msgid "Public Key Algorithm"
msgstr "Algoritmo de clave pública"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_jwk_uri
msgid "Public Key Jwk Uri"
msgstr "Clave pública Jwk Uri"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__public_key
msgid "Public key"
msgstr "Clave pública"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs256
msgid "RS256 - RSASSA-PKCS1-v1_5 using SHA-256"
msgstr "RS256 - RSASSA-PKCS1-v1_5 utilizando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs384
msgid "RS384 - RSASSA-PKCS1-v1_5 using SHA-384"
msgstr "RS384 - RSASSA-PKCS1-v1_5 utilizando SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs512
msgid "RS512 - RSASSA-PKCS1-v1_5 using SHA-512"
msgstr "RS512 - RSASSA-PKCS1-v1_5 utilizando SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__secret
msgid "Secret"
msgstr "Secreto"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_algorithm
msgid "Secret Algorithm"
msgstr "Algoritmo secreto"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_key
msgid "Secret Key"
msgstr "Clave secreta"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Set to false only for development without https."
msgstr "Establecer a Falso sólo para el desarrollo sin https."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__signature_type
msgid "Signature Type"
msgstr "Tipo de firma"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__user_id_strategy__static
msgid "Static"
msgstr "Estático"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__static_user_id
msgid "Static User"
msgstr "Usuario estático"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__issuer
msgid "To validate iss."
msgstr "Para validar el iss."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Token validation"
msgstr "Validación de símbolos"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "User"
msgstr "Usuario"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__user_id_strategy
msgid "User Id Strategy"
msgstr "Estrategia de ID de usuario"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Validators mustn't make a closed chain: {}."
msgstr "Los validadores no deben hacer una cadena cerrada: {}."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "arch"
msgstr "arch"

View file

@ -0,0 +1,354 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_jwt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-29 11:35+0000\n"
"Last-Translator: Francesco Foresti <francesco.foresti@ooops404.com>\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 4.17\n"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid ""
"A cookie name must be provided on JWT validator %s because it has cookie "
"mode enabled."
msgstr ""
"È necessario fornire un nome del cookie sul validatore JWT %s perché ha la "
"modalità cookie abilitata."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Algorithm"
msgstr "Algoritmo"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__audience
msgid "Audience"
msgstr "Audience"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__audience
msgid "Comma separated list of audiences, to validate aud."
msgstr "Elenco di audience separati da virgole, per validare aud."
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid ""
"Convert the JWT token into an HttpOnly Secure cookie. When both an "
"Authorization header and the cookie are present in the request, the cookie "
"is ignored."
msgstr ""
"Converti il token JWT in un cookie HttpOnly Secure. Quando nella richiesta "
"sono presenti sia un Authorization header che il cookie, il cookie viene "
"ignorato."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Cookie"
msgstr "Cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_enabled
msgid "Cookie Enabled"
msgstr "Cookie abilitato"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Cookie Max Age"
msgstr "Durata massima cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_name
msgid "Cookie Name"
msgstr "Nome cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_path
msgid "Cookie Path"
msgstr "Path cookie"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Cookie Secure"
msgstr "Cookie secure"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__create_date
msgid "Created on"
msgstr "Creato il"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256
msgid "ES256 - ECDSA using SHA-256"
msgstr "ES256 - ECDSA usando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es256k
msgid "ES256K - ECDSA with secp256k1 curve using SHA-256"
msgstr "ES256K - ECDSA con curva secp256k1 usando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es384
msgid "ES384 - ECDSA using SHA-384"
msgstr "ES384 - ECDSA usando SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__es512
msgid "ES512 - ECDSA using SHA-512"
msgstr "ES512 - ECDSA usando SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__partner_id_strategy__email
msgid "From email claim"
msgstr "Da richiesta e-mail"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "General"
msgstr "Generale"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs256
msgid "HS256 - HMAC using SHA-256 hash algorithm"
msgstr "HS256 - HMAC usando SHA-256 hash algorithm"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs384
msgid "HS384 - HMAC using SHA-384 hash algorithm"
msgstr "HS384 - HMAC usando SHA-384 hash algorithm"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__secret_algorithm__hs512
msgid "HS512 - HMAC using SHA-512 hash algorithm"
msgstr "HS512 - HMAC usando SHA-512 hash algorithm"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_ir_http
msgid "HTTP Routing"
msgstr "Instradamento HTTP"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__id
msgid "ID"
msgstr "ID"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__issuer
msgid "Issuer"
msgstr "Segnalatore"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "JWK URI"
msgstr "URI JWK"
#. module: auth_jwt
#: model:ir.model,name:auth_jwt.model_auth_jwt_validator
msgid "JWT Validator Configuration"
msgstr "Configurazione validatore JWT"
#. module: auth_jwt
#: model:ir.actions.act_window,name:auth_jwt.action_auth_jwt_validator
#: model:ir.ui.menu,name:auth_jwt.menu_auth_jwt_validator
msgid "JWT Validators"
msgstr "Validatori JWT"
#. module: auth_jwt
#: model:ir.model.constraint,message:auth_jwt.constraint_auth_jwt_validator_name_uniq
msgid "JWT validator names must be unique !"
msgstr "I nomi dei validatori JWT devono essere univoci!"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Key"
msgstr "Chiave"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__name
msgid "Name"
msgstr "Nome"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Name %r is not a valid python identifier."
msgstr "Il nome %r non è un identificatore Python valido."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next Validator"
msgstr "Validatore successivo"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__next_validator_id
msgid "Next validator to try if this one fails"
msgstr "Validatore successivo da provare se questo fallisce"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_max_age
msgid "Number of seconds until the cookie expires (Max-Age)."
msgstr "Numero di secondi fino alla scadenza del cookie (Durata max)."
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps256
msgid "PS256 - RSASSA-PSS using SHA-256 and MGF1 padding with SHA-256"
msgstr "PS256 - RSASSA-PSS usando SHA-256 e padding MGF1 con SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps384
msgid "PS384 - RSASSA-PSS using SHA-384 and MGF1 padding with SHA-384"
msgstr "PS384 - RSASSA-PSS usando SHA-384 e padding MGF1 con SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__ps512
msgid "PS512 - RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512"
msgstr "PS512 - RSASSA-PSS usando SHA-512 e padding MGF1 con SHA-512"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Partner"
msgstr "Partner"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_required
msgid "Partner Id Required"
msgstr "Partner ID obbligatorio"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__partner_id_strategy
msgid "Partner Id Strategy"
msgstr "Strategia Partner ID"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_algorithm
msgid "Public Key Algorithm"
msgstr "Algoritmo a chiave pubblica"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__public_key_jwk_uri
msgid "Public Key Jwk Uri"
msgstr "Jwk Uri a chiave pubblica"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__public_key
msgid "Public key"
msgstr "Chiave pubblica"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs256
msgid "RS256 - RSASSA-PKCS1-v1_5 using SHA-256"
msgstr "RS256 - RSASSA-PKCS1-v1_5 usando SHA-256"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs384
msgid "RS384 - RSASSA-PKCS1-v1_5 using SHA-384"
msgstr "RS384 - RSASSA-PKCS1-v1_5 usando SHA-384"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__public_key_algorithm__rs512
msgid "RS512 - RSASSA-PKCS1-v1_5 using SHA-512"
msgstr "RS512 - RSASSA-PKCS1-v1_5 usando SHA-512"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__signature_type__secret
msgid "Secret"
msgstr "Segreta"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_algorithm
msgid "Secret Algorithm"
msgstr "Algoritmo segreto"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__secret_key
msgid "Secret Key"
msgstr "Chiave segreta"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__cookie_secure
msgid "Set to false only for development without https."
msgstr "Imposta su false solo per lo sviluppo senza https."
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__signature_type
msgid "Signature Type"
msgstr "Tipo di firma"
#. module: auth_jwt
#: model:ir.model.fields.selection,name:auth_jwt.selection__auth_jwt_validator__user_id_strategy__static
msgid "Static"
msgstr "Statica"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__static_user_id
msgid "Static User"
msgstr "Utente statico"
#. module: auth_jwt
#: model:ir.model.fields,help:auth_jwt.field_auth_jwt_validator__issuer
msgid "To validate iss."
msgstr "Per validare iss."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "Token validation"
msgstr "Convalida del token"
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "User"
msgstr "Utente"
#. module: auth_jwt
#: model:ir.model.fields,field_description:auth_jwt.field_auth_jwt_validator__user_id_strategy
msgid "User Id Strategy"
msgstr "Strategia User ID"
#. module: auth_jwt
#. odoo-python
#: code:addons/auth_jwt/models/auth_jwt_validator.py:0
#, python-format
msgid "Validators mustn't make a closed chain: {}."
msgstr "I validatori non devono creare una catena chiusa: {}."
#. module: auth_jwt
#: model_terms:ir.ui.view,arch_db:auth_jwt.view_auth_jwt_validator_form
msgid "arch"
msgstr "arch"

View file

@ -0,0 +1,2 @@
from . import auth_jwt_validator
from . import ir_http

View file

@ -0,0 +1,316 @@
# Copyright 2021 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import datetime
import logging
import re
from calendar import timegm
from functools import partial
import jwt # pylint: disable=missing-manifest-dependency
from jwt import PyJWKClient
from werkzeug.exceptions import InternalServerError
from odoo import _, api, fields, models, tools
from odoo.exceptions import ValidationError
from ..exceptions import (
AmbiguousJwtValidator,
ConfigurationError,
JwtValidatorNotFound,
UnauthorizedInvalidToken,
UnauthorizedMalformedAuthorizationHeader,
UnauthorizedMissingAuthorizationHeader,
UnauthorizedPartnerNotFound,
)
_logger = logging.getLogger(__name__)
AUTHORIZATION_RE = re.compile(r"^Bearer ([^ ]+)$")
class AuthJwtValidator(models.Model):
_name = "auth.jwt.validator"
_description = "JWT Validator Configuration"
name = fields.Char(required=True)
signature_type = fields.Selection(
[("secret", "Secret"), ("public_key", "Public key")], required=True
)
secret_key = fields.Char()
secret_algorithm = fields.Selection(
[
# https://pyjwt.readthedocs.io/en/stable/algorithms.html
("HS256", "HS256 - HMAC using SHA-256 hash algorithm"),
("HS384", "HS384 - HMAC using SHA-384 hash algorithm"),
("HS512", "HS512 - HMAC using SHA-512 hash algorithm"),
],
default="HS256",
)
public_key_jwk_uri = fields.Char()
public_key_algorithm = fields.Selection(
[
# https://pyjwt.readthedocs.io/en/stable/algorithms.html
("ES256", "ES256 - ECDSA using SHA-256"),
("ES256K", "ES256K - ECDSA with secp256k1 curve using SHA-256"),
("ES384", "ES384 - ECDSA using SHA-384"),
("ES512", "ES512 - ECDSA using SHA-512"),
("RS256", "RS256 - RSASSA-PKCS1-v1_5 using SHA-256"),
("RS384", "RS384 - RSASSA-PKCS1-v1_5 using SHA-384"),
("RS512", "RS512 - RSASSA-PKCS1-v1_5 using SHA-512"),
("PS256", "PS256 - RSASSA-PSS using SHA-256 and MGF1 padding with SHA-256"),
("PS384", "PS384 - RSASSA-PSS using SHA-384 and MGF1 padding with SHA-384"),
("PS512", "PS512 - RSASSA-PSS using SHA-512 and MGF1 padding with SHA-512"),
],
default="RS256",
)
audience = fields.Char(
required=True, help="Comma separated list of audiences, to validate aud."
)
issuer = fields.Char(required=True, help="To validate iss.")
user_id_strategy = fields.Selection(
[("static", "Static")], required=True, default="static"
)
static_user_id = fields.Many2one("res.users", default=1)
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",
)
cookie_enabled = fields.Boolean(
help=(
"Convert the JWT token into an HttpOnly Secure cookie. "
"When both an Authorization header and the cookie are present "
"in the request, the cookie is ignored."
)
)
cookie_name = fields.Char(default="authorization")
cookie_path = fields.Char(default="/")
cookie_max_age = fields.Integer(
default=86400 * 365,
help="Number of seconds until the cookie expires (Max-Age).",
)
cookie_secure = fields.Boolean(
default=True, help="Set to false only for development without https."
)
_sql_constraints = [
("name_uniq", "unique(name)", "JWT validator names must be unique !"),
]
@api.constrains("name")
def _check_name(self):
for rec in self:
if not rec.name.isidentifier():
raise ValidationError(
_("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.constrains("cookie_enabled", "cookie_name")
def _check_cookie_name(self):
for rec in self:
if rec.cookie_enabled and not rec.cookie_name:
raise ValidationError(
_(
"A cookie name must be provided on JWT validator %s "
"because it has cookie mode enabled."
)
% (rec.name,)
)
@api.model
def _get_validator_by_name_domain(self, validator_name):
if validator_name:
return [("name", "=", validator_name)]
return []
@api.model
def _get_validator_by_name(self, validator_name):
domain = self._get_validator_by_name_domain(validator_name)
validator = self.search(domain)
if not validator:
_logger.error("JWT validator not found for name %r", validator_name)
raise JwtValidatorNotFound()
if len(validator) != 1:
_logger.error(
"More than one JWT validator found for name %r", validator_name
)
raise AmbiguousJwtValidator()
return validator
@tools.ormcache("self.public_key_jwk_uri", "kid")
def _get_key(self, kid):
jwks_client = PyJWKClient(self.public_key_jwk_uri, cache_keys=False)
return jwks_client.get_signing_key(kid).key
def _encode(self, payload, secret, expire):
"""Encode and sign a JWT payload so it can be decoded and validated with
_decode().
The aud and iss claims are set to this validator's values.
The exp claim is set according to the expire parameter.
"""
payload = dict(
payload,
exp=timegm(datetime.datetime.utcnow().utctimetuple()) + expire,
aud=self.audience,
iss=self.issuer,
)
return jwt.encode(payload, key=secret, algorithm="HS256")
def _decode(self, token, secret=None):
"""Validate and decode a JWT token, return the payload."""
if secret:
key = secret
algorithm = "HS256"
elif self.signature_type == "secret":
key = self.secret_key
algorithm = self.secret_algorithm
else:
try:
header = jwt.get_unverified_header(token)
except Exception as e:
_logger.info("Invalid token: %s", e)
raise UnauthorizedInvalidToken() from e
key = self._get_key(header.get("kid"))
algorithm = self.public_key_algorithm
try:
payload = jwt.decode(
token,
key=key,
algorithms=[algorithm],
options=dict(
require=["exp", "aud", "iss"],
verify_exp=True,
verify_aud=True,
verify_iss=True,
),
audience=self.audience.split(","),
issuer=self.issuer,
)
except Exception as e:
_logger.info("Invalid token: %s", e)
raise UnauthorizedInvalidToken() from e
return payload
def _get_uid(self, payload):
# override for additional strategies
if self.user_id_strategy == "static":
return self.static_user_id.id
def _get_and_check_uid(self, payload):
uid = self._get_uid(payload)
if not uid:
_logger.error("_get_uid did not return a user id")
raise InternalServerError()
return uid
def _get_partner_id(self, payload):
# override for additional strategies
if self.partner_id_strategy == "email":
email = payload.get("email")
if not email:
_logger.debug("JWT payload does not have an email claim")
return
partner = self.env["res.partner"].search([("email", "=", email)])
if len(partner) != 1:
_logger.debug("%d partners found for email %s", len(partner), email)
return
return partner.id
def _get_and_check_partner_id(self, payload):
partner_id = self._get_partner_id(payload)
if not partner_id and self.partner_id_required:
raise UnauthorizedPartnerNotFound()
return partner_id
def _register_hook(self):
res = super()._register_hook()
self.search([])._register_auth_method()
return res
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
),
)
def _unregister_auth_method(self):
IrHttp = self.env["ir.http"]
for rec in self:
try:
delattr(IrHttp.__class__, f"_auth_method_jwt_{rec.name}")
delattr(IrHttp.__class__, f"_auth_method_public_or_jwt_{rec.name}")
except AttributeError: # pylint: disable=except-pass
pass
@api.model_create_multi
def create(self, vals):
rec = super().create(vals)
rec._register_auth_method()
return rec
def write(self, vals):
if "name" in vals:
self._unregister_auth_method()
res = super().write(vals)
self._register_auth_method()
return res
def unlink(self):
self._unregister_auth_method()
return super().unlink()
def _get_jwt_cookie_secret(self):
secret = self.env["ir.config_parameter"].sudo().get_param("database.secret")
if not secret:
_logger.error("database.secret system parameter is not set.")
raise ConfigurationError()
return secret
@api.model
def _parse_bearer_authorization(self, authorization):
"""Parse a Bearer token authorization header and return the token.
Raises UnauthorizedMissingAuthorizationHeader if authorization is falsy.
Raises UnauthorizedMalformedAuthorizationHeader if invalid.
"""
if not authorization:
_logger.info("Missing Authorization header.")
raise UnauthorizedMissingAuthorizationHeader()
# https://tools.ietf.org/html/rfc6750#section-2.1
mo = AUTHORIZATION_RE.match(authorization)
if not mo:
_logger.info("Malformed Authorization header.")
raise UnauthorizedMalformedAuthorizationHeader()
return mo.group(1)

View file

@ -0,0 +1,144 @@
# Copyright 2021 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
from odoo import SUPERUSER_ID, api, models
from odoo.http import request
from ..exceptions import (
ConfigurationError,
Unauthorized,
UnauthorizedCompositeJwtError,
UnauthorizedMissingAuthorizationHeader,
UnauthorizedMissingCookie,
UnauthorizedSessionMismatch,
)
_logger = logging.getLogger(__name__)
class IrHttpJwt(models.AbstractModel):
_inherit = "ir.http"
@classmethod
def _authenticate(cls, endpoint):
"""Protect the _authenticate method.
This is to ensure that the _authenticate method is called
in the correct conditions to invoke _auth_method_jwt below.
When migrating, review this method carefully by reading the original
_authenticate method and make sure the conditions have not changed.
"""
auth_method = endpoint.routing["auth"]
if (
auth_method in ("jwt", "public_or_jwt")
or auth_method.startswith("jwt_")
or auth_method.startswith("public_or_jwt_")
):
if request.session.uid:
_logger.warning(
'A route with auth="jwt" must not be used within a user session.'
)
raise UnauthorizedSessionMismatch()
# Odoo calls _authenticate more than once (in v14? why?), so
# on the second call we have a request uid and that is not an error
# because _authenticate will not call _auth_method_jwt a second time.
if request.uid and not hasattr(request, "jwt_payload"):
_logger.error(
"A route with auth='jwt' should not have a request.uid here."
)
raise UnauthorizedSessionMismatch()
return super()._authenticate(endpoint)
@classmethod
def _get_jwt_payload(cls, validator):
"""Obtain and validate the JWT payload from the request authorization header or
cookie."""
try:
token = cls._get_bearer_token()
assert token
return validator._decode(token)
except UnauthorizedMissingAuthorizationHeader:
if not validator.cookie_enabled:
raise
token = cls._get_cookie_token(validator.cookie_name)
assert token
return validator._decode(token, secret=validator._get_jwt_cookie_secret())
@classmethod
def _auth_method_jwt(cls, validator_name=None):
assert not request.uid
assert not request.session.uid
# # 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 = cls._get_jwt_payload(validator)
break
except Unauthorized as e:
exceptions[validator.name] = e
validator = validator.next_validator_id
if not payload:
if len(exceptions) == 1:
raise list(exceptions.values())[0]
raise UnauthorizedCompositeJwtError(exceptions)
if validator.cookie_enabled:
if not validator.cookie_name:
_logger.info("Cookie name not set for validator %s", validator.name)
raise ConfigurationError()
request.future_response.set_cookie(
key=validator.cookie_name,
value=validator._encode(
payload,
secret=validator._get_jwt_cookie_secret(),
expire=validator.cookie_max_age,
),
max_age=validator.cookie_max_age,
path=validator.cookie_path or "/",
secure=validator.cookie_secure,
httponly=True,
)
uid = validator._get_and_check_uid(payload)
assert uid
partner_id = validator._get_and_check_partner_id(payload)
request.update_env(user=uid)
request.jwt_payload = payload
request.jwt_partner_id = partner_id
@classmethod
def _auth_method_public_or_jwt(cls, validator_name=None):
if "HTTP_AUTHORIZATION" not in request.httprequest.environ:
env = api.Environment(request.cr, SUPERUSER_ID, {})
validator = env["auth.jwt.validator"]._get_validator_by_name(validator_name)
assert len(validator) == 1
if not validator.cookie_enabled or not request.httprequest.cookies.get(
validator.cookie_name
):
return cls._auth_method_public()
return cls._auth_method_jwt(validator_name)
@classmethod
def _get_bearer_token(cls):
# https://tools.ietf.org/html/rfc2617#section-3.2.2
authorization = request.httprequest.environ.get("HTTP_AUTHORIZATION")
return request.env["auth.jwt.validator"]._parse_bearer_authorization(
authorization
)
@classmethod
def _get_cookie_token(cls, cookie_name):
token = request.httprequest.cookies.get(cookie_name)
if not token:
_logger.info("Missing cookie %s.", cookie_name)
raise UnauthorizedMissingCookie()
return token

View file

@ -0,0 +1 @@
* Stéphane Bidoul <stephane.bidoul@acsone.eu>

View file

@ -0,0 +1 @@
JWT bearer token authentication.

View file

@ -0,0 +1 @@
This module requires the ``pyjwt`` library to be installed.

View file

@ -0,0 +1,64 @@
This module lets developpers add a new ``jwt`` authentication method on Odoo
controller routes.
To use it, you must:
* Create an ``auth.jwt.validator`` record to configure how the JWT token will
be validated.
* Add an ``auth="jwt_{validator-name}"`` or ``auth="public_or_jwt_{validator-name}"``
attribute to the routes you want to protect where ``{validator-name}`` corresponds to
the name attribute of the JWT validator record.
The ``auth_jwt_demo`` module provides examples.
The JWT validator can be configured with the following properties:
* ``name``: the validator name, to match the ``auth="jwt_{validator-name}"``
route property.
* ``audience``: a comma-separated list of allowed audiences, used to validate
the ``aud`` claim.
* ``issuer``: used to validate the ``iss`` claim.
* Signature type (secret or public key), algorithm, secret and JWK URI
are used to validate the token signature.
In addition, the ``exp`` claim is validated to reject expired tokens.
If the ``Authorization`` HTTP header is missing, malformed, or contains
an invalid token, the request is rejected with a 401 (Unauthorized) code,
unless the cookie mode is enabled (see below).
If the token is valid, the request executes with the configured user id. By
default the user id selection strategy is ``static`` (i.e. the same for all
requests) and the selected user is configured on the JWT validator. Additional
strategies can be provided by overriding the ``_get_uid()`` method and
extending the ``user_id_strategy`` selection field.
The selected user is *not* stored in the session. It is only available in
``request.uid`` (and thus it is the one used in ``request.env``). To avoid any
confusion and mismatches between the bearer token and the session, this module
rejects requests made with an authenticated user session.
Additionally, if a ``partner_id_strategy`` is configured, a partner is searched
and if found, its id is stored in the ``request.jwt_partner_id`` attribute. If
``partner_id_required`` is set, a 401 (Unauthorized) is returned if no partner
was found. Otherwise ``request.jwt_partner_id`` is left falsy. Additional
strategies can be provided by overriding the ``_get_partner_id()`` method
and extending the ``partner_id_strategy`` selection field.
The decoded JWT payload is stored in ``request.jwt_payload``.
The ``public_auth_jwt`` method delegates authentication to the standard Odoo ``public``
method when the Authorization header is not set. If it is set, the regular JWT
authentication is performed as described above. This method is useful for public
endpoints that need to work for anonymous users, but can be enhanced when an
authenticated user is know. A typical use case is a "add to cart" endpoint that can work
for anonymous users, but can be enhanced by binding the cart to a known customer when
the authenticated user is known.
You can enable a cookie mode on JWT validators. In this case, the JWT payload obtained
from the ``Authorization`` header is returned as a Http-Only cookie. This mode is
sometimes simpler for front-end applications which do not then need to store and protect
the JWT token across requests and can simply rely on the cookie management mechanisms of
browsers. When both the ``Authorization`` header and a cookie are provided, the cookie
is ignored in order to let clients authenticate with a different user by providing a new
JWT token.

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_auth_jwt_validator_admin,auth_jwt_validator admin,model_auth_jwt_validator,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_auth_jwt_validator_admin auth_jwt_validator admin model_auth_jwt_validator base.group_system 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,487 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Auth JWT</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="auth-jwt">
<h1 class="title">Auth JWT</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d22309ac82ef1eb8879974683b10d4be288eb330fd7e250927f1a8d602dc3988
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-auth/tree/16.0/auth_jwt"><img alt="OCA/server-auth" src="https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-auth_jwt"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-auth&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>JWT bearer token authentication.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="toc-entry-1">Installation</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#toc-entry-1">Installation</a></h1>
<p>This module requires the <tt class="docutils literal">pyjwt</tt> library to be installed.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
<p>This module lets developpers add a new <tt class="docutils literal">jwt</tt> authentication method on Odoo
controller routes.</p>
<p>To use it, you must:</p>
<ul class="simple">
<li>Create an <tt class="docutils literal">auth.jwt.validator</tt> record to configure how the JWT token will
be validated.</li>
<li>Add an <tt class="docutils literal"><span class="pre">auth=&quot;jwt_{validator-name}&quot;</span></tt> or <tt class="docutils literal"><span class="pre">auth=&quot;public_or_jwt_{validator-name}&quot;</span></tt>
attribute to the routes you want to protect where <tt class="docutils literal"><span class="pre">{validator-name}</span></tt> corresponds to
the name attribute of the JWT validator record.</li>
</ul>
<p>The <tt class="docutils literal">auth_jwt_demo</tt> module provides examples.</p>
<p>The JWT validator can be configured with the following properties:</p>
<ul class="simple">
<li><tt class="docutils literal">name</tt>: the validator name, to match the <tt class="docutils literal"><span class="pre">auth=&quot;jwt_{validator-name}&quot;</span></tt>
route property.</li>
<li><tt class="docutils literal">audience</tt>: a comma-separated list of allowed audiences, used to validate
the <tt class="docutils literal">aud</tt> claim.</li>
<li><tt class="docutils literal">issuer</tt>: used to validate the <tt class="docutils literal">iss</tt> claim.</li>
<li>Signature type (secret or public key), algorithm, secret and JWK URI
are used to validate the token signature.</li>
</ul>
<p>In addition, the <tt class="docutils literal">exp</tt> claim is validated to reject expired tokens.</p>
<p>If the <tt class="docutils literal">Authorization</tt> HTTP header is missing, malformed, or contains
an invalid token, the request is rejected with a 401 (Unauthorized) code,
unless the cookie mode is enabled (see below).</p>
<p>If the token is valid, the request executes with the configured user id. By
default the user id selection strategy is <tt class="docutils literal">static</tt> (i.e. the same for all
requests) and the selected user is configured on the JWT validator. Additional
strategies can be provided by overriding the <tt class="docutils literal">_get_uid()</tt> method and
extending the <tt class="docutils literal">user_id_strategy</tt> selection field.</p>
<p>The selected user is <em>not</em> stored in the session. It is only available in
<tt class="docutils literal">request.uid</tt> (and thus it is the one used in <tt class="docutils literal">request.env</tt>). To avoid any
confusion and mismatches between the bearer token and the session, this module
rejects requests made with an authenticated user session.</p>
<p>Additionally, if a <tt class="docutils literal">partner_id_strategy</tt> is configured, a partner is searched
and if found, its id is stored in the <tt class="docutils literal">request.jwt_partner_id</tt> attribute. If
<tt class="docutils literal">partner_id_required</tt> is set, a 401 (Unauthorized) is returned if no partner
was found. Otherwise <tt class="docutils literal">request.jwt_partner_id</tt> is left falsy. Additional
strategies can be provided by overriding the <tt class="docutils literal">_get_partner_id()</tt> method
and extending the <tt class="docutils literal">partner_id_strategy</tt> selection field.</p>
<p>The decoded JWT payload is stored in <tt class="docutils literal">request.jwt_payload</tt>.</p>
<p>The <tt class="docutils literal">public_auth_jwt</tt> method delegates authentication to the standard Odoo <tt class="docutils literal">public</tt>
method when the Authorization header is not set. If it is set, the regular JWT
authentication is performed as described above. This method is useful for public
endpoints that need to work for anonymous users, but can be enhanced when an
authenticated user is know. A typical use case is a “add to cart” endpoint that can work
for anonymous users, but can be enhanced by binding the cart to a known customer when
the authenticated user is known.</p>
<p>You can enable a cookie mode on JWT validators. In this case, the JWT payload obtained
from the <tt class="docutils literal">Authorization</tt> header is returned as a Http-Only cookie. This mode is
sometimes simpler for front-end applications which do not then need to store and protect
the JWT token across requests and can simply rely on the cookie management mechanisms of
browsers. When both the <tt class="docutils literal">Authorization</tt> header and a cookie are provided, the cookie
is ignored in order to let clients authenticate with a different user by providing a new
JWT token.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-auth/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/server-auth/issues/new?body=module:%20auth_jwt%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>ACSONE SA/NV</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li>Stéphane Bidoul &lt;<a class="reference external" href="mailto:stephane.bidoul&#64;acsone.eu">stephane.bidoul&#64;acsone.eu</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/sbidoul"><img alt="sbidoul" src="https://github.com/sbidoul.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-auth/tree/16.0/auth_jwt">OCA/server-auth</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
from . import test_auth_jwt

View file

@ -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"

View file

@ -0,0 +1,106 @@
<?xml version="1.0" ?>
<odoo>
<record id="view_auth_jwt_validator_form" model="ir.ui.view">
<field name="name">auth.jwt.validator.form</field>
<field name="model">auth.jwt.validator</field>
<field name="arch" type="xml">
<form string="arch">
<sheet>
<group col="4">
<group colspan="2" string="General">
<field name="name" />
<field name="next_validator_id" />
</group>
<group colspan="2" string="Token validation">
<field name="audience" />
<field name="issuer" />
<field name="signature_type" />
<field
name="secret_key"
string="Key"
attrs="{'invisible': [('signature_type', '!=', 'secret')],
'required': [('signature_type', '=', 'secret')]}"
/>
<field
name="secret_algorithm"
string="Algorithm"
attrs="{'invisible': [('signature_type', '!=', 'secret')],
'required': [('signature_type', '=', 'secret')]}"
/>
<field
name="public_key_jwk_uri"
string="JWK URI"
attrs="{'invisible': [('signature_type', '!=', 'public_key')],
'required': [('signature_type', '=', 'public_key')]}"
widget="url"
/>
<field
name="public_key_algorithm"
string="Algorithm"
attrs="{'invisible': [('signature_type', '!=', 'public_key')],
'required': [('signature_type', '=', 'public_key')]}"
/>
</group>
<group colspan="2" string="User">
<field name="user_id_strategy" />
<field
name="static_user_id"
attrs="{'invisible': [('user_id_strategy', '!=', 'static')],
'required': [('user_id_strategy', '=', 'static')]}"
/>
</group>
<group colspan="2" string="Partner">
<field name="partner_id_strategy" />
<field name="partner_id_required" />
</group>
<group colspan="2" string="Cookie">
<field name="cookie_enabled" />
<field
name="cookie_name"
attrs="{'required': [('cookie_enabled', '=', True)],
'invisible': [('cookie_enabled', '=', False)]}"
/>
<field
name="cookie_path"
attrs="{'invisible': [('cookie_enabled', '=', False)]}"
/>
<field
name="cookie_max_age"
attrs="{'invisible': [('cookie_enabled', '=', False)]}"
/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_auth_jwt_validator_tree" model="ir.ui.view">
<field name="name">auth.jwt.validator.tree</field>
<field name="model">auth.jwt.validator</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="issuer" />
<field name="audience" />
<field name="signature_type" />
<field name="user_id_strategy" />
<field name="partner_id_strategy" />
<field name="partner_id_required" />
<field name="next_validator_id" />
</tree>
</field>
</record>
<record id="action_auth_jwt_validator" model="ir.actions.act_window">
<field name="name">JWT Validators</field>
<field name="res_model">auth.jwt.validator</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_auth_jwt_validator"
name="JWT Validators"
parent="base.menu_users"
sequence="30"
action="action_auth_jwt_validator"
groups="base.group_no_one"
/>
</odoo>