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.
Files changed (121) hide show
  1. bt_cli/__init__.py +3 -0
  2. bt_cli/cli.py +830 -0
  3. bt_cli/commands/__init__.py +1 -0
  4. bt_cli/commands/configure.py +415 -0
  5. bt_cli/commands/learn.py +229 -0
  6. bt_cli/commands/quick.py +784 -0
  7. bt_cli/core/__init__.py +1 -0
  8. bt_cli/core/auth.py +213 -0
  9. bt_cli/core/client.py +313 -0
  10. bt_cli/core/config.py +393 -0
  11. bt_cli/core/config_file.py +420 -0
  12. bt_cli/core/csv_utils.py +91 -0
  13. bt_cli/core/errors.py +247 -0
  14. bt_cli/core/output.py +205 -0
  15. bt_cli/core/prompts.py +87 -0
  16. bt_cli/core/rest_debug.py +221 -0
  17. bt_cli/data/CLAUDE.md +94 -0
  18. bt_cli/data/__init__.py +0 -0
  19. bt_cli/data/skills/bt/SKILL.md +108 -0
  20. bt_cli/data/skills/entitle/SKILL.md +170 -0
  21. bt_cli/data/skills/epmw/SKILL.md +144 -0
  22. bt_cli/data/skills/pra/SKILL.md +150 -0
  23. bt_cli/data/skills/pws/SKILL.md +198 -0
  24. bt_cli/entitle/__init__.py +1 -0
  25. bt_cli/entitle/client/__init__.py +5 -0
  26. bt_cli/entitle/client/base.py +443 -0
  27. bt_cli/entitle/commands/__init__.py +24 -0
  28. bt_cli/entitle/commands/accounts.py +53 -0
  29. bt_cli/entitle/commands/applications.py +39 -0
  30. bt_cli/entitle/commands/auth.py +68 -0
  31. bt_cli/entitle/commands/bundles.py +218 -0
  32. bt_cli/entitle/commands/integrations.py +60 -0
  33. bt_cli/entitle/commands/permissions.py +70 -0
  34. bt_cli/entitle/commands/policies.py +97 -0
  35. bt_cli/entitle/commands/resources.py +131 -0
  36. bt_cli/entitle/commands/roles.py +74 -0
  37. bt_cli/entitle/commands/users.py +123 -0
  38. bt_cli/entitle/commands/workflows.py +187 -0
  39. bt_cli/entitle/models/__init__.py +31 -0
  40. bt_cli/entitle/models/bundle.py +28 -0
  41. bt_cli/entitle/models/common.py +37 -0
  42. bt_cli/entitle/models/integration.py +30 -0
  43. bt_cli/entitle/models/permission.py +27 -0
  44. bt_cli/entitle/models/policy.py +25 -0
  45. bt_cli/entitle/models/resource.py +29 -0
  46. bt_cli/entitle/models/role.py +28 -0
  47. bt_cli/entitle/models/user.py +24 -0
  48. bt_cli/entitle/models/workflow.py +55 -0
  49. bt_cli/epmw/__init__.py +1 -0
  50. bt_cli/epmw/client/__init__.py +5 -0
  51. bt_cli/epmw/client/base.py +848 -0
  52. bt_cli/epmw/commands/__init__.py +33 -0
  53. bt_cli/epmw/commands/audits.py +250 -0
  54. bt_cli/epmw/commands/auth.py +55 -0
  55. bt_cli/epmw/commands/computers.py +140 -0
  56. bt_cli/epmw/commands/events.py +233 -0
  57. bt_cli/epmw/commands/groups.py +215 -0
  58. bt_cli/epmw/commands/policies.py +673 -0
  59. bt_cli/epmw/commands/quick.py +348 -0
  60. bt_cli/epmw/commands/requests.py +224 -0
  61. bt_cli/epmw/commands/roles.py +78 -0
  62. bt_cli/epmw/commands/tasks.py +38 -0
  63. bt_cli/epmw/commands/users.py +219 -0
  64. bt_cli/epmw/models/__init__.py +1 -0
  65. bt_cli/pra/__init__.py +1 -0
  66. bt_cli/pra/client/__init__.py +5 -0
  67. bt_cli/pra/client/base.py +618 -0
  68. bt_cli/pra/commands/__init__.py +30 -0
  69. bt_cli/pra/commands/auth.py +55 -0
  70. bt_cli/pra/commands/import_export.py +442 -0
  71. bt_cli/pra/commands/jump_clients.py +139 -0
  72. bt_cli/pra/commands/jump_groups.py +146 -0
  73. bt_cli/pra/commands/jump_items.py +638 -0
  74. bt_cli/pra/commands/jumpoints.py +95 -0
  75. bt_cli/pra/commands/policies.py +197 -0
  76. bt_cli/pra/commands/quick.py +470 -0
  77. bt_cli/pra/commands/teams.py +81 -0
  78. bt_cli/pra/commands/users.py +87 -0
  79. bt_cli/pra/commands/vault.py +564 -0
  80. bt_cli/pra/models/__init__.py +27 -0
  81. bt_cli/pra/models/common.py +12 -0
  82. bt_cli/pra/models/jump_client.py +25 -0
  83. bt_cli/pra/models/jump_group.py +15 -0
  84. bt_cli/pra/models/jump_item.py +72 -0
  85. bt_cli/pra/models/jumpoint.py +19 -0
  86. bt_cli/pra/models/team.py +14 -0
  87. bt_cli/pra/models/user.py +17 -0
  88. bt_cli/pra/models/vault.py +45 -0
  89. bt_cli/pws/__init__.py +1 -0
  90. bt_cli/pws/client/__init__.py +5 -0
  91. bt_cli/pws/client/base.py +356 -0
  92. bt_cli/pws/client/beyondinsight.py +869 -0
  93. bt_cli/pws/client/passwordsafe.py +1786 -0
  94. bt_cli/pws/commands/__init__.py +33 -0
  95. bt_cli/pws/commands/accounts.py +372 -0
  96. bt_cli/pws/commands/assets.py +311 -0
  97. bt_cli/pws/commands/auth.py +166 -0
  98. bt_cli/pws/commands/clouds.py +221 -0
  99. bt_cli/pws/commands/config.py +344 -0
  100. bt_cli/pws/commands/credentials.py +347 -0
  101. bt_cli/pws/commands/databases.py +306 -0
  102. bt_cli/pws/commands/directories.py +199 -0
  103. bt_cli/pws/commands/functional.py +298 -0
  104. bt_cli/pws/commands/import_export.py +452 -0
  105. bt_cli/pws/commands/platforms.py +118 -0
  106. bt_cli/pws/commands/quick.py +1646 -0
  107. bt_cli/pws/commands/search.py +256 -0
  108. bt_cli/pws/commands/secrets.py +1343 -0
  109. bt_cli/pws/commands/systems.py +389 -0
  110. bt_cli/pws/commands/users.py +415 -0
  111. bt_cli/pws/commands/workgroups.py +166 -0
  112. bt_cli/pws/config.py +18 -0
  113. bt_cli/pws/models/__init__.py +19 -0
  114. bt_cli/pws/models/account.py +186 -0
  115. bt_cli/pws/models/asset.py +102 -0
  116. bt_cli/pws/models/common.py +132 -0
  117. bt_cli/pws/models/system.py +121 -0
  118. bt_cli-0.4.13.dist-info/METADATA +417 -0
  119. bt_cli-0.4.13.dist-info/RECORD +121 -0
  120. bt_cli-0.4.13.dist-info/WHEEL +4 -0
  121. bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,186 @@
