midpoint-cli 1.5.0__tar.gz → 1.5.0.dev2__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.
Files changed (28) hide show
  1. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/PKG-INFO +11 -20
  2. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/README.md +6 -15
  3. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/pyproject.toml +5 -8
  4. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/__init__.py +19 -32
  5. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/__init__.py +0 -91
  6. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/objects.py +21 -91
  7. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/__init__.py +2 -74
  8. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/base.py +0 -8
  9. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/console.py +8 -7
  10. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/delete.py +0 -3
  11. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/get.py +0 -1
  12. midpoint_cli-1.5.0.dev2/src/midpoint_cli/prompt/org.py +87 -0
  13. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/put.py +0 -1
  14. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/resource.py +1 -9
  15. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/script.py +0 -1
  16. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/task.py +1 -16
  17. midpoint_cli-1.5.0.dev2/src/midpoint_cli/prompt/user.py +120 -0
  18. midpoint_cli-1.5.0/src/midpoint_cli/prompt/info.py +0 -27
  19. midpoint_cli-1.5.0/src/midpoint_cli/prompt/org.py +0 -146
  20. midpoint_cli-1.5.0/src/midpoint_cli/prompt/role.py +0 -122
  21. midpoint_cli-1.5.0/src/midpoint_cli/prompt/user.py +0 -293
  22. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/LICENSE +0 -0
  23. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/observer.py +0 -0
  24. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/patch.py +0 -0
  25. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/progress.py +0 -0
  26. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/client/session.py +0 -0
  27. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/complete.py +0 -0
  28. {midpoint_cli-1.5.0 → midpoint_cli-1.5.0.dev2}/src/midpoint_cli/prompt/configuration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: midpoint-cli
3
- Version: 1.5.0
3
+ Version: 1.5.0.dev2
4
4
  Summary: A command line client to Midpoint Identity Management system.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -22,11 +22,11 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: System :: Systems Administration
24
24
  Requires-Dist: prompt-toolkit (>=3.0,<4.0)
25
- Requires-Dist: pygments (>=2.18,<3.0)
26
- Requires-Dist: requests (>=2.32,<3.0)
27
- Requires-Dist: rich (>=13.0,<15.0)
25
+ Requires-Dist: pygments (>=2.0,<3.0)
26
+ Requires-Dist: requests (>=2.31,<3.0)
27
+ Requires-Dist: rich (>=13.0,<14.0)
28
28
  Requires-Dist: unidecode (>=1.3,<2.0)
29
- Requires-Dist: urllib3 (>=2.3,<3.0)
29
+ Requires-Dist: urllib3 (>=2.0,<3.0)
30
30
  Project-URL: Repository, https://gitlab.com/alcibiade/midpoint-cli
31
31
  Description-Content-Type: text/markdown
32
32
 
@@ -188,9 +188,8 @@ Available commands:
188
188
  user Manage users.
189
189
  Subcommands:
190
190
  - users: List all users
191
- - user search <term> [term ...]: Search for users (OR). Prefix term with + to make it mandatory (AND).
191
+ - user search <term>: Search for users by name
192
192
  Example: user search john
193
- Example: user search +Engineering Doe Smith
194
193
  ```
195
194
 
196
195
  ### External configuration files (since v1.2)
@@ -278,10 +277,7 @@ midpoint> users
278
277
  # Lists all users with their details
279
278
 
280
279
  midpoint> user search john
281
- # Search for users matching "john" (OR across all fields)
282
-
283
- midpoint> user search +Engineering Doe Smith
284
- # Users in Engineering who match Doe or Smith (mandatory + optional OR)
280
+ # Search for users matching "john"
285
281
 
286
282
  midpoint> org
287
283
  # List all organizational units
@@ -378,18 +374,13 @@ midpoint-cli -u admin -p pass -U https://localhost:8080/midpoint/ users
378
374
  #### Search for a specific user
379
375
 
380
376
  ```bash
