diff --git a/auth_api_key/README.rst b/auth_api_key/README.rst index a9a06edb95..7ded253885 100644 --- a/auth_api_key/README.rst +++ b/auth_api_key/README.rst @@ -70,6 +70,11 @@ 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. +When you create an API key, a random key is generated automatically. You +can replace it manually, or use the Generate Random Token button if an +existing record has no key. The key field is masked by default, and the +password widget provides a button to reveal the value when needed. + If you want to manage them via serve environment settings use auth_api_key_server_env. diff --git a/auth_api_key/models/auth_api_key.py b/auth_api_key/models/auth_api_key.py index 8124566bad..a2ab9ad64e 100644 --- a/auth_api_key/models/auth_api_key.py +++ b/auth_api_key/models/auth_api_key.py @@ -1,6 +1,8 @@ # Copyright 2018 ACSONE SA/NV # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +import secrets + from odoo import api, fields, models, tools from odoo.exceptions import AccessError, ValidationError from odoo.tools import consteq @@ -13,6 +15,7 @@ class AuthApiKey(models.Model): name = fields.Char(required=True) key = fields.Char( required=True, + default=lambda self: self._generate_random_key_value(), help="""The API key. Enter a dummy value in this field if it is obtained from the server environment configuration.""", ) @@ -31,6 +34,28 @@ class AuthApiKey(models.Model): _name_uniq = models.Constraint("unique(name)", "Api Key name must be unique.") + @api.model + def _generate_random_key_value(self): + """Return a random API key value. + + The token is generated by the Odoo server instance so XML data and the + UI button can create a secret without storing any default value in the + module sources. + """ + return secrets.token_urlsafe(32) + + def generate_random_key(self, api_key_ids=None): + """Generate a key for records that do not have one yet. + + :param list api_key_ids: optional record IDs, mainly used by XML data + function calls where the method is invoked on the model. + :return: True when the operation completed. + """ + api_keys = self.browse(api_key_ids) if api_key_ids else self + for api_key in api_keys.filtered(lambda record: not record.key): + api_key.key = api_key._generate_random_key_value() + return True + @api.model def _retrieve_api_key(self, key): return self.browse(self._retrieve_api_key_id(key)) diff --git a/auth_api_key/readme/CONFIGURE.md b/auth_api_key/readme/CONFIGURE.md index f791dd13ab..9c20871155 100644 --- a/auth_api_key/readme/CONFIGURE.md +++ b/auth_api_key/readme/CONFIGURE.md @@ -2,5 +2,10 @@ 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. +When you create an API key, a random key is generated automatically. You +can replace it manually, or use the Generate Random Token button if an +existing record has no key. The key field is masked by default, and the +password widget provides a button to reveal the value when needed. + If you want to manage them via serve environment settings use auth_api_key_server_env. diff --git a/auth_api_key/static/description/index.html b/auth_api_key/static/description/index.html index 9c70fd4298..f7414c1233 100644 --- a/auth_api_key/static/description/index.html +++ b/auth_api_key/static/description/index.html @@ -416,6 +416,10 @@

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.

+

When you create an API key, a random key is generated automatically. You +can replace it manually, or use the Generate Random Token button if an +existing record has no key. The key field is masked by default, and the +password widget provides a button to reveal the value when needed.

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

diff --git a/auth_api_key/tests/test_auth_api_key.py b/auth_api_key/tests/test_auth_api_key.py index 5fe6b9a187..d8dd5b843e 100644 --- a/auth_api_key/tests/test_auth_api_key.py +++ b/auth_api_key/tests/test_auth_api_key.py @@ -25,6 +25,25 @@ def setUpClass(cls, *args, **kwargs): {"name": "good", "user_id": cls.demo_user.id, "key": "api_key"} ) + def test_create_without_key_generates_random_key(self): + """Test API key creation generates an API key when no key is provided.""" + api_key = self.AuthApiKey.create( + {"name": "generated", "user_id": self.demo_user.id} + ) + self.assertTrue(api_key.key) + self.assertEqual( + self.AuthApiKey._retrieve_uid_from_api_key(api_key.key), + self.demo_user.id, + ) + + def test_generate_random_key_keeps_existing_key(self): + """Test random key generation does not overwrite an existing key.""" + api_key = self.AuthApiKey.create( + {"name": "existing", "user_id": self.demo_user.id, "key": "existing_key"} + ) + api_key.generate_random_key() + self.assertEqual(api_key.key, "existing_key") + def test_lookup_key_from_db(self): demo_user = self.demo_user self.assertEqual( diff --git a/auth_api_key/views/auth_api_key.xml b/auth_api_key/views/auth_api_key.xml index e52a8d8698..0518264407 100644 --- a/auth_api_key/views/auth_api_key.xml +++ b/auth_api_key/views/auth_api_key.xml @@ -21,7 +21,18 @@ - +