Skip to content

RE1-T46 bug fixes#296

Merged
ucswift merged 6 commits intomasterfrom
develop
Mar 11, 2026
Merged

RE1-T46 bug fixes#296
ucswift merged 6 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Mar 11, 2026

Summary by CodeRabbit

  • Style

    • Roles dropdown on Add Person now displays full width for improved usability.
  • Bug Fixes / Reliability

    • Improved cache handling to recover from deserialization failures and enable automatic fallback.
    • Made year parsing tolerant to invalid input by defaulting to the current year.
    • Added guards to avoid errors when shift data is missing.
    • Preserved audit timestamps on workflow steps and added an atomic workflow deletion to avoid integrity errors.
  • Security / Improvement

    • Strengthened server-side authorization and IDOR mitigations across department management and profile flows.
    • Added server permission checks for department modification.
  • UI

    • Document delete now uses a secure POST form with confirmation and anti-forgery protection.
  • Improvement

    • Action-log retrieval updated to return consistent user and log data.

@request-info
Copy link

request-info bot commented Mar 11, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Uses explicit SQL aliases for action-log queries, logs-and-clears Redis deserialize errors, adds year-parse fallback, guards shift calendar rendering, minor view tweaks, adds CanUserModifyDepartmentAsync and enforces it across controllers (IDOR mitigations), converts document delete to POST with antiforgery, and adds a document-delete claim helper.

Changes

Cohort / File(s) Summary
ActionLog SQL refactor
Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs, Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
SelectPreviousActionLogsByUserQuery and SelectLastActionLogByUserIdQuery now use table aliases (a1, u) and select a1.*, u.*; ORDER BY/LIMIT/TOP updated to reference aliased columns.
Redis cache handling
Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs
Deserialize exceptions are now caught as Exception deserializeEx, logged, non-environment cache entries removed (Remove/RemoveAsync), and data set to null instead of rethrowing.
Workflow cascading delete
Core/Resgrid.Model/Repositories/IWorkflowRepository.cs, Repositories/Resgrid.Repositories.DataRepository/WorkflowRepository.cs, Core/Resgrid.Services/WorkflowService.cs
New repository API DeleteWorkflowWithAllDependenciesAsync(string) implemented to delete run logs/runs/steps and the workflow in a single transaction; WorkflowService switched to call this atomic deletion.
Authorization API & usage
Core/Resgrid.Model/Services/IAuthorizationService.cs, Core/Resgrid.Services/AuthorizationService.cs, Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs
Added CanUserModifyDepartmentAsync(string userId, int departmentId) to interface and implementation; DepartmentController now invokes it broadly and enforces server-authoritative Department/DepartmentId values and address ownership checks, returning Unauthorized() when appropriate.
Home controller privilege hardening
Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
Derives server-side privilege flags (callerIsDepartmentAdmin, callerIsGroupAdmin, IsOwnProfile), enforces them for email/UDF/member-admin updates, and avoids trusting posted privilege fields.
Document deletion safety
Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs, Web/Resgrid.Web/Views/Documents/Index.cshtml, Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs
DeleteDocument converted from GET to POST with [ValidateAntiForgeryToken]; view replaced per-document actions with inline POST delete form; added CanDeleteDocument() claim helper.
Logs parsing fallback
Repositories/Resgrid.Repositories.DataRepository/LogsRepository.cs
Replaced int.Parse(year) with int.TryParse(...) ? parsedYear : DateTime.UtcNow.Year to avoid exceptions for invalid input.
Shift calendar guard
Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs
Added early return of an empty calendar when shift is null, department mismatch, or shift.Days is null/empty.
UI view tweaks
Web/Resgrid.Web/Areas/User/Views/Personnel/AddPerson.cshtml
Set Roles select element to style="width: 100%;".
Misc SQL/manifest tweaks
Repositories/.../PostgreSqlConfiguration.cs, Repositories/.../SqlServerConfiguration.cs, *.csproj
Small manifest and SQL projection changes noted in diff summary (lines changed).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client as "Client"
    participant DeptCtrl as "DepartmentController"
    participant AuthSvc as "AuthorizationService"
    participant DeptRepo as "DepartmentRepository"
    participant DB as "Database"

    Client->>DeptCtrl: POST/PUT modify department (with antiforgery, form data)
    DeptCtrl->>AuthSvc: CanUserModifyDepartmentAsync(userId, departmentId)
    AuthSvc->>DeptRepo: GetDepartmentById(departmentId)
    DeptRepo->>DB: SELECT Department WHERE Id=...
    DB-->>DeptRepo: DepartmentRecord
    DeptRepo-->>AuthSvc: Department object
    AuthSvc-->>DeptCtrl: true/false
    alt authorized
        DeptCtrl->>DeptRepo: Validate ownership / apply server-authoritative fields
        DeptRepo->>DB: UPDATE/DELETE...
        DB-->>DeptRepo: OK
        DeptCtrl-->>Client: 200 OK / redirect
    else unauthorized
        DeptCtrl-->>Client: 401 Unauthorized
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'RE1-T46 bug fixes' is vague and generic, using non-descriptive terms that don't convey meaningful information about the changeset. Replace with a specific description of the main bug fixes, e.g., 'Fix IDOR vulnerabilities in department management and authorization' or 'Add authorization checks and improve data validation across controllers'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs`:
- Around line 87-91: The query assigned to SelectPreviousActionLogsByUserQuery
can return any older row because it lacks deterministic ordering but is consumed
with FirstOrDefault(); update the SQL string in PostgreSqlConfiguration.cs
(SelectPreviousActionLogsByUserQuery) to order by a1.ActionLogId descending and
restrict to a single row (Postgres LIMIT 1) so it always returns the immediately
previous action log for the given user and ActionLogId.

