From 290a06cc1629982cc7a9e606eaf1dce097599075 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 31 Mar 2025 17:59:42 +0200 Subject: [PATCH 01/18] [ADD] cross_connect_server --- cross_connect_server/README.rst | 110 +++++ cross_connect_server/__init__.py | 1 + cross_connect_server/__manifest__.py | 25 + cross_connect_server/dependencies.py | 9 + cross_connect_server/models/__init__.py | 3 + .../models/cross_connect_client.py | 118 +++++ .../models/fastapi_endpoint.py | 104 ++++ cross_connect_server/models/res_users.py | 19 + cross_connect_server/readme/CONTRIBUTORS.md | 1 + cross_connect_server/readme/DESCRIPTION.md | 2 + cross_connect_server/readme/USAGE.md | 17 + cross_connect_server/routers/__init__.py | 1 + cross_connect_server/routers/cross_connect.py | 78 +++ cross_connect_server/schemas.py | 47 ++ .../security/ir_model_access.xml | 17 + cross_connect_server/security/res_groups.xml | 15 + .../static/description/index.html | 448 ++++++++++++++++++ cross_connect_server/tests/__init__.py | 1 + .../tests/test_cross_connect_server.py | 373 +++++++++++++++ .../views/fastapi_endpoint_views.xml | 46 ++ 20 files changed, 1435 insertions(+) create mode 100644 cross_connect_server/README.rst create mode 100644 cross_connect_server/__init__.py create mode 100644 cross_connect_server/__manifest__.py create mode 100644 cross_connect_server/dependencies.py create mode 100644 cross_connect_server/models/__init__.py create mode 100644 cross_connect_server/models/cross_connect_client.py create mode 100644 cross_connect_server/models/fastapi_endpoint.py create mode 100644 cross_connect_server/models/res_users.py create mode 100644 cross_connect_server/readme/CONTRIBUTORS.md create mode 100644 cross_connect_server/readme/DESCRIPTION.md create mode 100644 cross_connect_server/readme/USAGE.md create mode 100644 cross_connect_server/routers/__init__.py create mode 100644 cross_connect_server/routers/cross_connect.py create mode 100644 cross_connect_server/schemas.py create mode 100644 cross_connect_server/security/ir_model_access.xml create mode 100644 cross_connect_server/security/res_groups.xml create mode 100644 cross_connect_server/static/description/index.html create mode 100644 cross_connect_server/tests/__init__.py create mode 100644 cross_connect_server/tests/test_cross_connect_server.py create mode 100644 cross_connect_server/views/fastapi_endpoint_views.xml diff --git a/cross_connect_server/README.rst b/cross_connect_server/README.rst new file mode 100644 index 0000000000..c274ff7a11 --- /dev/null +++ b/cross_connect_server/README.rst @@ -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 `_. +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Florian Mounier florian.mounier@akretion.com + +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 `__: + +|maintainer-paradoxxxzero| + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/cross_connect_server/__init__.py b/cross_connect_server/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/cross_connect_server/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/cross_connect_server/__manifest__.py b/cross_connect_server/__manifest__.py new file mode 100644 index 0000000000..57e066de65 --- /dev/null +++ b/cross_connect_server/__manifest__.py @@ -0,0 +1,25 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# 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"], + }, +} diff --git a/cross_connect_server/dependencies.py b/cross_connect_server/dependencies.py new file mode 100644 index 0000000000..6f2ce7c3af --- /dev/null +++ b/cross_connect_server/dependencies.py @@ -0,0 +1,9 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# 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 diff --git a/cross_connect_server/models/__init__.py b/cross_connect_server/models/__init__.py new file mode 100644 index 0000000000..108ba9f5c2 --- /dev/null +++ b/cross_connect_server/models/__init__.py @@ -0,0 +1,3 @@ +from . import cross_connect_client +from . import fastapi_endpoint +from . import res_users diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py new file mode 100644 index 0000000000..04f5f0ed9a --- /dev/null +++ b/cross_connect_server/models/cross_connect_client.py @@ -0,0 +1,118 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# 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.One2many( + "res.users", + "cross_connect_client_id", + 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.")) + + user = self.user_ids.filtered( + lambda u: u.cross_connect_client_user_id == access_request.id + ) + vals = { + "login": access_request.login, + "email": access_request.email, + "name": access_request.name, + "lang": access_request.lang, + "groups_id": [(6, 0, groups.ids)], + "cross_connect_client_id": self.id, + "cross_connect_client_user_id": access_request.id, + } + # Create user if not exists + if not user: + user = self.env["res.users"].create(vals) + else: + user.write(vals) + + return jwt.encode( + { + "exp": datetime.now(tz=timezone.utc) + timedelta(minutes=2), + "aud": str(self.id), + "id": user.id, + "redirect_url": access_request.redirect_url or "/web", + }, + 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, obj["redirect_url"] diff --git a/cross_connect_server/models/fastapi_endpoint.py b/cross_connect_server/models/fastapi_endpoint.py new file mode 100644 index 0000000000..774e485346 --- /dev/null +++ b/cross_connect_server/models/fastapi_endpoint.py @@ -0,0 +1,104 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# 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 diff --git a/cross_connect_server/models/res_users.py b/cross_connect_server/models/res_users.py new file mode 100644 index 0000000000..d98d7ef9c5 --- /dev/null +++ b/cross_connect_server/models/res_users.py @@ -0,0 +1,19 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + cross_connect_client_id = fields.Many2one( + "cross.connect.client", + string="Cross Connect Client", + help="The cross connect client that created this user.", + ) + cross_connect_client_user_id = fields.Integer( + string="Cross Connect Client User ID", + help="The user ID on the cross connect client.", + ) diff --git a/cross_connect_server/readme/CONTRIBUTORS.md b/cross_connect_server/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..328a37da87 --- /dev/null +++ b/cross_connect_server/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Florian Mounier diff --git a/cross_connect_server/readme/DESCRIPTION.md b/cross_connect_server/readme/DESCRIPTION.md new file mode 100644 index 0000000000..4a034abe18 --- /dev/null +++ b/cross_connect_server/readme/DESCRIPTION.md @@ -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. diff --git a/cross_connect_server/readme/USAGE.md b/cross_connect_server/readme/USAGE.md new file mode 100644 index 0000000000..6d6e80bd57 --- /dev/null +++ b/cross_connect_server/readme/USAGE.md @@ -0,0 +1,17 @@ +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. diff --git a/cross_connect_server/routers/__init__.py b/cross_connect_server/routers/__init__.py new file mode 100644 index 0000000000..114cbd2c8b --- /dev/null +++ b/cross_connect_server/routers/__init__.py @@ -0,0 +1 @@ +from .cross_connect import cross_connect_router diff --git a/cross_connect_server/routers/cross_connect.py b/cross_connect_server/routers/cross_connect.py new file mode 100644 index 0000000000..94fe4ee83c --- /dev/null +++ b/cross_connect_server/routers/cross_connect.py @@ -0,0 +1,78 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from typing import Annotated + +from fastapi import APIRouter, Depends +from fastapi.responses import RedirectResponse + +from odoo import _, api +from odoo.exceptions import MissingError +from odoo.http import SESSION_LIFETIME, root + +from odoo.addons.fastapi.dependencies import odoo_env + +from ..dependencies import authenticated_cross_connect_client +from ..models.cross_connect_client import CrossConnectClient +from ..schemas import AccessRequest, AccessResponse, SyncResponse + +cross_connect_router = APIRouter(tags=["Cross Connect"]) + + +@cross_connect_router.get("/cross_connect/sync") +async def sync( + cross_connect_client: Annotated[ + CrossConnectClient, Depends(authenticated_cross_connect_client) + ], +) -> SyncResponse: + """Send back to client sync information.""" + return SyncResponse.from_groups(cross_connect_client.group_ids) + + +@cross_connect_router.post("/cross_connect/access") +async def access( + cross_connect_client: Annotated[ + CrossConnectClient, Depends(authenticated_cross_connect_client) + ], + access_request: AccessRequest, +) -> AccessResponse: + """Send back to client a token.""" + return AccessResponse.from_params( + client_id=cross_connect_client.id, + token=cross_connect_client.sudo()._request_access(access_request), + ) + + +@cross_connect_router.get("/cross_connect/login/{client_id}/{token}") +async def login( + client_id: int, + token: str, + env: Annotated[api.Environment, Depends(odoo_env)], +) -> RedirectResponse: + """Log user and redirect to odoo index.""" + cross_connect_client = env["cross.connect.client"].sudo().browse(client_id) + if not cross_connect_client: + raise MissingError(_("Client not found")) + user, redirect_url = cross_connect_client.sudo()._log_from_token(token) + user = user.with_user(user) + user._update_last_login() + env = env(user=user.id) + + # Create a odoo session + session = root.session_store.new() + session.db = env.cr.dbname + session.uid = user.id + session.login = user.login + session.context = dict(env["res.users"].context_get()) + session.session_token = user._compute_session_token(session.sid) + root.session_store.save(session) + # Redirect after login + response = RedirectResponse(url=redirect_url) + response.set_cookie( + "session_id", + session.sid, + httponly=True, + max_age=SESSION_LIFETIME, + ) + return response diff --git a/cross_connect_server/schemas.py b/cross_connect_server/schemas.py new file mode 100644 index 0000000000..04e59628f7 --- /dev/null +++ b/cross_connect_server/schemas.py @@ -0,0 +1,47 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from extendable_pydantic import StrictExtendableBaseModel + + +class CrossConnectGroup(StrictExtendableBaseModel): + id: int + name: str + comment: str | None = None + + @classmethod + def from_group(cls, group): + return cls.model_construct( + id=group.id, + name=group.full_name, + comment=group.comment or None, + ) + + +class SyncResponse(StrictExtendableBaseModel): + groups: list[CrossConnectGroup] + + @classmethod + def from_groups(cls, groups): + return cls.model_construct( + groups=[CrossConnectGroup.from_group(group) for group in groups] + ) + + +class AccessRequest(StrictExtendableBaseModel, extra="ignore"): + id: int + name: str + login: str + email: str + lang: str + groups: list[int] + redirect_url: str = None + + +class AccessResponse(StrictExtendableBaseModel): + client_id: int + token: str + + @classmethod + def from_params(cls, token, client_id): + return cls.model_construct(token=token, client_id=client_id) diff --git a/cross_connect_server/security/ir_model_access.xml b/cross_connect_server/security/ir_model_access.xml new file mode 100644 index 0000000000..cac56e1398 --- /dev/null +++ b/cross_connect_server/security/ir_model_access.xml @@ -0,0 +1,17 @@ + + + + + Cross Connect Client: Manager RW access + + + + + + + + diff --git a/cross_connect_server/security/res_groups.xml b/cross_connect_server/security/res_groups.xml new file mode 100644 index 0000000000..33defad930 --- /dev/null +++ b/cross_connect_server/security/res_groups.xml @@ -0,0 +1,15 @@ + + + + + Cross Connect Manager + + + diff --git a/cross_connect_server/static/description/index.html b/cross_connect_server/static/description/index.html new file mode 100644 index 0000000000..fed0bcb4ea --- /dev/null +++ b/cross_connect_server/static/description/index.html @@ -0,0 +1,448 @@ + + + + + +Cross Connect Server + + + +
+

