mirror of
https://github.com/kopia/kopia.git
synced 2026-02-02 02:33:14 -05:00
363 lines
9.0 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|