odoo-addon-fastapi-auth-api-key 17.0.1.0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
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
+ Fastapi Auth API Key
7
+ ====================
8
+
9
+ ..
10
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11
+ !! This file is generated by oca-gen-addon-readme !!
12
+ !! changes will be overwritten. !!
13
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14
+ !! source digest: sha256:aff879a734035d7341665e38dce9d294e1379d01dddf4c0631dcb62f75f2a7e7
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-AGPL--3-blue.png
21
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
22
+ :alt: License: AGPL-3
23
+ .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github
24
+ :target: https://github.com/OCA/rest-framework/tree/17.0/fastapi_auth_api_key
25
+ :alt: OCA/rest-framework
26
+ .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27
+ :target: https://translation.odoo-community.org/projects/rest-framework-17-0/rest-framework-17-0-fastapi_auth_api_key
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/rest-framework&target_branch=17.0
31
+ :alt: Try me on Runboat
32
+
33
+ |badge1| |badge2| |badge3| |badge4| |badge5|
34
+
35
+ Provides FastAPI dependencies for Api Key authentication.
36
+
37
+ **Table of contents**
38
+
39
+ .. contents::
40
+ :local:
41
+
42
+ Usage
43
+ =====
44
+
45
+ Getting an odoo environment
46
+ ---------------------------
47
+
48
+ If you need to get an odoo env based on the provided api key, you can
49
+ use authenticated_env_by_auth_api_key.
50
+
51
+ .. code:: python
52
+
53
+ @router.get("/example_with_authenticated_env")
54
+ def example_with_authenticated_env(
55
+ env: Annotated[Environment, Depends(authenticated_env_by_auth_api_key)],
56
+ ) -> None:
57
+ # env.user is the user attached to the provided key
58
+ pass
59
+
60
+ Getting the authenticated partner
61
+ ---------------------------------
62
+
63
+ If want to get the partned related to the the provided api key, you can
64
+ use authenticated_partner_by_api_key
65
+
66
+ .. code:: python
67
+
68
+ @router.get("/example_with_authenticated_partner")
69
+ def example_with_authenticated_partner(
70
+ partner: Annotated[Partner, Depends(authenticated_partner_by_api_key)],
71
+ ) -> None:
72
+ # partner is the partner related to the provided key key.user_id.partner_id
73
+ pass
74
+
75
+ Configuration
76
+ -------------
77
+
78
+ For this to work, the api key must be defined on the Endpoint. A new
79
+ field auth_api_key_group_id has been added to the Endpoint model.
80
+
81
+ Bug Tracker
82
+ ===========
83
+
84
+ Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/issues>`_.
85
+ In case of trouble, please check there if your issue has already been reported.
86
+ If you spotted it first, help us to smash it by providing a detailed and welcomed
87
+ `feedback <https://github.com/OCA/rest-framework/issues/new?body=module:%20fastapi_auth_api_key%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
88
+
89
+ Do not contact contributors directly about support or help with technical issues.
90
+
91
+ Credits
92
+ =======
93
+
94
+ Authors
95
+ -------
96
+
97
+ * Camptocamp
98
+
99
+ Contributors
100
+ ------------
101
+
102
+ - Matthieu Méquignon <matthieu.mequignon@camptocamp.com>
103
+ - Son Ho <sonhd@trobz.com>
104
+
105
+ Other credits
106
+ -------------
107
+
108
+ The migration of this module from 16.0 to 17.0 was financially supported
109
+ by Camptocamp
110
+
111
+ Maintainers
112
+ -----------
113
+
114
+ This module is maintained by the OCA.
115
+
116
+ .. image:: https://odoo-community.org/logo.png
117
+ :alt: Odoo Community Association
118
+ :target: https://odoo-community.org
119
+
120
+ OCA, or the Odoo Community Association, is a nonprofit organization whose
121
+ mission is to support the collaborative development of Odoo features and
122
+ promote its widespread use.
123
+
124
+ .. |maintainer-mmequignon| image:: https://github.com/mmequignon.png?size=40px
125
+ :target: https://github.com/mmequignon
126
+ :alt: mmequignon
127
+
128
+ Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
129
+
130
+ |maintainer-mmequignon|
131
+
132
+ This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/17.0/fastapi_auth_api_key>`_ project on GitHub.
133
+
134
+ You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
@@ -0,0 +1 @@
1
+ from . import models
@@ -0,0 +1,20 @@
1
+ # Copyright 2024 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
3
+
4
+ {
5
+ "name": "Fastapi Auth API Key",
6
+ "version": "17.0.1.0.0",
7
+ "category": "Others",
8
+ "website": "https://github.com/OCA/rest-framework",
9
+ "author": "Camptocamp, Odoo Community Association (OCA)",
10
+ "maintainers": ["mmequignon"],
11
+ "license": "AGPL-3",
12
+ "installable": True,
13
+ "depends": [
14
+ "fastapi",
15
+ "auth_api_key_group",
16
+ ],
17
+ "data": [
18
+ "views/fastapi_endpoint.xml",
19
+ ],
20
+ }
@@ -0,0 +1,66 @@
1
+ # Copyright 2024 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
3
+ import os
4
+ from typing import Annotated
5
+
6
+ from odoo import SUPERUSER_ID, _
7
+ from odoo.api import Environment
8
+ from odoo.exceptions import ValidationError
9
+
10
+ from odoo.addons.auth_api_key.models.auth_api_key import AuthApiKey
11
+ from odoo.addons.base.models.res_partner import Partner
12
+ from odoo.addons.fastapi.dependencies import fastapi_endpoint, odoo_env
13
+ from odoo.addons.fastapi.models.fastapi_endpoint import FastapiEndpoint
14
+
15
+ from fastapi import Depends, status
16
+ from fastapi.exceptions import HTTPException
17
+ from fastapi.security import APIKeyHeader
18
+
19
+ HTTP_API_KEY_HEADER = os.environ.get("FASTAPI_AUTH_HTTP_API_KEY_HEADER", "HTTP-API-KEY")
20
+
21
+
22
+ def authenticated_auth_api_key(
23
+ key: Annotated[str, Depends(APIKeyHeader(name=HTTP_API_KEY_HEADER))],
24
+ env: Annotated[Environment, Depends(odoo_env)],
25
+ endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)],
26
+ ) -> AuthApiKey:
27
+ if not key:
28
+ raise HTTPException(
29
+ status_code=status.HTTP_401_UNAUTHORIZED,
30
+ detail=_("Missing %(HTTP_API_KEY_HEADER)s header")
31
+ % {"HTTP_API_KEY_HEADER": HTTP_API_KEY_HEADER},
32
+ headers={"WWW-Authenticate": HTTP_API_KEY_HEADER},
33
+ )
34
+ admin_env = Environment(env.cr, SUPERUSER_ID, {})
35
+ try:
36
+ auth_api_key = admin_env["auth.api.key"]._retrieve_api_key(key)
37
+ except ValidationError as error:
38
+ raise HTTPException(
39
+ status_code=status.HTTP_401_UNAUTHORIZED,
40
+ detail=error.args,
41
+ headers={"WWW-Authenticate": HTTP_API_KEY_HEADER},
42
+ ) from error
43
+ # Ensure the api key is authorized for the current endpoint.
44
+ if (
45
+ endpoint.sudo().auth_api_key_group_id
46
+ and auth_api_key not in endpoint.sudo().auth_api_key_group_id.auth_api_key_ids
47
+ ):
48
+ raise HTTPException(
49
+ status_code=status.HTTP_401_UNAUTHORIZED,
50
+ detail=_("Unauthorized"),
51
+ headers={"WWW-Authenticate": HTTP_API_KEY_HEADER},
52
+ )
53
+ return auth_api_key
54
+
55
+
56
+ def authenticated_partner_by_api_key(
57
+ auth_api_key: Annotated[AuthApiKey, Depends(authenticated_auth_api_key)]
58
+ ) -> Partner:
59
+ return auth_api_key.user_id.partner_id
60
+
61
+
62
+ def authenticated_env_by_auth_api_key(
63
+ auth_api_key: Annotated[AuthApiKey, Depends(authenticated_auth_api_key)]
64
+ ) -> Environment:
65
+ # set api key id in context
66
+ return auth_api_key.with_user(auth_api_key.user_id).env
@@ -0,0 +1 @@
1
+ from . import fastapi_endpoint
@@ -0,0 +1,13 @@
1
+ # Copyright 2024 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
3
+
4
+ from odoo import fields, models
5
+
6
+
7
+ class FastapiEndpoint(models.Model):
8
+ _inherit = "fastapi.endpoint"
9
+
10
+ auth_api_key_group_id = fields.Many2one(
11
+ "auth.api.key.group",
12
+ help="If not set, all 'auth.api.key' are allowed to access to endpoints",
13
+ )
@@ -0,0 +1,2 @@
1
+ - Matthieu Méquignon \<<matthieu.mequignon@camptocamp.com>\>
2
+ - Son Ho \<<sonhd@trobz.com>\>
@@ -0,0 +1,2 @@
1
+ The migration of this module from 16.0 to 17.0 was financially supported
2
+ by Camptocamp
@@ -0,0 +1 @@
1
+ Provides FastAPI dependencies for Api Key authentication.
@@ -0,0 +1,32 @@
1
+ ## Getting an odoo environment
2
+
3
+ If you need to get an odoo env based on the provided api key, you can
4
+ use authenticated_env_by_auth_api_key.
5
+
6
+ ``` python
7
+ @router.get("/example_with_authenticated_env")
8
+ def example_with_authenticated_env(
9
+ env: Annotated[Environment, Depends(authenticated_env_by_auth_api_key)],
10
+ ) -> None:
11
+ # env.user is the user attached to the provided key
12
+ pass
13
+ ```
14
+
15
+ ## Getting the authenticated partner
16
+
17
+ If want to get the partned related to the the provided api key, you can
18
+ use authenticated_partner_by_api_key
19
+
20
+ ``` python
21
+ @router.get("/example_with_authenticated_partner")
22
+ def example_with_authenticated_partner(
23
+ partner: Annotated[Partner, Depends(authenticated_partner_by_api_key)],
24
+ ) -> None:
25
+ # partner is the partner related to the provided key key.user_id.partner_id
26
+ pass
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ For this to work, the api key must be defined on the Endpoint. A new
32
+ field auth_api_key_group_id has been added to the Endpoint model.
@@ -0,0 +1,369 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
7
+ <title>README.rst</title>
8
+ <style type="text/css">
9
+
10
+ /*
11
+ :Author: David Goodger (goodger@python.org)
12
+ :Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
13
+ :Copyright: This stylesheet has been placed in the public domain.
14
+
15
+ Default cascading style sheet for the HTML output of Docutils.
16
+
17
+ See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
18
+ customize this style sheet.
19
+ */
20
+
21
+ /* used to remove borders from tables and images */
22
+ .borderless, table.borderless td, table.borderless th {
23
+ border: 0 }
24
+
25
+ table.borderless td, table.borderless th {
26
+ /* Override padding for "table.docutils td" with "! important".
27
+ The right padding separates the table cells. */
28
+ padding: 0 0.5em 0 0 ! important }
29
+
30
+ .first {
31
+ /* Override more specific margin styles with "! important". */
32
+ margin-top: 0 ! important }
33
+
34
+ .last, .with-subtitle {
35
+ margin-bottom: 0 ! important }
36
+
37
+ .hidden {
38
+ display: none }
39
+
40
+ .subscript {
41
+ vertical-align: sub;
42
+ font-size: smaller }
43
+
44
+ .superscript {
45
+ vertical-align: super;
46
+ font-size: smaller }
47
+
48
+ a.toc-backref {
49
+ text-decoration: none ;
50
+ color: black }
51
+
52
+ blockquote.epigraph {
53
+ margin: 2em 5em ; }
54
+
55
+ dl.docutils dd {
56
+ margin-bottom: 0.5em }
57
+
58
+ object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
59
+ overflow: hidden;
60
+ }
61
+
62
+ /* Uncomment (and remove this text!) to get bold-faced definition list terms
63
+ dl.docutils dt {
64
+ font-weight: bold }
65
+ */
66
+
67
+ div.abstract {
68
+ margin: 2em 5em }
69
+
70
+ div.abstract p.topic-title {
71
+ font-weight: bold ;
72
+ text-align: center }
73
+
74
+ div.admonition, div.attention, div.caution, div.danger, div.error,
75
+ div.hint, div.important, div.note, div.tip, div.warning {
76
+ margin: 2em ;
77
+ border: medium outset ;
78
+ padding: 1em }
79
+
80
+ div.admonition p.admonition-title, div.hint p.admonition-title,
81
+ div.important p.admonition-title, div.note p.admonition-title,
82
+ div.tip p.admonition-title {
83
+ font-weight: bold ;
84
+ font-family: sans-serif }
85
+
86
+ div.attention p.admonition-title, div.caution p.admonition-title,
87
+ div.danger p.admonition-title, div.error p.admonition-title,
88
+ div.warning p.admonition-title, .code .error {
89
+ color: red ;
90
+ font-weight: bold ;
91
+ font-family: sans-serif }
92
+
93
+ /* Uncomment (and remove this text!) to get reduced vertical space in
94
+ compound paragraphs.
95
+ div.compound .compound-first, div.compound .compound-middle {
96
+ margin-bottom: 0.5em }
97
+
98
+ div.compound .compound-last, div.compound .compound-middle {
99
+ margin-top: 0.5em }
100
+ */
101
+
102
+ div.dedication {
103
+ margin: 2em 5em ;
104
+ text-align: center ;
105
+ font-style: italic }
106
+
107
+ div.dedication p.topic-title {
108
+ font-weight: bold ;
109
+ font-style: normal }
110
+
111
+ div.figure {
112
+ margin-left: 2em ;
113
+ margin-right: 2em }
114
+
115
+ div.footer, div.header {
116
+ clear: both;
117
+ font-size: smaller }
118
+
119
+ div.line-block {
120
+ display: block ;
121
+ margin-top: 1em ;
122
+ margin-bottom: 1em }
123
+
124
+ div.line-block div.line-block {
125
+ margin-top: 0 ;
126
+ margin-bottom: 0 ;
127
+ margin-left: 1.5em }
128
+
129
+ div.sidebar {
130
+ margin: 0 0 0.5em 1em ;
131
+ border: medium outset ;
132
+ padding: 1em ;
133
+ background-color: #ffffee ;
134
+ width: 40% ;
135
+ float: right ;
136
+ clear: right }
137
+
138
+ div.sidebar p.rubric {
139
+ font-family: sans-serif ;
140
+ font-size: medium }
141
+
142
+ div.system-messages {
143
+ margin: 5em }
144
+
145
+ div.system-messages h1 {
146
+ color: red }
147
+
148
+ div.system-message {
149
+ border: medium outset ;
150
+ padding: 1em }
151
+
152
+ div.system-message p.system-message-title {
153
+ color: red ;
154
+ font-weight: bold }
155
+
156
+ div.topic {
157
+ margin: 2em }
158
+
159
+ h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
160
+ h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
161
+ margin-top: 0.4em }
162
+
163
+ h1.title {
164
+ text-align: center }
165
+
166
+ h2.subtitle {
167
+ text-align: center }
168
+
169
+ hr.docutils {
170
+ width: 75% }
171
+
172
+ img.align-left, .figure.align-left, object.align-left, table.align-left {
173
+ clear: left ;
174
+ float: left ;
175
+ margin-right: 1em }
176
+
177
+ img.align-right, .figure.align-right, object.align-right, table.align-right {
178
+ clear: right ;
179
+ float: right ;
180
+ margin-left: 1em }
181
+
182
+ img.align-center, .figure.align-center, object.align-center {
183
+ display: block;
184
+ margin-left: auto;
185
+ margin-right: auto;
186
+ }
187
+
188
+ table.align-center {
189
+ margin-left: auto;
190
+ margin-right: auto;
191
+ }
192
+
193
+ .align-left {
194
+ text-align: left }
195
+
196
+ .align-center {
197
+ clear: both ;
198
+ text-align: center }
199
+
200
+ .align-right {
201
+ text-align: right }
202
+
203
+ /* reset inner alignment in figures */
204
+ div.align-right {
205
+ text-align: inherit }
206
+
207
+ /* div.align-center * { */
208
+ /* text-align: left } */
209
+
210
+ .align-top {
211
+ vertical-align: top }
212
+
213
+ .align-middle {
214
+ vertical-align: middle }
215
+
216
+ .align-bottom {
217
+ vertical-align: bottom }
218
+
219
+ ol.simple, ul.simple {
220
+ margin-bottom: 1em }
221
+
222
+ ol.arabic {
223
+ list-style: decimal }
224
+
225
+ ol.loweralpha {
226
+ list-style: lower-alpha }
227
+
228
+ ol.upperalpha {
229
+ list-style: upper-alpha }
230
+
231
+ ol.lowerroman {
232
+ list-style: lower-roman }
233
+
234
+ ol.upperroman {
235
+ list-style: upper-roman }
236
+
237
+ p.attribution {
238
+ text-align: right ;
239
+ margin-left: 50% }
240
+
241
+ p.caption {
242
+ font-style: italic }
243
+
244
+ p.credits {
245
+ font-style: italic ;
246
+ font-size: smaller }
247
+
248
+ p.label {
249
+ white-space: nowrap }
250
+
251
+ p.rubric {
252
+ font-weight: bold ;
253
+ font-size: larger ;
254
+ color: maroon ;
255
+ text-align: center }
256
+
257
+ p.sidebar-title {
258
+ font-family: sans-serif ;
259
+ font-weight: bold ;
260
+ font-size: larger }
261
+
262
+ p.sidebar-subtitle {
263
+ font-family: sans-serif ;
264
+ font-weight: bold }
265
+
266
+ p.topic-title {
267
+ font-weight: bold }
268
+
269
+ pre.address {
270
+ margin-bottom: 0 ;
271
+ margin-top: 0 ;
272
+ font: inherit }
273
+
274
+ pre.literal-block, pre.doctest-block, pre.math, pre.code {
275
+ margin-left: 2em ;
276
+ margin-right: 2em }
277
+
278
+ pre.code .ln { color: grey; } /* line numbers */
279
+ pre.code, code { background-color: #eeeeee }
280
+ pre.code .comment, code .comment { color: #5C6576 }
281
+ pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
282
+ pre.code .literal.string, code .literal.string { color: #0C5404 }
283
+ pre.code .name.builtin, code .name.builtin { color: #352B84 }
284
+ pre.code .deleted, code .deleted { background-color: #DEB0A1}
285
+ pre.code .inserted, code .inserted { background-color: #A3D289}
286
+
287
+ span.classifier {
288
+ font-family: sans-serif ;
289
+ font-style: oblique }
290
+
291
+ span.classifier-delimiter {
292
+ font-family: sans-serif ;
293
+ font-weight: bold }
294
+
295
+ span.interpreted {
296
+ font-family: sans-serif }
297
+
298
+ span.option {
299
+ white-space: nowrap }
300
+
301
+ span.pre {
302
+ white-space: pre }
303
+
304
+ span.problematic {
305
+ color: red }
306
+
307
+ span.section-subtitle {
308
+ /* font-size relative to parent (h1..h6 element) */
309
+ font-size: 80% }
310
+
311
+ table.citation {
312
+ border-left: solid 1px gray;
313
+ margin-left: 1px }
314
+
315
+ table.docinfo {
316
+ margin: 2em 4em }
317
+
318
+ table.docutils {
319
+ margin-top: 0.5em ;
320
+ margin-bottom: 0.5em }
321
+
322
+ table.footnote {
323
+ border-left: solid 1px black;
324
+ margin-left: 1px }
325
+
326
+ table.docutils td, table.docutils th,
327
+ table.docinfo td, table.docinfo th {
328
+ padding-left: 0.5em ;
329
+ padding-right: 0.5em ;
330
+ vertical-align: top }
331
+
332
+ table.docutils th.field-name, table.docinfo th.docinfo-name {
333
+ font-weight: bold ;
334
+ text-align: left ;
335
+ white-space: nowrap ;
336
+ padding-left: 0 }
337
+
338
+ /* "booktabs" style (no vertical lines) */
339
+ table.docutils.booktabs {
340
+ border: 0px;
341
+ border-top: 2px solid;
342
+ border-bottom: 2px solid;
343
+ border-collapse: collapse;
344
+ }
345
+ table.docutils.booktabs * {
346
+ border: 0px;
347
+ }
348
+ table.docutils.booktabs th {
349
+ border-bottom: thin solid;
350
+ text-align: left;
351
+ }
352
+
353
+ h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
354
+ h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
355
+ font-size: 100% }
356
+
357
+ ul.auto-toc {
358
+ list-style-type: none }
359
+
360
+ </style>
361
+ </head>
362
+ <body>
363
+ <div class="document">
364
+
365
+
366
+
367
+ </div>
368
+ </body>
369
+ </html>
@@ -0,0 +1 @@
1
+ from . import test_fastapi_api_key_dependencies
@@ -0,0 +1,111 @@
1
+ from odoo.tests import tagged
2
+ from odoo.tests.common import TransactionCase
3
+
4
+ from odoo.addons.fastapi_auth_api_key.dependencies import (
5
+ authenticated_auth_api_key,
6
+ authenticated_env_by_auth_api_key,
7
+ authenticated_partner_by_api_key,
8
+ )
9
+
10
+ from fastapi.exceptions import HTTPException
11
+
12
+
13
+ @tagged("-at_install", "post_install")
14
+ class TestFastapiAuthApiKey(TransactionCase):
15
+ @classmethod
16
+ def setUpClass(cls):
17
+ super().setUpClass()
18
+ cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
19
+ demo_user = cls.env.ref("base.user_demo")
20
+ cls.demo_env = demo_user.with_user(demo_user).env
21
+ cls.demo_endpoint = cls.env["fastapi.endpoint"].create(
22
+ {
23
+ "name": "Test Enpoint",
24
+ "app": "demo",
25
+ "root_path": "/path_demo",
26
+ "demo_auth_method": "api_key",
27
+ "user_id": demo_user.id,
28
+ }
29
+ )
30
+ cls.setUpClassApiKey()
31
+
32
+ @classmethod
33
+ def setUpClassApiKey(cls):
34
+ user_model = cls.env["res.users"].with_context(no_reset_password=True)
35
+ cls.authorized_user = user_model.create(
36
+ {
37
+ "name": "John Authorized",
38
+ "login": "johnauth",
39
+ }
40
+ )
41
+ cls.unauthorized_user = user_model.create(
42
+ {
43
+ "name": "Bob Unauthorized",
44
+ "login": "bobunauth",
45
+ }
46
+ )
47
+ api_key_model = cls.env["auth.api.key"]
48
+ cls.authorized_api_key = api_key_model.create(
49
+ {
50
+ "user_id": cls.authorized_user.id,
51
+ "name": "Authorized api key",
52
+ "key": "authorized_key",
53
+ }
54
+ )
55
+ cls.unauthorized_api_key = api_key_model.create(
56
+ {
57
+ "user_id": cls.unauthorized_user.id,
58
+ "name": "Unauthorized api key",
59
+ "key": "unauthorized_key",
60
+ }
61
+ )
62
+ api_key_group_model = cls.env["auth.api.key.group"]
63
+ cls.authorized_api_key_group = api_key_group_model.create(
64
+ {
65
+ "name": "Authorized api key group",
66
+ "code": "authorized_api_key_group",
67
+ "auth_api_key_ids": [(6, 0, cls.authorized_api_key.ids)],
68
+ }
69
+ )
70
+ cls.demo_endpoint.auth_api_key_group_id = cls.authorized_api_key_group
71
+
72
+ def test_authenticated_auth_api_key_no_api_key(self):
73
+ # An exception is raised when no api key is used
74
+ with self.assertRaises(HTTPException) as error:
75
+ authenticated_auth_api_key(False, self.demo_env, self.demo_endpoint)
76
+ self.assertEqual(error.exception.detail, "Missing HTTP-API-KEY header")
77
+
78
+ def test_authenticated_auth_api_key_no_api_key_found(self):
79
+ # An exception is raised when no api key record is found
80
+ with self.assertRaises(HTTPException) as error:
81
+ authenticated_auth_api_key("404", self.demo_env, self.demo_endpoint)
82
+ self.assertEqual(error.exception.detail, ("The key 404 is not allowed",))
83
+
84
+ def test_authenticated_auth_api_key_unauthorized_key(self):
85
+ # An exception is raised when unauthorized api key record is found
86
+ with self.assertRaisesRegex(HTTPException, r"Unauthorized"):
87
+ authenticated_auth_api_key(
88
+ "unauthorized_key", self.demo_env, self.demo_endpoint
89
+ )
90
+
91
+ def test_authenticated_auth_api_key(self):
92
+ result_key = authenticated_auth_api_key(
93
+ "authorized_key", self.demo_env, self.demo_endpoint
94
+ )
95
+ self.assertEqual(result_key, self.authorized_api_key)
96
+
97
+ def test_authenticated_auth_api_key_without_group(self):
98
+ # test with no group set unauthorized is allow to access
99
+ self.demo_endpoint.auth_api_key_group_id = False
100
+ result_key = authenticated_auth_api_key(
101
+ "unauthorized_key", self.demo_env, self.demo_endpoint
102
+ )
103
+ self.assertEqual(result_key, self.unauthorized_api_key)
104
+
105
+ def test_authenticated_partner_by_api_key(self):
106
+ result_partner = authenticated_partner_by_api_key(self.authorized_api_key)
107
+ self.assertEqual(result_partner, self.authorized_user.partner_id)
108
+
109
+ def test_authenticated_env_by_auth_api_key(self):
110
+ result_env = authenticated_env_by_auth_api_key(self.authorized_api_key)
111
+ self.assertEqual(result_env.user, self.authorized_user)
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <!-- Copyright 2024 Camptocamp SA
3
+ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
4
+ <odoo>
5
+
6
+ <record id="fastapi_endpoint_form_view" model="ir.ui.view">
7
+ <field name="name">fastapi.endpoint.form.inherit</field>
8
+ <field name="model">fastapi.endpoint</field>
9
+ <field name="inherit_id" ref="fastapi.fastapi_endpoint_form_view" />
10
+ <field name="arch" type="xml">
11
+ <group name="resoures" position="inside">
12
+ <field name="auth_api_key_group_id" />
13
+ </group>
14
+ </field>
15
+ </record>
16
+
17
+ </odoo>
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.1
2
+ Name: odoo-addon-fastapi_auth_api_key
3
+ Version: 17.0.1.0.0.2
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: odoo-addon-auth_api_key_group>=17.0dev,<17.1dev
6
+ Requires-Dist: odoo-addon-fastapi>=17.0dev,<17.1dev
7
+ Requires-Dist: odoo>=17.0a,<17.1dev
8
+ Summary: Fastapi Auth API Key
9
+ Home-page: https://github.com/OCA/rest-framework
10
+ License: AGPL-3
11
+ Author: Camptocamp, Odoo Community Association (OCA)
12
+ Author-email: support@odoo-community.org
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Framework :: Odoo
15
+ Classifier: Framework :: Odoo :: 17.0
16
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
17
+ Description-Content-Type: text/x-rst
18
+
19
+ .. image:: https://odoo-community.org/readme-banner-image
20
+ :target: https://odoo-community.org/get-involved?utm_source=readme
21
+ :alt: Odoo Community Association
22
+
23
+ ====================
24
+ Fastapi Auth API Key
25
+ ====================
26
+
27
+ ..
28
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
29
+ !! This file is generated by oca-gen-addon-readme !!
30
+ !! changes will be overwritten. !!
31
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
32
+ !! source digest: sha256:aff879a734035d7341665e38dce9d294e1379d01dddf4c0631dcb62f75f2a7e7
33
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
34
+
35
+ .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
36
+ :target: https://odoo-community.org/page/development-status
37
+ :alt: Beta
38
+ .. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
39
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
40
+ :alt: License: AGPL-3
41
+ .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github
42
+ :target: https://github.com/OCA/rest-framework/tree/17.0/fastapi_auth_api_key
43
+ :alt: OCA/rest-framework
44
+ .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
45
+ :target: https://translation.odoo-community.org/projects/rest-framework-17-0/rest-framework-17-0-fastapi_auth_api_key
46
+ :alt: Translate me on Weblate
47
+ .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
48
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=17.0
49
+ :alt: Try me on Runboat
50
+
51
+ |badge1| |badge2| |badge3| |badge4| |badge5|
52
+
53
+ Provides FastAPI dependencies for Api Key authentication.
54
+
55
+ **Table of contents**
56
+
57
+ .. contents::
58
+ :local:
59
+
60
+ Usage
61
+ =====
62
+
63
+ Getting an odoo environment
64
+ ---------------------------
65
+
66
+ If you need to get an odoo env based on the provided api key, you can
67
+ use authenticated_env_by_auth_api_key.
68
+
69
+ .. code:: python
70
+
71
+ @router.get("/example_with_authenticated_env")
72
+ def example_with_authenticated_env(
73
+ env: Annotated[Environment, Depends(authenticated_env_by_auth_api_key)],
74
+ ) -> None:
75
+ # env.user is the user attached to the provided key
76
+ pass
77
+
78
+ Getting the authenticated partner
79
+ ---------------------------------
80
+
81
+ If want to get the partned related to the the provided api key, you can
82
+ use authenticated_partner_by_api_key
83
+
84
+ .. code:: python
85
+
86
+ @router.get("/example_with_authenticated_partner")
87
+ def example_with_authenticated_partner(
88
+ partner: Annotated[Partner, Depends(authenticated_partner_by_api_key)],
89
+ ) -> None:
90
+ # partner is the partner related to the provided key key.user_id.partner_id
91
+ pass
92
+
93
+ Configuration
94
+ -------------
95
+
96
+ For this to work, the api key must be defined on the Endpoint. A new
97
+ field auth_api_key_group_id has been added to the Endpoint model.
98
+
99
+ Bug Tracker
100
+ ===========
101
+
102
+ Bugs are tracked on `GitHub Issues <https://github.com/OCA/rest-framework/issues>`_.
103
+ In case of trouble, please check there if your issue has already been reported.
104
+ If you spotted it first, help us to smash it by providing a detailed and welcomed
105
+ `feedback <https://github.com/OCA/rest-framework/issues/new?body=module:%20fastapi_auth_api_key%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
106
+
107
+ Do not contact contributors directly about support or help with technical issues.
108
+
109
+ Credits
110
+ =======
111
+
112
+ Authors
113
+ -------
114
+
115
+ * Camptocamp
116
+
117
+ Contributors
118
+ ------------
119
+
120
+ - Matthieu Méquignon <matthieu.mequignon@camptocamp.com>
121
+ - Son Ho <sonhd@trobz.com>
122
+
123
+ Other credits
124
+ -------------
125
+
126
+ The migration of this module from 16.0 to 17.0 was financially supported
127
+ by Camptocamp
128
+
129
+ Maintainers
130
+ -----------
131
+
132
+ This module is maintained by the OCA.
133
+
134
+ .. image:: https://odoo-community.org/logo.png
135
+ :alt: Odoo Community Association
136
+ :target: https://odoo-community.org
137
+
138
+ OCA, or the Odoo Community Association, is a nonprofit organization whose
139
+ mission is to support the collaborative development of Odoo features and
140
+ promote its widespread use.
141
+
142
+ .. |maintainer-mmequignon| image:: https://github.com/mmequignon.png?size=40px
143
+ :target: https://github.com/mmequignon
144
+ :alt: mmequignon
145
+
146
+ Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
147
+
148
+ |maintainer-mmequignon|
149
+
150
+ This module is part of the `OCA/rest-framework <https://github.com/OCA/rest-framework/tree/17.0/fastapi_auth_api_key>`_ project on GitHub.
151
+
152
+ You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
@@ -0,0 +1,19 @@
1
+ odoo/addons/fastapi_auth_api_key/README.rst,sha256=3_NEw2Cs2g_lLF6ecNG58dQFItqC1oTle1CvVd4oOrc,4539
2
+ odoo/addons/fastapi_auth_api_key/__init__.py,sha256=X9EJGOE2GtZbS0G82PtSXmWSZ_R8jEM0rlJTDliQjp4,21
3
+ odoo/addons/fastapi_auth_api_key/__manifest__.py,sha256=MczNigaWO00mSBj0Onx1Zs5i-fysEoRe9JW7cPb2i7w,525
4
+ odoo/addons/fastapi_auth_api_key/dependencies.py,sha256=L8-MovwUEyLbHSqRgM1jOk613hnR3okL8IxUl0MJ-Ok,2472
5
+ odoo/addons/fastapi_auth_api_key/models/__init__.py,sha256=NF75IvaT9G60mgm2FqAkfw91SzMZ9m7CL41HiOD3rj4,31
6
+ odoo/addons/fastapi_auth_api_key/models/fastapi_endpoint.py,sha256=o6mY2tDaq0O48zZEW9HtyLwH5CZlD-hedJJju8y2ZaM,364
7
+ odoo/addons/fastapi_auth_api_key/readme/CONTRIBUTORS.md,sha256=mx3lro8qYrF4g6_PfPHtP67Hgg0ZxjWogLbPCTndOTo,93
8
+ odoo/addons/fastapi_auth_api_key/readme/CREDITS.md,sha256=my2DTaWSAwefqnxfRSXjioSzh2xziwgUHxLCcdypQuk,87
9
+ odoo/addons/fastapi_auth_api_key/readme/DESCRIPTION.md,sha256=6b3Y62QEGlT7pl5KClLfoFKUiRZRszvLGLjKhs8hx3w,58
10
+ odoo/addons/fastapi_auth_api_key/readme/USAGE.md,sha256=RJKRqTYkJzfYgei0op3Cv760AYe2fS3zds8cTqCN9FI,978
11
+ odoo/addons/fastapi_auth_api_key/static/description/icon.png,sha256=CgnOEZCwoe6f1vlLwkqFVfc2q_uwBMU0UnXN8j6X5ag,10254
12
+ odoo/addons/fastapi_auth_api_key/static/description/index.html,sha256=cJaH4ZOi11VMtG9IyLN-2F95ZV5zvDidBurJw4bG25Y,7729
13
+ odoo/addons/fastapi_auth_api_key/tests/__init__.py,sha256=bYedQRS3KDphc7VnwZSrJ-EPD0joTC8XCtSmXI6g2_Q,48
14
+ odoo/addons/fastapi_auth_api_key/tests/test_fastapi_api_key_dependencies.py,sha256=a9IHS-3pMzm5F1vSx_pip9_lzY1v5XOBU6s4lDMcPuo,4412
15
+ odoo/addons/fastapi_auth_api_key/views/fastapi_endpoint.xml,sha256=QP05xAoftf5XEdjYEfKdf9WplCkPFTzVs-m86TP_YHg,599
16
+ odoo_addon_fastapi_auth_api_key-17.0.1.0.0.2.dist-info/METADATA,sha256=GuLlHUTxrhBIM6U0MNlJXoDr5PK0r-Qq3_7dQUoXkTw,5210
17
+ odoo_addon_fastapi_auth_api_key-17.0.1.0.0.2.dist-info/WHEEL,sha256=ZhOvUsYhy81Dx67gN3TV0RchQWBIIzutDZaJODDg2Vo,81
18
+ odoo_addon_fastapi_auth_api_key-17.0.1.0.0.2.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
19
+ odoo_addon_fastapi_auth_api_key-17.0.1.0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: Whool 1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+