Skip to content

SCRUM-386 fix(sites): restrict site shift templates to assigned emplo… - #362

Merged
Moha-sami merged 1 commit into
mainfrom
fix/idor-site-shift-templates-access-control
Sep 15, 2026
Merged

Moha-sami merged 1 commit into
mainfrom
fix/idor-site-shift-templates-access-control

Conversation

@Mahmoud-Alim

Copy link
Copy Markdown
Collaborator

Summary

Closes the IDOR on GET /api/v1/sites/{siteId}/shift-templates where any role-authorized user could read any site's templates. Manager/HR are now scoped to their EmployeeSite assignments, while Admin/SuperAdmin/HRAdmin bypass the site check. Unassigned or claim-less callers fail closed with 403.

What Changed

Application Layer

  • GetSiteShiftTemplatesQuery now carries ActorEmployeeId and BypassSiteAccess (both optional with safe defaults).
  • GetSiteShiftTemplatesQueryHandler checks site existence first (404), then EmployeeSite membership for non-bypass callers (403 "You do not have access to this site."), via a new HasSiteAccessAsync helper. Missing actor fails closed.
  • Result/Result<T> gain ResultErrorType.Forbidden + IsForbidden + Forbidden().

API / Controllers

  • GetSitesController.GetSiteShiftTemplates passes GetActorEmployeeId() + HasSiteBypass() (Admin/SuperAdmin/HRAdmin) into the query and maps IsForbidden to 403 { message }.

Authorization & Organization Isolation

  • Site-level isolation enforced through EmployeeSites (EmployeeId, SiteId composite key). No tenant concept exists in the codebase, so no tenant check was added.

Security

  • Fixes Broken Access Control on the site-scoped read path; template scoping Where(ShiftTemplateSites.Any(SiteId == ...)) is unchanged.
  • Fail-closed: null ActorEmployeeId with no bypass returns 403, not 200 or 400.
  • Known limitation: 404-before-403 ordering means 404 vs 403 still reveals whether a site id exists. Accepted to keep 404 semantics for genuinely missing sites.

API Behavior Changes

  • Endpoints affected: GET /api/v1/sites/{siteId}/shift-templates only.
  • 401 Unauthorized: unchanged (JWT bearer, role gate HRAdmin,Admin,Manager,HR,SuperAdmin).
  • 403 Forbidden: new — Manager/HR without an EmployeeSite link to the requested site, or with no resolvable actor claim.
  • 404 Not Found: unchanged — unknown siteId.
  • Breaking changes: intentional — unassigned Manager/HR calls that previously returned 200 now return 403. Bypass roles and assigned users are unaffected.

Testing

  • Unit tests: existing GetSiteShiftTemplatesTests updated for the new handler constructor (added EmployeeSite repository) and privileged bypass on the pre-existing filtering/search cases.
  • Authorization tests: 4 new cases — assigned user succeeds, cross-site user gets IsForbidden, null actor gets IsForbidden, bypass without assignment succeeds.
  • Executed: dotnet build HR_system.slnx — 0 warnings, 0 errors; filtered GetSiteShiftTemplatesTests — 17/17 passed; full Buy2.Domain.Tests — 714/714 passed.

Backward Compatibility

  • Query constructor is backward compatible for 2-arg callers via optional parameters. Direct GetSiteShiftTemplatesQueryHandler constructions need the third EmployeeSite repository (DI resolves it automatically in production).
  • Response DTO shape is unchanged.

Important Implementation Details

  • HasSiteAccessAsync is a single AnyAsync on EmployeeSites with AsNoTracking; handler complexity stays within limits.
  • No pagination or filtering semantics were changed on this endpoint.

Checklist

  • Build passes
  • Tests pass
  • Relevant authorization scenarios are covered
  • Organization / tenant isolation is preserved
  • API behavior is documented where changed
  • No unintended breaking changes
  • Security implications have been reviewed

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hr-system Ready Ready Preview Sep 15, 2026 3:54am UTC

@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