381
- # Search by any field (OR: matches users containing "john" OR "jane")
382
- midpoint-cli user search john jane
383
-
384
- # Mandatory terms with + prefix (AND: must contain both "john" AND "doe")
385
- midpoint-cli user search +john +doe
377
+ # Search by name or username
378
+ midpoint-cli user search john
386
379
 
387
- # Mixed: must be in Engineering AND match Doe or Smith
388
- midpoint-cli user search +Engineering Doe Smith
380
+ # Search with wildcards
381
+ midpoint-cli user search "j*"
389
382
  ```
390
383
 
391
- Search terms are case-insensitive and matched as substrings against all user fields (name, full name, email, OU, etc.). Unicode characters are normalised before matching (e.g. `François` matches `francois`).
392
-
393
384
  #### Get user details (XML definition)
394
385
 
395
386
  ```bash
@@ -156,9 +156,8 @@ Available commands:
156
156
  user Manage users.
157
157
  Subcommands:
158
158
  - users: List all users
159
- - user search <term> [term ...]: Search for users (OR). Prefix term with + to make it mandatory (AND).
159
+ - user search <term>: Search for users by name
160
160
  Example: user search john
161
- Example: user search +Engineering Doe Smith
162
161
  ```
163
162
 
164
163
  ### External configuration files (since v1.2)
@@ -246,10 +245,7 @@ midpoint> users
246
245
  # Lists all users with their details
247
246
 
248
247
  midpoint> user search john
249
- # Search for users matching "john" (OR across all fields)
250
-
251
- midpoint> user search +Engineering Doe Smith
252
- # Users in Engineering who match Doe or Smith (mandatory + optional OR)
248
+ # Search for users matching "john"
253
249
 
254
250
  midpoint> org
255
251
  # List all organizational units
@@ -346,18 +342,13 @@ midpoint-cli -u admin -p pass -U https://localhost:8080/midpoint/ users
346
342
  #### Search for a specific user
347
343
 
348
344
  ```bash
349
- # Search by any field (OR: matches users containing "john" OR "jane")
350
- midpoint-cli user search john jane
351
-
352
- # Mandatory terms with + prefix (AND: must contain both "john" AND "doe")
353
- midpoint-cli user search +john +doe
345
+ # Search by name or username
346
+ midpoint-cli user search john
354
347
 
355
- # Mixed: must be in Engineering AND match Doe or Smith
356
- midpoint-cli user search +Engineering Doe Smith
348
+ # Search with wildcards
349
+ midpoint-cli user search "j*"
357
350
  ```
358
351
 
359
- Search terms are case-insensitive and matched as substrings against all user fields (name, full name, email, OU, etc.). Unicode characters are normalised before matching (e.g. `François` matches `francois`).
360
-
361
352
  #### Get user details (XML definition)
362
353
 
363
354
  ```bash
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "midpoint-cli"
7
- version = "1.5.0"
7
+ version = "1.5.0.dev2"
8
8
  description = "A command line client to Midpoint Identity Management system."
9
9
  readme = "README.md"
10
10
 
@@ -38,11 +38,11 @@ midpoint-cli = 'midpoint_cli:main'
38
38
  [tool.poetry.dependencies]
39
39
  python = "^3.9.0"
40
40
  unidecode = "^1.3"
41
- requests = "^2.32"
42
- urllib3 = "^2.3"
43
- rich = ">=13.0,<15.0"
41
+ requests = "^2.31"
42
+ urllib3 = "^2.0"
43
+ rich = "^13.0"
44
44
  prompt-toolkit = "^3.0"
45
- pygments = "^2.18"
45
+ pygments = "^2.0"
46
46
 
47
47
  [tool.poetry.group.test.dependencies]
48
48
  pytest = "*"
@@ -52,9 +52,6 @@ pytest-cov = "*"
52
52
  ruff = "*"
53
53
  mypy = "*"
54
54
 
55
- [tool.pytest.ini_options]
56
- testpaths = ["test/unit"]
57
-
58
55
  [tool.ruff]
59
56
  target-version = "py39"
60
57
  line-length = 120
@@ -12,37 +12,6 @@ from midpoint_cli.prompt.configuration import compute_client_configuration
12
12
  from midpoint_cli.prompt.console import ConsoleDisplay
