-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[PM-40526] Access Leasing: domain #8001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
+589
−0
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| using Bit.Core.Entities; | ||
| using Bit.Core.Utilities; | ||
| using Bit.Pam.Enums; | ||
|
|
||
| namespace Bit.Pam.Entities; | ||
|
|
||
| /// <summary> | ||
| /// A single decision on a <see cref="AccessRequest"/>. In v0 there is exactly one decision per request: an automated | ||
| /// <see cref="AccessDeciderKind.Automatic"/> verdict for auto-approval, or a <see cref="AccessDeciderKind.Human"/> | ||
| /// verdict once approver endpoints land. | ||
| /// </summary> | ||
| public class AccessDecision : ITableObject<Guid> | ||
| { | ||
| public Guid Id { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The request this decision was made on. | ||
| /// </summary> | ||
| public Guid AccessRequestId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Discriminates the decision: determines whether <see cref="ApproverId"/> or <see cref="ConditionKind"/> is populated. | ||
| /// </summary> | ||
| public AccessDeciderKind DeciderKind { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The human approver. NULL when <see cref="DeciderKind"/> is <see cref="AccessDeciderKind.Automatic"/>. | ||
| /// </summary> | ||
| public Guid? ApproverId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The condition kind that decided (e.g. <see cref="AccessConditionKind.IpAllowlist"/>). NULL when | ||
| /// <see cref="DeciderKind"/> is <see cref="AccessDeciderKind.Human"/>. | ||
| /// </summary> | ||
| public AccessConditionKind? ConditionKind { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The approve-or-deny outcome recorded by this decision. | ||
| /// </summary> | ||
| public AccessDecisionVerdict Verdict { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Human comment, or a future automatic-evaluation reason string. | ||
| /// </summary> | ||
| public string? Comment { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Forward-compatible snapshot of the inputs the evaluation saw. Null in this slice (no signals are evaluated). | ||
| /// </summary> | ||
| public string? EvaluationContext { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// When the decision was recorded, stamped in UTC at construction. | ||
| /// </summary> | ||
| public DateTime CreationDate { get; set; } = DateTime.UtcNow; | ||
|
|
||
| public void SetNewId() | ||
| { | ||
| Id = CombGuid.Generate(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| using Bit.Core.Entities; | ||
| using Bit.Core.Utilities; | ||
| using Bit.Pam.Enums; | ||
|
|
||
| namespace Bit.Pam.Entities; | ||
|
|
||
| /// <summary> | ||
| /// An active grant of access to a cipher, born from an approved <see cref="AccessRequest"/>. Only | ||
| /// <see cref="AccessLeaseStatus.Active"/> leases inside their <see cref="NotBefore"/>/<see cref="NotAfter"/> window | ||
| /// authorize access. | ||
| /// </summary> | ||
| public class AccessLease : ITableObject<Guid> | ||
| { | ||
| public Guid Id { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The request that birthed this lease. | ||
| /// </summary> | ||
| public Guid AccessRequestId { get; set; } | ||
|
|
||
| public Guid OrganizationId { get; set; } | ||
| public Guid CollectionId { get; set; } | ||
| public Guid CipherId { get; set; } | ||
| public Guid RequesterId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The lease's position in its lifecycle. Only an <see cref="AccessLeaseStatus.Active"/> lease within its window | ||
| /// authorizes access. | ||
| /// </summary> | ||
| public AccessLeaseStatus Status { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The start of the granted access window, carried over from the approved <see cref="AccessRequest"/>. | ||
| /// </summary> | ||
| public DateTime NotBefore { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The end of the granted access window. | ||
| /// </summary> | ||
| public DateTime NotAfter { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Set when an operator revokes the lease (<see cref="AccessLeaseStatus.Revoked"/>). NULL otherwise. | ||
| /// </summary> | ||
| public DateTime? RevokedDate { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The operator who revoked the lease. NULL unless <see cref="Status"/> is <see cref="AccessLeaseStatus.Revoked"/>. | ||
| /// </summary> | ||
| public Guid? RevokedBy { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// When the lease was minted, stamped in UTC at construction. | ||
| /// </summary> | ||
| public DateTime CreationDate { get; set; } = DateTime.UtcNow; | ||
|
|
||
| public void SetNewId() | ||
| { | ||
| Id = CombGuid.Generate(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| using Bit.Core.Entities; | ||
| using Bit.Core.Utilities; | ||
| using Bit.Pam.Enums; | ||
|
|
||
| namespace Bit.Pam.Entities; | ||
|
|
||
| /// <summary> | ||
| /// A request to lease access to a cipher in a leasing-governed collection. Auto-approved requests are created | ||
| /// already <see cref="AccessRequestStatus.Approved"/>; requests that require human approval are created | ||
| /// <see cref="AccessRequestStatus.Pending"/> and resolved later by an approver. Neither approval mints the lease — | ||
| /// the requester activates the approved request within its window, and that activation produces the | ||
| /// <see cref="AccessLease"/>. | ||
| /// </summary> | ||
| public class AccessRequest : ITableObject<Guid> | ||
| { | ||
| public Guid Id { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// NULL for original requests. Set only for extension requests, which point at the lease being extended. | ||
| /// </summary> | ||
| public Guid? ExtensionOfLeaseId { get; set; } | ||
|
|
||
| public Guid OrganizationId { get; set; } | ||
| public Guid CollectionId { get; set; } | ||
| public Guid CipherId { get; set; } | ||
| public Guid RequesterId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The access rule that governed this request, resolved once at submit (oldest wins) and pinned here so every | ||
| /// downstream operation reads the same rule rather than re-resolving. Null for requests created before pinning | ||
| /// existed, or when the cipher was not leasing-gated through a stored rule. | ||
| /// </summary> | ||
| public Guid? RuleId { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The requested access window. For automatic approval this is <c>now</c>; for human approval it is the | ||
| /// requester-supplied start. | ||
| /// </summary> | ||
| public DateTime NotBefore { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The end of the requested access window. For automatic approval this is <c>now + duration</c>; for human | ||
| /// approval it is the requester-supplied end. | ||
| /// </summary> | ||
| public DateTime NotAfter { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Optional for automatic approval, required for human approval (enforced in the command). | ||
| /// </summary> | ||
| public string? Reason { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The request's position in its lifecycle. Created <see cref="AccessRequestStatus.Pending"/> for human approval or | ||
| /// already <see cref="AccessRequestStatus.Approved"/> for automatic approval, then settling in one terminal state. | ||
| /// </summary> | ||
| public AccessRequestStatus Status { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// When the request was submitted, stamped in UTC at construction. | ||
| /// </summary> | ||
| public DateTime CreationDate { get; set; } = DateTime.UtcNow; | ||
|
|
||
| /// <summary> | ||
| /// Set when the request leaves <see cref="AccessRequestStatus.Pending"/>. | ||
| /// </summary> | ||
| public DateTime? ResolvedDate { get; set; } | ||
|
|
||
| public void SetNewId() | ||
| { | ||
| Id = CombGuid.Generate(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// The kind of access condition that produced an automatic <see cref="Entities.AccessDecision"/>. Mirrors the | ||
| /// <c>kind</c> discriminator on <see cref="Models.Conditions.AccessCondition"/>, which remains the JSON wire format | ||
| /// for a rule's conditions; this enum is the persisted, type-safe form recorded against the decision. | ||
| /// </summary> | ||
| public enum AccessConditionKind : byte | ||
| { | ||
| /// <summary>Requires a human approver; matching requests are routed for manual approval rather than auto-decided.</summary> | ||
| HumanApproval = 0, | ||
|
|
||
| /// <summary>Matches when the requester's IP is on a configured allowlist.</summary> | ||
| IpAllowlist = 1, | ||
|
|
||
| /// <summary>Matches when the request falls within a configured time-of-day window.</summary> | ||
| TimeOfDay = 2, | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// Who made a <see cref="Entities.AccessDecision"/>: an automatic condition evaluation or a human approver. | ||
| /// </summary> | ||
| public enum AccessDeciderKind : byte | ||
| { | ||
| /// <summary>A condition on the governing access rule decided, with no human involved.</summary> | ||
| Automatic = 0, | ||
|
|
||
| /// <summary>A human approver decided.</summary> | ||
| Human = 1, | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The verdict recorded on a <see cref="Entities.AccessDecision"/>. | ||
| /// </summary> | ||
| public enum AccessDecisionVerdict : byte | ||
| { | ||
| /// <summary>Access was refused.</summary> | ||
| Deny = 0, | ||
|
|
||
| /// <summary>Access was granted.</summary> | ||
| Approve = 1, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// The result of a race-safe lease extension. The extension stored procedure returns a distinct integer code so the | ||
| /// caller can tell a lease that is no longer extendable apart from one that has already been extended. | ||
| /// </summary> | ||
| public enum AccessLeaseExtendOutcome | ||
| { | ||
| /// <summary>The lease's window was extended (stored proc returned 1).</summary> | ||
| Extended = 1, | ||
|
|
||
| /// <summary> | ||
| /// The lease was no longer active, or its window had already ended, when the guarded update ran (stored proc | ||
| /// returned 0). A concurrent revoke or expiry likely won. | ||
| /// </summary> | ||
| LeaseNotActive = 0, | ||
|
|
||
| /// <summary> | ||
| /// The lease has already been extended (a lease may be extended once; stored proc returned -1). Nothing was | ||
| /// persisted. | ||
| /// </summary> | ||
| AlreadyExtended = -1, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// The result of a race-safe lease mint. The mint stored procedures return a distinct integer code so the caller can | ||
| /// tell a lost-the-race precondition failure apart from a per-cipher single-active-lease conflict. | ||
| /// </summary> | ||
| public enum AccessLeaseMintOutcome | ||
| { | ||
| /// <summary>The active lease was minted (stored proc returned 1).</summary> | ||
| Minted = 1, | ||
|
|
||
| /// <summary> | ||
| /// A precondition no longer held when the guarded insert ran (stored proc returned 0, or the unique-index | ||
| /// backstop fired). A concurrent activation likely won; the caller re-reads the winner. | ||
| /// </summary> | ||
| PreconditionFailed = 0, | ||
|
|
||
| /// <summary> | ||
| /// Another active in-window lease already exists for this cipher and the governing rule enforces a per-cipher | ||
| /// singleton (stored proc returned -1). Nothing was persisted. | ||
| /// </summary> | ||
| SingleActiveLeaseConflict = -1, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// Lifecycle of a <see cref="Entities.AccessLease"/>. Only <see cref="Active"/> leases authorize access. | ||
| /// </summary> | ||
| public enum AccessLeaseStatus : byte | ||
| { | ||
| /// <summary>Live; within its window it authorizes access.</summary> | ||
| Active = 0, | ||
|
|
||
| /// <summary>The lease's window ended on its own.</summary> | ||
| Expired = 1, | ||
|
|
||
| /// <summary>An operator ended the lease early.</summary> | ||
| Revoked = 2, | ||
|
|
||
| /// <summary>The holder ended their own lease early, as opposed to <see cref="Revoked"/> (an operator ended it).</summary> | ||
| Cancelled = 3, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace Bit.Pam.Enums; | ||
|
|
||
| /// <summary> | ||
| /// Lifecycle of a <see cref="Entities.AccessRequest"/>. A request starts <see cref="Pending"/> and moves to exactly | ||
| /// one terminal state. Auto-approved requests are created already <see cref="Approved"/>. | ||
| /// </summary> | ||
| public enum AccessRequestStatus : byte | ||
| { | ||
| /// <summary>Awaiting a human approver's decision.</summary> | ||
| Pending = 0, | ||
|
|
||
| /// <summary>Approved automatically or by an approver; the requester can activate it into a lease within its window.</summary> | ||
| Approved = 1, | ||
|
|
||
| /// <summary>An approver refused the request.</summary> | ||
| Denied = 2, | ||
|
|
||
| /// <summary>Withdrawn by the requester before it was decided.</summary> | ||
| Cancelled = 3, | ||
|
|
||
| /// <summary>The approval window lapsed with no decision recorded.</summary> | ||
| ExpiredUnanswered = 4, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using Bit.Pam.Enums; | ||
|
|
||
| namespace Bit.Pam.Models; | ||
|
|
||
| /// <summary> | ||
| /// One decision on an <see cref="Entities.AccessRequest"/>, projected from an <see cref="Entities.AccessDecision"/> | ||
| /// row. The element of <see cref="AccessRequestDetails.Decisions"/> — there is one per recorded decision, human or | ||
| /// automatic. A human decision carries the approver's identity (<see cref="Id"/> plus the denormalized name/email); an | ||
| /// automatic decision has none (<see cref="Id"/> null — it was decided by an access-rule condition). | ||
| /// </summary> | ||
| public class AccessRequestDecision | ||
| { | ||
| /// <summary>Who decided: a human approver or an automatic condition evaluation.</summary> | ||
| public AccessDeciderKind DeciderKind { get; set; } | ||
|
|
||
| /// <summary>The human approver, or null for an automatic decision.</summary> | ||
| public Guid? Id { get; set; } | ||
|
|
||
| /// <summary>The human approver's display name, or null (automatic, or the server could not resolve the user).</summary> | ||
| public string? Name { get; set; } | ||
|
|
||
| /// <summary>The human approver's email, the fallback display when <see cref="Name"/> is unset.</summary> | ||
| public string? Email { get; set; } | ||
|
|
||
| /// <summary>The decision's comment (a human approver's note, or a future automatic-evaluation reason), if any.</summary> | ||
| public string? Comment { get; set; } | ||
|
|
||
| /// <summary>The verdict reached.</summary> | ||
| public AccessDecisionVerdict Verdict { get; set; } | ||
|
|
||
| /// <summary>When the decision was made (the decision's CreationDate).</summary> | ||
| public DateTime DecidedAt { get; set; } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎨 SUGGESTED:
Models.Conditions.AccessConditiondoes not resolveDetails and fix
No
AccessConditiontype exists underBit.Pam.Models.Conditions(or anywhere in the codebase), so this<see cref>will not link. The siblingAccessRule.csdocuments the same concept with plain code markup precisely because the type isn't referenceable from this project:/// <c>kind</c> discriminator on <c>AccessCondition</c>, which remains the JSON wire formatSwitching to
<c>AccessCondition</c>keeps the two files consistent and avoids a dangling reference. (Doc-only; no build impact sinceGenerateDocumentationFileis not set.)