You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/util/syspolicy/setting/policy_scope_test.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)
}
})
}
}