13
13
 
14
14
 
15
- def _build_commands_epilog(version: str) -> str:
16
- skip = {'EOF', 'exit'}
17
- commands = []
18
- for name in sorted(dir(MidpointClientPrompt)):
19
- if name.startswith('do_'):
20
- cmd = name[3:]
21
- if cmd in skip:
22
- continue
23
- method = getattr(MidpointClientPrompt, name)
24
- doc = method.__doc__.strip().splitlines()[0] if method.__doc__ else ''
25
- commands.append((cmd, doc))
26
-
27
- if not commands:
28
- return ''
29
-
30
- width = max(len(cmd) for cmd, _ in commands) + 2
31
- lines = ['', 'Available commands:']
32
- for cmd, doc in commands:
33
- lines.append(f' {cmd:<{width}}{doc}')
34
-
35
- lines += [
36
- '',
37
- 'Server URL and credentials can also be stored in ~/.midpoint-cli.cfg,',
38
- 'or read from environment variables MIDPOINT_URL, MIDPOINT_USERNAME and MIDPOINT_PASSWORD.',
39
- '',
40
- f'Midpoint-cli version {version}, created and maintained by Yannick Kirschhoffer alcibiade@alcibiade.org',
41
- '',
42
- ]
43
- return '\n'.join(lines)
44
-
45
-
46
15
  def main():
47
16
  logging.basicConfig(level=logging.INFO)
48
17
  logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR)
@@ -54,7 +23,25 @@ def main():
54
23
  argument_parser = ArgumentParser(
55
24
  description='An interactive Midpoint command line client.',
56
25
  formatter_class=RawTextHelpFormatter,
57
- epilog=_build_commands_epilog(version),
26
+ epilog="""
27
+ Available commands:
28
+ get Get an XML definition from the server from an existing OID reference.
29
+ put Create/Update a server object based on an XML structure.
30
+ delete Delete a server object based on its type and OID.
31
+
32
+ task Manage server tasks.
33
+ resource Manage resources on the server.
34
+
35
+ org Manage organizations.
36
+ user Manage users.
37
+
38
+ Server URL and credentials can also be stored in ~/.midpoint-cli.cfg, or read from environment variables MIDPOINT_URL,
39
+ MIDPOINT_USERNAME and MIDPOINT_PASSWORD.
40
+
41
+ Midpoint-cli version """
42
+ + version
43
+ + """, created and maintained by Yannick Kirschhoffer alcibiade@alcibiade.org
44
+ """,
58
45
  )
