trovesuite 1.0.10__tar.gz → 1.0.12__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 (42) hide show
  1. {trovesuite-1.0.10/src/trovesuite.egg-info → trovesuite-1.0.12}/PKG-INFO +1 -1
  2. {trovesuite-1.0.10 → trovesuite-1.0.12}/pyproject.toml +2 -2
  3. {trovesuite-1.0.10 → trovesuite-1.0.12}/setup.py +1 -1
  4. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/auth_read_dto.py +3 -2
  5. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/auth_service.py +93 -64
  6. {trovesuite-1.0.10 → trovesuite-1.0.12/src/trovesuite.egg-info}/PKG-INFO +1 -1
  7. {trovesuite-1.0.10 → trovesuite-1.0.12}/LICENSE +0 -0
  8. {trovesuite-1.0.10 → trovesuite-1.0.12}/MANIFEST.in +0 -0
  9. {trovesuite-1.0.10 → trovesuite-1.0.12}/README.md +0 -0
  10. {trovesuite-1.0.10 → trovesuite-1.0.12}/requirements.txt +0 -0
  11. {trovesuite-1.0.10 → trovesuite-1.0.12}/setup.cfg +0 -0
  12. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/__init__.py +0 -0
  13. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/__init__.py +0 -0
  14. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/auth_base.py +0 -0
  15. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/auth_controller.py +0 -0
  16. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/auth/auth_write_dto.py +0 -0
  17. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/configs/__init__.py +0 -0
  18. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/configs/database.py +0 -0
  19. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/configs/logging.py +0 -0
  20. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/configs/settings.py +0 -0
  21. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/entities/__init__.py +0 -0
  22. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/entities/health.py +0 -0
  23. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/entities/sh_response.py +0 -0
  24. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/__init__.py +0 -0
  25. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/notification_base.py +0 -0
  26. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/notification_controller.py +0 -0
  27. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/notification_read_dto.py +0 -0
  28. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/notification_service.py +0 -0
  29. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/notification/notification_write_dto.py +0 -0
  30. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/__init__.py +0 -0
  31. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/storage_base.py +0 -0
  32. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/storage_controller.py +0 -0
  33. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/storage_read_dto.py +0 -0
  34. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/storage_service.py +0 -0
  35. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/storage/storage_write_dto.py +0 -0
  36. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/utils/__init__.py +0 -0
  37. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite/utils/helper.py +0 -0
  38. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite.egg-info/SOURCES.txt +0 -0
  39. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite.egg-info/dependency_links.txt +0 -0
  40. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite.egg-info/not-zip-safe +0 -0
  41. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite.egg-info/requires.txt +0 -0
  42. {trovesuite-1.0.10 → trovesuite-1.0.12}/src/trovesuite.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trovesuite
3
- Version: 1.0.10
3
+ Version: 1.0.12
4
4
  Summary: TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications
5
5
  Home-page: https://dev.azure.com/brightgclt/trovesuite/_git/packages
6
6
  Author: Bright Debrah Owusu
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "trovesuite"
7
- version = "1.0.10"
7
+ version = "1.0.12"
8
8
  description = "TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications"
9
9
  authors = ["brightgclt <brightgclt@gmail.com>"]
10
10
  license = "MIT"
@@ -58,7 +58,7 @@ Documentation = "https://dev.azure.com/brightgclt/trovesuite/_git/packages"
58
58
 
59
59
  [project]
60
60
  name = "trovesuite"
61
- version = "1.0.10"
61
+ version = "1.0.12"
62
62
  description = "TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications"
63
63
  readme = "README.md"
64
64
  license = {text = "MIT"}
@@ -15,7 +15,7 @@ with open("pyproject.toml", "r", encoding="utf-8") as fh:
15
15
 
