diff --git a/auth_api_key/README.rst b/auth_api_key/README.rst new file mode 100644 index 0000000000..8c4f7f0f8b --- /dev/null +++ b/auth_api_key/README.rst @@ -0,0 +1,116 @@ +============ +Auth Api Key +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3adb1ff0898c08651ce6de2c1ee1a91bc08a719dc6411ca880be9598b2f62705 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/17.0/auth_api_key + :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-17-0/server-auth-17-0-auth_api_key + :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=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Authenticate http requests from an API key. + +API keys are codes passed in (in the http header API-KEY) by programs +calling an API in order to identify -in this case- the calling program's +user. + +Take care while using this kind of mechanism since information into http +headers are visible in clear. Thus, use it only to authenticate requests +from known sources. + +For unknown sources, it is a good practice to filter out this header at +proxy level. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +The api key menu is available into Settings > Technical in debug mode. +By default, when you create an API key, the key is saved into the +database. + +If you want to manage them via serve environment settings use +auth_api_key_server_env. + +Usage +===== + +To apply this authentication system to your http request you must set +'api_key' as value for the 'auth' parameter of your route definition +into your controller. + +.. code:: python + + class MyController(Controller): + + @route('/my_service', auth='api_key', ...) + def my_service(self, *args, **kwargs): + pass + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Denis Robinet +- Laurent Mignon +- Quentin Groulard +- Sébastien Beau +- Chafique Delli + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/auth_api_key/__init__.py b/auth_api_key/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/auth_api_key/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/auth_api_key/__manifest__.py b/auth_api_key/__manifest__.py new file mode 100644 index 0000000000..c894a84d7b --- /dev/null +++ b/auth_api_key/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2018 ACSONE SA/NV +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Auth Api Key", + "summary": """ + Authenticate http requests from an API key""", + "version": "17.0.1.0.0", + "license": "LGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-auth", + "development_status": "Beta", + "data": ["security/ir.model.access.csv", "views/auth_api_key.xml"], +} diff --git a/auth_api_key/i18n/auth_api_key.pot b/auth_api_key/i18n/auth_api_key.pot new file mode 100644 index 0000000000..7e919c3767 --- /dev/null +++ b/auth_api_key/i18n/auth_api_key.pot @@ -0,0 +1,113 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_api_key +# +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_api_key +#: model:ir.model,name:auth_api_key.model_auth_api_key +msgid "API Key" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.constraint,message:auth_api_key.constraint_auth_api_key_name_uniq +msgid "Api Key name must be unique." +msgstr "" + +#. module: auth_api_key +#: model:ir.actions.act_window,name:auth_api_key.auth_api_key_act_window +#: model:ir.ui.menu,name:auth_api_key.auth_api_key_menu +msgid "Auth Api Key" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__create_uid +msgid "Created by" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__create_date +msgid "Created on" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__display_name +msgid "Display Name" +msgstr "" + +#. module: auth_api_key +#: model:ir.model,name:auth_api_key.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__id +msgid "ID" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__key +msgid "Key" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key____last_update +msgid "Last Modified on" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__write_date +msgid "Last Updated on" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__name +msgid "Name" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,help:auth_api_key.field_auth_api_key__key +msgid "" +"The API key. Enter a dummy value in this field if it is\n" +" obtained from the server environment configuration." +msgstr "" + +#. module: auth_api_key +#. odoo-python +#: code:addons/auth_api_key/models/auth_api_key.py:0 +#, python-format +msgid "The key %s is not allowed" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,help:auth_api_key.field_auth_api_key__user_id +msgid "" +"The user used to process the requests authenticated by\n" +" the api key" +msgstr "" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__user_id +msgid "User" +msgstr "" + +#. module: auth_api_key +#. odoo-python +#: code:addons/auth_api_key/models/auth_api_key.py:0 +#, python-format +msgid "User is not allowed" +msgstr "" diff --git a/auth_api_key/i18n/it.po b/auth_api_key/i18n/it.po new file mode 100644 index 0000000000..c87f446a17 --- /dev/null +++ b/auth_api_key/i18n/it.po @@ -0,0 +1,123 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_api_key +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-02-12 10:45+0000\n" +"Last-Translator: Sergio Zanchetta \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.3.2\n" + +#. module: auth_api_key +#: model:ir.model,name:auth_api_key.model_auth_api_key +msgid "API Key" +msgstr "Chiave API" + +#. module: auth_api_key +#: model:ir.model.constraint,message:auth_api_key.constraint_auth_api_key_name_uniq +msgid "Api Key name must be unique." +msgstr "La chiave API deve essere univoca." + +#. module: auth_api_key +#: model:ir.actions.act_window,name:auth_api_key.auth_api_key_act_window +#: model:ir.ui.menu,name:auth_api_key.auth_api_key_menu +msgid "Auth Api Key" +msgstr "Chiave API di autenticazione" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__create_uid +msgid "Created by" +msgstr "Creata da" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__create_date +msgid "Created on" +msgstr "Creata il" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: auth_api_key +#: model:ir.model,name:auth_api_key.model_ir_http +msgid "HTTP Routing" +msgstr "Instradamento HTTP" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__id +msgid "ID" +msgstr "ID" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__key +msgid "Key" +msgstr "Chiave" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__name +msgid "Name" +msgstr "Nome" + +#. module: auth_api_key +#: model:ir.model.fields,help:auth_api_key.field_auth_api_key__key +msgid "" +"The API key. Enter a dummy value in this field if it is\n" +" obtained from the server environment configuration." +msgstr "" +"Chiave API. Se viene acquisita dalla configurazione\n" +" ambiente del server, inserire nel campo un valore fittizio." + +#. module: auth_api_key +#. odoo-python +#: code:addons/auth_api_key/models/auth_api_key.py:0 +#, python-format +msgid "The key %s is not allowed" +msgstr "Chiave %s non autorizzata" + +#. module: auth_api_key +#: model:ir.model.fields,help:auth_api_key.field_auth_api_key__user_id +msgid "" +"The user used to process the requests authenticated by\n" +" the api key" +msgstr "" +"Utente utilizzato per elaborare le richieste autenticate\n" +" dalla chiave API" + +#. module: auth_api_key +#: model:ir.model.fields,field_description:auth_api_key.field_auth_api_key__user_id +msgid "User" +msgstr "Utente" + +#. module: auth_api_key +#. odoo-python +#: code:addons/auth_api_key/models/auth_api_key.py:0 +#, python-format +msgid "User is not allowed" +msgstr "Utente non autorizzato" + +#~ msgid "Server Env Defaults" +#~ msgstr "Variabili ambiente predefinite del server" diff --git a/auth_api_key/models/__init__.py b/auth_api_key/models/__init__.py new file mode 100644 index 0000000000..dee3379fea --- /dev/null +++ b/auth_api_key/models/__init__.py @@ -0,0 +1,2 @@ +from . import ir_http +from . import auth_api_key diff --git a/auth_api_key/models/auth_api_key.py b/auth_api_key/models/auth_api_key.py new file mode 100644 index 0000000000..416cd4ab7f --- /dev/null +++ b/auth_api_key/models/auth_api_key.py @@ -0,0 +1,62 @@ +# Copyright 2018 ACSONE SA/NV +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import _, api, fields, models, tools +from odoo.exceptions import AccessError, ValidationError +from odoo.tools import consteq + + +class AuthApiKey(models.Model): + _name = "auth.api.key" + _description = "API Key" + + name = fields.Char(required=True) + key = fields.Char( + required=True, + help="""The API key. Enter a dummy value in this field if it is + obtained from the server environment configuration.""", + ) + user_id = fields.Many2one( + comodel_name="res.users", + string="User", + required=True, + help="""The user used to process the requests authenticated by + the api key""", + ) + + _sql_constraints = [("name_uniq", "unique(name)", "Api Key name must be unique.")] + + @api.model + def _retrieve_api_key(self, key): + return self.browse(self._retrieve_api_key_id(key)) + + @api.model + @tools.ormcache("key") + def _retrieve_api_key_id(self, key): + if not self.env.user.has_group("base.group_system"): + raise AccessError(_("User is not allowed")) + for api_key in self.search([]): + if api_key.key and consteq(key, api_key.key): + return api_key.id + raise ValidationError(_("The key %s is not allowed") % key) + + @api.model + @tools.ormcache("key") + def _retrieve_uid_from_api_key(self, key): + return self._retrieve_api_key(key).user_id.id + + def _clear_key_cache(self): + self.env.registry.clear_cache() + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + if any(["key" in vals or "user_id" in vals for vals in vals_list]): + self._clear_key_cache() + return records + + def write(self, vals): + super().write(vals) + if "key" in vals or "user_id" in vals: + self._clear_key_cache() + return True diff --git a/auth_api_key/models/ir_http.py b/auth_api_key/models/ir_http.py new file mode 100644 index 0000000000..7b02f0c39c --- /dev/null +++ b/auth_api_key/models/ir_http.py @@ -0,0 +1,36 @@ +# Copyright 2018 ACSONE SA/NV +# Copyright 2017 Akretion (http://www.akretion.com). +# @author Sébastien BEAU +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import models +from odoo.exceptions import AccessDenied +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + @classmethod + def _auth_method_api_key(cls): + headers = request.httprequest.environ + api_key = headers.get("HTTP_API_KEY") + if api_key: + request.update_env(user=1) + auth_api_key = request.env["auth.api.key"]._retrieve_api_key(api_key) + if auth_api_key: + # reset _env on the request since we change the uid... + # the next call to env will instantiate an new + # odoo.api.Environment with the user defined on the + # auth.api_key + request._env = None + request.update_env(user=auth_api_key.user_id.id) + request.auth_api_key = api_key + request.auth_api_key_id = auth_api_key.id + return True + _logger.error("Wrong HTTP_API_KEY, access denied") + raise AccessDenied() diff --git a/auth_api_key/pyproject.toml b/auth_api_key/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/auth_api_key/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/auth_api_key/readme/CONFIGURE.md b/auth_api_key/readme/CONFIGURE.md new file mode 100644 index 0000000000..f791dd13ab --- /dev/null +++ b/auth_api_key/readme/CONFIGURE.md @@ -0,0 +1,6 @@ +The api key menu is available into Settings \> Technical in debug mode. +By default, when you create an API key, the key is saved into the +database. + +If you want to manage them via serve environment settings use +auth_api_key_server_env. diff --git a/auth_api_key/readme/CONTRIBUTORS.md b/auth_api_key/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..1168b409a3 --- /dev/null +++ b/auth_api_key/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- Denis Robinet \<\> +- Laurent Mignon \<\> +- Quentin Groulard \<\> +- Sébastien Beau \<\> +- Chafique Delli \<\> diff --git a/auth_api_key/readme/DESCRIPTION.md b/auth_api_key/readme/DESCRIPTION.md new file mode 100644 index 0000000000..6d09ff29d8 --- /dev/null +++ b/auth_api_key/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +Authenticate http requests from an API key. + +API keys are codes passed in (in the http header API-KEY) by programs +calling an API in order to identify -in this case- the calling program's +user. + +Take care while using this kind of mechanism since information into http +headers are visible in clear. Thus, use it only to authenticate requests +from known sources. + +For unknown sources, it is a good practice to filter out this header at +proxy level. diff --git a/auth_api_key/readme/USAGE.md b/auth_api_key/readme/USAGE.md new file mode 100644 index 0000000000..547d48759c --- /dev/null +++ b/auth_api_key/readme/USAGE.md @@ -0,0 +1,11 @@ +To apply this authentication system to your http request you must set +'api_key' as value for the 'auth' parameter of your route definition +into your controller. + +``` python +class MyController(Controller): + + @route('/my_service', auth='api_key', ...) + def my_service(self, *args, **kwargs): + pass +``` diff --git a/auth_api_key/security/ir.model.access.csv b/auth_api_key/security/ir.model.access.csv new file mode 100644 index 0000000000..b964d8c1de --- /dev/null +++ b/auth_api_key/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_auth_api_key,access_auth_api_key,model_auth_api_key,base.group_system,1,1,1,1 diff --git a/auth_api_key/static/description/icon.png b/auth_api_key/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/auth_api_key/static/description/icon.png differ diff --git a/auth_api_key/static/description/index.html b/auth_api_key/static/description/index.html new file mode 100644 index 0000000000..0d80030b19 --- /dev/null +++ b/auth_api_key/static/description/index.html @@ -0,0 +1,455 @@ + + + + + +Auth Api Key + + + +
+

