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,307 @@
1
+ """
2
+ usertype Catalog Service
3
+
4
+ This service manages usertype templates stored in Firestore.
5
+ These templates are used to configure default settings for user profiles and statuses.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, List, Optional, Any
10
+ from google.cloud.firestore import Client
11
+ from ipulse_shared_base_ftredge import IAMUserType
12
+ from ipulse_shared_base_ftredge.enums.enums_status import ObjectOverallStatus
13
+ from ipulse_shared_core_ftredge.models.catalog.usertype import UserType
14
+ from ipulse_shared_core_ftredge.services.base.base_firestore_service import BaseFirestoreService
15
+
16
+ class CatalogUserTypeService(BaseFirestoreService[UserType]):
17
+ """
18
+ Service for managing usertype catalog configurations.
19
+
20
+ This service provides CRUD operations for usertype templates that define
21
+ the default settings and permissions for different usertypes.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ firestore_client: Client,
27
+ logger: Optional[logging.Logger] = None
28
+ ):
29
+ """
30
+ Initialize the usertype Catalog Service.
31
+
32
+ Args:
33
+ firestore_client: Firestore client instance
34
+ logger: Logger instance (optional)
35
+ """
36
+ super().__init__(
37
+ db=firestore_client,
38
+ collection_name="papp_core_catalog_usertypes",
39
+ resource_type="usertype",
40
+ model_class=UserType,
41
+ logger=logger or logging.getLogger(__name__)
42
+ )
43
+ self.archive_collection_name = "~archive_papp_core_catalog_usertypes"
44
+
45
+ async def create_usertype(
46
+ self,
47
+ usertype_id: str,
48
+ user_type: UserType,
49
+ creator_uid: str
50
+ ) -> UserType:
51
+ """
52
+ Create a new usertype.
53
+
54
+ Args:
55
+ usertype_id: Unique identifier for the usertype
56
+ user_type: Usertype data
57
+ creator_uid: UID of the user creating the usertype
58
+
59
+ Returns:
60
+ Created usertype
61
+
62
+ Raises:
63
+ ServiceError: If creation fails
64
+ ValidationError: If usertype data is invalid
65
+ """
66
+ self.logger.info(f"Creating usertype: {usertype_id}")
67
+
68
+ # Create the document
69
+ created_doc = await self.create_document(
70
+ doc_id=usertype_id,
71
+ data=user_type,
72
+ creator_uid=creator_uid
73
+ )
74
+
75
+ # Convert back to model
76
+ result = UserType.model_validate(created_doc)
77
+ self.logger.info(f"Successfully created usertype: {usertype_id}")
78
+ return result
79
+
80
+ async def get_usertype(self, usertype_id: str) -> Optional[UserType]:
81
+ """
82
+ Retrieve a usertype by ID.
83
+
84
+ Args:
85
+ usertype_id: Unique identifier for the usertype
86
+
87
+ Returns:
88
+ usertype if found, None otherwise
89
+
90
+ Raises:
91
+ ServiceError: If retrieval fails
92
+ """
93
+ self.logger.debug(f"Retrieving usertype: {usertype_id}")
94
+ doc_data = await self.get_document(usertype_id)
95
+ if doc_data is None:
96
+ return None
97
+ return UserType.model_validate(doc_data) if isinstance(doc_data, dict) else doc_data
98
+
99
+ async def update_usertype(
100
+ self,
101
+ usertype_id: str,
102
+ updates: Dict[str, Any],
103
+ updater_uid: str
104
+ ) -> UserType:
105
+ """
106
+ Update a usertype.
107
+
108
+ Args:
109
+ usertype_id: Unique identifier for the usertype
110
+ updates: Fields to update
111
+ updater_uid: UID of the user updating the usertype
112
+
113
+ Returns:
114
+ Updated usertype
115
+
116
+ Raises:
117
+ ServiceError: If update fails
118
+ ResourceNotFoundError: If usertype not found
119
+ ValidationError: If update data is invalid
120
+ """
121
+ self.logger.info(f"Updating usertype: {usertype_id}")
122
+
123
+ updated_doc = await self.update_document(
124
+ doc_id=usertype_id,
125
+ update_data=updates,
126
+ updater_uid=updater_uid
127
+ )
128
+
129
+ result = UserType.model_validate(updated_doc)
130
+ self.logger.info(f"Successfully updated usertype: {usertype_id}")
131
+ return result
132
+
133
+ async def delete_usertype(
134
+ self,
135
+ usertype_id: str,
136
+ archive: bool = True
137
+ ) -> bool:
138
+ """
139
+ Delete a usertype.
140
+
141
+ Args:
142
+ usertype_id: Unique identifier for the usertype
143
+ archive: Whether to archive the usertype before deletion
144
+
145
+ Returns:
146
+ True if deletion was successful
147
+
148
+ Raises:
149
+ ServiceError: If deletion fails
150
+ ResourceNotFoundError: If usertype not found
151
+ """
152
+ self.logger.info(f"Deleting usertype: {usertype_id}")
153
+
154
+ if archive:
155
+ # Get the usertype data before deletion for archiving
156
+ template = await self.get_usertype(usertype_id)
157
+ if template:
158
+ await self.archive_document(
159
+ document_data=template.model_dump(),
160
+ doc_id=usertype_id,
161
+ archive_collection=self.archive_collection_name,
162
+ archived_by="system"
163
+ )
164
+
165
+ result = await self.delete_document(usertype_id)
166
+ self.logger.info(f"Successfully deleted usertype: {usertype_id}")
167
+ return result
168
+
169
+ async def list_usertypes(
170
+ self,
171
+ primary_usertype: Optional[IAMUserType] = None,
172
+ pulse_status: Optional[ObjectOverallStatus] = None,
173
+ latest_version_only: bool = False,
174
+ limit: Optional[int] = None
175
+ ) -> List[UserType]:
176
+ """
177
+ List usertypes with optional filtering.
178
+
179
+ Args:
180
+ primary_usertype: Filter by specific primary usertype
181
+ pulse_status: Filter by specific pulse status
182
+ latest_version_only: Only return the latest version per usertype
183
+ limit: Maximum number of usertypes to return
184
+
185
+ Returns:
186
+ List of usertypes
187
+
188
+ Raises:
189
+ ServiceError: If listing fails
190
+ """
191
+ self.logger.debug(f"Listing usertypes - primary_usertype: {primary_usertype}, pulse_status: {pulse_status}, latest_version_only: {latest_version_only}")
192
+
193
+ # Build query filters
194
+ filters = []
195
+ if primary_usertype:
196
+ filters.append(("primary_usertype", "==", primary_usertype.value))
197
+ if pulse_status:
198
+ filters.append(("pulse_status", "==", pulse_status.value))
199
+
200
+ # If latest_version_only is requested, order by version descending
201
+ order_by = None
202
+ if latest_version_only:
203
+ order_by = "version"
204
+ # We'll need to group by primary_usertype and take the first from each group
205
+ # This is more complex, so let's handle it differently
206
+
207
+ docs = await self.list_documents(
208
+ filters=filters,
209
+ order_by=order_by,
210
+ limit=limit
211
+ )
212
+
213
+ # Convert to UserType models
214
+ usertypes = [UserType.model_validate(doc) if isinstance(doc, dict) else doc for doc in docs]
215
+
216
+ # If latest_version_only is requested, group by primary_usertype and get highest version
217
+ if latest_version_only:
218
+ # Group by primary_usertype
219
+ usertype_groups = {}
220
+ for usertype in usertypes:
221
+ key = usertype.primary_usertype.value
222
+ if key not in usertype_groups:
223
+ usertype_groups[key] = []
224
+ usertype_groups[key].append(usertype)
225
+
226
+ # Get the latest version for each group
227
+ latest_usertypes = []
228
+ for group in usertype_groups.values():
229
+ if group:
230
+ # Sort by version descending and take the first
231
+ latest = max(group, key=lambda x: x.version)
232
+ latest_usertypes.append(latest)
233
+
234
+ return latest_usertypes
235
+
236
+ return usertypes
237
+
238
+ def _get_collection(self):
239
+ """Get the Firestore collection reference."""
240
+ return self.db.collection(self.collection_name)
241
+
242
+ async def usertype_exists(self, usertype_id: str) -> bool:
243
+ """
244
+ Check if a usertype exists.
245
+
246
+ Args:
247
+ usertype_id: Unique identifier for the usertype
248
+
249
+ Returns:
250
+ True if usertype exists, False otherwise
251
+
252
+ Raises:
253
+ ServiceError: If check fails
254
+ """
255
+ return await self.document_exists(usertype_id)
256
+
257
+ async def validate_usertype_data(self, usertype_data: Dict[str, Any]) -> tuple[bool, List[str]]:
258
+ """
259
+ Validate usertype data.
260
+
261
+ Args:
262
+ usertype_data: Usertype data to validate
263
+
264
+ Returns:
265
+ Tuple of (is_valid, list_of_errors)
266
+ """
267
+ try:
268
+ UserType.model_validate(usertype_data)
269
+ return True, []
270
+ except Exception as e:
271
+ return False, [str(e)]
272
+
273
+
274
+ async def get_default_credits_for_usertype(self, primary_usertype: IAMUserType) -> Dict[str, int]:
275
+ """
276
+ Get default credit settings for a specific usertype.
277
+
278
+ Args:
279
+ primary_usertype: The primary usertype
280
+
281
+ Returns:
282
+ Dictionary with default credit values
283
+
284
+ Raises:
285
+ ServiceError: If retrieval fails
286
+ """
287
+ self.logger.debug(f"Getting default credits for usertype: {primary_usertype}")
288
+
289
+ usertypes = await self.list_usertypes(
290
+ primary_usertype=primary_usertype,
291
+ pulse_status=ObjectOverallStatus.ACTIVE,
292
+ latest_version_only=True,
293
+ limit=1
294
+ )
295
+ usertype = usertypes[0] if usertypes else None
296
+ if not usertype:
297
+ self.logger.warning(f"No active usertype found for: {primary_usertype}")
298
+ return {
299
+ "subscription_based_insight_credits": 0,
300
+ "extra_insight_credits": 0,
301
+ "voting_credits": 0
302
+ }
303
+
304
+ return {
305
+ "extra_insight_credits": usertype.default_extra_insight_credits,
306
+ "voting_credits": usertype.default_voting_credits
307
+ }
@@ -1,7 +1,7 @@
1
1
  """Reusable credit checking and charging utilities for services."""
