aidp-cli 1.0.0__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.
- aidp_cli/__init__.py +3 -0
- aidp_cli/body_security.py +166 -0
- aidp_cli/discovery.py +34 -0
- aidp_cli/main.py +2006 -0
- aidp_cli/manifest.py +229 -0
- aidp_cli/operation_manifest.json +29073 -0
- aidp_cli-1.0.0.dist-info/METADATA +10 -0
- aidp_cli-1.0.0.dist-info/RECORD +13 -0
- aidp_cli-1.0.0.dist-info/WHEEL +5 -0
- aidp_cli-1.0.0.dist-info/entry_points.txt +2 -0
- aidp_cli-1.0.0.dist-info/licenses/LICENSE.txt +35 -0
- aidp_cli-1.0.0.dist-info/licenses/NOTICE.txt +1 -0
- aidp_cli-1.0.0.dist-info/top_level.txt +1 -0
aidp_cli/__init__.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from aidp_cli.manifest import BodyField, CommandDefinition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
MAX_BODY_SCAN_DEPTH = 20
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _normalized_identifier(value: str) -> str:
|
|
15
|
+
return re.sub(r"[^A-Za-z0-9]", "", value).lower()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
SENSITIVE_BODY_FIELD_NAMES = {
|
|
19
|
+
_normalized_identifier(name)
|
|
20
|
+
for name in (
|
|
21
|
+
"apiKey",
|
|
22
|
+
"accessToken",
|
|
23
|
+
"clientSecret",
|
|
24
|
+
"credentialDetails",
|
|
25
|
+
"passphrase",
|
|
26
|
+
"password",
|
|
27
|
+
"personalAccessToken",
|
|
28
|
+
"privateApiKey",
|
|
29
|
+
"privateKey",
|
|
30
|
+
"refreshToken",
|
|
31
|
+
"secret",
|
|
32
|
+
"secretContentBase64",
|
|
33
|
+
"secretId",
|
|
34
|
+
"secretKey",
|
|
35
|
+
"secretTokenPair",
|
|
36
|
+
"secretValue",
|
|
37
|
+
"token",
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def json_body_argument_source(value: str) -> str:
|
|
43
|
+
if value == "-":
|
|
44
|
+
return "stdin"
|
|
45
|
+
if value.startswith("@") or value.startswith("file://"):
|
|
46
|
+
return "file"
|
|
47
|
+
return "inline"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def command_has_sensitive_body_fields(command: CommandDefinition) -> bool:
|
|
51
|
+
return _fields_contain_sensitive_body_field(
|
|
52
|
+
command,
|
|
53
|
+
_root_body_fields(command),
|
|
54
|
+
"",
|
|
55
|
+
(),
|
|
56
|
+
set(),
|
|
57
|
+
0,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def body_contains_sensitive_field(command: CommandDefinition, body: Any) -> bool:
|
|
62
|
+
return _value_contains_sensitive_field(command, body, _root_body_model_name(command), (), 0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _fields_contain_sensitive_body_field(
|
|
66
|
+
command: CommandDefinition,
|
|
67
|
+
fields: tuple[BodyField, ...],
|
|
68
|
+
model_name: str,
|
|
69
|
+
parent_path: tuple[str, ...],
|
|
70
|
+
seen_models: set[str],
|
|
71
|
+
depth: int,
|
|
72
|
+
) -> bool:
|
|
73
|
+
if depth >= MAX_BODY_SCAN_DEPTH:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
for field in fields:
|
|
77
|
+
path = (*parent_path, field.name)
|
|
78
|
+
if _is_sensitive_field(field.name, model_name, path):
|
|
79
|
+
return True
|
|
80
|
+
if not field.model_name or field.model_name in seen_models:
|
|
81
|
+
continue
|
|
82
|
+
model = command.body_models.get(field.model_name)
|
|
83
|
+
if model is None:
|
|
84
|
+
continue
|
|
85
|
+
next_seen = set(seen_models)
|
|
86
|
+
next_seen.add(field.model_name)
|
|
87
|
+
if _fields_contain_sensitive_body_field(
|
|
88
|
+
command,
|
|
89
|
+
model.fields,
|
|
90
|
+
field.model_name,
|
|
91
|
+
path,
|
|
92
|
+
next_seen,
|
|
93
|
+
depth + 1,
|
|
94
|
+
):
|
|
95
|
+
return True
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _value_contains_sensitive_field(
|
|
100
|
+
command: CommandDefinition,
|
|
101
|
+
value: Any,
|
|
102
|
+
model_name: str,
|
|
103
|
+
parent_path: tuple[str, ...],
|
|
104
|
+
depth: int,
|
|
105
|
+
) -> bool:
|
|
106
|
+
if depth >= MAX_BODY_SCAN_DEPTH:
|
|
107
|
+
return False
|
|
108
|
+
if isinstance(value, list):
|
|
109
|
+
return any(
|
|
110
|
+
_value_contains_sensitive_field(command, item, model_name, parent_path, depth + 1)
|
|
111
|
+
for item in value
|
|
112
|
+
)
|
|
113
|
+
if not isinstance(value, dict):
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
for key, nested_value in value.items():
|
|
117
|
+
path = (*parent_path, str(key))
|
|
118
|
+
if _is_sensitive_field(str(key), model_name, path):
|
|
119
|
+
return True
|
|
120
|
+
field = _body_field_for_key(command, model_name, str(key))
|
|
121
|
+
next_model_name = field.model_name if field is not None else ""
|
|
122
|
+
if _value_contains_sensitive_field(command, nested_value, next_model_name, path, depth + 1):
|
|
123
|
+
return True
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _is_sensitive_field(field_name: str, model_name: str, path: tuple[str, ...]) -> bool:
|
|
128
|
+
normalized_name = _normalized_identifier(field_name)
|
|
129
|
+
if normalized_name in SENSITIVE_BODY_FIELD_NAMES:
|
|
130
|
+
return True
|
|
131
|
+
if normalized_name == "credential":
|
|
132
|
+
return _normalized_identifier(model_name) == "gitconfig" or _parent_path_name(path) == "gitconfig"
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _body_field_for_key(command: CommandDefinition, model_name: str, key: str) -> BodyField | None:
|
|
137
|
+
normalized_key = _normalized_identifier(key)
|
|
138
|
+
return next(
|
|
139
|
+
(
|
|
140
|
+
field
|
|
141
|
+
for field in _fields_for_model(command, model_name)
|
|
142
|
+
if _normalized_identifier(field.name) == normalized_key
|
|
143
|
+
),
|
|
144
|
+
None,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _fields_for_model(command: CommandDefinition, model_name: str) -> tuple[BodyField, ...]:
|
|
149
|
+
if model_name and model_name in command.body_models:
|
|
150
|
+
return command.body_models[model_name].fields
|
|
151
|
+
return _root_body_fields(command)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _root_body_fields(command: CommandDefinition) -> tuple[BodyField, ...]:
|
|
155
|
+
model_name = _root_body_model_name(command)
|
|
156
|
+
if model_name and model_name in command.body_models:
|
|
157
|
+
return command.body_models[model_name].fields
|
|
158
|
+
return command.body_fields
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _root_body_model_name(command: CommandDefinition) -> str:
|
|
162
|
+
return command.body_model if command.body_model and command.body_model in command.body_models else ""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _parent_path_name(path: tuple[str, ...]) -> str:
|
|
166
|
+
return _normalized_identifier(path[-2]) if len(path) >= 2 else ""
|
aidp_cli/discovery.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import re
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def discover_clients(sdk_module: ModuleType) -> dict[str, type]:
|
|
11
|
+
clients: dict[str, type] = {}
|
|
12
|
+
for class_name, client_cls in inspect.getmembers(sdk_module, inspect.isclass):
|
|
13
|
+
if not is_generated_client(class_name, client_cls, sdk_module):
|
|
14
|
+
continue
|
|
15
|
+
clients[service_name_from_client_class(class_name)] = client_cls
|
|
16
|
+
return dict(sorted(clients.items()))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_generated_client(class_name: str, client_cls: type, sdk_module: ModuleType) -> bool:
|
|
20
|
+
if not class_name.endswith("Client"):
|
|
21
|
+
return False
|
|
22
|
+
if class_name.endswith("CompositeOperations"):
|
|
23
|
+
return False
|
|
24
|
+
return client_cls.__module__.startswith(sdk_module.__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def service_name_from_client_class(class_name: str) -> str:
|
|
28
|
+
stem = class_name.removesuffix("Client")
|
|
29
|
+
words = re.findall(r"[A-Z]+(?=[A-Z][a-z]|\d|$)|[A-Z]?[a-z]+|\d+", stem)
|
|
30
|
+
return "-".join(word.lower() for word in words)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def client_class_from_service_name(service_name: str) -> str:
|
|
34
|
+
return "".join(part.capitalize() for part in service_name.split("-")) + "Client"
|