In
`@Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs`:
- Around line 85-89: The SQL in SelectPreviousActionLogsByUserQuery can return
multiple rows and an indeterminate "previous" row; update the query used by
ActionLogsRepository.GetPreviousActionLogAsync to return a single deterministic
row by selecting only the top row and ordering by the ActionLogId descending
(e.g., use SELECT TOP 1 for SQL Server and add ORDER BY a1.[ActionLogId] DESC)
so the repository's FirstOrDefault() always receives the most recent earlier log
for the given %USERID% and %ACTIONLOGID%.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eff148c3-3e47-4589-8127-2bed56aa4477

📥 Commits

Reviewing files that changed from the base of the PR and between 3e41cf5 and a0a5825.

📒 Files selected for processing (3)
  • Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
  • Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
  • Web/Resgrid.Web/Areas/User/Views/Personnel/AddPerson.cshtml

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs`:
- Around line 39-44: In the catch block that handles deserialization failures,
avoid double-prefixing the key: call Remove with the original cacheKey
(Remove(cacheKey)) instead of Remove(SetCacheKeyForEnv(cacheKey)), because
Remove already calls SetCacheKeyForEnv internally; update the catch block where
deserializeEx is handled (and referenced functions SetCacheKeyForEnv and Remove)
to pass the unmodified cacheKey so the actual stored entry is removed.
- Around line 115-120: In AzureRedisCacheProvider, the catch block is calling
RemoveAsync(SetCacheKeyForEnv(cacheKey)) which double-applies SetCacheKeyForEnv
(RemoveAsync already calls SetCacheKeyForEnv internally); change the call to
pass the original cacheKey (e.g., RemoveAsync(cacheKey)) so RemoveAsync can
apply SetCacheKeyForEnv exactly once and the corrupted cache entry is correctly
removed; keep Logging.LogException(deserializeEx) and null assignment for data
unchanged.

In `@Repositories/Resgrid.Repositories.DataRepository/LogsRepository.cs`:
- Line 204: The code in LogsRepository.cs currently substitutes any malformed
year string with DateTime.UtcNow.Year; change this so only null/whitespace falls
back to the current year and any non-empty, non-numeric year is rejected: update
the dynamicParameters.Add("Year", ...) logic in the method that builds query
parameters (the line with dynamicParameters.Add("Year", int.TryParse(year, out
var parsedYear) ? parsedYear : DateTime.UtcNow.Year)) to check if
string.IsNullOrWhiteSpace(year) then use DateTime.UtcNow.Year, else attempt
int.TryParse and if parsing fails throw an ArgumentException (or propagate a
validation error) so callers (e.g., LogsController/WorkLogsService) can surface
invalid input instead of silently using the current year.

In `@Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs`:
- Around line 1089-1092: The endpoint currently returns shift data without
verifying department ownership; after retrieving shift via
_shiftsService.GetShiftByIdAsync(shiftId) add a guard that compares
shift.DepartmentId to the controller's DepartmentId (or equivalent property) and
if they do not match return Json(calendarItems); ensure this check is placed
alongside the existing null/empty Days guard so other departments cannot access
another department's shift data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fee94518-d80b-4630-971e-44607f825b02

📥 Commits

Reviewing files that changed from the base of the PR and between a0a5825 and acaa56e.

📒 Files selected for processing (3)
  • Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs
  • Repositories/Resgrid.Repositories.DataRepository/LogsRepository.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ShiftsController.cs

var dynamicParameters = new DynamicParametersExtension();
dynamicParameters.Add("DepartmentId", departmentId);
dynamicParameters.Add("Year", int.Parse(year));
dynamicParameters.Add("Year", int.TryParse(year, out var parsedYear) ? parsedYear : DateTime.UtcNow.Year);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't substitute malformed year values with the current year.

Line 204 turns invalid input into a valid-but-wrong filter. Because Web/Resgrid.Web/Areas/User/Controllers/LogsController.cs passes the query-string value through and Core/Resgrid.Services/WorkLogsService.cs does not validate it, a request like ?year=abcd will now return current-year logs instead of surfacing bad input. That is a correctness regression and will be very hard to spot from the UI.

Prefer failing validation upstream, or at minimum only apply a fallback for null/whitespace and reject malformed values explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Repositories/Resgrid.Repositories.DataRepository/LogsRepository.cs` at line
204, The code in LogsRepository.cs currently substitutes any malformed year
string with DateTime.UtcNow.Year; change this so only null/whitespace falls back
to the current year and any non-empty, non-numeric year is rejected: update the
dynamicParameters.Add("Year", ...) logic in the method that builds query
parameters (the line with dynamicParameters.Add("Year", int.TryParse(year, out
var parsedYear) ? parsedYear : DateTime.UtcNow.Year)) to check if
string.IsNullOrWhiteSpace(year) then use DateTime.UtcNow.Year, else attempt
int.TryParse and if parsing fails throw an ArgumentException (or propagate a
validation error) so callers (e.g., LogsController/WorkLogsService) can surface
invalid input instead of silently using the current year.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs (1)

