|
| 1 | +.. image:: https://odoo-community.org/readme-banner-image |
| 2 | + :target: https://odoo-community.org/get-involved?utm_source=readme |
| 3 | + :alt: Odoo Community Association |
| 4 | + |
| 5 | +========================= |
| 6 | +Auth API Key Provisioning |
| 7 | +========================= |
| 8 | + |
| 9 | +.. |
| 10 | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
| 11 | + !! This file is generated by oca-gen-addon-readme !! |
| 12 | + !! changes will be overwritten. !! |
| 13 | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
| 14 | + !! source digest: sha256:0cb9fbc9eba209dde189ea2ccd2b6b6dd60771cae3bfc626fa86889274af611f |
| 15 | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
| 16 | +
|
| 17 | +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png |
| 18 | + :target: https://odoo-community.org/page/development-status |
| 19 | + :alt: Beta |
| 20 | +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png |
| 21 | + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
| 22 | + :alt: License: LGPL-3 |
| 23 | +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github |
| 24 | + :target: https://github.com/OCA/server-auth/tree/18.0/auth_api_key_provisioning |
| 25 | + :alt: OCA/server-auth |
| 26 | +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png |
| 27 | + :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_api_key_provisioning |
| 28 | + :alt: Translate me on Weblate |
| 29 | +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png |
| 30 | + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0 |
| 31 | + :alt: Try me on Runboat |
| 32 | + |
| 33 | +|badge1| |badge2| |badge3| |badge4| |badge5| |
| 34 | + |
| 35 | +This module lets a trusted provisioning/service account **mint a |
| 36 | +short-lived, ``rpc``-scoped API key on behalf of another internal |
| 37 | +user**, over RPC. |
| 38 | + |
| 39 | +It exists for *delegated per-user identity*: when an external |
| 40 | +integration (an MCP server, an AI agent, or any backend service) needs |
| 41 | +to act **as an end user** rather than as a single shared service |
| 42 | +account. A key minted for the target user carries only that user's own |
| 43 | +permissions, so subsequent calls apply Odoo's native record rules and |
| 44 | +record the real user as ``create_uid``/``write_uid`` — instead of |
| 45 | +attributing everything to one shared account. |
| 46 | + |
| 47 | +Stock Odoo has no supported way to mint an API key *for another user* |
| 48 | +over RPC: ``res.users.apikeys.generate`` is not exposed as an RPC method |
| 49 | +and ``_generate`` is private. This module adds two narrowly-scoped, |
| 50 | +group-gated methods on ``res.users`` to close that gap safely. |
| 51 | + |
| 52 | +What it adds |
| 53 | +------------ |
| 54 | + |
| 55 | +- ``res.users.mint_apikey(name=None, ttl_days=None) -> str`` — called on |
| 56 | + the target user recordset; returns a freshly generated ``rpc``-scoped |
| 57 | + key once. |
| 58 | +- ``res.users.revoke_provisioned_apikeys() -> int`` — revokes all keys |
| 59 | + this module minted for that user. |
| 60 | +- An auditable log (``auth.api.key.provisioning.log``) of every mint, |
| 61 | + visible under *Settings → Users → Provisioned API Keys*. |
| 62 | + |
| 63 | +MCP / AI-agent usage is the motivating example, but the module itself is |
| 64 | +generic. |
| 65 | + |
| 66 | +**Table of contents** |
| 67 | + |
| 68 | +.. contents:: |
| 69 | + :local: |
| 70 | + |
| 71 | +Configuration |
| 72 | +============= |
| 73 | + |
| 74 | +After installing the module: |
| 75 | + |
| 76 | +1. Add your integration / service account to the **API Key |
| 77 | + Provisioning** group (*Settings → Users & Companies → Users*). Do |
| 78 | + **not** use a system administrator for this — the whole point is |
| 79 | + least privilege. |
| 80 | +2. Add each user that integrations may act as to the **API Key Mintable |
| 81 | + Target** group. |
| 82 | + |
| 83 | +Two system parameters (*Settings → Technical → System Parameters*) tune |
| 84 | +the lifetime: |
| 85 | + |
| 86 | +- ``auth_api_key_provisioning.default_ttl_days`` (default ``30``) — |
| 87 | + applied when a mint request omits ``ttl_days``. |
| 88 | +- ``auth_api_key_provisioning.max_ttl_days`` (default ``90``) — |
| 89 | + requested lifetimes are clamped down to this. An absolute ceiling is |
| 90 | + also enforced in code. |
| 91 | + |
| 92 | +Usage |
| 93 | +===== |
| 94 | + |
| 95 | +From a provisioning/service account (a member of *API Key |
| 96 | +Provisioning*), call the method over RPC on the target user. The target |
| 97 | +must be an internal user that is a member of the *API Key Mintable |
| 98 | +Target* group. |
| 99 | + |
| 100 | +.. code:: python |
| 101 | +
|
| 102 | + import xmlrpc.client |
| 103 | +
|
| 104 | + common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common") |
| 105 | + uid = common.authenticate(db, "prov-svc", password, {}) |
| 106 | + models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object") |
| 107 | +
|
| 108 | + # Mint a 7-day rpc key for user id 42: |
| 109 | + api_key = models.execute_kw( |
| 110 | + db, uid, password, |
| 111 | + "res.users", "mint_apikey", [[42]], |
| 112 | + {"name": "my-integration", "ttl_days": 7}, |
| 113 | + ) |
| 114 | +
|
| 115 | + # Later, revoke everything this module minted for that user: |
| 116 | + models.execute_kw(db, uid, password, "res.users", "revoke_provisioned_apikeys", [[42]]) |
| 117 | +
|
| 118 | +The integration then authenticates as user 42 using ``api_key`` as the |
| 119 | +password on RPC/``/xmlrpc/2/object`` calls; Odoo applies that user's own |
| 120 | +ACLs and record rules. |
| 121 | + |
| 122 | +Security model |
| 123 | +-------------- |
| 124 | + |
| 125 | +- **Caller gating** — only members of *API Key Provisioning* may mint or |
| 126 | + revoke; this is a dedicated least-privilege group, **not** |
| 127 | + ``base.group_system``. |
| 128 | +- **Target allowlist** — keys are minted only for users explicitly |
| 129 | + placed in *API Key Mintable Target*. An allowlist is used rather than |
| 130 | + a blocklist because custom modules add their own high-privilege groups |
| 131 | + that no fixed blocklist could enumerate. |
| 132 | +- **Elevated targets refused** — minting is always refused for the |
| 133 | + superuser and for any member of ``base.group_system`` / |
| 134 | + ``base.group_erp_manager``, even if mis-added to the allowlist. |
| 135 | + Portal/public (share) and archived users are refused too. |
| 136 | +- **Privilege-drift protection** — API keys carry no permission |
| 137 | + snapshot; they authenticate with the user's *current* groups. If a |
| 138 | + target with minted keys is later promoted into an elevated group (from |
| 139 | + the user side or the group side) or archived, its provisioned keys are |
| 140 | + revoked immediately; a daily cron is a backstop for changes that |
| 141 | + bypass the ORM hooks. |
| 142 | +- **Bounded lifetime** — keys are always ``rpc``-scoped and expiring. |
| 143 | + The TTL defaults to 30 days and is clamped to a configurable maximum |
| 144 | + (90 days), with an absolute code ceiling. |
| 145 | +- **Auditable** — every mint is logged with who/for-whom/when and a |
| 146 | + revocation timestamp. |
| 147 | + |
| 148 | +Residual risks (by design) |
| 149 | +-------------------------- |
| 150 | + |
| 151 | +- Like all Odoo API keys, a minted key is **not** invalidated by a |
| 152 | + target password reset. Use ``revoke_provisioned_apikeys`` (or archive |
| 153 | + the user) on a suspected compromise. |
| 154 | +- A compromised provisioning account can mint keys for any *mintable* |
| 155 | + (non-elevated) user. Keep that group's membership minimal and monitor |
| 156 | + the provisioning log. |
| 157 | + |
| 158 | +Bug Tracker |
| 159 | +=========== |
| 160 | + |
| 161 | +Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_. |
| 162 | +In case of trouble, please check there if your issue has already been reported. |
| 163 | +If you spotted it first, help us to smash it by providing a detailed and welcomed |
| 164 | +`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20auth_api_key_provisioning%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
| 165 | + |
| 166 | +Do not contact contributors directly about support or help with technical issues. |
| 167 | + |
| 168 | +Credits |
| 169 | +======= |
| 170 | + |
| 171 | +Authors |
| 172 | +------- |
| 173 | + |
| 174 | +* AI Cognitive Leap |
| 175 | + |
| 176 | +Contributors |
| 177 | +------------ |
| 178 | + |
| 179 | +- Jiri Manas <jiri.manas@keboola.com> |
| 180 | + |
| 181 | +Other credits |
| 182 | +------------- |
| 183 | + |
| 184 | +The development of this module was funded by AI Cognitive Leap. |
| 185 | + |
| 186 | +Maintainers |
| 187 | +----------- |
| 188 | + |
| 189 | +This module is maintained by the OCA. |
| 190 | + |
| 191 | +.. image:: https://odoo-community.org/logo.png |
| 192 | + :alt: Odoo Community Association |
| 193 | + :target: https://odoo-community.org |
| 194 | + |
| 195 | +OCA, or the Odoo Community Association, is a nonprofit organization whose |
| 196 | +mission is to support the collaborative development of Odoo features and |
| 197 | +promote its widespread use. |
| 198 | + |
| 199 | +.. |maintainer-manana2520| image:: https://github.com/manana2520.png?size=40px |
| 200 | + :target: https://github.com/manana2520 |
| 201 | + :alt: manana2520 |
| 202 | + |
| 203 | +Current `maintainer <https://odoo-community.org/page/maintainer-role>`__: |
| 204 | + |
| 205 | +|maintainer-manana2520| |
| 206 | + |
| 207 | +This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/18.0/auth_api_key_provisioning>`_ project on GitHub. |
| 208 | + |
| 209 | +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
0 commit comments