Cross Connect Server

+ + +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

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

+ +
+

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. +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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

Current maintainer:

+

paradoxxxzero

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/cross_connect_server/tests/__init__.py b/cross_connect_server/tests/__init__.py new file mode 100644 index 0000000000..f49cef96d5 --- /dev/null +++ b/cross_connect_server/tests/__init__.py @@ -0,0 +1 @@ +from . import test_cross_connect_server diff --git a/cross_connect_server/tests/test_cross_connect_server.py b/cross_connect_server/tests/test_cross_connect_server.py new file mode 100644 index 0000000000..9006cf61a8 --- /dev/null +++ b/cross_connect_server/tests/test_cross_connect_server.py @@ -0,0 +1,373 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.http import root +from odoo.tests.common import RecordCapturer, tagged + +from odoo.addons.extendable_fastapi.tests.common import FastAPITransactionCase + +from ..routers import cross_connect_router + + +@tagged("post_install", "-at_install") +class TestCrossConnectServer(FastAPITransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.endpoint = cls.env["fastapi.endpoint"].create( + { + "name": "Cross Connect Server Endpoint", + "root_path": "/api", + "app": "cross_connect", + } + ) + cls.available_groups = ( + cls.env.ref("base.group_user") + | cls.env.ref("fastapi.group_fastapi_user") + | cls.env.ref("fastapi.group_fastapi_manager") + ) + + cls.endpoint.cross_connect_allowed_group_ids = cls.available_groups + + cls.client = cls.env["cross.connect.client"].create( + { + "name": "Test Client", + "endpoint_id": cls.endpoint.id, + "api_key": "server-api-key", + "group_ids": [ + ( + 6, + 0, + ( + cls.available_groups + - cls.env.ref("fastapi.group_fastapi_manager") + ).ids, + ) + ], + } + ) + + cls.other_client = cls.env["cross.connect.client"].create( + { + "name": "Other Test Client", + "endpoint_id": cls.endpoint.id, + "api_key": "other-server-api-key", + "group_ids": [ + ( + 6, + 0, + (cls.available_groups - cls.env.ref("base.group_user")).ids, + ) + ], + } + ) + + cls.endpoint_user = cls.env["res.users"].create( + { + "name": "FastAPI Endpoint User", + "login": "fastapi_endpoint_user", + "groups_id": [ + (6, 0, [cls.env.ref("fastapi.group_fastapi_endpoint_runner").id]) + ], + } + ) + + cls.endpoint._handle_registry_sync(cls.endpoint.ids) + + cls.default_fastapi_running_user = cls.endpoint_user + cls.default_fastapi_router = cross_connect_router + cls.default_fastapi_app = cls.endpoint._get_app() + cls.default_fastapi_dependency_overrides = ( + cls.default_fastapi_app.dependency_overrides + ) + cls.default_fastapi_app.exception_handlers = {} + + def test_base(self): + self.assertTrue(self.endpoint.cross_connect_secret_key) + self.assertEqual(len(self.endpoint.cross_connect_client_ids), 2) + self.assertFalse(self.endpoint.save_http_session) + self.assertFalse(self.client.user_ids) + + def test_sync_ok(self): + with self._create_test_client() as test_client: + response = test_client.get( + "/cross_connect/sync", headers={"api-key": "server-api-key"} + ) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), + { + "groups": [ + { + "id": self.env.ref("base.group_user").id, + "name": "User types / Internal User", + "comment": None, + }, + { + "id": self.env.ref("fastapi.group_fastapi_user").id, + "name": "FastAPI / User", + "comment": None, + }, + ] + }, + ) + + def test_sync_other(self): + with self._create_test_client() as test_client: + response = test_client.get( + "/cross_connect/sync", headers={"api-key": "other-server-api-key"} + ) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.json(), + { + "groups": [ + { + "id": self.env.ref("fastapi.group_fastapi_manager").id, + "name": "FastAPI / Administrator", + "comment": None, + }, + { + "id": self.env.ref("fastapi.group_fastapi_user").id, + "name": "FastAPI / User", + "comment": None, + }, + ] + }, + ) + + def test_sync_401(self): + with self._create_test_client(raise_server_exceptions=False) as test_client: + response = test_client.get( + "/cross_connect/sync", headers={"api-key": "wrong-api-key"} + ) + self.assertEqual(response.status_code, 401) + + def test_access_ok(self): + with RecordCapturer(self.env["res.users"], []) as rc: + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + + self.assertEqual(response.status_code, 200) + json = response.json() + self.assertEqual(json["client_id"], self.client.id) + self.assertTrue(json["token"]) + + self.assertEqual(len(rc.records), 1) + new_user = rc.records[0] + self.assertEqual(new_user.name, "Client User") + self.assertEqual(new_user.login, "user@client.example.org") + self.assertEqual(new_user.email, "user@client.example.org") + self.assertEqual(new_user.lang, "en_US") + self.assertEqual(new_user.cross_connect_client_id.id, self.client.id) + self.assertEqual(new_user.cross_connect_client_user_id, 12) + self.assertIn( + self.env.ref("base.group_user"), + new_user.groups_id, + ) + self.assertNotIn(self.env.ref("fastapi.group_fastapi_user"), new_user.groups_id) + self.assertNotIn( + self.env.ref("fastapi.group_fastapi_manager"), new_user.groups_id + ) + + def test_access_401(self): + with RecordCapturer(self.env["res.users"], []) as rc: + with self._create_test_client(raise_server_exceptions=False) as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "wrong-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + + self.assertEqual(response.status_code, 401) + self.assertEqual(len(rc.records), 0) + + def test_access_wrong_groups(self): + with RecordCapturer(self.env["res.users"], []) as rc: + with self._create_test_client(raise_server_exceptions=False) as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "wrong-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("fastapi.group_fastapi_manager").id, + ], + }, + ) + + self.assertEqual(response.status_code, 401) + self.assertEqual(len(rc.records), 0) + + def test_access_existing(self): + with RecordCapturer(self.env["res.users"], []) as rc: + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + self.assertEqual(response.status_code, 200) + + with RecordCapturer(self.env["res.users"], []) as rc2: + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User2", + "login": "user2@client.example.org", + "email": "user2@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("fastapi.group_fastapi_user").id, + ], + }, + ) + + self.assertEqual(response.status_code, 200) + json = response.json() + self.assertEqual(json["client_id"], self.client.id) + self.assertTrue(json["token"]) + + self.assertEqual(len(rc.records), 1) + self.assertEqual(len(rc2.records), 0) + new_user = rc.records[0] + self.assertEqual(new_user.name, "Client User2") + self.assertEqual(new_user.login, "user2@client.example.org") + self.assertEqual(new_user.email, "user2@client.example.org") + self.assertEqual(new_user.lang, "en_US") + self.assertIn(self.env.ref("fastapi.group_fastapi_user"), new_user.groups_id) + self.assertNotIn( + self.env.ref("fastapi.group_fastapi_manager"), new_user.groups_id + ) + + def test_login_ok(self): + with RecordCapturer(self.env["res.users"], []) as rc: + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + self.assertEqual(response.status_code, 200) + + new_user = rc.records[0] + + json = response.json() + + with self._create_test_client() as test_client: + response = test_client.get( + f"/cross_connect/login/{json['client_id']}/{json['token']}", + follow_redirects=False, + ) + + self.assertEqual(response.status_code, 307) + self.assertEqual(response.headers["location"], "/web") + self.assertIn("session_id", response.cookies) + self.assertEqual( + root.session_store.get(response.cookies["session_id"]).get("uid"), + new_user.id, + ) + + def test_login_wrong_client(self): + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + self.assertEqual(response.status_code, 200) + + json = response.json() + + with self._create_test_client(raise_server_exceptions=False) as test_client: + response = test_client.get( + f"/cross_connect/login/{self.other_client.id}/{json['token']}", + follow_redirects=False, + ) + + self.assertEqual(response.status_code, 403) + + def test_login_wrong_token(self): + with self._create_test_client() as test_client: + response = test_client.post( + "/cross_connect/access", + headers={"api-key": "server-api-key"}, + json={ + "id": 12, + "name": "Client User", + "login": "user@client.example.org", + "email": "user@client.example.org", + "lang": "en_US", + "groups": [ + self.env.ref("base.group_user").id, + ], + }, + ) + self.assertEqual(response.status_code, 200) + + json = response.json() + + with self._create_test_client(raise_server_exceptions=False) as test_client: + response = test_client.get( + f"/cross_connect/login/{json['client_id']}/wrong-token", + follow_redirects=False, + ) + + self.assertEqual(response.status_code, 403) diff --git a/cross_connect_server/views/fastapi_endpoint_views.xml b/cross_connect_server/views/fastapi_endpoint_views.xml new file mode 100644 index 0000000000..41f2c4660d --- /dev/null +++ b/cross_connect_server/views/fastapi_endpoint_views.xml @@ -0,0 +1,46 @@ + + + + + fastapi.endpoint + + + + + {'invisible': [('app', '==', 'cross_connect')]} + + + + + + + + + + + + + + + + + + + From 7896b009ca2cb21c4258c5211ad99d318d43b9b7 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 3 Apr 2025 17:06:08 +0200 Subject: [PATCH 02/18] [IMP] Ensure login unicity for users coming from cross server client --- cross_connect_server/models/cross_connect_client.py | 2 +- cross_connect_server/tests/test_cross_connect_server.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index 04f5f0ed9a..f60f665335 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -73,7 +73,7 @@ def _request_access(self, access_request): lambda u: u.cross_connect_client_user_id == access_request.id ) vals = { - "login": access_request.login, + "login": f"{self.id}_{access_request.id}_{access_request.login}", "email": access_request.email, "name": access_request.name, "lang": access_request.lang, diff --git a/cross_connect_server/tests/test_cross_connect_server.py b/cross_connect_server/tests/test_cross_connect_server.py index 9006cf61a8..371ffefcd0 100644 --- a/cross_connect_server/tests/test_cross_connect_server.py +++ b/cross_connect_server/tests/test_cross_connect_server.py @@ -170,7 +170,7 @@ def test_access_ok(self): self.assertEqual(len(rc.records), 1) new_user = rc.records[0] self.assertEqual(new_user.name, "Client User") - self.assertEqual(new_user.login, "user@client.example.org") + self.assertEqual(new_user.login, f"{self.client.id}_12_user@client.example.org") self.assertEqual(new_user.email, "user@client.example.org") self.assertEqual(new_user.lang, "en_US") self.assertEqual(new_user.cross_connect_client_id.id, self.client.id) @@ -271,7 +271,9 @@ def test_access_existing(self): self.assertEqual(len(rc2.records), 0) new_user = rc.records[0] self.assertEqual(new_user.name, "Client User2") - self.assertEqual(new_user.login, "user2@client.example.org") + self.assertEqual( + new_user.login, f"{self.client.id}_12_user2@client.example.org" + ) self.assertEqual(new_user.email, "user2@client.example.org") self.assertEqual(new_user.lang, "en_US") self.assertIn(self.env.ref("fastapi.group_fastapi_user"), new_user.groups_id) From 8cb8ceb1030185fb6e2b321addeb2dcb169c3608 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 3 Apr 2025 16:21:53 +0000 Subject: [PATCH 03/18] [UPD] Update cross_connect_server.pot --- .../i18n/cross_connect_server.pot | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 cross_connect_server/i18n/cross_connect_server.pot diff --git a/cross_connect_server/i18n/cross_connect_server.pot b/cross_connect_server/i18n/cross_connect_server.pot new file mode 100644 index 0000000000..534c52f471 --- /dev/null +++ b/cross_connect_server/i18n/cross_connect_server.pot @@ -0,0 +1,209 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cross_connect_server +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__api_key +msgid "API Key" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__app +msgid "App" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/routers/cross_connect.py:0 +#, python-format +msgid "Client not found" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_uid +msgid "Created by" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_date +msgid "Created on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__allowed_group_ids +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids +msgid "Cross Connect Allowed Groups" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_cross_connect_client +#: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_id +msgid "Cross Connect Client" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_user_id +msgid "Cross Connect Client User ID" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids +msgid "Cross Connect Clients" +msgstr "" + +#. module: cross_connect_server +#: model_terms:ir.ui.view,arch_db:cross_connect_server.fastapi_endpoint_form_view +msgid "Cross Connect Configuration" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields.selection,name:cross_connect_server.selection__fastapi_endpoint__app__cross_connect +msgid "Cross Connect Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:res.groups,name:cross_connect_server.group_cross_connect_manager +msgid "Cross Connect Manager" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key +msgid "Cross Connect Secret Key" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_count +msgid "Cross Connected User Count" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__display_name +msgid "Display Name" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__endpoint_id +msgid "Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_fastapi_endpoint +msgid "FastAPI Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__group_ids +msgid "Groups" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__id +msgid "ID" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#, python-format +msgid "Invalid Token" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client____last_update +msgid "Last Modified on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_date +msgid "Last Updated on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__name +msgid "Name" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__server_env_defaults +msgid "Server Env Defaults" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__api_key +msgid "The API key to give to configure on the client." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids +msgid "The clients that can access this endpoint." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_id +msgid "The cross connect client that created this user." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__allowed_group_ids +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids +msgid "The groups that can access the cross connect clients of this endpoint." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__group_ids +msgid "The groups that this client belongs to." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_count +msgid "The number of users created by this cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key +msgid "The secret key used for cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_user_id +msgid "The user ID on the cross connect client." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_ids +msgid "The users created by this cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_res_users +msgid "User" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_ids +msgid "Users" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#, python-format +msgid "You are not allowed to access this endpoint." +msgstr "" From 61ae70263cc8c7330e1b3fb7da527143d32195b2 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 3 Apr 2025 16:26:25 +0000 Subject: [PATCH 04/18] [BOT] post-merge updates --- cross_connect_server/README.rst | 2 +- .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 cross_connect_server/static/description/icon.png diff --git a/cross_connect_server/README.rst b/cross_connect_server/README.rst index c274ff7a11..3190888c7e 100644 --- a/cross_connect_server/README.rst +++ b/cross_connect_server/README.rst @@ -7,7 +7,7 @@ Cross Connect Server !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:e7f2983ebb91caf2611da85b500923b3a91de86fbb4577c967a2a30e0ce7e739 + !! source digest: sha256:9b79c5d9a0171f9fec42f09049c6d1cbb817a7fd4337a229574e06473cbe74c2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/cross_connect_server/static/description/icon.png b/cross_connect_server/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 Connect Server !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:e7f2983ebb91caf2611da85b500923b3a91de86fbb4577c967a2a30e0ce7e739 +!! source digest: sha256:9b79c5d9a0171f9fec42f09049c6d1cbb817a7fd4337a229574e06473cbe74c2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module allows other odoo instances, where the From b3829c4548e097f46842f6a56eb46c2a458f45d9 Mon Sep 17 00:00:00 2001 From: mymage Date: Sat, 5 Apr 2025 09:09:40 +0000 Subject: [PATCH 05/18] Added translation using Weblate (Italian) --- cross_connect_server/i18n/it.po | 210 ++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 cross_connect_server/i18n/it.po diff --git a/cross_connect_server/i18n/it.po b/cross_connect_server/i18n/it.po new file mode 100644 index 0000000000..103f666893 --- /dev/null +++ b/cross_connect_server/i18n/it.po @@ -0,0 +1,210 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cross_connect_server +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__api_key +msgid "API Key" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__app +msgid "App" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/routers/cross_connect.py:0 +#, python-format +msgid "Client not found" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_uid +msgid "Created by" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_date +msgid "Created on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__allowed_group_ids +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids +msgid "Cross Connect Allowed Groups" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_cross_connect_client +#: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_id +msgid "Cross Connect Client" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_user_id +msgid "Cross Connect Client User ID" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids +msgid "Cross Connect Clients" +msgstr "" + +#. module: cross_connect_server +#: model_terms:ir.ui.view,arch_db:cross_connect_server.fastapi_endpoint_form_view +msgid "Cross Connect Configuration" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields.selection,name:cross_connect_server.selection__fastapi_endpoint__app__cross_connect +msgid "Cross Connect Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:res.groups,name:cross_connect_server.group_cross_connect_manager +msgid "Cross Connect Manager" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key +msgid "Cross Connect Secret Key" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_count +msgid "Cross Connected User Count" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__display_name +msgid "Display Name" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__endpoint_id +msgid "Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_fastapi_endpoint +msgid "FastAPI Endpoint" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__group_ids +msgid "Groups" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__id +msgid "ID" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#, python-format +msgid "Invalid Token" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client____last_update +msgid "Last Modified on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_date +msgid "Last Updated on" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__name +msgid "Name" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__server_env_defaults +msgid "Server Env Defaults" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__api_key +msgid "The API key to give to configure on the client." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids +msgid "The clients that can access this endpoint." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_id +msgid "The cross connect client that created this user." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__allowed_group_ids +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids +msgid "The groups that can access the cross connect clients of this endpoint." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__group_ids +msgid "The groups that this client belongs to." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_count +msgid "The number of users created by this cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key +msgid "The secret key used for cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_user_id +msgid "The user ID on the cross connect client." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_ids +msgid "The users created by this cross connection." +msgstr "" + +#. module: cross_connect_server +#: model:ir.model,name:cross_connect_server.model_res_users +msgid "User" +msgstr "" + +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_ids +msgid "Users" +msgstr "" + +#. module: cross_connect_server +#. odoo-python +#: code:addons/cross_connect_server/models/cross_connect_client.py:0 +#, python-format +msgid "You are not allowed to access this endpoint." +msgstr "" From 0fbf0c0d1aa557d2a6ab144d1feb12d113c4fb44 Mon Sep 17 00:00:00 2001 From: mymage Date: Fri, 16 May 2025 06:21:20 +0000 Subject: [PATCH 06/18] Translated using Weblate (Italian) Currently translated at 100.0% (37 of 37 strings) Translation: server-auth-16.0/server-auth-16.0-cross_connect_server Translate-URL: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-cross_connect_server/it/ --- cross_connect_server/i18n/it.po | 77 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/cross_connect_server/i18n/it.po b/cross_connect_server/i18n/it.po index 103f666893..ff1e43c43b 100644 --- a/cross_connect_server/i18n/it.po +++ b/cross_connect_server/i18n/it.po @@ -6,112 +6,114 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2025-05-16 08:24+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__api_key msgid "API Key" -msgstr "" +msgstr "Chiave API" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__app msgid "App" -msgstr "" +msgstr "Applicazione" #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/routers/cross_connect.py:0 #, python-format msgid "Client not found" -msgstr "" +msgstr "Client non trovato" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_uid msgid "Created by" -msgstr "" +msgstr "Creato da" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__create_date msgid "Created on" -msgstr "" +msgstr "Creato il" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__allowed_group_ids #: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids msgid "Cross Connect Allowed Groups" -msgstr "" +msgstr "Gruppi abilitati cross-connect" #. module: cross_connect_server #: model:ir.model,name:cross_connect_server.model_cross_connect_client #: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_id msgid "Cross Connect Client" -msgstr "" +msgstr "Client cross-connect" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_res_users__cross_connect_client_user_id msgid "Cross Connect Client User ID" -msgstr "" +msgstr "ID utente cliente cross-connect" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids msgid "Cross Connect Clients" -msgstr "" +msgstr "Client cross-connect" #. module: cross_connect_server #: model_terms:ir.ui.view,arch_db:cross_connect_server.fastapi_endpoint_form_view msgid "Cross Connect Configuration" -msgstr "" +msgstr "Configurazione cross-connect" #. module: cross_connect_server #: model:ir.model.fields.selection,name:cross_connect_server.selection__fastapi_endpoint__app__cross_connect msgid "Cross Connect Endpoint" -msgstr "" +msgstr "Endopoint cross-connect" #. module: cross_connect_server #: model:res.groups,name:cross_connect_server.group_cross_connect_manager msgid "Cross Connect Manager" -msgstr "" +msgstr "Gestore cross-connect" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key msgid "Cross Connect Secret Key" -msgstr "" +msgstr "Chiave segreta cross-connect" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_count msgid "Cross Connected User Count" -msgstr "" +msgstr "Conteggio utenti cross-connect" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__display_name msgid "Display Name" -msgstr "" +msgstr "Nome visualizzato" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__endpoint_id msgid "Endpoint" -msgstr "" +msgstr "Endpoint" #. module: cross_connect_server #: model:ir.model,name:cross_connect_server.model_fastapi_endpoint msgid "FastAPI Endpoint" -msgstr "" +msgstr "Endopoint FastAPI" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__group_ids msgid "Groups" -msgstr "" +msgstr "Gruppi" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__id msgid "ID" -msgstr "" +msgstr "ID" #. module: cross_connect_server #. odoo-python @@ -119,92 +121,93 @@ msgstr "" #: code:addons/cross_connect_server/models/cross_connect_client.py:0 #, python-format msgid "Invalid Token" -msgstr "" +msgstr "Token non valido" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client____last_update msgid "Last Modified on" -msgstr "" +msgstr "Ultima modifica il" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_uid msgid "Last Updated by" -msgstr "" +msgstr "Ultimo aggiornamento di" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__write_date msgid "Last Updated on" -msgstr "" +msgstr "Ultimo aggiornamento il" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__name msgid "Name" -msgstr "" +msgstr "Nome" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__server_env_defaults msgid "Server Env Defaults" -msgstr "" +msgstr "Predefiniti ambiente server" #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__api_key msgid "The API key to give to configure on the client." -msgstr "" +msgstr "Chiave API da fornire per configurare il client." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_client_ids msgid "The clients that can access this endpoint." -msgstr "" +msgstr "I client che possono accedere questo endpoint." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_id msgid "The cross connect client that created this user." -msgstr "" +msgstr "Il client cross-connect che ha creato questo utente." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__allowed_group_ids #: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_allowed_group_ids msgid "The groups that can access the cross connect clients of this endpoint." msgstr "" +"I gruppi che possono accedere i client cross-connect di questo endpoint." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__group_ids msgid "The groups that this client belongs to." -msgstr "" +msgstr "I gruppi a cui appartiene questo client." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_count msgid "The number of users created by this cross connection." -msgstr "" +msgstr "Il numero di utenti creati da questa cross-connect." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_fastapi_endpoint__cross_connect_secret_key msgid "The secret key used for cross connection." -msgstr "" +msgstr "La chiave segreta usata per la cross-connect." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_res_users__cross_connect_client_user_id msgid "The user ID on the cross connect client." -msgstr "" +msgstr "L'ID utente nel client cross-connect." #. module: cross_connect_server #: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__user_ids msgid "The users created by this cross connection." -msgstr "" +msgstr "Gli utenti creati da questa cross-connect." #. module: cross_connect_server #: model:ir.model,name:cross_connect_server.model_res_users msgid "User" -msgstr "" +msgstr "Utente" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__user_ids msgid "Users" -msgstr "" +msgstr "Utenti" #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/models/cross_connect_client.py:0 #, python-format msgid "You are not allowed to access this endpoint." -msgstr "" +msgstr "Non si è autorizzati ad accedere a questo endpoint." From f62a713380078dd2f6756cd322e7c894cef9d909 Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 20 May 2025 07:47:39 +0000 Subject: [PATCH 07/18] Translated using Weblate (Italian) Currently translated at 100.0% (37 of 37 strings) Translation: server-auth-16.0/server-auth-16.0-cross_connect_server Translate-URL: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-cross_connect_server/it/ --- cross_connect_server/i18n/it.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cross_connect_server/i18n/it.po b/cross_connect_server/i18n/it.po index ff1e43c43b..782a78918f 100644 --- a/cross_connect_server/i18n/it.po +++ b/cross_connect_server/i18n/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2025-05-16 08:24+0000\n" +"PO-Revision-Date: 2025-05-20 10:26+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -103,7 +103,7 @@ msgstr "Endpoint" #. module: cross_connect_server #: model:ir.model,name:cross_connect_server.model_fastapi_endpoint msgid "FastAPI Endpoint" -msgstr "Endopoint FastAPI" +msgstr "Endpoint FastAPI" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__group_ids From ce3ed6874783c00f4f0c64b108752060f6a051fd Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 3 Jun 2025 12:20:37 +0200 Subject: [PATCH 08/18] [FIX] No invitation email for cross server created user These users are not meant to be able to log in the normal way, it is always a redirection from another Odoo with automatic login --- cross_connect_server/models/cross_connect_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index f60f665335..42a615a0b6 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -83,7 +83,9 @@ def _request_access(self, access_request): } # Create user if not exists if not user: - user = self.env["res.users"].create(vals) + user = ( + self.env["res.users"].with_context(no_reset_password=True).create(vals) + ) else: user.write(vals) From 9c9e56d950e03a56023a39a9c2e547a65b26bd5a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 3 Jun 2025 15:16:40 +0000 Subject: [PATCH 09/18] [BOT] post-merge updates --- cross_connect_server/README.rst | 8 ++++-- cross_connect_server/__manifest__.py | 2 +- .../static/description/index.html | 28 +++++++++++-------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/cross_connect_server/README.rst b/cross_connect_server/README.rst index 3190888c7e..b2a2e6989a 100644 --- a/cross_connect_server/README.rst +++ b/cross_connect_server/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ==================== Cross Connect Server ==================== @@ -7,13 +11,13 @@ Cross Connect Server !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:9b79c5d9a0171f9fec42f09049c6d1cbb817a7fd4337a229574e06473cbe74c2 + !! source digest: sha256:8008e84d0b38e668c79989062dede0d43276fbcbaa120effb1097b09f31e2968 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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 diff --git a/cross_connect_server/__manifest__.py b/cross_connect_server/__manifest__.py index 57e066de65..08ad498925 100644 --- a/cross_connect_server/__manifest__.py +++ b/cross_connect_server/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Cross Connect Server", - "version": "16.0.1.0.0", + "version": "16.0.1.0.1", "author": "Akretion, Odoo Community Association (OCA)", "summary": "Cross Connect Server allows Cross Connect Client to connect to it.", "category": "Tools", diff --git a/cross_connect_server/static/description/index.html b/cross_connect_server/static/description/index.html index 224be3a2b0..aab9c5fa50 100644 --- a/cross_connect_server/static/description/index.html +++ b/cross_connect_server/static/description/index.html @@ -3,7 +3,7 @@ -Cross Connect Server +README.rst -

-

Cross Connect Server

+
+ + +Odoo Community Association + +
+

Cross Connect Server

-

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module allows other odoo instances, where the cross_connect_client module is installed and configured, users to connect directly on this odoo instance.

@@ -387,7 +392,7 @@

Cross Connect Server

-

Usage

+

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 @@ -407,7 +412,7 @@

Usage

client will be able to give to its users.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub 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 @@ -415,21 +420,21 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

+
From 4a37823a7f753840a8dde9514fc0c64137db1201 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 9 Dec 2025 10:30:05 +0100 Subject: [PATCH 10/18] [FIX] cross_connect_server: Default to en_US when user lang is not available --- cross_connect_server/models/cross_connect_client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index 42a615a0b6..121158ad57 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -72,6 +72,13 @@ def _request_access(self, access_request): user = self.user_ids.filtered( lambda u: u.cross_connect_client_user_id == access_request.id ) + + # Fallback to default lang if not installed + if access_request.lang not in [ + code for code, _name in self.env["res.lang"].get_installed() + ]: + access_request.lang = "en_US" + vals = { "login": f"{self.id}_{access_request.id}_{access_request.login}", "email": access_request.email, From f5e7a9a3e3371e0bb425539edacb2fddc0c7c1b4 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 9 Dec 2025 12:03:48 +0100 Subject: [PATCH 11/18] [IMP] cross_connect_server: Work with params and not server redirect_url --- cross_connect_server/models/cross_connect_client.py | 9 +++++++-- cross_connect_server/routers/cross_connect.py | 10 +++++++--- cross_connect_server/schemas.py | 1 - 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index 121158ad57..1a79cc8ed8 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -101,7 +101,6 @@ def _request_access(self, access_request): "exp": datetime.now(tz=timezone.utc) + timedelta(minutes=2), "aud": str(self.id), "id": user.id, - "redirect_url": access_request.redirect_url or "/web", }, self.endpoint_id.cross_connect_secret_key, algorithm="HS256", @@ -124,4 +123,10 @@ def _log_from_token(self, token): if not user: raise AccessDenied(_("Invalid Token")) - return user, obj["redirect_url"] + return user + + def _get_final_redirect_url(self, **params): + """Get the final redirect url after login. + Override this method to customize the local landing action. + """ + return "/web" diff --git a/cross_connect_server/routers/cross_connect.py b/cross_connect_server/routers/cross_connect.py index 94fe4ee83c..f3dfe1992a 100644 --- a/cross_connect_server/routers/cross_connect.py +++ b/cross_connect_server/routers/cross_connect.py @@ -4,7 +4,7 @@ from typing import Annotated -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Request from fastapi.responses import RedirectResponse from odoo import _, api @@ -49,12 +49,14 @@ async def login( client_id: int, token: str, env: Annotated[api.Environment, Depends(odoo_env)], + request: Request, ) -> RedirectResponse: """Log user and redirect to odoo index.""" cross_connect_client = env["cross.connect.client"].sudo().browse(client_id) if not cross_connect_client: raise MissingError(_("Client not found")) - user, redirect_url = cross_connect_client.sudo()._log_from_token(token) + params = request.query_params + user = cross_connect_client.sudo()._log_from_token(token) user = user.with_user(user) user._update_last_login() env = env(user=user.id) @@ -68,7 +70,9 @@ async def login( session.session_token = user._compute_session_token(session.sid) root.session_store.save(session) # Redirect after login - response = RedirectResponse(url=redirect_url) + response = RedirectResponse( + url=cross_connect_client._get_final_redirect_url(**params) + ) response.set_cookie( "session_id", session.sid, diff --git a/cross_connect_server/schemas.py b/cross_connect_server/schemas.py index 04e59628f7..b1295e3729 100644 --- a/cross_connect_server/schemas.py +++ b/cross_connect_server/schemas.py @@ -35,7 +35,6 @@ class AccessRequest(StrictExtendableBaseModel, extra="ignore"): email: str lang: str groups: list[int] - redirect_url: str = None class AccessResponse(StrictExtendableBaseModel): From b89a8375c910bfab95aac3e85a83d09003243ebc Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 9 Dec 2025 12:04:29 +0100 Subject: [PATCH 12/18] [IMP] cross_connect_server: Add bypass login users by re matching --- .../models/cross_connect_client.py | 15 +++++++++++++++ cross_connect_server/routers/cross_connect.py | 5 +++++ .../views/fastapi_endpoint_views.xml | 1 + 3 files changed, 21 insertions(+) diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index 1a79cc8ed8..739aad94f3 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -1,6 +1,7 @@ # Copyright 2024 Akretion (http://www.akretion.com). # @author Florian Mounier # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re from datetime import datetime, timedelta, timezone from secrets import token_urlsafe @@ -34,6 +35,14 @@ class CrossConnectClient(models.Model): related="endpoint_id.cross_connect_allowed_group_ids", ) + bypass_user_mail_re = fields.Char( + string="Bypass Users Email Regexes", + help=( + "If set, users with an email matching one of these regex will bypass " + "the token user/login creation. The regexes are comma separated." + ), + ) + group_ids = fields.Many2many( "res.groups", string="Groups", @@ -64,6 +73,12 @@ def _compute_user_count(self): record.user_count = len(record.user_ids) def _request_access(self, access_request): + if self.bypass_user_mail_re and any( + re.search(mail_re.strip(), access_request.email) + for mail_re in self.bypass_user_mail_re.split(",") + ): + return "bypass" + # check groups groups = self.env["res.groups"].browse(access_request.groups) if groups - self.group_ids or not groups.exists(): diff --git a/cross_connect_server/routers/cross_connect.py b/cross_connect_server/routers/cross_connect.py index f3dfe1992a..0341c9d681 100644 --- a/cross_connect_server/routers/cross_connect.py +++ b/cross_connect_server/routers/cross_connect.py @@ -56,6 +56,11 @@ async def login( if not cross_connect_client: raise MissingError(_("Client not found")) params = request.query_params + if token == "bypass": + return RedirectResponse( + url=cross_connect_client._get_final_redirect_url(bypass=True, **params) + ) + user = cross_connect_client.sudo()._log_from_token(token) user = user.with_user(user) user._update_last_login() diff --git a/cross_connect_server/views/fastapi_endpoint_views.xml b/cross_connect_server/views/fastapi_endpoint_views.xml index 41f2c4660d..06557c82ba 100644 --- a/cross_connect_server/views/fastapi_endpoint_views.xml +++ b/cross_connect_server/views/fastapi_endpoint_views.xml @@ -36,6 +36,7 @@ widget="many2many_tags" options="{'no_create': True}" /> + From 744b7fa814cb35cc8ba2eebc68a9ef3af7f2894c Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 10 Feb 2026 12:57:32 +0000 Subject: [PATCH 13/18] [UPD] Update cross_connect_server.pot --- cross_connect_server/i18n/cross_connect_server.pot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cross_connect_server/i18n/cross_connect_server.pot b/cross_connect_server/i18n/cross_connect_server.pot index 534c52f471..ae6fe29da5 100644 --- a/cross_connect_server/i18n/cross_connect_server.pot +++ b/cross_connect_server/i18n/cross_connect_server.pot @@ -23,6 +23,11 @@ msgstr "" msgid "App" msgstr "" +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__bypass_user_mail_re +msgid "Bypass Users Email Regexes" +msgstr "" + #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/routers/cross_connect.py:0 @@ -112,6 +117,13 @@ msgstr "" msgid "ID" msgstr "" +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__bypass_user_mail_re +msgid "" +"If set, users with an email matching one of these regex will bypass the " +"token user/login creation. The regexes are comma separated." +msgstr "" + #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/models/cross_connect_client.py:0 From f1095784e06d811b0721aca73ec748e1c381190b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 10 Feb 2026 13:01:55 +0000 Subject: [PATCH 14/18] [BOT] post-merge updates --- cross_connect_server/README.rst | 2 +- cross_connect_server/__manifest__.py | 2 +- cross_connect_server/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cross_connect_server/README.rst b/cross_connect_server/README.rst index b2a2e6989a..7203a48753 100644 --- a/cross_connect_server/README.rst +++ b/cross_connect_server/README.rst @@ -11,7 +11,7 @@ Cross Connect Server !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:8008e84d0b38e668c79989062dede0d43276fbcbaa120effb1097b09f31e2968 + !! source digest: sha256:03b7d29200b2b3b6c3cd0ea4077f895238659de2a6b18a3c644d1761129897de !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/cross_connect_server/__manifest__.py b/cross_connect_server/__manifest__.py index 08ad498925..668396ec0b 100644 --- a/cross_connect_server/__manifest__.py +++ b/cross_connect_server/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Cross Connect Server", - "version": "16.0.1.0.1", + "version": "16.0.1.1.0", "author": "Akretion, Odoo Community Association (OCA)", "summary": "Cross Connect Server allows Cross Connect Client to connect to it.", "category": "Tools", diff --git a/cross_connect_server/static/description/index.html b/cross_connect_server/static/description/index.html index aab9c5fa50..058a5e97d9 100644 --- a/cross_connect_server/static/description/index.html +++ b/cross_connect_server/static/description/index.html @@ -372,7 +372,7 @@

Cross Connect Server

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:8008e84d0b38e668c79989062dede0d43276fbcbaa120effb1097b09f31e2968 +!! source digest: sha256:03b7d29200b2b3b6c3cd0ea4077f895238659de2a6b18a3c644d1761129897de !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module allows other odoo instances, where the From 4ba358e9bee8c87d4c92bd9c5a5b99ec782f8795 Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 10 Feb 2026 13:02:07 +0000 Subject: [PATCH 15/18] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: server-auth-16.0/server-auth-16.0-cross_connect_server Translate-URL: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-cross_connect_server/ --- cross_connect_server/i18n/it.po | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cross_connect_server/i18n/it.po b/cross_connect_server/i18n/it.po index 782a78918f..375eccf83a 100644 --- a/cross_connect_server/i18n/it.po +++ b/cross_connect_server/i18n/it.po @@ -26,6 +26,11 @@ msgstr "Chiave API" msgid "App" msgstr "Applicazione" +#. module: cross_connect_server +#: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__bypass_user_mail_re +msgid "Bypass Users Email Regexes" +msgstr "" + #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/routers/cross_connect.py:0 @@ -115,10 +120,16 @@ msgstr "Gruppi" msgid "ID" msgstr "ID" +#. module: cross_connect_server +#: model:ir.model.fields,help:cross_connect_server.field_cross_connect_client__bypass_user_mail_re +msgid "" +"If set, users with an email matching one of these regex will bypass the " +"token user/login creation. The regexes are comma separated." +msgstr "" + #. module: cross_connect_server #. odoo-python #: code:addons/cross_connect_server/models/cross_connect_client.py:0 -#: code:addons/cross_connect_server/models/cross_connect_client.py:0 #, python-format msgid "Invalid Token" msgstr "Token non valido" From 134a7a648e22f87a4861f14b3bc5dbf7e4169f8c Mon Sep 17 00:00:00 2001 From: mymage Date: Wed, 11 Feb 2026 08:04:22 +0000 Subject: [PATCH 16/18] Translated using Weblate (Italian) Currently translated at 100.0% (39 of 39 strings) Translation: server-auth-16.0/server-auth-16.0-cross_connect_server Translate-URL: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-cross_connect_server/it/ --- cross_connect_server/i18n/it.po | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cross_connect_server/i18n/it.po b/cross_connect_server/i18n/it.po index 375eccf83a..070e3f76f8 100644 --- a/cross_connect_server/i18n/it.po +++ b/cross_connect_server/i18n/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2025-05-20 10:26+0000\n" +"PO-Revision-Date: 2026-02-11 09:31+0000\n" "Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" @@ -14,7 +14,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.10.4\n" +"X-Generator: Weblate 5.15.2\n" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__api_key @@ -29,7 +29,7 @@ msgstr "Applicazione" #. module: cross_connect_server #: model:ir.model.fields,field_description:cross_connect_server.field_cross_connect_client__bypass_user_mail_re msgid "Bypass Users Email Regexes" -msgstr "" +msgstr "Baypassa espressione regolare e-mail utenti" #. module: cross_connect_server #. odoo-python @@ -126,6 +126,9 @@ msgid "" "If set, users with an email matching one of these regex will bypass the " "token user/login creation. The regexes are comma separated." msgstr "" +"Se impostato, gli utenti con un indirizzo e-mail corrispondente a una di " +"queste espressioni regolari ignoreranno la creazione del token utente/" +"accesso. Le espressioni regolari sono separate da virgole." #. module: cross_connect_server #. odoo-python From 024137ff3dc17c003f8796f24596dab34e980244 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 16 Mar 2026 18:07:02 +0100 Subject: [PATCH 17/18] [IMP] cross_connect_server: pre-commit auto fixes --- cross_connect_server/models/fastapi_endpoint.py | 13 +++++++------ cross_connect_server/pyproject.toml | 3 +++ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 cross_connect_server/pyproject.toml diff --git a/cross_connect_server/models/fastapi_endpoint.py b/cross_connect_server/models/fastapi_endpoint.py index 774e485346..384afbe8c6 100644 --- a/cross_connect_server/models/fastapi_endpoint.py +++ b/cross_connect_server/models/fastapi_endpoint.py @@ -1,8 +1,9 @@ # Copyright 2024 Akretion (http://www.akretion.com). # @author Florian Mounier # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections.abc import Callable from secrets import token_urlsafe -from typing import Annotated, Callable, Dict, List +from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import APIKeyHeader @@ -47,7 +48,7 @@ def _generate_secret_key(self): # generate random ~64 chars secret key return token_urlsafe(64) - def _get_fastapi_routers(self) -> List[APIRouter]: + def _get_fastapi_routers(self) -> list[APIRouter]: routers = super()._get_fastapi_routers() if self.app == "cross_connect": @@ -55,13 +56,13 @@ def _get_fastapi_routers(self) -> List[APIRouter]: return routers - def _get_app_dependencies_overrides(self) -> Dict[Callable, Callable]: + 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 + overrides[authenticated_cross_connect_client] = ( + api_key_based_authenticated_cross_connect_client + ) return overrides diff --git a/cross_connect_server/pyproject.toml b/cross_connect_server/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/cross_connect_server/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" From 90ae1491f8a5f666bb8ed4282dd419c31c7742c0 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Wed, 18 Mar 2026 16:22:33 +0100 Subject: [PATCH 18/18] [MIG] cross_connect_server: Migration to 18.0 --- cross_connect_server/README.rst | 16 ++++------ cross_connect_server/__manifest__.py | 2 +- .../models/cross_connect_client.py | 10 ++++--- .../models/fastapi_endpoint.py | 2 +- cross_connect_server/routers/cross_connect.py | 9 +++--- .../static/description/index.html | 30 ++++++++----------- .../views/fastapi_endpoint_views.xml | 10 +++---- test-requirements.txt | 1 + 8 files changed, 35 insertions(+), 45 deletions(-) diff --git a/cross_connect_server/README.rst b/cross_connect_server/README.rst index 7203a48753..4dea6cac7d 100644 --- a/cross_connect_server/README.rst +++ b/cross_connect_server/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ==================== Cross Connect Server ==================== @@ -17,17 +13,17 @@ Cross Connect Server .. |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/license-AGPL--3-blue.png +.. |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 + :target: https://github.com/OCA/server-auth/tree/18.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 + :target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-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 + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -71,7 +67,7 @@ Bug Tracker Bugs are tracked on `GitHub 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -109,6 +105,6 @@ Current `maintainer `__: |maintainer-paradoxxxzero| -This module is part of the `OCA/server-auth `_ project on GitHub. +This module is part of the `OCA/server-auth `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/cross_connect_server/__manifest__.py b/cross_connect_server/__manifest__.py index 668396ec0b..cbdb5b22e3 100644 --- a/cross_connect_server/__manifest__.py +++ b/cross_connect_server/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Cross Connect Server", - "version": "16.0.1.1.0", + "version": "18.0.1.0.0", "author": "Akretion, Odoo Community Association (OCA)", "summary": "Cross Connect Server allows Cross Connect Client to connect to it.", "category": "Tools", diff --git a/cross_connect_server/models/cross_connect_client.py b/cross_connect_server/models/cross_connect_client.py index 739aad94f3..23637bec37 100644 --- a/cross_connect_server/models/cross_connect_client.py +++ b/cross_connect_server/models/cross_connect_client.py @@ -7,7 +7,7 @@ import jwt -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.exceptions import AccessDenied @@ -82,7 +82,9 @@ 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.")) + raise AccessDenied( + self.env._("You are not allowed to access this endpoint.") + ) user = self.user_ids.filtered( lambda u: u.cross_connect_client_user_id == access_request.id @@ -131,12 +133,12 @@ def _log_from_token(self, token): algorithms=["HS256"], ) except jwt.PyJWTError as e: - raise AccessDenied(_("Invalid Token")) from e + raise AccessDenied(self.env._("Invalid Token")) from e user = self.env["res.users"].browse(obj["id"]) if not user: - raise AccessDenied(_("Invalid Token")) + raise AccessDenied(self.env._("Invalid Token")) return user diff --git a/cross_connect_server/models/fastapi_endpoint.py b/cross_connect_server/models/fastapi_endpoint.py index 384afbe8c6..2f212a36bb 100644 --- a/cross_connect_server/models/fastapi_endpoint.py +++ b/cross_connect_server/models/fastapi_endpoint.py @@ -67,7 +67,7 @@ def _get_app_dependencies_overrides(self) -> dict[Callable, Callable]: return overrides def _get_routing_info(self): - if self.app == "cross_connect": + if self.app == "cross_connect" and self.save_http_session: # Force to not save the HTTP session for the login to work correctly self.save_http_session = False return super()._get_routing_info() diff --git a/cross_connect_server/routers/cross_connect.py b/cross_connect_server/routers/cross_connect.py index 0341c9d681..6d4be7abfd 100644 --- a/cross_connect_server/routers/cross_connect.py +++ b/cross_connect_server/routers/cross_connect.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, Request from fastapi.responses import RedirectResponse -from odoo import _, api +from odoo import api from odoo.exceptions import MissingError from odoo.http import SESSION_LIFETIME, root @@ -54,7 +54,7 @@ async def login( """Log user and redirect to odoo index.""" cross_connect_client = env["cross.connect.client"].sudo().browse(client_id) if not cross_connect_client: - raise MissingError(_("Client not found")) + raise MissingError(env._("Client not found")) params = request.query_params if token == "bypass": return RedirectResponse( @@ -73,11 +73,10 @@ async def login( session.login = user.login session.context = dict(env["res.users"].context_get()) session.session_token = user._compute_session_token(session.sid) + url = cross_connect_client._get_final_redirect_url(session=session, **params) root.session_store.save(session) # Redirect after login - response = RedirectResponse( - url=cross_connect_client._get_final_redirect_url(**params) - ) + response = RedirectResponse(url=url) response.set_cookie( "session_id", session.sid, diff --git a/cross_connect_server/static/description/index.html b/cross_connect_server/static/description/index.html index 058a5e97d9..9d297c1433 100644 --- a/cross_connect_server/static/description/index.html +++ b/cross_connect_server/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Cross Connect Server -

+
+

Cross Connect Server

- - -Odoo Community Association - -
-

Cross Connect Server

-

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

This module allows other odoo instances, where the cross_connect_client module is installed and configured, users to connect directly on this odoo instance.

@@ -392,7 +387,7 @@

Cross Connect Server

-

Usage

+

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 @@ -412,29 +407,29 @@

Usage

client will be able to give to its users.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub 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.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Akretion
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -444,11 +439,10 @@

Maintainers

promote its widespread use.

Current maintainer:

paradoxxxzero

-

This module is part of the OCA/server-auth project on GitHub.

+

This module is part of the OCA/server-auth project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

-
diff --git a/cross_connect_server/views/fastapi_endpoint_views.xml b/cross_connect_server/views/fastapi_endpoint_views.xml index 06557c82ba..4330e7aacb 100644 --- a/cross_connect_server/views/fastapi_endpoint_views.xml +++ b/cross_connect_server/views/fastapi_endpoint_views.xml @@ -10,15 +10,13 @@ - - {'invisible': [('app', '==', 'cross_connect')]} - + app == 'cross_connect' - + @@ -38,7 +36,7 @@ /> - + diff --git a/test-requirements.txt b/test-requirements.txt index 2cb24f43db..265b7f6b1e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ responses +httpx