718-743: ⚠️ Potential issue | 🔴 Critical

Keep role updates inside the admin-only branch.

The new hardening protects group/admin flags, but roles is still applied for any caller who can reach this action. Because SetRolesForUserAsync only receives the target user and role ids, this path has no actor context, and roles are permission-bearing here (for example, Providers/Resgrid.Providers.Claims/ClaimsLogic.cs:524-601 grants document create/delete claims from role membership). A self-edit or group-admin post can still escalate privileges through this block.

🛡️ Suggested fix
-				if (form.ContainsKey("roles"))
+				if (callerIsDepartmentAdmin && form.ContainsKey("roles"))
 				{
 					var roles = form["roles"].ToString().Split(char.Parse(","));

 					if (roles.Any())
 						await _personnelRolesService.SetRolesForUserAsync(DepartmentId, model.UserId, roles, cancellationToken);
 				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs` around lines 718 -
743, The roles update block currently runs for any caller; restrict it to
department admins by moving the form.ContainsKey("roles") handling inside the
callerIsDepartmentAdmin branch (where group/admin flag changes are applied).
Ensure the code that calls
_personnelRolesService.SetRolesForUserAsync(DepartmentId, model.UserId, roles,
cancellationToken) only executes when callerIsDepartmentAdmin is true (preserve
parsing of form["roles"] to string[] and existing use of DepartmentId,
model.UserId and cancellationToken). This prevents non-admins from invoking
SetRolesForUserAsync and escalating privileges.
Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs (3)

664-669: ⚠️ Potential issue | 🟠 Major

Do not rotate department keys from GET endpoints.

These actions still mutate API/RSS keys on GET, so a cross-site navigation can trigger unintended key rotation under an authenticated session. Convert them to POST and require an antiforgery token.

🛡️ Suggested fix
-[HttpGet]
+[HttpPost]
+[ValidateAntiForgeryToken]
 [Authorize(Policy = ResgridResources.Department_Update)]
 public async Task<IActionResult> ProvisionApiKey(CancellationToken cancellationToken)

Apply the same change to Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.ProvisionApiKeyAsync(CancellationToken) and Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.ProvisionActiveCallRssKey(CancellationToken), then update the callers to submit POSTs.

Also applies to: 682-687, 696-701

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs` around lines
664 - 669, The ProvisionApiKey, ProvisionApiKeyAsync and
ProvisionActiveCallRssKey controller actions currently rotate keys on an HTTP
GET which is unsafe; change each action's routing attribute from [HttpGet] to
[HttpPost] and add [ValidateAntiForgeryToken] (keeping existing [Authorize]
checks) and then update any client callers/views/forms to submit POST requests
(including antiforgery tokens) to these endpoints so key rotation occurs only
via explicit POSTs.

1810-1815: ⚠️ Potential issue | 🔴 Critical

Use the delete-specific authorization check here.

Core/Resgrid.Services.AuthorizationService.CanUserDeleteDepartmentAsync(string userId, int departmentId) already limits department deletion to ManagingUserId. Replacing it with Core/Resgrid.Model.Services.IAuthorizationService.CanUserModifyDepartmentAsync(string userId, int departmentId) broadens deletion to any department admin, and this POST still has no antiforgery validation.

🛡️ Suggested fix
 [HttpPost]
+[ValidateAntiForgeryToken]
 [Authorize(Policy = ResgridResources.Department_Update)]
 public async Task<IActionResult> DeleteDepartment(DeleteDepartmentView model, CancellationToken cancellationToken)
 {
-	if (!await _authorizationService.CanUserModifyDepartmentAsync(UserId, DepartmentId))
+	if (!await _authorizationService.CanUserDeleteDepartmentAsync(UserId, DepartmentId))
 		return Unauthorized();

The corresponding confirmation form should post an antiforgery token as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs` around lines
1810 - 1815, The DeleteDepartment POST action uses CanUserModifyDepartmentAsync
but should call CanUserDeleteDepartmentAsync to enforce deletion-only rights;
update the authorization check in the DeleteDepartment method to await
_authorizationService.CanUserDeleteDepartmentAsync(UserId, DepartmentId) instead
of CanUserModifyDepartmentAsync, and add antiforgery protection by annotating
the action with [ValidateAntiForgeryToken] (and ensure the confirmation form in
the DeleteDepartment view includes `@Html.AntiForgeryToken`() or equivalent) so
the POST requires a valid antiforgery token.

865-870: ⚠️ Potential issue | 🟠 Major

These configuration POSTs still lack request-forgery protection.

Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.CallSettings(CallSettingsView, CancellationToken) and Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.SubmitSetupWizard(SetupWizardFormPayload, CancellationToken) both mutate department configuration, but neither validates an antiforgery token. The new authorization check does not protect against a forged browser request.

🛡️ Suggested fix
 [HttpPost]
+[ValidateAntiForgeryToken]
 [Authorize(Policy = ResgridResources.Department_Update)]
 public async Task<IActionResult> CallSettings(CallSettingsView model, CancellationToken cancellationToken)

 [HttpPost]
+[ValidateAntiForgeryToken]
 [Authorize(Policy = ResgridResources.Department_Update)]
 public async Task<IActionResult> SubmitSetupWizard([FromBody] SetupWizardFormPayload payload, CancellationToken cancellationToken)

For the JSON setup-wizard POST, send the request token in a header from the client.

Also applies to: 1536-1541

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs` around lines
865 - 870, Both CallSettings(CallSettingsView, CancellationToken) and
SubmitSetupWizard(SetupWizardFormPayload, CancellationToken) mutate department
configuration but do not validate an antiforgery token; add antiforgery
validation to these POST endpoints by invoking the
IAntiforgery/ValidateRequestAsync flow (or decorating with
[ValidateAntiForgeryToken] where applicable) so requests are checked for a valid
token. For the JSON-based SubmitSetupWizard endpoint, accept the antiforgery
token from a request header (e.g., X-CSRF-TOKEN) on the server side and validate
it before performing any updates; ensure the client sends the token in that
header when posting JSON. Ensure the checks occur before calling
_authorizationService.CanUserModifyDepartmentAsync or performing any state
changes in these methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Web/Resgrid.Web/Areas/User/Views/Documents/Index.cshtml`:
- Around line 79-80: The inline onsubmit handler is vulnerable because it
injects doc.Name directly into JavaScript; update the Documents view to
JavaScript-encode the document name before embedding it (or remove the dynamic
name from the inline handler and show a static confirmation text or use a
separate data-attribute + event handler). Locate the form with the onsubmit
attribute that references `@doc.Name` in Index.cshtml and replace the direct
interpolation with a safely encoded value (e.g., use a server-side JavaScript
string encoder to produce a safe literal) or move the confirmation logic into a
script that reads a data-name attribute and encodes/escapes it before using
confirm().

---

Outside diff comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs`:
- Around line 664-669: The ProvisionApiKey, ProvisionApiKeyAsync and
ProvisionActiveCallRssKey controller actions currently rotate keys on an HTTP
GET which is unsafe; change each action's routing attribute from [HttpGet] to
[HttpPost] and add [ValidateAntiForgeryToken] (keeping existing [Authorize]
checks) and then update any client callers/views/forms to submit POST requests
(including antiforgery tokens) to these endpoints so key rotation occurs only
via explicit POSTs.
- Around line 1810-1815: The DeleteDepartment POST action uses
CanUserModifyDepartmentAsync but should call CanUserDeleteDepartmentAsync to
enforce deletion-only rights; update the authorization check in the
DeleteDepartment method to await
_authorizationService.CanUserDeleteDepartmentAsync(UserId, DepartmentId) instead
of CanUserModifyDepartmentAsync, and add antiforgery protection by annotating
the action with [ValidateAntiForgeryToken] (and ensure the confirmation form in
the DeleteDepartment view includes `@Html.AntiForgeryToken`() or equivalent) so
the POST requires a valid antiforgery token.
- Around line 865-870: Both CallSettings(CallSettingsView, CancellationToken)
and SubmitSetupWizard(SetupWizardFormPayload, CancellationToken) mutate
department configuration but do not validate an antiforgery token; add
antiforgery validation to these POST endpoints by invoking the
IAntiforgery/ValidateRequestAsync flow (or decorating with
[ValidateAntiForgeryToken] where applicable) so requests are checked for a valid
token. For the JSON-based SubmitSetupWizard endpoint, accept the antiforgery
token from a request header (e.g., X-CSRF-TOKEN) on the server side and validate
it before performing any updates; ensure the client sends the token in that
header when posting JSON. Ensure the checks occur before calling
_authorizationService.CanUserModifyDepartmentAsync or performing any state
changes in these methods.

In `@Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs`:
- Around line 718-743: The roles update block currently runs for any caller;
restrict it to department admins by moving the form.ContainsKey("roles")
handling inside the callerIsDepartmentAdmin branch (where group/admin flag
changes are applied). Ensure the code that calls
_personnelRolesService.SetRolesForUserAsync(DepartmentId, model.UserId, roles,
cancellationToken) only executes when callerIsDepartmentAdmin is true (preserve
parsing of form["roles"] to string[] and existing use of DepartmentId,
model.UserId and cancellationToken). This prevents non-admins from invoking
SetRolesForUserAsync and escalating privileges.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 51d4aa50-d3bf-441e-aa36-bae90814a565

📥 Commits

Reviewing files that changed from the base of the PR and between acaa56e and ae8f185.

📒 Files selected for processing (7)
  • Core/Resgrid.Model/Services/IAuthorizationService.cs
  • Core/Resgrid.Services/AuthorizationService.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
  • Web/Resgrid.Web/Areas/User/Views/Documents/Index.cshtml
  • Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs

Comment on lines +79 to +80
<form asp-controller="Documents" asp-action="DeleteDocument" asp-route-area="User" method="post"
onsubmit="return confirm('WARNING: This will permanently delete the document \'@doc.Name\'. Are you sure?');"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

JavaScript-encode doc.Name before putting it in onsubmit.

A document name with an apostrophe already breaks this handler, and a crafted name can turn this into stored script execution when an authorized user clicks Delete. Encode for JavaScript or remove the dynamic name from the inline handler.

🛡️ Suggested fix
-                                          onsubmit="return confirm('WARNING: This will permanently delete the document \'@doc.Name\'. Are you sure?');"
+                                          onsubmit="return confirm('WARNING: This will permanently delete the document \'@System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(doc.Name)\'. Are you sure?');"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form asp-controller="Documents" asp-action="DeleteDocument" asp-route-area="User" method="post"
onsubmit="return confirm('WARNING: This will permanently delete the document \'@doc.Name\'. Are you sure?');"
<form asp-controller="Documents" asp-action="DeleteDocument" asp-route-area="User" method="post"
onsubmit="return confirm('WARNING: This will permanently delete the document \'@System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(doc.Name)\'. Are you sure?');"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Documents/Index.cshtml` around lines 79 -
80, The inline onsubmit handler is vulnerable because it injects doc.Name
directly into JavaScript; update the Documents view to JavaScript-encode the
document name before embedding it (or remove the dynamic name from the inline
handler and show a static confirmation text or use a separate data-attribute +
event handler). Locate the form with the onsubmit attribute that references
`@doc.Name` in Index.cshtml and replace the direct interpolation with a safely
encoded value (e.g., use a server-side JavaScript string encoder to produce a
safe literal) or move the confirmation logic into a script that reads a
data-name attribute and encodes/escapes it before using confirm().

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Core/Resgrid.Services/WorkflowService.cs`:
- Around line 174-184: The code treats a missing existing step as if it were an
update by fabricating CreatedOn and continuing; instead, when existing (result
of _stepRepository.GetByIdAsync(step.WorkflowStepId)) is null you should
short-circuit and surface a clear not-found/stale-object error (e.g., throw
KeyNotFoundException or return a not-found result) rather than setting
step.CreatedOn or step.CreatedByUserId and proceeding; remove the fallback
assignment for step.CreatedOn in the update path and only apply default
timestamps when creating new records, ensuring the update method fails fast for
missing existing entries.

In `@Repositories/Resgrid.Repositories.DataRepository/WorkflowRepository.cs`:
- Around line 143-147: DeleteWorkflowWithAllDependenciesAsync currently creates
its own connection and transaction via _connectionProvider.Create() and
BeginTransaction, which bypasses any ambient _unitOfWork; change the method to
enlist in the existing unit of work by reusing the connection and transaction
provided by _unitOfWork (e.g., use _unitOfWork.Connection and
_unitOfWork.Transaction or call the unit-of-work's enlist/BeginIfNeeded API)
instead of calling _connectionProvider.Create() and BeginTransaction so the
deletes participate in the caller's transaction scope and do not commit
independently.
- Around line 147-173: Before deleting child rows in the transaction started in
the method that builds dp (DynamicParametersExtension) and runs
deleteRunLogsQuery, deleteRunsQuery, deleteStepsQuery and then executes
deleteWorkflowSql, first lock the parent workflow row by issuing a SELECT for
that workflowId inside the same transaction: for PostgreSQL use "SELECT ... FOR
UPDATE" and for SQL Server use "SELECT ... WITH (UPDLOCK, HOLDLOCK)" (use
DataConfig.DatabaseType and _sqlConfiguration.ParameterNotation to build the
appropriate select and pass the same dp); do this lock-select immediately after
creating dp and before executing the child delete queries so the parent row is
reserved and prevents concurrent inserts, then continue with the existing child
deletes and the final deleteWorkflowSql, and add integration tests that
concurrently attempt WorkflowRun inserts and workflow deletes for both
PostgreSQL and SQL Server to verify no FK violations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5266a8a5-9647-4f17-837f-703734dc6b50

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc8f3f and 84105a4.

📒 Files selected for processing (3)
  • Core/Resgrid.Model/Repositories/IWorkflowRepository.cs
  • Core/Resgrid.Services/WorkflowService.cs
  • Repositories/Resgrid.Repositories.DataRepository/WorkflowRepository.cs

@ucswift
Copy link
Member Author

ucswift commented Mar 11, 2026

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is approved.

@ucswift ucswift merged commit 115a62c into master Mar 11, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant