feat: select Windows execution contexts
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
// Package windows contains Windows-supervisor policy that is safe to unit test
|
||||
// without loading Win32. Narrow build-tagged adapters obtain and verify the
|
||||
// real token/session facts before they reach this selector.
|
||||
package windows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rvbox/rvbox/internal/domain"
|
||||
)
|
||||
|
||||
// ExecutionContext is the only context vocabulary exposed by the Windows v1
|
||||
// policy. The caller expresses merely Elevated; all fallback ordering remains
|
||||
// local to the client daemon.
|
||||
type ExecutionContext string
|
||||
|
||||
const (
|
||||
ContextActiveUser ExecutionContext = "ACTIVE_USER"
|
||||
ContextActiveUserElevated ExecutionContext = "ACTIVE_USER_ELEVATED"
|
||||
ContextActiveSystem ExecutionContext = "ACTIVE_SYSTEM"
|
||||
ContextLocalService ExecutionContext = "LOCAL_SERVICE"
|
||||
ContextLocalSystem ExecutionContext = "LOCAL_SYSTEM"
|
||||
)
|
||||
|
||||
type AttemptReason string
|
||||
|
||||
const (
|
||||
ReasonSelected AttemptReason = "SELECTED"
|
||||
ReasonNoUsableActiveSession AttemptReason = "NO_USABLE_ACTIVE_SESSION"
|
||||
ReasonAmbiguousActiveSessions AttemptReason = "AMBIGUOUS_ACTIVE_SESSIONS"
|
||||
ReasonStandardToken AttemptReason = "STANDARD_OR_FILTERED_TOKEN"
|
||||
ReasonRestrictedToken AttemptReason = "RESTRICTED_MEDIUM_TOKEN"
|
||||
ReasonRestrictedTokenUnavailable AttemptReason = "RESTRICTED_TOKEN_UNAVAILABLE"
|
||||
ReasonElevationUnavailable AttemptReason = "ELEVATION_UNAVAILABLE"
|
||||
ReasonApprovalPolicy AttemptReason = "APPROVAL_POLICY"
|
||||
ReasonActiveSystemUnavailable AttemptReason = "ACTIVE_SYSTEM_UNAVAILABLE"
|
||||
ReasonLocalServiceUnavailable AttemptReason = "LOCAL_SERVICE_UNAVAILABLE"
|
||||
ReasonLocalSystemUnavailable AttemptReason = "LOCAL_SYSTEM_UNAVAILABLE"
|
||||
)
|
||||
|
||||
// TokenFacts are verified observations, not token handles. The native adapter
|
||||
// must set these only after it has checked SID, session, type, elevation, and
|
||||
// integrity properties.
|
||||
type TokenFacts struct {
|
||||
Usable bool
|
||||
StandardOrFiltered bool
|
||||
FullAdministrator bool
|
||||
LinkedFullAvailable bool
|
||||
RestrictedMediumAllowed bool
|
||||
ApprovalPolicyRequired bool
|
||||
}
|
||||
|
||||
// SessionCandidate represents an active WTS session after native enumeration.
|
||||
// It deliberately contains no handles or credentials.
|
||||
type SessionCandidate struct {
|
||||
SessionID uint32
|
||||
Console bool
|
||||
UserSID string
|
||||
LogonSID string
|
||||
Token TokenFacts
|
||||
}
|
||||
|
||||
type SelectionInput struct {
|
||||
Elevated bool
|
||||
ActiveSessions []SessionCandidate
|
||||
ActiveSystemAvailable bool
|
||||
LocalServiceAvailable bool
|
||||
LocalSystemAvailable bool
|
||||
}
|
||||
|
||||
type Attempt struct {
|
||||
Context ExecutionContext
|
||||
Reason AttemptReason
|
||||
Success bool
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
Context ExecutionContext
|
||||
SessionID *uint32
|
||||
UserSID string
|
||||
LogonSID string
|
||||
}
|
||||
|
||||
type Selection struct {
|
||||
Elevated bool
|
||||
Attempts []Attempt
|
||||
Effective *Identity
|
||||
NoActiveReason AttemptReason
|
||||
Error *domain.Error
|
||||
}
|
||||
|
||||
// Select applies the v1 hierarchy. It never chooses an arbitrary active
|
||||
// session and never substitutes a service identity for a failed normal active
|
||||
// user selection.
|
||||
func Select(input SelectionInput) Selection {
|
||||
selected, absentReason := chooseActiveSession(input.ActiveSessions)
|
||||
result := Selection{Elevated: input.Elevated, NoActiveReason: absentReason}
|
||||
if selected != nil {
|
||||
if !input.Elevated {
|
||||
return selectActiveNormal(result, *selected)
|
||||
}
|
||||
return selectActiveElevated(result, *selected, input)
|
||||
}
|
||||
if input.Elevated {
|
||||
return selectNoUserElevated(result, input)
|
||||
}
|
||||
return selectNoUserNormal(result, input)
|
||||
}
|
||||
|
||||
func chooseActiveSession(candidates []SessionCandidate) (*SessionCandidate, AttemptReason) {
|
||||
var usable []SessionCandidate
|
||||
for _, candidate := range candidates {
|
||||
if candidate.Token.Usable && candidate.UserSID != "" && candidate.LogonSID != "" {
|
||||
if candidate.Console {
|
||||
selected := candidate
|
||||
return &selected, ""
|
||||
}
|
||||
usable = append(usable, candidate)
|
||||
}
|
||||
}
|
||||
if len(usable) == 1 {
|
||||
return &usable[0], ""
|
||||
}
|
||||
if len(usable) > 1 {
|
||||
return nil, ReasonAmbiguousActiveSessions
|
||||
}
|
||||
return nil, ReasonNoUsableActiveSession
|
||||
}
|
||||
|
||||
func selectActiveNormal(result Selection, candidate SessionCandidate) Selection {
|
||||
if candidate.Token.StandardOrFiltered {
|
||||
return success(result, ContextActiveUser, ReasonStandardToken, candidate)
|
||||
}
|
||||
if candidate.Token.FullAdministrator && candidate.Token.RestrictedMediumAllowed {
|
||||
return success(result, ContextActiveUser, ReasonRestrictedToken, candidate)
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextActiveUser, Reason: ReasonRestrictedTokenUnavailable})
|
||||
result.Error = domain.NewExecutionContextUnavailable("a non-elevated active-user token could not be prepared", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func selectActiveElevated(result Selection, candidate SessionCandidate, input SelectionInput) Selection {
|
||||
if candidate.Token.ApprovalPolicyRequired {
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextActiveUserElevated, Reason: ReasonApprovalPolicy})
|
||||
} else if candidate.Token.FullAdministrator || candidate.Token.LinkedFullAvailable {
|
||||
return success(result, ContextActiveUserElevated, ReasonSelected, candidate)
|
||||
} else {
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextActiveUserElevated, Reason: ReasonElevationUnavailable})
|
||||
}
|
||||
if input.ActiveSystemAvailable {
|
||||
return success(result, ContextActiveSystem, ReasonSelected, candidate)
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextActiveSystem, Reason: ReasonActiveSystemUnavailable})
|
||||
if input.LocalSystemAvailable {
|
||||
return success(result, ContextLocalSystem, ReasonSelected, SessionCandidate{})
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextLocalSystem, Reason: ReasonLocalSystemUnavailable})
|
||||
result.Error = domain.NewElevationUnavailable("no elevated Windows execution context could be prepared", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func selectNoUserNormal(result Selection, input SelectionInput) Selection {
|
||||
if input.LocalServiceAvailable {
|
||||
return success(result, ContextLocalService, ReasonSelected, SessionCandidate{})
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextLocalService, Reason: ReasonLocalServiceUnavailable})
|
||||
result.Error = domain.NewExecutionContextUnavailable("LocalService execution context could not be prepared", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func selectNoUserElevated(result Selection, input SelectionInput) Selection {
|
||||
if input.LocalSystemAvailable {
|
||||
return success(result, ContextLocalSystem, ReasonSelected, SessionCandidate{})
|
||||
}
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: ContextLocalSystem, Reason: ReasonLocalSystemUnavailable})
|
||||
result.Error = domain.NewElevationUnavailable("LocalSystem execution context could not be prepared", "")
|
||||
return result
|
||||
}
|
||||
|
||||
func success(result Selection, context ExecutionContext, reason AttemptReason, candidate SessionCandidate) Selection {
|
||||
result.Attempts = append(result.Attempts, Attempt{Context: context, Reason: reason, Success: true})
|
||||
identity := &Identity{Context: context}
|
||||
if context == ContextActiveUser || context == ContextActiveUserElevated || context == ContextActiveSystem {
|
||||
identity.SessionID = &candidate.SessionID
|
||||
identity.UserSID = candidate.UserSID
|
||||
identity.LogonSID = candidate.LogonSID
|
||||
}
|
||||
result.Effective = identity
|
||||
return result
|
||||
}
|
||||
|
||||
func (selection Selection) Validate() error {
|
||||
if selection.Effective != nil && selection.Error != nil {
|
||||
return fmt.Errorf("effective context and error cannot coexist")
|
||||
}
|
||||
if selection.Effective == nil && selection.Error == nil {
|
||||
return fmt.Errorf("selection has neither context nor error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user