trovesuite 1.0.1__py3-none-any.whl → 1.0.31__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.
- trovesuite/__init__.py +12 -5
- trovesuite/auth/__init__.py +2 -5
- trovesuite/auth/auth_controller.py +6 -5
- trovesuite/auth/auth_read_dto.py +3 -3
- trovesuite/auth/auth_service.py +223 -80
- trovesuite/auth/auth_write_dto.py +1 -1
- trovesuite/configs/database.py +212 -58
- trovesuite/configs/settings.py +75 -132
- trovesuite/entities/health.py +4 -4
- trovesuite/notification/__init__.py +14 -0
- trovesuite/notification/notification_base.py +13 -0
- trovesuite/notification/notification_controller.py +21 -0
- trovesuite/notification/notification_read_dto.py +21 -0
- trovesuite/notification/notification_service.py +73 -0
- trovesuite/notification/notification_write_dto.py +21 -0
- trovesuite/storage/__init__.py +42 -0
- trovesuite/storage/storage_base.py +63 -0
- trovesuite/storage/storage_controller.py +198 -0
- trovesuite/storage/storage_read_dto.py +74 -0
- trovesuite/storage/storage_service.py +529 -0
- trovesuite/storage/storage_write_dto.py +70 -0
- trovesuite/utils/__init__.py +3 -1
- trovesuite/utils/helper.py +714 -5
- trovesuite/utils/templates.py +487 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.31.dist-info}/METADATA +184 -9
- trovesuite-1.0.31.dist-info/RECORD +34 -0
- trovesuite-1.0.1.dist-info/RECORD +0 -21
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.31.dist-info}/WHEEL +0 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.31.dist-info}/licenses/LICENSE +0 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.31.dist-info}/top_level.txt +0 -0
trovesuite/__init__.py
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"""
|
|
2
|
-
TroveSuite
|
|
2
|
+
TroveSuite Package
|
|
3
3
|
|
|
4
|
-
A comprehensive authentication and
|
|
5
|
-
Provides JWT token validation, user authorization,
|
|
4
|
+
A comprehensive authentication, authorization, notification, and storage service for ERP systems.
|
|
5
|
+
Provides JWT token validation, user authorization, permission checking, notification capabilities,
|
|
6
|
+
Azure Storage blob management, and utility functions for multi-tenant applications.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from .auth import AuthService
|
|
10
|
+
from .notification import NotificationService
|
|
11
|
+
from .storage import StorageService
|
|
12
|
+
from .utils import Helper
|
|
9
13
|
|
|
10
|
-
__version__ = "1.0.
|
|
14
|
+
__version__ = "1.0.29"
|
|
11
15
|
__author__ = "Bright Debrah Owusu"
|
|
12
16
|
__email__ = "owusu.debrah@deladetech.com"
|
|
13
17
|
|
|
14
18
|
__all__ = [
|
|
15
|
-
"AuthService"
|
|
19
|
+
"AuthService",
|
|
20
|
+
"NotificationService",
|
|
21
|
+
"StorageService",
|
|
22
|
+
"Helper",
|
|
16
23
|
]
|
trovesuite/auth/__init__.py
CHANGED
|
@@ -5,13 +5,10 @@ Authentication and authorization services for ERP systems.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from .auth_service import AuthService
|
|
8
|
-
from .
|
|
9
|
-
from .auth_read_dto import AuthServiceReadDto, AuthControllerReadDto
|
|
8
|
+
from .auth_write_dto import AuthServiceWriteDto
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
12
11
|
"AuthService",
|
|
13
|
-
"
|
|
14
|
-
"AuthServiceReadDto",
|
|
15
|
-
"AuthControllerReadDto"
|
|
12
|
+
"AuthServiceWriteDto"
|
|
16
13
|
]
|
|
17
14
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from fastapi import APIRouter
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
2
|
+
from .auth_write_dto import AuthControllerWriteDto
|
|
3
|
+
from .auth_read_dto import AuthControllerReadDto
|
|
4
|
+
from .auth_service import AuthService
|
|
5
|
+
from ..entities.sh_response import Respons
|
|
5
6
|
|
|
6
|
-
auth_router = APIRouter()
|
|
7
|
+
auth_router = APIRouter(tags=["Auth"])
|
|
7
8
|
|
|
8
|
-
@auth_router.post("/auth", response_model=AuthControllerReadDto)
|
|
9
|
+
@auth_router.post("/auth", response_model=Respons[AuthControllerReadDto])
|
|
9
10
|
async def authorize(data: AuthControllerWriteDto):
|
|
10
11
|
return AuthService.authorize(data=data)
|
trovesuite/auth/auth_read_dto.py
CHANGED
|
@@ -3,16 +3,16 @@ 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
|
|
11
11
|
role_id: Optional[str] = None
|
|
12
12
|
tenant_id: Optional[str] = None
|
|
13
13
|
permissions: Optional[List[str]] = None
|
|
14
|
-
shared_resource_id: Optional[str] = None
|
|
15
14
|
resource_id: Optional[str] = None
|
|
15
|
+
resource_type: Optional[str] = None
|
|
16
16
|
|
|
17
17
|
class AuthServiceReadDto(AuthControllerReadDto):
|
|
18
18
|
pass
|
trovesuite/auth/auth_service.py
CHANGED
|
@@ -45,7 +45,7 @@ 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
|
|
|
@@ -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",
|
|
@@ -68,16 +68,16 @@ class AuthService:
|
|
|
68
68
|
status_code=400,
|
|
69
69
|
error="INVALID_TENANT_ID"
|
|
70
70
|
)
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
try:
|
|
73
73
|
|
|
74
74
|
is_tenant_verified = DatabaseManager.execute_query(
|
|
75
|
-
f"SELECT is_verified FROM {db_settings.
|
|
75
|
+
f"SELECT is_verified FROM {db_settings.CORE_PLATFORM_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("
|
|
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("
|
|
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,37 @@ class AuthService:
|
|
|
96
96
|
error="TENANT_NOT_VERIFIED"
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
(user_id,),
|
|
99
|
+
# 1️⃣ Get all groups the user belongs to
|
|
100
|
+
user_groups = DatabaseManager.execute_query(
|
|
101
|
+
f"""SELECT group_id FROM {db_settings.CORE_PLATFORM_USER_GROUPS_TABLE}
|
|
102
|
+
WHERE tenant_id = %s AND delete_status = 'NOT_DELETED' AND is_active = true AND user_id = %s AND (is_system = false OR is_system IS NULL)""",
|
|
103
|
+
(tenant_id, user_id,),
|
|
105
104
|
)
|
|
106
105
|
|
|
106
|
+
# 2️⃣ Prepare list of group_ids
|
|
107
|
+
group_ids = [g["group_id"] for g in user_groups] if user_groups else []
|
|
108
|
+
|
|
109
|
+
# 3️⃣ Get login settings - check user-level first, then group-level
|
|
110
|
+
if group_ids:
|
|
111
|
+
login_settings_details = DatabaseManager.execute_query(
|
|
112
|
+
f"""SELECT user_id, group_id, is_suspended, can_always_login,
|
|
113
|
+
is_multi_factor_enabled, is_login_before, working_days,
|
|
114
|
+
login_on, logout_on FROM {db_settings.CORE_PLATFORM_LOGIN_SETTINGS_TABLE}
|
|
115
|
+
WHERE tenant_id = %s AND (delete_status = 'NOT_DELETED' AND is_active = true )
|
|
116
|
+
AND (user_id = %s OR group_id = ANY(%s))
|
|
117
|
+
ORDER BY user_id NULLS LAST
|
|
118
|
+
LIMIT 1""",
|
|
119
|
+
(tenant_id, user_id, group_ids),
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
login_settings_details = DatabaseManager.execute_query(
|
|
123
|
+
f"""SELECT user_id, group_id, is_suspended, can_always_login,
|
|
124
|
+
is_multi_factor_enabled, is_login_before, working_days,
|
|
125
|
+
login_on, logout_on FROM {db_settings.CORE_PLATFORM_LOGIN_SETTINGS_TABLE}
|
|
126
|
+
WHERE tenant_id = %s AND (delete_status = 'NOT_DELETED' AND is_active = true ) AND user_id = %s""",
|
|
127
|
+
(tenant_id, user_id,),
|
|
128
|
+
)
|
|
129
|
+
|
|
107
130
|
if not login_settings_details or len(login_settings_details) == 0:
|
|
108
131
|
logger.warning("Authorization failed - user not found: %s in tenant: %s", user_id, tenant_id)
|
|
109
132
|
return Respons[AuthServiceReadDto](
|
|
@@ -124,86 +147,194 @@ class AuthService:
|
|
|
124
147
|
error="USER_SUSPENDED"
|
|
125
148
|
)
|
|
126
149
|
|
|
150
|
+
# ✅ UPDATED: Mutually exclusive login restrictions logic
|
|
127
151
|
if not login_settings_details[0]['can_always_login']:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
logout_on = login_settings_details[0]['logout_on']
|
|
152
|
+
# Get from database (should already be datetime objects)
|
|
153
|
+
login_on = login_settings_details[0]['login_on']
|
|
154
|
+
logout_on = login_settings_details[0]['logout_on']
|
|
155
|
+
working_days = login_settings_details[0]['working_days']
|
|
156
|
+
|
|
157
|
+
# Only ONE restriction type can be active at a time:
|
|
158
|
+
# 1. working_days restriction (if login_on/logout_on are NULL)
|
|
159
|
+
# 2. time period restriction (if login_on/logout_on are set)
|
|
160
|
+
|
|
161
|
+
if login_on and logout_on:
|
|
139
162
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
# Time period restriction is active
|
|
164
|
+
logger.info(f"Checking time period restriction for user: {user_id}")
|
|
165
|
+
|
|
166
|
+
# Get current datetime (full date and time) with timezone
|
|
167
|
+
current_datetime = datetime.now(timezone.utc).replace(
|
|
168
|
+
microsecond=0, second=0
|
|
169
|
+
)
|
|
145
170
|
|
|
146
171
|
# Compare full datetime objects (both date and time)
|
|
147
172
|
if not (login_on <= current_datetime <= logout_on):
|
|
148
|
-
logger.warning(
|
|
173
|
+
logger.warning(
|
|
174
|
+
f"Authorization failed - outside allowed period for user: {user_id}"
|
|
175
|
+
)
|
|
149
176
|
return Respons[AuthServiceReadDto](
|
|
150
|
-
detail="
|
|
177
|
+
detail="Access is not allowed at this time. Please check your access schedule.",
|
|
151
178
|
data=[],
|
|
152
179
|
success=False,
|
|
153
180
|
status_code=403,
|
|
154
181
|
error="LOGIN_TIME_RESTRICTED"
|
|
155
182
|
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
WHERE delete_status = 'NOT_DELETED' AND is_active = true AND user_id = %s""",(user_id,),
|
|
161
|
-
)
|
|
183
|
+
elif working_days:
|
|
184
|
+
# Working days restriction is active
|
|
185
|
+
logger.info(f"Checking working days restriction for user: {user_id}")
|
|
186
|
+
current_day = datetime.now().strftime("%A").upper()
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
188
|
+
if current_day not in working_days:
|
|
189
|
+
logger.warning(
|
|
190
|
+
f"Authorization failed - not a working day for user: {user_id}"
|
|
191
|
+
)
|
|
192
|
+
return Respons[AuthServiceReadDto](
|
|
193
|
+
detail="Access is not allowed on this day. Please contact your administrator.",
|
|
194
|
+
data=[],
|
|
195
|
+
success=False,
|
|
196
|
+
status_code=403,
|
|
197
|
+
error="LOGIN_DAY_RESTRICTED"
|
|
198
|
+
)
|
|
165
199
|
|
|
166
|
-
#
|
|
200
|
+
# 4️⃣ Build query dynamically to include groups (if any) + user
|
|
201
|
+
# ⚠️ CHANGED: Simplified to new schema - only select user_id, group_id, role_id, resource_type
|
|
167
202
|
if group_ids:
|
|
168
203
|
get_user_roles = DatabaseManager.execute_query(
|
|
169
204
|
f"""
|
|
170
|
-
SELECT DISTINCT ON (
|
|
171
|
-
|
|
172
|
-
FROM
|
|
173
|
-
WHERE delete_status = 'NOT_DELETED'
|
|
205
|
+
SELECT DISTINCT ON (group_id, user_id, role_id)
|
|
206
|
+
group_id, user_id, role_id, resource_type
|
|
207
|
+
FROM {db_settings.CORE_PLATFORM_ASSIGN_ROLES_TABLE}
|
|
208
|
+
WHERE tenant_id = %s AND delete_status = 'NOT_DELETED'
|
|
174
209
|
AND is_active = true
|
|
210
|
+
AND (is_system = false OR is_system IS NULL)
|
|
175
211
|
AND (user_id = %s OR group_id = ANY(%s))
|
|
176
|
-
ORDER BY
|
|
212
|
+
ORDER BY group_id, user_id, role_id;
|
|
177
213
|
""",
|
|
178
|
-
(user_id, group_ids),
|
|
214
|
+
(tenant_id, user_id, group_ids),
|
|
179
215
|
)
|
|
180
216
|
else:
|
|
181
217
|
# No groups, just check roles for user
|
|
182
218
|
get_user_roles = DatabaseManager.execute_query(
|
|
183
219
|
f"""
|
|
184
|
-
SELECT DISTINCT ON (
|
|
185
|
-
|
|
186
|
-
FROM
|
|
187
|
-
WHERE delete_status = 'NOT_DELETED'
|
|
220
|
+
SELECT DISTINCT ON (user_id, role_id)
|
|
221
|
+
user_id, role_id, resource_type
|
|
222
|
+
FROM {db_settings.CORE_PLATFORM_ASSIGN_ROLES_TABLE}
|
|
223
|
+
WHERE tenant_id = %s AND delete_status = 'NOT_DELETED'
|
|
188
224
|
AND is_active = true
|
|
225
|
+
AND (is_system = false OR is_system IS NULL)
|
|
189
226
|
AND user_id = %s
|
|
190
|
-
ORDER BY
|
|
227
|
+
ORDER BY user_id, role_id;
|
|
191
228
|
""",
|
|
192
|
-
(user_id,),
|
|
229
|
+
(tenant_id, user_id,),
|
|
193
230
|
)
|
|
194
231
|
|
|
232
|
+
# ✅ NEW: Get system-level roles from cp_assign_roles with is_system=true
|
|
233
|
+
# NOTE: system_groups, system_user_groups, and system_assign_roles are now consolidated
|
|
234
|
+
# into cp_groups, cp_user_groups, and cp_assign_roles with is_system flag
|
|
235
|
+
# Use LEFT JOIN starting from cp_assign_roles to find BOTH direct user assignments AND group-based assignments
|
|
236
|
+
logger.info(f"Fetching system-level roles for user: {user_id}")
|
|
237
|
+
|
|
238
|
+
system_roles = DatabaseManager.execute_query(
|
|
239
|
+
f"""
|
|
240
|
+
SELECT DISTINCT COALESCE(sar.group_id::TEXT, NULL) as group_id, sar.user_id, sar.role_id, sar.resource_type
|
|
241
|
+
FROM {db_settings.CORE_PLATFORM_ASSIGN_ROLES_TABLE} sar
|
|
242
|
+
LEFT JOIN {db_settings.CORE_PLATFORM_USER_GROUPS_TABLE} sug
|
|
243
|
+
ON sar.group_id = sug.group_id AND sar.tenant_id = sug.tenant_id
|
|
244
|
+
WHERE sar.tenant_id = 'system-tenant-id'
|
|
245
|
+
AND sar.is_system = true
|
|
246
|
+
AND sar.delete_status = 'NOT_DELETED'
|
|
247
|
+
AND sar.is_active = true
|
|
248
|
+
AND (sar.user_id = %s OR (sug.user_id = %s AND sug.tenant_id = 'system-tenant-id' AND sug.is_system = true AND sug.is_active = true AND sug.delete_status = 'NOT_DELETED'))
|
|
249
|
+
""",
|
|
250
|
+
(user_id, user_id)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if system_roles:
|
|
254
|
+
logger.info(f"Found {len(system_roles)} system-level role(s) for user: {user_id}")
|
|
255
|
+
else:
|
|
256
|
+
logger.info(f"No system-level roles found for user: {user_id}")
|
|
257
|
+
|
|
258
|
+
# ✅ NEW: Merge tenant-level and system-level roles
|
|
259
|
+
all_roles = get_user_roles + system_roles
|
|
260
|
+
logger.info(f"Total roles (tenant + system) for user {user_id}: {len(all_roles)}")
|
|
261
|
+
|
|
195
262
|
# GET permissions and Append to Role
|
|
196
263
|
get_user_roles_with_tenant_and_permissions = []
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
264
|
+
|
|
265
|
+
# Track system role IDs by querying cp_roles table for is_system flag (more reliable)
|
|
266
|
+
system_role_ids = set()
|
|
267
|
+
if all_roles:
|
|
268
|
+
role_ids = [r.get("role_id") for r in all_roles if r.get("role_id")]
|
|
269
|
+
if role_ids:
|
|
270
|
+
try:
|
|
271
|
+
# Check which roles are system roles by querying the roles table
|
|
272
|
+
system_roles_check = DatabaseManager.execute_query(
|
|
273
|
+
f"""SELECT id FROM {db_settings.CORE_PLATFORM_ROLES_TABLE}
|
|
274
|
+
WHERE id = ANY(%s) AND is_system = true AND delete_status = 'NOT_DELETED'""",
|
|
275
|
+
params=(role_ids,),
|
|
276
|
+
)
|
|
277
|
+
if system_roles_check:
|
|
278
|
+
for role_record in system_roles_check:
|
|
279
|
+
role_id = role_record.get("id") if isinstance(role_record, dict) else (role_record[0] if isinstance(role_record, (list, tuple)) and len(role_record) > 0 else None)
|
|
280
|
+
if role_id:
|
|
281
|
+
system_role_ids.add(role_id)
|
|
282
|
+
|
|
283
|
+
logger.info(f"Identified {len(system_role_ids)} system roles for user {user_id}")
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.warning(f"Error checking system roles: {str(e)}")
|
|
286
|
+
# Fallback: use system_roles query results
|
|
287
|
+
system_role_ids = {r.get("role_id") for r in system_roles if r.get("role_id")} if system_roles else set()
|
|
288
|
+
|
|
289
|
+
for role in all_roles:
|
|
290
|
+
role_id = role.get("role_id")
|
|
291
|
+
if not role_id:
|
|
292
|
+
logger.warning(f"Skipping role with missing role_id: {role}")
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
# Determine which tenant_id to use for querying permissions
|
|
296
|
+
# For system roles, use 'system-tenant-id'; for tenant roles, use the user's tenant_id
|
|
297
|
+
is_system_role = role_id in system_role_ids
|
|
298
|
+
|
|
299
|
+
# Try the primary tenant_id first based on whether it's a system role
|
|
300
|
+
if is_system_role:
|
|
301
|
+
primary_tenant_id = 'system-tenant-id'
|
|
302
|
+
fallback_tenant_id = tenant_id
|
|
303
|
+
else:
|
|
304
|
+
primary_tenant_id = tenant_id
|
|
305
|
+
fallback_tenant_id = 'system-tenant-id'
|
|
306
|
+
|
|
307
|
+
# Query permissions for this role with primary tenant_id
|
|
308
|
+
permissions = []
|
|
309
|
+
try:
|
|
310
|
+
permissions = DatabaseManager.execute_query(
|
|
311
|
+
f"""SELECT permission_id FROM {db_settings.CORE_PLATFORM_ROLE_PERMISSIONS_TABLE}
|
|
312
|
+
WHERE role_id = %s AND tenant_id = %s AND delete_status = 'NOT_DELETED'""",
|
|
313
|
+
params=(role_id, primary_tenant_id),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# If no permissions found with primary tenant_id, try fallback (handles edge cases)
|
|
317
|
+
if not permissions or len(permissions) == 0:
|
|
318
|
+
logger.debug(f"No permissions found for role {role_id} with tenant {primary_tenant_id}, trying fallback {fallback_tenant_id}")
|
|
319
|
+
fallback_permissions = DatabaseManager.execute_query(
|
|
320
|
+
f"""SELECT permission_id FROM {db_settings.CORE_PLATFORM_ROLE_PERMISSIONS_TABLE}
|
|
321
|
+
WHERE role_id = %s AND tenant_id = %s AND delete_status = 'NOT_DELETED'""",
|
|
322
|
+
params=(role_id, fallback_tenant_id),
|
|
323
|
+
)
|
|
324
|
+
if fallback_permissions and len(fallback_permissions) > 0:
|
|
325
|
+
permissions = fallback_permissions
|
|
326
|
+
logger.info(f"Found permissions for role {role_id} in fallback tenant {fallback_tenant_id}")
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"Error querying permissions for role {role_id}: {str(e)}", exc_info=True)
|
|
329
|
+
permissions = []
|
|
201
330
|
|
|
202
331
|
role_dict = {**role, "tenant_id": tenant_id, "permissions": [p['permission_id'] for p in permissions]}
|
|
203
332
|
get_user_roles_with_tenant_and_permissions.append(role_dict)
|
|
204
333
|
|
|
205
334
|
roles_dto = Helper.map_to_dto(get_user_roles_with_tenant_and_permissions, AuthServiceReadDto)
|
|
206
335
|
|
|
336
|
+
logger.info(f"Authorization successful for user: {user_id} with {len(roles_dto)} total role entries")
|
|
337
|
+
|
|
207
338
|
return Respons[AuthServiceReadDto](
|
|
208
339
|
detail="Authorized",
|
|
209
340
|
data=roles_dto,
|
|
@@ -216,7 +347,7 @@ class AuthService:
|
|
|
216
347
|
raise http_ex
|
|
217
348
|
|
|
218
349
|
except Exception as e:
|
|
219
|
-
logger.error("Authorization check failed for user: %s", str(e))
|
|
350
|
+
logger.error("Authorization check failed for user: %s - Error: %s", user_id, str(e), exc_info=True)
|
|
220
351
|
return Respons[AuthServiceReadDto](
|
|
221
352
|
detail=None,
|
|
222
353
|
data=[],
|
|
@@ -224,31 +355,27 @@ class AuthService:
|
|
|
224
355
|
status_code=500,
|
|
225
356
|
error="Authorization check failed due to an internal error"
|
|
226
357
|
)
|
|
227
|
-
|
|
358
|
+
|
|
228
359
|
@staticmethod
|
|
229
|
-
def check_permission(users_data: list, action=None,
|
|
230
|
-
resource_id=None, shared_resource_id=None) -> bool:
|
|
360
|
+
def check_permission(users_data: list, action=None, resource_type=None) -> bool:
|
|
231
361
|
"""
|
|
232
|
-
Check if user has a given permission (action)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
362
|
+
Check if user has a given permission (action).
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
users_data: List of user authorization data containing roles and permissions
|
|
366
|
+
action: The permission/action to check for
|
|
367
|
+
resource_type: Optional resource type filter (e.g., 'rt-user', 'rt-group')
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
bool: True if user has the permission, False otherwise
|
|
236
371
|
"""
|
|
237
372
|
for user_data in users_data:
|
|
238
|
-
# Check
|
|
239
|
-
if user_data.
|
|
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):
|
|
373
|
+
# Check resource_type if specified
|
|
374
|
+
if resource_type and user_data.resource_type and user_data.resource_type != resource_type:
|
|
248
375
|
continue
|
|
249
376
|
|
|
250
377
|
# Check if the permission exists
|
|
251
|
-
if action in user_data.permissions:
|
|
378
|
+
if action and action in user_data.permissions:
|
|
252
379
|
return True
|
|
253
380
|
|
|
254
381
|
return False
|
|
@@ -273,18 +400,34 @@ class AuthService:
|
|
|
273
400
|
def authorize_user_from_token(token: str) -> Respons[AuthServiceReadDto]:
|
|
274
401
|
"""
|
|
275
402
|
Convenience method to authorize a user directly from a JWT token.
|
|
276
|
-
|
|
403
|
+
|
|
277
404
|
Args:
|
|
278
405
|
token: JWT token string
|
|
279
|
-
|
|
406
|
+
|
|
280
407
|
Returns:
|
|
281
408
|
Respons[AuthServiceReadDto]: Authorization result with user roles and permissions
|
|
282
|
-
|
|
409
|
+
|
|
283
410
|
Raises:
|
|
284
411
|
HTTPException: If token is invalid
|
|
285
412
|
"""
|
|
286
|
-
|
|
287
|
-
|
|
413
|
+
credentials_exception = HTTPException(
|
|
414
|
+
status_code=401,
|
|
415
|
+
detail="Could not validate credentials",
|
|
416
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
417
|
+
)
|
|
418
|
+
try:
|
|
419
|
+
payload = jwt.decode(token, db_settings.SECRET_KEY, algorithms=[db_settings.ALGORITHM])
|
|
420
|
+
user_id = payload.get("user_id")
|
|
421
|
+
tenant_id = payload.get("tenant_id")
|
|
422
|
+
|
|
423
|
+
if user_id is None or tenant_id is None:
|
|
424
|
+
raise credentials_exception
|
|
425
|
+
|
|
426
|
+
data = AuthServiceWriteDto(user_id=user_id, tenant_id=tenant_id)
|
|
427
|
+
return AuthService.authorize(data=data)
|
|
428
|
+
|
|
429
|
+
except jwt.InvalidTokenError as exc:
|
|
430
|
+
raise credentials_exception from exc
|
|
288
431
|
|
|
289
432
|
@staticmethod
|
|
290
433
|
def get_user_permissions(user_roles: list) -> list:
|