|
| 1 | +# Copyright 2024 Akretion (http://www.akretion.com). |
| 2 | +# @author Florian Mounier <florian.mounier@akretion.com> |
| 3 | +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
| 4 | +from secrets import token_urlsafe |
| 5 | +from typing import Annotated, Callable, Dict, List |
| 6 | + |
| 7 | +from fastapi import APIRouter, Depends, HTTPException, status |
| 8 | +from fastapi.security import APIKeyHeader |
| 9 | + |
| 10 | +from odoo import api, fields, models |
| 11 | +from odoo.api import Environment |
| 12 | + |
| 13 | +from odoo.addons.fastapi.dependencies import fastapi_endpoint, odoo_env |
| 14 | + |
| 15 | +from ..dependencies import authenticated_cross_connect_client |
| 16 | +from ..routers import cross_connect_router |
| 17 | +from .cross_connect_client import CrossConnectClient |
| 18 | + |
| 19 | + |
| 20 | +class FastapiEndpoint(models.Model): |
| 21 | + _inherit = "fastapi.endpoint" |
| 22 | + |
| 23 | + app = fields.Selection( |
| 24 | + selection_add=[("cross_connect", "Cross Connect Endpoint")], |
| 25 | + ondelete={"cross_connect": "cascade"}, |
| 26 | + ) |
| 27 | + |
| 28 | + cross_connect_client_ids = fields.One2many( |
| 29 | + "cross.connect.client", |
| 30 | + "endpoint_id", |
| 31 | + string="Cross Connect Clients", |
| 32 | + help="The clients that can access this endpoint.", |
| 33 | + ) |
| 34 | + cross_connect_allowed_group_ids = fields.Many2many( |
| 35 | + "res.groups", |
| 36 | + string="Cross Connect Allowed Groups", |
| 37 | + help="The groups that can access the cross connect clients of this endpoint.", |
| 38 | + ) |
| 39 | + cross_connect_secret_key = fields.Char( |
| 40 | + help="The secret key used for cross connection.", |
| 41 | + required=True, |
| 42 | + default=lambda self: self._generate_secret_key(), |
| 43 | + ) |
| 44 | + |
| 45 | + @api.model |
| 46 | + def _generate_secret_key(self): |
| 47 | + # generate random ~64 chars secret key |
| 48 | + return token_urlsafe(64) |
| 49 | + |
| 50 | + def _get_fastapi_routers(self) -> List[APIRouter]: |
| 51 | + routers = super()._get_fastapi_routers() |
| 52 | + |
| 53 | + if self.app == "cross_connect": |
| 54 | + routers += [cross_connect_router] |
| 55 | + |
| 56 | + return routers |
| 57 | + |
| 58 | + def _get_app_dependencies_overrides(self) -> Dict[Callable, Callable]: |
| 59 | + overrides = super()._get_app_dependencies_overrides() |
| 60 | + |
| 61 | + if self.app == "cross_connect": |
| 62 | + overrides[ |
| 63 | + authenticated_cross_connect_client |
| 64 | + ] = api_key_based_authenticated_cross_connect_client |
| 65 | + |
| 66 | + return overrides |
| 67 | + |
| 68 | + def _get_routing_info(self): |
| 69 | + if self.app == "cross_connect": |
| 70 | + # Force to not save the HTTP session for the login to work correctly |
| 71 | + self.save_http_session = False |
| 72 | + return super()._get_routing_info() |
| 73 | + |
| 74 | + @property |
| 75 | + def _server_env_fields(self): |
| 76 | + return {"cross_connect_secret_key": {}} |
| 77 | + |
| 78 | + |
| 79 | +def api_key_based_authenticated_cross_connect_client( |
| 80 | + api_key: Annotated[ |
| 81 | + str, |
| 82 | + Depends( |
| 83 | + APIKeyHeader( |
| 84 | + name="api-key", |
| 85 | + description="Cross Connect Client API key.", |
| 86 | + ) |
| 87 | + ), |
| 88 | + ], |
| 89 | + fastapi_endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)], |
| 90 | + env: Annotated[Environment, Depends(odoo_env)], |
| 91 | +) -> CrossConnectClient: |
| 92 | + cross_connect_client = ( |
| 93 | + env["cross.connect.client"] |
| 94 | + .sudo() |
| 95 | + .search( |
| 96 | + [("api_key", "=", api_key), ("endpoint_id", "=", fastapi_endpoint.id)], |
| 97 | + limit=1, |
| 98 | + ) |
| 99 | + ) |
| 100 | + if not cross_connect_client: |
| 101 | + raise HTTPException( |
| 102 | + status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect API Key" |
| 103 | + ) |
| 104 | + return cross_connect_client |
0 commit comments