Update dependencies

This commit is contained in:
bluepython508
2024-11-01 17:33:34 +00:00
parent 033ac0b400
commit 5cdfab398d
3596 changed files with 1033483 additions and 259 deletions

122
vendor/tailscale.com/util/syspolicy/caching_handler.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
import (
"errors"
"sync"
)
// CachingHandler is a handler that reads policies from an underlying handler the first time each key is requested
// and permanently caches the result unless there is an error. If there is an ErrNoSuchKey error, that result is cached,
// otherwise the actual error is returned and the next read for that key will retry using the handler.
type CachingHandler struct {
mu sync.Mutex
strings map[string]string
uint64s map[string]uint64
bools map[string]bool
strArrs map[string][]string
notFound map[string]bool
handler Handler
}
// NewCachingHandler creates a CachingHandler given a handler.
func NewCachingHandler(handler Handler) *CachingHandler {
return &CachingHandler{
handler: handler,
strings: make(map[string]string),
uint64s: make(map[string]uint64),
bools: make(map[string]bool),
strArrs: make(map[string][]string),
notFound: make(map[string]bool),
}
}
// ReadString reads the policy settings value string given the key.
// ReadString first reads from the handler's cache before resorting to using the handler.
func (ch *CachingHandler) ReadString(key string) (string, error) {
ch.mu.Lock()
defer ch.mu.Unlock()
if val, ok := ch.strings[key]; ok {
return val, nil
}
if notFound := ch.notFound[key]; notFound {
return "", ErrNoSuchKey
}
val, err := ch.handler.ReadString(key)
if errors.Is(err, ErrNoSuchKey) {
ch.notFound[key] = true
return "", err
} else if err != nil {
return "", err
}
ch.strings[key] = val
return val, nil
}
// ReadUInt64 reads the policy settings uint64 value given the key.
// ReadUInt64 first reads from the handler's cache before resorting to using the handler.
func (ch *CachingHandler) ReadUInt64(key string) (uint64, error) {
ch.mu.Lock()
defer ch.mu.Unlock()
if val, ok := ch.uint64s[key]; ok {
return val, nil
}
if notFound := ch.notFound[key]; notFound {
return 0, ErrNoSuchKey
}
val, err := ch.handler.ReadUInt64(key)
if errors.Is(err, ErrNoSuchKey) {
ch.notFound[key] = true
return 0, err
} else if err != nil {
return 0, err
}
ch.uint64s[key] = val
return val, nil
}
// ReadBoolean reads the policy settings boolean value given the key.
// ReadBoolean first reads from the handler's cache before resorting to using the handler.
func (ch *CachingHandler) ReadBoolean(key string) (bool, error) {
ch.mu.Lock()
defer ch.mu.Unlock()
if val, ok := ch.bools[key]; ok {
return val, nil
}
if notFound := ch.notFound[key]; notFound {
return false, ErrNoSuchKey
}
val, err := ch.handler.ReadBoolean(key)
if errors.Is(err, ErrNoSuchKey) {
ch.notFound[key] = true
return false, err
} else if err != nil {
return false, err
}
ch.bools[key] = val
return val, nil
}
// ReadBoolean reads the policy settings boolean value given the key.
// ReadBoolean first reads from the handler's cache before resorting to using the handler.
func (ch *CachingHandler) ReadStringArray(key string) ([]string, error) {
ch.mu.Lock()
defer ch.mu.Unlock()
if val, ok := ch.strArrs[key]; ok {
return val, nil
}
if notFound := ch.notFound[key]; notFound {
return nil, ErrNoSuchKey
}
val, err := ch.handler.ReadStringArray(key)
if errors.Is(err, ErrNoSuchKey) {
ch.notFound[key] = true
return nil, err
} else if err != nil {
return nil, err
}
ch.strArrs[key] = val
return val, nil
}

83
vendor/tailscale.com/util/syspolicy/handler.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
import (
"errors"
"sync/atomic"
)
var (
handlerUsed atomic.Bool
handler Handler = defaultHandler{}
)
// Handler reads system policies from OS-specific storage.
type Handler interface {
// ReadString reads the policy setting's string value for the given key.
// It should return ErrNoSuchKey if the key does not have a value set.
ReadString(key string) (string, error)
// ReadUInt64 reads the policy setting's uint64 value for the given key.
// It should return ErrNoSuchKey if the key does not have a value set.
ReadUInt64(key string) (uint64, error)
// ReadBool reads the policy setting's boolean value for the given key.
// It should return ErrNoSuchKey if the key does not have a value set.
ReadBoolean(key string) (bool, error)
// ReadStringArray reads the policy setting's string array value for the given key.
// It should return ErrNoSuchKey if the key does not have a value set.
ReadStringArray(key string) ([]string, error)
}
// ErrNoSuchKey is returned by a Handler when the specified key does not have a
// value set.
var ErrNoSuchKey = errors.New("no such key")
// defaultHandler is the catch all syspolicy type for anything that isn't windows or apple.
type defaultHandler struct{}
func (defaultHandler) ReadString(_ string) (string, error) {
return "", ErrNoSuchKey
}
func (defaultHandler) ReadUInt64(_ string) (uint64, error) {
return 0, ErrNoSuchKey
}
func (defaultHandler) ReadBoolean(_ string) (bool, error) {
return false, ErrNoSuchKey
}
func (defaultHandler) ReadStringArray(_ string) ([]string, error) {
return nil, ErrNoSuchKey
}
// markHandlerInUse is called before handler methods are called.
func markHandlerInUse() {
handlerUsed.Store(true)
}
// RegisterHandler initializes the policy handler and ensures registration will happen once.
func RegisterHandler(h Handler) {
// Technically this assignment is not concurrency safe, but in the
// event that there was any risk of a data race, we will panic due to
// the CompareAndSwap failing.
handler = h
if !handlerUsed.CompareAndSwap(false, true) {
panic("handler was already used before registration")
}
}
// TB is a subset of testing.TB that we use to set up test helpers.
// It's defined here to avoid pulling in the testing package.
type TB interface {
Helper()
Cleanup(func())
}
func SetHandlerForTest(tb TB, h Handler) {
tb.Helper()
oldHandler := handler
handler = h
tb.Cleanup(func() { handler = oldHandler })
}