All Checks Passed!

  • Jira key / Task reference detected.
  • Code formatting passed (4 changed C# file(s)).
  • .NET 10 solution compiled successfully.
  • .NET analyzers passed.
  • Security scan skipped (no dependency changes in this PR).

Ready for maintainer review!

…yees

## Summary
Closes the IDOR on `GET /api/v1/sites/{siteId}/shift-templates` where any role-authorized user could read any site's templates. `Manager`/`HR` are now scoped to their `EmployeeSite` assignments, while `Admin`/`SuperAdmin`/`HRAdmin` bypass the site check. Unassigned or claim-less callers fail closed with `403`.

## What Changed
### Application Layer
- `GetSiteShiftTemplatesQuery` now carries `ActorEmployeeId` and `BypassSiteAccess` (both optional with safe defaults).
- `GetSiteShiftTemplatesQueryHandler` checks site existence first (`404`), then `EmployeeSite` membership for non-bypass callers (`403 "You do not have access to this site."`), via a new `HasSiteAccessAsync` helper. Missing actor fails closed.
- `Result`/`Result<T>` gain `ResultErrorType.Forbidden` + `IsForbidden` + `Forbidden()`.

### API / Controllers
- `GetSitesController.GetSiteShiftTemplates` passes `GetActorEmployeeId()` + `HasSiteBypass()` (`Admin`/`SuperAdmin`/`HRAdmin`) into the query and maps `IsForbidden` to `403 { message }`.

### Authorization & Organization Isolation
- Site-level isolation enforced through `EmployeeSites` (`EmployeeId`, `SiteId` composite key). No tenant concept exists in the codebase, so no tenant check was added.

## Security
- Fixes Broken Access Control on the site-scoped read path; template scoping `Where(ShiftTemplateSites.Any(SiteId == ...))` is unchanged.
- Fail-closed: null `ActorEmployeeId` with no bypass returns `403`, not `200` or `400`.
- Known limitation: `404`-before-`403` ordering means `404` vs `403` still reveals whether a site id exists. Accepted to keep `404` semantics for genuinely missing sites.

## API Behavior Changes
- Endpoints affected: `GET /api/v1/sites/{siteId}/shift-templates` only.
- 401 Unauthorized: unchanged (JWT bearer, role gate `HRAdmin,Admin,Manager,HR,SuperAdmin`).
- 403 Forbidden: new — `Manager`/`HR` without an `EmployeeSite` link to the requested site, or with no resolvable actor claim.
- 404 Not Found: unchanged — unknown `siteId`.
- Breaking changes: intentional — unassigned `Manager`/`HR` calls that previously returned `200` now return `403`. Bypass roles and assigned users are unaffected.

## Testing
- Unit tests: existing `GetSiteShiftTemplatesTests` updated for the new handler constructor (added `EmployeeSite` repository) and privileged bypass on the pre-existing filtering/search cases.
- Authorization tests: 4 new cases — assigned user succeeds, cross-site user gets `IsForbidden`, null actor gets `IsForbidden`, bypass without assignment succeeds.
- Executed: `dotnet build HR_system.slnx` — 0 warnings, 0 errors; filtered `GetSiteShiftTemplatesTests` — 17/17 passed; full `Buy2.Domain.Tests` — 714/714 passed.

## Backward Compatibility
- Query constructor is backward compatible for 2-arg callers via optional parameters. Direct `GetSiteShiftTemplatesQueryHandler` constructions need the third `EmployeeSite` repository (DI resolves it automatically in production).
- Response DTO shape is unchanged.

## Important Implementation Details
- `HasSiteAccessAsync` is a single `AnyAsync` on `EmployeeSites` with `AsNoTracking`; handler complexity stays within limits.
- No pagination or filtering semantics were changed on this endpoint.

## Checklist
- [x] Build passes
- [x] Tests pass
- [x] Relevant authorization scenarios are covered
- [x] Organization / tenant isolation is preserved
- [x] API behavior is documented where changed
- [x] No unintended breaking changes
- [x] Security implications have been reviewed
@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

All Checks Passed!

  • Jira key / Task reference detected.
  • Code formatting passed (4 changed C# file(s)).
  • .NET 10 solution compiled successfully.
  • .NET analyzers passed.
  • Security scan skipped (no dependency changes in this PR).

Ready for maintainer review!

Repository owner deleted a comment from github-actions Bot Sep 15, 2026

@Moha-sami Moha-sami left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Approved. Site shift template IDOR fix and authorization tests look great.

@Moha-sami
Moha-sami merged commit 86036b0 into main Sep 15, 2026
6 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.

2 participants