Skip to content

Commit 921adec

Browse files
committed
feat: make remote_logger router VO-aware
1 parent d92645c commit 921adec

File tree

2 files changed

+85
-40
lines changed

2 files changed

+85
-40
lines changed

diracx-routers/src/diracx/routers/pilot_logging/access_policies.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55

66
from fastapi import Depends, HTTPException, status
77

8-
from diracx.core.properties import GENERIC_PILOT, OPERATOR, PILOT, SERVICE_ADMINISTRATOR
8+
from diracx.core.properties import (
9+
GENERIC_PILOT,
10+
NORMAL_USER,
11+
OPERATOR,
12+
PILOT,
13+
SERVICE_ADMINISTRATOR,
14+
)
915
from diracx.db.os import PilotLogsDB
1016
from diracx.routers.access_policies import BaseAccessPolicy
1117

@@ -15,8 +21,6 @@
1521
class ActionType(StrEnum):
1622
#: Create/update pilot log records
1723
CREATE = auto()
18-
#: download pilot logs
19-
READ = auto()
2024
#: delete pilot logs
2125
DELETE = auto()
2226
#: Search
@@ -43,14 +47,16 @@ async def policy(
4347
assert action, "action is a mandatory parameter"
4448
assert pilot_db, "pilot_db is a mandatory parameter"
4549

46-
if GENERIC_PILOT in user_info.properties:
47-
return
48-
if PILOT in user_info.properties:
49-
return
50+
if GENERIC_PILOT in user_info.properties and action == ActionType.CREATE:
51+
return user_info
52+
if PILOT in user_info.properties and action == ActionType.CREATE:
53+
return user_info
54+
if NORMAL_USER in user_info.properties and action == ActionType.QUERY:
55+
return user_info
5056
if SERVICE_ADMINISTRATOR in user_info.properties:
51-
return
57+
return user_info
5258
if OPERATOR in user_info.properties:
53-
return
59+
return user_info
5460

5561
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=user_info.properties)
5662

diracx-routers/src/diracx/routers/pilot_logging/remote_logger.py

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33
import datetime
44

5+
from fastapi import HTTPException, status
56
from pydantic import BaseModel
67
from sqlalchemy import select
8+
from sqlalchemy.exc import NoResultFound
79

810
from diracx.core.exceptions import InvalidQueryError
11+
from diracx.core.properties import OPERATOR, SERVICE_ADMINISTRATOR
912
from diracx.db.sql.pilot_agents.schema import PilotAgents
1013
from diracx.db.sql.utils import BaseSQLDB
1114

1215
from ..dependencies import PilotLogsDB
1316
from ..fastapi_classes import DiracxRouter
1417
from ..pilot_logging import logger
18+
from ..utils.users import AuthorizedUserInfo
1519
from .access_policies import ActionType, CheckPilotLogsPolicyCallable
1620

1721
router = DiracxRouter()
@@ -29,7 +33,6 @@ class LogMessage(BaseModel):
2933

3034

3135
class 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")
97115
async 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

Comments
 (0)