16
16
  setup(
17
17
  name="trovesuite",
18
- version="1.0.10",
18
+ version="1.0.12",
19
19
  author="Bright Debrah Owusu",
20
20
  author_email="owusu.debrah@deladetech.com",
21
21
  description="TroveSuite services package providing authentication, authorization, notifications, and other enterprise services for TroveSuite applications",
@@ -3,8 +3,8 @@ from pydantic import BaseModel
3
3
 
4
4
  class AuthControllerReadDto(BaseModel):
5
5
  org_id: Optional[str] = None
6
- bus_id: Optional[str] = None
7
- app_id: Optional[str] = None
6
+ bus_id: Optional[str] = None
7
+ app_id: Optional[str] = None
8
8
  shared_resource_id: Optional[str] = None
9
9
  user_id: Optional[str] = None
10
10
  group_id: Optional[str] = None
@@ -12,6 +12,7 @@ class AuthControllerReadDto(BaseModel):
12
12
  tenant_id: Optional[str] = None
13
13
  permissions: Optional[List[str]] = None
14
14
  resource_id: Optional[str] = None
15
+ resource_type: Optional[str] = None
15
16
 
16
17
  class AuthServiceReadDto(AuthControllerReadDto):
17
18
  pass
@@ -45,10 +45,10 @@ class AuthService:
45
45
 
46
46
  @staticmethod
47
47
  def authorize(data: AuthServiceWriteDto) -> Respons[AuthServiceReadDto]:
48
-
48
+
49
49
  user_id: str = data.user_id
50
50
  tenant_id: str = data.tenant_id
51
-
51
+
52
52
  """Check if a user is authorized based on login settings and roles"""
53
53
  # Input validation
54
54
  if not user_id or not isinstance(user_id, str):
@@ -59,7 +59,7 @@ class AuthService:
59
59
  status_code=400,
60
60
  error="INVALID_USER_ID"
61
61
  )
62
-
62
+
63
63
  if not tenant_id or not isinstance(tenant_id, str):
64
64
  return Respons[AuthServiceReadDto](
65
65
  detail="Invalid tenant_id: must be a non-empty string",
@@ -75,9 +75,9 @@ class AuthService:
75
75
  f"SELECT is_verified FROM {db_settings.MAIN_TENANTS_TABLE} WHERE delete_status = 'NOT_DELETED' AND id = %s",
76
76
  (tenant_id,),
77
77
  )
78
-
78
+
79
79
  if not is_tenant_verified or len(is_tenant_verified) == 0:
80
- logger.warning("Login failed - tenant not found: %s", tenant_id)
80
+ logger.warning("Authorization failed - tenant not found: %s", tenant_id)
81
81
  return Respons[AuthServiceReadDto](
82
82
  detail=f"Tenant '{tenant_id}' not found or has been deleted",
83
83
  data=[],
@@ -85,9 +85,9 @@ class AuthService:
85
85
  status_code=404,
86
86
  error="TENANT_NOT_FOUND"
87
87
  )
88
-
88
+
89
89
  if not is_tenant_verified[0]['is_verified']:
90
- logger.warning("Login failed - tenant not verified for user: %s, tenant: %s", user_id, tenant_id)
90
+ logger.warning("Authorization failed - tenant not verified for user: %s, tenant: %s", user_id, tenant_id)
91
91
  return Respons[AuthServiceReadDto](
92
92
  detail=f"Tenant '{tenant_id}' is not verified. Please contact your administrator.",
93
93
  data=[],
@@ -96,14 +96,36 @@ class AuthService:
96
96
  error="TENANT_NOT_VERIFIED"
97
97
  )
98
98
 