Auth Api Key

+ + +

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

+

Authenticate http requests from an API key.

+

API keys are codes passed in (in the http header API-KEY) by programs +calling an API in order to identify -in this case- the calling program’s +user.

+

Take care while using this kind of mechanism since information into http +headers are visible in clear. Thus, use it only to authenticate requests +from known sources.

+

For unknown sources, it is a good practice to filter out this header at +proxy level.

+

Table of contents

+ +
+

Configuration

+

The api key menu is available into Settings > Technical in debug mode. +By default, when you create an API key, the key is saved into the +database.

+

If you want to manage them via serve environment settings use +auth_api_key_server_env.

+
+
+

Usage

+

To apply this authentication system to your http request you must set +‘api_key’ as value for the ‘auth’ parameter of your route definition +into your controller.

+
+class MyController(Controller):
+
+    @route('/my_service', auth='api_key', ...)
+    def my_service(self, *args, **kwargs):
+        pass
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/auth_api_key/tests/__init__.py b/auth_api_key/tests/__init__.py new file mode 100644 index 0000000000..56e3e32a3a --- /dev/null +++ b/auth_api_key/tests/__init__.py @@ -0,0 +1 @@ +from . import test_auth_api_key diff --git a/auth_api_key/tests/test_auth_api_key.py b/auth_api_key/tests/test_auth_api_key.py new file mode 100644 index 0000000000..b8e0804097 --- /dev/null +++ b/auth_api_key/tests/test_auth_api_key.py @@ -0,0 +1,45 @@ +# Copyright 2018 ACSONE SA/NV +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo.exceptions import AccessError, ValidationError +from odoo.tests.common import TransactionCase + + +class TestAuthApiKey(TransactionCase): + @classmethod + def setUpClass(cls, *args, **kwargs): + super().setUpClass(*args, **kwargs) + cls.AuthApiKey = cls.env["auth.api.key"] + cls.demo_user = cls.env.ref("base.user_demo") + cls.api_key_good = cls.AuthApiKey.create( + {"name": "good", "user_id": cls.demo_user.id, "key": "api_key"} + ) + + def test_lookup_key_from_db(self): + demo_user = self.env.ref("base.user_demo") + self.assertEqual( + self.env["auth.api.key"]._retrieve_uid_from_api_key("api_key"), demo_user.id + ) + + def test_wrong_key(self): + with self.assertRaises(ValidationError), self.env.cr.savepoint(): + self.env["auth.api.key"]._retrieve_uid_from_api_key("api_wrong_key") + + def test_user_not_allowed(self): + # only system users can check for key + with self.assertRaises(AccessError), self.env.cr.savepoint(): + self.env["auth.api.key"].with_user( + user=self.demo_user + )._retrieve_uid_from_api_key("api_wrong_key") + + def test_cache_invalidation(self): + self.assertEqual( + self.env["auth.api.key"]._retrieve_uid_from_api_key("api_key"), + self.demo_user.id, + ) + self.api_key_good.write({"key": "updated_key"}) + self.assertEqual( + self.env["auth.api.key"]._retrieve_uid_from_api_key("updated_key"), + self.demo_user.id, + ) + with self.assertRaises(ValidationError): + self.env["auth.api.key"]._retrieve_uid_from_api_key("api_key") diff --git a/auth_api_key/views/auth_api_key.xml b/auth_api_key/views/auth_api_key.xml new file mode 100644 index 0000000000..c2305274ae --- /dev/null +++ b/auth_api_key/views/auth_api_key.xml @@ -0,0 +1,46 @@ + + + + + auth.api.key.form (in auth_api_key) + auth.api.key + +
+ + +
+
+
+ + auth.api.key.tree (in auth_api_key) + auth.api.key + + + + + + + + + Auth Api Key + auth.api.key + tree,form + [] + {} + + + Auth Api Key + + + + +