Raised in review of #1717, which makes UpdateAssignedSite write EformUser.IsActive — the platform's login kill-switch — when a worker is resigned. The write is correct; the endpoint it rides on is the problem, and it predates that PR.
TimePlanningSettingsController:
[HttpPut]
[Route("assigned-site")]
[Authorize(Policy = TimePlanningClaims.GetWorkingHours)]
public async Task<OperationResult> UpdateAssignedSite([FromBody] AssignedSite site)
Two problems, which only matter together:
GetWorkingHours is a read claim. The same policy guards the GET endpoints beside it. A write with offboarding consequences now sits behind "may view working hours".
- No ownership scoping.
site.SiteId comes straight from the request body and is looked up with no manager/tag check, so any site id is addressable.
So any account holding GetWorkingHours can PUT {"siteId": <any>, "resigned": true, ...} and disable that employee's login everywhere — the web back office and both flutter apps — since core refuses IsActive = false at every credential surface. There is no admin UI anywhere that shows or sets IsActive, so the only way back is another PUT with resigned: false.
Related sharp edge: the model binds from the body, so a payload that omits resigned deserializes to false. #1717 made the login write conditional on the resignation actually changing, which contains the blast radius — but the underlying clobber is still there for the settings row itself.
Options
- an admin-scoped claim for the
resigned field specifically, rather than the whole endpoint (smallest change that matches what the permission means)
- scope the write to sites the caller manages, the way the rest of the plugin scopes by managing tags
- split resignation out of the settings endpoint entirely
Worth deciding before the feature reaches customers, not after.
Raised in review of #1717, which makes
UpdateAssignedSitewriteEformUser.IsActive— the platform's login kill-switch — when a worker is resigned. The write is correct; the endpoint it rides on is the problem, and it predates that PR.TimePlanningSettingsController:Two problems, which only matter together:
GetWorkingHoursis a read claim. The same policy guards theGETendpoints beside it. A write with offboarding consequences now sits behind "may view working hours".site.SiteIdcomes straight from the request body and is looked up with no manager/tag check, so any site id is addressable.So any account holding
GetWorkingHourscanPUT {"siteId": <any>, "resigned": true, ...}and disable that employee's login everywhere — the web back office and both flutter apps — since core refusesIsActive = falseat every credential surface. There is no admin UI anywhere that shows or setsIsActive, so the only way back is anotherPUTwithresigned: false.Related sharp edge: the model binds from the body, so a payload that omits
resigneddeserializes tofalse. #1717 made the login write conditional on the resignation actually changing, which contains the blast radius — but the underlying clobber is still there for the settings row itself.Options
resignedfield specifically, rather than the whole endpoint (smallest change that matches what the permission means)Worth deciding before the feature reaches customers, not after.