Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/python/api/v2/database_util/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,20 @@ async def list_collections(
# If NO DAAC is selected:
# Admins/Security see all collections from all groups.
if 'admin' in user_roles or 'security' in user_roles:
where_clause = "WHERE is_deleted = FALSE" # No filter, show all active
where_clause = "WHERE c.is_deleted = FALSE" # No filter, show all active
else:
# All other roles see an empty list if no DAAC is selected.
# This forces managers to select a DAAC to see its collections.
where_clause = "WHERE FALSE" # Return no rows

if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
if where_clause != "WHERE FALSE":
params.append(user_id)
where_clause += f" AND c.provider_id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"

limit_param = len(params) + 1
offset_param = len(params) + 2

Expand Down Expand Up @@ -138,6 +146,14 @@ async def get_collection_count(conn: Connection, requesting_user: Dict[str, Any]
# This forces managers to select a DAAC to see its collections.
where_clause = "WHERE FALSE" # Return no rows

if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
if where_clause != "WHERE FALSE":
params.append(user_id)
where_clause += f" AND provider_id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"

try:
query = f"SELECT count(id) FROM collection {where_clause}"
total_row = await conn.fetchrow(query, *params)
Expand Down
16 changes: 14 additions & 2 deletions src/python/api/v2/database_util/cueuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ async def list_users(

# Use an EXISTS subquery for efficient filtering without disturbing the main query structure
where_conditions = []
if active_ngroup_id:
if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
params.append(user_id)
where_conditions.append(f"u.id = ${len(params)}")
elif active_ngroup_id:
where_conditions.append(
"EXISTS (SELECT 1 FROM cueuser_ngroup ug WHERE ug.cueuser_id = u.id AND ug.ngroup_id = $1)"
)
Expand Down Expand Up @@ -313,7 +319,13 @@ async def get_users_count(
params = []
where_conditions = []

if active_ngroup_id:
if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
params.append(user_id)
where_conditions.append(f"u.id = ${len(params)}")
elif active_ngroup_id:
# Filter only users in this DAAC
where_conditions.append(
"EXISTS (SELECT 1 FROM cueuser_ngroup ug WHERE ug.cueuser_id = u.id AND ug.ngroup_id = $1)"
Expand Down
22 changes: 22 additions & 0 deletions src/python/api/v2/database_util/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ async def list_providers(
# This forces managers to select a DAAC to see its providers.
where_clause = "WHERE FALSE" # Return no rows

if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
if where_clause != "WHERE FALSE":
params.append(user_id)
if where_clause:
where_clause += f" AND p.id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"
else:
where_clause = f"WHERE p.id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"

can_upload_param = len(params) + 1
if can_upload is not None:
#test where class with empty string
Expand Down Expand Up @@ -148,6 +159,17 @@ async def get_providers_count(conn: Connection, requesting_user: Dict[str, Any],
# This forces managers to select a DAAC to see its providers.
where_clause = "WHERE FALSE" # Return no rows

if 'provider' in user_roles:
user_id = requesting_user.get('id')
if isinstance(user_id, str):
user_id = UUID(user_id)
if where_clause != "WHERE FALSE":
params.append(user_id)
if where_clause:
where_clause += f" AND id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"
else:
where_clause = f"WHERE id IN (SELECT provider_id FROM cueuser_provider WHERE cueuser_id = ${len(params)})"

can_upload_param = len(params) + 1
if can_upload is not None:
#test where class with empty string
Expand Down
10 changes: 10 additions & 0 deletions src/python/api/v2/endpoints/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ async def get_collection_endpoint(request: Request, collection_id: UUID, user: A

if not is_admin and str(collection['ngroup_id']) not in user_ngroup_ids:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.")

if not is_admin and "provider" in user.roles:
async with request.state.pool.acquire() as conn:
has_association = await conn.fetchval(
"SELECT EXISTS(SELECT 1 FROM cueuser_provider WHERE cueuser_id = $1 AND provider_id = $2)",
user.id, collection['provider_id']
)
if not has_association:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.")

return CollectionResponse.model_validate(collection)
except collection_utils.CollectionNotFoundError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
Expand Down
33 changes: 29 additions & 4 deletions src/python/api/v2/endpoints/cueuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,58 @@ async def find_user_endpoint(
email: Optional[str] = Query(None, description="Email to search for (case-insensitive, partial match)."),
cueusername: Optional[str] = Query(None, description="Username to search for (case-insensitive, partial match)."),
name: Optional[str] = Query(None, description="Name to search for (case-insensitive, partial match)."),
edpub_id: Optional[str] = Query(None, description="Exact EdPub ID to search for.")
edpub_id: Optional[str] = Query(None, description="Exact EdPub ID to search for."),
user: User = Depends(get_current_user)
):
"""Finds users based on various criteria."""
try:
users = await cueuser_utils.find_users_by_criteria(request, email, cueusername, name, edpub_id)
is_admin = "admin" in user.roles
if not is_admin and "provider" in user.roles:
users = [u for u in users if u['id'] == user.id]
return [UserFindResponse.model_validate(u) for u in users]
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))

@router.get("/by_role/{role_id}", response_model=List[UserResponse], dependencies=[Depends(require_privilege("user:read"))])
async def get_users_by_role_endpoint(request: Request, role_id: UUID):
async def get_users_by_role_endpoint(
request: Request,
role_id: UUID,
user: User = Depends(get_current_user)
):
"""Lists all users assigned to a specific role."""
users = await cueuser_utils.get_users_by_role(request, role_id)
is_admin = "admin" in user.roles
if not is_admin and "provider" in user.roles:
users = [u for u in users if u['id'] == user.id]
return [UserResponse.model_validate(u) for u in users]

@router.get("/{user_id}", response_model=UserResponse, dependencies=[Depends(require_privilege("user:read"))])
async def get_user_by_id_endpoint(request: Request, user_id: UUID):
async def get_user_by_id_endpoint(
request: Request,
user_id: UUID,
user: User = Depends(get_current_user)
):
"""Retrieves a specific user's profile by their ID."""
is_admin = "admin" in user.roles
if not is_admin and "provider" in user.roles and user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.")
try:
user_profile = await cueuser_utils.get_user_profile(request, user_id)
return UserResponse.model_validate(user_profile)
except cueuser_utils.UserNotFoundError:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")

@router.get("/by_username/{cueusername}", response_model=UserResponse, dependencies=[Depends(require_privilege("user:read"))])
async def get_user_by_username_endpoint(request: Request, cueusername: str):
async def get_user_by_username_endpoint(
request: Request,
cueusername: str,
user: User = Depends(get_current_user)
):
"""Retrieves a specific user's profile by their username."""
is_admin = "admin" in user.roles
if not is_admin and "provider" in user.roles and cueusername != user.cueusername:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied.")
try:
user_profile = await cueuser_utils.get_user_profile_by_username(request, cueusername)
return UserResponse.model_validate(user_profile)
Expand Down