security(v3): harden multi-tenant isolation and enhance failure feedback

This commit is contained in:
Scott Idem
2026-02-13 18:45:20 -05:00
parent 61e17f1efa
commit 2266f149f7
15 changed files with 389 additions and 317 deletions

View File

@@ -110,7 +110,7 @@ async def get_obj(
obj_type_l1: str = Path(min_length=2, max_length=50),
obj_id: str = Path(min_length=11, max_length=22),
view: str = Query('default'),
account: AccountContext = Depends(get_account_context),
account: AccountContext = Depends(get_account_context_optional),
serialization: SerializationParams = Depends(),
delay: DelayParams = Depends(),
):
@@ -143,8 +143,13 @@ async def get_obj(
if sql_result := sql_select(table_name=table_name, record_id=record_id):
if not obj_cfg.get('public_read', False):
# Strict context check for non-public objects
if account.auth_method == 'guest' or (account.account_id is None and not account.super):
reason = account.auth_error or "Account context required."
return mk_resp(data=False, status_code=403, response=response, status_message=reason)
if not check_account_access(sql_result, account, obj_name):
return mk_resp(data=False, status_code=403, response=response, status_message="Access denied.")
return mk_resp(data=False, status_code=403, response=response, status_message="Access denied. Record belongs to another account.")
resp_data = base_name(**sql_result).dict(by_alias=serialization.by_alias, exclude_unset=serialization.exclude_unset, exclude_defaults=serialization.exclude_defaults, exclude_none=serialization.exclude_none)
return mk_resp(data=resp_data, response=response)
else:
@@ -334,7 +339,7 @@ async def search_obj_li(
if not account.super and for_obj_id != account.account_id_random:
return mk_resp(data=False, status_code=403, response=response, status_message="Access denied to requested account.")
if not account.super and account.auth_method != 'bypass' and account.account_id:
if not is_public_read and not account.super and account.auth_method != 'bypass':
if search_query.and_filters is None: search_query.and_filters = []
if obj_name == 'account':
search_query.and_filters.append(SearchFilter(field='id', op='eq', value=account.account_id))