systemlink-cli 1.6.2__tar.gz → 1.6.3__tar.gz
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.
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/PKG-INFO +1 -1
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/pyproject.toml +1 -1
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/_version.py +1 -1
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/user_click.py +99 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/utils.py +1 -1
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/LICENSE +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/dff-editor/index.html +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/__init__.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/__main__.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/config.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/config_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/example_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/file_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/function_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/main.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/platform.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/profiles.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/system_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/workspace_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.6.
|
|
3
|
+
version = "1.6.3"
|
|
4
4
|
description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
|
|
5
5
|
authors = ["Fred Visser <fred.visser@emerson.com>"]
|
|
6
6
|
packages = [{ include = "slcli" }]
|
|
@@ -29,6 +29,103 @@ from .workspace_utils import get_workspace_display_name, resolve_workspace_id
|
|
|
29
29
|
|
|
30
30
|
USER_QUERY_PAGE_SIZE = 100
|
|
31
31
|
USER_JSON_DEFAULT_TAKE = 1000
|
|
32
|
+
AUTH_WILDCARD_VALUES = {"*", "*/*", "*:*"}
|
|
33
|
+
AUTH_MUTATING_VERBS = {"*", "create", "write", "update", "manage", "admin", "delete"}
|
|
34
|
+
AUTH_ADMIN_RESOURCE_KEYWORDS = {"policy", "policies", "role", "roles"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _has_global_auth_scope(value: Any) -> bool:
|
|
38
|
+
"""Return whether an auth scope value grants wildcard access."""
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
return value.strip().lower() in AUTH_WILDCARD_VALUES
|
|
41
|
+
if isinstance(value, list):
|
|
42
|
+
return any(
|
|
43
|
+
isinstance(item, str) and item.strip().lower() in AUTH_WILDCARD_VALUES for item in value
|
|
44
|
+
)
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _action_grants_auth_management(action: str) -> bool:
|
|
49
|
+
"""Return whether an action implies auth policy or role management access."""
|
|
50
|
+
normalized = action.strip().lower()
|
|
51
|
+
if normalized in AUTH_WILDCARD_VALUES:
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
action_parts = [part.strip().lower() for part in normalized.split(":") if part.strip()]
|
|
55
|
+
if len(action_parts) < 2 or action_parts[0] not in {"niauth", "auth"}:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
remainder = action_parts[1:]
|
|
59
|
+
if remainder == ["*"]:
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
if remainder[-1] not in AUTH_MUTATING_VERBS and "*" not in remainder:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
if len(remainder) == 1:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
return any(part in AUTH_ADMIN_RESOURCE_KEYWORDS for part in remainder[:-1])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _has_user_admin_access(auth_context: Dict[str, Any]) -> bool:
|
|
72
|
+
"""Return whether the current caller appears to have server-admin style access."""
|
|
73
|
+
for policy in auth_context.get("policies", []):
|
|
74
|
+
statements = policy.get("statements", [])
|
|
75
|
+
if not isinstance(statements, list):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
for statement in statements:
|
|
79
|
+
if not isinstance(statement, dict):
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
if not _has_global_auth_scope(statement.get("workspace")):
|
|
83
|
+
continue
|
|
84
|
+
if not _has_global_auth_scope(statement.get("resource")):
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
actions = statement.get("actions", [])
|
|
88
|
+
if not isinstance(actions, list):
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
for action in actions:
|
|
92
|
+
if isinstance(action, str) and _action_grants_auth_management(action):
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _try_get_current_auth_context() -> Optional[Dict[str, Any]]:
|
|
99
|
+
"""Fetch effective auth data for the current caller when available."""
|
|
100
|
+
url = f"{get_base_url()}/niauth/v1/auth"
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
auth_response = make_api_request("GET", url, payload=None, handle_errors=False)
|
|
104
|
+
auth_context = auth_response.json()
|
|
105
|
+
return auth_context if isinstance(auth_context, dict) else None
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
error_response = getattr(exc, "response", None)
|
|
108
|
+
if error_response is not None and error_response.status_code in {401, 403}:
|
|
109
|
+
handle_api_error(exc)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _ensure_user_admin_access(operation: str) -> None:
|
|
114
|
+
"""Fail fast when the caller clearly lacks admin access for user mutations."""
|
|
115
|
+
auth_context = _try_get_current_auth_context()
|
|
116
|
+
if auth_context is None:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if _has_user_admin_access(auth_context):
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
click.echo(
|
|
123
|
+
f"✗ User and service account {operation} requires server admin permissions. "
|
|
124
|
+
"The active API key does not appear to have wildcard auth role or policy access "
|
|
125
|
+
"across all workspaces.",
|
|
126
|
+
err=True,
|
|
127
|
+
)
|
|
128
|
+
sys.exit(ExitCodes.PERMISSION_DENIED)
|
|
32
129
|
|
|
33
130
|
|
|
34
131
|
def _get_policy_details(policy_id: str) -> Optional[dict]:
|
|
@@ -851,6 +948,7 @@ def register_user_commands(cli: click.Group) -> None:
|
|
|
851
948
|
from .utils import check_readonly_mode
|
|
852
949
|
|
|
853
950
|
check_readonly_mode("create a user")
|
|
951
|
+
_ensure_user_admin_access("creation")
|
|
854
952
|
|
|
855
953
|
# If user_type wasn't specified via CLI, prompt for it first
|
|
856
954
|
if user_type is None:
|
|
@@ -1060,6 +1158,7 @@ def register_user_commands(cli: click.Group) -> None:
|
|
|
1060
1158
|
from .utils import check_readonly_mode
|
|
1061
1159
|
|
|
1062
1160
|
check_readonly_mode("update a user")
|
|
1161
|
+
_ensure_user_admin_access("updates")
|
|
1063
1162
|
|
|
1064
1163
|
# First, fetch the user to check if it's a service account
|
|
1065
1164
|
get_url = f"{get_base_url()}/niuser/v1/users/{user_id}"
|
|
@@ -75,7 +75,7 @@ def handle_api_error(exc: Exception) -> None:
|
|
|
75
75
|
if "not found" in error_msg:
|
|
76
76
|
click.echo(f"✗ Resource not found: {exc}", err=True)
|
|
77
77
|
sys.exit(ExitCodes.NOT_FOUND)
|
|
78
|
-
elif "permission" in error_msg or "unauthorized" in error_msg:
|
|
78
|
+
elif "permission" in error_msg or "unauthorized" in error_msg or "forbidden" in error_msg:
|
|
79
79
|
click.echo(f"✗ Permission denied: {exc}", err=True)
|
|
80
80
|
sys.exit(ExitCodes.PERMISSION_DENIED)
|
|
81
81
|
elif "network" in error_msg or "connection" in error_msg:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-complete-workflow/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/demo-complete-workflow/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-7-1-test-plans/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/exercise-7-1-test-plans/config.yaml
RENAMED
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/examples/spec-compliance-notebooks/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.6.2 → systemlink_cli-1.6.3}/slcli/skills/slcli/references/analysis-recipes.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|