1
+ """Managed Account models for Password Safe API."""
2
+
3
+ from typing import Any, Optional
4
+ from datetime import datetime
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class ManagedAccount(BaseModel):
9
+ """Managed account in Password Safe."""
10
+
11
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
12
+
13
+ managed_account_id: int = Field(alias="ManagedAccountID")
14
+ managed_system_id: int = Field(alias="ManagedSystemID")
15
+ account_name: str = Field(alias="AccountName")
16
+ domain_name: Optional[str] = Field(default=None, alias="DomainName")
17
+ description: Optional[str] = Field(default=None, alias="Description")
18
+
19
+ # System info (populated in some responses)
20
+ system_name: Optional[str] = Field(default=None, alias="SystemName")
21
+ platform_id: Optional[int] = Field(default=None, alias="PlatformID")
22
+
23
+ # API access
24
+ api_enabled: Optional[bool] = Field(default=None, alias="ApiEnabled")
25
+
26
+ # Password management flags
27
+ auto_management_flag: Optional[bool] = Field(default=None, alias="AutoManagementFlag")
28
+ check_password_flag: Optional[bool] = Field(default=None, alias="CheckPasswordFlag")
29
+ change_password_after_any_release_flag: Optional[bool] = Field(
30
+ default=None, alias="ChangePasswordAfterAnyReleaseFlag"
31
+ )
32
+ reset_password_on_mismatch_flag: Optional[bool] = Field(
33
+ default=None, alias="ResetPasswordOnMismatchFlag"
34
+ )
35
+
36
+ # Password change schedule
37
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
38
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
39
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
40
+ next_change_date: Optional[datetime] = Field(default=None, alias="NextChangeDate")
41
+ last_change_date: Optional[datetime] = Field(default=None, alias="LastChangeDate")
42
+
43
+ # Password rules
44
+ password_rule_id: Optional[int] = Field(default=None, alias="PasswordRuleID")
45
+ dss_key_rule_id: Optional[int] = Field(default=None, alias="DSSKeyRuleID")
46
+
47
+ # Status
48
+ status: Optional[str] = Field(default=None, alias="Status")
49
+ is_subscribed_account: Optional[bool] = Field(default=None, alias="IsSubscribedAccount")
50
+ created_date: Optional[datetime] = Field(default=None, alias="CreatedDate")
51
+
52
+ @classmethod
53
+ def from_api(cls, data: dict[str, Any]) -> "ManagedAccount":
54
+ """Create ManagedAccount from API response."""
55
+ return cls.model_validate(data)
56
+
57
+ @property
58
+ def display_name(self) -> str:
59
+ """Get a display name for the account."""
60
+ if self.domain_name:
61
+ return f"{self.domain_name}\\{self.account_name}"
62
+ return self.account_name
63
+
64
+ @property
65
+ def full_path(self) -> str:
66
+ """Get the full path (system/account)."""
67
+ if self.system_name:
68
+ return f"{self.system_name}/{self.account_name}"
69
+ return f"System-{self.managed_system_id}/{self.account_name}"
70
+
71
+ @property
72
+ def id(self) -> int:
73
+ """Alias for managed_account_id for convenience."""
74
+ return self.managed_account_id
75
+
76
+
77
+ class ManagedAccountCreate(BaseModel):
78
+ """Data for creating a managed account."""
79
+
80
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
81
+
82
+ account_name: str = Field(alias="AccountName")
83
+ password: Optional[str] = Field(default=None, alias="Password")
84
+ domain_name: Optional[str] = Field(default=None, alias="DomainName")
85
+ description: Optional[str] = Field(default=None, alias="Description")
86
+ api_enabled: bool = Field(default=True, alias="ApiEnabled")
87
+ auto_management_flag: bool = Field(default=True, alias="AutoManagementFlag")
88
+ check_password_flag: bool = Field(default=True, alias="CheckPasswordFlag")
89
+ change_password_after_any_release_flag: bool = Field(
90
+ default=False, alias="ChangePasswordAfterAnyReleaseFlag"
91
+ )
92
+ reset_password_on_mismatch_flag: bool = Field(
93
+ default=False, alias="ResetPasswordOnMismatchFlag"
94
+ )
95
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
96
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
97
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
98
+ next_change_date: Optional[str] = Field(default=None, alias="NextChangeDate")
99
+ password_rule_id: Optional[int] = Field(default=None, alias="PasswordRuleID")
100
+ dss_key_rule_id: Optional[int] = Field(default=None, alias="DSSKeyRuleID")
101
+
102
+ def to_api_dict(self) -> dict[str, Any]:
103
+ """Convert to API-compatible dictionary."""
104
+ return self.model_dump(by_alias=True, exclude_none=True)
105
+
106
+
107
+ class ManagedAccountUpdate(BaseModel):
108
+ """Data for updating a managed account."""
109
+
110
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
111
+
112
+ account_name: Optional[str] = Field(default=None, alias="AccountName")
113
+ domain_name: Optional[str] = Field(default=None, alias="DomainName")
114
+ description: Optional[str] = Field(default=None, alias="Description")
115
+ api_enabled: Optional[bool] = Field(default=None, alias="ApiEnabled")
116
+ auto_management_flag: Optional[bool] = Field(default=None, alias="AutoManagementFlag")
117
+ check_password_flag: Optional[bool] = Field(default=None, alias="CheckPasswordFlag")
118
+ change_password_after_any_release_flag: Optional[bool] = Field(
119
+ default=None, alias="ChangePasswordAfterAnyReleaseFlag"
120
+ )
121
+ reset_password_on_mismatch_flag: Optional[bool] = Field(
122
+ default=None, alias="ResetPasswordOnMismatchFlag"
123
+ )
124
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
125
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
126
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
127
+ password_rule_id: Optional[int] = Field(default=None, alias="PasswordRuleID")
128
+ dss_key_rule_id: Optional[int] = Field(default=None, alias="DSSKeyRuleID")
129
+
130
+ def to_api_dict(self) -> dict[str, Any]:
131
+ """Convert to API-compatible dictionary."""
132
+ return self.model_dump(by_alias=True, exclude_none=True)
133
+
134
+
135
+ class FunctionalAccount(BaseModel):
136
+ """Functional account used for system management operations."""
137
+
138
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
139
+
140
+ functional_account_id: int = Field(alias="FunctionalAccountID")
141
+ account_name: str = Field(alias="AccountName")
142
+ domain_name: Optional[str] = Field(default=None, alias="DomainName")
143
+ platform_id: Optional[int] = Field(default=None, alias="PlatformID")
144
+ description: Optional[str] = Field(default=None, alias="Description")
145
+
146
+ @classmethod
147
+ def from_api(cls, data: dict[str, Any]) -> "FunctionalAccount":
148
+ """Create FunctionalAccount from API response."""
149
+ return cls.model_validate(data)
150
+
151
+
152
+ class PasswordRule(BaseModel):
153
+ """Password complexity rule."""
154
+
155
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
156
+
157
+ password_rule_id: int = Field(alias="PasswordRuleID")
158
+ name: str = Field(alias="Name")
159
+ description: Optional[str] = Field(default=None, alias="Description")
160
+ min_length: Optional[int] = Field(default=None, alias="MinLength")
161
+ max_length: Optional[int] = Field(default=None, alias="MaxLength")
162
+ require_upper: Optional[bool] = Field(default=None, alias="RequireUpper")
163
+ require_lower: Optional[bool] = Field(default=None, alias="RequireLower")
164
+ require_numeric: Optional[bool] = Field(default=None, alias="RequireNumeric")
165
+ require_special: Optional[bool] = Field(default=None, alias="RequireSpecial")
166
+
167
+ @classmethod
168
+ def from_api(cls, data: dict[str, Any]) -> "PasswordRule":
169
+ """Create PasswordRule from API response."""
170
+ return cls.model_validate(data)
171
+
172
+
173
+ class Application(BaseModel):
174
+ """Application that can be assigned to accounts."""
175
+
176
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
177
+
178
+ application_id: int = Field(alias="ApplicationID")
179
+ name: str = Field(alias="Name")
180
+ description: Optional[str] = Field(default=None, alias="Description")
181
+ version: Optional[str] = Field(default=None, alias="Version")
182
+
183
+ @classmethod
184
+ def from_api(cls, data: dict[str, Any]) -> "Application":
185
+ """Create Application from API response."""
186
+ return cls.model_validate(data)
@@ -0,0 +1,102 @@
1
+ """Asset models for BeyondInsight API."""
2
+
3
+ from typing import Any, Optional
4
+ from datetime import datetime
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class Asset(BaseModel):
9
+ """Asset (discovered or manually created system)."""
10
+
11
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
12
+
13
+ asset_id: int = Field(alias="AssetID")
14
+ asset_name: Optional[str] = Field(default=None, alias="AssetName")
15
+ ip_address: Optional[str] = Field(default=None, alias="IPAddress")
16
+ dns_name: Optional[str] = Field(default=None, alias="DnsName")
17
+ domain_name: Optional[str] = Field(default=None, alias="DomainName")
18
+ mac_address: Optional[str] = Field(default=None, alias="MACAddress")
19
+ asset_type: Optional[str] = Field(default=None, alias="AssetType")
20
+ workgroup_id: Optional[int] = Field(default=None, alias="WorkgroupID")
21
+ workgroup_name: Optional[str] = Field(default=None, alias="WorkgroupName")
22
+ operating_system: Optional[str] = Field(default=None, alias="OperatingSystem")
23
+ created_date: Optional[datetime] = Field(default=None, alias="CreatedDate")
24
+ last_updated_date: Optional[datetime] = Field(default=None, alias="LastUpdatedDate")
25
+
26
+ @classmethod
27
+ def from_api(cls, data: dict[str, Any]) -> "Asset":
28
+ """Create Asset from API response."""
29
+ return cls.model_validate(data)
30
+
31
+ @property
32
+ def display_name(self) -> str:
33
+ """Get a display name for the asset."""
34
+ return self.asset_name or self.dns_name or self.ip_address or f"Asset-{self.asset_id}"
35
+
36
+
37
+ class Workgroup(BaseModel):
38
+ """Workgroup for organizing assets and systems."""
39
+
40
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
41
+
42
+ workgroup_id: int = Field(alias="WorkgroupID")
43
+ name: str = Field(alias="Name")
44
+ description: Optional[str] = Field(default=None, alias="Description")
45
+ created_date: Optional[datetime] = Field(default=None, alias="CreatedDate")
46
+
47
+ @classmethod
48
+ def from_api(cls, data: dict[str, Any]) -> "Workgroup":
49
+ """Create Workgroup from API response."""
50
+ return cls.model_validate(data)
51
+
52
+
53
+ class Platform(BaseModel):
54
+ """Platform (OS type) for managed systems."""
55
+
56
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
57
+
58
+ platform_id: int = Field(alias="PlatformID")
59
+ name: str = Field(alias="Name")
60
+ short_name: Optional[str] = Field(default=None, alias="ShortName")
61
+ platform_type: Optional[str] = Field(default=None, alias="PlatformType")
62
+ port_number: Optional[int] = Field(default=None, alias="PortNumber")
63
+ supports_sessions: Optional[bool] = Field(default=None, alias="SupportsSessions")
64
+
65
+ @classmethod
66
+ def from_api(cls, data: dict[str, Any]) -> "Platform":
67
+ """Create Platform from API response."""
68
+ return cls.model_validate(data)
69
+
70
+
71
+ class SmartRule(BaseModel):
72
+ """Smart rule for automated system/account management."""
73
+
74
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
75
+
76
+ smart_rule_id: int = Field(alias="SmartRuleID")
77
+ name: str = Field(alias="Name")
78
+ description: Optional[str] = Field(default=None, alias="Description")
79
+ category: Optional[str] = Field(default=None, alias="Category")
80
+ status: Optional[str] = Field(default=None, alias="Status")
81
+ created_date: Optional[datetime] = Field(default=None, alias="CreatedDate")
82
+
83
+ @classmethod
84
+ def from_api(cls, data: dict[str, Any]) -> "SmartRule":
85
+ """Create SmartRule from API response."""
86
+ return cls.model_validate(data)
87
+
88
+
89
+ class Attribute(BaseModel):
90
+ """Custom attribute for assets, systems, or accounts."""
91
+
92
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
93
+
94
+ attribute_id: int = Field(alias="AttributeID")
95
+ name: str = Field(alias="Name")
96
+ attribute_type: Optional[str] = Field(default=None, alias="AttributeType")
97
+ description: Optional[str] = Field(default=None, alias="Description")
98
+
99
+ @classmethod
100
+ def from_api(cls, data: dict[str, Any]) -> "Attribute":
101
+ """Create Attribute from API response."""
102
+ return cls.model_validate(data)
@@ -0,0 +1,132 @@
1
+ """Common models and types shared across the API."""
2
+
3
+ from typing import Any, Generic, Optional, TypeVar
4
+ from datetime import datetime
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ # Type variable for generic paginated responses
9
+ T = TypeVar("T", bound=BaseModel)
10
+
11
+
12
+ class BaseAPIModel(BaseModel):
13
+ """Base model for all API responses.
14
+
15
+ Allows extra fields to handle API changes gracefully.
16
+ """
17
+
18
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
19
+
20
+ @classmethod
21
+ def from_api(cls, data: dict[str, Any]) -> "BaseAPIModel":
22
+ """Create model instance from API response data.
23
+
24
+ Args:
25
+ data: Dictionary from API response
26
+
27
+ Returns:
28
+ Model instance
29
+ """
30
+ return cls.model_validate(data)
31
+
32
+
33
+ class EntityRef(BaseModel):
34
+ """Reference to an entity by ID and optional name."""
35
+
36
+ model_config = ConfigDict(extra="allow")
37
+
38
+ id: int
39
+ name: Optional[str] = None
40
+
41
+
42
+ class PaginatedResponse(BaseModel, Generic[T]):
43
+ """Generic paginated response wrapper."""
44
+
45
+ model_config = ConfigDict(extra="allow")
46
+
47
+ data: list[T]
48
+ total: Optional[int] = None
49
+ offset: Optional[int] = None
50
+ limit: Optional[int] = None
51
+
52
+
53
+ class ApiError(BaseModel):
54
+ """API error response."""
55
+
56
+ model_config = ConfigDict(extra="allow")
57
+
58
+ message: Optional[str] = None
59
+ error: Optional[str] = None
60
+ status_code: Optional[int] = None
61
+ details: Optional[dict[str, Any]] = None
62
+
63
+
64
+ class Timestamp(BaseModel):
65
+ """Timestamp wrapper for API datetime fields."""
66
+
67
+ model_config = ConfigDict(extra="allow")
68
+
69
+ value: Optional[datetime] = None
70
+
71
+ @classmethod
72
+ def from_api_string(cls, value: Optional[str]) -> "Timestamp":
73
+ """Parse timestamp from API string format.
74
+
75
+ Args:
76
+ value: ISO format datetime string or None
77
+
78
+ Returns:
79
+ Timestamp instance
80
+ """
81
+ if not value:
82
+ return cls(value=None)
83
+ try:
84
+ # Handle various datetime formats
85
+ return cls(value=datetime.fromisoformat(value.replace("Z", "+00:00")))
86
+ except (ValueError, AttributeError):
87
+ return cls(value=None)
88
+
89
+
90
+ # Common field aliases for API responses
91
+ COMMON_FIELD_ALIASES = {
92
+ "id": ["Id", "ID"],
93
+ "name": ["Name", "SystemName", "AccountName"],
94
+ "description": ["Description", "Desc"],
95
+ "created_at": ["CreatedDate", "DateCreated", "CreateDate"],
96
+ "updated_at": ["UpdatedDate", "DateUpdated", "ModifiedDate", "LastModifiedDate"],
97
+ }
98
+
99
+
100
+ def normalize_api_response(data: dict[str, Any]) -> dict[str, Any]:
101
+ """Normalize API response field names to lowercase with underscores.
102
+
103
+ Args:
104
+ data: API response dictionary
105
+
106
+ Returns:
107
+ Normalized dictionary with consistent field names
108
+ """
109
+ if not data:
110
+ return data
111
+
112
+ result = {}
113
+ for key, value in data.items():
114
+ # Convert PascalCase to snake_case
115
+ normalized_key = ""
116
+ for i, char in enumerate(key):
117
+ if char.isupper() and i > 0:
118
+ normalized_key += "_"
119
+ normalized_key += char.lower()
120
+
121
+ # Recursively normalize nested objects
122
+ if isinstance(value, dict):
123
+ value = normalize_api_response(value)
124
+ elif isinstance(value, list):
125
+ value = [
126
+ normalize_api_response(item) if isinstance(item, dict) else item
127
+ for item in value
128
+ ]
129
+
130
+ result[normalized_key] = value
131
+
132
+ return result
@@ -0,0 +1,121 @@
1
+ """Managed System models for Password Safe API."""
2
+
3
+ from typing import Any, Optional
4
+ from datetime import datetime
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class ManagedSystem(BaseModel):
9
+ """Managed system in Password Safe."""
10
+
11
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
12
+
13
+ managed_system_id: int = Field(alias="ManagedSystemID")
14
+ system_name: str = Field(alias="SystemName")
15
+ platform_id: Optional[int] = Field(default=None, alias="PlatformID")
16
+ platform_name: Optional[str] = Field(default=None, alias="PlatformName")
17
+ workgroup_id: Optional[int] = Field(default=None, alias="WorkgroupID")
18
+ workgroup_name: Optional[str] = Field(default=None, alias="WorkgroupName")
19
+ asset_id: Optional[int] = Field(default=None, alias="AssetID")
20
+ contact_email: Optional[str] = Field(default=None, alias="ContactEmail")
21
+ description: Optional[str] = Field(default=None, alias="Description")
22
+ port: Optional[int] = Field(default=None, alias="Port")
23
+ timeout: Optional[int] = Field(default=None, alias="Timeout")
24
+
25
+ # Functional account for management operations
26
+ functional_account_id: Optional[int] = Field(default=None, alias="FunctionalAccountID")
27
+ functional_account_name: Optional[str] = Field(default=None, alias="FunctionalAccountName")
28
+
29
+ # Elevation settings
30
+ elevation_command: Optional[str] = Field(default=None, alias="ElevationCommand")
31
+
32
+ # Password management settings
33
+ check_password_flag: Optional[bool] = Field(default=None, alias="CheckPasswordFlag")
34
+ change_password_after_any_release_flag: Optional[bool] = Field(
35
+ default=None, alias="ChangePasswordAfterAnyReleaseFlag"
36
+ )
37
+ reset_password_on_mismatch_flag: Optional[bool] = Field(
38
+ default=None, alias="ResetPasswordOnMismatchFlag"
39
+ )
40
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
41
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
42
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
43
+
44
+ # Status and metadata
45
+ is_reachable: Optional[bool] = Field(default=None, alias="IsReachable")
46
+ last_change_date: Optional[datetime] = Field(default=None, alias="LastChangeDate")
47
+ created_date: Optional[datetime] = Field(default=None, alias="CreatedDate")
48
+
49
+ @classmethod
50
+ def from_api(cls, data: dict[str, Any]) -> "ManagedSystem":
51
+ """Create ManagedSystem from API response."""
52
+ return cls.model_validate(data)
53
+
54
+ @property
55
+ def display_name(self) -> str:
56
+ """Get a display name for the system."""
57
+ return self.system_name
58
+
59
+ @property
60
+ def id(self) -> int:
61
+ """Alias for managed_system_id for convenience."""
62
+ return self.managed_system_id
63
+
64
+
65
+ class ManagedSystemCreate(BaseModel):
66
+ """Data for creating a managed system."""
67
+
68
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
69
+
70
+ system_name: str = Field(alias="SystemName")
71
+ platform_id: int = Field(alias="PlatformID")
72
+ workgroup_id: Optional[int] = Field(default=None, alias="WorkgroupID")
73
+ asset_id: Optional[int] = Field(default=None, alias="AssetID")
74
+ contact_email: Optional[str] = Field(default=None, alias="ContactEmail")
75
+ description: Optional[str] = Field(default=None, alias="Description")
76
+ port: Optional[int] = Field(default=None, alias="Port")
77
+ timeout: Optional[int] = Field(default=None, alias="Timeout")
78
+ functional_account_id: Optional[int] = Field(default=None, alias="FunctionalAccountID")
79
+ elevation_command: Optional[str] = Field(default=None, alias="ElevationCommand")
80
+ check_password_flag: Optional[bool] = Field(default=None, alias="CheckPasswordFlag")
81
+ change_password_after_any_release_flag: Optional[bool] = Field(
82
+ default=None, alias="ChangePasswordAfterAnyReleaseFlag"
83
+ )
84
+ reset_password_on_mismatch_flag: Optional[bool] = Field(
85
+ default=None, alias="ResetPasswordOnMismatchFlag"
86
+ )
87
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
88
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
89
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
90
+
91
+ def to_api_dict(self) -> dict[str, Any]:
92
+ """Convert to API-compatible dictionary."""
93
+ return self.model_dump(by_alias=True, exclude_none=True)
94
+
95
+
96
+ class ManagedSystemUpdate(BaseModel):
97
+ """Data for updating a managed system."""
98
+
99
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
100
+
101
+ system_name: Optional[str] = Field(default=None, alias="SystemName")
102
+ contact_email: Optional[str] = Field(default=None, alias="ContactEmail")
103
+ description: Optional[str] = Field(default=None, alias="Description")
104
+ port: Optional[int] = Field(default=None, alias="Port")
105
+ timeout: Optional[int] = Field(default=None, alias="Timeout")
106
+ functional_account_id: Optional[int] = Field(default=None, alias="FunctionalAccountID")
107
+ elevation_command: Optional[str] = Field(default=None, alias="ElevationCommand")
108
+ check_password_flag: Optional[bool] = Field(default=None, alias="CheckPasswordFlag")
109
+ change_password_after_any_release_flag: Optional[bool] = Field(
110
+ default=None, alias="ChangePasswordAfterAnyReleaseFlag"
111
+ )
112
+ reset_password_on_mismatch_flag: Optional[bool] = Field(
113
+ default=None, alias="ResetPasswordOnMismatchFlag"
114
+ )
115
+ change_frequency_type: Optional[str] = Field(default=None, alias="ChangeFrequencyType")
116
+ change_frequency_days: Optional[int] = Field(default=None, alias="ChangeFrequencyDays")
117
+ change_time: Optional[str] = Field(default=None, alias="ChangeTime")
118
+
119
+ def to_api_dict(self) -> dict[str, Any]:
120
+ """Convert to API-compatible dictionary."""
121
+ return self.model_dump(by_alias=True, exclude_none=True)