Skip to content

Commit 4e8ebb3

Browse files
committed
[19.0][IMP] auth_api_key: generate API keys automatically
1 parent 957944c commit 4e8ebb3

6 files changed

Lines changed: 70 additions & 1 deletion

File tree

auth_api_key/README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ The api key menu is available into Settings > Technical in debug mode.
7070
By default, when you create an API key, the key is saved into the
7171
database.
7272

73+
When you create an API key, a random key is generated automatically. You
74+
can replace it manually, or use the Generate Random Token button if an
75+
existing record has no key. The key field is masked by default, and the
76+
password widget provides a button to reveal the value when needed.
77+
7378
If you want to manage them via serve environment settings use
7479
auth_api_key_server_env.
7580

auth_api_key/models/auth_api_key.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright 2018 ACSONE SA/NV
22
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
33

4+
import secrets
5+
46
from odoo import api, fields, models, tools
57
from odoo.exceptions import AccessError, ValidationError
68
from odoo.tools import consteq
@@ -13,6 +15,7 @@ class AuthApiKey(models.Model):
1315
name = fields.Char(required=True)
1416
key = fields.Char(
1517
required=True,
18+
default=lambda self: self._generate_random_key_value(),
1619
help="""The API key. Enter a dummy value in this field if it is
1720
obtained from the server environment configuration.""",
1821
)
@@ -31,6 +34,28 @@ class AuthApiKey(models.Model):
3134

3235
_name_uniq = models.Constraint("unique(name)", "Api Key name must be unique.")
3336

37+
@api.model
38+
def _generate_random_key_value(self):
39+
"""Return a random API key value.
40+
41+
The token is generated by the Odoo server instance so XML data and the
42+
UI button can create a secret without storing any default value in the
43+
module sources.
44+
"""
45+
return secrets.token_urlsafe(32)
46+
47+
def generate_random_key(self, api_key_ids=None):
48+
"""Generate a key for records that do not have one yet.
49+
50+
:param list api_key_ids: optional record IDs, mainly used by XML data
51+
function calls where the method is invoked on the model.
52+
:return: True when the operation completed.
53+
"""
54+
api_keys = self.browse(api_key_ids) if api_key_ids else self
55+
for api_key in api_keys.filtered(lambda record: not record.key):
56+
api_key.key = api_key._generate_random_key_value()
57+
return True
58+
3459
@api.model
3560
def _retrieve_api_key(self, key):
3661
return self.browse(self._retrieve_api_key_id(key))

auth_api_key/readme/CONFIGURE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ The api key menu is available into Settings \> Technical in debug mode.
22
By default, when you create an API key, the key is saved into the
33
database.
44

5+
When you create an API key, a random key is generated automatically. You
6+
can replace it manually, or use the Generate Random Token button if an
7+
existing record has no key. The key field is masked by default, and the
8+
password widget provides a button to reveal the value when needed.
9+
510
If you want to manage them via serve environment settings use
611
auth_api_key_server_env.

auth_api_key/static/description/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,10 @@ <h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
416416
<p>The api key menu is available into Settings &gt; Technical in debug mode.
417417
By default, when you create an API key, the key is saved into the
418418
database.</p>
419+
<p>When you create an API key, a random key is generated automatically. You
420+
can replace it manually, or use the Generate Random Token button if an
421+
existing record has no key. The key field is masked by default, and the
422+
password widget provides a button to reveal the value when needed.</p>
419423
<p>If you want to manage them via serve environment settings use
420424
auth_api_key_server_env.</p>
421425
</div>

auth_api_key/tests/test_auth_api_key.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ def setUpClass(cls, *args, **kwargs):
2525
{"name": "good", "user_id": cls.demo_user.id, "key": "api_key"}
2626
)
2727

28+
def test_create_without_key_generates_random_key(self):
29+
"""Test API key creation generates an API key when no key is provided."""
30+
api_key = self.AuthApiKey.create(
31+
{"name": "generated", "user_id": self.demo_user.id}
32+
)
33+
self.assertTrue(api_key.key)
34+
self.assertEqual(
35+
self.AuthApiKey._retrieve_uid_from_api_key(api_key.key),
36+
self.demo_user.id,
37+
)
38+
39+
def test_generate_random_key_keeps_existing_key(self):
40+
"""Test random key generation does not overwrite an existing key."""
41+
api_key = self.AuthApiKey.create(
42+
{"name": "existing", "user_id": self.demo_user.id, "key": "existing_key"}
43+
)
44+
api_key.generate_random_key()
45+
self.assertEqual(api_key.key, "existing_key")
46+
2847
def test_lookup_key_from_db(self):
2948
demo_user = self.demo_user
3049
self.assertEqual(

auth_api_key/views/auth_api_key.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,18 @@
2121
</h1>
2222
<group name="config" colspan="4" col="4">
2323
<field name="user_id" colspan="4" />
24-
<field name="key" colspan="4" password="True" />
24+
<label for="key" />
25+
<div class="o_row" colspan="3">
26+
<!-- The password widget masks the key and provides a reveal button. -->
27+
<field name="key" widget="password" required="1" />
28+
<button
29+
name="generate_random_key"
30+
string="Generate Random Token"
31+
type="object"
32+
class="btn-secondary"
33+
invisible="key"
34+
/>
35+
</div>
2536
</group>
2637
</sheet>
2738
</form>

0 commit comments

Comments
 (0)