2
2
  import os
3
3
  from typing import Dict, Any, List, Optional, Callable, Awaitable, TypeVar
4
- from ipulse_shared_core_ftredge.services.charging_service import ChargingService, ValidationError
4
+ from ipulse_shared_core_ftredge.services.user_charging_service import UserChargingService, ValidationError
5
5
  import logging
6
6
 
7
7
  T = TypeVar('T')
@@ -9,8 +9,8 @@ T = TypeVar('T')
9
9
  class ChargingProcessor:
10
10
  """Handles credit checking and charging for both single item and batch access."""
11
11
 
12
- def __init__(self, charging_service: ChargingService, logger: logging.Logger):
13
- self.charging_service = charging_service
12
+ def __init__(self, user_charging_service: UserChargingService, logger: logging.Logger):
13
+ self.user_charging_service = user_charging_service
14
14
  self.logger = logger
15
15
 
16
16
  async def process_single_item_charging(
@@ -54,9 +54,9 @@ class ChargingProcessor:
54
54
  # For free items, provide current credits if available
55
55
  if pre_fetched_credits:
56
56
  updated_user_credits = pre_fetched_credits
57
- elif self.charging_service: # Attempt to get current credits if not pre-fetched
57
+ elif self.user_charging_service: # Attempt to get current credits if not pre-fetched
58
58
  try:
59
- _, current_user_credits_from_verify = await self.charging_service.verify_credits(user_uid, 0, None)
59
+ _, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
60
60
  updated_user_credits = current_user_credits_from_verify
61
61
  except Exception: # pylint: disable=broad-except
62
62
  self.logger.warning(f"Could not fetch current credits for user {user_uid} for free item.")
@@ -75,9 +75,9 @@ class ChargingProcessor:
75
75
  # Similar to free items, provide current credits if available
76
76
  if pre_fetched_credits:
77
77
  updated_user_credits = pre_fetched_credits
78
- elif self.charging_service:
78
+ elif self.user_charging_service:
79
79
  try:
80
- _, current_user_credits_from_verify = await self.charging_service.verify_credits(user_uid, 0, None)
80
+ _, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
81
81
  updated_user_credits = current_user_credits_from_verify
82
82
  except Exception: # pylint: disable=broad-except
83
83
  self.logger.warning(f"Could not fetch current credits for user {user_uid} during debug bypass.")
@@ -90,7 +90,7 @@ class ChargingProcessor:
90
90
  }
