From f32c2efcc28819dd6317c64e11c1433a6cea3b0d Mon Sep 17 00:00:00 2001 From: Jackie Date: Wed, 16 Jul 2025 20:18:50 +0200 Subject: [PATCH 1/2] Add create-by audit for scenario creation (again) --- api/src/api/app/apis/scenarios_api.py | 7 ++++++- api/src/api/app/controller/scenario_controller.py | 4 +++- api/src/api/app/db/models.py | 2 ++ api/src/api/app/db/tasks.py | 4 +++- api/src/api/app/middlewares/authentication_middleware.py | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/api/src/api/app/apis/scenarios_api.py b/api/src/api/app/apis/scenarios_api.py index c27fb75..48627f3 100644 --- a/api/src/api/app/apis/scenarios_api.py +++ b/api/src/api/app/apis/scenarios_api.py @@ -41,10 +41,15 @@ response_model_by_alias=True, ) async def create_scenario( + request: Request, scenario: Scenario = Body(None, description="") ) -> ID: """Create a new scenario to be simulated.""" - return await controller.create_scenario(scenario) + return await controller.create_scenario( + scenario, + request.state.user.userId if request.state.user else None, + request.state.realm if request.state.realm else None + ) @router.delete( diff --git a/api/src/api/app/controller/scenario_controller.py b/api/src/api/app/controller/scenario_controller.py index de1c9c3..3c129d1 100644 --- a/api/src/api/app/controller/scenario_controller.py +++ b/api/src/api/app/controller/scenario_controller.py @@ -62,11 +62,13 @@ class ScenarioController: async def create_scenario( self, scenario: Optional[Scenario], + userId: Optional[str], + orgId: Optional[str] ) -> ID: """Create a new scenario to be simulated.""" if not scenario: raise HTTPException(status_code=500, detail="No scenario provided") - return scenario_create(scenario) + return scenario_create(scenario, userId, orgId) async def delete_scenario( diff --git a/api/src/api/app/db/models.py b/api/src/api/app/db/models.py index c53675b..4eaaffc 100644 --- a/api/src/api/app/db/models.py +++ b/api/src/api/app/db/models.py @@ -28,6 +28,8 @@ class Scenario(SQLModel, table=True): timestampSubmitted: Optional[datetime] = Field(default=None, nullable=True) timestampSimulated: Optional[datetime] = Field(default=None, nullable=True) + userId: Optional[str] = Field(default=None, nullable=True) # Created by user + orgId: Optional[str] = Field(default=None, nullable=True) # Created by user's LHA/Organization class ParameterDefinition(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) diff --git a/api/src/api/app/db/tasks.py b/api/src/api/app/db/tasks.py index cdcf33c..0ee8794 100644 --- a/api/src/api/app/db/tasks.py +++ b/api/src/api/app/db/tasks.py @@ -412,7 +412,7 @@ def parameter_definition_delete(id: StrictStr) -> None: ## Scenarios ## -def scenario_create(scenario: Scenario) -> ID: +def scenario_create(scenario: Scenario, userId: Optional[str], orgId: Optional[str]) -> ID: scenario_obj = db.Scenario( name=scenario.name, description=scenario.description, @@ -423,6 +423,8 @@ def scenario_create(scenario: Scenario) -> ID: percentiles=','.join([str(perc) for perc in scenario.percentiles]) if scenario.percentiles else '50', timestampSubmitted=datetime.now(), timestampSimulated=None, + userId=userId, + orgId=orgId ) with next(get_session()) as session: nested_dict = lambda: defaultdict(nested_dict) diff --git a/api/src/api/app/middlewares/authentication_middleware.py b/api/src/api/app/middlewares/authentication_middleware.py index c3013f3..5308980 100644 --- a/api/src/api/app/middlewares/authentication_middleware.py +++ b/api/src/api/app/middlewares/authentication_middleware.py @@ -22,7 +22,7 @@ async def dispatch(self, request, call_next): # async def get_user(request: Request): # return request.state.user request.state.user = user - + request.state.realm = realm # (Optional) role check can be added # if ['admin'] not in user.role: # raise HTTPException( From fbedee87f5bda8616e5dc1d1f1b5f6a276874e28 Mon Sep 17 00:00:00 2001 From: Jackie Date: Sun, 20 Jul 2025 21:45:19 +0200 Subject: [PATCH 2/2] Refactor scenario creation related function so that creator info is contained in the DTO and now get scenario by id also returns creator info --- api/src/api/app/apis/scenarios_api.py | 9 ++++++--- api/src/api/app/controller/scenario_controller.py | 6 ++---- api/src/api/app/db/models.py | 4 ++-- api/src/api/app/db/tasks.py | 8 +++++--- api/src/api/app/models/scenario.py | 3 +++ 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/api/src/api/app/apis/scenarios_api.py b/api/src/api/app/apis/scenarios_api.py index 48627f3..7c024ed 100644 --- a/api/src/api/app/apis/scenarios_api.py +++ b/api/src/api/app/apis/scenarios_api.py @@ -45,10 +45,13 @@ async def create_scenario( scenario: Scenario = Body(None, description="") ) -> ID: """Create a new scenario to be simulated.""" + + # Tag creator info + scenario.creator_user_id = request.state.user.userId if request.state.user else None + scenario.creator_org_id = request.state.realm if request.state.realm else None + return await controller.create_scenario( - scenario, - request.state.user.userId if request.state.user else None, - request.state.realm if request.state.realm else None + scenario ) diff --git a/api/src/api/app/controller/scenario_controller.py b/api/src/api/app/controller/scenario_controller.py index 3c129d1..92eb620 100644 --- a/api/src/api/app/controller/scenario_controller.py +++ b/api/src/api/app/controller/scenario_controller.py @@ -61,14 +61,12 @@ class ScenarioController: async def create_scenario( self, - scenario: Optional[Scenario], - userId: Optional[str], - orgId: Optional[str] + scenario: Optional[Scenario] ) -> ID: """Create a new scenario to be simulated.""" if not scenario: raise HTTPException(status_code=500, detail="No scenario provided") - return scenario_create(scenario, userId, orgId) + return scenario_create(scenario) async def delete_scenario( diff --git a/api/src/api/app/db/models.py b/api/src/api/app/db/models.py index 4eaaffc..940fb81 100644 --- a/api/src/api/app/db/models.py +++ b/api/src/api/app/db/models.py @@ -28,8 +28,8 @@ class Scenario(SQLModel, table=True): timestampSubmitted: Optional[datetime] = Field(default=None, nullable=True) timestampSimulated: Optional[datetime] = Field(default=None, nullable=True) - userId: Optional[str] = Field(default=None, nullable=True) # Created by user - orgId: Optional[str] = Field(default=None, nullable=True) # Created by user's LHA/Organization + creatorUserId: Optional[uuid.UUID] = Field(default=None, nullable=True) + creatorOrgId: Optional[str] = Field(default=None, nullable=True) class ParameterDefinition(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) diff --git a/api/src/api/app/db/tasks.py b/api/src/api/app/db/tasks.py index 0ee8794..e7b4deb 100644 --- a/api/src/api/app/db/tasks.py +++ b/api/src/api/app/db/tasks.py @@ -412,7 +412,7 @@ def parameter_definition_delete(id: StrictStr) -> None: ## Scenarios ## -def scenario_create(scenario: Scenario, userId: Optional[str], orgId: Optional[str]) -> ID: +def scenario_create(scenario: Scenario) -> ID: scenario_obj = db.Scenario( name=scenario.name, description=scenario.description, @@ -423,8 +423,8 @@ def scenario_create(scenario: Scenario, userId: Optional[str], orgId: Optional[s percentiles=','.join([str(perc) for perc in scenario.percentiles]) if scenario.percentiles else '50', timestampSubmitted=datetime.now(), timestampSimulated=None, - userId=userId, - orgId=orgId + creatorUserId=scenario.creator_user_id, + creatorOrgId=scenario.creator_org_id ) with next(get_session()) as session: nested_dict = lambda: defaultdict(nested_dict) @@ -538,6 +538,8 @@ def scenario_get_by_id(id: StrictStr) -> Scenario: percentiles=[int(perc) for perc in scenario.percentiles.split(',')] if scenario.percentiles else [50], timestampSubmitted=scenario.timestampSubmitted, timestampSimulated=scenario.timestampSimulated, + creator_user_id=str(scenario.creatorUserId), + creator_org_id=scenario.creatorOrgId ) def scenario_get_all() -> List[ReducedScenario]: diff --git a/api/src/api/app/models/scenario.py b/api/src/api/app/models/scenario.py index 523d582..eed241c 100644 --- a/api/src/api/app/models/scenario.py +++ b/api/src/api/app/models/scenario.py @@ -49,6 +49,9 @@ class Scenario(BaseModel): timestamp_simulated: Optional[datetime] = Field(default=None, alias="timestampSimulated", description="Timestamp when the scenario was finished simulating and data is available") __properties: ClassVar[List[str]] = ["id", "name", "description", "startDate", "endDate", "modelId", "modelParameters", "nodeListId", "linkedInterventions", "percentiles", "timestampSubmitted", "timestampSimulated"] + creator_user_id: Optional[str] = None + creator_org_id: Optional[str] = None + model_config = { "populate_by_name": True, "validate_assignment": True,