105
vendor/tailscale.com/util/syspolicy/handler_windows.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
import (
"errors"
"fmt"
"tailscale.com/util/clientmetric"
"tailscale.com/util/winutil"
)
var (
windowsErrors = clientmetric.NewCounter("windows_syspolicy_errors")
windowsAny = clientmetric.NewGauge("windows_syspolicy_any")
)
type windowsHandler struct{}
func init() {
RegisterHandler(NewCachingHandler(windowsHandler{}))
keyList := []struct {
isSet func(Key) bool
keys []Key
}{
{
isSet: func(k Key) bool {
_, err := handler.ReadString(string(k))
return err == nil
},
keys: stringKeys,
},
{
isSet: func(k Key) bool {
_, err := handler.ReadBoolean(string(k))
return err == nil
},
keys: boolKeys,
},
{
isSet: func(k Key) bool {
_, err := handler.ReadUInt64(string(k))
return err == nil
},
keys: uint64Keys,
},
}
var anySet bool
for _, l := range keyList {
for _, k := range l.keys {
if !l.isSet(k) {
continue
}
clientmetric.NewGauge(fmt.Sprintf("windows_syspolicy_%s", k)).Set(1)
anySet = true
}
}
if anySet {
windowsAny.Set(1)
}
}
func (windowsHandler) ReadString(key string) (string, error) {
s, err := winutil.GetPolicyString(key)
if errors.Is(err, winutil.ErrNoValue) {
err = ErrNoSuchKey
} else if err != nil {
windowsErrors.Add(1)
}
return s, err
}
func (windowsHandler) ReadUInt64(key string) (uint64, error) {
value, err := winutil.GetPolicyInteger(key)
if errors.Is(err, winutil.ErrNoValue) {
err = ErrNoSuchKey
} else if err != nil {
windowsErrors.Add(1)
}
return value, err
}
func (windowsHandler) ReadBoolean(key string) (bool, error) {
value, err := winutil.GetPolicyInteger(key)
if errors.Is(err, winutil.ErrNoValue) {
err = ErrNoSuchKey
} else if err != nil {
windowsErrors.Add(1)
}
return value != 0, err
}
func (windowsHandler) ReadStringArray(key string) ([]string, error) {
value, err := winutil.GetPolicyStringArray(key)
if errors.Is(err, winutil.ErrNoValue) {
err = ErrNoSuchKey
} else if err != nil {
windowsErrors.Add(1)
}
return value, err
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package internal contains miscellaneous functions and types
// that are internal to the syspolicy packages.
package internal
import (
"bytes"
"github.com/go-json-experiment/json/jsontext"
"tailscale.com/types/lazy"
"tailscale.com/version"
)
// OSForTesting is the operating system override used for testing.
// It follows the same naming convention as [version.OS].
var OSForTesting lazy.SyncValue[string]
// OS is like [version.OS], but supports a test hook.
func OS() string {
return OSForTesting.Get(version.OS)
}
// TB is a subset of testing.TB that we use to set up test helpers.
// It's defined here to avoid pulling in the testing package.
type TB interface {
Helper()
Cleanup(func())
Logf(format string, args ...any)
Error(args ...any)
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
}
// EqualJSONForTest compares the JSON in j1 and j2 for semantic equality.
// It returns "", "", true if j1 and j2 are equal. Otherwise, it returns
// indented versions of j1 and j2 and false.
func EqualJSONForTest(tb TB, j1, j2 jsontext.Value) (s1, s2 string, equal bool) {
tb.Helper()
j1 = j1.Clone()
j2 = j2.Clone()
// Canonicalize JSON values for comparison.
if err := j1.Canonicalize(); err != nil {
tb.Error(err)
}
if err := j2.Canonicalize(); err != nil {
tb.Error(err)
}
// Check and return true if the two values are structurally equal.
if bytes.Equal(j1, j2) {
return "", "", true
}
// Otherwise, format the values for display and return false.
if err := j1.Indent("", "\t"); err != nil {
tb.Fatal(err)
}
if err := j2.Indent("", "\t"); err != nil {
tb.Fatal(err)
}
return j1.String(), j2.String(), false
}

View File

@@ -0,0 +1,64 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package loggerx provides logging functions to the rest of the syspolicy packages.
package loggerx
import (
"log"
"sync/atomic"
"tailscale.com/types/lazy"
"tailscale.com/types/logger"
"tailscale.com/util/syspolicy/internal"
)
const (
normalPrefix = "syspolicy: "
verbosePrefix = "syspolicy: [v2] "
)
var (
debugLogging atomic.Bool // whether debugging logging is enabled
lazyPrintf lazy.SyncValue[logger.Logf]
lazyVerbosef lazy.SyncValue[logger.Logf]
)
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
func SetDebugLoggingEnabled(v bool) {
debugLogging.Store(v)
}
// Errorf formats and writes an error message to the log.
func Errorf(format string, args ...any) {
printf(format, args...)
}
// Verbosef formats and writes an optional, verbose message to the log.
func Verbosef(format string, args ...any) {
if debugLogging.Load() {
printf(format, args...)
} else {
verbosef(format, args...)
}
}
func printf(format string, args ...any) {
lazyPrintf.Get(func() logger.Logf {
return logger.WithPrefix(log.Printf, normalPrefix)
})(format, args...)
}
func verbosef(format string, args ...any) {
lazyVerbosef.Get(func() logger.Logf {
return logger.WithPrefix(log.Printf, verbosePrefix)
})(format, args...)
}
// SetForTest sets the specified printf and verbosef functions for the duration
// of tb and its subtests.
func SetForTest(tb internal.TB, printf, verbosef logger.Logf) {
lazyPrintf.SetForTest(tb, printf, nil)
lazyVerbosef.SetForTest(tb, verbosef, nil)
}

112
vendor/tailscale.com/util/syspolicy/policy_keys.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
import "tailscale.com/util/syspolicy/setting"
type Key = setting.Key
const (
// Keys with a string value
ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
// ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
// Exit node ID takes precedence over exit node IP.
// To find the node ID, go to /api.md#device.
ExitNodeID Key = "ExitNodeID"
ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
// Keys with a string value that specifies an option: "always", "never", "user-decides".
// The default is "user-decides" unless otherwise stated. Enforcement of
// these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
// typically hide menu items related to policies that are enforced.
EnableIncomingConnections Key = "AllowIncomingConnections"
EnableServerMode Key = "UnattendedMode"
ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
EnableTailscaleSubnets Key = "UseTailscaleSubnets"
// CheckUpdates is the key to signal if the updater should periodically
// check for updates.
CheckUpdates Key = "CheckUpdates"
// ApplyUpdates is the key to signal if updates should be automatically
// installed. Its value is "InstallUpdates" because of an awkwardly-named
// visibility option "ApplyUpdates" on MacOS.
ApplyUpdates Key = "InstallUpdates"
// EnableRunExitNode controls if the device acts as an exit node. Even when
// running as an exit node, the device must be approved by a tailnet
// administrator. Its name is slightly awkward because RunExitNodeVisibility
// predates this option but is preserved for backwards compatibility.
EnableRunExitNode Key = "AdvertiseExitNode"
// Keys with a string value that controls visibility: "show", "hide".
// The default is "show" unless otherwise stated. Enforcement of these
// policies is typically performed by the UI code for the relevant operating
// system.
AdminConsoleVisibility Key = "AdminConsole"
NetworkDevicesVisibility Key = "NetworkDevices"
TestMenuVisibility Key = "TestMenu"
UpdateMenuVisibility Key = "UpdateMenu"
ResetToDefaultsVisibility Key = "ResetToDefaults"
// RunExitNodeVisibility controls if the "run as exit node" menu item is
// visible, without controlling the setting itself. This is preserved for
// backwards compatibility but prefer EnableRunExitNode in new deployments.
RunExitNodeVisibility Key = "RunExitNode"
PreferencesMenuVisibility Key = "PreferencesMenu"
ExitNodeMenuVisibility Key = "ExitNodesPicker"
// AutoUpdateVisibility is the key to signal if the menu item for automatic
// installation of updates should be visible. It is only used by macsys
// installations and uses the Sparkle naming convention, even though it does
// not actually control updates, merely the UI for that setting.
AutoUpdateVisibility Key = "ApplyUpdates"
// SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
// When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
SuggestedExitNodeVisibility Key = "SuggestedExitNode"
// Keys with a string value formatted for use with time.ParseDuration().
KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
// Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
// DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
// The default is 0 unless otherwise stated.
LogSCMInteractions Key = "LogSCMInteractions"
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
// PostureChecking indicates if posture checking is enabled and the client shall gather
// posture data.
// Key is a string value that specifies an option: "always", "never", "user-decides".
// The default is "user-decides" unless otherwise stated.
PostureChecking Key = "PostureChecking"
// DeviceSerialNumber is the serial number of the device that is running Tailscale.
// This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
// We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
DeviceSerialNumber Key = "DeviceSerialNumber"
// ManagedByOrganizationName indicates the name of the organization managing the Tailscale
// install. It is displayed inside the client UI in a prominent location.
ManagedByOrganizationName Key = "ManagedByOrganizationName"
// ManagedByCaption is an info message displayed inside the client UI as a caption when
// ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
// for Tailscale within the organization.
ManagedByCaption Key = "ManagedByCaption"
// ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
// organization. A button in the client UI provides easy access to this URL.
ManagedByURL Key = "ManagedByURL"
// AuthKey is an auth key that will be used to login whenever the backend starts. This can be used to
// automatically authenticate managed devices, without requiring user interaction.
AuthKey Key = "AuthKey"
// MachineCertificateSubject is the exact name of a Subject that needs
// to be present in an identity's certificate chain to sign a RegisterRequest,
// formatted as per pkix.Name.String(). The Subject may be that of the identity
// itself, an intermediate CA or the root CA.
//
// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
MachineCertificateSubject Key = "MachineCertificateSubject"
// Keys with a string array value.
// AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
)

View File

@@ -0,0 +1,38 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
var stringKeys = []Key{
ControlURL,
LogTarget,
Tailnet,
ExitNodeID,
ExitNodeIP,
EnableIncomingConnections,
EnableServerMode,
ExitNodeAllowLANAccess,
EnableTailscaleDNS,
EnableTailscaleSubnets,
AdminConsoleVisibility,
NetworkDevicesVisibility,
TestMenuVisibility,
UpdateMenuVisibility,
RunExitNodeVisibility,
PreferencesMenuVisibility,
ExitNodeMenuVisibility,
AutoUpdateVisibility,
ResetToDefaultsVisibility,
KeyExpirationNoticeTime,
PostureChecking,
ManagedByOrganizationName,
ManagedByCaption,
ManagedByURL,
}
var boolKeys = []Key{
LogSCMInteractions,
FlushDNSOnSessionUnlock,
}
var uint64Keys = []Key{}

71
vendor/tailscale.com/util/syspolicy/setting/errors.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"errors"
"tailscale.com/types/ptr"
)
var (
// ErrNotConfigured is returned when the requested policy setting is not configured.
ErrNotConfigured = errors.New("not configured")
// ErrTypeMismatch is returned when there's a type mismatch between the actual type
// of the setting value and the expected type.
ErrTypeMismatch = errors.New("type mismatch")
// ErrNoSuchKey is returned by [DefinitionOf] when no policy setting
// has been registered with the specified key.
//
// Until 2024-08-02, this error was also returned by a [Handler] when the specified
// key did not have a value set. While the package maintains compatibility with this
// usage of ErrNoSuchKey, it is recommended to return [ErrNotConfigured] from newer
// [source.Store] implementations.
ErrNoSuchKey = errors.New("no such key")
)
// ErrorText represents an error that occurs when reading or parsing a policy setting.
// This includes errors due to permissions issues, value type and format mismatches,
// and other platform- or source-specific errors. It does not include
// [ErrNotConfigured] and [ErrNoSuchKey], as those correspond to unconfigured
// policy settings rather than settings that cannot be read or parsed
// due to an error.
//
// ErrorText is used to marshal errors when a policy setting is sent over the wire,
// allowing the error to be logged or displayed. It does not preserve the
// type information of the underlying error.
type ErrorText string
// NewErrorText returns a [ErrorText] with the specified error message.
func NewErrorText(text string) *ErrorText {
return ptr.To(ErrorText(text))
}
// MaybeErrorText returns an [ErrorText] with the text of the specified error,
// or nil if err is nil, [ErrNotConfigured], or [ErrNoSuchKey].
func MaybeErrorText(err error) *ErrorText {
if err == nil || errors.Is(err, ErrNotConfigured) || errors.Is(err, ErrNoSuchKey) {
return nil
}
if err, ok := err.(*ErrorText); ok {
return err
}
return ptr.To(ErrorText(err.Error()))
}
// Error implements error.
func (e ErrorText) Error() string {
return string(e)
}
// MarshalText implements [encoding.TextMarshaler].
func (e ErrorText) MarshalText() (text []byte, err error) {
return []byte(e.Error()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler].
func (e *ErrorText) UnmarshalText(text []byte) error {
*e = ErrorText(text)
return nil
}

13
vendor/tailscale.com/util/syspolicy/setting/key.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
// Key is a string that uniquely identifies a policy and must remain unchanged
// once established and documented for a given policy setting. It may contain
// alphanumeric characters and zero or more [KeyPathSeparator]s to group
// individual policy settings into categories.
type Key string
// KeyPathSeparator allows logical grouping of policy settings into categories.
const KeyPathSeparator = "/"

71
vendor/tailscale.com/util/syspolicy/setting/origin.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"fmt"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
// Origin describes where a policy or a policy setting is configured.
type Origin struct {
data settingOrigin
}
// settingOrigin is the marshallable data of an [Origin].
type settingOrigin struct {
Name string `json:",omitzero"`
Scope PolicyScope
}
// NewOrigin returns a new [Origin] with the specified scope.
func NewOrigin(scope PolicyScope) *Origin {
return NewNamedOrigin("", scope)
}
// NewNamedOrigin returns a new [Origin] with the specified scope and name.
func NewNamedOrigin(name string, scope PolicyScope) *Origin {
return &Origin{settingOrigin{name, scope}}
}
// Scope reports the policy [PolicyScope] where the setting is configured.
func (s Origin) Scope() PolicyScope {
return s.data.Scope
}
// Name returns the name of the policy source where the setting is configured,
// or "" if not available.
func (s Origin) Name() string {
return s.data.Name
}
// String implements [fmt.Stringer].
func (s Origin) String() string {
if s.Name() != "" {
return fmt.Sprintf("%s (%v)", s.Name(), s.Scope())
}
return s.Scope().String()
}
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
func (s Origin) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
return jsonv2.MarshalEncode(out, &s.data, opts)
}
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
func (s *Origin) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
return jsonv2.UnmarshalDecode(in, &s.data, opts)
}
// MarshalJSON implements [json.Marshaler].
func (s Origin) MarshalJSON() ([]byte, error) {
return jsonv2.Marshal(s) // uses MarshalJSONV2
}
// UnmarshalJSON implements [json.Unmarshaler].
func (s *Origin) UnmarshalJSON(b []byte) error {
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
}

