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,123 @@
|
|
|
1
|
+
"""User commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage users")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_users(
|
|
16
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search by email or name"),
|
|
17
|
+
limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
|
|
18
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
19
|
+
) -> None:
|
|
20
|
+
"""List users in Entitle.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
bt entitle users list
|
|
24
|
+
bt entitle users list -s "john"
|
|
25
|
+
bt entitle users list -l 50 -o json
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
with get_client() as client:
|
|
29
|
+
data = client.list_users(search=search, limit=limit)
|
|
30
|
+
|
|
31
|
+
if output == "json":
|
|
32
|
+
print_json(data)
|
|
33
|
+
else:
|
|
34
|
+
# Format for display - combine givenName/familyName, format date
|
|
35
|
+
display_data = []
|
|
36
|
+
for user in data:
|
|
37
|
+
given = user.get("givenName", "") or ""
|
|
38
|
+
family = user.get("familyName", "") or ""
|
|
39
|
+
name = f"{given} {family}".strip() or "-"
|
|
40
|
+
created = user.get("createdAt", "")
|
|
41
|
+
if created:
|
|
42
|
+
created = created[:10] # Just the date part
|
|
43
|
+
display_data.append({
|
|
44
|
+
"id": user.get("id"),
|
|
45
|
+
"email": user.get("email"),
|
|
46
|
+
"name": name,
|
|
47
|
+
"created": created,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
print_table(
|
|
51
|
+
display_data,
|
|
52
|
+
[("ID", "id"), ("Email", "email"), ("Name", "name"), ("Created", "created")],
|
|
53
|
+
title="Users",
|
|
54
|
+
)
|
|
55
|
+
except httpx.HTTPStatusError as e:
|
|
56
|
+
print_api_error(e, "list users")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
except httpx.RequestError as e:
|
|
59
|
+
print_api_error(e, "list users")
|
|
60
|
+
raise typer.Exit(1)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print_api_error(e, "list users")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command("get")
|
|
67
|
+
def get_user(
|
|
68
|
+
search: str = typer.Argument(..., help="User email or name to search for"),
|
|
69
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Find and display a user by email or name.
|
|
72
|
+
|
|
73
|
+
Note: The Entitle API doesn't support getting users by ID directly.
|
|
74
|
+
This command searches for users matching the provided email or name.
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
bt entitle users get john@example.com
|
|
78
|
+
bt entitle users get "John Smith"
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
with get_client() as client:
|
|
82
|
+
data = client.list_users(search=search, limit=10)
|
|
83
|
+
|
|
84
|
+
if not data:
|
|
85
|
+
console.print(f"[yellow]No users found matching '{search}'[/yellow]")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
if output == "json":
|
|
89
|
+
print_json(data)
|
|
90
|
+
else:
|
|
91
|
+
# Show matching users
|
|
92
|
+
display_data = []
|
|
93
|
+
for user in data:
|
|
94
|
+
given = user.get("givenName", "") or ""
|
|
95
|
+
family = user.get("familyName", "") or ""
|
|
96
|
+
name = f"{given} {family}".strip() or "-"
|
|
97
|
+
created = user.get("createdAt", "")
|
|
98
|
+
if created:
|
|
99
|
+
created = created[:10]
|
|
100
|
+
display_data.append({
|
|
101
|
+
"id": user.get("id"),
|
|
102
|
+
"email": user.get("email"),
|
|
103
|
+
"name": name,
|
|
104
|
+
"created": created,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
print_table(
|
|
108
|
+
display_data,
|
|
109
|
+
[("ID", "id"), ("Email", "email"), ("Name", "name"), ("Created", "created")],
|
|
110
|
+
title=f"Users matching '{search}'",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
except httpx.HTTPStatusError as e:
|
|
114
|
+
print_api_error(e, "find user")
|
|
115
|
+
raise typer.Exit(1)
|
|
116
|
+
except httpx.RequestError as e:
|
|
117
|
+
print_api_error(e, "find user")
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
except typer.Exit:
|
|
120
|
+
raise
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print_api_error(e, "find user")
|
|
123
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Workflow commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage workflows")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _summarize_workflow(workflow: dict) -> str:
|
|
15
|
+
"""Generate a short description of a workflow based on its rules."""
|
|
16
|
+
rules = workflow.get("rules", [])
|
|
17
|
+
if not rules:
|
|
18
|
+
return "No rules defined"
|
|
19
|
+
|
|
20
|
+
# Check first rule's approval type
|
|
21
|
+
first_rule = rules[0]
|
|
22
|
+
approval_flow = first_rule.get("approvalFlow", {})
|
|
23
|
+
steps = approval_flow.get("steps", [])
|
|
24
|
+
|
|
25
|
+
if not steps:
|
|
26
|
+
return "No approval steps"
|
|
27
|
+
|
|
28
|
+
# Check if auto-approve
|
|
29
|
+
first_step = steps[0]
|
|
30
|
+
entities = first_step.get("approvalEntities", [])
|
|
31
|
+
if entities and entities[0].get("type") == "Automatic":
|
|
32
|
+
duration = first_rule.get("underDuration", 0)
|
|
33
|
+
hours = duration // 3600
|
|
34
|
+
return f"Auto-approve (≤{hours}h)"
|
|
35
|
+
|
|
36
|
+
# Count steps and approvers
|
|
37
|
+
if len(steps) == 1:
|
|
38
|
+
return f"Single approver ({len(entities)} options)"
|
|
39
|
+
else:
|
|
40
|
+
return f"Multi-step ({len(steps)} steps)"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("list")
|
|
44
|
+
def list_workflows(
|
|
45
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
46
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
47
|
+
) -> None:
|
|
48
|
+
"""List all workflows with details."""
|
|
49
|
+
try:
|
|
50
|
+
with get_client() as client:
|
|
51
|
+
# List API only returns id/name, fetch full details
|
|
52
|
+
workflows_list = client.list_workflows(search=search)
|
|
53
|
+
|
|
54
|
+
data = []
|
|
55
|
+
for wf in workflows_list:
|
|
56
|
+
wf_id = wf.get("id")
|
|
57
|
+
if wf_id:
|
|
58
|
+
response = client.get_workflow(wf_id)
|
|
59
|
+
full_wf = response.get("result", response)
|
|
60
|
+
data.append(full_wf)
|
|
61
|
+
|
|
62
|
+
if output == "json":
|
|
63
|
+
print_json(data)
|
|
64
|
+
else:
|
|
65
|
+
# Format for display
|
|
66
|
+
display_data = []
|
|
67
|
+
for wf in data:
|
|
68
|
+
rules = wf.get("rules", [])
|
|
69
|
+
display_data.append({
|
|
70
|
+
"id": wf.get("id"),
|
|
71
|
+
"name": wf.get("name"),
|
|
72
|
+
"type": _summarize_workflow(wf),
|
|
73
|
+
"rules": len(rules),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
print_table(
|
|
77
|
+
display_data,
|
|
78
|
+
[("ID", "id"), ("Name", "name"), ("Type", "type"), ("Rules", "rules")],
|
|
79
|
+
title="Workflows",
|
|
80
|
+
)
|
|
81
|
+
except httpx.HTTPStatusError as e:
|
|
82
|
+
print_api_error(e, "list workflows")
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
except httpx.RequestError as e:
|
|
85
|
+
print_api_error(e, "list workflows")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print_api_error(e, "list workflows")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command("get")
|
|
93
|
+
def get_workflow(
|
|
94
|
+
workflow_id: str = typer.Argument(..., help="Workflow ID"),
|
|
95
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Get a workflow by ID with full details."""
|
|
98
|
+
try:
|
|
99
|
+
with get_client() as client:
|
|
100
|
+
response = client.get_workflow(workflow_id)
|
|
101
|
+
|
|
102
|
+
workflow = response.get("result", response)
|
|
103
|
+
|
|
104
|
+
if output == "json":
|
|
105
|
+
print_json(response)
|
|
106
|
+
else:
|
|
107
|
+
from rich.panel import Panel
|
|
108
|
+
from rich.table import Table
|
|
109
|
+
|
|
110
|
+
name = workflow.get("name", "")
|
|
111
|
+
wf_id = workflow.get("id", "")
|
|
112
|
+
rules = workflow.get("rules", [])
|
|
113
|
+
|
|
114
|
+
console.print(Panel(
|
|
115
|
+
f"[bold]{name}[/bold]\n\n"
|
|
116
|
+
f"[dim]Type:[/dim] {_summarize_workflow(workflow)}\n"
|
|
117
|
+
f"[dim]Rules:[/dim] {len(rules)}",
|
|
118
|
+
title="Workflow Details",
|
|
119
|
+
subtitle=f"ID: {wf_id}",
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
# Rules table
|
|
123
|
+
if rules:
|
|
124
|
+
for i, rule in enumerate(rules, 1):
|
|
125
|
+
duration = rule.get("underDuration", 0)
|
|
126
|
+
hours = duration // 3600 if duration else 0
|
|
127
|
+
|
|
128
|
+
console.print(f"\n[bold cyan]Rule {i}[/bold cyan]" +
|
|
129
|
+
(f" (≤{hours}h)" if duration else ""))
|
|
130
|
+
|
|
131
|
+
approval_flow = rule.get("approvalFlow", {})
|
|
132
|
+
steps = approval_flow.get("steps", [])
|
|
133
|
+
|
|
134
|
+
if steps:
|
|
135
|
+
table = Table(show_header=True, header_style="bold")
|
|
136
|
+
table.add_column("Step", style="dim", width=6)
|
|
137
|
+
table.add_column("Approvers", style="green")
|
|
138
|
+
table.add_column("Notified", style="yellow")
|
|
139
|
+
table.add_column("Operator", style="cyan")
|
|
140
|
+
|
|
141
|
+
for j, step in enumerate(steps, 1):
|
|
142
|
+
approvers = step.get("approvalEntities", [])
|
|
143
|
+
notified = step.get("notifiedEntities", [])
|
|
144
|
+
operator = step.get("operator", "")
|
|
145
|
+
|
|
146
|
+
# Format approvers
|
|
147
|
+
approver_list = []
|
|
148
|
+
for a in approvers:
|
|
149
|
+
a_type = a.get("type", "")
|
|
150
|
+
entity = a.get("entity")
|
|
151
|
+
if entity:
|
|
152
|
+
approver_list.append(f"{a_type}: {entity.get('name', entity.get('email', ''))}")
|
|
153
|
+
else:
|
|
154
|
+
approver_list.append(a_type)
|
|
155
|
+
|
|
156
|
+
# Format notified
|
|
157
|
+
notified_list = []
|
|
158
|
+
for n in notified:
|
|
159
|
+
n_type = n.get("type", "")
|
|
160
|
+
entity = n.get("entity")
|
|
161
|
+
if entity:
|
|
162
|
+
notified_list.append(f"{n_type}: {entity.get('name', entity.get('email', ''))}")
|
|
163
|
+
else:
|
|
164
|
+
notified_list.append(n_type)
|
|
165
|
+
|
|
166
|
+
table.add_row(
|
|
167
|
+
str(j),
|
|
168
|
+
"\n".join(approver_list) or "-",
|
|
169
|
+
"\n".join(notified_list) or "-",
|
|
170
|
+
operator or "-",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
console.print(table)
|
|
174
|
+
else:
|
|
175
|
+
console.print("[yellow]No approval steps defined[/yellow]")
|
|
176
|
+
else:
|
|
177
|
+
console.print("\n[yellow]No rules defined[/yellow]")
|
|
178
|
+
|
|
179
|
+
except httpx.HTTPStatusError as e:
|
|
180
|
+
print_api_error(e, "get workflow")
|
|
181
|
+
raise typer.Exit(1)
|
|
182
|
+
except httpx.RequestError as e:
|
|
183
|
+
print_api_error(e, "get workflow")
|
|
184
|
+
raise typer.Exit(1)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print_api_error(e, "get workflow")
|
|
187
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Pydantic models for Entitle API responses."""
|
|
2
|
+
|
|
3
|
+
from .common import (
|
|
4
|
+
EntityRef,
|
|
5
|
+
UserRef,
|
|
6
|
+
PaginatedResponse,
|
|
7
|
+
)
|
|
8
|
+
from .integration import Integration
|
|
9
|
+
from .resource import Resource
|
|
10
|
+
from .role import Role
|
|
11
|
+
from .bundle import Bundle
|
|
12
|
+
from .workflow import Workflow, WorkflowRule, ApprovalFlow
|
|
13
|
+
from .user import User
|
|
14
|
+
from .permission import Permission
|
|
15
|
+
from .policy import Policy
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"EntityRef",
|
|
19
|
+
"UserRef",
|
|
20
|
+
"PaginatedResponse",
|
|
21
|
+
"Integration",
|
|
22
|
+
"Resource",
|
|
23
|
+
"Role",
|
|
24
|
+
"Bundle",
|
|
25
|
+
"Workflow",
|
|
26
|
+
"WorkflowRule",
|
|
27
|
+
"ApprovalFlow",
|
|
28
|
+
"User",
|
|
29
|
+
"Permission",
|
|
30
|
+
"Policy",
|
|
31
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Bundle model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Bundle(BaseModel):
|
|
10
|
+
"""Entitle bundle (cross-application permission package)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
description: Optional[str] = None
|
|
17
|
+
category: Optional[str] = None
|
|
18
|
+
tags: Optional[list[str]] = None
|
|
19
|
+
workflow: Optional[EntityRef] = None
|
|
20
|
+
roles: Optional[list[EntityRef]] = None
|
|
21
|
+
allowed_durations: Optional[list[int]] = None
|
|
22
|
+
created_at: Optional[str] = None
|
|
23
|
+
updated_at: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_api(cls, data: dict[str, Any]) -> "Bundle":
|
|
27
|
+
"""Create from API response."""
|
|
28
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Common models used across the API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EntityRef(BaseModel):
|
|
8
|
+
"""Reference to an entity by ID."""
|
|
9
|
+
|
|
10
|
+
model_config = ConfigDict(extra="allow")
|
|
11
|
+
|
|
12
|
+
id: str
|
|
13
|
+
name: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UserRef(BaseModel):
|
|
17
|
+
"""Reference to a user."""
|
|
18
|
+
|
|
19
|
+
model_config = ConfigDict(extra="allow")
|
|
20
|
+
|
|
21
|
+
id: Optional[str] = None
|
|
22
|
+
email: Optional[str] = None
|
|
23
|
+
name: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PaginatedResponse(BaseModel, Generic[T]):
|
|
30
|
+
"""Paginated API response."""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(extra="allow")
|
|
33
|
+
|
|
34
|
+
data: list[T]
|
|
35
|
+
total: Optional[int] = None
|
|
36
|
+
offset: Optional[int] = None
|
|
37
|
+
limit: Optional[int] = None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Integration model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef, UserRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Integration(BaseModel):
|
|
10
|
+
"""Entitle integration (connection to external application)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
application: Optional[str] = None
|
|
17
|
+
owner: Optional[UserRef] = None
|
|
18
|
+
workflow: Optional[EntityRef] = None
|
|
19
|
+
allowed_durations: Optional[list[int]] = None
|
|
20
|
+
allow_creating_accounts: Optional[bool] = None
|
|
21
|
+
allow_changing_account_permissions: Optional[bool] = None
|
|
22
|
+
readonly: Optional[bool] = None
|
|
23
|
+
maintainers: Optional[list[UserRef]] = None
|
|
24
|
+
created_at: Optional[str] = None
|
|
25
|
+
updated_at: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_api(cls, data: dict[str, Any]) -> "Integration":
|
|
29
|
+
"""Create from API response."""
|
|
30
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Permission model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef, UserRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Permission(BaseModel):
|
|
10
|
+
"""Entitle permission (granted access)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
user: Optional[UserRef] = None
|
|
16
|
+
role: Optional[EntityRef] = None
|
|
17
|
+
resource: Optional[EntityRef] = None
|
|
18
|
+
integration: Optional[EntityRef] = None
|
|
19
|
+
status: Optional[str] = None
|
|
20
|
+
granted_at: Optional[str] = None
|
|
21
|
+
expires_at: Optional[str] = None
|
|
22
|
+
created_at: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_api(cls, data: dict[str, Any]) -> "Permission":
|
|
26
|
+
"""Create from API response."""
|
|
27
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Policy model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Policy(BaseModel):
|
|
10
|
+
"""Entitle policy (birthright permissions based on group membership)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
in_groups: Optional[list[EntityRef]] = None
|
|
16
|
+
roles: Optional[list[EntityRef]] = None
|
|
17
|
+
bundles: Optional[list[EntityRef]] = None
|
|
18
|
+
sort_order: Optional[int] = None
|
|
19
|
+
created_at: Optional[str] = None
|
|
20
|
+
updated_at: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_api(cls, data: dict[str, Any]) -> "Policy":
|
|
24
|
+
"""Create from API response."""
|
|
25
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Resource model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef, UserRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Resource(BaseModel):
|
|
10
|
+
"""Entitle resource (target system or asset)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
integration: Optional[EntityRef] = None
|
|
17
|
+
requestable: Optional[bool] = None
|
|
18
|
+
owner: Optional[UserRef] = None
|
|
19
|
+
workflow: Optional[EntityRef] = None
|
|
20
|
+
allowed_durations: Optional[list[int]] = None
|
|
21
|
+
maintainers: Optional[list[UserRef]] = None
|
|
22
|
+
user_defined_tags: Optional[list[str]] = None
|
|
23
|
+
created_at: Optional[str] = None
|
|
24
|
+
updated_at: Optional[str] = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_api(cls, data: dict[str, Any]) -> "Resource":
|
|
28
|
+
"""Create from API response."""
|
|
29
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Role model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Role(BaseModel):
|
|
10
|
+
"""Entitle role (permission set within a resource)."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
resource: Optional[EntityRef] = None
|
|
17
|
+
requestable: Optional[bool] = None
|
|
18
|
+
allowed_durations: Optional[list[int]] = None
|
|
19
|
+
workflow: Optional[EntityRef] = None
|
|
20
|
+
prerequisite_permissions: Optional[list[EntityRef]] = None
|
|
21
|
+
virtualized_role: Optional[bool] = None
|
|
22
|
+
created_at: Optional[str] = None
|
|
23
|
+
updated_at: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_api(cls, data: dict[str, Any]) -> "Role":
|
|
27
|
+
"""Create from API response."""
|
|
28
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""User model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class User(BaseModel):
|
|
8
|
+
"""Entitle user."""
|
|
9
|
+
|
|
10
|
+
model_config = ConfigDict(extra="allow")
|
|
11
|
+
|
|
12
|
+
id: str
|
|
13
|
+
email: Optional[str] = None
|
|
14
|
+
name: Optional[str] = None
|
|
15
|
+
first_name: Optional[str] = None
|
|
16
|
+
last_name: Optional[str] = None
|
|
17
|
+
status: Optional[str] = None
|
|
18
|
+
created_at: Optional[str] = None
|
|
19
|
+
updated_at: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_api(cls, data: dict[str, Any]) -> "User":
|
|
23
|
+
"""Create from API response."""
|
|
24
|
+
return cls.model_validate(data)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Workflow model."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from entitle_admin.models.common import EntityRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApprovalEntity(BaseModel):
|
|
10
|
+
"""Entity that can approve requests."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="allow")
|
|
13
|
+
|
|
14
|
+
type: str # "Automatic", "User", "Group", etc.
|
|
15
|
+
id: Optional[str] = None
|
|
16
|
+
name: Optional[str] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApprovalFlow(BaseModel):
|
|
20
|
+
"""Single step in approval flow."""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(extra="allow")
|
|
23
|
+
|
|
24
|
+
approval_entities: list[ApprovalEntity] = []
|
|
25
|
+
operator: Optional[str] = None # "and" or "or"
|
|
26
|
+
sort_order: Optional[int] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WorkflowRule(BaseModel):
|
|
30
|
+
"""Rule within a workflow."""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(extra="allow")
|
|
33
|
+
|
|
34
|
+
any_schedule: Optional[bool] = None
|
|
35
|
+
schedules: Optional[list[EntityRef]] = None
|
|
36
|
+
for_groups: Optional[list[EntityRef]] = None
|
|
37
|
+
duration: Optional[int] = None # max duration in seconds
|
|
38
|
+
approval_flow: Optional[list[ApprovalFlow]] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Workflow(BaseModel):
|
|
42
|
+
"""Entitle workflow (approval process)."""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(extra="allow")
|
|
45
|
+
|
|
46
|
+
id: str
|
|
47
|
+
name: str
|
|
48
|
+
rules: Optional[list[WorkflowRule]] = None
|
|
49
|
+
created_at: Optional[str] = None
|
|
50
|
+
updated_at: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_api(cls, data: dict[str, Any]) -> "Workflow":
|
|
54
|
+
"""Create from API response."""
|
|
55
|
+
return cls.model_validate(data)
|
bt_cli/epmw/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EPM Windows CLI module."""
|