mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 11:35:35 +00:00
67df9abdc6
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, and added in a preparation for the next PRs. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
566 lines
14 KiB
Go
566 lines
14 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package setting
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
jsonv2 "github.com/go-json-experiment/json"
|
|
)
|
|
|
|
func TestPolicyScopeIsApplicableSetting(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scope PolicyScope
|
|
setting *Definition
|
|
wantApplicable bool
|
|
}{
|
|
{
|
|
name: "DeviceScope/DeviceSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
{
|
|
name: "DeviceScope/ProfileSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantApplicable: false,
|
|
},
|
|
{
|
|
name: "DeviceScope/UserSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantApplicable: false,
|
|
},
|
|
{
|
|
name: "ProfileScope/DeviceSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
{
|
|
name: "ProfileScope/ProfileSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
{
|
|
name: "ProfileScope/UserSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantApplicable: false,
|
|
},
|
|
{
|
|
name: "UserScope/DeviceSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
{
|
|
name: "UserScope/ProfileSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
{
|
|
name: "UserScope/UserSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantApplicable: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotApplicable := tt.scope.IsApplicableSetting(tt.setting)
|
|
if gotApplicable != tt.wantApplicable {
|
|
t.Fatalf("got %v, want %v", gotApplicable, tt.wantApplicable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicyScopeIsConfigurableSetting(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scope PolicyScope
|
|
setting *Definition
|
|
wantConfigurable bool
|
|
}{
|
|
{
|
|
name: "DeviceScope/DeviceSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
{
|
|
name: "DeviceScope/ProfileSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
{
|
|
name: "DeviceScope/UserSetting",
|
|
scope: DeviceScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
{
|
|
name: "ProfileScope/DeviceSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantConfigurable: false,
|
|
},
|
|
{
|
|
name: "ProfileScope/ProfileSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
{
|
|
name: "ProfileScope/UserSetting",
|
|
scope: CurrentProfileScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
{
|
|
name: "UserScope/DeviceSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", DeviceSetting, IntegerValue),
|
|
wantConfigurable: false,
|
|
},
|
|
{
|
|
name: "UserScope/ProfileSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", ProfileSetting, IntegerValue),
|
|
wantConfigurable: false,
|
|
},
|
|
{
|
|
name: "UserScope/UserSetting",
|
|
scope: CurrentUserScope,
|
|
setting: NewDefinition("TestSetting", UserSetting, IntegerValue),
|
|
wantConfigurable: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotConfigurable := tt.scope.IsConfigurableSetting(tt.setting)
|
|
if gotConfigurable != tt.wantConfigurable {
|
|
t.Fatalf("got %v, want %v", gotConfigurable, tt.wantConfigurable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicyScopeContains(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scopeA PolicyScope
|
|
scopeB PolicyScope
|
|
wantAContainsB bool
|
|
wantAStrictlyContainsB bool
|
|
}{
|
|
{
|
|
name: "DeviceScope/DeviceScope",
|
|
scopeA: DeviceScope,
|
|
scopeB: DeviceScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "DeviceScope/CurrentProfileScope",
|
|
scopeA: DeviceScope,
|
|
scopeB: CurrentProfileScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: true,
|
|
},
|
|
{
|
|
name: "DeviceScope/UserScope",
|
|
scopeA: DeviceScope,
|
|
scopeB: CurrentUserScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: true,
|
|
},
|
|
{
|
|
name: "ProfileScope/DeviceScope",
|
|
scopeA: CurrentProfileScope,
|
|
scopeB: DeviceScope,
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "ProfileScope/ProfileScope",
|
|
scopeA: CurrentProfileScope,
|
|
scopeB: CurrentProfileScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "ProfileScope/UserScope",
|
|
scopeA: CurrentProfileScope,
|
|
scopeB: CurrentUserScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: true,
|
|
},
|
|
{
|
|
name: "UserScope/DeviceScope",
|
|
scopeA: CurrentUserScope,
|
|
scopeB: DeviceScope,
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "UserScope/ProfileScope",
|
|
scopeA: CurrentUserScope,
|
|
scopeB: CurrentProfileScope,
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "UserScope/UserScope",
|
|
scopeA: CurrentUserScope,
|
|
scopeB: CurrentUserScope,
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "UserScope(1234)/UserScope(1234)",
|
|
scopeA: UserScopeOf("1234"),
|
|
scopeB: UserScopeOf("1234"),
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "UserScope(1234)/UserScope(5678)",
|
|
scopeA: UserScopeOf("1234"),
|
|
scopeB: UserScopeOf("5678"),
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "ProfileScope(A)/UserScope(A/1234)",
|
|
scopeA: PolicyScope{kind: ProfileSetting, profileID: "A"},
|
|
scopeB: PolicyScope{kind: UserSetting, userID: "1234", profileID: "A"},
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: true,
|
|
},
|
|
{
|
|
name: "ProfileScope(A)/UserScope(B/1234)",
|
|
scopeA: PolicyScope{kind: ProfileSetting, profileID: "A"},
|
|
scopeB: PolicyScope{kind: UserSetting, userID: "1234", profileID: "B"},
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
{
|
|
name: "UserScope(1234)/UserScope(A/1234)",
|
|
scopeA: PolicyScope{kind: UserSetting, userID: "1234"},
|
|
scopeB: PolicyScope{kind: UserSetting, userID: "1234", profileID: "A"},
|
|
wantAContainsB: true,
|
|
wantAStrictlyContainsB: true,
|
|
},
|
|
{
|
|
name: "UserScope(1234)/UserScope(A/5678)",
|
|
scopeA: PolicyScope{kind: UserSetting, userID: "1234"},
|
|
scopeB: PolicyScope{kind: UserSetting, userID: "5678", profileID: "A"},
|
|
wantAContainsB: false,
|
|
wantAStrictlyContainsB: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotContains := tt.scopeA.Contains(tt.scopeB)
|
|
if gotContains != tt.wantAContainsB {
|
|
t.Fatalf("WithinOf: got %v, want %v", gotContains, tt.wantAContainsB)
|
|
}
|
|
|
|
gotStrictlyContains := tt.scopeA.StrictlyContains(tt.scopeB)
|
|
if gotStrictlyContains != tt.wantAStrictlyContainsB {
|
|
t.Fatalf("StrictlyWithinOf: got %v, want %v", gotStrictlyContains, tt.wantAStrictlyContainsB)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicyScopeMarshalUnmarshal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in any
|
|
wantJSON string
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "null-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{},
|
|
wantJSON: `{"Scope":"Device"}`,
|
|
},
|
|
{
|
|
name: "null-scope-omit-zero",
|
|
in: &struct {
|
|
Scope PolicyScope `json:",omitzero"`
|
|
}{},
|
|
wantJSON: `{}`,
|
|
},
|
|
{
|
|
name: "device-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{DeviceScope},
|
|
wantJSON: `{"Scope":"Device"}`,
|
|
},
|
|
{
|
|
name: "current-profile-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{CurrentProfileScope},
|
|
wantJSON: `{"Scope":"Profile"}`,
|
|
},
|
|
{
|
|
name: "current-user-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{CurrentUserScope},
|
|
wantJSON: `{"Scope":"User"}`,
|
|
},
|
|
{
|
|
name: "specific-user-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{UserScopeOf("_")},
|
|
wantJSON: `{"Scope":"User(_)"}`,
|
|
},
|
|
{
|
|
name: "specific-user-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{UserScopeOf("S-1-5-21-3698941153-1525015703-2649197413-1001")},
|
|
wantJSON: `{"Scope":"User(S-1-5-21-3698941153-1525015703-2649197413-1001)"}`,
|
|
},
|
|
{
|
|
name: "specific-profile-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{PolicyScope{kind: ProfileSetting, profileID: "1234"}},
|
|
wantJSON: `{"Scope":"Profile(1234)"}`,
|
|
},
|
|
{
|
|
name: "specific-profile-and-user-scope",
|
|
in: &struct {
|
|
Scope PolicyScope
|
|
}{PolicyScope{
|
|
kind: UserSetting,
|
|
profileID: "1234",
|
|
userID: "S-1-5-21-3698941153-1525015703-2649197413-1001",
|
|
}},
|
|
wantJSON: `{"Scope":"Profile(1234)/User(S-1-5-21-3698941153-1525015703-2649197413-1001)"}`,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotJSON, err := jsonv2.Marshal(tt.in)
|
|
if err != nil {
|
|
t.Fatalf("Marshal failed: %v", err)
|
|
}
|
|
if string(gotJSON) != tt.wantJSON {
|
|
t.Fatalf("Marshal got %s, want %s", gotJSON, tt.wantJSON)
|
|
}
|
|
wantBack := tt.in
|
|
gotBack := reflect.New(reflect.TypeOf(tt.in).Elem()).Interface()
|
|
err = jsonv2.Unmarshal(gotJSON, gotBack)
|
|
if err != nil {
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(gotBack, wantBack) {
|
|
t.Fatalf("Unmarshal got %+v, want %+v", gotBack, wantBack)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicyScopeUnmarshalSpecial(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
json string
|
|
want any
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "empty",
|
|
json: "{}",
|
|
want: &struct {
|
|
Scope PolicyScope
|
|
}{},
|
|
},
|
|
{
|
|
name: "too-many-scopes",
|
|
json: `{"Scope":"Device/Profile/User"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "user/profile", // incorrect order
|
|
json: `{"Scope":"User/Profile"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "profile-user-no-params",
|
|
json: `{"Scope":"Profile/User"}`,
|
|
want: &struct {
|
|
Scope PolicyScope
|
|
}{CurrentUserScope},
|
|
},
|
|
{
|
|
name: "unknown-scope",
|
|
json: `{"Scope":"Unknown"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "unknown-scope/unknown-scope",
|
|
json: `{"Scope":"Unknown/Unknown"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "device-scope/unknown-scope",
|
|
json: `{"Scope":"Device/Unknown"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "unknown-scope/device-scope",
|
|
json: `{"Scope":"Unknown/Device"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "slash",
|
|
json: `{"Scope":"/"}`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "empty",
|
|
json: `{"Scope": ""`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "no-closing-bracket",
|
|
json: `{"Scope": "user(1234"`,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "device-with-id",
|
|
json: `{"Scope": "device(123)"`,
|
|
wantError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := &struct {
|
|
Scope PolicyScope
|
|
}{}
|
|
err := jsonv2.Unmarshal([]byte(tt.json), got)
|
|
if (err != nil) != tt.wantError {
|
|
t.Errorf("Marshal error: got %v, want %v", err, tt.wantError)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Fatalf("Unmarshal got %+v, want %+v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestExtractScopeAndParams(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
scope string
|
|
params string
|
|
wantOk bool
|
|
}{
|
|
{
|
|
name: "empty",
|
|
s: "",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "scope-only",
|
|
s: "device",
|
|
scope: "device",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "scope-with-params",
|
|
s: "user(1234)",
|
|
scope: "user",
|
|
params: "1234",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "params-empty-scope",
|
|
s: "(1234)",
|
|
scope: "",
|
|
params: "1234",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "params-with-brackets",
|
|
s: "test()())))())",
|
|
scope: "test",
|
|
params: ")())))()",
|
|
wantOk: true,
|
|
},
|
|
{
|
|
name: "no-closing-bracket",
|
|
s: "user(1234",
|
|
scope: "",
|
|
params: "",
|
|
wantOk: false,
|
|
},
|
|
{
|
|
name: "open-before-close",
|
|
s: ")user(1234",
|
|
scope: "",
|
|
params: "",
|
|
wantOk: false,
|
|
},
|
|
{
|
|
name: "brackets-only",
|
|
s: ")(",
|
|
scope: "",
|
|
params: "",
|
|
wantOk: false,
|
|
},
|
|
{
|
|
name: "closing-bracket",
|
|
s: ")",
|
|
scope: "",
|
|
params: "",
|
|
wantOk: false,
|
|
},
|
|
{
|
|
name: "opening-bracket",
|
|
s: ")",
|
|
scope: "",
|
|
params: "",
|
|
wantOk: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
scope, params, ok := extractScopeAndParams(tt.s)
|
|
if ok != tt.wantOk {
|
|
t.Logf("OK: got %v; want %v", ok, tt.wantOk)
|
|
}
|
|
if scope != tt.scope {
|
|
t.Logf("Scope: got %q; want %q", scope, tt.scope)
|
|
}
|
|
if params != tt.params {
|
|
t.Logf("Params: got %v; want %v", params, tt.params)
|
|
}
|
|
})
|
|
}
|
|
}
|