diff --git a/auth_api_key/README.rst b/auth_api_key/README.rst index a9a06edb95..627ac8b97a 100644 --- a/auth_api_key/README.rst +++ b/auth_api_key/README.rst @@ -70,6 +70,8 @@ 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 a database is neutralized, stored API key values are cleared. + If you want to manage them via serve environment settings use auth_api_key_server_env. diff --git a/auth_api_key/data/neutralize.sql b/auth_api_key/data/neutralize.sql new file mode 100644 index 0000000000..d23b229ef7 --- /dev/null +++ b/auth_api_key/data/neutralize.sql @@ -0,0 +1,3 @@ +-- remove API keys +UPDATE auth_api_key + SET key = NULL; diff --git a/auth_api_key/models/auth_api_key.py b/auth_api_key/models/auth_api_key.py index 8124566bad..ee8564a6f1 100644 --- a/auth_api_key/models/auth_api_key.py +++ b/auth_api_key/models/auth_api_key.py @@ -12,7 +12,6 @@ class AuthApiKey(models.Model): 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.""", ) @@ -31,6 +30,12 @@ class AuthApiKey(models.Model): _name_uniq = models.Constraint("unique(name)", "Api Key name must be unique.") + @api.constrains("key") + def _check_key_required(self): + for api_key in self: + if not api_key.key: + raise ValidationError(self.env._("The API key is required.")) + @api.model def _retrieve_api_key(self, key): return self.browse(self._retrieve_api_key_id(key)) @@ -40,7 +45,7 @@ def _retrieve_api_key(self, key): def _retrieve_api_key_id(self, key): if not self.env.user.has_group("base.group_system"): raise AccessError(self.env._("User is not allowed")) - for api_key in self.search([], limit=None): + for api_key in self.search([("key", "!=", False)], limit=None): if api_key.key and consteq(key, api_key.key): return api_key.id raise ValidationError(self.env._("The key '%s' is not allowed", key)) diff --git a/auth_api_key/readme/CONFIGURE.md b/auth_api_key/readme/CONFIGURE.md index f791dd13ab..d333318925 100644 --- a/auth_api_key/readme/CONFIGURE.md +++ b/auth_api_key/readme/CONFIGURE.md @@ -2,5 +2,7 @@ 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 a database is neutralized, stored API key values are cleared. + 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..0a9612b8a9 100644 --- a/auth_api_key/static/description/index.html +++ b/auth_api_key/static/description/index.html @@ -416,6 +416,7 @@

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 a database is neutralized, stored API key values are cleared.

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

diff --git a/auth_api_key/tests/__init__.py b/auth_api_key/tests/__init__.py index 308b13b596..7cb6186a03 100644 --- a/auth_api_key/tests/__init__.py +++ b/auth_api_key/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_auth_api_key from . import test_controllers +from . import test_neutralize diff --git a/auth_api_key/tests/test_auth_api_key.py b/auth_api_key/tests/test_auth_api_key.py index 5fe6b9a187..d78e299bff 100644 --- a/auth_api_key/tests/test_auth_api_key.py +++ b/auth_api_key/tests/test_auth_api_key.py @@ -35,6 +35,14 @@ 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_empty_key_is_rejected(self): + with self.assertRaises(ValidationError), self.env.cr.savepoint(): + self.AuthApiKey.create( + {"name": "empty", "user_id": self.demo_user.id, "key": ""} + ) + with self.assertRaises(ValidationError), self.env.cr.savepoint(): + self.api_key_good.key = False + def test_user_not_allowed(self): # only system users can check for key with self.assertRaises(AccessError), self.env.cr.savepoint(): diff --git a/auth_api_key/tests/test_neutralize.py b/auth_api_key/tests/test_neutralize.py new file mode 100644 index 0000000000..4b5d16828b --- /dev/null +++ b/auth_api_key/tests/test_neutralize.py @@ -0,0 +1,25 @@ +# Copyright 2026 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.modules import neutralize +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +@tagged("post_install", "-at_install", "neutralize") +class TestAuthApiKeyNeutralize(TransactionCase): + def test_neutralize_removes_api_key_values(self): + """Test database neutralization clears stored API key secrets.""" + api_key = self.env["auth.api.key"].create( + { + "name": "neutralize", + "user_id": self.env.ref("base.user_admin").id, + "key": "secret-key", + } + ) + + queries = neutralize.get_neutralization_queries(["auth_api_key"]) + for query in queries: + self.cr.execute(query) + api_key.invalidate_recordset(["key"]) + self.assertFalse(api_key.key)