View File

@@ -0,0 +1,189 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"fmt"
"strings"
"tailscale.com/types/lazy"
)
var (
lazyDefaultScope lazy.SyncValue[PolicyScope]
// DeviceScope indicates a scope containing device-global policies.
DeviceScope = PolicyScope{kind: DeviceSetting}
// CurrentProfileScope indicates a scope containing policies that apply to the
// currently active Tailscale profile.
CurrentProfileScope = PolicyScope{kind: ProfileSetting}
// CurrentUserScope indicates a scope containing policies that apply to the
// current user, for whatever that means on the current platform and
// in the current application context.
CurrentUserScope = PolicyScope{kind: UserSetting}
)
// PolicyScope is a management scope.
type PolicyScope struct {
kind Scope
userID string
profileID string
}
// DefaultScope returns the default [PolicyScope] to be used by a program
// when querying policy settings.
// It returns [DeviceScope], unless explicitly changed with [SetDefaultScope].
func DefaultScope() PolicyScope {
return lazyDefaultScope.Get(func() PolicyScope { return DeviceScope })
}
// SetDefaultScope attempts to set the specified scope as the default scope
// to be used by a program when querying policy settings.
// It fails and returns false if called more than once, or if the [DefaultScope]
// has already been used.
func SetDefaultScope(scope PolicyScope) bool {
return lazyDefaultScope.Set(scope)
}
// UserScopeOf returns a policy [PolicyScope] of the user with the specified id.
func UserScopeOf(uid string) PolicyScope {
return PolicyScope{kind: UserSetting, userID: uid}
}
// Kind reports the scope kind of s.
func (s PolicyScope) Kind() Scope {
return s.kind
}
// IsApplicableSetting reports whether the specified setting applies to
// and can be retrieved for this scope. Policy settings are applicable
// to their own scopes as well as more specific scopes. For example,
// device settings are applicable to device, profile and user scopes,
// but user settings are only applicable to user scopes.
// For instance, a menu visibility setting is inherently a user setting
// and only makes sense in the context of a specific user.
func (s PolicyScope) IsApplicableSetting(setting *Definition) bool {
return setting != nil && setting.Scope() <= s.Kind()
}
// IsConfigurableSetting reports whether the specified setting can be configured
// by a policy at this scope. Policy settings are configurable at their own scopes
// as well as broader scopes. For example, [UserSetting]s are configurable in
// user, profile, and device scopes, but [DeviceSetting]s are only configurable
// in the [DeviceScope]. For instance, the InstallUpdates policy setting
// can only be configured in the device scope, as it controls whether updates
// will be installed automatically on the device, rather than for specific users.
func (s PolicyScope) IsConfigurableSetting(setting *Definition) bool {
return setting != nil && setting.Scope() >= s.Kind()
}
// Contains reports whether policy settings that apply to s also apply to s2.
// For example, policy settings that apply to the [DeviceScope] also apply to
// the [CurrentUserScope].
func (s PolicyScope) Contains(s2 PolicyScope) bool {
if s.Kind() > s2.Kind() {
return false
}
switch s.Kind() {
case DeviceSetting:
return true
case ProfileSetting:
return s.profileID == s2.profileID
case UserSetting:
return s.userID == s2.userID
default:
panic("unreachable")
}
}
// StrictlyContains is like [PolicyScope.Contains], but returns false
// when s and s2 is the same scope.
func (s PolicyScope) StrictlyContains(s2 PolicyScope) bool {
return s != s2 && s.Contains(s2)
}
// String implements [fmt.Stringer].
func (s PolicyScope) String() string {
if s.profileID == "" && s.userID == "" {
return s.kind.String()
}
return s.stringSlow()
}
// MarshalText implements [encoding.TextMarshaler].
func (s PolicyScope) MarshalText() ([]byte, error) {
return []byte(s.String()), nil
}
// MarshalText implements [encoding.TextUnmarshaler].
func (s *PolicyScope) UnmarshalText(b []byte) error {
*s = PolicyScope{}
parts := strings.SplitN(string(b), "/", 2)
for i, part := range parts {
kind, id, err := parseScopeAndID(part)
if err != nil {
return err
}
if i > 0 && kind <= s.kind {
return fmt.Errorf("invalid scope hierarchy: %s", b)
}
s.kind = kind
switch kind {
case DeviceSetting:
if id != "" {
return fmt.Errorf("the device scope must not have an ID: %s", b)
}
case ProfileSetting:
s.profileID = id
case UserSetting:
s.userID = id
}
}
return nil
}
func (s PolicyScope) stringSlow() string {
var sb strings.Builder
writeScopeWithID := func(s Scope, id string) {
sb.WriteString(s.String())
if id != "" {
sb.WriteRune('(')
sb.WriteString(id)
sb.WriteRune(')')
}
}
if s.kind == ProfileSetting || s.profileID != "" {
writeScopeWithID(ProfileSetting, s.profileID)
if s.kind != ProfileSetting {
sb.WriteRune('/')
}
}
if s.kind == UserSetting {
writeScopeWithID(UserSetting, s.userID)
}
return sb.String()
}
func parseScopeAndID(s string) (scope Scope, id string, err error) {
name, params, ok := extractScopeAndParams(s)
if !ok {
return 0, "", fmt.Errorf("%q is not a valid scope string", s)
}
if err := scope.UnmarshalText([]byte(name)); err != nil {
return 0, "", err
}
return scope, params, nil
}
func extractScopeAndParams(s string) (name, params string, ok bool) {
paramsStart := strings.Index(s, "(")
if paramsStart == -1 {
return s, "", true
}
paramsEnd := strings.LastIndex(s, ")")
if paramsEnd < paramsStart {
return "", "", false
}
return s[0:paramsStart], s[paramsStart+1 : paramsEnd], true
}