91
91
 
92
92
  # Verify credit service is available
93
- if not self.charging_service:
93
+ if not self.user_charging_service:
94
94
  self.logger.error("ChargingService not initialized.")
95
95
  return {
96
96
  'access_granted': False,
@@ -101,7 +101,7 @@ class ChargingProcessor:
101
101
  }
102
102
 
103
103
  # Verify user has enough credits
104
- has_credits, current_user_credits_from_verify = await self.charging_service.verify_credits(
104
+ has_credits, current_user_credits_from_verify = await self.user_charging_service.verify_credits(
105
105
  user_uid,
106
106
  credit_cost,
107
107
  pre_fetched_user_credits=pre_fetched_credits
@@ -121,7 +121,7 @@ class ChargingProcessor:
121
121
  }
122
122
 
123
123
  # Charge the user - this now returns (bool, Optional[Dict])
124
- charged, calculated_updated_credits = await self.charging_service.charge_credits_transaction(
124
+ charged, calculated_updated_credits = await self.user_charging_service.charge_credits_transaction(
125
125
  user_uid,
126
126
  credit_cost,
127
127
  operation_description
@@ -143,9 +143,9 @@ class ChargingProcessor:
143
143
  except ValidationError as ve:
144
144
  self.logger.error(f"Validation error for item {item_id}, user {user_uid}: {str(ve)}")
145
145
  # Try to get current credits to return
146
- if self.charging_service:
146
+ if self.user_charging_service:
147
147
  try:
148
- _, updated_user_credits = await self.charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
148
+ _, updated_user_credits = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
149
149
  except Exception: # pylint: disable=broad-except
150
150
  pass # Keep updated_user_credits as None
151
151
  ve.additional_info = ve.additional_info or {}
@@ -155,9 +155,9 @@ class ChargingProcessor:
155
155
  self.logger.error(f"Unexpected error during credit processing for item {item_id}, user {user_uid}: {str(e)}", exc_info=True)
156
156
  # Try to get current credits to return
157
157
  current_user_credits_on_error = None
158
- if self.charging_service:
158
+ if self.user_charging_service:
159
159
  try:
160
- _, current_user_credits_on_error = await self.charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
160
+ _, current_user_credits_on_error = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
161
161
  except Exception: # pylint: disable=broad-except
162
162
  pass
163
163
  return {
@@ -228,9 +228,9 @@ class ChargingProcessor:
228
228
  if not paid_items:
229
229
  if pre_fetched_credits:
230
230
  updated_user_credits = pre_fetched_credits
231
- elif self.charging_service:
231
+ elif self.user_charging_service:
232
232
  try:
233
- _, current_user_credits_from_verify = await self.charging_service.verify_credits(user_uid, 0, None)
233
+ _, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
234
234
  updated_user_credits = current_user_credits_from_verify
235
235
 
236
236
  except Exception: # pylint: disable=broad-except
@@ -250,9 +250,9 @@ class ChargingProcessor:
250
250
  self.logger.info(f"Bypassing credit check for {len(paid_items)} paid items due to debug mode")
251
251
  if pre_fetched_credits:
252
252
  updated_user_credits = pre_fetched_credits
253
- elif self.charging_service:
253
+ elif self.user_charging_service:
254
254
  try:
255
- _, current_user_credits_from_verify = await self.charging_service.verify_credits(user_uid, 0, None)
255
+ _, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, None)
256
256
  updated_user_credits = current_user_credits_from_verify
257
257
  except Exception: # pylint: disable=broad-except
258
258
  self.logger.warning(f"Could not fetch current credits for user {user_uid} during debug bypass for batch.")
@@ -267,7 +267,7 @@ class ChargingProcessor:
267
267
  }
268
268
 
269
269
  # Verify credit service is available
270
- if not self.charging_service:
270
+ if not self.user_charging_service:
271
271
  self.logger.error("ChargingService not initialized for batch processing.")
272
272
  return {
273
273
  'accessible_items': free_items,
@@ -280,7 +280,7 @@ class ChargingProcessor:
280
280
 
281
281
  try:
282
282
  # Verify user has enough credits for total cost
283
- has_credits, current_user_credits_from_verify = await self.charging_service.verify_credits(
283
+ has_credits, current_user_credits_from_verify = await self.user_charging_service.verify_credits(
284
284
  user_uid,
285
285
  total_cost,
286
286
  pre_fetched_user_credits=pre_fetched_credits
@@ -300,7 +300,7 @@ class ChargingProcessor:
300
300
  }
301
301
 
302
302
  # Charge the user for all paid items
303
- charged, calculated_updated_credits = await self.charging_service.charge_credits_transaction(
303
+ charged, calculated_updated_credits = await self.user_charging_service.charge_credits_transaction(
304
304
  user_uid,
305
305
  total_cost,
306
306
  f"{operation_description} ({len(paid_items)} items, total cost: {total_cost})"
@@ -322,9 +322,9 @@ class ChargingProcessor:
322
322
 
323
323
  except ValidationError as ve:
324
324
  self.logger.error(f"Validation error during batch credit check for user {user_uid}: {str(ve)}")
325
- if self.charging_service:
325
+ if self.user_charging_service:
326
326
  try:
327
- _, current_user_credits_from_verify = await self.charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
327
+ _, current_user_credits_from_verify = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
328
328
  updated_user_credits = current_user_credits_from_verify
329
329
  except Exception: # pylint: disable=broad-except
330
330
  pass
@@ -334,9 +334,9 @@ class ChargingProcessor:
334
334
  except Exception as e:
335
335
  self.logger.error(f"Unexpected error during batch credit check for user {user_uid}: {str(e)}", exc_info=True)
336
336
  current_credits_on_error = None
337
- if self.charging_service:
337
+ if self.user_charging_service:
338
338
  try:
339
- _, current_credits_on_error = await self.charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
339
+ _, current_credits_on_error = await self.user_charging_service.verify_credits(user_uid, 0, pre_fetched_credits)
340
340
  updated_user_credits = current_credits_on_error
341
341
  except Exception: # pylint: disable=broad-except
342
342
  pass
@@ -10,28 +10,8 @@ This module contains all user-related services organized into specialized operat
10
10
  - User-specific exceptions: Specialized exception classes for user operations
11
11
  """
12
12
 
13
- from .user_account_operations import UserAccountOperations
14
- from .subscription_management_operations import (
15
- SubscriptionManagementOperations,
16
- SubscriptionPlanDocument
17
- )
18
- from .iam_management_operations import IAMManagementOperations
19
- from .user_auth_operations import UserAuthOperations
20
- from .user_holistic_operations import UserHolisticOperations
21
- from .user_core_service import UserCoreService, UserTypeDefaultsDocument
22
-
23
- __all__ = [
24
- # Operation classes
25
- 'UserAccountOperations',
26
- 'SubscriptionManagementOperations',
27
- 'IAMManagementOperations',
28
- 'UserAuthOperations',
29
- 'UserHolisticOperations',
30
-
31
- # Main orchestrating service
32
- 'UserCoreService',
33
-
34
- # Supporting models
35
- 'SubscriptionPlanDocument',
36
- 'UserTypeDefaultsDocument'
37
- ]
13
+ from .user_subscription_operations import UsersubscriptionOperations
14
+ from .user_permissions_operations import UserpermissionsOperations
15
+ from .userauth_operations import UserauthOperations
16
+ from .user_multistep_operations import UsermultistepOperations
17
+ from .user_core_service import UserCoreService
@@ -0,0 +1,160 @@
1
+ """
2
+ Firebase Admin Helper Functions
3
+
4
+ This module provides utility functions for managing Firebase Auth users and permissions.
5
+ These functions are designed for admin operations and testing purposes.
6
+ """
7
+
8
+ import os
9
+ import time
10
+ import logging
11
+ from typing import Dict, Any, List, Optional, Tuple
12
+ import firebase_admin
13
+ from firebase_admin import auth
14
+ from ipulse_shared_base_ftredge import log_info, log_warning, log_error, log_debug, LogLevel
15
+
16
+
17
+ def get_user_auth_token(
18
+ email: str,
19
+ password: str,
20
+ api_key: str,
21
+ logger: Optional[logging.Logger] = None,
22
+ print_out: bool = False,
23
+ debug: bool = False
24
+ ) -> Optional[str]:
25
+ """
26
+ Get a user authentication token using the Firebase REST API.
27
+
28
+ Args:
29
+ email: User email
30
+ password: User password
31
+ api_key: Firebase API key
32
+ logger: Optional logger instance
33
+ print_out: Whether to print output
34
+ debug: Whether to print detailed debug info
35
+
36
+ Returns:
37
+ ID token or None if failed
38
+ """
39
+ import requests # Import here to keep it optional
40
+
41
+ url = f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={api_key}"
42
+ payload = {
43
+ "email": email,
44
+ "password": password,
45
+ "returnSecureToken": True
46
+ }
47
+
48
+ try:
49
+ if debug:
50
+ log_info(f"Sending authentication request to: {url}", logger=logger, print_out=print_out)
51
+ log_info(f"Request payload: {payload}", logger=logger, print_out=print_out)
52
+
53
+ response = requests.post(url, json=payload)
54
+
55
+ # Add detailed error logging
56
+ if not response.ok:
57
+ error_details = response.text
58
+ try:
59
+ error_json = response.json()
60
+ if "error" in error_json:
61
+ error_details = f"{error_json['error'].get('message', 'Unknown error')}"
62
+ except Exception:
63
+ pass
64
+
65
+ log_error(f"Auth error ({response.status_code}): {error_details}", logger=logger, print_out=print_out)
66
+
67
+ # Check for specific error conditions
68
+ if "EMAIL_NOT_FOUND" in error_details or "INVALID_PASSWORD" in error_details:
69
+ log_error(f"Authentication failed - invalid credentials for {email}", logger=logger, print_out=print_out)
70
+ elif "USER_DISABLED" in error_details:
71
+ log_error(f"User account is disabled: {email}", logger=logger, print_out=print_out)
72
+ elif "INVALID_EMAIL" in error_details:
73
+ log_error(f"Invalid email format: {email}", logger=logger, print_out=print_out)
74
+
75
+ return None
76
+
77
+ token = response.json().get("idToken")
78
+ log_info(f"Successfully obtained auth token for {email}", logger=logger, print_out=print_out)
79
+ return token
80
+ except Exception as e:
81
+ log_error(f"Error getting auth token: {e}", logger=logger, print_out=print_out)
82
+ return None
83
+
84
+ def list_users(max_results: int = 1000, logger: Optional[logging.Logger] = None, print_out: bool = False) -> List[Dict[str, Any]]:
85
+ """
86
+ List users from Firebase Auth.
87
+
88
+ Args:
89
+ max_results: Maximum number of users to return
90
+ logger: Optional logger instance
91
+ print_out: Whether to print output
92
+
93
+ Returns:
94
+ List of user dicts
95
+ """
96
+ try:
97
+ users = []
98
+ page = auth.list_users()
99
+ for user in page.users:
100
+ users.append(user._data)
101
+ if len(users) >= max_results:
102
+ break
103
+
104
+ log_info(f"Listed {len(users)} users from Firebase Auth", logger=logger, print_out=print_out)
105
+ return users
106
+ except Exception as e:
107
+ log_error(f"Error listing users: {e}", logger=logger, print_out=print_out)
108
+ return []
109
+
110
+ def create_custom_token(
111
+ user_uid: str,
112
+ additional_claims: Dict[str, Any] = None,
113
+ logger: Optional[logging.Logger] = None,
114
+ print_out: bool = False
115
+ ) -> str:
116
+ """
117
+ Create a custom token for a user.
118
+
119
+ Args:
120
+ user_uid: User's UID
121
+ additional_claims: Additional claims to include in the token
122
+ logger: Optional logger instance
123
+ print_out: Whether to print output
124
+
125
+ Returns:
126
+ Custom token
127
+ """
128
+ try:
129
+ token = auth.create_custom_token(user_uid, additional_claims)
130
+ log_debug(f"Created custom token for user {user_uid}", logger=logger, print_out=print_out)
131
+ return token
132
+ except Exception as e:
133
+ log_error(f"Error creating custom token: {e}", logger=logger, print_out=print_out)
134
+ raise
135
+
136
+ def verify_id_token(
137
+ token: str,
138
+ check_revoked: bool = False,
139
+ logger: Optional[logging.Logger] = None,
140
+ print_out: bool = False
141
+ ) -> Dict[str, Any]:
142
+ """
143
+ Verify an ID token.
144
+
145
+ Args:
146
+ token: ID token to verify
147
+ check_revoked: Whether to check if the token has been revoked
148
+ logger: Optional logger instance
149
+ print_out: Whether to print output
150
+
151
+ Returns:
152
+ Token claims
153
+ """
154
+ try:
155
+ claims = auth.verify_id_token(token, check_revoked=check_revoked)
156
+ log_debug(f"Verified ID token for user {claims.get('uid')}", logger=logger, print_out=print_out)
157
+ return claims
158
+ except Exception as e:
159
+ log_error(f"Error verifying ID token: {e}", logger=logger, print_out=print_out)
160
+ raise