diff --git a/consul/filter.go b/consul/filter.go index 9ccefc0797..5577aa47a0 100644 --- a/consul/filter.go +++ b/consul/filter.go @@ -5,35 +5,85 @@ import ( "github.com/hashicorp/consul/consul/structs" ) -func FilterDirEnt(acl acl.ACL, ent structs.DirEntries) structs.DirEntries { - // Remove any keys blocked by ACLs - removed := 0 - for i := 0; i < len(ent); i++ { - if !acl.KeyRead(ent[i].Key) { - ent[i] = nil - removed++ - } - } +type dirEntFilter struct { + acl acl.ACL + ent structs.DirEntries +} +func (d *dirEntFilter) Len() int { + return len(d.ent) +} +func (d *dirEntFilter) Filter(i int) bool { + return !d.acl.KeyRead(d.ent[i].Key) +} +func (d *dirEntFilter) Move(dst, src, span int) { + copy(d.ent[dst:dst+span], d.ent[src:src+span]) +} + +// FilterDirEnt is used to filter a list of directory entries +// by applying an ACL policy +func FilterDirEnt(acl acl.ACL, ent structs.DirEntries) structs.DirEntries { + df := dirEntFilter{acl: acl, ent: ent} + return ent[:FilterEntries(&df)] +} + +type keyFilter struct { + acl acl.ACL + keys []string +} + +func (k *keyFilter) Len() int { + return len(k.keys) +} +func (k *keyFilter) Filter(i int) bool { + return !k.acl.KeyRead(k.keys[i]) +} + +func (k *keyFilter) Move(dst, src, span int) { + copy(k.keys[dst:dst+span], k.keys[src:src+span]) +} + +// FilterKeys is used to filter a list of keys by +// applying an ACL policy +func FilterKeys(acl acl.ACL, keys []string) []string { + kf := keyFilter{acl: acl, keys: keys} + return keys[:FilterEntries(&kf)] +} + +// Filter interfae is used with FilterEntries to do an +// in-place filter of a slice. +type Filter interface { + Len() int + Filter(int) bool + Move(dst, src, span int) +} + +// FilterEntries is used to do an inplace filter of +// a slice. This has cost proportional to the list length. +func FilterEntries(f Filter) int { // Compact the list dst := 0 src := 0 - n := len(ent) - removed + n := f.Len() for dst < n { - for ent[src] == nil && src < n { + for src < n && f.Filter(src) { src++ } + if src == n { + break + } end := src + 1 - for ent[end] != nil && end < n { + for end < n && !f.Filter(end) { end++ } span := end - src - copy(ent[dst:dst+span], ent[src:src+span]) - dst += span - src += span + if span > 0 { + f.Move(dst, src, span) + dst += span + src += span + } } - // Trim the entries - ent = ent[:n] - return ent + // Return the size of the slice + return dst } diff --git a/consul/filter_test.go b/consul/filter_test.go index 99c0398a62..15feb1e686 100644 --- a/consul/filter_test.go +++ b/consul/filter_test.go @@ -49,6 +49,37 @@ func TestFilterDirEnt(t *testing.T) { } } +func TestKeys(t *testing.T) { + policy, _ := acl.Parse(testFilterRules) + aclR, _ := acl.New(acl.DenyAll(), policy) + + type tcase struct { + in []string + out []string + } + cases := []tcase{ + tcase{ + in: []string{"foo/test", "foo/priv/nope", "foo/other", "zoo"}, + out: []string{"foo/test", "foo/other"}, + }, + tcase{ + in: []string{"abe", "lincoln"}, + out: nil, + }, + tcase{ + in: []string{"abe", "foo/1", "foo/2", "foo/3", "nope"}, + out: []string{"foo/1", "foo/2", "foo/3"}, + }, + } + + for _, tc := range cases { + out := FilterKeys(aclR, tc.in) + if !reflect.DeepEqual(out, tc.out) { + t.Fatalf("bad: %#v %#v", out, tc.out) + } + } +} + var testFilterRules = ` key "" { policy = "deny"