-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathcondition-sets.e2e.spec.ts
More file actions
149 lines (130 loc) · 5.98 KB
/
Copy pathcondition-sets.e2e.spec.ts
File metadata and controls
149 lines (130 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import pino from 'pino';
import { IPermitClient } from '../../index';
import { createTestClient } from '../fixtures';
import { waitForCheck } from '../helpers/wait-for';
let permit: IPermitClient;
let logger: pino.Logger;
// Unique per-run suffix so concurrent/repeated runs never collide on the shared
// environment, and so the tolerant afterAll only ever touches what this spec made.
const rand = Math.random().toString(36).slice(2, 8);
const RESOURCE_KEY = `cs_e2e_doc_${rand}`;
const USERSET_KEY = `cs_e2e_userset_${rand}`;
const RESOURCESET_KEY = `cs_e2e_resourceset_${rand}`;
const MATCHING_USER_KEY = `cs_e2e_user_match_${rand}`;
const OTHER_USER_KEY = `cs_e2e_user_other_${rand}`;
// Built-in 'user' resource key in Permit. A userset condition that references
// user.<attr> requires the attribute to exist on this resource first.
const USER_RESOURCE_KEY = '__user';
const USER_ATTR = `cs_e2e_clearance_${rand}`;
const ACTION = 'read';
const PERMISSION = `${RESOURCE_KEY}:${ACTION}`;
const TENANT = 'default';
beforeAll(() => {
({ permit, logger } = createTestClient());
});
afterAll(async () => {
// Tolerant teardown in dependency order (rule -> condition sets -> users ->
// resource). Each delete swallows its own error so a partial run (the body
// threw before creating an entity) neither throws nor masks the original
// failure. The deletes cascade, then the assertions confirm nothing leaked.
await permit.api.conditionSetRules
.delete({ user_set: USERSET_KEY, permission: PERMISSION, resource_set: RESOURCESET_KEY })
.catch(() => undefined);
await permit.api.conditionSets.delete(USERSET_KEY).catch(() => undefined);
await permit.api.conditionSets.delete(RESOURCESET_KEY).catch(() => undefined);
// Remove the attribute from the built-in user resource after the userset that
// referenced it is gone. The built-in '__user' resource itself is never deleted.
await permit.api.resourceAttributes.delete(USER_RESOURCE_KEY, USER_ATTR).catch(() => undefined);
await permit.api.users.delete(MATCHING_USER_KEY).catch(() => undefined);
await permit.api.users.delete(OTHER_USER_KEY).catch(() => undefined);
await permit.api.resources.delete(RESOURCE_KEY).catch(() => undefined);
const sets = await permit.api.conditionSets.list();
expect(sets.some((set) => set.key === USERSET_KEY)).toBe(false);
expect(sets.some((set) => set.key === RESOURCESET_KEY)).toBe(false);
const resources = await permit.api.resources.list();
expect(resources.some((resource) => resource.key === RESOURCE_KEY)).toBe(false);
const users = (await permit.api.users.list()).data;
expect(users.some((user) => user.key === MATCHING_USER_KEY)).toBe(false);
expect(users.some((user) => user.key === OTHER_USER_KEY)).toBe(false);
});
it('ABAC condition-set permission check e2e test', async () => {
logger.info('creating an attributed resource for ABAC');
const resource = await permit.api.resources.create({
key: RESOURCE_KEY,
name: RESOURCE_KEY,
actions: { [ACTION]: {} },
attributes: {
confidential: {
type: 'bool',
description: 'whether the document is confidential',
},
},
});
expect(resource.key).toBe(RESOURCE_KEY);
expect((resource.actions ?? {})[ACTION]).not.toBe(undefined);
logger.info('registering the user attribute referenced by the userset condition');
const userAttribute = await permit.api.resourceAttributes.create(USER_RESOURCE_KEY, {
key: USER_ATTR,
type: 'string',
});
expect(userAttribute.key).toBe(USER_ATTR);
logger.info('creating the userset condition set (matches a user attribute)');
const userSet = await permit.api.conditionSets.create({
key: USERSET_KEY,
name: USERSET_KEY,
type: 'userset',
conditions: {
allOf: [{ [`user.${USER_ATTR}`]: { equals: 'top_secret' } }],
},
});
expect(userSet.key).toBe(USERSET_KEY);
expect(userSet.type).toBe('userset');
logger.info('creating the resourceset condition set (matches a resource attribute)');
const resourceSet = await permit.api.conditionSets.create({
key: RESOURCESET_KEY,
name: RESOURCESET_KEY,
type: 'resourceset',
resource_id: RESOURCE_KEY,
conditions: {
allOf: [{ 'resource.confidential': { equals: true } }],
},
});
expect(resourceSet.key).toBe(RESOURCESET_KEY);
expect(resourceSet.type).toBe('resourceset');
logger.info('linking the sets with a condition-set rule that grants the action');
const rule = await permit.api.conditionSetRules.create({
user_set: USERSET_KEY,
permission: PERMISSION,
resource_set: RESOURCESET_KEY,
});
expect(rule.user_set).toBe(USERSET_KEY);
expect(rule.resource_set).toBe(RESOURCESET_KEY);
expect(rule.permission).toBe(PERMISSION);
logger.info('syncing a user that matches the userset, and one that does not');
const { user: matchingUser } = await permit.api.users.sync({
key: MATCHING_USER_KEY,
attributes: { [USER_ATTR]: 'top_secret' },
});
expect(matchingUser.key).toBe(MATCHING_USER_KEY);
expect((matchingUser.attributes as Record<string, unknown>)[USER_ATTR]).toBe('top_secret');
const { user: otherUser } = await permit.api.users.sync({
key: OTHER_USER_KEY,
attributes: { [USER_ATTR]: 'public' },
});
expect(otherUser.key).toBe(OTHER_USER_KEY);
const confidentialResource = {
type: RESOURCE_KEY,
tenant: TENANT,
attributes: { confidential: true },
};
// Wait for the writes above to propagate from the control plane to the PDP.
// Condition sets compile to new policy (rego), which takes longer to take
// effect than plain role/fact propagation, so allow a wider budget.
await waitForCheck(() => permit.check(MATCHING_USER_KEY, ACTION, confidentialResource), true, {
timeoutMs: 180_000,
});
logger.info('positive ABAC check: matching user reads a confidential document');
expect(await permit.check(MATCHING_USER_KEY, ACTION, confidentialResource)).toBe(true);
logger.info('negative ABAC check: non-matching user is denied');
expect(await permit.check(OTHER_USER_KEY, ACTION, confidentialResource)).toBe(false);
});