// 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 }