View File

@@ -0,0 +1,67 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"fmt"
"tailscale.com/types/structs"
)
// RawItem contains a raw policy setting value as read from a policy store, or an
// error if the requested setting could not be read from the store. As a special
// case, it may also hold a value of the [Visibility], [PreferenceOption],
// or [time.Duration] types. While the policy store interface does not support
// these types natively, and the values of these types have to be unmarshalled
// or converted from strings, these setting types predate the typed policy
// hierarchies, and must be supported at this layer.
type RawItem struct {
_ structs.Incomparable
value any
err *ErrorText
origin *Origin // or nil
}
// RawItemOf returns a [RawItem] with the specified value.
func RawItemOf(value any) RawItem {
return RawItemWith(value, nil, nil)
}
// RawItemWith returns a [RawItem] with the specified value, error and origin.
func RawItemWith(value any, err *ErrorText, origin *Origin) RawItem {
return RawItem{value: value, err: err, origin: origin}
}
// Value returns the value of the policy setting, or nil if the policy setting
// is not configured, or an error occurred while reading it.
func (i RawItem) Value() any {
return i.value
}
// Error returns the error that occurred when reading the policy setting,
// or nil if no error occurred.
func (i RawItem) Error() error {
if i.err != nil {
return i.err
}
return nil
}
// Origin returns an optional [Origin] indicating where the policy setting is
// configured.
func (i RawItem) Origin() *Origin {
return i.origin
}
// String implements [fmt.Stringer].
func (i RawItem) String() string {
var suffix string
if i.origin != nil {
suffix = fmt.Sprintf(" - {%v}", i.origin)
}
if i.err != nil {
return fmt.Sprintf("Error{%q}%s", i.err.Error(), suffix)
}
return fmt.Sprintf("%v%s", i.value, suffix)
}

348
vendor/tailscale.com/util/syspolicy/setting/setting.go generated vendored Normal file
View File

@@ -0,0 +1,348 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package setting contains types for defining and representing policy settings.
// It facilitates the registration of setting definitions using [Register] and [RegisterDefinition],
// and the retrieval of registered setting definitions via [Definitions] and [DefinitionOf].
// This package is intended for use primarily within the syspolicy package hierarchy.
package setting
import (
"fmt"
"slices"
"strings"
"sync"
"time"
"tailscale.com/types/lazy"
"tailscale.com/util/syspolicy/internal"
)
// Scope indicates the broadest scope at which a policy setting may apply,
// and the narrowest scope at which it may be configured.
type Scope int8
const (
// DeviceSetting indicates a policy setting that applies to a device, regardless of
// which OS user or Tailscale profile is currently active, if any.
// It can only be configured at a [DeviceScope].
DeviceSetting Scope = iota
// ProfileSetting indicates a policy setting that applies to a Tailscale profile.
// It can only be configured for a specific profile or at a [DeviceScope],
// in which case it applies to all profiles on the device.
ProfileSetting
// UserSetting indicates a policy setting that applies to users.
// It can be configured for a user, profile, or the entire device.
UserSetting
// NumScopes is the number of possible [Scope] values.
NumScopes int = iota // must be the last value in the const block.
)
// String implements [fmt.Stringer].
func (s Scope) String() string {
switch s {
case DeviceSetting:
return "Device"
case ProfileSetting:
return "Profile"
case UserSetting:
return "User"
default:
panic("unreachable")
}
}
// MarshalText implements [encoding.TextMarshaler].
func (s Scope) MarshalText() (text []byte, err error) {
return []byte(s.String()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler].
func (s *Scope) UnmarshalText(text []byte) error {
switch strings.ToLower(string(text)) {
case "device":
*s = DeviceSetting
case "profile":
*s = ProfileSetting
case "user":
*s = UserSetting
default:
return fmt.Errorf("%q is not a valid scope", string(text))
}
return nil
}
// Type is a policy setting value type.
// Except for [InvalidValue], which represents an invalid policy setting type,
// and [PreferenceOptionValue], [VisibilityValue], and [DurationValue],
// which have special handling due to their legacy status in the package,
// SettingTypes represent the raw value types readable from policy stores.
type Type int
const (
// InvalidValue indicates an invalid policy setting value type.
InvalidValue Type = iota
// BooleanValue indicates a policy setting whose underlying type is a bool.
BooleanValue
// IntegerValue indicates a policy setting whose underlying type is a uint64.
IntegerValue
// StringValue indicates a policy setting whose underlying type is a string.
StringValue
// StringListValue indicates a policy setting whose underlying type is a []string.
StringListValue
// PreferenceOptionValue indicates a three-state policy setting whose
// underlying type is a string, but the actual value is a [PreferenceOption].
PreferenceOptionValue
// VisibilityValue indicates a two-state boolean-like policy setting whose
// underlying type is a string, but the actual value is a [Visibility].
VisibilityValue
// DurationValue indicates an interval/period/duration policy setting whose
// underlying type is a string, but the actual value is a [time.Duration].
DurationValue
)
// String returns a string representation of t.
func (t Type) String() string {
switch t {
case InvalidValue:
return "Invalid"
case BooleanValue:
return "Boolean"
case IntegerValue:
return "Integer"
case StringValue:
return "String"
case StringListValue:
return "StringList"
case PreferenceOptionValue:
return "PreferenceOption"
case VisibilityValue:
return "Visibility"
case DurationValue:
return "Duration"
default:
panic("unreachable")
}
}
// ValueType is a constraint that allows Go types corresponding to [Type].
type ValueType interface {
bool | uint64 | string | []string | Visibility | PreferenceOption | time.Duration
}
// Definition defines policy key, scope and value type.
type Definition struct {
key Key
scope Scope
typ Type
platforms PlatformList
}
// NewDefinition returns a new [Definition] with the specified
// key, scope, type and supported platforms (see [PlatformList]).
func NewDefinition(k Key, s Scope, t Type, platforms ...string) *Definition {
return &Definition{key: k, scope: s, typ: t, platforms: platforms}
}
// Key returns a policy setting's identifier.
func (d *Definition) Key() Key {
if d == nil {
return ""
}
return d.key
}
// Scope reports the broadest [Scope] the policy setting may apply to.
func (d *Definition) Scope() Scope {
if d == nil {
return 0
}
return d.scope
}
// Type reports the underlying value type of the policy setting.
func (d *Definition) Type() Type {
if d == nil {
return InvalidValue
}
return d.typ
}
// IsSupported reports whether the policy setting is supported on the current OS.
func (d *Definition) IsSupported() bool {
if d == nil {
return false
}
return d.platforms.HasCurrent()
}
// SupportedPlatforms reports platforms on which the policy setting is supported.
// An empty [PlatformList] indicates that s is available on all platforms.
func (d *Definition) SupportedPlatforms() PlatformList {
if d == nil {
return nil
}
return d.platforms
}
// String implements [fmt.Stringer].
func (d *Definition) String() string {
if d == nil {
return "(nil)"
}
return fmt.Sprintf("%v(%q, %v)", d.scope, d.key, d.typ)
}
// Equal reports whether d and d2 have the same key, type and scope.
// It does not check whether both s and s2 are supported on the same platforms.
func (d *Definition) Equal(d2 *Definition) bool {
if d == d2 {
return true
}
if d == nil || d2 == nil {
return false
}
return d.key == d2.key && d.typ == d2.typ && d.scope == d2.scope
}
// DefinitionMap is a map of setting [Definition] by [Key].
type DefinitionMap map[Key]*Definition
var (
definitions lazy.SyncValue[DefinitionMap]
definitionsMu sync.Mutex
definitionsList []*Definition
definitionsUsed bool
)
// Register registers a policy setting with the specified key, scope, value type,
// and an optional list of supported platforms. All policy settings must be
// registered before any of them can be used. Register panics if called after
// invoking any functions that use the registered policy definitions. This
// includes calling [Definitions] or [DefinitionOf] directly, or reading any
// policy settings via syspolicy.
func Register(k Key, s Scope, t Type, platforms ...string) {
RegisterDefinition(NewDefinition(k, s, t, platforms...))
}
// RegisterDefinition is like [Register], but accepts a [Definition].
func RegisterDefinition(d *Definition) {
definitionsMu.Lock()
defer definitionsMu.Unlock()
registerLocked(d)
}
func registerLocked(d *Definition) {
if definitionsUsed {
panic("policy definitions are already in use")
}
definitionsList = append(definitionsList, d)
}
func settingDefinitions() (DefinitionMap, error) {
return definitions.GetErr(func() (DefinitionMap, error) {
definitionsMu.Lock()
defer definitionsMu.Unlock()
definitionsUsed = true
return DefinitionMapOf(definitionsList)
})
}
// DefinitionMapOf returns a [DefinitionMap] with the specified settings,
// or an error if any settings have the same key but different type or scope.
func DefinitionMapOf(settings []*Definition) (DefinitionMap, error) {
m := make(DefinitionMap, len(settings))
for _, s := range settings {
if existing, exists := m[s.key]; exists {
if existing.Equal(s) {
// Ignore duplicate setting definitions if they match. It is acceptable
// if the same policy setting was registered more than once
// (e.g. by the syspolicy package itself and by iOS/Android code).
existing.platforms.mergeFrom(s.platforms)
continue
}
return nil, fmt.Errorf("duplicate policy definition: %q", s.key)
}
m[s.key] = s
}
return m, nil
}
// SetDefinitionsForTest allows to register the specified setting definitions
// for the test duration. It is not concurrency-safe, but unlike [Register],
// it does not panic and can be called anytime.
// It returns an error if ds contains two different settings with the same [Key].
func SetDefinitionsForTest(tb lazy.TB, ds ...*Definition) error {
m, err := DefinitionMapOf(ds)
if err != nil {
return err
}
definitions.SetForTest(tb, m, err)
return nil
}
// DefinitionOf returns a setting definition by key,
// or [ErrNoSuchKey] if the specified key does not exist,
// or an error if there are conflicting policy definitions.
func DefinitionOf(k Key) (*Definition, error) {
ds, err := settingDefinitions()
if err != nil {
return nil, err
}
if d, ok := ds[k]; ok {
return d, nil
}
return nil, ErrNoSuchKey
}
// Definitions returns all registered setting definitions,
// or an error if different policies were registered under the same name.
func Definitions() ([]*Definition, error) {
ds, err := settingDefinitions()
if err != nil {
return nil, err
}
res := make([]*Definition, 0, len(ds))
for _, d := range ds {
res = append(res, d)
}
return res, nil
}
// PlatformList is a list of OSes.
// An empty list indicates that all possible platforms are supported.
type PlatformList []string
// Has reports whether l contains the target platform.
func (l PlatformList) Has(target string) bool {
if len(l) == 0 {
return true
}
return slices.ContainsFunc(l, func(os string) bool {
return strings.EqualFold(os, target)
})
}
// HasCurrent is like Has, but for the current platform.
func (l PlatformList) HasCurrent() bool {
return l.Has(internal.OS())
}
// mergeFrom merges l2 into l. Since an empty list indicates no platform restrictions,
// if either l or l2 is empty, the merged result in l will also be empty.
func (l *PlatformList) mergeFrom(l2 PlatformList) {
switch {
case len(*l) == 0:
// No-op. An empty list indicates no platform restrictions.
case len(l2) == 0:
// Merging with an empty list results in an empty list.
*l = l2
default:
// Append, sort and dedup.
*l = append(*l, l2...)
slices.Sort(*l)
*l = slices.Compact(*l)
}
}

170
vendor/tailscale.com/util/syspolicy/setting/snapshot.go generated vendored Normal file
View File

@@ -0,0 +1,170 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"iter"
"maps"
"slices"
"strings"
xmaps "golang.org/x/exp/maps"
"tailscale.com/util/deephash"
)
// Snapshot is an immutable collection of ([Key], [RawItem]) pairs, representing
// a set of policy settings applied at a specific moment in time.
// A nil pointer to [Snapshot] is valid.
type Snapshot struct {
m map[Key]RawItem
sig deephash.Sum // of m
summary Summary
}
// NewSnapshot returns a new [Snapshot] with the specified items and options.
func NewSnapshot(items map[Key]RawItem, opts ...SummaryOption) *Snapshot {
return &Snapshot{m: xmaps.Clone(items), sig: deephash.Hash(&items), summary: SummaryWith(opts...)}
}
// All returns an iterator over policy settings in s. The iteration order is not
// specified and is not guaranteed to be the same from one call to the next.
func (s *Snapshot) All() iter.Seq2[Key, RawItem] {
if s == nil {
return func(yield func(Key, RawItem) bool) {}
}
return maps.All(s.m)
}
// Get returns the value of the policy setting with the specified key
// or nil if it is not configured or has an error.
func (s *Snapshot) Get(k Key) any {
v, _ := s.GetErr(k)
return v
}
// GetErr returns the value of the policy setting with the specified key,
// [ErrNotConfigured] if it is not configured, or an error returned by
// the policy Store if the policy setting could not be read.
func (s *Snapshot) GetErr(k Key) (any, error) {
if s != nil {
if s, ok := s.m[k]; ok {
return s.Value(), s.Error()
}
}
return nil, ErrNotConfigured
}
// GetSetting returns the untyped policy setting with the specified key and true
// if a policy setting with such key has been configured;
// otherwise, it returns zero, false.
func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
setting, ok = s.m[k]
return setting, ok
}
// Equal reports whether s and s2 are equal.
func (s *Snapshot) Equal(s2 *Snapshot) bool {
if !s.EqualItems(s2) {
return false
}
return s.Summary() == s2.Summary()
}
// EqualItems reports whether items in s and s2 are equal.
func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
if s == s2 {
return true
}
if s.Len() != s2.Len() {
return false
}
if s.Len() == 0 {
return true
}
return s.sig == s2.sig
}
// Keys return an iterator over keys in s. The iteration order is not specified
// and is not guaranteed to be the same from one call to the next.
func (s *Snapshot) Keys() iter.Seq[Key] {
if s.m == nil {
return func(yield func(Key) bool) {}
}
return maps.Keys(s.m)
}
// Len reports the number of [RawItem]s in s.
func (s *Snapshot) Len() int {
if s == nil {
return 0
}
return len(s.m)
}
// Summary returns information about s as a whole rather than about specific [RawItem]s in it.
func (s *Snapshot) Summary() Summary {
if s == nil {
return Summary{}
}
return s.summary
}
// String implements [fmt.Stringer]
func (s *Snapshot) String() string {
if s.Len() == 0 && s.Summary().IsEmpty() {
return "{Empty}"
}
var sb strings.Builder
if !s.summary.IsEmpty() {
sb.WriteRune('{')
if s.Len() == 0 {
sb.WriteString("Empty, ")
}
sb.WriteString(s.summary.String())
sb.WriteRune('}')
}
for _, k := range slices.Sorted(s.Keys()) {
if sb.Len() != 0 {
sb.WriteRune('\n')
}
sb.WriteString(string(k))
sb.WriteString(" = ")
sb.WriteString(s.m[k].String())
}
return sb.String()
}
// MergeSnapshots returns a [Snapshot] that contains all [RawItem]s
// from snapshot1 and snapshot2 and the [Summary] with the narrower [PolicyScope].
// If there's a conflict between policy settings in the two snapshots,
// the policy settings from the snapshot with the broader scope take precedence.
// In other words, policy settings configured for the [DeviceScope] win
// over policy settings configured for a user scope.
func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
scope1, ok1 := snapshot1.Summary().Scope().GetOk()
scope2, ok2 := snapshot2.Summary().Scope().GetOk()
if ok1 && ok2 && scope1.StrictlyContains(scope2) {
// Swap snapshots if snapshot1 has higher precedence than snapshot2.
snapshot1, snapshot2 = snapshot2, snapshot1
}
if snapshot2.Len() == 0 {
return snapshot1
}
summaryOpts := make([]SummaryOption, 0, 2)
if scope, ok := snapshot1.Summary().Scope().GetOk(); ok {
// Use the scope from snapshot1, if present, which is the more specific snapshot.
summaryOpts = append(summaryOpts, scope)
}
if snapshot1.Len() == 0 {
if origin, ok := snapshot2.Summary().Origin().GetOk(); ok {
// Use the origin from snapshot2 if snapshot1 is empty.
summaryOpts = append(summaryOpts, origin)
}
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
}
m := make(map[Key]RawItem, snapshot1.Len()+snapshot2.Len())
xmaps.Copy(m, snapshot1.m)
xmaps.Copy(m, snapshot2.m) // snapshot2 has higher precedence
return &Snapshot{m, deephash.Hash(&m), SummaryWith(summaryOpts...)}
}

