bt-cli 0.4.13__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 +94 -0
- bt_cli/data/__init__.py +0 -0
- bt_cli/data/skills/bt/SKILL.md +108 -0
- bt_cli/data/skills/entitle/SKILL.md +170 -0
- bt_cli/data/skills/epmw/SKILL.md +144 -0
- bt_cli/data/skills/pra/SKILL.md +150 -0
- bt_cli/data/skills/pws/SKILL.md +198 -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.13.dist-info/METADATA +417 -0
- bt_cli-0.4.13.dist-info/RECORD +121 -0
- bt_cli-0.4.13.dist-info/WHEEL +4 -0
- bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""CLI commands for managing assets."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ...core.output import print_api_error
|
|
12
|
+
from ..client.base import get_client
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(no_args_is_help=True, help="Manage assets in BeyondInsight")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_assets_table(assets: list[dict], title: str = "Assets") -> None:
|
|
19
|
+
"""Print assets in a formatted table."""
|
|
20
|
+
table = Table(title=title)
|
|
21
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
22
|
+
table.add_column("Name", style="green")
|
|
23
|
+
table.add_column("IP Address", style="yellow")
|
|
24
|
+
table.add_column("DNS Name", style="magenta")
|
|
25
|
+
table.add_column("Workgroup", style="blue")
|
|
26
|
+
table.add_column("OS", style="dim")
|
|
27
|
+
|
|
28
|
+
for asset in assets:
|
|
29
|
+
table.add_row(
|
|
30
|
+
str(asset.get("AssetID", "")),
|
|
31
|
+
asset.get("AssetName", "-"),
|
|
32
|
+
asset.get("IPAddress", "-"),
|
|
33
|
+
asset.get("DnsName", "-"),
|
|
34
|
+
asset.get("WorkgroupName", str(asset.get("WorkgroupID", "-"))),
|
|
35
|
+
asset.get("OperatingSystem", "-"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
console.print(table)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def print_asset_detail(asset: dict) -> None:
|
|
42
|
+
"""Print detailed asset information."""
|
|
43
|
+
display_name = asset.get("AssetName") or asset.get("DnsName") or asset.get("IPAddress") or "Unknown"
|
|
44
|
+
console.print(f"\n[bold cyan]Asset: {display_name}[/bold cyan]\n")
|
|
45
|
+
|
|
46
|
+
info_table = Table(show_header=False, box=None)
|
|
47
|
+
info_table.add_column("Field", style="dim")
|
|
48
|
+
info_table.add_column("Value")
|
|
49
|
+
|
|
50
|
+
fields = [
|
|
51
|
+
("ID", "AssetID"),
|
|
52
|
+
("Name", "AssetName"),
|
|
53
|
+
("IP Address", "IPAddress"),
|
|
54
|
+
("DNS Name", "DnsName"),
|
|
55
|
+
("Domain", "DomainName"),
|
|
56
|
+
("MAC Address", "MACAddress"),
|
|
57
|
+
("Asset Type", "AssetType"),
|
|
58
|
+
("Operating System", "OperatingSystem"),
|
|
59
|
+
("Workgroup", "WorkgroupName"),
|
|
60
|
+
("Workgroup ID", "WorkgroupID"),
|
|
61
|
+
("Created", "CreatedDate"),
|
|
62
|
+
("Last Updated", "LastUpdatedDate"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
for label, key in fields:
|
|
66
|
+
value = asset.get(key)
|
|
67
|
+
if value is not None:
|
|
68
|
+
info_table.add_row(label, str(value))
|
|
69
|
+
|
|
70
|
+
console.print(info_table)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command("list")
|
|
74
|
+
def list_assets(
|
|
75
|
+
workgroup: Optional[int] = typer.Option(None, "--workgroup", "-w", help="Filter by workgroup ID"),
|
|
76
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Maximum results (default: 50)"),
|
|
77
|
+
fetch_all: bool = typer.Option(False, "--all", help="Fetch all results (may be slow)"),
|
|
78
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
79
|
+
) -> None:
|
|
80
|
+
"""List assets (by workgroup or all).
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
bt pws assets list # First 50 assets
|
|
84
|
+
bt pws assets list --all # All assets
|
|
85
|
+
bt pws assets list -w 3 # Assets in workgroup 3
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
with get_client() as client:
|
|
89
|
+
client.authenticate()
|
|
90
|
+
assets = client.list_assets(workgroup_id=workgroup, limit=None if fetch_all else limit)
|
|
91
|
+
|
|
92
|
+
if output == "json":
|
|
93
|
+
console.print_json(json.dumps(assets, default=str))
|
|
94
|
+
else:
|
|
95
|
+
if assets:
|
|
96
|
+
print_assets_table(assets)
|
|
97
|
+
if not fetch_all and len(assets) == limit:
|
|
98
|
+
console.print(f"[dim]Showing {len(assets)} results. Use --all to fetch all results.[/dim]")
|
|
99
|
+
else:
|
|
100
|
+
console.print("[yellow]No assets found.[/yellow]")
|
|
101
|
+
|
|
102
|
+
except httpx.HTTPStatusError as e:
|
|
103
|
+
print_api_error(e, "manage assets")
|
|
104
|
+
raise typer.Exit(1)
|
|
105
|
+
except httpx.RequestError as e:
|
|
106
|
+
print_api_error(e, "manage assets")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print_api_error(e, "manage assets")
|
|
110
|
+
raise typer.Exit(1)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.command("search")
|
|
114
|
+
def search_assets(
|
|
115
|
+
term: str = typer.Argument(..., help="Search term"),
|
|
116
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Maximum results (default: 50)"),
|
|
117
|
+
fetch_all: bool = typer.Option(False, "--all", help="Fetch all results (may be slow)"),
|
|
118
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Search for assets."""
|
|
121
|
+
try:
|
|
122
|
+
with get_client() as client:
|
|
123
|
+
client.authenticate()
|
|
124
|
+
assets = client.search_assets(search_term=term, limit=None if fetch_all else limit)
|
|
125
|
+
|
|
126
|
+
if output == "json":
|
|
127
|
+
console.print_json(json.dumps(assets, default=str))
|
|
128
|
+
else:
|
|
129
|
+
if assets:
|
|
130
|
+
print_assets_table(assets)
|
|
131
|
+
if not fetch_all and len(assets) == limit:
|
|
132
|
+
console.print(f"[dim]Showing {len(assets)} results. Use --all to fetch all results.[/dim]")
|
|
133
|
+
else:
|
|
134
|
+
console.print("[yellow]No assets found.[/yellow]")
|
|
135
|
+
|
|
136
|
+
except httpx.HTTPStatusError as e:
|
|
137
|
+
print_api_error(e, "manage assets")
|
|
138
|
+
raise typer.Exit(1)
|
|
139
|
+
except httpx.RequestError as e:
|
|
140
|
+
print_api_error(e, "manage assets")
|
|
141
|
+
raise typer.Exit(1)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print_api_error(e, "manage assets")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.command("get")
|
|
148
|
+
def get_asset(
|
|
149
|
+
asset_id: int = typer.Argument(..., help="Asset ID"),
|
|
150
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Get an asset by ID."""
|
|
153
|
+
try:
|
|
154
|
+
with get_client() as client:
|
|
155
|
+
client.authenticate()
|
|
156
|
+
asset = client.get_asset(asset_id)
|
|
157
|
+
|
|
158
|
+
if output == "json":
|
|
159
|
+
console.print_json(json.dumps(asset, default=str))
|
|
160
|
+
else:
|
|
161
|
+
print_asset_detail(asset)
|
|
162
|
+
|
|
163
|
+
except httpx.HTTPStatusError as e:
|
|
164
|
+
print_api_error(e, "manage assets")
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
except httpx.RequestError as e:
|
|
167
|
+
print_api_error(e, "manage assets")
|
|
168
|
+
raise typer.Exit(1)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print_api_error(e, "manage assets")
|
|
171
|
+
raise typer.Exit(1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@app.command("create")
|
|
175
|
+
def create_asset(
|
|
176
|
+
workgroup_id: int = typer.Option(..., "--workgroup", "-w", help="Workgroup ID (required)"),
|
|
177
|
+
ip_address: str = typer.Option(..., "--ip", "-i", help="IP address"),
|
|
178
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Asset name"),
|
|
179
|
+
dns_name: Optional[str] = typer.Option(None, "--dns", help="DNS name"),
|
|
180
|
+
domain: Optional[str] = typer.Option(None, "--domain", "-d", help="Domain name"),
|
|
181
|
+
mac_address: Optional[str] = typer.Option(None, "--mac", help="MAC address"),
|
|
182
|
+
asset_type: Optional[str] = typer.Option(None, "--type", "-t", help="Asset type"),
|
|
183
|
+
description: Optional[str] = typer.Option(None, "--description", help="Description"),
|
|
184
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Create a new asset in a workgroup."""
|
|
187
|
+
try:
|
|
188
|
+
with get_client() as client:
|
|
189
|
+
client.authenticate()
|
|
190
|
+
asset = client.create_asset(
|
|
191
|
+
workgroup_id=workgroup_id,
|
|
192
|
+
ip_address=ip_address,
|
|
193
|
+
asset_name=name,
|
|
194
|
+
dns_name=dns_name,
|
|
195
|
+
domain_name=domain,
|
|
196
|
+
mac_address=mac_address,
|
|
197
|
+
asset_type=asset_type,
|
|
198
|
+
description=description,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if output == "json":
|
|
202
|
+
console.print_json(json.dumps(asset, default=str))
|
|
203
|
+
else:
|
|
204
|
+
display_name = asset.get("AssetName") or asset.get("IPAddress") or "Unknown"
|
|
205
|
+
console.print(f"[green]Created asset:[/green] {display_name}")
|
|
206
|
+
console.print(f" ID: {asset.get('AssetID', 'N/A')}")
|
|
207
|
+
|
|
208
|
+
except httpx.HTTPStatusError as e:
|
|
209
|
+
print_api_error(e, "manage assets")
|
|
210
|
+
raise typer.Exit(1)
|
|
211
|
+
except httpx.RequestError as e:
|
|
212
|
+
print_api_error(e, "manage assets")
|
|
213
|
+
raise typer.Exit(1)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print_api_error(e, "manage assets")
|
|
216
|
+
raise typer.Exit(1)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@app.command("update")
|
|
220
|
+
def update_asset(
|
|
221
|
+
asset_id: int = typer.Argument(..., help="Asset ID to update"),
|
|
222
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="New asset name"),
|
|
223
|
+
ip_address: Optional[str] = typer.Option(None, "--ip", "-i", help="New IP address"),
|
|
224
|
+
dns_name: Optional[str] = typer.Option(None, "--dns", help="New DNS name"),
|
|
225
|
+
domain: Optional[str] = typer.Option(None, "--domain", "-d", help="New domain name"),
|
|
226
|
+
mac_address: Optional[str] = typer.Option(None, "--mac", help="New MAC address"),
|
|
227
|
+
asset_type: Optional[str] = typer.Option(None, "--type", "-t", help="New asset type"),
|
|
228
|
+
description: Optional[str] = typer.Option(None, "--description", help="New description"),
|
|
229
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Update an existing asset.
|
|
232
|
+
|
|
233
|
+
Examples:
|
|
234
|
+
bt pws assets update 123 --dns "server.internal"
|
|
235
|
+
bt pws assets update 123 --name "new-name" --description "Updated"
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
kwargs = {}
|
|
239
|
+
if name is not None:
|
|
240
|
+
kwargs["asset_name"] = name
|
|
241
|
+
if ip_address is not None:
|
|
242
|
+
kwargs["ip_address"] = ip_address
|
|
243
|
+
if dns_name is not None:
|
|
244
|
+
kwargs["dns_name"] = dns_name
|
|
245
|
+
if domain is not None:
|
|
246
|
+
kwargs["domain_name"] = domain
|
|
247
|
+
if mac_address is not None:
|
|
248
|
+
kwargs["mac_address"] = mac_address
|
|
249
|
+
if asset_type is not None:
|
|
250
|
+
kwargs["asset_type"] = asset_type
|
|
251
|
+
if description is not None:
|
|
252
|
+
kwargs["description"] = description
|
|
253
|
+
|
|
254
|
+
if not kwargs:
|
|
255
|
+
console.print("[yellow]No updates specified.[/yellow]")
|
|
256
|
+
raise typer.Exit(0)
|
|
257
|
+
|
|
258
|
+
with get_client() as client:
|
|
259
|
+
client.authenticate()
|
|
260
|
+
asset = client.update_asset(asset_id, **kwargs)
|
|
261
|
+
|
|
262
|
+
if output == "json":
|
|
263
|
+
console.print_json(json.dumps(asset, default=str))
|
|
264
|
+
else:
|
|
265
|
+
display_name = asset.get("AssetName") or asset.get("IPAddress") or "Unknown"
|
|
266
|
+
console.print(f"[green]Updated asset:[/green] {display_name}")
|
|
267
|
+
console.print(f" ID: {asset_id}")
|
|
268
|
+
|
|
269
|
+
except httpx.HTTPStatusError as e:
|
|
270
|
+
print_api_error(e, "update asset")
|
|
271
|
+
raise typer.Exit(1)
|
|
272
|
+
except httpx.RequestError as e:
|
|
273
|
+
print_api_error(e, "update asset")
|
|
274
|
+
raise typer.Exit(1)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
print_api_error(e, "update asset")
|
|
277
|
+
raise typer.Exit(1)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@app.command("delete")
|
|
281
|
+
def delete_asset(
|
|
282
|
+
asset_id: int = typer.Argument(..., help="Asset ID to delete"),
|
|
283
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
284
|
+
) -> None:
|
|
285
|
+
"""Delete an asset."""
|
|
286
|
+
try:
|
|
287
|
+
with get_client() as client:
|
|
288
|
+
client.authenticate()
|
|
289
|
+
|
|
290
|
+
if not force:
|
|
291
|
+
asset = client.get_asset(asset_id)
|
|
292
|
+
name = asset.get("AssetName") or asset.get("IPAddress") or "Unknown"
|
|
293
|
+
confirm = typer.confirm(
|
|
294
|
+
f"Are you sure you want to delete asset '{name}' (ID: {asset_id})?"
|
|
295
|
+
)
|
|
296
|
+
if not confirm:
|
|
297
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
298
|
+
raise typer.Exit(0)
|
|
299
|
+
|
|
300
|
+
client.delete_asset(asset_id)
|
|
301
|
+
console.print(f"[green]Deleted asset ID: {asset_id}[/green]")
|
|
302
|
+
|
|
303
|
+
except httpx.HTTPStatusError as e:
|
|
304
|
+
print_api_error(e, "manage assets")
|
|
305
|
+
raise typer.Exit(1)
|
|
306
|
+
except httpx.RequestError as e:
|
|
307
|
+
print_api_error(e, "manage assets")
|
|
308
|
+
raise typer.Exit(1)
|
|
309
|
+
except Exception as e:
|
|
310
|
+
print_api_error(e, "manage assets")
|
|
311
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""CLI commands for authentication."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
from ...core.output import print_error, print_api_error
|
|
9
|
+
from ..client.base import get_client
|
|
10
|
+
from ..config import get_config
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(no_args_is_help=True, help="Authentication commands")
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("login")
|
|
17
|
+
def login() -> None:
|
|
18
|
+
"""Authenticate and establish a session with Password Safe.
|
|
19
|
+
|
|
20
|
+
This tests your credentials and establishes a session.
|
|
21
|
+
The session is automatically managed per command, but this
|
|
22
|
+
command is useful for verifying your configuration.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
pws auth login
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
config = get_config()
|
|
29
|
+
console.print(f"[dim]API URL:[/dim] {config.api_url}")
|
|
30
|
+
console.print(f"[dim]Auth Method:[/dim] {config.auth_method}")
|
|
31
|
+
|
|
32
|
+
with get_client() as client:
|
|
33
|
+
response = client.authenticate()
|
|
34
|
+
|
|
35
|
+
user_name = response.get("UserName", "Unknown")
|
|
36
|
+
user_id = response.get("UserId", "N/A")
|
|
37
|
+
|
|
38
|
+
console.print(Panel(
|
|
39
|
+
f"[green]Authentication successful![/green]\n\n"
|
|
40
|
+
f"User: [bold]{user_name}[/bold]\n"
|
|
41
|
+
f"User ID: {user_id}",
|
|
42
|
+
title="Logged In",
|
|
43
|
+
))
|
|
44
|
+
|
|
45
|
+
except ValueError as e:
|
|
46
|
+
print_error(f"Configuration error: {e}")
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
except httpx.HTTPStatusError as e:
|
|
49
|
+
print_api_error(e, "authenticate")
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
except httpx.RequestError as e:
|
|
52
|
+
print_api_error(e, "authenticate")
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print_api_error(e, "authenticate")
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command("logout")
|
|
60
|
+
def logout() -> None:
|
|
61
|
+
"""Sign out and end the current session.
|
|
62
|
+
|
|
63
|
+
Note: Sessions are automatically cleaned up, but this
|
|
64
|
+
command can be used to explicitly end a session.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
pws auth logout
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
with get_client() as client:
|
|
71
|
+
client.authenticate()
|
|
72
|
+
client.sign_out()
|
|
73
|
+
console.print("[green]Signed out successfully.[/green]")
|
|
74
|
+
|
|
75
|
+
except httpx.HTTPStatusError as e:
|
|
76
|
+
print_api_error(e, "sign out")
|
|
77
|
+
raise typer.Exit(1)
|
|
78
|
+
except httpx.RequestError as e:
|
|
79
|
+
print_api_error(e, "sign out")
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print_api_error(e, "sign out")
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command("status")
|
|
87
|
+
def status() -> None:
|
|
88
|
+
"""Show current authentication configuration and status.
|
|
89
|
+
|
|
90
|
+
Displays the configured API URL and authentication method
|
|
91
|
+
without actually authenticating.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
pws auth status
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
config = get_config()
|
|
98
|
+
|
|
99
|
+
console.print("\n[bold cyan]Password Safe Configuration[/bold cyan]\n")
|
|
100
|
+
console.print(f" API URL: [green]{config.api_url}[/green]")
|
|
101
|
+
console.print(f" Auth Method: [yellow]{config.auth_method}[/yellow]")
|
|
102
|
+
|
|
103
|
+
if config.auth_method == "api_key":
|
|
104
|
+
masked_key = config.api_key[:8] + "..." if config.api_key and len(config.api_key) > 8 else "***"
|
|
105
|
+
console.print(f" API Key: [dim]{masked_key}[/dim]")
|
|
106
|
+
if config.run_as:
|
|
107
|
+
console.print(f" Run As: [blue]{config.run_as}[/blue]")
|
|
108
|
+
else:
|
|
109
|
+
masked_id = config.client_id[:8] + "..." if config.client_id and len(config.client_id) > 8 else "***"
|
|
110
|
+
console.print(f" Client ID: [dim]{masked_id}[/dim]")
|
|
111
|
+
|
|
112
|
+
console.print(f" SSL Verify: {'Yes' if config.verify_ssl else 'No'}")
|
|
113
|
+
console.print(f" Timeout: {config.timeout}s")
|
|
114
|
+
console.print(f" API Version: {config.api_version}")
|
|
115
|
+
console.print()
|
|
116
|
+
|
|
117
|
+
except ValueError as e:
|
|
118
|
+
print_error(f"Configuration error: {e}")
|
|
119
|
+
console.print("\n[dim]Set environment variables:[/dim]")
|
|
120
|
+
console.print(" PWS_API_URL=https://your-server/BeyondTrust/api/public/v3")
|
|
121
|
+
console.print(" PWS_API_KEY=your-api-key")
|
|
122
|
+
console.print(" [dim]or[/dim]")
|
|
123
|
+
console.print(" PWS_CLIENT_ID=your-client-id")
|
|
124
|
+
console.print(" PWS_CLIENT_SECRET=your-client-secret")
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command("test")
|
|
129
|
+
def test_connection() -> None:
|
|
130
|
+
"""Test the API connection and authentication.
|
|
131
|
+
|
|
132
|
+
Performs a full authentication and makes a simple API call
|
|
133
|
+
to verify connectivity.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
pws auth test
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
config = get_config()
|
|
140
|
+
console.print(f"[dim]Testing connection to:[/dim] {config.api_url}")
|
|
141
|
+
|
|
142
|
+
with get_client() as client:
|
|
143
|
+
# Authenticate
|
|
144
|
+
console.print("[dim]Authenticating...[/dim]")
|
|
145
|
+
response = client.authenticate()
|
|
146
|
+
console.print("[green]✓[/green] Authentication successful")
|
|
147
|
+
|
|
148
|
+
# Try a simple API call
|
|
149
|
+
console.print("[dim]Testing API call...[/dim]")
|
|
150
|
+
platforms = client.list_platforms()
|
|
151
|
+
console.print(f"[green]✓[/green] API call successful ({len(platforms)} platforms found)")
|
|
152
|
+
|
|
153
|
+
console.print("\n[bold green]All tests passed![/bold green]")
|
|
154
|
+
|
|
155
|
+
except ValueError as e:
|
|
156
|
+
print_error(f"Configuration error: {e}")
|
|
157
|
+
raise typer.Exit(1)
|
|
158
|
+
except httpx.HTTPStatusError as e:
|
|
159
|
+
print_api_error(e, "test connection")
|
|
160
|
+
raise typer.Exit(1)
|
|
161
|
+
except httpx.RequestError as e:
|
|
162
|
+
print_api_error(e, "test connection")
|
|
163
|
+
raise typer.Exit(1)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print_api_error(e, "test connection")
|
|
166
|
+
raise typer.Exit(1)
|