ipulse-shared-core-ftredge 19.0.1__py3-none-any.whl → 22.1.1__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.

Potentially problematic release.


This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.

Files changed (47) hide show
  1. ipulse_shared_core_ftredge/cache/shared_cache.py +1 -2
  2. ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +4 -4
  3. ipulse_shared_core_ftredge/exceptions/base_exceptions.py +23 -0
  4. ipulse_shared_core_ftredge/models/__init__.py +3 -7
  5. ipulse_shared_core_ftredge/models/base_data_model.py +17 -19
  6. ipulse_shared_core_ftredge/models/catalog/__init__.py +10 -0
  7. ipulse_shared_core_ftredge/models/catalog/subscriptionplan.py +273 -0
  8. ipulse_shared_core_ftredge/models/catalog/usertype.py +170 -0
  9. ipulse_shared_core_ftredge/models/user/__init__.py +5 -0
  10. ipulse_shared_core_ftredge/models/user/user_permissions.py +66 -0
  11. ipulse_shared_core_ftredge/models/{subscription.py → user/user_subscription.py} +66 -20
  12. ipulse_shared_core_ftredge/models/{user_auth.py → user/userauth.py} +19 -10
  13. ipulse_shared_core_ftredge/models/{user_profile.py → user/userprofile.py} +53 -21
  14. ipulse_shared_core_ftredge/models/user/userstatus.py +430 -0
  15. ipulse_shared_core_ftredge/monitoring/__init__.py +2 -2
  16. ipulse_shared_core_ftredge/monitoring/tracemon.py +320 -0
  17. ipulse_shared_core_ftredge/services/__init__.py +11 -13
  18. ipulse_shared_core_ftredge/services/base/__init__.py +3 -1
  19. ipulse_shared_core_ftredge/services/base/base_firestore_service.py +73 -14
  20. ipulse_shared_core_ftredge/services/{cache_aware_firestore_service.py → base/cache_aware_firestore_service.py} +46 -32
  21. ipulse_shared_core_ftredge/services/catalog/__init__.py +14 -0
  22. ipulse_shared_core_ftredge/services/catalog/catalog_subscriptionplan_service.py +273 -0
  23. ipulse_shared_core_ftredge/services/catalog/catalog_usertype_service.py +307 -0
  24. ipulse_shared_core_ftredge/services/charging_processors.py +25 -25
  25. ipulse_shared_core_ftredge/services/user/__init__.py +5 -25
  26. ipulse_shared_core_ftredge/services/user/firebase_auth_admin_helpers.py +160 -0
  27. ipulse_shared_core_ftredge/services/user/user_core_service.py +423 -515
  28. ipulse_shared_core_ftredge/services/user/user_multistep_operations.py +726 -0
  29. ipulse_shared_core_ftredge/services/user/user_permissions_operations.py +392 -0
  30. ipulse_shared_core_ftredge/services/user/user_subscription_operations.py +484 -0
  31. ipulse_shared_core_ftredge/services/user/userauth_operations.py +928 -0
  32. ipulse_shared_core_ftredge/services/user/userprofile_operations.py +166 -0
  33. ipulse_shared_core_ftredge/services/user/userstatus_operations.py +212 -0
  34. ipulse_shared_core_ftredge/services/{charging_service.py → user_charging_service.py} +9 -9
  35. {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/METADATA +3 -4
  36. ipulse_shared_core_ftredge-22.1.1.dist-info/RECORD +51 -0
  37. ipulse_shared_core_ftredge/models/user_status.py +0 -495
  38. ipulse_shared_core_ftredge/monitoring/microservmon.py +0 -483
  39. ipulse_shared_core_ftredge/services/user/iam_management_operations.py +0 -326
  40. ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +0 -384
  41. ipulse_shared_core_ftredge/services/user/user_account_operations.py +0 -479
  42. ipulse_shared_core_ftredge/services/user/user_auth_operations.py +0 -305
  43. ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +0 -436
  44. ipulse_shared_core_ftredge-19.0.1.dist-info/RECORD +0 -41
  45. {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/WHEEL +0 -0
  46. {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/licenses/LICENCE +0 -0
  47. {ipulse_shared_core_ftredge-19.0.1.dist-info → ipulse_shared_core_ftredge-22.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,392 @@
1
+ """
2
+ Useriam Management Operations - Handle user permissions and access rights
3
+ """
4
+ import logging
5
+ from typing import Dict, Optional, List
6
+ from datetime import datetime
7
+ from ipulse_shared_base_ftredge.enums.enums_iam import IAMUnit
8
+ from ...models import UserPermission
9
+ from .userstatus_operations import UserstatusOperations
10
+ from ...exceptions import IAMPermissionError, UserStatusError
11
+
12
+
13
+ class UserpermissionsOperations:
14
+ """
15
+ Handles IAM permissions and access rights management
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ userstatus_ops: UserstatusOperations,
21
+ logger: Optional[logging.Logger] = None
22
+ ):
23
+ self.userstatus_ops = userstatus_ops
24
+ self.logger = logger or logging.getLogger(__name__)
25
+
26
+ # IAM Permission Operations
27
+
28
+ async def add_permission_to_user(
29
+ self,
30
+ user_uid: str,
31
+ permission: UserPermission,
32
+ updater_uid: Optional[str] = None
33
+ ) -> bool:
34
+ """
35
+ Adds a permission to a user using a UserPermission object.
36
+
37
+ Args:
38
+ user_uid: The user ID to add permission to
39
+ permission: UserPermission object containing all permission details
40
+ updater_uid: The ID of the user performing the update
41
+
42
+ Returns:
43
+ True if permission was added successfully
44
+
45
+ Raises:
46
+ UserStatusError: If user status not found
47
+ IAMPermissionError: If permission addition fails
48
+ """
49
+ self.logger.info(f"Adding {permission.iam_unit_type.value} permission for user {user_uid}: "
50
+ f"domain='{permission.domain}', name='{permission.permission_ref}'")
51
+
52
+ try:
53
+ userstatus = await self.userstatus_ops.get_userstatus(user_uid)
54
+ if not userstatus:
55
+ raise UserStatusError(f"UserStatus not found for user_uid {user_uid}")
56
+
57
+ # Use the model's method to add the permission directly
58
+ userstatus.add_permission(permission)
59
+
60
+ # Update the user status with the modified model
61
+ await self.userstatus_ops.update_userstatus(
62
+ user_uid=user_uid,
63
+ status_data=userstatus.model_dump(exclude_none=True),
64
+ updater_uid=updater_uid or "system"
65
+ )
66
+
67
+ self.logger.info(f"Successfully added permission for user {user_uid}")
68
+ return True
69
+
70
+ except UserStatusError as e:
71
+ self.logger.error("UserStatusError in add_permission_to_user: %s", e, exc_info=True)
72
+ raise
73
+ except Exception as e:
74
+ self.logger.error("Failed to add permission for user %s: %s", user_uid, e, exc_info=True)
75
+ raise IAMPermissionError(
76
+ detail=f"Failed to add permission: {str(e)}",
77
+ user_uid=user_uid,
78
+ operation="add_permission_to_user",
79
+ original_error=e
80
+ ) from e
81
+
82
+ async def remove_permission_from_user(
83
+ self,
84
+ user_uid: str,
85
+ domain: Optional[str] = None,
86
+ permission_type: Optional[IAMUnit] = None,
87
+ permission_name: Optional[str] = None,
88
+ source: Optional[str] = None,
89
+ updater_uid: Optional[str] = None
90
+ ) -> bool:
91
+ """
92
+ Removes permissions from a user based on flexible filter criteria.
93
+ At least one filter criteria must be provided.
94
+
95
+ Args:
96
+ user_uid: The user ID to remove permission from
97
+ domain: Optional domain filter (e.g., 'papp')
98
+ permission_type: Optional IAM assignment type filter (GROUP, ROLE, etc.)
99
+ permission_name: Optional permission name filter
100
+ source: Optional source filter
101
+ updater_uid: The ID of the user performing the update
102
+
103
+ Returns:
104
+ True if permission(s) were removed successfully
105
+
106
+ Raises:
107
+ UserStatusError: If user status not found
108
+ IAMPermissionError: If permission removal fails
109
+ ValueError: If no filter criteria are provided
110
+
111
+ Examples:
112
+ # Remove specific permission
113
+ await remove_permission_from_user(uid, domain="papp", permission_type=IAMUnit.ROLE, permission_name="analyst")
114
+
115
+ # Remove all permissions from a domain
116
+ await remove_permission_from_user(uid, domain="papp")
117
+
118
+ # Remove all permissions of a specific type
119
+ await remove_permission_from_user(uid, permission_type=IAMUnit.ROLE)
120
+
121
+ # Remove all permissions from a source
122
+ await remove_permission_from_user(uid, source="subscription_123")
123
+ """
124
+ if not any([domain, permission_type, permission_name, source]):
125
+ raise ValueError("At least one filter criteria must be provided")
126
+
127
+ # Build description for logging
128
+ filters = []
129
+ if domain: filters.append(f"domain='{domain}'")
130
+ if permission_type: filters.append(f"type='{permission_type.value}'")
131
+ if permission_name: filters.append(f"name='{permission_name}'")
132
+ if source: filters.append(f"source='{source}'")
133
+
134
+ filter_desc = ", ".join(filters)
135
+ self.logger.info(f"Removing permissions for user {user_uid} with filters: {filter_desc}")
136
+
137
+ try:
138
+ userstatus = await self.userstatus_ops.get_userstatus(user_uid)
139
+ if not userstatus:
140
+ raise UserStatusError(f"UserStatus not found for user_uid {user_uid}")
141
+
142
+ # Use the model's method to remove the permission assignment
143
+ removed_count = userstatus.remove_permission(
144
+ domain=domain,
145
+ iam_unit_type=permission_type,
146
+ permission_ref=permission_name,
147
+ source=source
148
+ )
149
+
150
+ if removed_count > 0:
151
+ # Update using the full model to maintain consistency
152
+ await self.userstatus_ops.update_userstatus(
153
+ user_uid=user_uid,
154
+ status_data=userstatus.model_dump(exclude_none=True),
155
+ updater_uid=updater_uid or "system"
156
+ )
157
+ self.logger.info(f"Successfully removed {removed_count} permission(s) for user {user_uid}")
158
+ return True
159
+ else:
160
+ self.logger.warning("No matching permissions found for user %s with filters: %s", user_uid, filter_desc)
161
+ return False
162
+
163
+ except ValueError as e:
164
+ self.logger.error("Invalid parameters in remove_permission_from_user: %s", e)
165
+ raise IAMPermissionError(
166
+ detail=f"Invalid parameters: {str(e)}",
167
+ user_uid=user_uid,
168
+ operation="remove_permission_from_user",
169
+ original_error=e
170
+ ) from e
171
+ except UserStatusError as e:
172
+ self.logger.error("UserStatusError in remove_permission_from_user: %s", e, exc_info=True)
173
+ raise
174
+ except Exception as e:
175
+ self.logger.error("Failed to remove permission for user %s: %s", user_uid, e, exc_info=True)
176
+ raise IAMPermissionError(
177
+ detail=f"Failed to remove permission: {str(e)}",
178
+ user_uid=user_uid,
179
+ operation="remove_permission_from_user",
180
+ original_error=e
181
+ ) from e
182
+
183
+ async def cleanup_expired_permissions_of_user(
184
+ self,
185
+ user_uid: str,
186
+ iam_unit_type: Optional[IAMUnit] = None,
187
+ updater_uid: Optional[str] = None
188
+ ) -> int:
189
+ """
190
+ Clean up expired permissions for a user.
191
+
192
+ Args:
193
+ user_uid: The user identifier
194
+ iam_unit_type: Optional filter for specific permission type
195
+ updater_uid: The ID of the user performing the cleanup
196
+
197
+ Returns:
198
+ Number of permissions removed
199
+ """
200
+ self.logger.info(f"Cleaning up expired permissions for user {user_uid}")
201
+ try:
202
+ userstatus = await self.userstatus_ops.get_userstatus(user_uid)
203
+ if not userstatus:
204
+ raise UserStatusError(f"UserStatus not found for user_uid {user_uid}")
205
+
206
+ removed_count = userstatus.cleanup_expired_permissions(iam_unit_type=iam_unit_type)
207
+
208
+ if removed_count > 0:
209
+ await self.userstatus_ops.update_userstatus(
210
+ user_uid=user_uid,
211
+ status_data=userstatus.model_dump(),
212
+ updater_uid=updater_uid or "system_cleanup_expired_permissions"
213
+ )
214
+ self.logger.info(f"Removed {removed_count} expired permissions for user {user_uid}")
215
+
216
+ return removed_count
217
+
218
+ except UserStatusError as e:
219
+ self.logger.error("UserStatusError in cleanup_expired_permissions_of_user: %s", e, exc_info=True)
220
+ raise
221
+ except Exception as e:
222
+ self.logger.error("Failed to cleanup expired permissions for user %s: %s", user_uid, e, exc_info=True)
223
+ raise IAMPermissionError(
224
+ detail=f"Failed to cleanup expired permissions: {str(e)}",
225
+ user_uid=user_uid,
226
+ operation="cleanup_expired_permissions",
227
+ original_error=e
228
+ ) from e
229
+
230
+ async def remove_all_permissions_from_user(
231
+ self,
232
+ user_uid: str,
233
+ source: Optional[str] = None,
234
+ updater_uid: Optional[str] = None
235
+ ) -> int:
236
+ """
237
+ Remove all permissions from a user, optionally filtered by source.
238
+
239
+ Args:
240
+ user_uid: The user identifier
241
+ source: Optional source filter (if None, removes all permissions)
242
+ updater_uid: The ID of the user performing the update
243
+
244
+ Returns:
245
+ Number of permissions removed
246
+ """
247
+ self.logger.info(f"Removing all permissions from user {user_uid}" +
248
+ (f" with source {source}" if source else ""))
249
+ try:
250
+ userstatus = await self.userstatus_ops.get_userstatus(user_uid)
251
+ if not userstatus:
252
+ raise UserStatusError(f"UserStatus not found for user_uid {user_uid}")
253
+
254
+ removed_count = userstatus.remove_all_permissions(source=source)
255
+
256
+ if removed_count > 0:
257
+ await self.userstatus_ops.update_userstatus(
258
+ user_uid=user_uid,
259
+ status_data=userstatus.model_dump(),
260
+ updater_uid=updater_uid or "system_remove_all_permissions"
261
+ )
262
+ self.logger.info(f"Removed {removed_count} permissions from user {user_uid}")
263
+
264
+ return removed_count
265
+
266
+ except UserStatusError as e:
267
+ self.logger.error("UserStatusError in remove_all_permissions_from_user: %s", e, exc_info=True)
268
+ raise
269
+ except Exception as e:
270
+ self.logger.error("Failed to remove all permissions from user %s: %s", user_uid, e, exc_info=True)
271
+ raise IAMPermissionError(
272
+ detail=f"Failed to remove all permissions: {str(e)}",
273
+ user_uid=user_uid,
274
+ operation="remove_all_permissions",
275
+ original_error=e
276
+ ) from e
277
+
278
+ async def get_permissions_of_user(
279
+ self,
280
+ user_uid: str,
281
+ valid_post_expir_date: Optional[datetime] = None,
282
+ domain: Optional[str] = None,
283
+ iam_unit_type: Optional[IAMUnit] = None,
284
+ source: Optional[str] = None
285
+ ) -> List[UserPermission]:
286
+ """
287
+ Retrieves IAM permissions for a user with flexible filtering options.
288
+
289
+ Args:
290
+ user_uid: The user identifier
291
+ valid_post_expir_date: If provided, only return permissions valid after this date
292
+ domain: Optional domain filter
293
+ iam_unit_type: Optional IAM unit type filter
294
+ source: Optional source filter
295
+
296
+ Returns:
297
+ List of matching UserPermission objects
298
+ """
299
+ self.logger.info(f"Getting permissions for user {user_uid} with filters")
300
+ try:
301
+ userstatus = await self.userstatus_ops.get_userstatus(user_uid)
302
+ if not userstatus:
303
+ raise UserStatusError(f"UserStatus not found for user_uid {user_uid}")
304
+
305
+ permissions = userstatus.iam_permissions
306
+
307
+ # Apply filters
308
+ if valid_post_expir_date:
309
+ permissions = [
310
+ perm for perm in permissions
311
+ if perm.expires_at is None or perm.expires_at > valid_post_expir_date
312
+ ]
313
+
314
+ if domain:
315
+ permissions = [perm for perm in permissions if perm.domain == domain]
316
+
317
+ if iam_unit_type:
318
+ permissions = [perm for perm in permissions if perm.iam_unit_type == iam_unit_type]
319
+
320
+ if source:
321
+ permissions = [perm for perm in permissions if perm.source == source]
322
+
323
+ return permissions
324
+
325
+ except UserStatusError as e:
326
+ self.logger.error("UserStatusError in get_permissions_of_user: %s", e, exc_info=True)
327
+ raise
328
+ except Exception as e:
329
+ self.logger.error("Failed to get permissions for user %s: %s", user_uid, e, exc_info=True)
330
+ raise IAMPermissionError(
331
+ detail=f"Failed to get permissions: {str(e)}",
332
+ user_uid=user_uid,
333
+ operation="get_user_permissions",
334
+ original_error=e
335
+ ) from e
336
+
337
+ async def get_bulk_users_with_permission(
338
+ self,
339
+ domain: str,
340
+ iam_unit_type: IAMUnit,
341
+ permission_ref: str,
342
+ limit: int = 100,
343
+ valid_only: bool = True
344
+ ) -> List[str]:
345
+ """
346
+ Get a list of user UIDs who have a specific permission.
347
+
348
+ Note: This is a basic implementation that queries the database.
349
+ For production use with large datasets, consider implementing
350
+ database-level queries for better performance.
351
+
352
+ Args:
353
+ domain: The domain for the permission
354
+ iam_unit_type: Type of IAM assignment
355
+ permission_ref: The permission identifier
356
+ limit: Maximum number of users to return (default: 100)
357
+ valid_only: If True, only return users with valid (non-expired) permissions
358
+
359
+ Returns:
360
+ List of user UIDs
361
+ """
362
+ self.logger.info(f"Getting bulk users with permission {domain}.{iam_unit_type.value}.{permission_ref}")
363
+ try:
364
+ # This is a simplified implementation - in production, you would want to use
365
+ # Firestore queries to filter users with specific permissions rather than
366
+ # fetching all users. For now, we'll return an empty list and log a warning.
367
+
368
+ self.logger.warning(
369
+ "get_bulk_users_with_permission: This method requires database-level "
370
+ "querying for optimal performance. Current implementation is not "
371
+ "suitable for production use with large user bases."
372
+ )
373
+
374
+ # TODO: Implement proper Firestore querying for users with specific permissions
375
+ # Example query structure:
376
+ # collection('userstatuses')
377
+ # .where('iam_permissions', 'array_contains', {
378
+ # 'domain': domain,
379
+ # 'iam_unit_type': iam_unit_type.value,
380
+ # 'permission_ref': permission_ref
381
+ # })
382
+ # .limit(limit)
383
+
384
+ return []
385
+
386
+ except Exception as e:
387
+ self.logger.error("Failed to get bulk users with permission: %s", e, exc_info=True)
388
+ raise IAMPermissionError(
389
+ detail=f"Failed to get bulk users with permission: {str(e)}",
390
+ operation="get_bulk_users_with_permission",
391
+ original_error=e
392
+ ) from e