99
- login_settings_details = DatabaseManager.execute_query(
100
- f"""SELECT user_id, group_id, is_suspended, can_always_login,
101
- is_multi_factor_enabled, is_login_before, working_days,
102
- login_on, logout_on FROM "{tenant_id}".{db_settings.TENANT_LOGIN_SETTINGS_TABLE}
103
- WHERE (delete_status = 'NOT_DELETED' AND is_active = true ) AND user_id = %s""",
104
- (user_id,),
99
+ # 1️⃣ Get all groups the user belongs to
100
+ user_groups = DatabaseManager.execute_query(
101
+ f"""SELECT group_id FROM "{tenant_id}".{db_settings.TENANT_USER_GROUPS_TABLE}
102
+ WHERE delete_status = 'NOT_DELETED' AND is_active = true AND user_id = %s""",(user_id,),
105
103
  )
106
104
 
105
+ # 2️⃣ Prepare list of group_ids
106
+ group_ids = [g["group_id"] for g in user_groups] if user_groups else []
107
+
108
+ # 3️⃣ Get login settings - check user-level first, then group-level
109
+ if group_ids:
110
+ login_settings_details = DatabaseManager.execute_query(
111
+ f"""SELECT user_id, group_id, is_suspended, can_always_login,
112
+ is_multi_factor_enabled, is_login_before, working_days,
113
+ login_on, logout_on FROM "{tenant_id}".{db_settings.TENANT_LOGIN_SETTINGS_TABLE}
114
+ WHERE (delete_status = 'NOT_DELETED' AND is_active = true )
115
+ AND (user_id = %s OR group_id = ANY(%s))
116
+ ORDER BY user_id NULLS LAST
117
+ LIMIT 1""",
118
+ (user_id, group_ids),
119
+ )
120
+ else:
121
+ login_settings_details = DatabaseManager.execute_query(
122
+ f"""SELECT user_id, group_id, is_suspended, can_always_login,
123
+ is_multi_factor_enabled, is_login_before, working_days,
124
+ login_on, logout_on FROM "{tenant_id}".{db_settings.TENANT_LOGIN_SETTINGS_TABLE}
125
+ WHERE (delete_status = 'NOT_DELETED' AND is_active = true ) AND user_id = %s""",
126
+ (user_id,),
127
+ )
128
+
107
129
  if not login_settings_details or len(login_settings_details) == 0:
108
130
  logger.warning("Authorization failed - user not found: %s in tenant: %s", user_id, tenant_id)
109
131
  return Respons[AuthServiceReadDto](
@@ -124,56 +146,67 @@ class AuthService:
124
146
  error="USER_SUSPENDED"
125
147
  )
126
148
 
149
+ # ✅ UPDATED: Mutually exclusive login restrictions logic
127
150
  if not login_settings_details[0]['can_always_login']:
128
- current_day = datetime.now().strftime("%A").upper()
129
-
130
- if current_day not in login_settings_details[0]['working_days']:
131
- logger.warning("Authorization failed - outside working days for user: %s checking custom login period", user_id)
132
-
151
+ # Get from database (should already be datetime objects)
152
+ login_on = login_settings_details[0]['login_on']
153
+ logout_on = login_settings_details[0]['logout_on']
154
+ working_days = login_settings_details[0]['working_days']
155
+
156
+ # Only ONE restriction type can be active at a time:
157
+ # 1. working_days restriction (if login_on/logout_on are NULL)
158
+ # 2. time period restriction (if login_on/logout_on are set)
159
+
160
+ if login_on and logout_on:
161
+ # Time period restriction is active
162
+ logger.info(f"Checking time period restriction for user: {user_id}")
163
+
133
164
  # Get current datetime (full date and time) with timezone
134
- current_datetime = datetime.now(timezone.utc).replace(microsecond=0, second=0)
135
-
136
- # Get from database (should already be datetime objects)
137
- login_on = login_settings_details[0]['login_on']
138
- logout_on = login_settings_details[0]['logout_on']
139
-
140
- # Set defaults if None (with timezone awareness)
141
- if not login_on:
142
- login_on = datetime.min.replace(tzinfo=timezone.utc)
143
- if not logout_on:
144
- logout_on = datetime.max.replace(tzinfo=timezone.utc)
165
+ current_datetime = datetime.now(timezone.utc).replace(
166
+ microsecond=0, second=0
167
+ )
145
168
 
146
169
  # Compare full datetime objects (both date and time)
147
170
  if not (login_on <= current_datetime <= logout_on):
148
- logger.warning("Authorization failed - outside allowed period for user: %s", user_id)
171
+ logger.warning(
172
+ f"Authorization failed - outside allowed period for user: {user_id}"
173
+ )
149
174
  return Respons[AuthServiceReadDto](
150
- detail="Login is not allowed at this time. Please check your access schedule.",
175
+ detail="Access is not allowed at this time. Please check your access schedule.",
151
176
  data=[],
152
177
  success=False,
153
178
  status_code=403,
154
179
  error="LOGIN_TIME_RESTRICTED"
155
180
  )
156
-
157
- # 1️⃣ Get all groups the user belongs to
158
- user_groups = DatabaseManager.execute_query(
159
- f"""SELECT group_id FROM "{tenant_id}".{db_settings.TENANT_USER_GROUPS_TABLE}
160
- WHERE delete_status = 'NOT_DELETED' AND is_active = true AND user_id = %s""",(user_id,),
161
- )
181
+ elif working_days:
182
+ # Working days restriction is active
183
+ logger.info(f"Checking working days restriction for user: {user_id}")
184
+ current_day = datetime.now().strftime("%A").upper()
162
185
 
163
- # 2️⃣ Prepare list of group_ids
164
- group_ids = [g["group_id"] for g in user_groups] if user_groups else []
186
+ if current_day not in working_days:
187
+ logger.warning(
188
+ f"Authorization failed - not a working day for user: {user_id}"
189
+ )
190
+ return Respons[AuthServiceReadDto](
191
+ detail="Access is not allowed on this day. Please contact your administrator.",
192
+ data=[],
193
+ success=False,
194
+ status_code=403,
195
+ error="LOGIN_DAY_RESTRICTED"
196
+ )
165
197
 
166
- # 3️⃣ Build query dynamically to include groups (if any) + user
198
+ # 4️⃣ Build query dynamically to include groups (if any) + user
199
+ # ⚠️ CHANGED: Simplified to new schema - only select user_id, group_id, role_id, resource_type
167
200
  if group_ids:
168
201
  get_user_roles = DatabaseManager.execute_query(
169
202
  f"""
170
- SELECT DISTINCT ON (org_id, group_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id)
171
- org_id, group_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id
203
+ SELECT DISTINCT ON (group_id, user_id, role_id)
204
+ group_id, user_id, role_id, resource_type
172
205
  FROM "{tenant_id}".{db_settings.TENANT_ASSIGN_ROLES_TABLE}
173
206
  WHERE delete_status = 'NOT_DELETED'
174
207
  AND is_active = true
175
208
  AND (user_id = %s OR group_id = ANY(%s))
176
- ORDER BY org_id, group_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id;
209
+ ORDER BY group_id, user_id, role_id;
177
210
  """,
178
211
  (user_id, group_ids),
179
212
  )
@@ -181,13 +214,13 @@ class AuthService:
181
214
  # No groups, just check roles for user
182
215
  get_user_roles = DatabaseManager.execute_query(
183
216
  f"""
184
- SELECT DISTINCT ON (org_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id)
185
- org_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id
217
+ SELECT DISTINCT ON (user_id, role_id)
218
+ user_id, role_id, resource_type
186
219
  FROM "{tenant_id}".{db_settings.TENANT_ASSIGN_ROLES_TABLE}
187
220
  WHERE delete_status = 'NOT_DELETED'
188
221
  AND is_active = true
189
222
  AND user_id = %s
190
- ORDER BY org_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id;
223
+ ORDER BY user_id, role_id;
191
224
  """,
192
225
  (user_id,),
193
226
  )
@@ -224,31 +257,27 @@ class AuthService:
224
257
  status_code=500,
225
258
  error="Authorization check failed due to an internal error"
226
259
  )
