Skip to content

[PM-40336] Access Rules: domain, persistence & schema - #7981

Open
Hinton wants to merge 14 commits into
mainfrom
pam/access-rule
Open

[PM-40336] Access Rules: domain, persistence & schema#7981
Hinton wants to merge 14 commits into
mainfrom
pam/access-rule

Conversation

@Hinton

@Hinton Hinton commented Jul 14, 2026

Copy link
Copy Markdown
Member

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-40336

📔 Objective

Add the domain and persistence layer for org-scoped PAM Access Rules — the
reusable policy objects that gate PAM credential leasing. This is the first of two
stacked PRs; the commercial CRUD API that consumes this layer follows in #7983
(stacked on this branch).

Access-rule data (name, description, conditions) is organization configuration
metadata, not Vault Data — no zero-knowledge surface is touched. Nothing here is
reachable at runtime until the API lands and the pm-37044-pam-v-0
(FeatureFlagKeys.Pam) flag is enabled.

Builds on the Data base library extracted in #7976 (already merged to main);
Pam.Domain references it for ITableObject/IRepository/CombGuid. The
dependency arrow points inward — Core does not reference Pam.Domain;
infrastructure and the Pam service depend on it directly.

What's included

Domain — new Pam.Domain library (src/Pam.Domain)

  • AccessRule entity: org-scoped rule with lease semantics — SingleActiveLease,
    DefaultLeaseDurationSeconds, MaxLeaseDurationSeconds, Enabled,
    AllowsExtensions / MaxExtensionDurationSeconds, and LastEditedBy.
  • Conditions stored as a JSON AccessCondition tree (validated in [PM-40336] Access Rules: API #7983).
  • AccessRuleDetails (rule + governed collection IDs) and IAccessRuleRepository.

Audit-event recording is not part of this PR. The earlier audit contract and
write seam were incomplete and have been removed; PAM audit will be reintroduced
as its own piece of work once properly designed.

Persistence

  • Dapper (Infrastructure.Dapper) and EF Core (Infrastructure.EntityFramework)
    AccessRuleRepository implementations, registered in DI.
  • SSDT: AccessRule table + stored procedures (_Create, _Update, _DeleteById,
    _ReadById, _ReadByOrganizationId, _ReadDetailsById,
    _ReadDetailsByOrganizationId, Collection_SetAccessRuleAssociations).
  • MSSQL migration (2026-07-14_01_AddAccessRule.sql) and EF migrations +
    snapshots for Postgres/MySQL/SQLite.
  • Repository integration tests.

Collection association

  • Collection.AccessRuleId FK (null = leasing disabled for that collection);
    updated Collection_* stored procedures and the Collection table.
    (Touches AdminConsole-owned files — flagging for that team's review.)

Comment thread bitwarden_license/src/Services/Pam/Services/AccessRuleValidator.cs Fixed
Comment thread src/Infrastructure.Dapper/Pam/Repositories/AccessRuleRepository.cs Dismissed
Comment thread bitwarden_license/src/Services/Pam/Services/AccessRuleValidator.cs Fixed
Comment thread src/Infrastructure.EntityFramework/Pam/Models/AccessRule.cs Dismissed
@Hinton
Hinton force-pushed the pam/access-rule branch from 93100c7 to 79f7f4f Compare July 14, 2026 17:33
@Hinton Hinton changed the title [PM-40336] Access Rules [PM-40336] Access Rules: domain, persistence & schema Jul 14, 2026
@Hinton
Hinton force-pushed the pam/access-rule branch from 79f7f4f to 51d6075 Compare July 14, 2026 17:57
@Hinton Hinton added the t:feature Change Type - Feature Development label Jul 14, 2026
@Hinton

Hinton commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Reviewed the automated code-quality suggestions. Decisions below:

1. AccessRuleValidator.ValidateIpAllowlist — "use .Where" — declining.
The predicate treats null/whitespace CIDRs as invalid. A .Where(...).FirstOrDefault() rewrite returns null both for "no invalid entry found" and when the offending entry is itself null, so a null CIDR would be silently accepted. Keeping the explicit foreach to preserve correctness.

2. Dapper AccessRuleRepository.GetManyDetailsByOrganizationIdAsync — "use .Where" — declining.
The loop performs a property mutation (rule.CollectionIds = ...) guarded by TryGetValue. Filtering with .Where(r => collectionIdsByRule.ContainsKey(r.Id)) adds a redundant second dictionary lookup, and using .Where to drive a side effect is an anti-pattern. The foreach + TryGetValue is the intended form.

3. AccessRuleValidator.Validate — "use .Select" — optional/acceptable.
conditions.Select(ValidateCondition).FirstOrDefault(r => !r.IsValid) is behavior-preserving (deferred + short-circuits). Purely cosmetic vs. the current early-return foreach; will fold in if this file is retouched.

4. EF AccessRule : Bit.Pam.Entities.AccessRule — "rename to AccessRuleEntity" — declining.
This matches the established EF-model convention across the repo: every EF model shares its domain entity's name and inherits from it (Collection : Core.Entities.Collection, Organization : Core.AdminConsole.Entities.Organization, Event : Core.Entities.Event, and ~30 others). Renaming would make PAM the sole exception.

Net: keeping (1), (2), and (4) as-is by design; (3) is an optional cosmetic tweak.

@Hinton
Hinton force-pushed the pam/access-rule branch 2 times, most recently from f5a81b6 to 74777a5 Compare July 14, 2026 18:16
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 54.20561% with 98 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.29%. Comparing base (b4d4c55) to head (2c74d20).

Files with missing lines Patch % Lines
...Framework/Pam/Repositories/AccessRuleRepository.cs 46.37% 36 Missing and 1 partial ⚠️
...re.Dapper/Pam/Repositories/AccessRuleRepository.cs 30.43% 31 Missing and 1 partial ⚠️
src/Pam.Domain/Models/AccessRuleDetails.cs 5.26% 18 Missing ⚠️
.../AdminConsole/Repositories/CollectionRepository.cs 65.51% 9 Missing and 1 partial ⚠️
...structure.EntityFramework/Pam/Models/AccessRule.cs 83.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #7981       +/-   ##
===========================================
+ Coverage   14.99%   67.29%   +52.30%     
===========================================
  Files        1409     2306      +897     
  Lines       61097   100426    +39329     
  Branches     4864     9028     +4164     
===========================================
+ Hits         9161    67582    +58421     
+ Misses      51780    30562    -21218     
- Partials      156     2282     +2126     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Hinton
Hinton force-pushed the pam/access-rule branch from 74777a5 to 268b892 Compare July 14, 2026 19:05
@Hinton
Hinton marked this pull request as ready for review July 16, 2026 09:47
@Hinton
Hinton requested review from a team as code owners July 16, 2026 09:47
@Hinton
Hinton requested a review from BTreston July 16, 2026 09:47
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the domain and persistence layer for org-scoped PAM Access Rules: the new Pam.Domain library, the Dapper and EF Core AccessRuleRepository implementations, SSDT schema plus the consolidated MSSQL migration, the EF migrations/snapshots for Postgres/MySQL/SQLite, the Collection.AccessRuleId association, and repository integration tests. FK delete behavior is consistent across all four databases (NO ACTION / Restrict), the delete path correctly detaches governed collections before removing the rule, and the unique (OrganizationId, Name) index is applied in both ORMs. Dual-ORM parity, migration idempotency guards, and DI registration all follow established repository conventions.

Code Review Details
  • ♻️ : Comment references a removed "RuleDeleted audit event"; misleads on delete semantics (also present in the dated migration).
    • src/Sql/dbo/Pam/Stored Procedures/AccessRule_DeleteById.sql:21

Comment thread src/Sql/dbo/Pam/Stored Procedures/AccessRule_DeleteById.sql Outdated
@Hinton
Hinton force-pushed the pam/access-rule branch from dee1129 to b250faa Compare July 17, 2026 12:28
@Hinton
Hinton requested a review from a team as a code owner July 17, 2026 13:45
@Hinton
Hinton force-pushed the pam/access-rule branch 3 times, most recently from 3c05d0f to 95d575b Compare July 27, 2026 15:53
@Hinton
Hinton requested a review from eliykat July 27, 2026 16:02
@Hinton
Hinton force-pushed the pam/access-rule branch 2 times, most recently from d680dcf to 5b920b2 Compare July 30, 2026 12:03

@eliykat eliykat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Generally LGTM aside from the 1 question below.

Comment thread src/Infrastructure.Dapper/Pam/Repositories/AccessRuleRepository.cs Outdated
Hinton added 12 commits July 31, 2026 16:31
The audit-event kinds, payload fields, and emitter doc referenced request, lease, credential, and system-control events that no action emits yet. Reduce AccessAuditEventKind to the rule group (RuleCreated/Updated/Deleted, 0-9) and AccessAuditEventData to the fields the rule commands set, keeping the emitter seam for the read side that lands later.
Wrap the EF delete (clear collection links, then remove the rule) in an explicit transaction so a partial delete cannot leave ungoverned collections. Read rule details via ReadFirstOrDefaultAsync and make the Dapper collection-mapping properties settable.
The audit-event kinds, phases, payload, and emitter seam were incomplete and need proper design before they can be relied on (notably a read-side pairing key). Drop AccessAuditEventKind, AccessAuditEventPhase, AccessAuditEventData, IAccessAuditEventEmitter, and NoopAccessAuditEventEmitter, and remove the comments that referenced those events. Audit will be reintroduced as its own piece of work.
Replace the per-directory PAM entries with one **/Pam* glob covering the Pam service, Pam.Domain, the Infrastructure Pam repos, and the PAM integration tests. Re-assert dept-dbops on src/Sql/dbo/Pam so PAM DB schema keeps its DBOps review, consistent with src/Sql/**.
…lder

Collection sprocs stay together under the table's owning team (AC), per PR review.
The AccessRule migrations were generated on 2026-07-14, before the
migrations that have since landed on main, so the chronological-order
check fails. Re-date them to the end of the chain.

The MSSQL script is a pure rename; its contents are unchanged and every
statement is already guarded, so re-running it on a database that applied
it under the old name is a no-op.

The EF migrations are regenerated rather than renamed, so each provider's
Designer snapshot targets the model as of its new position in the chain.
The Up/Down bodies are byte-identical to the originals and
DatabaseContextModelSnapshot is unchanged.
Infrastructure.Dapper and Infrastructure.EntityFramework now reference
Pam.Domain directly, so every project that transitively depends on them
(Api, Admin, Identity, the Pam service, tests, etc.) needs the reference
recorded in its own lock file. Force-evaluated a full-solution restore
to bring every lock file back in sync with the actual dependency graph.
Collection_SetAccessRuleAssociations is written as a collections sproc and its
SSDT definition is already AdminConsole-owned, so keeping the method on
IAccessRuleRepository split ownership: AC owned the MSSQL sproc while PAM owned
the EF implementation. Both implementations now live on ICollectionRepository
alongside the sproc they wrap.

Addresses review feedback on #7981.
@Hinton
Hinton force-pushed the pam/access-rule branch from 5b920b2 to 2c74d20 Compare July 31, 2026 14:57
Hinton added a commit that referenced this pull request Jul 31, 2026
The access-rule commands now take ICollectionRepository for the collection
association write, matching the method's new home after review feedback on
#7981.
@Hinton
Hinton requested a review from eliykat July 31, 2026 15:04

@eliykat eliykat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add database integration tests for the new repository method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants