1package consul
2
3import (
4	"github.com/hashicorp/consul/acl"
5	"github.com/hashicorp/consul/agent/structs"
6)
7
8type dirEntFilter struct {
9	authorizer acl.Authorizer
10	ent        structs.DirEntries
11}
12
13func (d *dirEntFilter) Len() int {
14	return len(d.ent)
15}
16func (d *dirEntFilter) Filter(i int) bool {
17	var entCtx acl.AuthorizerContext
18	d.ent[i].FillAuthzContext(&entCtx)
19
20	return d.authorizer.KeyRead(d.ent[i].Key, &entCtx) != acl.Allow
21}
22func (d *dirEntFilter) Move(dst, src, span int) {
23	copy(d.ent[dst:dst+span], d.ent[src:src+span])
24}
25
26// FilterDirEnt is used to filter a list of directory entries
27// by applying an ACL policy
28func FilterDirEnt(authorizer acl.Authorizer, ent structs.DirEntries) structs.DirEntries {
29	df := dirEntFilter{authorizer: authorizer, ent: ent}
30	return ent[:FilterEntries(&df)]
31}
32
33type txnResultsFilter struct {
34	authorizer acl.Authorizer
35	results    structs.TxnResults
36}
37
38func (t *txnResultsFilter) Len() int {
39	return len(t.results)
40}
41
42func (t *txnResultsFilter) Filter(i int) bool {
43	result := t.results[i]
44	var authzContext acl.AuthorizerContext
45	switch {
46	case result.KV != nil:
47		result.KV.EnterpriseMeta.FillAuthzContext(&authzContext)
48		return t.authorizer.KeyRead(result.KV.Key, &authzContext) != acl.Allow
49	case result.Node != nil:
50		structs.WildcardEnterpriseMeta().FillAuthzContext(&authzContext)
51		return t.authorizer.NodeRead(result.Node.Node, &authzContext) != acl.Allow
52	case result.Service != nil:
53		result.Service.EnterpriseMeta.FillAuthzContext(&authzContext)
54		return t.authorizer.ServiceRead(result.Service.Service, &authzContext) != acl.Allow
55	case result.Check != nil:
56		result.Check.EnterpriseMeta.FillAuthzContext(&authzContext)
57		if result.Check.ServiceName != "" {
58			return t.authorizer.ServiceRead(result.Check.ServiceName, &authzContext) != acl.Allow
59		}
60		return t.authorizer.NodeRead(result.Check.Node, &authzContext) != acl.Allow
61	}
62	return false
63}
64
65func (t *txnResultsFilter) Move(dst, src, span int) {
66	copy(t.results[dst:dst+span], t.results[src:src+span])
67}
68
69// FilterTxnResults is used to filter a list of transaction results by
70// applying an ACL policy.
71func FilterTxnResults(authorizer acl.Authorizer, results structs.TxnResults) structs.TxnResults {
72	rf := txnResultsFilter{authorizer: authorizer, results: results}
73	return results[:FilterEntries(&rf)]
74}
75
76// Filter interface is used with FilterEntries to do an
77// in-place filter of a slice.
78type Filter interface {
79	Len() int
80	Filter(int) bool
81	Move(dst, src, span int)
82}
83
84// FilterEntries is used to do an inplace filter of
85// a slice. This has cost proportional to the list length.
86func FilterEntries(f Filter) int {
87	// Compact the list
88	dst := 0
89	src := 0
90	n := f.Len()
91	for dst < n {
92		for src < n && f.Filter(src) {
93			src++
94		}
95		if src == n {
96			break
97		}
98		end := src + 1
99		for end < n && !f.Filter(end) {
100			end++
101		}
102		span := end - src
103		if span > 0 {
104			f.Move(dst, src, span)
105			dst += span
106			src += span
107		}
108	}
109
110	// Return the size of the slice
111	return dst
112}
113