Skip to content

Commit 97ad2b6

Browse files
feat(assets): add support for AllowDirectApiAccess (#1722)
1 parent 99b686b commit 97ad2b6

7 files changed

Lines changed: 313 additions & 31 deletions

File tree

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.1.65"
3+
version = "0.1.66"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/orchestrator/_assets_service.py

Lines changed: 129 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -283,13 +283,55 @@ async def retrieve_async(
283283
else:
284284
return Asset.model_validate(response.json()["value"][0])
285285

286-
def _ensure_robot_context(self) -> None:
286+
def _resolve_robot_key(
287+
self,
288+
name: str,
289+
*,
290+
folder_key: Optional[str] = None,
291+
folder_path: Optional[str] = None,
292+
) -> Optional[str]:
293+
"""Return the robot key, or ``None`` if the asset opts into direct API access.
294+
295+
Raises ``ValueError`` when no robot key is available and ``AllowDirectApiAccess``
296+
is not enabled on the asset.
297+
"""
287298
try:
288-
is_user = self._execution_context.robot_key is not None
299+
robot_key = self._execution_context.robot_key
289300
except ValueError:
290-
is_user = False
291-
if not is_user:
292-
raise ValueError("This method can only be used for robot assets.")
301+
robot_key = None
302+
303+
if robot_key is None:
304+
asset = self.retrieve(
305+
name=name, folder_key=folder_key, folder_path=folder_path
306+
)
307+
if not asset.allow_direct_api_access:
308+
raise ValueError(
309+
f"No robot key available and 'AllowDirectApiAccess' is disabled for asset '{name}'."
310+
)
311+
return robot_key
312+
313+
async def _resolve_robot_key_async(
314+
self,
315+
name: str,
316+
*,
317+
folder_key: Optional[str] = None,
318+
folder_path: Optional[str] = None,
319+
) -> Optional[str]:
320+
"""Async variant of :meth:`_resolve_robot_key`."""
321+
try:
322+
robot_key = self._execution_context.robot_key
323+
except ValueError:
324+
robot_key = None
325+
326+
if robot_key is None:
327+
asset = await self.retrieve_async(
328+
name=name, folder_key=folder_key, folder_path=folder_path
329+
)
330+
if not asset.allow_direct_api_access:
331+
raise ValueError(
332+
f"No robot key available and 'AllowDirectApiAccess' is disabled for asset '{name}'."
333+
)
334+
return robot_key
293335

294336
@resource_override(resource_type="asset")
295337
@traced(
@@ -305,6 +347,10 @@ def retrieve_credential(
305347
"""Get the decrypted password of a Credential asset.
306348
307349
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
350+
If no robot key is available, the asset's `AllowDirectApiAccess` flag is checked: when
351+
enabled, the credential is fetched without a robot key; otherwise a `ValueError` is raised.
352+
353+
Related Activity: [Get Credential](https://docs.uipath.com/activities/other/latest/workflow/get-robot-credential)
308354
309355
Args:
310356
name (str): The name of the credential asset.
@@ -315,10 +361,18 @@ def retrieve_credential(
315361
Optional[str]: The decrypted credential password.
316362
317363
Raises:
318-
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
364+
ValueError: If no robot key is available and the asset does not have `AllowDirectApiAccess` enabled.
319365
"""
320-
self._ensure_robot_context()
321-
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
366+
robot_key = self._resolve_robot_key(
367+
name, folder_key=folder_key, folder_path=folder_path
368+
)
369+
370+
spec = self._retrieve_credential_spec(
371+
name,
372+
robot_key=robot_key,
373+
folder_key=folder_key,
374+
folder_path=folder_path,
375+
)
322376
response = self.request(
323377
spec.method,
324378
url=spec.endpoint,
@@ -343,6 +397,10 @@ async def retrieve_credential_async(
343397
"""Asynchronously get the decrypted password of a Credential asset.
344398
345399
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
400+
If no robot key is available, the asset's `AllowDirectApiAccess` flag is checked: when
401+
enabled, the credential is fetched without a robot key; otherwise a `ValueError` is raised.
402+
403+
Related Activity: [Get Credential](https://docs.uipath.com/activities/other/latest/workflow/get-robot-credential)
346404
347405
Args:
348406
name (str): The name of the credential asset.
@@ -353,10 +411,18 @@ async def retrieve_credential_async(
353411
Optional[str]: The decrypted credential password.
354412
355413
Raises:
356-
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
414+
ValueError: If no robot key is available and the asset does not have `AllowDirectApiAccess` enabled.
357415
"""
358-
self._ensure_robot_context()
359-
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
416+
robot_key = await self._resolve_robot_key_async(
417+
name, folder_key=folder_key, folder_path=folder_path
418+
)
419+
420+
spec = self._retrieve_credential_spec(
421+
name,
422+
robot_key=robot_key,
423+
folder_key=folder_key,
424+
folder_path=folder_path,
425+
)
360426
response = await self.request_async(
361427
spec.method,
362428
url=spec.endpoint,
@@ -379,6 +445,8 @@ def retrieve_secret(
379445
"""Get the decrypted value of a Secret asset.
380446
381447
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
448+
If no robot key is available, the asset's `AllowDirectApiAccess` flag is checked: when
449+
enabled, the secret is fetched without a robot key; otherwise a `ValueError` is raised.
382450
383451
Args:
384452
name (str): The name of the secret asset.
@@ -389,10 +457,18 @@ def retrieve_secret(
389457
Optional[str]: The decrypted secret value.
390458
391459
Raises:
392-
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
460+
ValueError: If no robot key is available and the asset does not have `AllowDirectApiAccess` enabled.
393461
"""
394-
self._ensure_robot_context()
395-
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
462+
robot_key = self._resolve_robot_key(
463+
name, folder_key=folder_key, folder_path=folder_path
464+
)
465+
466+
spec = self._retrieve_credential_spec(
467+
name,
468+
robot_key=robot_key,
469+
folder_key=folder_key,
470+
folder_path=folder_path,
471+
)
396472
response = self.request(
397473
spec.method,
398474
url=spec.endpoint,
@@ -415,6 +491,8 @@ async def retrieve_secret_async(
415491
"""Asynchronously get the decrypted value of a Secret asset.
416492
417493
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
494+
If no robot key is available, the asset's `AllowDirectApiAccess` flag is checked: when
495+
enabled, the secret is fetched without a robot key; otherwise a `ValueError` is raised.
418496
419497
Args:
420498
name (str): The name of the secret asset.
@@ -425,10 +503,18 @@ async def retrieve_secret_async(
425503
Optional[str]: The decrypted secret value.
426504
427505
Raises:
428-
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
506+
ValueError: If no robot key is available and the asset does not have `AllowDirectApiAccess` enabled.
429507
"""
430-
self._ensure_robot_context()
431-
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
508+
robot_key = await self._resolve_robot_key_async(
509+
name, folder_key=folder_key, folder_path=folder_path
510+
)
511+
512+
spec = self._retrieve_credential_spec(
513+
name,
514+
robot_key=robot_key,
515+
folder_key=folder_key,
516+
folder_path=folder_path,
517+
)
432518
response = await self.request_async(
433519
spec.method,
434520
url=spec.endpoint,
@@ -559,6 +645,32 @@ def _retrieve_spec(
559645
},
560646
)
561647

648+
def _retrieve_credential_spec(
649+
self,
650+
name: str,
651+
*,
652+
robot_key: Optional[str],
653+
folder_key: Optional[str] = None,
654+
folder_path: Optional[str] = None,
655+
) -> RequestSpec:
656+
body: Dict[str, Any] = {
657+
"assetName": name,
658+
"supportsCredentialsProxyDisconnected": True,
659+
}
660+
if robot_key is not None:
661+
body["robotKey"] = robot_key
662+
663+
return RequestSpec(
664+
method="POST",
665+
endpoint=Endpoint(
666+
"/orchestrator_/odata/Assets/UiPath.Server.Configuration.OData.GetRobotAssetByNameForRobotKey"
667+
),
668+
json=body,
669+
headers={
670+
**header_folder(folder_key, folder_path),
671+
},
672+
)
673+
562674
def _update_spec(
563675
self,
564676
robot_asset: UserAsset,

packages/uipath-platform/src/uipath/platform/orchestrator/assets.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class UserAsset(BaseModel):
4747
connection_data: Optional[CredentialsConnectionData] = Field(
4848
default=None, alias="ConnectionData"
4949
)
50+
allow_direct_api_access: Optional[bool] = Field(
51+
default=None, alias="AllowDirectApiAccess"
52+
)
5053
id: Optional[int] = Field(default=None, alias="Id")
5154

5255

@@ -73,3 +76,6 @@ class Asset(BaseModel):
7376
secret_value: Optional[str] = Field(default=None, alias="SecretValue")
7477
external_name: Optional[str] = Field(default=None, alias="ExternalName")
7578
credential_store_id: Optional[int] = Field(default=None, alias="CredentialStoreId")
79+
allow_direct_api_access: Optional[bool] = Field(
80+
default=None, alias="AllowDirectApiAccess"
81+
)

0 commit comments

Comments
 (0)