bt-cli 0.4.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bt_cli/__init__.py +3 -0
- bt_cli/cli.py +830 -0
- bt_cli/commands/__init__.py +1 -0
- bt_cli/commands/configure.py +415 -0
- bt_cli/commands/learn.py +229 -0
- bt_cli/commands/quick.py +784 -0
- bt_cli/core/__init__.py +1 -0
- bt_cli/core/auth.py +213 -0
- bt_cli/core/client.py +313 -0
- bt_cli/core/config.py +393 -0
- bt_cli/core/config_file.py +420 -0
- bt_cli/core/csv_utils.py +91 -0
- bt_cli/core/errors.py +247 -0
- bt_cli/core/output.py +205 -0
- bt_cli/core/prompts.py +87 -0
- bt_cli/core/rest_debug.py +221 -0
- bt_cli/data/CLAUDE.md +88 -0
- bt_cli/data/__init__.py +0 -0
- bt_cli/data/skills/bt/SKILL.md +98 -0
- bt_cli/data/skills/entitle/SKILL.md +159 -0
- bt_cli/data/skills/epmw/SKILL.md +145 -0
- bt_cli/data/skills/pra/SKILL.md +149 -0
- bt_cli/data/skills/pws/SKILL.md +197 -0
- bt_cli/entitle/__init__.py +1 -0
- bt_cli/entitle/client/__init__.py +5 -0
- bt_cli/entitle/client/base.py +443 -0
- bt_cli/entitle/commands/__init__.py +24 -0
- bt_cli/entitle/commands/accounts.py +53 -0
- bt_cli/entitle/commands/applications.py +39 -0
- bt_cli/entitle/commands/auth.py +68 -0
- bt_cli/entitle/commands/bundles.py +218 -0
- bt_cli/entitle/commands/integrations.py +60 -0
- bt_cli/entitle/commands/permissions.py +70 -0
- bt_cli/entitle/commands/policies.py +97 -0
- bt_cli/entitle/commands/resources.py +131 -0
- bt_cli/entitle/commands/roles.py +74 -0
- bt_cli/entitle/commands/users.py +123 -0
- bt_cli/entitle/commands/workflows.py +187 -0
- bt_cli/entitle/models/__init__.py +31 -0
- bt_cli/entitle/models/bundle.py +28 -0
- bt_cli/entitle/models/common.py +37 -0
- bt_cli/entitle/models/integration.py +30 -0
- bt_cli/entitle/models/permission.py +27 -0
- bt_cli/entitle/models/policy.py +25 -0
- bt_cli/entitle/models/resource.py +29 -0
- bt_cli/entitle/models/role.py +28 -0
- bt_cli/entitle/models/user.py +24 -0
- bt_cli/entitle/models/workflow.py +55 -0
- bt_cli/epmw/__init__.py +1 -0
- bt_cli/epmw/client/__init__.py +5 -0
- bt_cli/epmw/client/base.py +848 -0
- bt_cli/epmw/commands/__init__.py +33 -0
- bt_cli/epmw/commands/audits.py +250 -0
- bt_cli/epmw/commands/auth.py +55 -0
- bt_cli/epmw/commands/computers.py +140 -0
- bt_cli/epmw/commands/events.py +233 -0
- bt_cli/epmw/commands/groups.py +215 -0
- bt_cli/epmw/commands/policies.py +673 -0
- bt_cli/epmw/commands/quick.py +348 -0
- bt_cli/epmw/commands/requests.py +224 -0
- bt_cli/epmw/commands/roles.py +78 -0
- bt_cli/epmw/commands/tasks.py +38 -0
- bt_cli/epmw/commands/users.py +219 -0
- bt_cli/epmw/models/__init__.py +1 -0
- bt_cli/pra/__init__.py +1 -0
- bt_cli/pra/client/__init__.py +5 -0
- bt_cli/pra/client/base.py +618 -0
- bt_cli/pra/commands/__init__.py +30 -0
- bt_cli/pra/commands/auth.py +55 -0
- bt_cli/pra/commands/import_export.py +442 -0
- bt_cli/pra/commands/jump_clients.py +139 -0
- bt_cli/pra/commands/jump_groups.py +146 -0
- bt_cli/pra/commands/jump_items.py +638 -0
- bt_cli/pra/commands/jumpoints.py +95 -0
- bt_cli/pra/commands/policies.py +197 -0
- bt_cli/pra/commands/quick.py +470 -0
- bt_cli/pra/commands/teams.py +81 -0
- bt_cli/pra/commands/users.py +87 -0
- bt_cli/pra/commands/vault.py +564 -0
- bt_cli/pra/models/__init__.py +27 -0
- bt_cli/pra/models/common.py +12 -0
- bt_cli/pra/models/jump_client.py +25 -0
- bt_cli/pra/models/jump_group.py +15 -0
- bt_cli/pra/models/jump_item.py +72 -0
- bt_cli/pra/models/jumpoint.py +19 -0
- bt_cli/pra/models/team.py +14 -0
- bt_cli/pra/models/user.py +17 -0
- bt_cli/pra/models/vault.py +45 -0
- bt_cli/pws/__init__.py +1 -0
- bt_cli/pws/client/__init__.py +5 -0
- bt_cli/pws/client/base.py +356 -0
- bt_cli/pws/client/beyondinsight.py +869 -0
- bt_cli/pws/client/passwordsafe.py +1786 -0
- bt_cli/pws/commands/__init__.py +33 -0
- bt_cli/pws/commands/accounts.py +372 -0
- bt_cli/pws/commands/assets.py +311 -0
- bt_cli/pws/commands/auth.py +166 -0
- bt_cli/pws/commands/clouds.py +221 -0
- bt_cli/pws/commands/config.py +344 -0
- bt_cli/pws/commands/credentials.py +347 -0
- bt_cli/pws/commands/databases.py +306 -0
- bt_cli/pws/commands/directories.py +199 -0
- bt_cli/pws/commands/functional.py +298 -0
- bt_cli/pws/commands/import_export.py +452 -0
- bt_cli/pws/commands/platforms.py +118 -0
- bt_cli/pws/commands/quick.py +1646 -0
- bt_cli/pws/commands/search.py +256 -0
- bt_cli/pws/commands/secrets.py +1343 -0
- bt_cli/pws/commands/systems.py +389 -0
- bt_cli/pws/commands/users.py +415 -0
- bt_cli/pws/commands/workgroups.py +166 -0
- bt_cli/pws/config.py +18 -0
- bt_cli/pws/models/__init__.py +19 -0
- bt_cli/pws/models/account.py +186 -0
- bt_cli/pws/models/asset.py +102 -0
- bt_cli/pws/models/common.py +132 -0
- bt_cli/pws/models/system.py +121 -0
- bt_cli-0.4.7.dist-info/METADATA +172 -0
- bt_cli-0.4.7.dist-info/RECORD +121 -0
- bt_cli-0.4.7.dist-info/WHEEL +4 -0
- bt_cli-0.4.7.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
"""BeyondInsight API client for asset and system management."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .base import PasswordSafeClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BeyondInsightMixin:
|
|
10
|
+
"""Mixin class providing BeyondInsight API methods.
|
|
11
|
+
|
|
12
|
+
Add to PasswordSafeClient to provide asset, system, and workgroup operations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# =========================================================================
|
|
16
|
+
# Assets
|
|
17
|
+
# =========================================================================
|
|
18
|
+
|
|
19
|
+
def list_assets(
|
|
20
|
+
self: "PasswordSafeClient",
|
|
21
|
+
workgroup_id: Optional[int] = None,
|
|
22
|
+
limit: Optional[int] = None,
|
|
23
|
+
) -> list[dict[str, Any]]:
|
|
24
|
+
"""List assets by workgroup.
|
|
25
|
+
|
|
26
|
+
Assets must be retrieved by workgroup ID.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
workgroup_id: Workgroup ID (if None, fetches from all workgroups)
|
|
30
|
+
limit: Maximum number of results
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of asset objects
|
|
34
|
+
"""
|
|
35
|
+
params = {}
|
|
36
|
+
if limit:
|
|
37
|
+
params["limit"] = limit
|
|
38
|
+
|
|
39
|
+
if workgroup_id:
|
|
40
|
+
return self.paginate(f"/Workgroups/{workgroup_id}/Assets", params=params)
|
|
41
|
+
else:
|
|
42
|
+
# Fetch assets from all workgroups
|
|
43
|
+
all_assets = []
|
|
44
|
+
workgroups = self.list_workgroups()
|
|
45
|
+
for wg in workgroups:
|
|
46
|
+
wg_id = wg.get("WorkgroupID", wg.get("ID"))
|
|
47
|
+
if wg_id:
|
|
48
|
+
try:
|
|
49
|
+
assets = self.paginate(f"/Workgroups/{wg_id}/Assets", params=params)
|
|
50
|
+
all_assets.extend(assets)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass # Skip workgroups we can't access
|
|
53
|
+
return all_assets
|
|
54
|
+
|
|
55
|
+
def get_asset(self: "PasswordSafeClient", asset_id: int) -> dict[str, Any]:
|
|
56
|
+
"""Get an asset by ID.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
asset_id: Asset ID
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Asset object
|
|
63
|
+
"""
|
|
64
|
+
return self.get(f"/Assets/{asset_id}")
|
|
65
|
+
|
|
66
|
+
def create_asset(
|
|
67
|
+
self: "PasswordSafeClient",
|
|
68
|
+
workgroup_id: int,
|
|
69
|
+
ip_address: str,
|
|
70
|
+
asset_name: Optional[str] = None,
|
|
71
|
+
dns_name: Optional[str] = None,
|
|
72
|
+
domain_name: Optional[str] = None,
|
|
73
|
+
mac_address: Optional[str] = None,
|
|
74
|
+
asset_type: Optional[str] = None,
|
|
75
|
+
description: Optional[str] = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
"""Create a new asset in a workgroup.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
workgroup_id: Workgroup ID to add asset to
|
|
81
|
+
ip_address: IP address of the asset
|
|
82
|
+
asset_name: Optional name for the asset
|
|
83
|
+
dns_name: Optional DNS name
|
|
84
|
+
domain_name: Optional domain name
|
|
85
|
+
mac_address: Optional MAC address
|
|
86
|
+
asset_type: Optional asset type
|
|
87
|
+
description: Optional description
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Created asset object
|
|
91
|
+
"""
|
|
92
|
+
data: dict[str, Any] = {"IPAddress": ip_address}
|
|
93
|
+
if asset_name:
|
|
94
|
+
data["AssetName"] = asset_name
|
|
95
|
+
if dns_name:
|
|
96
|
+
data["DnsName"] = dns_name
|
|
97
|
+
if domain_name:
|
|
98
|
+
data["DomainName"] = domain_name
|
|
99
|
+
if mac_address:
|
|
100
|
+
data["MACAddress"] = mac_address
|
|
101
|
+
if asset_type:
|
|
102
|
+
data["AssetType"] = asset_type
|
|
103
|
+
if description:
|
|
104
|
+
data["Description"] = description
|
|
105
|
+
|
|
106
|
+
return self.post(f"/Workgroups/{workgroup_id}/Assets", json=data)
|
|
107
|
+
|
|
108
|
+
def update_asset(
|
|
109
|
+
self: "PasswordSafeClient",
|
|
110
|
+
asset_id: int,
|
|
111
|
+
**kwargs: Any,
|
|
112
|
+
) -> dict[str, Any]:
|
|
113
|
+
"""Update an asset.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
asset_id: Asset ID to update
|
|
117
|
+
**kwargs: Fields to update (asset_name, dns_name, etc.)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Updated asset object
|
|
121
|
+
"""
|
|
122
|
+
# Convert snake_case to PascalCase for API
|
|
123
|
+
data = {}
|
|
124
|
+
for key, value in kwargs.items():
|
|
125
|
+
if value is not None:
|
|
126
|
+
pascal_key = "".join(word.capitalize() for word in key.split("_"))
|
|
127
|
+
data[pascal_key] = value
|
|
128
|
+
|
|
129
|
+
return self.put(f"/Assets/{asset_id}", json=data)
|
|
130
|
+
|
|
131
|
+
def delete_asset(self: "PasswordSafeClient", asset_id: int) -> dict[str, Any]:
|
|
132
|
+
"""Delete an asset by ID.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
asset_id: Asset ID to delete
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Empty response on success
|
|
139
|
+
"""
|
|
140
|
+
return self.delete(f"/Assets/{asset_id}")
|
|
141
|
+
|
|
142
|
+
def delete_asset_by_workgroup(
|
|
143
|
+
self: "PasswordSafeClient",
|
|
144
|
+
workgroup_id: int,
|
|
145
|
+
asset_name: str,
|
|
146
|
+
) -> dict[str, Any]:
|
|
147
|
+
"""Delete an asset by workgroup and asset name.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
workgroup_id: Workgroup ID
|
|
151
|
+
asset_name: Asset name to delete
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Empty response on success
|
|
155
|
+
"""
|
|
156
|
+
return self.delete(f"/Workgroups/{workgroup_id}/Assets/{asset_name}")
|
|
157
|
+
|
|
158
|
+
def search_assets(
|
|
159
|
+
self: "PasswordSafeClient",
|
|
160
|
+
search_term: str,
|
|
161
|
+
limit: Optional[int] = None,
|
|
162
|
+
) -> list[dict[str, Any]]:
|
|
163
|
+
"""Search for assets.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
search_term: Search term
|
|
167
|
+
limit: Maximum number of results
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of matching asset objects
|
|
171
|
+
"""
|
|
172
|
+
data = {"SearchTerm": search_term}
|
|
173
|
+
if limit:
|
|
174
|
+
data["Limit"] = limit
|
|
175
|
+
|
|
176
|
+
response = self.post("/Assets/Search", json=data)
|
|
177
|
+
if isinstance(response, list):
|
|
178
|
+
return response
|
|
179
|
+
return response.get("Data", response.get("data", []))
|
|
180
|
+
|
|
181
|
+
# =========================================================================
|
|
182
|
+
# Managed Systems
|
|
183
|
+
# =========================================================================
|
|
184
|
+
|
|
185
|
+
def list_managed_systems(
|
|
186
|
+
self: "PasswordSafeClient",
|
|
187
|
+
workgroup_id: Optional[int] = None,
|
|
188
|
+
search: Optional[str] = None,
|
|
189
|
+
limit: Optional[int] = None,
|
|
190
|
+
) -> list[dict[str, Any]]:
|
|
191
|
+
"""List managed systems.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
workgroup_id: Optional workgroup filter
|
|
195
|
+
search: Optional search filter
|
|
196
|
+
limit: Maximum number of results
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of managed system objects
|
|
200
|
+
"""
|
|
201
|
+
params = {}
|
|
202
|
+
if search:
|
|
203
|
+
params["search"] = search
|
|
204
|
+
if limit:
|
|
205
|
+
params["limit"] = limit
|
|
206
|
+
|
|
207
|
+
if workgroup_id:
|
|
208
|
+
return self.paginate(
|
|
209
|
+
f"/Workgroups/{workgroup_id}/ManagedSystems", params=params
|
|
210
|
+
)
|
|
211
|
+
return self.paginate("/ManagedSystems", params=params)
|
|
212
|
+
|
|
213
|
+
def get_managed_system(
|
|
214
|
+
self: "PasswordSafeClient", system_id: int
|
|
215
|
+
) -> dict[str, Any]:
|
|
216
|
+
"""Get a managed system by ID.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
system_id: Managed system ID
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Managed system object
|
|
223
|
+
"""
|
|
224
|
+
return self.get(f"/ManagedSystems/{system_id}")
|
|
225
|
+
|
|
226
|
+
def get_managed_system_by_name(
|
|
227
|
+
self: "PasswordSafeClient", system_name: str
|
|
228
|
+
) -> Optional[dict[str, Any]]:
|
|
229
|
+
"""Get a managed system by name.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
system_name: System name to search for
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Managed system object or None if not found
|
|
236
|
+
"""
|
|
237
|
+
systems = self.list_managed_systems(search=system_name)
|
|
238
|
+
for system in systems:
|
|
239
|
+
if system.get("SystemName", "").lower() == system_name.lower():
|
|
240
|
+
return system
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def create_managed_system(
|
|
244
|
+
self: "PasswordSafeClient",
|
|
245
|
+
system_name: str,
|
|
246
|
+
platform_id: int,
|
|
247
|
+
workgroup_id: Optional[int] = None,
|
|
248
|
+
asset_id: Optional[int] = None,
|
|
249
|
+
contact_email: Optional[str] = None,
|
|
250
|
+
description: Optional[str] = None,
|
|
251
|
+
port: Optional[int] = None,
|
|
252
|
+
timeout: Optional[int] = None,
|
|
253
|
+
functional_account_id: Optional[int] = None,
|
|
254
|
+
elevation_command: Optional[str] = None,
|
|
255
|
+
auto_management_flag: Optional[bool] = None,
|
|
256
|
+
check_password_flag: Optional[bool] = None,
|
|
257
|
+
change_password_after_any_release_flag: Optional[bool] = None,
|
|
258
|
+
reset_password_on_mismatch_flag: Optional[bool] = None,
|
|
259
|
+
change_frequency_type: Optional[str] = None,
|
|
260
|
+
change_frequency_days: Optional[int] = None,
|
|
261
|
+
change_time: Optional[str] = None,
|
|
262
|
+
password_rule_id: Optional[int] = None,
|
|
263
|
+
) -> dict[str, Any]:
|
|
264
|
+
"""Create a new managed system.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
system_name: Name for the managed system
|
|
268
|
+
platform_id: Platform ID (determines OS/system type)
|
|
269
|
+
workgroup_id: Workgroup to add system to
|
|
270
|
+
asset_id: Optional asset ID to link
|
|
271
|
+
contact_email: Contact email address
|
|
272
|
+
description: System description
|
|
273
|
+
port: Connection port
|
|
274
|
+
timeout: Connection timeout
|
|
275
|
+
functional_account_id: Functional account for management
|
|
276
|
+
elevation_command: Command for privilege elevation
|
|
277
|
+
auto_management_flag: Enable automatic password management
|
|
278
|
+
check_password_flag: Enable password checking
|
|
279
|
+
change_password_after_any_release_flag: Change password after release
|
|
280
|
+
reset_password_on_mismatch_flag: Reset on password mismatch
|
|
281
|
+
change_frequency_type: Password change frequency type
|
|
282
|
+
change_frequency_days: Days between password changes
|
|
283
|
+
change_time: Time of day for password changes
|
|
284
|
+
password_rule_id: Password rule ID for password generation
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Created managed system object
|
|
288
|
+
"""
|
|
289
|
+
data: dict[str, Any] = {
|
|
290
|
+
"SystemName": system_name,
|
|
291
|
+
"PlatformID": platform_id,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if workgroup_id is not None:
|
|
295
|
+
data["WorkgroupID"] = workgroup_id
|
|
296
|
+
if asset_id is not None:
|
|
297
|
+
data["AssetID"] = asset_id
|
|
298
|
+
if contact_email:
|
|
299
|
+
data["ContactEmail"] = contact_email
|
|
300
|
+
if description:
|
|
301
|
+
data["Description"] = description
|
|
302
|
+
if port is not None:
|
|
303
|
+
data["Port"] = port
|
|
304
|
+
if timeout is not None:
|
|
305
|
+
data["Timeout"] = timeout
|
|
306
|
+
if functional_account_id is not None:
|
|
307
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
308
|
+
if elevation_command:
|
|
309
|
+
data["ElevationCommand"] = elevation_command
|
|
310
|
+
if auto_management_flag is not None:
|
|
311
|
+
data["AutoManagementFlag"] = auto_management_flag
|
|
312
|
+
if check_password_flag is not None:
|
|
313
|
+
data["CheckPasswordFlag"] = check_password_flag
|
|
314
|
+
if change_password_after_any_release_flag is not None:
|
|
315
|
+
data["ChangePasswordAfterAnyReleaseFlag"] = change_password_after_any_release_flag
|
|
316
|
+
if reset_password_on_mismatch_flag is not None:
|
|
317
|
+
data["ResetPasswordOnMismatchFlag"] = reset_password_on_mismatch_flag
|
|
318
|
+
if change_frequency_type:
|
|
319
|
+
data["ChangeFrequencyType"] = change_frequency_type
|
|
320
|
+
if change_frequency_days is not None:
|
|
321
|
+
data["ChangeFrequencyDays"] = change_frequency_days
|
|
322
|
+
if change_time:
|
|
323
|
+
data["ChangeTime"] = change_time
|
|
324
|
+
if password_rule_id is not None:
|
|
325
|
+
data["PasswordRuleID"] = password_rule_id
|
|
326
|
+
|
|
327
|
+
# Determine endpoint based on relationship
|
|
328
|
+
if workgroup_id:
|
|
329
|
+
return self.post(f"/Workgroups/{workgroup_id}/ManagedSystems", json=data)
|
|
330
|
+
elif asset_id:
|
|
331
|
+
return self.post(f"/Assets/{asset_id}/ManagedSystems", json=data)
|
|
332
|
+
else:
|
|
333
|
+
return self.post("/ManagedSystems", json=data)
|
|
334
|
+
|
|
335
|
+
def update_managed_system(
|
|
336
|
+
self: "PasswordSafeClient",
|
|
337
|
+
system_id: int,
|
|
338
|
+
**kwargs: Any,
|
|
339
|
+
) -> dict[str, Any]:
|
|
340
|
+
"""Update a managed system.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
system_id: System ID to update
|
|
344
|
+
**kwargs: Fields to update (SystemName, Description, etc.)
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Updated managed system object
|
|
348
|
+
"""
|
|
349
|
+
# Convert snake_case to PascalCase for API
|
|
350
|
+
data = {}
|
|
351
|
+
for key, value in kwargs.items():
|
|
352
|
+
if value is not None:
|
|
353
|
+
pascal_key = "".join(word.capitalize() for word in key.split("_"))
|
|
354
|
+
data[pascal_key] = value
|
|
355
|
+
|
|
356
|
+
return self.put(f"/ManagedSystems/{system_id}", json=data)
|
|
357
|
+
|
|
358
|
+
def delete_managed_system(
|
|
359
|
+
self: "PasswordSafeClient", system_id: int
|
|
360
|
+
) -> dict[str, Any]:
|
|
361
|
+
"""Delete a managed system.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
system_id: System ID to delete
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Empty response on success
|
|
368
|
+
"""
|
|
369
|
+
return self.delete(f"/ManagedSystems/{system_id}")
|
|
370
|
+
|
|
371
|
+
# =========================================================================
|
|
372
|
+
# Workgroups
|
|
373
|
+
# =========================================================================
|
|
374
|
+
|
|
375
|
+
def list_workgroups(
|
|
376
|
+
self: "PasswordSafeClient",
|
|
377
|
+
search: Optional[str] = None,
|
|
378
|
+
limit: Optional[int] = None,
|
|
379
|
+
) -> list[dict[str, Any]]:
|
|
380
|
+
"""List all workgroups.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
search: Optional search filter
|
|
384
|
+
limit: Maximum number of results
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
List of workgroup objects
|
|
388
|
+
"""
|
|
389
|
+
params = {}
|
|
390
|
+
if search:
|
|
391
|
+
params["search"] = search
|
|
392
|
+
if limit:
|
|
393
|
+
params["limit"] = limit
|
|
394
|
+
|
|
395
|
+
return self.paginate("/Workgroups", params=params)
|
|
396
|
+
|
|
397
|
+
def get_workgroup(self: "PasswordSafeClient", workgroup_id: int) -> dict[str, Any]:
|
|
398
|
+
"""Get a workgroup by ID.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
workgroup_id: Workgroup ID
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Workgroup object
|
|
405
|
+
"""
|
|
406
|
+
return self.get(f"/Workgroups/{workgroup_id}")
|
|
407
|
+
|
|
408
|
+
def create_workgroup(
|
|
409
|
+
self: "PasswordSafeClient",
|
|
410
|
+
name: str,
|
|
411
|
+
description: Optional[str] = None,
|
|
412
|
+
) -> dict[str, Any]:
|
|
413
|
+
"""Create a new workgroup.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
name: Workgroup name
|
|
417
|
+
description: Optional description
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Created workgroup object
|
|
421
|
+
"""
|
|
422
|
+
data = {"Name": name}
|
|
423
|
+
if description:
|
|
424
|
+
data["Description"] = description
|
|
425
|
+
|
|
426
|
+
return self.post("/Workgroups", json=data)
|
|
427
|
+
|
|
428
|
+
def delete_workgroup(self: "PasswordSafeClient", workgroup_id: int) -> dict[str, Any]:
|
|
429
|
+
"""Delete a workgroup.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
workgroup_id: Workgroup ID to delete
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Empty response on success
|
|
436
|
+
"""
|
|
437
|
+
return self.delete(f"/Workgroups/{workgroup_id}")
|
|
438
|
+
|
|
439
|
+
# =========================================================================
|
|
440
|
+
# Platforms
|
|
441
|
+
# =========================================================================
|
|
442
|
+
|
|
443
|
+
def list_platforms(
|
|
444
|
+
self: "PasswordSafeClient",
|
|
445
|
+
search: Optional[str] = None,
|
|
446
|
+
) -> list[dict[str, Any]]:
|
|
447
|
+
"""List all platforms (OS types).
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
search: Optional search filter
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
List of platform objects
|
|
454
|
+
"""
|
|
455
|
+
params = {}
|
|
456
|
+
if search:
|
|
457
|
+
params["search"] = search
|
|
458
|
+
|
|
459
|
+
return self.paginate("/Platforms", params=params)
|
|
460
|
+
|
|
461
|
+
def get_platform(self: "PasswordSafeClient", platform_id: int) -> dict[str, Any]:
|
|
462
|
+
"""Get a platform by ID.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
platform_id: Platform ID
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
Platform object
|
|
469
|
+
"""
|
|
470
|
+
return self.get(f"/Platforms/{platform_id}")
|
|
471
|
+
|
|
472
|
+
# =========================================================================
|
|
473
|
+
# Databases
|
|
474
|
+
# =========================================================================
|
|
475
|
+
|
|
476
|
+
def list_databases(
|
|
477
|
+
self: "PasswordSafeClient",
|
|
478
|
+
asset_id: Optional[int] = None,
|
|
479
|
+
limit: Optional[int] = None,
|
|
480
|
+
) -> list[dict[str, Any]]:
|
|
481
|
+
"""List databases.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
asset_id: Optional asset ID to filter by
|
|
485
|
+
limit: Maximum number of results (None for all)
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
List of database objects
|
|
489
|
+
"""
|
|
490
|
+
endpoint = f"/Assets/{asset_id}/Databases" if asset_id else "/Databases"
|
|
491
|
+
|
|
492
|
+
# If limit is set, do a single request
|
|
493
|
+
if limit is not None:
|
|
494
|
+
result = self.get(endpoint, params={"limit": limit})
|
|
495
|
+
if isinstance(result, list):
|
|
496
|
+
return result
|
|
497
|
+
if isinstance(result, dict):
|
|
498
|
+
return result.get("Data", result.get("results", []))
|
|
499
|
+
return []
|
|
500
|
+
|
|
501
|
+
return self.paginate(endpoint)
|
|
502
|
+
|
|
503
|
+
def get_database(self: "PasswordSafeClient", database_id: int) -> dict[str, Any]:
|
|
504
|
+
"""Get a database by ID.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
database_id: Database ID
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Database object
|
|
511
|
+
"""
|
|
512
|
+
return self.get(f"/Databases/{database_id}")
|
|
513
|
+
|
|
514
|
+
def create_database(
|
|
515
|
+
self: "PasswordSafeClient",
|
|
516
|
+
asset_id: int,
|
|
517
|
+
platform_id: int,
|
|
518
|
+
instance_name: str,
|
|
519
|
+
port: Optional[int] = None,
|
|
520
|
+
is_default_instance: bool = False,
|
|
521
|
+
version: Optional[str] = None,
|
|
522
|
+
template: Optional[str] = None,
|
|
523
|
+
) -> dict[str, Any]:
|
|
524
|
+
"""Create a database on an asset.
|
|
525
|
+
|
|
526
|
+
Database platforms include: PostgreSQL (79), MySQL (10), MS SQL Server (11),
|
|
527
|
+
Oracle (8), MongoDB (74), etc.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
asset_id: Asset ID to create database on
|
|
531
|
+
platform_id: Database platform ID
|
|
532
|
+
instance_name: Database instance name
|
|
533
|
+
port: Connection port (uses platform default if not specified)
|
|
534
|
+
is_default_instance: Whether this is the default instance
|
|
535
|
+
version: Database version
|
|
536
|
+
template: Database template
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Created database object
|
|
540
|
+
"""
|
|
541
|
+
data: dict[str, Any] = {
|
|
542
|
+
"PlatformID": platform_id,
|
|
543
|
+
"InstanceName": instance_name,
|
|
544
|
+
}
|
|
545
|
+
if port is not None:
|
|
546
|
+
data["Port"] = port
|
|
547
|
+
if is_default_instance:
|
|
548
|
+
data["IsDefaultInstance"] = is_default_instance
|
|
549
|
+
if version:
|
|
550
|
+
data["Version"] = version
|
|
551
|
+
if template:
|
|
552
|
+
data["Template"] = template
|
|
553
|
+
|
|
554
|
+
return self.post(f"/Assets/{asset_id}/Databases", json=data)
|
|
555
|
+
|
|
556
|
+
def delete_database(self: "PasswordSafeClient", database_id: int) -> dict[str, Any]:
|
|
557
|
+
"""Delete a database.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
database_id: Database ID to delete
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Empty response on success
|
|
564
|
+
"""
|
|
565
|
+
return self.delete(f"/Databases/{database_id}")
|
|
566
|
+
|
|
567
|
+
def create_database_managed_system(
|
|
568
|
+
self: "PasswordSafeClient",
|
|
569
|
+
database_id: int,
|
|
570
|
+
platform_id: int,
|
|
571
|
+
system_name: Optional[str] = None,
|
|
572
|
+
port: Optional[int] = None,
|
|
573
|
+
functional_account_id: Optional[int] = None,
|
|
574
|
+
description: Optional[str] = None,
|
|
575
|
+
auto_management_flag: bool = False,
|
|
576
|
+
check_password_flag: bool = True,
|
|
577
|
+
change_password_after_any_release_flag: bool = True,
|
|
578
|
+
reset_password_on_mismatch_flag: bool = True,
|
|
579
|
+
change_frequency_type: Optional[str] = None,
|
|
580
|
+
change_frequency_days: Optional[int] = None,
|
|
581
|
+
change_time: Optional[str] = None,
|
|
582
|
+
) -> dict[str, Any]:
|
|
583
|
+
"""Create a managed system for a database.
|
|
584
|
+
|
|
585
|
+
This is the correct way to create managed systems for database platforms
|
|
586
|
+
like PostgreSQL, MySQL, Oracle, etc.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
database_id: Database ID to create managed system for
|
|
590
|
+
platform_id: Platform ID (must match database platform)
|
|
591
|
+
system_name: Name for the managed system
|
|
592
|
+
port: Connection port
|
|
593
|
+
functional_account_id: Functional account for password management
|
|
594
|
+
description: System description
|
|
595
|
+
auto_management_flag: Enable automatic password management
|
|
596
|
+
check_password_flag: Enable password checking
|
|
597
|
+
change_password_after_any_release_flag: Change password after release
|
|
598
|
+
reset_password_on_mismatch_flag: Reset on password mismatch
|
|
599
|
+
change_frequency_type: Password change frequency (first, last, xdays)
|
|
600
|
+
change_frequency_days: Days between changes (if xdays)
|
|
601
|
+
change_time: Time for password changes (HH:MM format)
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Created managed system object
|
|
605
|
+
"""
|
|
606
|
+
data: dict[str, Any] = {
|
|
607
|
+
"PlatformID": platform_id,
|
|
608
|
+
}
|
|
609
|
+
if system_name:
|
|
610
|
+
data["SystemName"] = system_name
|
|
611
|
+
if port is not None:
|
|
612
|
+
data["Port"] = port
|
|
613
|
+
if description:
|
|
614
|
+
data["Description"] = description
|
|
615
|
+
if auto_management_flag:
|
|
616
|
+
data["AutoManagementFlag"] = auto_management_flag
|
|
617
|
+
if functional_account_id is not None:
|
|
618
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
619
|
+
if change_frequency_type:
|
|
620
|
+
data["ChangeFrequencyType"] = change_frequency_type
|
|
621
|
+
if change_frequency_days is not None:
|
|
622
|
+
data["ChangeFrequencyDays"] = change_frequency_days
|
|
623
|
+
if change_time:
|
|
624
|
+
data["ChangeTime"] = change_time
|
|
625
|
+
if check_password_flag is not None:
|
|
626
|
+
data["CheckPasswordFlag"] = check_password_flag
|
|
627
|
+
if change_password_after_any_release_flag is not None:
|
|
628
|
+
data["ChangePasswordAfterAnyReleaseFlag"] = change_password_after_any_release_flag
|
|
629
|
+
if reset_password_on_mismatch_flag is not None:
|
|
630
|
+
data["ResetPasswordOnMismatchFlag"] = reset_password_on_mismatch_flag
|
|
631
|
+
|
|
632
|
+
return self.post(f"/Databases/{database_id}/ManagedSystems", json=data)
|
|
633
|
+
|
|
634
|
+
# =========================================================================
|
|
635
|
+
# Directories (Entra ID, Active Directory)
|
|
636
|
+
# =========================================================================
|
|
637
|
+
|
|
638
|
+
def list_directories(self: "PasswordSafeClient") -> list[dict[str, Any]]:
|
|
639
|
+
"""List directories (Active Directory, Entra ID).
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
List of directory objects
|
|
643
|
+
"""
|
|
644
|
+
return self.paginate("/Directories")
|
|
645
|
+
|
|
646
|
+
def get_directory(self: "PasswordSafeClient", directory_id: int) -> dict[str, Any]:
|
|
647
|
+
"""Get a directory by ID.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
directory_id: Directory ID
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Directory object
|
|
654
|
+
"""
|
|
655
|
+
return self.get(f"/Directories/{directory_id}")
|
|
656
|
+
|
|
657
|
+
def create_directory_managed_system(
|
|
658
|
+
self: "PasswordSafeClient",
|
|
659
|
+
workgroup_id: int,
|
|
660
|
+
platform_id: int,
|
|
661
|
+
host_name: str,
|
|
662
|
+
domain_name: Optional[str] = None,
|
|
663
|
+
system_name: Optional[str] = None,
|
|
664
|
+
functional_account_id: Optional[int] = None,
|
|
665
|
+
description: Optional[str] = None,
|
|
666
|
+
auto_management_flag: bool = False,
|
|
667
|
+
account_name_format: int = 1,
|
|
668
|
+
check_password_flag: bool = False,
|
|
669
|
+
change_password_after_any_release_flag: bool = False,
|
|
670
|
+
reset_password_on_mismatch_flag: bool = False,
|
|
671
|
+
change_frequency_type: Optional[str] = None,
|
|
672
|
+
change_frequency_days: Optional[int] = None,
|
|
673
|
+
change_time: Optional[str] = None,
|
|
674
|
+
) -> dict[str, Any]:
|
|
675
|
+
"""Create a managed system for a directory (Entra ID, Active Directory).
|
|
676
|
+
|
|
677
|
+
This is used for directory platforms like Microsoft Entra ID (84) and
|
|
678
|
+
Active Directory (25).
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
workgroup_id: Workgroup ID to add the system to
|
|
682
|
+
platform_id: Platform ID (84=Entra ID, 25=Active Directory)
|
|
683
|
+
host_name: Host name (domain name for Entra ID)
|
|
684
|
+
domain_name: Domain name
|
|
685
|
+
system_name: Name for the managed system
|
|
686
|
+
functional_account_id: Functional account for management
|
|
687
|
+
description: System description
|
|
688
|
+
auto_management_flag: Enable automatic password management
|
|
689
|
+
account_name_format: 0=Domain/Account, 1=UPN, 2=SAM
|
|
690
|
+
check_password_flag: Enable password checking
|
|
691
|
+
change_password_after_any_release_flag: Change password after release
|
|
692
|
+
reset_password_on_mismatch_flag: Reset on password mismatch
|
|
693
|
+
change_frequency_type: Password change frequency (first, last, xdays)
|
|
694
|
+
change_frequency_days: Days between changes (if xdays)
|
|
695
|
+
change_time: Time for password changes (HH:MM format)
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
Created managed system object
|
|
699
|
+
"""
|
|
700
|
+
data: dict[str, Any] = {
|
|
701
|
+
"EntityTypeID": 3, # Directory
|
|
702
|
+
"PlatformID": platform_id,
|
|
703
|
+
"HostName": host_name,
|
|
704
|
+
"AccountNameFormat": account_name_format,
|
|
705
|
+
}
|
|
706
|
+
if domain_name:
|
|
707
|
+
data["DomainName"] = domain_name
|
|
708
|
+
if system_name:
|
|
709
|
+
data["SystemName"] = system_name
|
|
710
|
+
if description:
|
|
711
|
+
data["Description"] = description
|
|
712
|
+
if auto_management_flag:
|
|
713
|
+
data["AutoManagementFlag"] = auto_management_flag
|
|
714
|
+
if functional_account_id is not None:
|
|
715
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
716
|
+
if change_frequency_type:
|
|
717
|
+
data["ChangeFrequencyType"] = change_frequency_type
|
|
718
|
+
if change_frequency_days is not None:
|
|
719
|
+
data["ChangeFrequencyDays"] = change_frequency_days
|
|
720
|
+
if change_time:
|
|
721
|
+
data["ChangeTime"] = change_time
|
|
722
|
+
elif functional_account_id is not None:
|
|
723
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
724
|
+
if check_password_flag is not None:
|
|
725
|
+
data["CheckPasswordFlag"] = check_password_flag
|
|
726
|
+
if change_password_after_any_release_flag is not None:
|
|
727
|
+
data["ChangePasswordAfterAnyReleaseFlag"] = change_password_after_any_release_flag
|
|
728
|
+
if reset_password_on_mismatch_flag is not None:
|
|
729
|
+
data["ResetPasswordOnMismatchFlag"] = reset_password_on_mismatch_flag
|
|
730
|
+
|
|
731
|
+
return self.post(f"/Workgroups/{workgroup_id}/ManagedSystems", json=data)
|
|
732
|
+
|
|
733
|
+
def create_cloud_managed_system(
|
|
734
|
+
self: "PasswordSafeClient",
|
|
735
|
+
workgroup_id: int,
|
|
736
|
+
platform_id: int,
|
|
737
|
+
host_name: str,
|
|
738
|
+
system_name: Optional[str] = None,
|
|
739
|
+
access_url: Optional[str] = None,
|
|
740
|
+
functional_account_id: Optional[int] = None,
|
|
741
|
+
description: Optional[str] = None,
|
|
742
|
+
auto_management_flag: bool = False,
|
|
743
|
+
check_password_flag: bool = False,
|
|
744
|
+
change_password_after_any_release_flag: bool = False,
|
|
745
|
+
reset_password_on_mismatch_flag: bool = False,
|
|
746
|
+
change_frequency_type: Optional[str] = None,
|
|
747
|
+
change_frequency_days: Optional[int] = None,
|
|
748
|
+
change_time: Optional[str] = None,
|
|
749
|
+
) -> dict[str, Any]:
|
|
750
|
+
"""Create a managed system for a cloud platform (AWS, Azure, GCP).
|
|
751
|
+
|
|
752
|
+
This is used for cloud platforms like Amazon (47).
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
workgroup_id: Workgroup ID to add the system to
|
|
756
|
+
platform_id: Platform ID (47=Amazon)
|
|
757
|
+
host_name: Host name / identifier for the cloud account
|
|
758
|
+
system_name: Name for the managed system
|
|
759
|
+
access_url: Access URL (e.g., https://account-id.signin.aws.amazon.com/console)
|
|
760
|
+
functional_account_id: Functional account for management
|
|
761
|
+
description: System description
|
|
762
|
+
auto_management_flag: Enable automatic password management
|
|
763
|
+
check_password_flag: Enable password checking
|
|
764
|
+
change_password_after_any_release_flag: Change password after release
|
|
765
|
+
reset_password_on_mismatch_flag: Reset on password mismatch
|
|
766
|
+
change_frequency_type: Password change frequency (first, last, xdays)
|
|
767
|
+
change_frequency_days: Days between changes (if xdays)
|
|
768
|
+
change_time: Time for password changes (HH:MM format)
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
Created managed system object
|
|
772
|
+
"""
|
|
773
|
+
data: dict[str, Any] = {
|
|
774
|
+
"EntityTypeID": 4, # Cloud
|
|
775
|
+
"PlatformID": platform_id,
|
|
776
|
+
"HostName": host_name,
|
|
777
|
+
}
|
|
778
|
+
if system_name:
|
|
779
|
+
data["SystemName"] = system_name
|
|
780
|
+
if access_url:
|
|
781
|
+
data["AccessURL"] = access_url
|
|
782
|
+
if description:
|
|
783
|
+
data["Description"] = description
|
|
784
|
+
if auto_management_flag:
|
|
785
|
+
data["AutoManagementFlag"] = auto_management_flag
|
|
786
|
+
if functional_account_id is not None:
|
|
787
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
788
|
+
if change_frequency_type:
|
|
789
|
+
data["ChangeFrequencyType"] = change_frequency_type
|
|
790
|
+
if change_frequency_days is not None:
|
|
791
|
+
data["ChangeFrequencyDays"] = change_frequency_days
|
|
792
|
+
if change_time:
|
|
793
|
+
data["ChangeTime"] = change_time
|
|
794
|
+
elif functional_account_id is not None:
|
|
795
|
+
data["FunctionalAccountID"] = functional_account_id
|
|
796
|
+
if check_password_flag is not None:
|
|
797
|
+
data["CheckPasswordFlag"] = check_password_flag
|
|
798
|
+
if change_password_after_any_release_flag is not None:
|
|
799
|
+
data["ChangePasswordAfterAnyReleaseFlag"] = change_password_after_any_release_flag
|
|
800
|
+
if reset_password_on_mismatch_flag is not None:
|
|
801
|
+
data["ResetPasswordOnMismatchFlag"] = reset_password_on_mismatch_flag
|
|
802
|
+
|
|
803
|
+
return self.post(f"/Workgroups/{workgroup_id}/ManagedSystems", json=data)
|
|
804
|
+
|
|
805
|
+
# =========================================================================
|
|
806
|
+
# Smart Rules
|
|
807
|
+
# =========================================================================
|
|
808
|
+
|
|
809
|
+
def list_smart_rules(
|
|
810
|
+
self: "PasswordSafeClient",
|
|
811
|
+
search: Optional[str] = None,
|
|
812
|
+
) -> list[dict[str, Any]]:
|
|
813
|
+
"""List all smart rules.
|
|
814
|
+
|
|
815
|
+
Args:
|
|
816
|
+
search: Optional search filter
|
|
817
|
+
|
|
818
|
+
Returns:
|
|
819
|
+
List of smart rule objects
|
|
820
|
+
"""
|
|
821
|
+
params = {}
|
|
822
|
+
if search:
|
|
823
|
+
params["search"] = search
|
|
824
|
+
|
|
825
|
+
return self.paginate("/SmartRules", params=params)
|
|
826
|
+
|
|
827
|
+
def get_smart_rule(self: "PasswordSafeClient", rule_id: int) -> dict[str, Any]:
|
|
828
|
+
"""Get a smart rule by ID.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
rule_id: Smart rule ID
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
Smart rule object
|
|
835
|
+
"""
|
|
836
|
+
return self.get(f"/SmartRules/{rule_id}")
|
|
837
|
+
|
|
838
|
+
# =========================================================================
|
|
839
|
+
# Attributes
|
|
840
|
+
# =========================================================================
|
|
841
|
+
|
|
842
|
+
def list_attributes(
|
|
843
|
+
self: "PasswordSafeClient",
|
|
844
|
+
attribute_type: Optional[str] = None,
|
|
845
|
+
) -> list[dict[str, Any]]:
|
|
846
|
+
"""List attributes.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
attribute_type: Filter by type (Asset, ManagedSystem, ManagedAccount)
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
List of attribute objects
|
|
853
|
+
"""
|
|
854
|
+
params = {}
|
|
855
|
+
if attribute_type:
|
|
856
|
+
params["type"] = attribute_type
|
|
857
|
+
|
|
858
|
+
return self.paginate("/Attributes", params=params)
|
|
859
|
+
|
|
860
|
+
def get_attribute(self: "PasswordSafeClient", attribute_id: int) -> dict[str, Any]:
|
|
861
|
+
"""Get an attribute by ID.
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
attribute_id: Attribute ID
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
Attribute object
|
|
868
|
+
"""
|
|
869
|
+
return self.get(f"/Attributes/{attribute_id}")
|