100
vendor/tailscale.com/util/syspolicy/setting/summary.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
"tailscale.com/types/opt"
)
// Summary is an immutable [PolicyScope] and [Origin].
type Summary struct {
data summary
}
type summary struct {
Scope opt.Value[PolicyScope] `json:",omitzero"`
Origin opt.Value[Origin] `json:",omitzero"`
}
// SummaryWith returns a [Summary] with the specified options.
func SummaryWith(opts ...SummaryOption) Summary {
var summary Summary
for _, o := range opts {
o.applySummaryOption(&summary)
}
return summary
}
// IsEmpty reports whether s is empty.
func (s Summary) IsEmpty() bool {
return s == Summary{}
}
// Scope reports the [PolicyScope] in s.
func (s Summary) Scope() opt.Value[PolicyScope] {
return s.data.Scope
}
// Origin reports the [Origin] in s.
func (s Summary) Origin() opt.Value[Origin] {
return s.data.Origin
}
// String implements [fmt.Stringer].
func (s Summary) String() string {
if s.IsEmpty() {
return "{Empty}"
}
if origin, ok := s.data.Origin.GetOk(); ok {
return origin.String()
}
return s.data.Scope.String()
}
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
func (s Summary) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
return jsonv2.MarshalEncode(out, &s.data, opts)
}
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
func (s *Summary) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
return jsonv2.UnmarshalDecode(in, &s.data, opts)
}
// MarshalJSON implements [json.Marshaler].
func (s Summary) MarshalJSON() ([]byte, error) {
return jsonv2.Marshal(s) // uses MarshalJSONV2
}
// UnmarshalJSON implements [json.Unmarshaler].
func (s *Summary) UnmarshalJSON(b []byte) error {
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
}
// SummaryOption is an option that configures [Summary]
// The following are allowed options:
//
// - [Summary]
// - [PolicyScope]
// - [Origin]
type SummaryOption interface {
applySummaryOption(summary *Summary)
}
func (s PolicyScope) applySummaryOption(summary *Summary) {
summary.data.Scope.Set(s)
}
func (o Origin) applySummaryOption(summary *Summary) {
summary.data.Origin.Set(o)
if !summary.data.Scope.IsSet() {
summary.data.Scope.Set(o.Scope())
}
}
func (s Summary) applySummaryOption(summary *Summary) {
*summary = s
}

