Skip to content

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

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

[PM-40336] Access Rules: domain, persistence & schema#7981
Hinton wants to merge 9 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 53.77358% with 98 lines in your changes missing coverage. Please review.
✅ Project coverage is 15.00%. Comparing base (04c886b) to head (0d9002b).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
...Framework/Pam/Repositories/AccessRuleRepository.cs 52.04% 45 Missing and 2 partials ⚠️
...re.Dapper/Pam/Repositories/AccessRuleRepository.cs 45.76% 31 Missing and 1 partial ⚠️
src/Pam.Domain/Models/AccessRuleDetails.cs 5.26% 18 Missing ⚠️
...structure.EntityFramework/Pam/Models/AccessRule.cs 83.33% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (04c886b) and HEAD (0d9002b). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (04c886b) HEAD (0d9002b)
2 1
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #7981       +/-   ##
===========================================
- Coverage   66.58%   15.00%   -51.58%     
===========================================
  Files        2277     1391      -886     
  Lines       99452    60449    -39003     
  Branches     8983     4806     -4177     
===========================================
- Hits        66220     9071    -57149     
- Misses      30963    51224    +20261     
+ Partials     2269      154     -2115     

☔ 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 added 3 commits July 16, 2026 11:16
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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@eliykat Do you think this sproc belongs to AC or PAM?

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.

I could go either way. PAM will be the ones most likely to update it in the future. But it's probably best that all Collection sprocs stay together and that the owner of the table (AC) have some oversight for the sprocs that change it. Overall I would say AC should own it, but let me know if you disagree.

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/**.
@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
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