diff --git a/util/syspolicy/policy_keys.go b/util/syspolicy/policy_keys.go index 166bbe601..209356486 100644 --- a/util/syspolicy/policy_keys.go +++ b/util/syspolicy/policy_keys.go @@ -58,6 +58,9 @@ const ( // 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 @@ -84,4 +87,8 @@ const ( // 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" + + // 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" ) diff --git a/util/syspolicy/syspolicy_test.go b/util/syspolicy/syspolicy_test.go index 97707aeff..c2810ebbb 100644 --- a/util/syspolicy/syspolicy_test.go +++ b/util/syspolicy/syspolicy_test.go @@ -5,6 +5,7 @@ package syspolicy import ( "errors" + "slices" "testing" "time" ) @@ -408,6 +409,63 @@ func TestGetDuration(t *testing.T) { } } +func TestGetStringArray(t *testing.T) { + tests := []struct { + name string + key Key + handlerValue []string + handlerError error + defaultValue []string + wantValue []string + wantError error + }{ + { + name: "read existing value", + key: AllowedSuggestedExitNodes, + handlerValue: []string{"foo", "bar"}, + wantValue: []string{"foo", "bar"}, + }, + { + name: "read non-existing value", + key: AllowedSuggestedExitNodes, + handlerError: ErrNoSuchKey, + wantError: nil, + }, + { + name: "read non-existing value, non nil default", + key: AllowedSuggestedExitNodes, + handlerError: ErrNoSuchKey, + defaultValue: []string{"foo", "bar"}, + wantValue: []string{"foo", "bar"}, + wantError: nil, + }, + { + name: "reading value returns other error", + key: AllowedSuggestedExitNodes, + handlerError: someOtherError, + wantError: someOtherError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetHandlerForTest(t, &testHandler{ + t: t, + key: tt.key, + sArr: tt.handlerValue, + err: tt.handlerError, + }) + value, err := GetStringArray(tt.key, tt.defaultValue) + if err != tt.wantError { + t.Errorf("err=%q, want %q", err, tt.wantError) + } + if !slices.Equal(tt.wantValue, value) { + t.Errorf("value=%v, want %v", value, tt.wantValue) + } + }) + } +} + func TestSelectControlURL(t *testing.T) { tests := []struct { reg, disk, want string