-
-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
035093d
commit 7c9a99d
Showing
23 changed files
with
1,396 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
==================== | ||
Cross Connect Server | ||
==================== | ||
|
||
.. | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
!! This file is generated by oca-gen-addon-readme !! | ||
!! changes will be overwritten. !! | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
!! source digest: sha256:e7f2983ebb91caf2611da85b500923b3a91de86fbb4577c967a2a30e0ce7e739 | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png | ||
:target: https://odoo-community.org/page/development-status | ||
:alt: Beta | ||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png | ||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html | ||
:alt: License: AGPL-3 | ||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github | ||
:target: https://github.com/OCA/server-auth/tree/16.0/cross_connect_server | ||
:alt: OCA/server-auth | ||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png | ||
:target: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-cross_connect_server | ||
:alt: Translate me on Weblate | ||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png | ||
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=16.0 | ||
:alt: Try me on Runboat | ||
|
||
|badge1| |badge2| |badge3| |badge4| |badge5| | ||
|
||
This module allows other odoo instances, where the | ||
``cross_connect_client`` module is installed and configured, users to | ||
connect directly on this odoo instance. | ||
|
||
**Table of contents** | ||
|
||
.. contents:: | ||
:local: | ||
|
||
Usage | ||
===== | ||
|
||
First of all after installing the module, you need to configure a | ||
fastapi endpoint. | ||
|
||
In order to do that, you need to go to the menu | ||
``FastAPI > FastAPI Endpoints`` and create a new endpoint for the client | ||
to connect to. | ||
|
||
Fill the fields with the endpoint's information : | ||
|
||
- App: ``cross_connect`` | ||
- Cross Connect Allowed Groups: The groups that will be allowed to be | ||
selected for the clients groups. | ||
|
||
Then for each client, you will have to add an entry in the | ||
``Cross Connect Clients`` table. | ||
|
||
An api key will be automatically generated for each client, this is the | ||
key that you will have to provide to the client in order for them to | ||
connect to the server. You will also have to choose the groups that this | ||
client will be able to give to its users. | ||
|
||
Bug Tracker | ||
=========== | ||
|
||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_. | ||
In case of trouble, please check there if your issue has already been reported. | ||
If you spotted it first, help us to smash it by providing a detailed and welcomed | ||
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20cross_connect_server%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. | ||
|
||
Do not contact contributors directly about support or help with technical issues. | ||
|
||
Credits | ||
======= | ||
|
||
Authors | ||
------- | ||
|
||
* Akretion | ||
|
||
Contributors | ||
------------ | ||
|
||
- Florian Mounier [email protected] | ||
|
||
Maintainers | ||
----------- | ||
|
||
This module is maintained by the OCA. | ||
|
||
.. image:: https://odoo-community.org/logo.png | ||
:alt: Odoo Community Association | ||
:target: https://odoo-community.org | ||
|
||
OCA, or the Odoo Community Association, is a nonprofit organization whose | ||
mission is to support the collaborative development of Odoo features and | ||
promote its widespread use. | ||
|
||
.. |maintainer-paradoxxxzero| image:: https://github.com/paradoxxxzero.png?size=40px | ||
:target: https://github.com/paradoxxxzero | ||
:alt: paradoxxxzero | ||
|
||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__: | ||
|
||
|maintainer-paradoxxxzero| | ||
|
||
This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/16.0/cross_connect_server>`_ project on GitHub. | ||
|
||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Copyright 2024 Akretion (http://www.akretion.com). | ||
# @author Florian Mounier <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
{ | ||
"name": "Cross Connect Server", | ||
"version": "16.0.1.0.0", | ||
"author": "Akretion, Odoo Community Association (OCA)", | ||
"summary": "Cross Connect Server allows Cross Connect Client to connect to it.", | ||
"category": "Tools", | ||
"depends": ["extendable_fastapi", "server_environment"], | ||
"website": "https://github.com/OCA/server-auth", | ||
"data": [ | ||
"security/res_groups.xml", | ||
"security/ir_model_access.xml", | ||
"views/fastapi_endpoint_views.xml", | ||
], | ||
"maintainers": ["paradoxxxzero"], | ||
"demo": [], | ||
"installable": True, | ||
"license": "AGPL-3", | ||
"external_dependencies": { | ||
"python": ["pyjwt"], | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Copyright 2024 Akretion (http://www.akretion.com). | ||
# @author Florian Mounier <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from .models.cross_connect_client import CrossConnectClient | ||
|
||
|
||
def authenticated_cross_connect_client() -> CrossConnectClient: | ||
pass | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import cross_connect_client | ||
from . import fastapi_endpoint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Copyright 2024 Akretion (http://www.akretion.com). | ||
# @author Florian Mounier <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from datetime import datetime, timedelta, timezone | ||
from secrets import token_urlsafe | ||
|
||
import jwt | ||
|
||
from odoo import _, api, fields, models | ||
from odoo.exceptions import AccessDenied | ||
|
||
|
||
class CrossConnectClient(models.Model): | ||
_name = "cross.connect.client" | ||
_description = "Cross Connect Client" | ||
_inherit = "server.env.mixin" | ||
|
||
name = fields.Char(required=True) | ||
|
||
endpoint_id = fields.Many2one( | ||
"fastapi.endpoint", | ||
required=True, | ||
string="Endpoint", | ||
) | ||
|
||
api_key = fields.Char( | ||
required=True, | ||
string="API Key", | ||
help="The API key to give to configure on the client.", | ||
default=lambda self: self._generate_api_key(), | ||
) | ||
|
||
allowed_group_ids = fields.Many2many( | ||
related="endpoint_id.cross_connect_allowed_group_ids", | ||
) | ||
|
||
group_ids = fields.Many2many( | ||
"res.groups", | ||
string="Groups", | ||
help="The groups that this client belongs to.", | ||
domain="[('id', 'in', allowed_group_ids)]", | ||
) | ||
|
||
user_ids = fields.Many2many( | ||
"res.users", | ||
string="Users", | ||
help="The users created by this cross connection.", | ||
) | ||
user_count = fields.Integer( | ||
compute="_compute_user_count", | ||
string="Cross Connected User Count", | ||
help="The number of users created by this cross connection.", | ||
) | ||
|
||
@api.model | ||
def _generate_api_key(self): | ||
# generate random ~64 chars secret key | ||
return token_urlsafe(64) | ||
|
||
@api.depends("user_ids") | ||
def _compute_user_count(self): | ||
for record in self: | ||
record.user_count = len(record.user_ids) | ||
|
||
def _request_access(self, access_request): | ||
# check groups | ||
groups = self.env["res.groups"].browse(access_request.groups) | ||
if groups - self.group_ids or not groups.exists(): | ||
raise AccessDenied(_("You are not allowed to access this endpoint.")) | ||
|
||
# TODO: check remote id change? | ||
user = self.user_ids.filtered(lambda u: u.login == access_request.login) | ||
vals = { | ||
"login": access_request.login, | ||
"name": access_request.name, | ||
"lang": access_request.lang, | ||
"groups_id": [(6, 0, groups.ids)], | ||
} | ||
# Create user if not exists | ||
if not user: | ||
user = self.env["res.users"].create(vals) | ||
self.user_ids |= user | ||
else: | ||
user.write(vals) | ||
|
||
return jwt.encode( | ||
{ | ||
"exp": datetime.now(tz=timezone.utc) + timedelta(minutes=2), | ||
"aud": str(self.id), | ||
"id": user.id, | ||
}, | ||
self.endpoint_id.cross_connect_secret_key, | ||
algorithm="HS256", | ||
) | ||
|
||
def _log_from_token(self, token): | ||
try: | ||
obj = jwt.decode( | ||
token, | ||
self.endpoint_id.cross_connect_secret_key, | ||
audience=str(self.id), | ||
options={"require": ["exp", "aud", "id"]}, | ||
algorithms=["HS256"], | ||
) | ||
except jwt.PyJWTError as e: | ||
raise AccessDenied(_("Invalid Token")) from e | ||
|
||
user = self.env["res.users"].browse(obj["id"]) | ||
|
||
if not user: | ||
raise AccessDenied(_("Invalid Token")) | ||
|
||
return user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Copyright 2024 Akretion (http://www.akretion.com). | ||
# @author Florian Mounier <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from secrets import token_urlsafe | ||
from typing import Annotated, Callable, Dict, List | ||
|
||
from fastapi import APIRouter, Depends, HTTPException, status | ||
from fastapi.security import APIKeyHeader | ||
|
||
from odoo import api, fields, models | ||
from odoo.api import Environment | ||
|
||
from odoo.addons.fastapi.dependencies import fastapi_endpoint, odoo_env | ||
|
||
from ..dependencies import authenticated_cross_connect_client | ||
from ..routers import cross_connect_router | ||
from .cross_connect_client import CrossConnectClient | ||
|
||
|
||
class FastapiEndpoint(models.Model): | ||
_inherit = "fastapi.endpoint" | ||
|
||
app = fields.Selection( | ||
selection_add=[("cross_connect", "Cross Connect Endpoint")], | ||
ondelete={"cross_connect": "cascade"}, | ||
) | ||
|
||
cross_connect_client_ids = fields.One2many( | ||
"cross.connect.client", | ||
"endpoint_id", | ||
string="Cross Connect Clients", | ||
help="The clients that can access this endpoint.", | ||
) | ||
cross_connect_allowed_group_ids = fields.Many2many( | ||
"res.groups", | ||
string="Cross Connect Allowed Groups", | ||
help="The groups that can access the cross connect clients of this endpoint.", | ||
) | ||
cross_connect_secret_key = fields.Char( | ||
help="The secret key used for cross connection.", | ||
required=True, | ||
default=lambda self: self._generate_secret_key(), | ||
) | ||
|
||
@api.model | ||
def _generate_secret_key(self): | ||
# generate random ~64 chars secret key | ||
return token_urlsafe(64) | ||
|
||
def _get_fastapi_routers(self) -> List[APIRouter]: | ||
routers = super()._get_fastapi_routers() | ||
|
||
if self.app == "cross_connect": | ||
routers += [cross_connect_router] | ||
|
||
return routers | ||
|
||
def _get_app_dependencies_overrides(self) -> Dict[Callable, Callable]: | ||
overrides = super()._get_app_dependencies_overrides() | ||
|
||
if self.app == "cross_connect": | ||
overrides[ | ||
authenticated_cross_connect_client | ||
] = api_key_based_authenticated_cross_connect_client | ||
|
||
return overrides | ||
|
||
def _get_routing_info(self): | ||
if self.app == "cross_connect": | ||
# Force to not save the HTTP session for the login to work correctly | ||
self.save_http_session = False | ||
return super()._get_routing_info() | ||
|
||
@property | ||
def _server_env_fields(self): | ||
return {"cross_connect_secret_key": {}} | ||
|
||
|
||
def api_key_based_authenticated_cross_connect_client( | ||
api_key: Annotated[ | ||
str, | ||
Depends( | ||
APIKeyHeader( | ||
name="api-key", | ||
description="Cross Connect Client API key.", | ||
) | ||
), | ||
], | ||
fastapi_endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)], | ||
env: Annotated[Environment, Depends(odoo_env)], | ||
) -> CrossConnectClient: | ||
cross_connect_client = ( | ||
env["cross.connect.client"] | ||
.sudo() | ||
.search( | ||
[("api_key", "=", api_key), ("endpoint_id", "=", fastapi_endpoint.id)], | ||
limit=1, | ||
) | ||
) | ||
if not cross_connect_client: | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect API Key" | ||
) | ||
return cross_connect_client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
- Florian Mounier <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
This module allows other odoo instances, where the `cross_connect_client` module is | ||
installed and configured, users to connect directly on this odoo instance. |
Oops, something went wrong.