1package state
2
3import (
4	"strconv"
5	"strings"
6	"testing"
7
8	"github.com/hashicorp/consul/agent/consul/stream"
9	"github.com/hashicorp/consul/agent/structs"
10	"github.com/stretchr/testify/require"
11)
12
13func TestACLChangeUnsubscribeEvent(t *testing.T) {
14	cases := []struct {
15		Name     string
16		Setup    func(tx *txn) error
17		Mutate   func(tx *txn) error
18		expected stream.Event
19	}{
20		{
21			Name: "token create",
22			Mutate: func(tx *txn) error {
23				return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{})
24			},
25			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
26		},
27		{
28			Name: "token update",
29			Setup: func(tx *txn) error {
30				return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{})
31			},
32			Mutate: func(tx *txn) error {
33				// Add a policy to the token (never mind it doesn't exist for now) we
34				// allow it in the set command below.
35				token := newACLToken(1)
36				token.Policies = []structs.ACLTokenPolicyLink{{ID: "33333333-1111-1111-1111-111111111111"}}
37				return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{AllowMissingPolicyAndRoleIDs: true})
38			},
39			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
40		},
41		{
42			Name: "token delete",
43			Setup: func(tx *txn) error {
44				return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{})
45			},
46			Mutate: func(tx *txn) error {
47				token := newACLToken(1)
48				return aclTokenDeleteTxn(tx, tx.Index, token.AccessorID, "id", nil)
49			},
50			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
51		},
52		{
53			Name:   "policy create",
54			Mutate: newACLPolicyWithSingleToken,
55			// two identical tokens, because Mutate has two changes
56			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)),
57		},
58		{
59			Name:  "policy update",
60			Setup: newACLPolicyWithSingleToken,
61			Mutate: func(tx *txn) error {
62				policy := newACLPolicy(1)
63				policy.Rules = `operator = "write"`
64				return aclPolicySetTxn(tx, tx.Index, policy)
65			},
66			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
67		},
68		{
69			Name:  "policy delete",
70			Setup: newACLPolicyWithSingleToken,
71			Mutate: func(tx *txn) error {
72				policy := newACLPolicy(1)
73				return aclPolicyDeleteTxn(tx, tx.Index, policy.ID, aclPolicyGetByID, nil)
74			},
75			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
76		},
77		{
78			Name:   "role create",
79			Mutate: newACLRoleWithSingleToken,
80			// Two tokens with the same ID, because there are two changes in Mutate
81			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)),
82		},
83		{
84			Name:  "role update",
85			Setup: newACLRoleWithSingleToken,
86			Mutate: func(tx *txn) error {
87				role := newACLRole(1, newACLRolePolicyLink(1))
88				policy2 := newACLPolicy(2)
89				role.Policies = append(role.Policies, structs.ACLRolePolicyLink{
90					ID:   policy2.ID,
91					Name: policy2.Name,
92				})
93				return aclRoleSetTxn(tx, tx.Index, role, true)
94			},
95			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
96		},
97		{
98			Name:  "role delete",
99			Setup: newACLRoleWithSingleToken,
100			Mutate: func(tx *txn) error {
101				role := newACLRole(1, newACLRolePolicyLink(1))
102				return aclRoleDeleteTxn(tx, tx.Index, role.ID, aclRoleGetByID, nil)
103			},
104			expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)),
105		},
106	}
107
108	for _, tc := range cases {
109		tc := tc
110		t.Run(tc.Name, func(t *testing.T) {
111			s := testStateStore(t)
112
113			if tc.Setup != nil {
114				// Bypass the publish mechanism for this test or we get into odd
115				// recursive stuff...
116				setupTx := s.db.WriteTxn(10)
117				require.NoError(t, tc.Setup(setupTx))
118				// Commit the underlying transaction without using wrapped Commit so we
119				// avoid the whole event publishing system for setup here. It _should_
120				// work but it makes debugging test hard as it will call the function
121				// under test for the setup data...
122				setupTx.Txn.Commit()
123			}
124
125			tx := s.db.WriteTxn(100)
126			require.NoError(t, tc.Mutate(tx))
127
128			// Note we call the func under test directly rather than publishChanges so
129			// we can test this in isolation.
130			events, err := aclChangeUnsubscribeEvent(tx, Changes{Index: 100, Changes: tx.Changes()})
131			require.NoError(t, err)
132
133			require.Len(t, events, 1)
134			actual := events[0]
135			require.Equal(t, tc.expected, actual)
136		})
137	}
138}
139
140func newACLRoleWithSingleToken(tx *txn) error {
141	role := newACLRole(1, newACLRolePolicyLink(1))
142	if err := aclRoleSetTxn(tx, tx.Index, role, true); err != nil {
143		return err
144	}
145	token := newACLToken(1)
146	token.Roles = append(token.Roles, structs.ACLTokenRoleLink{ID: role.ID})
147	return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{})
148}
149
150func newACLPolicyWithSingleToken(tx *txn) error {
151	policy := newACLPolicy(1)
152	if err := aclPolicySetTxn(tx, tx.Index, policy); err != nil {
153		return err
154	}
155	token := newACLToken(1)
156	token.Policies = append(token.Policies, structs.ACLTokenPolicyLink{ID: policy.ID})
157	return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{})
158}
159
160func newSecretIDs(ids ...int) []string {
161	result := make([]string, 0, len(ids))
162	for _, id := range ids {
163		uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(id))
164		result = append(result, uuid)
165	}
166	return result
167}
168
169func newACLToken(n int) *structs.ACLToken {
170	uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(n))
171	return &structs.ACLToken{
172		AccessorID: uuid,
173		SecretID:   uuid,
174	}
175}
176
177func newACLPolicy(n int) *structs.ACLPolicy {
178	numStr := strconv.Itoa(n)
179	uuid := strings.ReplaceAll("22222222-????-????-????-????????????", "?", numStr)
180	return &structs.ACLPolicy{
181		ID:    uuid,
182		Name:  "test_policy_" + numStr,
183		Rules: `operator = "read"`,
184	}
185}
186
187func newACLRole(n int, policies ...structs.ACLRolePolicyLink) *structs.ACLRole {
188	numStr := strconv.Itoa(n)
189	uuid := strings.ReplaceAll("33333333-????-????-????-????????????", "?", numStr)
190	return &structs.ACLRole{
191		ID:       uuid,
192		Name:     "test_role_" + numStr,
193		Policies: policies,
194	}
195}
196
197func newACLRolePolicyLink(n int) structs.ACLRolePolicyLink {
198	policy := newACLPolicy(n)
199	return structs.ACLRolePolicyLink{
200		ID:   policy.ID,
201		Name: policy.Name,
202	}
203}
204