@camunda/e2e-test-suite 0.0.1242 → 0.0.1244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fixtures/api.d.ts +15 -0
- package/dist/fixtures/api.js +27 -0
- package/dist/pages/SM-8.10/ModelerCreatePage.js +5 -4
- package/dist/tests/api/v2/authorization/authorization-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/authorization/authorization-api-tests.spec.js +247 -0
- package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.d.ts +18 -0
- package/dist/tests/api/v2/authorization/authorization-enforcement-api-tests.spec.js +208 -0
- package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/batch-operation/batch-operation-api-tests.spec.js +264 -0
- package/dist/tests/api/v2/cluster/cluster-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/cluster/cluster-api-tests.spec.js +86 -0
- package/dist/tests/api/v2/decision/decision-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/decision/decision-api-tests.spec.js +233 -0
- package/dist/tests/api/v2/deployment/deployment-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/deployment/deployment-api-tests.spec.js +147 -0
- package/dist/tests/api/v2/document/document-api-tests.spec.d.ts +9 -0
- package/dist/tests/api/v2/document/document-api-tests.spec.js +111 -0
- package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/element-instance/element-instance-api-tests.spec.js +127 -0
- package/dist/tests/api/v2/flows/end-to-end-flows.spec.d.ts +9 -0
- package/dist/tests/api/v2/flows/end-to-end-flows.spec.js +204 -0
- package/dist/tests/api/v2/form/form-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/form/form-api-tests.spec.js +88 -0
- package/dist/tests/api/v2/identity/group-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/group-api-tests.spec.js +172 -0
- package/dist/tests/api/v2/identity/membership-api-tests.spec.d.ts +10 -0
- package/dist/tests/api/v2/identity/membership-api-tests.spec.js +164 -0
- package/dist/tests/api/v2/identity/role-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/role-api-tests.spec.js +212 -0
- package/dist/tests/api/v2/identity/tenant-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/tenant-api-tests.spec.js +319 -0
- package/dist/tests/api/v2/identity/user-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/identity/user-api-tests.spec.js +148 -0
- package/dist/tests/api/v2/job/job-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/job/job-api-tests.spec.js +260 -0
- package/dist/tests/api/v2/message/message-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/message/message-api-tests.spec.js +219 -0
- package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/process-definition/process-definition-api-tests.spec.js +118 -0
- package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/process-instance/process-instance-api-tests.spec.js +335 -0
- package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/runtime/batch-modification-api-tests.spec.js +123 -0
- package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/runtime/variable-incident-api-tests.spec.js +174 -0
- package/dist/tests/api/v2/system/clock-api-tests.spec.d.ts +19 -0
- package/dist/tests/api/v2/system/clock-api-tests.spec.js +62 -0
- package/dist/tests/api/v2/system/system-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/system/system-api-tests.spec.js +183 -0
- package/dist/tests/api/v2/user-task/user-task-api-tests.spec.d.ts +1 -0
- package/dist/tests/api/v2/user-task/user-task-api-tests.spec.js +258 -0
- package/dist/utils/api/assertions.d.ts +50 -0
- package/dist/utils/api/assertions.js +142 -0
- package/dist/utils/api/auth.d.ts +27 -0
- package/dist/utils/api/auth.js +137 -0
- package/dist/utils/api/beans.d.ts +46 -0
- package/dist/utils/api/beans.js +87 -0
- package/dist/utils/api/bpmn.d.ts +50 -0
- package/dist/utils/api/bpmn.js +217 -0
- package/dist/utils/api/capabilities.d.ts +90 -0
- package/dist/utils/api/capabilities.js +198 -0
- package/dist/utils/api/cleanup.d.ts +24 -0
- package/dist/utils/api/cleanup.js +120 -0
- package/dist/utils/api/config.d.ts +103 -0
- package/dist/utils/api/config.js +62 -0
- package/dist/utils/api/deployments.d.ts +45 -0
- package/dist/utils/api/deployments.js +120 -0
- package/dist/utils/api/eventually.d.ts +36 -0
- package/dist/utils/api/eventually.js +54 -0
- package/dist/utils/api/http.d.ts +56 -0
- package/dist/utils/api/http.js +244 -0
- package/dist/utils/api/identity.d.ts +29 -0
- package/dist/utils/api/identity.js +43 -0
- package/dist/utils/api/keys.d.ts +37 -0
- package/dist/utils/api/keys.js +40 -0
- package/dist/utils/api/paths.d.ts +25 -0
- package/dist/utils/api/paths.js +167 -0
- package/dist/utils/api/tenancy.d.ts +31 -0
- package/dist/utils/api/tenancy.js +65 -0
- package/package.json +1 -1
- package/playwright.config.ts +14 -2
- package/dist/resources/test-api-v2-complete.sh +0 -12246
- package/resources/test-api-v2-complete.sh +0 -12246
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright fixture for the Orchestration Cluster REST v2 API suite.
|
|
3
|
+
*
|
|
4
|
+
* Provides an authenticated `api` client and a `resources` tracker whose
|
|
5
|
+
* contents are deleted after the worker finishes, so a spec never has to write
|
|
6
|
+
* its own teardown loop.
|
|
7
|
+
*/
|
|
8
|
+
import { ApiClient } from '../utils/api/http';
|
|
9
|
+
import { ResourceTracker } from '../utils/api/cleanup';
|
|
10
|
+
interface ApiFixtures {
|
|
11
|
+
api: ApiClient;
|
|
12
|
+
resources: ResourceTracker;
|
|
13
|
+
}
|
|
14
|
+
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & ApiFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
15
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Playwright fixture for the Orchestration Cluster REST v2 API suite.
|
|
4
|
+
*
|
|
5
|
+
* Provides an authenticated `api` client and a `resources` tracker whose
|
|
6
|
+
* contents are deleted after the worker finishes, so a spec never has to write
|
|
7
|
+
* its own teardown loop.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.expect = exports.test = void 0;
|
|
11
|
+
const test_1 = require("@playwright/test");
|
|
12
|
+
const http_1 = require("../utils/api/http");
|
|
13
|
+
const cleanup_1 = require("../utils/api/cleanup");
|
|
14
|
+
// Configuration is read once at module load, so specs that need it import
|
|
15
|
+
// `apiConfig` directly rather than taking it as a fixture.
|
|
16
|
+
exports.test = test_1.test.extend({
|
|
17
|
+
api: async ({ request }, use) => {
|
|
18
|
+
await use(new http_1.ApiClient(request));
|
|
19
|
+
},
|
|
20
|
+
resources: async ({ api }, use) => {
|
|
21
|
+
const tracker = new cleanup_1.ResourceTracker();
|
|
22
|
+
await use(tracker);
|
|
23
|
+
await tracker.cleanup(api);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
var test_2 = require("@playwright/test");
|
|
27
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
|
|
@@ -341,10 +341,11 @@ class ModelerCreatePage {
|
|
|
341
341
|
// substring match is case-insensitive -- hence the strict-mode violation.
|
|
342
342
|
// Key on the sidebar toggle's data-test plus its state-dependent title so
|
|
343
343
|
// the locator stays unambiguous and still distinguishes collapse from
|
|
344
|
-
// expand
|
|
345
|
-
// design-system toggle
|
|
346
|
-
|
|
347
|
-
this.
|
|
344
|
+
// expand. The title is matched case-insensitively (`i`) because the
|
|
345
|
+
// design-system toggle renders "Close details panel" while the legacy one
|
|
346
|
+
// renders "Close Details Panel".
|
|
347
|
+
this.closeDetailsPanel = page.locator('[data-test="details-panel-toggle"][title="Close Details Panel" i]');
|
|
348
|
+
this.openDetailsPanel = page.locator('[data-test="details-panel-toggle"][title="Open Details Panel" i]');
|
|
348
349
|
}
|
|
349
350
|
async deployProcessInstance(tenant = '') {
|
|
350
351
|
await this.clickDeployButton();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const api_1 = require("../../../../fixtures/api");
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const assertions_1 = require("../../../../utils/api/assertions");
|
|
6
|
+
const beans_1 = require("../../../../utils/api/beans");
|
|
7
|
+
const eventually_1 = require("../../../../utils/api/eventually");
|
|
8
|
+
const bpmn_1 = require("../../../../utils/api/bpmn");
|
|
9
|
+
const deployments_1 = require("../../../../utils/api/deployments");
|
|
10
|
+
const identity_1 = require("../../../../utils/api/identity");
|
|
11
|
+
const keys_1 = require("../../../../utils/api/keys");
|
|
12
|
+
const capabilities_1 = require("../../../../utils/api/capabilities");
|
|
13
|
+
async function createAuthorization(api, resources, body) {
|
|
14
|
+
const response = await api.post('/authorizations', { body, tenant: 'none' });
|
|
15
|
+
// The shell suite expected `201 409` throughout this section, so a run
|
|
16
|
+
// where every authorization already existed — or where creation kept
|
|
17
|
+
// conflicting — was indistinguishable from one where it worked.
|
|
18
|
+
const created = (await (0, assertions_1.assertCreated)(response));
|
|
19
|
+
const key = String(created.authorizationKey);
|
|
20
|
+
resources.track('authorization', key);
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
api_1.test.describe('Authorizations', () => {
|
|
24
|
+
(0, api_1.test)('creates an authorization for a group owner', async ({ api, resources, }) => {
|
|
25
|
+
const group = (0, beans_1.newGroup)();
|
|
26
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
27
|
+
resources.track('group', group.groupId);
|
|
28
|
+
const processId = (0, bpmn_1.uniqueId)('authz-process');
|
|
29
|
+
const deployed = await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
|
|
30
|
+
const key = await createAuthorization(api, resources, {
|
|
31
|
+
ownerType: 'GROUP',
|
|
32
|
+
ownerId: group.groupId,
|
|
33
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
34
|
+
resourceId: deployed.processDefinitionId,
|
|
35
|
+
permissionTypes: ['READ_PROCESS_INSTANCE'],
|
|
36
|
+
});
|
|
37
|
+
await (0, eventually_1.eventually)(async () => {
|
|
38
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
39
|
+
params: { authorizationKey: key },
|
|
40
|
+
tenant: 'none',
|
|
41
|
+
});
|
|
42
|
+
const json = (await (0, assertions_1.assertOk)(response));
|
|
43
|
+
(0, assertions_1.assertRequiredFields)(json, [
|
|
44
|
+
'authorizationKey',
|
|
45
|
+
'ownerId',
|
|
46
|
+
'ownerType',
|
|
47
|
+
'resourceType',
|
|
48
|
+
'permissionTypes',
|
|
49
|
+
]);
|
|
50
|
+
(0, test_1.expect)(json.ownerId).toBe(group.groupId);
|
|
51
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(['READ_PROCESS_INSTANCE']);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
(0, api_1.test)('creates an authorization carrying several permissions', async ({ api, resources, }) => {
|
|
55
|
+
const group = (0, beans_1.newGroup)();
|
|
56
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
57
|
+
resources.track('group', group.groupId);
|
|
58
|
+
const key = await createAuthorization(api, resources, {
|
|
59
|
+
ownerType: 'GROUP',
|
|
60
|
+
ownerId: group.groupId,
|
|
61
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
62
|
+
resourceId: '*',
|
|
63
|
+
permissionTypes: ['READ_PROCESS_DEFINITION', 'READ_PROCESS_INSTANCE'],
|
|
64
|
+
});
|
|
65
|
+
await (0, eventually_1.eventually)(async () => {
|
|
66
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
67
|
+
params: { authorizationKey: key },
|
|
68
|
+
tenant: 'none',
|
|
69
|
+
});
|
|
70
|
+
const json = (await (0, assertions_1.assertOk)(response));
|
|
71
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(test_1.expect.arrayContaining([
|
|
72
|
+
'READ_PROCESS_DEFINITION',
|
|
73
|
+
'READ_PROCESS_INSTANCE',
|
|
74
|
+
]));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
(0, api_1.test)('creates an authorization for a user owner', async ({ api, resources, }) => {
|
|
78
|
+
const username = await (0, identity_1.provisionUsername)(api, resources);
|
|
79
|
+
const key = await createAuthorization(api, resources, {
|
|
80
|
+
ownerType: 'USER',
|
|
81
|
+
ownerId: username,
|
|
82
|
+
resourceType: 'DECISION_DEFINITION',
|
|
83
|
+
resourceId: '*',
|
|
84
|
+
permissionTypes: ['READ_DECISION_DEFINITION'],
|
|
85
|
+
});
|
|
86
|
+
(0, test_1.expect)(key).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
(0, api_1.test)('finds an authorization by owner', async ({ api, resources }) => {
|
|
89
|
+
const group = (0, beans_1.newGroup)();
|
|
90
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
91
|
+
resources.track('group', group.groupId);
|
|
92
|
+
await createAuthorization(api, resources, {
|
|
93
|
+
ownerType: 'GROUP',
|
|
94
|
+
ownerId: group.groupId,
|
|
95
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
96
|
+
resourceId: '*',
|
|
97
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
98
|
+
});
|
|
99
|
+
await (0, eventually_1.eventually)(async () => {
|
|
100
|
+
const response = await api.post('/authorizations/search', {
|
|
101
|
+
body: { filter: { ownerId: group.groupId }, page: { limit: 10 } },
|
|
102
|
+
tenant: 'none',
|
|
103
|
+
});
|
|
104
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
105
|
+
for (const item of page.items) {
|
|
106
|
+
(0, test_1.expect)(item.ownerId).toBe(group.groupId);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
(0, api_1.test)('filters an authorization search by owner type', async ({ api, resources, }) => {
|
|
111
|
+
const group = (0, beans_1.newGroup)();
|
|
112
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
113
|
+
resources.track('group', group.groupId);
|
|
114
|
+
await createAuthorization(api, resources, {
|
|
115
|
+
ownerType: 'GROUP',
|
|
116
|
+
ownerId: group.groupId,
|
|
117
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
118
|
+
resourceId: '*',
|
|
119
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
120
|
+
});
|
|
121
|
+
await (0, eventually_1.eventually)(async () => {
|
|
122
|
+
const response = await api.post('/authorizations/search', {
|
|
123
|
+
body: { filter: { ownerType: 'GROUP' }, page: { limit: 50 } },
|
|
124
|
+
tenant: 'none',
|
|
125
|
+
});
|
|
126
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
127
|
+
for (const item of page.items) {
|
|
128
|
+
(0, test_1.expect)(item.ownerType).toBe('GROUP');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
(0, api_1.test)('updates the permissions on an authorization', async ({ api, resources, }) => {
|
|
133
|
+
const group = (0, beans_1.newGroup)();
|
|
134
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
135
|
+
resources.track('group', group.groupId);
|
|
136
|
+
const key = await createAuthorization(api, resources, {
|
|
137
|
+
ownerType: 'GROUP',
|
|
138
|
+
ownerId: group.groupId,
|
|
139
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
140
|
+
resourceId: '*',
|
|
141
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
142
|
+
});
|
|
143
|
+
const response = await api.put('/authorizations/{authorizationKey}', {
|
|
144
|
+
params: { authorizationKey: key },
|
|
145
|
+
body: {
|
|
146
|
+
ownerType: 'GROUP',
|
|
147
|
+
ownerId: group.groupId,
|
|
148
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
149
|
+
resourceId: '*',
|
|
150
|
+
permissionTypes: ['READ_PROCESS_DEFINITION', 'UPDATE_PROCESS_INSTANCE'],
|
|
151
|
+
},
|
|
152
|
+
tenant: 'none',
|
|
153
|
+
});
|
|
154
|
+
await (0, assertions_1.assertStatusIn)(response, [204], 'the update is acknowledged');
|
|
155
|
+
await (0, eventually_1.eventually)(async () => {
|
|
156
|
+
const lookup = await api.get('/authorizations/{authorizationKey}', {
|
|
157
|
+
params: { authorizationKey: key },
|
|
158
|
+
tenant: 'none',
|
|
159
|
+
});
|
|
160
|
+
const json = (await (0, assertions_1.assertOk)(lookup));
|
|
161
|
+
(0, test_1.expect)(json.permissionTypes).toEqual(test_1.expect.arrayContaining(['UPDATE_PROCESS_INSTANCE']));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
(0, api_1.test)('deletes an authorization', async ({ api, resources }) => {
|
|
165
|
+
const group = (0, beans_1.newGroup)();
|
|
166
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
167
|
+
resources.track('group', group.groupId);
|
|
168
|
+
const key = await createAuthorization(api, resources, {
|
|
169
|
+
ownerType: 'GROUP',
|
|
170
|
+
ownerId: group.groupId,
|
|
171
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
172
|
+
resourceId: '*',
|
|
173
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
174
|
+
});
|
|
175
|
+
const response = await api.delete('/authorizations/{authorizationKey}', {
|
|
176
|
+
params: { authorizationKey: key },
|
|
177
|
+
tenant: 'none',
|
|
178
|
+
});
|
|
179
|
+
await (0, assertions_1.assertStatusIn)(response, [204], 'deletion is acknowledged');
|
|
180
|
+
resources.forget('authorization', key);
|
|
181
|
+
await (0, eventually_1.eventually)(async () => {
|
|
182
|
+
const lookup = await api.get('/authorizations/{authorizationKey}', {
|
|
183
|
+
params: { authorizationKey: key },
|
|
184
|
+
tenant: 'none',
|
|
185
|
+
});
|
|
186
|
+
await (0, assertions_1.assertNotFound)(lookup);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
(0, api_1.test)('rejects an authorization with no permission types', async ({ api, resources, }) => {
|
|
190
|
+
const group = (0, beans_1.newGroup)();
|
|
191
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
192
|
+
resources.track('group', group.groupId);
|
|
193
|
+
const response = await api.post('/authorizations', {
|
|
194
|
+
body: {
|
|
195
|
+
ownerType: 'GROUP',
|
|
196
|
+
ownerId: group.groupId,
|
|
197
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
198
|
+
resourceId: '*',
|
|
199
|
+
permissionTypes: [],
|
|
200
|
+
},
|
|
201
|
+
tenant: 'none',
|
|
202
|
+
});
|
|
203
|
+
await (0, assertions_1.assertBadRequest)(response);
|
|
204
|
+
});
|
|
205
|
+
(0, api_1.test)('rejects an authorization with an unknown resource type', async ({ api, resources, }) => {
|
|
206
|
+
const group = (0, beans_1.newGroup)();
|
|
207
|
+
await (0, assertions_1.assertCreated)(await api.post('/groups', { body: group, tenant: 'none' }));
|
|
208
|
+
resources.track('group', group.groupId);
|
|
209
|
+
const response = await api.post('/authorizations', {
|
|
210
|
+
body: {
|
|
211
|
+
ownerType: 'GROUP',
|
|
212
|
+
ownerId: group.groupId,
|
|
213
|
+
resourceType: 'NOT_A_RESOURCE_TYPE',
|
|
214
|
+
resourceId: '*',
|
|
215
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
216
|
+
},
|
|
217
|
+
tenant: 'none',
|
|
218
|
+
});
|
|
219
|
+
await (0, assertions_1.assertBadRequest)(response);
|
|
220
|
+
});
|
|
221
|
+
(0, api_1.test)('rejects a lookup for an absent authorization', async ({ api }) => {
|
|
222
|
+
const response = await api.get('/authorizations/{authorizationKey}', {
|
|
223
|
+
params: { authorizationKey: keys_1.ABSENT_KEY },
|
|
224
|
+
tenant: 'none',
|
|
225
|
+
});
|
|
226
|
+
await (0, assertions_1.assertNotFound)(response);
|
|
227
|
+
});
|
|
228
|
+
(0, api_1.test)('rejects an unauthenticated authorization search', async ({ api }) => {
|
|
229
|
+
api_1.test.skip(capabilities_1.unauthenticatedAccessAllowed !== false, capabilities_1.unauthenticatedAccessAllowed === false
|
|
230
|
+
? ''
|
|
231
|
+
: capabilities_1.unauthenticatedAccessAllowed);
|
|
232
|
+
const response = await api.post('/authorizations/search', {
|
|
233
|
+
body: { filter: {} },
|
|
234
|
+
tenant: 'none',
|
|
235
|
+
authorization: null,
|
|
236
|
+
});
|
|
237
|
+
await (0, assertions_1.assertUnauthorized)(response);
|
|
238
|
+
});
|
|
239
|
+
(0, api_1.test)('honours the page size on an authorization search', async ({ api }) => {
|
|
240
|
+
const response = await api.post('/authorizations/search', {
|
|
241
|
+
body: { filter: {}, page: { limit: 1 } },
|
|
242
|
+
tenant: 'none',
|
|
243
|
+
});
|
|
244
|
+
const page = await (0, assertions_1.assertPaginated)(response, { minItems: 1 });
|
|
245
|
+
(0, test_1.expect)(page.items.length).toBe(1);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization *enforcement* — that the cluster actually refuses an
|
|
3
|
+
* unprivileged principal, not merely that the authorization API accepts writes.
|
|
4
|
+
*
|
|
5
|
+
* This is the coverage a Helm-upgrade run most needs, because role bindings are
|
|
6
|
+
* what an upgrade is most likely to lose. In the shell suite it was 2,700 lines
|
|
7
|
+
* (9506-12224) inside a single `if [[ "$TEST_CLIENT_TOKEN_VALID" ]]` block: if
|
|
8
|
+
* the token fetch failed, the entire section silently did not run and the script
|
|
9
|
+
* still exited 0.
|
|
10
|
+
*
|
|
11
|
+
* Two things change here. The gate is a hard failure rather than a silent skip,
|
|
12
|
+
* so "enforcement was never checked" can no longer look like success. And the
|
|
13
|
+
* unprivileged principal is a user this spec creates, rather than the suite's own
|
|
14
|
+
* admin client with its permissions temporarily stripped and restored — that
|
|
15
|
+
* approach mutated cluster-wide admin state mid-run, so a failure between the
|
|
16
|
+
* strip and the restore left the rest of the suite running without permissions.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Authorization *enforcement* — that the cluster actually refuses an
|
|
4
|
+
* unprivileged principal, not merely that the authorization API accepts writes.
|
|
5
|
+
*
|
|
6
|
+
* This is the coverage a Helm-upgrade run most needs, because role bindings are
|
|
7
|
+
* what an upgrade is most likely to lose. In the shell suite it was 2,700 lines
|
|
8
|
+
* (9506-12224) inside a single `if [[ "$TEST_CLIENT_TOKEN_VALID" ]]` block: if
|
|
9
|
+
* the token fetch failed, the entire section silently did not run and the script
|
|
10
|
+
* still exited 0.
|
|
11
|
+
*
|
|
12
|
+
* Two things change here. The gate is a hard failure rather than a silent skip,
|
|
13
|
+
* so "enforcement was never checked" can no longer look like success. And the
|
|
14
|
+
* unprivileged principal is a user this spec creates, rather than the suite's own
|
|
15
|
+
* admin client with its permissions temporarily stripped and restored — that
|
|
16
|
+
* approach mutated cluster-wide admin state mid-run, so a failure between the
|
|
17
|
+
* strip and the restore left the rest of the suite running without permissions.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
const api_1 = require("../../../../fixtures/api");
|
|
21
|
+
const test_1 = require("@playwright/test");
|
|
22
|
+
const assertions_1 = require("../../../../utils/api/assertions");
|
|
23
|
+
const beans_1 = require("../../../../utils/api/beans");
|
|
24
|
+
const config_1 = require("../../../../utils/api/config");
|
|
25
|
+
const auth_1 = require("../../../../utils/api/auth");
|
|
26
|
+
const capabilities_1 = require("../../../../utils/api/capabilities");
|
|
27
|
+
const eventually_1 = require("../../../../utils/api/eventually");
|
|
28
|
+
const bpmn_1 = require("../../../../utils/api/bpmn");
|
|
29
|
+
const deployments_1 = require("../../../../utils/api/deployments");
|
|
30
|
+
/**
|
|
31
|
+
* Obtains a principal that holds no roles, by whichever route the cluster
|
|
32
|
+
* allows.
|
|
33
|
+
*
|
|
34
|
+
* Throws when neither route works. That is deliberate: without a second
|
|
35
|
+
* principal there is no way to observe enforcement, and a suite that quietly
|
|
36
|
+
* reports success in that state is worse than one that fails. The tests are
|
|
37
|
+
* gated on `enforcementPrincipalUnavailable` before they get here, so a throw
|
|
38
|
+
* means a configured route broke rather than that none was configured.
|
|
39
|
+
*/
|
|
40
|
+
async function unprivilegedPrincipal(api, request, resources) {
|
|
41
|
+
// OIDC cluster: act as a second identity-provider client. It must hold no
|
|
42
|
+
// roles for the assertions below to mean anything, which is a property of
|
|
43
|
+
// how it was provisioned rather than something this suite can enforce.
|
|
44
|
+
if (!config_1.apiConfig.usersApiEnabled) {
|
|
45
|
+
const { clientId, clientSecret } = config_1.apiConfig.secondaryClient;
|
|
46
|
+
const header = await (0, auth_1.clientAuthHeader)(request, clientId, clientSecret);
|
|
47
|
+
if (!header) {
|
|
48
|
+
throw new Error(`Could not obtain a token for the secondary client "${clientId}" from ` +
|
|
49
|
+
`${config_1.apiConfig.oauth.tokenUrl}. Enforcement cannot be observed without a ` +
|
|
50
|
+
`second principal, so this is a failure rather than a skip. Check that ` +
|
|
51
|
+
`SECONDARY_CLIENT_SECRET matches the client and that client credentials ` +
|
|
52
|
+
`are enabled for it.`);
|
|
53
|
+
}
|
|
54
|
+
return { ownerId: clientId, ownerType: 'CLIENT', authorization: header };
|
|
55
|
+
}
|
|
56
|
+
const user = (0, beans_1.newUser)();
|
|
57
|
+
await (0, assertions_1.assertCreated)(await api.post('/users', { body: user, tenant: 'none' }));
|
|
58
|
+
resources.track('user', user.username);
|
|
59
|
+
if (config_1.apiConfig.authMethod === 'basic') {
|
|
60
|
+
const encoded = Buffer.from(`${user.username}:${user.password}`).toString('base64');
|
|
61
|
+
return {
|
|
62
|
+
ownerId: user.username,
|
|
63
|
+
ownerType: 'USER',
|
|
64
|
+
authorization: `Basic ${encoded}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const header = await (0, auth_1.userAuthHeader)(request, user.username, user.password);
|
|
68
|
+
if (!header) {
|
|
69
|
+
throw new Error(`Could not obtain a token for the freshly created user "${user.username}" ` +
|
|
70
|
+
`from ${config_1.apiConfig.oauth.tokenUrl}. Enforcement cannot be observed without a ` +
|
|
71
|
+
`second principal, so this is a failure rather than a skip. Check that the ` +
|
|
72
|
+
`"${config_1.apiConfig.oauth.clientId}" client has direct access grants enabled and that ` +
|
|
73
|
+
`users created through /v2/users are resolvable by the identity provider.`);
|
|
74
|
+
}
|
|
75
|
+
return { ownerId: user.username, ownerType: 'USER', authorization: header };
|
|
76
|
+
}
|
|
77
|
+
api_1.test.describe('Authorization enforcement', () => {
|
|
78
|
+
// Without a second, unprivileged principal there is nothing to enforce
|
|
79
|
+
// against. The reason names what to configure so this reads as a gap to
|
|
80
|
+
// close rather than a test that mysteriously does not run.
|
|
81
|
+
api_1.test.skip(capabilities_1.enforcementPrincipalUnavailable !== false, capabilities_1.enforcementPrincipalUnavailable === false
|
|
82
|
+
? ''
|
|
83
|
+
: capabilities_1.enforcementPrincipalUnavailable);
|
|
84
|
+
(0, api_1.test)('refuses a process-definition search to an unprivileged user', async ({ api, request, resources, }) => {
|
|
85
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
86
|
+
const response = await api.post('/process-definitions/search', {
|
|
87
|
+
body: { filter: {} },
|
|
88
|
+
authorization: principal.authorization,
|
|
89
|
+
});
|
|
90
|
+
// A cluster that has lost its authorization wiring answers 200 here.
|
|
91
|
+
await (0, assertions_1.assertStatusIn)(response, [200, 403], 'an unprivileged principal is refused outright, or served an empty result set');
|
|
92
|
+
if (response.status() === 200) {
|
|
93
|
+
// Where the API filters rather than refuses, the result must be empty —
|
|
94
|
+
// seeing another principal's definitions is the same defect as a 200 on
|
|
95
|
+
// a forbidden call.
|
|
96
|
+
await (0, assertions_1.assertPaginated)(response, { exactItems: 0 });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
(0, api_1.test)('refuses a user list to an unprivileged user', async ({ api, request, resources, }) => {
|
|
100
|
+
// In OIDC mode /v2/users/** is closed to everyone, so a 403 here would
|
|
101
|
+
// come from the endpoint filter rather than from the authorization check
|
|
102
|
+
// this test is about — it would pass without enforcement working at all.
|
|
103
|
+
api_1.test.skip(capabilities_1.usersApiUnavailable !== false, capabilities_1.usersApiUnavailable === false ? '' : capabilities_1.usersApiUnavailable);
|
|
104
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
105
|
+
const response = await api.post('/users/search', {
|
|
106
|
+
body: { filter: {} },
|
|
107
|
+
tenant: 'none',
|
|
108
|
+
authorization: principal.authorization,
|
|
109
|
+
});
|
|
110
|
+
await (0, assertions_1.assertStatusIn)(response, [200, 403], 'an unprivileged principal is refused outright, or served an empty result set');
|
|
111
|
+
if (response.status() === 200) {
|
|
112
|
+
await (0, assertions_1.assertPaginated)(response, { exactItems: 0 });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
(0, api_1.test)('refuses a deployment to an unprivileged user', async ({ api, request, resources, }) => {
|
|
116
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
117
|
+
const processId = (0, bpmn_1.uniqueId)('enforcement-deploy');
|
|
118
|
+
const response = await api.post('/deployments', {
|
|
119
|
+
tenant: 'none',
|
|
120
|
+
authorization: principal.authorization,
|
|
121
|
+
multipart: {
|
|
122
|
+
resources: {
|
|
123
|
+
name: `${processId}.bpmn`,
|
|
124
|
+
mimeType: 'application/xml',
|
|
125
|
+
buffer: Buffer.from((0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))), 'utf-8'),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
await (0, assertions_1.assertForbidden)(response);
|
|
130
|
+
// Backcheck: the refusal must also mean nothing was created. The shell
|
|
131
|
+
// suite called this step "BACKCHECK" and it is the part worth keeping.
|
|
132
|
+
await (0, eventually_1.eventually)(async () => {
|
|
133
|
+
const search = await api.post('/process-definitions/search', {
|
|
134
|
+
body: { filter: { processDefinitionId: processId } },
|
|
135
|
+
});
|
|
136
|
+
await (0, assertions_1.assertPaginated)(search, { exactItems: 0 });
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
(0, api_1.test)('refuses role creation to an unprivileged user', async ({ api, request, resources, }) => {
|
|
140
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
141
|
+
const roleId = (0, bpmn_1.uniqueId)('enforcement-role');
|
|
142
|
+
const response = await api.post('/roles', {
|
|
143
|
+
body: { roleId, name: 'should not be created' },
|
|
144
|
+
tenant: 'none',
|
|
145
|
+
authorization: principal.authorization,
|
|
146
|
+
});
|
|
147
|
+
await (0, assertions_1.assertForbidden)(response);
|
|
148
|
+
const lookup = await api.get('/roles/{roleId}', {
|
|
149
|
+
params: { roleId },
|
|
150
|
+
tenant: 'none',
|
|
151
|
+
});
|
|
152
|
+
(0, test_1.expect)(lookup.status(), 'a refused role creation must leave no role behind').toBe(404);
|
|
153
|
+
});
|
|
154
|
+
(0, api_1.test)('refuses starting a process instance to an unprivileged user', async ({ api, request, resources, }) => {
|
|
155
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
156
|
+
const processId = (0, bpmn_1.uniqueId)('enforcement-start');
|
|
157
|
+
await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
|
|
158
|
+
const response = await api.post('/process-instances', {
|
|
159
|
+
body: { processDefinitionId: processId },
|
|
160
|
+
tenant: 'root',
|
|
161
|
+
authorization: principal.authorization,
|
|
162
|
+
});
|
|
163
|
+
await (0, assertions_1.assertForbidden)(response);
|
|
164
|
+
await (0, eventually_1.eventually)(async () => {
|
|
165
|
+
const search = await api.post('/process-instances/search', {
|
|
166
|
+
body: { filter: { processDefinitionId: processId } },
|
|
167
|
+
});
|
|
168
|
+
await (0, assertions_1.assertPaginated)(search, { exactItems: 0 });
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
(0, api_1.test)('admits a user once granted the matching authorization', async ({ api, request, resources, }) => {
|
|
172
|
+
const principal = await unprivilegedPrincipal(api, request, resources);
|
|
173
|
+
const processId = (0, bpmn_1.uniqueId)('enforcement-grant');
|
|
174
|
+
await (0, deployments_1.deployProcess)(api, resources, processId, (0, bpmn_1.linearProcess)(processId, bpmn_1.element.serviceTask((0, bpmn_1.uniqueId)('job'))));
|
|
175
|
+
// Refused before the grant.
|
|
176
|
+
const before = await api.post('/process-definitions/search', {
|
|
177
|
+
body: { filter: { processDefinitionId: processId } },
|
|
178
|
+
authorization: principal.authorization,
|
|
179
|
+
});
|
|
180
|
+
if (before.status() === 200) {
|
|
181
|
+
await (0, assertions_1.assertPaginated)(before, { exactItems: 0 });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
await (0, assertions_1.assertForbidden)(before);
|
|
185
|
+
}
|
|
186
|
+
const grant = await api.post('/authorizations', {
|
|
187
|
+
body: {
|
|
188
|
+
ownerType: principal.ownerType,
|
|
189
|
+
ownerId: principal.ownerId,
|
|
190
|
+
resourceType: 'PROCESS_DEFINITION',
|
|
191
|
+
resourceId: processId,
|
|
192
|
+
permissionTypes: ['READ_PROCESS_DEFINITION'],
|
|
193
|
+
},
|
|
194
|
+
tenant: 'none',
|
|
195
|
+
});
|
|
196
|
+
const created = (await (0, assertions_1.assertCreated)(grant));
|
|
197
|
+
resources.track('authorization', String(created.authorizationKey));
|
|
198
|
+
// Admitted after it. This direction is what proves the refusal above came
|
|
199
|
+
// from the authorization check rather than from the resource being absent.
|
|
200
|
+
await (0, eventually_1.eventually)(async () => {
|
|
201
|
+
const after = await api.post('/process-definitions/search', {
|
|
202
|
+
body: { filter: { processDefinitionId: processId } },
|
|
203
|
+
authorization: principal.authorization,
|
|
204
|
+
});
|
|
205
|
+
await (0, assertions_1.assertPaginated)(after, { exactItems: 1 });
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|