227
-
260
+
228
261
  @staticmethod
229
- def check_permission(users_data: list, action=None, org_id=None, bus_id=None, app_id=None,
230
- resource_id=None, shared_resource_id=None) -> bool:
262
+ def check_permission(users_data: list, action=None, resource_type=None) -> bool:
231
263
  """
232
- Check if user has a given permission (action) for a specific target.
233
-
234
- Hierarchy: organization > business > app > location > resource/shared_resource
235
- If a field in role is None, it applies to all under that level.
264
+ Check if user has a given permission (action).
265
+
266
+ Args:
267
+ users_data: List of user authorization data containing roles and permissions
268
+ action: The permission/action to check for
269
+ resource_type: Optional resource type filter (e.g., 'rt-user', 'rt-group')
270
+
271
+ Returns:
272
+ bool: True if user has the permission, False otherwise
236
273
  """
237
274
  for user_data in users_data:
238
- # Check hierarchy: None means "all"
239
- if user_data.org_id not in (None, org_id):
240
- continue
241
- if user_data.bus_id not in (None, bus_id):
242
- continue
243
- if user_data.app_id not in (None, app_id):
244
- continue
245
- if user_data.resource_id not in (None, resource_id):
246
- continue
247
- if user_data.shared_resource_id not in (None, shared_resource_id):
275
+ # Check resource_type if specified
276
+ if resource_type and user_data.resource_type and user_data.resource_type != resource_type:
248
277
  continue
249
278
 
250
279
  # Check if the permission exists
251
- if action in user_data.permissions:
280
+ if action and action in user_data.permissions:
252
281
  return True
253
282
 
254
283
  return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trovesuite
3
- Version: 1.0.10
3
+ Version: 1.0.12
4
4
  Summary: TroveSuite services package providing authentication, authorization, notifications, Azure Storage, and other enterprise services for TroveSuite applications
5
5
  Home-page: https://dev.azure.com/brightgclt/trovesuite/_git/packages
6
6
  Author: Bright Debrah Owusu
File without changes
File without changes
File without changes
File without changes