suhailshafi007 commited on
Commit
acd778f
·
1 Parent(s): afd9209

feat(security): isolate only superadmin whitelist for employee access across users and db tabs

Browse files
Files changed (2) hide show
  1. app/auth_deps.py +0 -3
  2. app/superadmin_routes.py +118 -46
app/auth_deps.py CHANGED
@@ -125,10 +125,7 @@ async def require_admin(
125
 
126
  # Authoritative whitelist check for superadmin override
127
  whitelist = {
128
- "admin@sahihislam.com",
129
- "suhaileng.bhat@gmail.com",
130
  "zainshafibhat@gmail.com",
131
- "zainshafibhat2@gmail.com",
132
  }
133
  env_emails = os.getenv("SUPERADMIN_EMAILS", "")
134
  for e in env_emails.split(","):
 
125
 
126
  # Authoritative whitelist check for superadmin override
127
  whitelist = {
 
 
128
  "zainshafibhat@gmail.com",
 
129
  }
130
  env_emails = os.getenv("SUPERADMIN_EMAILS", "")
131
  for e in env_emails.split(","):
app/superadmin_routes.py CHANGED
@@ -59,10 +59,7 @@ router = APIRouter()
59
  # SUPERADMIN ACCESS CONTROL (ENTERPRISE DEFENSE-IN-DEPTH)
60
  # =========================================================
61
  SUPERADMIN_IMMUTABLE_WHITELIST = {
62
- "admin@sahihislam.com",
63
- "suhaileng.bhat@gmail.com",
64
  "zainshafibhat@gmail.com",
65
- "zainshafibhat2@gmail.com",
66
  }
67
 
68
  PROTECTED_SECURITY_ATTRIBUTES = {
@@ -539,24 +536,22 @@ async def get_all_users(
539
  is_sa_caller = bool(admin.get("is_superadmin", False))
540
 
541
  # 1. SECURITY PRIVACY FILTER:
542
- # If caller is an employee (not true SuperAdmin), filter out all SuperAdmin accounts
543
- # AND all fellow employee / staff user records!
544
  # The SuperAdmin caller sees EVERY single user in the entire system with zero exclusions.
545
  if not is_sa_caller:
546
  protected_emails = set(whitelist)
547
- protected_uids = set()
548
 
549
- async for s in staff_permissions_collection.find({}, {"email": 1, "uid": 1}):
550
- if s.get("email"):
551
- protected_emails.add(s["email"].strip().lower())
552
- if s.get("uid"):
553
- protected_uids.add(s["uid"].strip())
554
 
555
  # Forbid employee from filtering by SUPERADMIN role
556
  if role and role.strip().upper() == "SUPERADMIN":
557
  return []
558
 
559
- protected_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in protected_emails]
560
  exclusion_clauses = [{"email": {"$nin": protected_regexes}}]
561
  if protected_uids:
562
  exclusion_clauses.append({"uid": {"$nin": list(protected_uids)}})
@@ -713,13 +708,10 @@ async def update_user_attributes(
713
  if not is_sa_caller:
714
  target_email = (target.get("email") or "").strip().lower()
715
  whitelist = get_superadmin_whitelist()
716
- is_staff_target = await staff_permissions_collection.find_one({
717
- "$or": [{"uid": uid}, {"email": target_email}]
718
- })
719
- if target_email in whitelist or is_staff_target:
720
  raise HTTPException(
721
  status_code=403,
722
- detail="Access denied: Administrative and staff accounts cannot be modified by employees."
723
  )
724
 
725
  update_data = {}
@@ -831,13 +823,10 @@ async def delete_user(uid: str, admin: dict = Depends(require_superadmin)):
831
  if not is_sa_caller:
832
  target_email = (target.get("email") or "").strip().lower()
833
  whitelist = get_superadmin_whitelist()
834
- is_staff_target = await staff_permissions_collection.find_one({
835
- "$or": [{"uid": uid}, {"email": target_email}]
836
- })
837
- if target_email in whitelist or is_staff_target:
838
  raise HTTPException(
839
  status_code=403,
840
- detail="Access denied: Administrative and staff accounts cannot be deleted by employees."
841
  )
842
 
843
  # Delete user document
@@ -890,10 +879,7 @@ async def search_user_by_email(
890
  if not is_sa_caller:
891
  target_email = (user.get("email") or "").strip().lower()
892
  whitelist = get_superadmin_whitelist()
893
- is_staff_target = await staff_permissions_collection.find_one({
894
- "$or": [{"uid": user.get("uid")}, {"email": target_email}]
895
- })
896
- if target_email in whitelist or is_staff_target:
897
  raise HTTPException(status_code=404, detail=f"No user found with email matching '{clean_email}'")
898
 
899
  # Concurrent fetch of wallet balance & recent VIP transactions for user
@@ -2130,18 +2116,19 @@ async def get_statistics(admin: dict = Depends(require_superadmin)):
2130
  """
2131
  t0 = time.perf_counter()
2132
  try:
 
 
 
 
2133
  async def _count_users():
 
 
2134
  return await users_collection.count_documents({})
2135
 
2136
  async def _count_superadmins():
2137
- return await users_collection.count_documents({
2138
- "$or": [
2139
- {"superadmin": True},
2140
- {"auth.superadmin": True},
2141
- {"auth.is_superadmin": True},
2142
- {"role": "SUPERADMIN"}
2143
- ]
2144
- })
2145
 
2146
  async def _count_pro_users():
2147
  return await users_collection.count_documents({
@@ -2341,6 +2328,10 @@ async def get_database_overview(admin: dict = Depends(require_superadmin)):
2341
  ("sahihislam_commerce", "Commerce & Ads", db_commerce, "financial"),
2342
  ]
2343
 
 
 
 
 
2344
  db_summaries = []
2345
  overall_total_docs = 0
2346
  overall_total_collections = 0
@@ -2349,18 +2340,30 @@ async def get_database_overview(admin: dict = Depends(require_superadmin)):
2349
  coll_names = await db_handle.list_collection_names()
2350
  filtered_colls = sorted([c for c in coll_names if not c.startswith("system.")])
2351
 
 
 
 
 
2352
  async def _inspect_collection(c_name: str):
2353
  coll = db_handle[c_name]
2354
  try:
2355
- stats = await db_handle.command("collStats", c_name)
2356
- doc_count = stats.get("count", 0)
2357
- size_bytes = stats.get("size", 0)
2358
- index_count = stats.get("nindexes", 1)
 
 
 
 
 
2359
  except Exception:
2360
  try:
2361
- doc_count = await coll.estimated_document_count()
 
 
 
2362
  except Exception:
2363
- doc_count = await coll.count_documents({})
2364
  size_bytes = 0
2365
  index_count = 1
2366
  return {
@@ -2426,7 +2429,30 @@ async def get_collection_documents(
2426
  db_handle, db_name = _get_target_database(database if isinstance(database, str) else None)
2427
  coll = db_handle[collection]
2428
 
 
 
 
 
 
 
 
 
 
 
 
2429
  query_parts = []
 
 
 
 
 
 
 
 
 
 
 
 
2430
 
2431
  # 1. Apply raw JSON filter if provided
2432
  if isinstance(filter_json, str) and filter_json.strip():
@@ -2572,9 +2598,19 @@ async def get_single_document(
2572
  try:
2573
  db_handle, db_name = _get_target_database(database)
2574
  coll = db_handle[collection]
 
 
 
 
2575
  doc = await coll.find_one(_get_doc_id_query(doc_id))
2576
  if not doc:
2577
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
 
 
 
 
 
 
2578
  return {
2579
  "database": db_name,
2580
  "collection": collection,
@@ -2672,6 +2708,16 @@ async def update_document(
2672
  if not existing:
2673
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2674
 
 
 
 
 
 
 
 
 
 
 
2675
  update_fields = dict(payload)
2676
  if "_id" in update_fields:
2677
  del update_fields["_id"]
@@ -2737,6 +2783,16 @@ async def delete_document(
2737
  if not existing:
2738
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2739
 
 
 
 
 
 
 
 
 
 
 
2740
  # Safeguard 1: Prevent self-deletion of superadmin user
2741
  if collection == "users":
2742
  target_uid = existing.get("uid")
@@ -2800,6 +2856,22 @@ async def execute_query(
2800
  if not collection_name:
2801
  raise HTTPException(status_code=400, detail="Collection name required")
2802
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2803
  coll = db_handle[collection_name]
2804
 
2805
  if operation == "count":
@@ -2823,11 +2895,6 @@ async def execute_query(
2823
  except Exception as e:
2824
  logger.error(f"SuperAdmin query error: {str(e)}")
2825
  raise HTTPException(status_code=500, detail=f"Query failed: {str(e)}")
2826
- except HTTPException:
2827
- raise
2828
- except Exception as e:
2829
- logger.error(f"SuperAdmin query error: {str(e)}")
2830
- raise HTTPException(status_code=500, detail=f"Query failed: {str(e)}")
2831
 
2832
 
2833
  # =========================================================
@@ -5533,8 +5600,13 @@ async def simulate_search_tier_query(
5533
  async def get_database_stats(admin: dict = Depends(require_superadmin)):
5534
  """Get database collection statistics."""
5535
  try:
 
 
 
 
 
5536
  stats = {
5537
- "users_count": await users_collection.count_documents({}),
5538
  "masjids_count": await masjid_collection.count_documents({}),
5539
  "hadiths_count": await hadith_collection.count_documents({}),
5540
  "ads_count": await ads_collection.count_documents({}),
 
59
  # SUPERADMIN ACCESS CONTROL (ENTERPRISE DEFENSE-IN-DEPTH)
60
  # =========================================================
61
  SUPERADMIN_IMMUTABLE_WHITELIST = {
 
 
62
  "zainshafibhat@gmail.com",
 
63
  }
64
 
65
  PROTECTED_SECURITY_ATTRIBUTES = {
 
536
  is_sa_caller = bool(admin.get("is_superadmin", False))
537
 
538
  # 1. SECURITY PRIVACY FILTER:
539
+ # If caller is an employee (not true SuperAdmin), filter out ONLY the SuperAdmin account(s).
540
+ # Regular users and fellow accounts remain visible to the employee.
541
  # The SuperAdmin caller sees EVERY single user in the entire system with zero exclusions.
542
  if not is_sa_caller:
543
  protected_emails = set(whitelist)
544
+ protected_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in protected_emails]
545
 
546
+ protected_uids = set()
547
+ async for u in users_collection.find({"email": {"$in": protected_regexes}}, {"uid": 1}):
548
+ if u.get("uid"):
549
+ protected_uids.add(u["uid"].strip())
 
550
 
551
  # Forbid employee from filtering by SUPERADMIN role
552
  if role and role.strip().upper() == "SUPERADMIN":
553
  return []
554
 
 
555
  exclusion_clauses = [{"email": {"$nin": protected_regexes}}]
556
  if protected_uids:
557
  exclusion_clauses.append({"uid": {"$nin": list(protected_uids)}})
 
708
  if not is_sa_caller:
709
  target_email = (target.get("email") or "").strip().lower()
710
  whitelist = get_superadmin_whitelist()
711
+ if target_email in whitelist:
 
 
 
712
  raise HTTPException(
713
  status_code=403,
714
+ detail="Access denied: SuperAdmin accounts cannot be modified by employees."
715
  )
716
 
717
  update_data = {}
 
823
  if not is_sa_caller:
824
  target_email = (target.get("email") or "").strip().lower()
825
  whitelist = get_superadmin_whitelist()
826
+ if target_email in whitelist:
 
 
 
827
  raise HTTPException(
828
  status_code=403,
829
+ detail="Access denied: SuperAdmin accounts cannot be deleted by employees."
830
  )
831
 
832
  # Delete user document
 
879
  if not is_sa_caller:
880
  target_email = (user.get("email") or "").strip().lower()
881
  whitelist = get_superadmin_whitelist()
882
+ if target_email in whitelist:
 
 
 
883
  raise HTTPException(status_code=404, detail=f"No user found with email matching '{clean_email}'")
884
 
885
  # Concurrent fetch of wallet balance & recent VIP transactions for user
 
2116
  """
2117
  t0 = time.perf_counter()
2118
  try:
2119
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2120
+ whitelist = get_superadmin_whitelist()
2121
+ whitelist_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in whitelist]
2122
+
2123
  async def _count_users():
2124
+ if not is_sa_caller:
2125
+ return await users_collection.count_documents({"email": {"$nin": whitelist_regexes}})
2126
  return await users_collection.count_documents({})
2127
 
2128
  async def _count_superadmins():
2129
+ if not is_sa_caller:
2130
+ return 0
2131
+ return len(whitelist)
 
 
 
 
 
2132
 
2133
  async def _count_pro_users():
2134
  return await users_collection.count_documents({
 
2328
  ("sahihislam_commerce", "Commerce & Ads", db_commerce, "financial"),
2329
  ]
2330
 
2331
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2332
+ whitelist = get_superadmin_whitelist()
2333
+ whitelist_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in whitelist]
2334
+
2335
  db_summaries = []
2336
  overall_total_docs = 0
2337
  overall_total_collections = 0
 
2340
  coll_names = await db_handle.list_collection_names()
2341
  filtered_colls = sorted([c for c in coll_names if not c.startswith("system.")])
2342
 
2343
+ # Hide staff_permissions collection from employees
2344
+ if not is_sa_caller and name == "sahihislam_admin":
2345
+ filtered_colls = [c for c in filtered_colls if c != "staff_permissions"]
2346
+
2347
  async def _inspect_collection(c_name: str):
2348
  coll = db_handle[c_name]
2349
  try:
2350
+ if not is_sa_caller and c_name == "users":
2351
+ doc_count = await coll.count_documents({"email": {"$nin": whitelist_regexes}})
2352
+ size_bytes = 0
2353
+ index_count = 1
2354
+ else:
2355
+ stats = await db_handle.command("collStats", c_name)
2356
+ doc_count = stats.get("count", 0)
2357
+ size_bytes = stats.get("size", 0)
2358
+ index_count = stats.get("nindexes", 1)
2359
  except Exception:
2360
  try:
2361
+ if not is_sa_caller and c_name == "users":
2362
+ doc_count = await coll.count_documents({"email": {"$nin": whitelist_regexes}})
2363
+ else:
2364
+ doc_count = await coll.count_documents({})
2365
  except Exception:
2366
+ doc_count = 0
2367
  size_bytes = 0
2368
  index_count = 1
2369
  return {
 
2429
  db_handle, db_name = _get_target_database(database if isinstance(database, str) else None)
2430
  coll = db_handle[collection]
2431
 
2432
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2433
+ whitelist = get_superadmin_whitelist()
2434
+ whitelist_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in whitelist]
2435
+
2436
+ # Security restriction: staff_permissions collection is restricted to master SuperAdmin only
2437
+ if not is_sa_caller and collection == "staff_permissions":
2438
+ raise HTTPException(
2439
+ status_code=403,
2440
+ detail="Access denied: staff permissions collection is restricted to master SuperAdmin."
2441
+ )
2442
+
2443
  query_parts = []
2444
+
2445
+ # Security isolation: In users collection, filter out SuperAdmin accounts for employee callers
2446
+ if not is_sa_caller and collection == "users":
2447
+ sa_uids = set()
2448
+ async for u in users_collection.find({"email": {"$in": whitelist_regexes}}, {"uid": 1}):
2449
+ if u.get("uid"):
2450
+ sa_uids.add(u["uid"].strip())
2451
+
2452
+ query_parts.append({"email": {"$nin": whitelist_regexes}})
2453
+ if sa_uids:
2454
+ query_parts.append({"uid": {"$nin": list(sa_uids)}})
2455
+ query_parts.append({"_id": {"$nin": list(sa_uids)}})
2456
 
2457
  # 1. Apply raw JSON filter if provided
2458
  if isinstance(filter_json, str) and filter_json.strip():
 
2598
  try:
2599
  db_handle, db_name = _get_target_database(database)
2600
  coll = db_handle[collection]
2601
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2602
+ if not is_sa_caller and collection == "staff_permissions":
2603
+ raise HTTPException(status_code=403, detail="Access denied.")
2604
+
2605
  doc = await coll.find_one(_get_doc_id_query(doc_id))
2606
  if not doc:
2607
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2608
+
2609
+ if not is_sa_caller and collection == "users":
2610
+ target_email = (doc.get("email") or "").strip().lower()
2611
+ whitelist = get_superadmin_whitelist()
2612
+ if target_email in whitelist:
2613
+ raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2614
  return {
2615
  "database": db_name,
2616
  "collection": collection,
 
2708
  if not existing:
2709
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2710
 
2711
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2712
+ if not is_sa_caller:
2713
+ if collection == "staff_permissions":
2714
+ raise HTTPException(status_code=403, detail="Forbidden: Staff permissions cannot be altered via generic DB tools.")
2715
+ if collection == "users":
2716
+ target_email = (existing.get("email") or "").strip().lower()
2717
+ whitelist = get_superadmin_whitelist()
2718
+ if target_email in whitelist:
2719
+ raise HTTPException(status_code=403, detail="Forbidden: SuperAdmin records cannot be modified.")
2720
+
2721
  update_fields = dict(payload)
2722
  if "_id" in update_fields:
2723
  del update_fields["_id"]
 
2783
  if not existing:
2784
  raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found in {db_name}.{collection}")
2785
 
2786
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2787
+ if not is_sa_caller:
2788
+ if collection == "staff_permissions":
2789
+ raise HTTPException(status_code=403, detail="Forbidden: Staff permissions cannot be altered via generic DB tools.")
2790
+ if collection == "users":
2791
+ target_email = (existing.get("email") or "").strip().lower()
2792
+ whitelist = get_superadmin_whitelist()
2793
+ if target_email in whitelist:
2794
+ raise HTTPException(status_code=403, detail="Forbidden: SuperAdmin records cannot be deleted.")
2795
+
2796
  # Safeguard 1: Prevent self-deletion of superadmin user
2797
  if collection == "users":
2798
  target_uid = existing.get("uid")
 
2856
  if not collection_name:
2857
  raise HTTPException(status_code=400, detail="Collection name required")
2858
 
2859
+ is_sa_caller = bool(admin.get("is_superadmin", False))
2860
+ if not is_sa_caller:
2861
+ if collection_name == "staff_permissions":
2862
+ raise HTTPException(status_code=403, detail="Access denied: staff permissions collection is restricted to master SuperAdmin.")
2863
+ if collection_name == "users":
2864
+ whitelist = get_superadmin_whitelist()
2865
+ whitelist_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in whitelist]
2866
+ sa_exclusion = {"email": {"$nin": whitelist_regexes}}
2867
+ if isinstance(query, dict):
2868
+ if "$and" in query:
2869
+ query["$and"].append(sa_exclusion)
2870
+ elif query:
2871
+ query = {"$and": [query, sa_exclusion]}
2872
+ else:
2873
+ query = sa_exclusion
2874
+
2875
  coll = db_handle[collection_name]
2876
 
2877
  if operation == "count":
 
2895
  except Exception as e:
2896
  logger.error(f"SuperAdmin query error: {str(e)}")
2897
  raise HTTPException(status_code=500, detail=f"Query failed: {str(e)}")
 
 
 
 
 
2898
 
2899
 
2900
  # =========================================================
 
5600
  async def get_database_stats(admin: dict = Depends(require_superadmin)):
5601
  """Get database collection statistics."""
5602
  try:
5603
+ is_sa_caller = bool(admin.get("is_superadmin", False))
5604
+ whitelist = get_superadmin_whitelist()
5605
+ whitelist_regexes = [re.compile(f"^{re.escape(e)}$", re.IGNORECASE) for e in whitelist]
5606
+ user_query = {} if is_sa_caller else {"email": {"$nin": whitelist_regexes}}
5607
+
5608
  stats = {
5609
+ "users_count": await users_collection.count_documents(user_query),
5610
  "masjids_count": await masjid_collection.count_documents({}),
5611
  "hadiths_count": await hadith_collection.count_documents({}),
5612
  "ads_count": await ads_collection.count_documents({}),