Files
kopia/internal/acl/acl_manager_test.go
2021-09-08 18:44:03 -07:00

363 lines
9.0 KiB
Go

package acl_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/internal/acl"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
const (
actualUser = "bob"
actualHostname = "home"
anotherHostname = "office"
actualUserAtHostname = actualUser + "@" + actualHostname
)
func TestEffectivePermissions(t *testing.T) {
cases := []struct {
entries []*acl.Entry
target map[string]string
want acl.AccessLevel
}{
// no rules
{
entries: []*acl.Entry{},
target: map[string]string{manifest.TypeLabelKey: snapshot.ManifestType},
want: acl.AccessLevelNone,
},
{
entries: []*acl.Entry{},
target: map[string]string{manifest.TypeLabelKey: acl.ContentManifestType},
want: acl.AccessLevelNone,
},
// multiple rules that match subject, highest access wins
{
entries: []*acl.Entry{
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUserAtHostname,
Access: acl.AccessLevelAppend,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUserAtHostname,
Access: acl.AccessLevelFull,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUserAtHostname,
Access: acl.AccessLevelRead,
},
},
target: map[string]string{manifest.TypeLabelKey: snapshot.ManifestType},
want: acl.AccessLevelFull,
},
{
entries: []*acl.Entry{
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUserAtHostname,
Access: acl.AccessLevelAppend,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: policy.ManifestType}, // matches another type
User: actualUserAtHostname,
Access: acl.AccessLevelFull,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUserAtHostname,
Access: acl.AccessLevelRead,
},
},
target: map[string]string{manifest.TypeLabelKey: snapshot.ManifestType},
want: acl.AccessLevelAppend,
},
{
entries: []*acl.Entry{
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: actualUser + "@*", // match any hostname using wildcard
Access: acl.AccessLevelAppend,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: anotherHostname, // matches another user
Access: acl.AccessLevelFull,
},
{
Target: acl.TargetRule{manifest.TypeLabelKey: snapshot.ManifestType},
User: "*@" + actualHostname, // match any username using wildcard
Access: acl.AccessLevelRead,
},
},
target: map[string]string{manifest.TypeLabelKey: snapshot.ManifestType},
want: acl.AccessLevelAppend,
},
{
entries: []*acl.Entry{
{
Target: acl.TargetRule{
manifest.TypeLabelKey: snapshot.ManifestType,
"foo": "bar",
"u": acl.OwnUser,
"h": acl.OwnHost,
},
User: "*@*", // match any user
Access: acl.AccessLevelAppend,
},
{
Target: acl.TargetRule{
manifest.TypeLabelKey: snapshot.ManifestType,
"foo": "bar",
"u": "another-user",
"h": acl.OwnHost,
},
User: "*@*", // match any user
Access: acl.AccessLevelAppend,
},
},
target: map[string]string{
manifest.TypeLabelKey: snapshot.ManifestType,
"foo": "bar",
"u": actualUser,
"h": actualHostname,
"extra": "aaa",
},
want: acl.AccessLevelAppend,
},
}
for _, tc := range cases {
if got := acl.EffectivePermissions(actualUser, actualHostname, tc.target, tc.entries); got != tc.want {
t.Errorf("invalid access level for entries: %#v and target %#v: %v, want %v", tc.entries, tc.target, got, tc.want)
}
// should get same results for filtered entries for the user
filteredEntries := acl.EntriesForUser(tc.entries, actualUser, actualHostname)
if got := acl.EffectivePermissions(actualUser, actualHostname, tc.target, filteredEntries); got != tc.want {
t.Errorf("invalid access level for entries: %#v and target %#v: %v, want %v", tc.entries, tc.target, got, tc.want)
}
}
}
func TestLoadEntries(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
// load from nil repository
entries, err := acl.LoadEntries(ctx, nil, nil)
require.NoError(t, err)
if got, want := len(entries), 0; got != want {
t.Fatalf("invalid number of entries %v, want %v", got, want)
}
entries, err = acl.LoadEntries(ctx, env.RepositoryWriter, nil)
require.NoError(t, err)
if got, want := len(entries), 0; got != want {
t.Fatalf("invalid number of entries %v, want %v", got, want)
}
e1 := &acl.Entry{
User: actualUserAtHostname,
Target: acl.TargetRule{
manifest.TypeLabelKey: acl.ContentManifestType,
},
Access: acl.AccessLevelFull,
}
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e1))
entries, err = acl.LoadEntries(ctx, env.RepositoryWriter, entries)
require.NoError(t, err)
if got, want := len(entries), 1; got != want {
t.Fatalf("invalid number of entries %v, want %v", got, want)
}
e2 := &acl.Entry{
User: actualUserAtHostname,
Target: acl.TargetRule{
manifest.TypeLabelKey: snapshot.ManifestType,
},
Access: acl.AccessLevelFull,
}
require.NoError(t, acl.AddACL(ctx, env.RepositoryWriter, e2))
entries, err = acl.LoadEntries(ctx, env.RepositoryWriter, entries)
require.NoError(t, err)
if got, want := len(entries), 2; got != want {
t.Fatalf("invalid number of entries %v, want %v", got, want)
}
}
func TestACLEntryValidation(t *testing.T) {
cases := []struct {
Entry *acl.Entry
WantErr string
}{
{
Entry: nil,
WantErr: "nil acl",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "foo",
"bar": "baz",
},
Access: acl.AccessLevelFull,
},
WantErr: "invalid 'type' label, must be one of: acl, content, policy, snapshot, user",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "snapshot",
"bar": "baz",
},
Access: acl.AccessLevelFull,
},
WantErr: "unsupported label 'bar' for type 'snapshot', must be one of: hostname, path, username",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "snapshot",
"hostname": "",
},
Access: acl.AccessLevelFull,
},
WantErr: "invalid label 'hostname=' for type 'snapshot': must be non-empty",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "policy",
"policyType": "blah",
},
Access: acl.AccessLevelFull,
},
WantErr: "invalid label 'policyType=blah' for type 'policy': must be one of: global, host, user, path",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "snapshot",
"hostname": "somehost",
"username": "someuser",
"path": "/",
},
Access: acl.AccessLevelFull,
},
WantErr: "",
},
{
// valid policy
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "policy",
"hostname": "somehost",
"username": "someuser",
"path": "/",
"policyType": "path",
},
Access: acl.AccessLevelFull,
},
WantErr: "",
},
{
// global policy
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "policy",
"policyType": "global",
},
Access: acl.AccessLevelFull,
},
WantErr: "",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
// ACL that gives access to set ACLs
"type": "acl",
},
Access: acl.AccessLevelFull,
},
WantErr: "",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
// ACL that gives access to set other user passwords
"type": "user",
"username": "user@host1",
},
Access: acl.AccessLevelFull,
},
WantErr: "",
},
{
Entry: &acl.Entry{
User: "foo@bar@baz",
Target: acl.TargetRule{
"type": "snapshot",
},
Access: acl.AccessLevelFull,
},
WantErr: "user must be 'username@hostname' possibly including wildcards",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"bar": "baz",
},
Access: acl.AccessLevelFull,
},
WantErr: "ACL target must have a 'type' label",
},
{
Entry: &acl.Entry{
User: "foo@bar",
Target: acl.TargetRule{
"type": "snapshot",
},
Access: -1,
},
WantErr: "valid access level must be specified",
},
}
for _, tc := range cases {
if err := tc.Entry.Validate(); err != nil {
if got, want := err.Error(), tc.WantErr; got != want {
t.Fatalf("invalid error %q, want %q", got, want)
}
} else if tc.WantErr != "" {
t.Fatalf("unexpected success, want %q", tc.WantErr)
}
}
}