From d1223a605e6b971806914ca15b7c84b9887ab0c3 Mon Sep 17 00:00:00 2001 From: ionmincu Date: Fri, 20 Mar 2026 10:33:54 +0200 Subject: [PATCH] fix(login): fix login on as version 25.10.2 --- packages/uipath-platform/pyproject.toml | 2 +- .../uipath/platform/orchestrator/__init__.py | 7 +- .../platform/orchestrator/_server_version.py | 24 +++++++ packages/uipath-platform/uv.lock | 2 +- packages/uipath/pyproject.toml | 4 +- .../src/uipath/_cli/_auth/_oidc_utils.py | 31 ++++---- .../_cli/_auth/auth_config_25_10_2.json | 6 ++ packages/uipath/tests/cli/test_oidc_utils.py | 71 ++++++++++++++----- packages/uipath/uv.lock | 4 +- 9 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 packages/uipath/src/uipath/_cli/_auth/auth_config_25_10_2.json diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 0d6d59836..524b09334 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.1" +version = "0.1.2" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/orchestrator/__init__.py b/packages/uipath-platform/src/uipath/platform/orchestrator/__init__.py index 02a51d3d9..79d6dde30 100644 --- a/packages/uipath-platform/src/uipath/platform/orchestrator/__init__.py +++ b/packages/uipath-platform/src/uipath/platform/orchestrator/__init__.py @@ -12,7 +12,11 @@ from ._orchestrator_setup_service import OrchestratorSetupService from ._processes_service import ProcessesService from ._queues_service import QueuesService -from ._server_version import get_server_version, get_server_version_async +from ._server_version import ( + get_server_info_async, + get_server_version, + get_server_version_async, +) from .assets import Asset, UserAsset from .attachment import Attachment from .buckets import Bucket, BucketFile @@ -37,6 +41,7 @@ "ProcessesService", "QueuesService", "OrchestratorSetupService", + "get_server_info_async", "get_server_version", "get_server_version_async", "Asset", diff --git a/packages/uipath-platform/src/uipath/platform/orchestrator/_server_version.py b/packages/uipath-platform/src/uipath/platform/orchestrator/_server_version.py index 2254b81da..4cd4f97d9 100644 --- a/packages/uipath-platform/src/uipath/platform/orchestrator/_server_version.py +++ b/packages/uipath-platform/src/uipath/platform/orchestrator/_server_version.py @@ -1,5 +1,7 @@ """Utility for retrieving the Orchestrator server version.""" +from typing import Any + import httpx from ..common._http_config import get_httpx_client_kwargs @@ -28,6 +30,28 @@ def get_server_version(domain: str) -> str | None: return None +async def get_server_info_async(domain: str) -> dict[Any, Any] | None: + """Get the full Orchestrator server status info. + + Args: + domain: The base URL of the UiPath platform (e.g., "https://cloud.uipath.com"). + + Returns: + The full server status JSON as a dict, or None if the request fails for any reason. + """ + url = f"{domain}/orchestrator_/api/status/version" + + try: + client_kwargs = get_httpx_client_kwargs() + client_kwargs["timeout"] = 5.0 + async with httpx.AsyncClient(**client_kwargs) as client: + response = await client.get(url) + response.raise_for_status() + return response.json() + except Exception: + return None + + async def get_server_version_async(domain: str) -> str | None: """Get the Orchestrator server version. diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index f04a7b4a3..944afad38 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1088,7 +1088,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.1" +version = "0.1.2" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 94aeee4f0..daef3366c 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath" -version = "2.10.22" +version = "2.10.23" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.2, <0.6.0", "uipath-runtime>=0.9.1, <0.10.0", - "uipath-platform>=0.1.1, <0.2.0", + "uipath-platform>=0.1.2, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", diff --git a/packages/uipath/src/uipath/_cli/_auth/_oidc_utils.py b/packages/uipath/src/uipath/_cli/_auth/_oidc_utils.py index cc97945ab..bb0be816a 100644 --- a/packages/uipath/src/uipath/_cli/_auth/_oidc_utils.py +++ b/packages/uipath/src/uipath/_cli/_auth/_oidc_utils.py @@ -4,7 +4,7 @@ import os from urllib.parse import urlencode, urlparse -from uipath.platform.orchestrator import get_server_version_async +from uipath.platform.orchestrator import get_server_info_async from .._utils._console import ConsoleLogger from ._models import AuthConfig @@ -50,10 +50,11 @@ async def _select_config_file(domain: str) -> str: Logic: 1. If domain is alpha/staging/cloud.uipath.com -> use auth_config_cloud.json - 2. Otherwise, try to get version from API - 3. If version starts with '25.10' -> use auth_config_25_10.json - 4. If version can't be determined -> fallback to auth_config_cloud.json - 5. Otherwise -> fallback to auth_config_cloud.json + 2. Otherwise, fetch server info from API + 3. If deployment is not 'ServiceFabric' (cloud deployment) -> use auth_config_cloud.json + 4. If version starts with '25.10' (AS release 25.10) -> use auth_config_25_10.json + 5. If version starts with '26.3' (AS release 25.10.2) -> use auth_config_25_10_2.json + 6. Otherwise (unknown version) -> use auth_config_25_10_2.json Args: domain: The UiPath domain @@ -65,19 +66,25 @@ async def _select_config_file(domain: str) -> str: if _is_cloud_domain(domain): return "auth_config_cloud.json" - # Try to get version from API - version = await get_server_version_async(domain) + # Fetch full server info + info = await get_server_info_async(domain) - # If we can't determine version, fallback to cloud config - if version is None: + # Non-ServiceFabric deployments are cloud + if info is None or info.get("deployment") != "ServiceFabric": return "auth_config_cloud.json" - # Check if version is 25.10.* + version = info.get("version") or "" + + # Check if version is 25.10.* (AS release 25.10) if version.startswith("25.10"): return "auth_config_25_10.json" - # Default fallback to cloud config - return "auth_config_cloud.json" + # Check if version is 26.3.* (AS release 25.10.2) + if version.startswith("26.3"): + return "auth_config_25_10_2.json" + + # Default fallback to latest AS release config + return "auth_config_25_10_2.json" class OidcUtils: diff --git a/packages/uipath/src/uipath/_cli/_auth/auth_config_25_10_2.json b/packages/uipath/src/uipath/_cli/_auth/auth_config_25_10_2.json new file mode 100644 index 000000000..0716b1ea8 --- /dev/null +++ b/packages/uipath/src/uipath/_cli/_auth/auth_config_25_10_2.json @@ -0,0 +1,6 @@ +{ + "client_id": "36dea5b8-e8bb-423d-8e7b-c808df8f1c00", + "redirect_uri": "http://localhost:__PY_REPLACE_PORT__/oidc/login", + "scope": "offline_access ProcessMining OrchestratorApiUserAccess StudioWebBackend IdentityServerApi ConnectionService DataService DocumentUnderstanding Du.Digitization.Api Du.Classification.Api Du.Extraction.Api Du.Validation.Api EnterpriseContextService Directory JamJamApi LLMGateway LLMOps OMS RCS.FolderAuthorization TM.Projects TM.TestCases TM.Requirements TM.TestSets", + "port": 8104 +} diff --git a/packages/uipath/tests/cli/test_oidc_utils.py b/packages/uipath/tests/cli/test_oidc_utils.py index acf4d417a..4a9ba422f 100644 --- a/packages/uipath/tests/cli/test_oidc_utils.py +++ b/packages/uipath/tests/cli/test_oidc_utils.py @@ -121,32 +121,66 @@ def test_is_cloud_domain(self, domain, expected): assert _is_cloud_domain(domain) == expected @pytest.mark.parametrize( - "domain,mock_version,expected_config", + "domain,mock_info,expected_config", [ - # Cloud domains should always use auth_config_cloud.json + # Cloud domains always use auth_config_cloud.json (no API call needed) ("https://alpha.uipath.com", None, "auth_config_cloud.json"), ("https://staging.uipath.com", None, "auth_config_cloud.json"), ("https://cloud.uipath.com", None, "auth_config_cloud.json"), - # Version 25.10.* should use auth_config_25_10.json + # Non-ServiceFabric deployments (cloud releases) use auth_config_cloud.json ( "https://custom.domain.com", - "25.10.0-beta.415", - "auth_config_25_10.json", + {"version": "26.3.0-s188.574", "deployment": None}, + "auth_config_cloud.json", + ), + ( + "https://custom.domain.com", + {"version": "25.10.0-s99", "deployment": "Kubernetes"}, + "auth_config_cloud.json", ), - ("https://custom.domain.com", "25.10.1", "auth_config_25_10.json"), - # Other versions should fallback to cloud config - ("https://custom.domain.com", "24.10.0", "auth_config_cloud.json"), - ("https://custom.domain.com", "26.1.0", "auth_config_cloud.json"), - # Unable to determine version should fallback to cloud config + # API unreachable uses auth_config_cloud.json ("https://custom.domain.com", None, "auth_config_cloud.json"), + # ServiceFabric + 25.10.* (AS release 25.10) uses auth_config_25_10.json + ( + "https://custom.domain.com", + {"version": "25.10.0-beta.415", "deployment": "ServiceFabric"}, + "auth_config_25_10.json", + ), + ( + "https://custom.domain.com", + {"version": "25.10.1", "deployment": "ServiceFabric"}, + "auth_config_25_10.json", + ), + # ServiceFabric + 26.3.* (AS release 25.10.2) uses auth_config_25_10_2.json + ( + "https://custom.domain.com", + {"version": "26.3.0-beta.188", "deployment": "ServiceFabric"}, + "auth_config_25_10_2.json", + ), + ( + "https://custom.domain.com", + {"version": "26.3.1", "deployment": "ServiceFabric"}, + "auth_config_25_10_2.json", + ), + # ServiceFabric + unknown version falls back to latest AS config + ( + "https://custom.domain.com", + {"version": "24.10.0", "deployment": "ServiceFabric"}, + "auth_config_25_10_2.json", + ), + ( + "https://custom.domain.com", + {"version": "26.1.0", "deployment": "ServiceFabric"}, + "auth_config_25_10_2.json", + ), ], ) - async def test_select_config_file(self, domain, mock_version, expected_config): - """Test _select_config_file selects the correct config based on domain and version.""" + async def test_select_config_file(self, domain, mock_info, expected_config): + """Test _select_config_file selects the correct config based on domain and server info.""" with patch( - "uipath._cli._auth._oidc_utils.get_server_version_async", + "uipath._cli._auth._oidc_utils.get_server_info_async", new_callable=AsyncMock, - return_value=mock_version, + return_value=mock_info, ): config_file = await _select_config_file(domain) assert config_file == expected_config @@ -170,12 +204,15 @@ async def test_get_auth_config_with_cloud_domain(self): assert config["port"] == 8104 async def test_get_auth_config_with_25_10_version(self): - """Test get_auth_config with version 25.10 uses auth_config_25_10.json.""" + """Test get_auth_config with AS 25.10 ServiceFabric deployment uses auth_config_25_10.json.""" with ( patch( - "uipath._cli._auth._oidc_utils.get_server_version_async", + "uipath._cli._auth._oidc_utils.get_server_info_async", new_callable=AsyncMock, - return_value="25.10.0-beta.415", + return_value={ + "version": "25.10.0-beta.415", + "deployment": "ServiceFabric", + }, ), patch( "uipath._cli._auth._oidc_utils.OidcUtils._find_free_port", diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 992f421f7..e12c060bc 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2540,7 +2540,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.22" +version = "2.10.23" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, @@ -2679,7 +2679,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.1" +version = "0.1.2" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" },