136
vendor/tailscale.com/util/syspolicy/setting/types.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"encoding"
)
// PreferenceOption is a policy that governs whether a boolean variable
// is forcibly assigned an administrator-defined value, or allowed to receive
// a user-defined value.
type PreferenceOption byte
const (
ShowChoiceByPolicy PreferenceOption = iota
NeverByPolicy
AlwaysByPolicy
)
// Show returns if the UI option that controls the choice administered by this
// policy should be shown. Currently this is true if and only if the policy is
// [ShowChoiceByPolicy].
func (p PreferenceOption) Show() bool {
return p == ShowChoiceByPolicy
}
// ShouldEnable checks if the choice administered by this policy should be
// enabled. If the administrator has chosen a setting, the administrator's
// setting is returned, otherwise userChoice is returned.
func (p PreferenceOption) ShouldEnable(userChoice bool) bool {
switch p {
case NeverByPolicy:
return false
case AlwaysByPolicy:
return true
default:
return userChoice
}
}
// IsAlways reports whether the preference should always be enabled.
func (p PreferenceOption) IsAlways() bool {
return p == AlwaysByPolicy
}
// IsNever reports whether the preference should always be disabled.
func (p PreferenceOption) IsNever() bool {
return p == NeverByPolicy
}
// WillOverride checks if the choice administered by the policy is different
// from the user's choice.
func (p PreferenceOption) WillOverride(userChoice bool) bool {
return p.ShouldEnable(userChoice) != userChoice
}
// String returns a string representation of p.
func (p PreferenceOption) String() string {
switch p {
case AlwaysByPolicy:
return "always"
case NeverByPolicy:
return "never"
default:
return "user-decides"
}
}
// MarshalText implements [encoding.TextMarshaler].
func (p *PreferenceOption) MarshalText() (text []byte, err error) {
return []byte(p.String()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler].
// It never fails and sets p to [ShowChoiceByPolicy] if the specified text
// does not represent a valid [PreferenceOption].
func (p *PreferenceOption) UnmarshalText(text []byte) error {
switch string(text) {
case "always":
*p = AlwaysByPolicy
case "never":
*p = NeverByPolicy
default:
*p = ShowChoiceByPolicy
}
return nil
}
// Visibility is a policy that controls whether or not a particular
// component of a user interface is to be shown.
type Visibility byte
var (
_ encoding.TextMarshaler = (*Visibility)(nil)
_ encoding.TextUnmarshaler = (*Visibility)(nil)
)
const (
VisibleByPolicy Visibility = 'v'
HiddenByPolicy Visibility = 'h'
)
// Show reports whether the UI option administered by this policy should be shown.
// Currently this is true if the policy is not [hiddenByPolicy].
func (v Visibility) Show() bool {
return v != HiddenByPolicy
}
// String returns a string representation of v.
func (v Visibility) String() string {
switch v {
case 'h':
return "hide"
default:
return "show"
}
}
// MarshalText implements [encoding.TextMarshaler].
func (v Visibility) MarshalText() (text []byte, err error) {
return []byte(v.String()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler].
// It never fails and sets v to [VisibleByPolicy] if the specified text
// does not represent a valid [Visibility].
func (v *Visibility) UnmarshalText(text []byte) error {
switch string(text) {
case "hide":
*v = HiddenByPolicy
default:
*v = VisibleByPolicy
}
return nil
}

143
vendor/tailscale.com/util/syspolicy/syspolicy.go generated vendored Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package syspolicy provides functions to retrieve system settings of a device.
package syspolicy
import (
"errors"
"time"
"tailscale.com/util/syspolicy/internal/loggerx"
"tailscale.com/util/syspolicy/setting"
)
func GetString(key Key, defaultValue string) (string, error) {
markHandlerInUse()
v, err := handler.ReadString(string(key))
if errors.Is(err, ErrNoSuchKey) {
return defaultValue, nil
}
return v, err
}
func GetUint64(key Key, defaultValue uint64) (uint64, error) {
markHandlerInUse()
v, err := handler.ReadUInt64(string(key))
if errors.Is(err, ErrNoSuchKey) {
return defaultValue, nil
}
return v, err
}
func GetBoolean(key Key, defaultValue bool) (bool, error) {
markHandlerInUse()
v, err := handler.ReadBoolean(string(key))
if errors.Is(err, ErrNoSuchKey) {
return defaultValue, nil
}
return v, err
}
func GetStringArray(key Key, defaultValue []string) ([]string, error) {
markHandlerInUse()
v, err := handler.ReadStringArray(string(key))
if errors.Is(err, ErrNoSuchKey) {
return defaultValue, nil
}
return v, err
}
// GetPreferenceOption loads a policy from the registry that can be
// managed by an enterprise policy management system and allows administrative
// overrides of users' choices in a way that we do not want tailcontrol to have
// the authority to set. It describes user-decides/always/never options, where
// "always" and "never" remove the user's ability to make a selection. If not
// present or set to a different value, "user-decides" is the default.
func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
s, err := GetString(name, "user-decides")
if err != nil {
return setting.ShowChoiceByPolicy, err
}
var opt setting.PreferenceOption
err = opt.UnmarshalText([]byte(s))
return opt, err
}
// GetVisibility loads a policy from the registry that can be managed
// by an enterprise policy management system and describes show/hide decisions
// for UI elements. The registry value should be a string set to "show" (return
// true) or "hide" (return true). If not present or set to a different value,
// "show" (return false) is the default.
func GetVisibility(name Key) (setting.Visibility, error) {
s, err := GetString(name, "show")
if err != nil {
return setting.VisibleByPolicy, err
}
var visibility setting.Visibility
visibility.UnmarshalText([]byte(s))
return visibility, nil
}
// GetDuration loads a policy from the registry that can be managed
// by an enterprise policy management system and describes a duration for some
// action. The registry value should be a string that time.ParseDuration
// understands. If the registry value is "" or can not be processed,
// defaultValue is returned instead.
func GetDuration(name Key, defaultValue time.Duration) (time.Duration, error) {
opt, err := GetString(name, "")
if opt == "" || err != nil {
return defaultValue, err
}
v, err := time.ParseDuration(opt)
if err != nil || v < 0 {
return defaultValue, nil
}
return v, nil
}
// SelectControlURL returns the ControlURL to use based on a value in
// the registry (LoginURL) and the one on disk (in the GUI's
// prefs.conf). If both are empty, it returns a default value. (It
// always return a non-empty value)
//
// See https://github.com/tailscale/tailscale/issues/2798 for some background.
func SelectControlURL(reg, disk string) string {
const def = "https://controlplane.tailscale.com"
// Prior to Dec 2020's commit 739b02e6, the installer
// wrote a LoginURL value of https://login.tailscale.com to the registry.
const oldRegDef = "https://login.tailscale.com"
// If they have an explicit value in the registry, use it,
// unless it's an old default value from an old installer.
// Then we have to see which is better.
if reg != "" {
if reg != oldRegDef {
// Something explicit in the registry that we didn't
// set ourselves by the installer.
return reg
}
if disk == "" {
// Something in the registry is better than nothing on disk.
return reg
}
if disk != def && disk != oldRegDef {
// The value in the registry is the old
// default (login.tailscale.com) but the value
// on disk is neither our old nor new default
// value, so it must be some custom thing that
// the user cares about. Prefer the disk value.
return disk
}
}
if disk != "" {
return disk
}
return def
}
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
func SetDebugLoggingEnabled(v bool) {
loggerx.SetDebugLoggingEnabled(v)
}