feat(v3-data-store): harden search security and standardize test suite
This commit is contained in:
@@ -242,34 +242,75 @@ async def search_v3_data_store_obj_w_code(
|
||||
):
|
||||
"""
|
||||
Advanced Search for Data Store (within a code hierarchy).
|
||||
Enforces Account Context isolation and uses ID Vision (v_data_store).
|
||||
"""
|
||||
if delay.sleep_time_s > 0: await asyncio.sleep(delay.sleep_time_s)
|
||||
|
||||
# 1. Resolve IDs
|
||||
# 1. Resolve parent IDs if provided
|
||||
resolved_for_id = None
|
||||
if for_type and for_id:
|
||||
resolved_for_id = redis_lookup_id_random(record_id_random=for_id, table_name=for_type)
|
||||
if not resolved_for_id:
|
||||
return mk_resp(data=False, status_code=404, status_message="Parent for_id not found.")
|
||||
|
||||
# 2. Apply advanced search on top of the standard lookup
|
||||
# We use sql_select with the SearchQuery
|
||||
sql_result = sql_select(
|
||||
table_name='data_store',
|
||||
enabled=status_filter.enabled,
|
||||
hidden=status_filter.hidden,
|
||||
search_query=search_query,
|
||||
field_name='code',
|
||||
field_value=data_store_code,
|
||||
limit=pagination.limit,
|
||||
offset=pagination.offset,
|
||||
as_list=True,
|
||||
# 2. Construct the hierarchical search SQL
|
||||
# We must enforce that users only see their own account records OR global defaults (account_id IS NULL)
|
||||
from app.db_sql import sql_enable_part, sql_hidden_part, sql_search_qry_part, sql_limit_offset_part
|
||||
|
||||
sql_enabled, data_enabled = sql_enable_part('data_store', status_filter.enabled)
|
||||
sql_hidden, data_hidden = sql_hidden_part('data_store', status_filter.hidden)
|
||||
|
||||
# Generate search logic from the SearchQuery model
|
||||
search_sql, search_data = sql_search_qry_part(
|
||||
search_query=search_query,
|
||||
table_name='v_data_store'
|
||||
)
|
||||
|
||||
sql_limit = sql_limit_offset_part(limit=pagination.limit, offset=pagination.offset)
|
||||
|
||||
# Prepare parameter dictionary
|
||||
data = {
|
||||
'code': data_store_code,
|
||||
'account_id': account.account_id,
|
||||
'for_type': for_type,
|
||||
'for_id': resolved_for_id
|
||||
}
|
||||
data.update(search_data)
|
||||
if data_enabled is not None: data['enable'] = data_enabled
|
||||
if data_hidden is not None: data['hide'] = data_hidden
|
||||
|
||||
# Hierarchical Fallback Logic: (Object Override > Account Override > Global System Default)
|
||||
# This matches the GET lookup logic but allows multiple results via search.
|
||||
sql = f"""
|
||||
SELECT *
|
||||
FROM `v_data_store` AS `data_store`
|
||||
WHERE
|
||||
(
|
||||
`data_store`.account_id = :account_id
|
||||
OR `data_store`.account_id IS NULL
|
||||
OR (`data_store`.for_type = :for_type AND `data_store`.for_id = :for_id)
|
||||
)
|
||||
AND `data_store`.code = :code
|
||||
{sql_enabled}
|
||||
{sql_hidden}
|
||||
{search_sql}
|
||||
ORDER BY `data_store`.for_id DESC, `data_store`.account_id DESC, `data_store`.created_on DESC
|
||||
{sql_limit};
|
||||
"""
|
||||
|
||||
sql_result = sql_select(sql=sql, data=data, as_list=True)
|
||||
|
||||
if sql_result is False:
|
||||
return mk_resp(data=False, status_code=500, status_message="Database error during search.")
|
||||
|
||||
return mk_resp(data=sql_result)
|
||||
# 3. Parse results through Pydantic model to enforce ID Vision (random IDs only)
|
||||
try:
|
||||
data_objs = [Data_Store_Base(**r) for r in sql_result]
|
||||
except Exception as e:
|
||||
log.error(f"Validation error during Data Store search: {e}")
|
||||
return mk_resp(data=False, status_code=500, status_message="Data integrity error during result mapping.")
|
||||
|
||||
return mk_resp(data=data_objs)
|
||||
|
||||
|
||||
async def handle_get_data_store_obj_w_code(
|
||||
|
||||
Reference in New Issue
Block a user