22
33import datetime
44
5+ from fastapi import HTTPException , status
56from pydantic import BaseModel
67from sqlalchemy import select
8+ from sqlalchemy .exc import NoResultFound
79
810from diracx .core .exceptions import InvalidQueryError
11+ from diracx .core .properties import OPERATOR , SERVICE_ADMINISTRATOR
912from diracx .db .sql .pilot_agents .schema import PilotAgents
1013from diracx .db .sql .utils import BaseSQLDB
1114
1215from ..dependencies import PilotLogsDB
1316from ..fastapi_classes import DiracxRouter
1417from ..pilot_logging import logger
18+ from ..utils .users import AuthorizedUserInfo
1519from .access_policies import ActionType , CheckPilotLogsPolicyCallable
1620
1721router = DiracxRouter ()
@@ -29,7 +33,6 @@ class LogMessage(BaseModel):
2933
3034
3135class DateRange (BaseModel ):
32- pilot_id : int | None = None
3336 min : str | None = None # expects a string in ISO 8601 ("%Y-%m-%dT%H:%M:%S.%f%z")
3437 max : str | None = None # expects a string in ISO 8601 ("%Y-%m-%dT%H:%M:%S.%f%z")
3538
@@ -39,10 +42,12 @@ async def send_message(
3942 data : LogMessage ,
4043 pilot_logs_db : PilotLogsDB ,
4144 check_permissions : CheckPilotLogsPolicyCallable ,
42- ):
43- logger .warning (f"Message received '{ data } '" )
44- await check_permissions (action = ActionType .CREATE , pilot_db = pilot_logs_db )
45+ ) -> int :
4546
47+ logger .warning (f"Message received '{ data } '" )
48+ user_info = await check_permissions (
49+ action = ActionType .CREATE , pilot_db = pilot_logs_db
50+ )
4651 pilot_id = 0 # need to get pilot id from pilot_stamp (via PilotAgentsDB)
4752 # also add a timestamp to be able to select and delete logs based on pilot creation dates, even if corresponding
4853 # pilots have been already deleted from PilotAgentsDB (so the logs can live longer than pilots).
@@ -51,12 +56,18 @@ async def send_message(
5156 url = BaseSQLDB .available_urls ()["PilotAgentsDB" ]
5257 db = piloAgentsDB (url )
5358
54- async with db .engine_context ():
55- async with db :
56- stmt = select (PilotAgents .PilotID , PilotAgents .SubmissionTime ).where (
57- PilotAgents .PilotStamp == data .pilot_stamp
58- )
59- pilot_id , submission_time = (await db .conn .execute (stmt )).one ()
59+ try :
60+ async with db .engine_context ():
61+ async with db :
62+ stmt = select (PilotAgents .PilotID , PilotAgents .SubmissionTime ).where (
63+ PilotAgents .PilotStamp == data .pilot_stamp
64+ )
65+ pilot_id , submission_time = (await db .conn .execute (stmt )).one ()
66+ except NoResultFound as exc :
67+ logger .error (
68+ f"Cannot determine PilotID for requested PilotStamp: { data .pilot_stamp } , Error: { exc } ."
69+ )
70+ raise HTTPException (status .HTTP_400_BAD_REQUEST , detail = str (exc )) from exc
6071
6172 docs = []
6273 for line in data .lines :
@@ -65,7 +76,7 @@ async def send_message(
6576 "PilotStamp" : data .pilot_stamp ,
6677 "PilotID" : pilot_id ,
6778 "SubmissionTime" : submission_time ,
68- "VO" : data .vo ,
79+ "VO" : user_info .vo ,
6980 "LineNumber" : line .line_no ,
7081 "Message" : line .line ,
7182 }
@@ -79,41 +90,69 @@ async def get_logs(
7990 pilot_id : int ,
8091 db : PilotLogsDB ,
8192 check_permissions : CheckPilotLogsPolicyCallable ,
82- ):
93+ ) -> list [dict ]:
94+
8395 logger .warning (f"Retrieving logs for pilot ID '{ pilot_id } '" )
84- await check_permissions (action = ActionType .QUERY , pilot_db = db )
96+ user_info = await check_permissions (action = ActionType .QUERY , pilot_db = db )
8597
98+ # here, users with privileged properties will see logs from all VOs. Is it what we want ?
99+ search_params = [{"parameter" : "PilotID" , "operator" : "eq" , "value" : pilot_id }]
100+ if _non_privileged (user_info ):
101+ search_params .append (
102+ {"parameter" : "VO" , "operator" : "eq" , "value" : user_info .vo }
103+ )
86104 result = await db .search (
87105 ["Message" ],
88- [{ "parameter" : "PilotID" , "operator" : "eq" , "value" : pilot_id }] ,
106+ search_params ,
89107 [{"parameter" : "LineNumber" , "direction" : "asc" }],
90108 )
91109 if not result :
92- return [f"No logs for pilot ID = { pilot_id } " ]
110+ return [{ "Message" : f"No logs for pilot ID = { pilot_id } " } ]
93111 return result
94112
95113
96114@router .delete ("/logs" )
97115async def delete (
116+ pilot_id : int ,
98117 data : DateRange ,
99118 db : PilotLogsDB ,
100119 check_permissions : CheckPilotLogsPolicyCallable ,
101120) -> str :
102- """Delete either logs for a specific PilotID or a creation date range."""
103- await check_permissions (action = ActionType .DELETE , pilot_db = db )
104- if data .pilot_id :
105- await db .delete (
106- [{"parameter" : "PilotID" , "operator" : "eq" , "value" : data .pilot_id }]
107- )
108- return f"Logs for pilot ID '{ data .pilot_id } ' successfully deleted"
109- if data .min and not data .max :
110- logger .warning (f"Deleting logs for pilots with submission data >='{ data .min } '" )
111- await db .delete (
112- [{"parameter" : "SubmissionTime" , "operator" : "gt" , "value" : data .min }]
113- )
114- return f"Logs for for pilots with submission data >='{ data .min } ' successfully deleted"
115- if data .min and data .max :
121+ """Delete either logs for a specific PilotID or a creation date range.
122+ Non-privileged users can only delete log files within their own VO.
123+ """
124+ message = "no-op"
125+ user_info = await check_permissions (action = ActionType .DELETE , pilot_db = db )
126+ non_privil_params = {"parameter" : "VO" , "operator" : "eq" , "value" : user_info .vo }
127+
128+ # id pilot_id is provided we ignore data.min and data.max
129+ if data .min and data .max and not pilot_id :
116130 raise InvalidQueryError (
117- "This query requires a range operater definition in DiracX"
131+ "This query requires a range operator definition in DiracX"
118132 )
119- return "no-op"
133+
134+ if pilot_id :
135+ search_params = [{"parameter" : "PilotID" , "operator" : "eq" , "value" : pilot_id }]
136+ if _non_privileged (user_info ):
137+ search_params .append (non_privil_params )
138+ await db .delete (search_params )
139+ message = f"Logs for pilot ID '{ pilot_id } ' successfully deleted"
140+
141+ elif data .min :
142+ logger .warning (f"Deleting logs for pilots with submission data >='{ data .min } '" )
143+ search_params = [
144+ {"parameter" : "SubmissionTime" , "operator" : "gt" , "value" : data .min }
145+ ]
146+ if _non_privileged (user_info ):
147+ search_params .append (non_privil_params )
148+ await db .delete (search_params )
149+ message = f"Logs for for pilots with submission data >='{ data .min } ' successfully deleted"
150+
151+ return message
152+
153+
154+ def _non_privileged (user_info : AuthorizedUserInfo ):
155+ return (
156+ SERVICE_ADMINISTRATOR not in user_info .properties
157+ and OPERATOR not in user_info .properties
158+ )
0 commit comments