59
46
  argument_parser.add_argument(
60
47
  '-v', '--version', help='Set the username to authenticate this session.', action='store_true'
@@ -14,8 +14,6 @@ from midpoint_cli.client.objects import (
14
14
  MidpointObjectTypes,
15
15
  MidpointOrganization,
16
16
  MidpointResource,
17
- MidpointRole,
18
- MidpointShadow,
19
17
  MidpointTask,
20
18
  MidpointUser,
21
19
  namespaces,
@@ -82,19 +80,6 @@ class RestApiClient:
82
80
 
83
81
  return cast(str, response.content.decode())
84
82
 
85
- def patch_element(self, element_class: MidpointObjectType, element_oid: str, xml_body: str) -> str:
86
- assert element_class.endpoint is not None, f'Element class {element_class} has no endpoint'
87
- target_url = urljoin(self.configuration.url, 'ws/rest/' + element_class.endpoint + '/' + element_oid)
88
- response = self.requests_session.patch(
89
- url=target_url,
90
- data=xml_body,
91
- headers={'Content-Type': 'application/xml'},
92
- auth=(self.configuration.username, self.configuration.password),
93
- )
94
- if response.status_code >= 300:
95
- raise MidpointServerError(f'Error {response.status_code} received from server at {target_url}')
96
- return cast(str, response.content.decode())
97
-
98
83
  def get_elements(self, element_class: MidpointObjectType) -> Element:
99
84
  assert element_class.endpoint is not None, f'Element class {element_class} has no endpoint'
100
85
  url = urljoin(self.configuration.url, 'ws/rest/' + element_class.endpoint)
@@ -114,13 +99,6 @@ class RestApiClient:
114
99
 
115
100
  return cast(bytes, response.content)
116
101
 
117
- def get_endpoint(self, path: str) -> str:
118
- url = urljoin(self.configuration.url, path)
119
- response = self.requests_session.get(url=url, auth=(self.configuration.username, self.configuration.password))
120
- if response.status_code >= 300:
121
- raise MidpointServerError(f'Server responded with status code {response.status_code} on {url}')
122
- return cast(str, response.content.decode())
123
-
124
102
  def put_element(
125
103
  self, xml_filename: str, patch_file: Optional[str], patch_write: bool
126
104
  ) -> tuple[MidpointObjectType, str]:
@@ -218,55 +196,6 @@ class MidpointClient:
218
196
  def get_orgs(self) -> MidpointObjectList:
219
197
  return self.__get_collection(MidpointObjectTypes.ORG.value, MidpointOrganization)
220
198
 
221
- def get_roles(self) -> MidpointObjectList:
222
- return self.__get_collection(MidpointObjectTypes.ROLE.value, MidpointRole)
223
-
224
- def assign_role(self, user_oid: str, role_oid: str) -> None:
225
- xml_body = (
226
- '<objectModification'
227
- " xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'"
228
- " xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'"
229
- " xmlns:t='http://prism.evolveum.com/xml/ns/public/types-3'>"
230
- '<itemDelta>'
231
- '<t:modificationType>add</t:modificationType>'
232
- '<t:path>c:assignment</t:path>'
233
- '<t:value>'
234
- f"<c:targetRef oid='{role_oid}' type='c:RoleType'/>"
235
- '</t:value>'
236
- '</itemDelta>'
237
- '</objectModification>'
238
- )
239
- self.api_client.patch_element(MidpointObjectTypes.USER.value, user_oid, xml_body)
240
-
241
- def unassign_role(self, user_oid: str, role_oid: str) -> None:
242
- xml_body = (
243
- '<objectModification'
244
- " xmlns='http://midpoint.evolveum.com/xml/ns/public/common/api-types-3'"
245
- " xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'"
246
- " xmlns:t='http://prism.evolveum.com/xml/ns/public/types-3'>"
247
- '<itemDelta>'
248
- '<t:modificationType>delete</t:modificationType>'
249
- '<t:path>c:assignment</t:path>'
250
- '<t:value>'
251
- f"<c:targetRef oid='{role_oid}' type='c:RoleType'/>"
252
- '</t:value>'
253
- '</itemDelta>'
254
- '</objectModification>'
255
- )
256
- self.api_client.patch_element(MidpointObjectTypes.USER.value, user_oid, xml_body)
257
-
258
- def get_resource(self, oid: str) -> MidpointResource:
259
- return MidpointResource(self.__get_object(MidpointObjectTypes.RESOURCE.value, oid))
260
-
261
- def get_role(self, oid: str) -> MidpointRole:
262
- return MidpointRole(self.__get_object(MidpointObjectTypes.ROLE.value, oid))
263
-
264
- def get_org(self, oid: str) -> MidpointOrganization:
265
- return MidpointOrganization(self.__get_object(MidpointObjectTypes.ORG.value, oid))
266
-
267
- def get_shadow(self, oid: str) -> MidpointShadow:
268
- return MidpointShadow(self.__get_object(MidpointObjectTypes.SHADOW.value, oid))
269
-
270
199
  def task_action(self, task_oid: str, task_action: str, progress_monitor: ProgressMonitor) -> Optional[MidpointTask]:
271
200
  self.api_client.execute_action(MidpointObjectTypes.TASK.value, task_oid, task_action)
272
201
 
@@ -336,23 +265,3 @@ class MidpointClient:
336
265
 
337
266
  def delete(self, midpoint_type: MidpointObjectType, oid: str) -> str:
338
267
  return self.api_client.delete(midpoint_type, oid)
339
-
340
- def get_system_info(self) -> dict[str, str]:
341
- import json
342
-
343
- raw = self.api_client.get_endpoint('actuator/info')
344
- data = json.loads(raw)
345
-
346
- info: dict[str, str] = {}
347
- if 'version' in data:
348
- info['Midpoint Version'] = data['version']
349
- if 'build' in data:
350
- info['Build'] = data['build']
351
- if 'branch' in data:
352
- info['Branch'] = data['branch']
353
- if 'buildTimestamp' in data:
354
- info['Build Timestamp'] = data['buildTimestamp']
355
- if 'license' in data:
356
- info['License'] = data['license']
357
-
358
- return info
@@ -2,9 +2,9 @@ import itertools
2
2
  import re
3
3
  from collections import OrderedDict
4
4
  from dataclasses import dataclass
5
- from datetime import datetime, timezone
5
+ from datetime import datetime
6
6
  from enum import Enum
7
- from typing import Callable, Optional, TypeVar
7
+ from typing import Callable, Optional
8
8
  from xml.etree.ElementTree import Element
9
9
 
10
10
  from rich.console import Group
@@ -32,9 +32,6 @@ namespaces = {
32
32
  }
33
33
 
34
34
 
35
- _T = TypeVar('_T', bound='MidpointObject')
36
-
37
-
38
35
  class MidpointObject(OrderedDict):
39
36
  def get_oid(self) -> Optional[str]:
40
37
  oid = self.get('OID')
@@ -44,14 +41,6 @@ class MidpointObject(OrderedDict):
44
41
  name = self.get('Name')
45
42
  return str(name) if name is not None else None
46
43
 
47
- @classmethod
48
- def error_stub(cls: type[_T], oid: str) -> _T:
49
- obj = cls.__new__(cls)
50
- OrderedDict.__init__(obj)
51
- obj['OID'] = oid
52
- obj['Status'] = 'fetch error'
53
- return obj
54
-
55
44
 
56
45
  class MidpointObjectList(list[MidpointObject]):
57
46
  def find_object(self, search_reference) -> Optional[MidpointObject]:
@@ -62,23 +51,21 @@ class MidpointObjectList(list[MidpointObject]):
62
51
  return None
63
52
 
64
53
  def filter(self, queryterms: list[str]):
65
- selected_objects = MidpointObjectList()
66
-
67
- mandatory_terms = [t[1:] for t in queryterms if t.startswith('+')]
68
- optional_terms = [t for t in queryterms if not t.startswith('+')]
54
+ selected_users = MidpointObjectList()
69
55
 
70
- for obj in self:
71
- values = [unidecode(str(v)).casefold() for v in obj.values() if v is not None]
56
+ for user in self:
57
+ selected = False
72
58
 
73
- mandatory_match = all(any(unidecode(term).casefold() in v for v in values) for term in mandatory_terms)
74
- optional_match = not optional_terms or any(
75
- unidecode(term).casefold() in v for term in optional_terms for v in values
76
- )
59
+ for uservalue in user.values():
60
+ if uservalue is not None:
61
+ for term in queryterms:
62
+ if unidecode(term).casefold() in unidecode(uservalue).casefold():
63
+ selected = True
77
64
 
78
- if mandatory_match and optional_match:
79
- selected_objects.append(obj)
65
+ if selected:
66
+ selected_users.append(user)
80
67
 
81
- return selected_objects
68
+ return selected_users
82
69
 
83
70
 
84
71
  class MidpointTask(MidpointObject):
@@ -138,7 +125,7 @@ class MidpointTask(MidpointObject):
138
125
 
139
126
  actions_executed = statistics.find('c:actionsExecuted', namespaces)
140
127
 
141
- for object_actions_entry in actions_executed if actions_executed is not None else []:
128
+ for object_actions_entry in actions_executed or []:
142
129
  object_type = safe_get_text(object_actions_entry.find('c:objectType', namespaces))
143
130
  operation = safe_get_text(object_actions_entry.find('c:operation', namespaces))
144
131
  count_success = safe_get_int(object_actions_entry.find('c:totalSuccessCount', namespaces))
@@ -221,9 +208,11 @@ class MidpointTask(MidpointObject):
221
208
  if self.get('Execution Status') in ['RUNNING', 'RUNNABLE']:
222
209
  # Get now in matching timezone awareness
223
210
  if start_dt.tzinfo is not None and start_dt.tzinfo.utcoffset(start_dt) is not None:
211
+ from datetime import timezone
212
+
224
213
  now = datetime.now(timezone.utc)
225
214
  else:
226
- now = datetime.now(timezone.utc).replace(tzinfo=None)
215
+ now = datetime.utcnow()
227
216
  duration_seconds = (now - start_dt).total_seconds()
228
217
  return self._format_duration(duration_seconds)
229
218
 
@@ -342,53 +331,14 @@ class MidpointUser(MidpointObject):
342
331
 
343
332
  self._all_attributes = []
344
333
 
345
- for child in itertools.chain(
346
- xml_entity,
347
- activation if activation is not None else [],
348
- extfields if extfields is not None else [],
349
- ):
334
+ for child in itertools.chain(xml_entity, activation or [], extfields or []):
350
335
  # Check if the child has no children and contains non-empty text
351
336
  if len(child) == 0 and (child.text is not None and child.text.strip()):
352
337
  self._all_attributes.append((self.strip_namespace(child.tag), child.text))
353
338
 
354
- self._role_assignment_oids: list[str] = []
355
- self._role_assignment_id_map: dict[str, str] = {}
356
- self._org_assignment_oids: list[str] = []
357
- for assignment in xml_entity.findall('c:assignment', namespaces):
358
- target_ref = assignment.find('c:targetRef', namespaces)
359
- if target_ref is not None:
360
- ref_oid = target_ref.attrib.get('oid')
361
- ref_type = target_ref.attrib.get('type', '')
362
- if ref_oid:
363
- if 'RoleType' in ref_type:
364
- self._role_assignment_oids.append(ref_oid)
365
- assignment_id = assignment.attrib.get('id')
366
- if assignment_id:
367
- self._role_assignment_id_map[ref_oid] = assignment_id
368
- elif 'OrgType' in ref_type:
369
- self._org_assignment_oids.append(ref_oid)
370
-
371
- self._link_ref_oids: list[str] = []
372
- for link_ref in xml_entity.findall('c:linkRef', namespaces):
373
- ref_oid = link_ref.attrib.get('oid')
374
- if ref_oid:
375
- self._link_ref_oids.append(ref_oid)
376
-
377
339
  def get_all_attributes(self):
378
340
  return self._all_attributes
379
341
 
380
- def get_role_assignment_oids(self) -> list[str]:
381
- return self._role_assignment_oids
382
-
383
- def get_role_assignment_id(self, role_oid: str) -> Optional[str]:
384
- return self._role_assignment_id_map.get(role_oid)
385
-
386
- def get_org_assignment_oids(self) -> list[str]:
387
- return self._org_assignment_oids
388
-
389
- def get_link_ref_oids(self) -> list[str]:
390
- return self._link_ref_oids
391
-
392
342
 
393
343
  class MidpointOrganization(MidpointObject):
394
344
  def __init__(self, xml_entity: Element):
@@ -401,28 +351,6 @@ class MidpointOrganization(MidpointObject):
401
351
  self['Parent'] = None if parentorg is None else parentorg.attrib['oid']
402
352
 
403
353
 
404
- class MidpointRole(MidpointObject):
405
- def __init__(self, xml_entity: Element):
406
- super().__init__()
407
- self['OID'] = xml_entity.attrib['oid']
408
- self['Name'] = safe_get_text(xml_entity.find('c:name', namespaces))
409
- self['DisplayName'] = optional_text(xml_entity.find('c:displayName', namespaces))
410
- self['Description'] = optional_text(xml_entity.find('c:description', namespaces))
411
- self['Status'] = safe_get_text(xml_entity.find('c:activation/c:effectiveStatus', namespaces))
412
-
413
-
414
- class MidpointShadow(MidpointObject):
415
- def __init__(self, xml_entity: Element):
416
- super().__init__()
417
- self['OID'] = xml_entity.attrib['oid']
418
- self['Name'] = safe_get_text(xml_entity.find('c:name', namespaces))
419
- resource_ref = xml_entity.find('c:resourceRef', namespaces)
420
- self['ResourceOID'] = resource_ref.attrib['oid'] if resource_ref is not None else None
421
- self['Kind'] = optional_text(xml_entity.find('c:kind', namespaces))
422
- self['Intent'] = optional_text(xml_entity.find('c:intent', namespaces))
423
- self['Status'] = safe_get_text(xml_entity.find('c:activation/c:effectiveStatus', namespaces))
424
-
425
-
426
354
  @dataclass
427
355
  class MidpointObjectType:
428
356
  object_type: Optional[str]
@@ -524,7 +452,9 @@ class MidpointObjectTypes(Enum):
524
452
  valid_types.append(mp_type.value.tagname)
525
453
 
526
454
  valid_types_str = ', '.join(sorted(valid_types))
527
- raise MidpointTypeNotFound(f'No type found for value "{name}". Valid types are: {valid_types_str}')
455
+ raise MidpointTypeNotFound(
456
+ f'No type found for value "{name}". Valid types are: {valid_types_str}'
457
+ )
528
458
 
529
459
 
530
460
  def optional_text(node: Optional[Element]) -> Optional[str]:
@@ -14,11 +14,9 @@ from midpoint_cli.client.objects import MidpointObjectList
14
14
  from midpoint_cli.prompt.console import get_console, print_error
15
15
  from midpoint_cli.prompt.delete import DeleteClientPrompt
16
16
  from midpoint_cli.prompt.get import GetClientPrompt
17
- from midpoint_cli.prompt.info import InfoClientPrompt
18
17
  from midpoint_cli.prompt.org import OrgClientPrompt
19
18
  from midpoint_cli.prompt.put import PutClientPrompt
20
19
  from midpoint_cli.prompt.resource import ResourceClientPrompt
21
- from midpoint_cli.prompt.role import RoleClientPrompt
22
20
  from midpoint_cli.prompt.script import ScriptClientPrompt
23
21
  from midpoint_cli.prompt.task import TaskClientPrompt
24
22
  from midpoint_cli.prompt.user import UserClientPrompt
@@ -32,7 +30,6 @@ class MidpointCompleter(Completer):
32
30
  self.commands = {
33
31
  'exit': 'Exit the shell',
34
32
  'help': 'Show help',
35
- 'info': 'Show server information',
36
33
  'get': 'Get a server object',
37
34
  'put': 'Create/Update a server object',
38
35
  'delete': 'Delete a server object',
@@ -44,31 +41,24 @@ class MidpointCompleter(Completer):
44
41
  'users': 'List all server users',
45
42
  'org': 'Manage organizations',
46
43
  'orgs': 'List all server organizations',
47
- 'role': 'Manage roles',
48
- 'roles': 'List all server roles',
49
44
  'script': 'Execute server-side scripts',
50
45
  }
51
46
 
52
47
  # Subcommands for each main command
53
48
  self.subcommands = {
54
49
  'task': ['ls', 'get', 'run', 'wait', 'suspend', 'resume'],
55
- 'user': ['ls', 'search', 'get', 'assign', 'unassign'],
50
+ 'user': ['ls', 'search', 'get'],
56
51
  'org': ['ls', 'search'],
57
- 'role': ['ls', 'search'],
58
52
  'resource': ['ls', 'test'],
59
53
  }
60
54
 
61
55
  # Client for dynamic completions
62
56
  self.client = client
63
57
 
64
- # Cache for task/user/role lists (to avoid repeated API calls)
58
+ # Cache for task list (to avoid repeated API calls)
65
59
  self._task_cache: Optional[MidpointObjectList] = None
66
60
  self._task_cache_time: float = 0.0
67
61
  self._task_cache_ttl = 5 # Cache for 5 seconds
68
- self._user_cache: Optional[MidpointObjectList] = None
69
- self._user_cache_time: float = 0.0
70
- self._role_cache: Optional[MidpointObjectList] = None
71
- self._role_cache_time: float = 0.0
72
62
 
73
63
  def _get_tasks(self):
74
64
  """Get tasks with caching to avoid repeated API calls."""
@@ -90,40 +80,6 @@ class MidpointCompleter(Completer):
90
80
  # If fetching fails, return empty list (don't break autocomplete)
91
81
  return []
92
82
 
93
- def _get_users(self):
94
- """Get users with caching to avoid repeated API calls."""
95
- if self.client is None:
96
- return []
97
-
98
- current_time = time.time()
99
- if self._user_cache is not None and (current_time - self._user_cache_time) < self._task_cache_ttl:
100
- return self._user_cache
101
-
102
- try:
103
- users = self.client.get_users()
104
- self._user_cache = users
105
- self._user_cache_time = current_time
106
- return users
107
- except Exception:
108
- return []
109
-
110
- def _get_roles(self):
111
- """Get roles with caching to avoid repeated API calls."""
112
- if self.client is None:
113
- return []
114
-
115
- current_time = time.time()
116
- if self._role_cache is not None and (current_time - self._role_cache_time) < self._task_cache_ttl:
117
- return self._role_cache
118
-
119
- try:
120
- roles = self.client.get_roles()
121
- self._role_cache = roles
122
- self._role_cache_time = current_time
123
- return roles
124
- except Exception:
125
- return []
126
-
127
83
  def get_completions(self, document, complete_event):
128
84
  text_before_cursor = document.text_before_cursor
129
85
  words = text_before_cursor.split()
@@ -146,32 +102,6 @@ class MidpointCompleter(Completer):
146
102
  if subcmd.startswith(word.lower()):
147
103
  yield Completion(subcmd, start_position=-len(word))
148
104
 
149
- # Complete user assign/unassign arguments
150
- elif main_cmd == 'user' and len(words) >= 2:
151
- subcmd = words[1]
152
- if subcmd in ['assign', 'unassign']:
153
- if text_before_cursor.endswith(' '):
154
- word = ''
155
- else:
156
- word = words[-1] if words else ''
157
-
158
- # Third position: complete user name
159
- if len(words) == 2 or (len(words) == 3 and not text_before_cursor.endswith(' ')):
160
- actual_word = words[2] if len(words) == 3 else ''
161
- for u in self._get_users():
162
- name = u.get_name()
163
- oid = u.get_oid()
164
- if name and actual_word.lower() in name.lower():
165
- yield Completion(name, start_position=-len(actual_word), display_meta=f'OID: {oid}')
166
- # Fourth position: complete role name
167
- elif len(words) == 3 or (len(words) == 4 and not text_before_cursor.endswith(' ')):
168
- actual_word = words[3] if len(words) == 4 else ''
169
- for r in self._get_roles():
170
- name = r.get_name()
171
- oid = r.get_oid()
172
- if name and actual_word.lower() in name.lower():
173
- yield Completion(name, start_position=-len(actual_word), display_meta=f'OID: {oid}')
174
-
175
105
  # Complete task arguments for task subcommands
176
106
  elif main_cmd == 'task' and len(words) >= 2:
177
107
  subcmd = words[1]
@@ -211,9 +141,7 @@ class MidpointClientPrompt(
211
141
  ResourceClientPrompt,
212
142
  UserClientPrompt,
213
143
  OrgClientPrompt,
214
- RoleClientPrompt,
215
144
  ScriptClientPrompt,
216
- InfoClientPrompt,
217
145
  ):
218
146
  def __init__(self, client: MidpointClient):
219
147
  Cmd.__init__(self)
@@ -11,11 +11,3 @@ class PromptBase:
11
11
  self.client: MidpointClient
12
12
  self.error_code: Optional[int] = None
13
13
  self.error_message: Optional[str] = None
14
-
15
- @staticmethod
16
- def _subcommand_help(args, subparsers) -> bool:
17
- """If -h/--help follows a known subcommand, print that subcommand's help and return True."""
18
- if args and args[0] in subparsers and any(a in ('-h', '--help') for a in args[1:]):
19
- subparsers[args[0]].print_help()
20
- return True
21
- return False