@credibledata/cred-cli 0.0.102 → 0.0.103
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/README.md +1 -1
- package/coverage/coverage-summary.json +40 -0
- package/coverage/tmp/coverage-2152-1789496974457-0.json +1 -0
- package/coverage/tmp/coverage-2153-1789496954404-0.json +1 -0
- package/dist/client/api.js +4193 -1342
- package/dist/client/api.js.map +1 -1
- package/dist/clientconfiguration.js +57 -4
- package/dist/clientconfiguration.js.map +1 -1
- package/dist/clientconfiguration.spec.js +90 -0
- package/dist/clientconfiguration.spec.js.map +1 -1
- package/dist/commands/add.js +18 -0
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/environment/updateenvironment.js +63 -1
- package/dist/commands/environment/updateenvironment.js.map +1 -1
- package/dist/commands/environment/updateenvironment.spec.js +84 -0
- package/dist/commands/environment/updateenvironment.spec.js.map +1 -1
- package/dist/commands/get.js +52 -1
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/list.js +78 -0
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/materialization/cancelrun.js +62 -0
- package/dist/commands/materialization/cancelrun.js.map +1 -0
- package/dist/commands/materialization/getindex.js +52 -0
- package/dist/commands/materialization/getindex.js.map +1 -0
- package/dist/commands/materialization/getmaterialization.js +54 -0
- package/dist/commands/materialization/getmaterialization.js.map +1 -0
- package/dist/commands/materialization/getpreaggregate.js +55 -0
- package/dist/commands/materialization/getpreaggregate.js.map +1 -0
- package/dist/commands/materialization/getrun.js +54 -0
- package/dist/commands/materialization/getrun.js.map +1 -0
- package/dist/commands/materialization/listindexes.js +58 -0
- package/dist/commands/materialization/listindexes.js.map +1 -0
- package/dist/commands/materialization/listmaterializations.js +52 -0
- package/dist/commands/materialization/listmaterializations.js.map +1 -0
- package/dist/commands/materialization/listpreaggregates.js +61 -0
- package/dist/commands/materialization/listpreaggregates.js.map +1 -0
- package/dist/commands/materialization/listruns.js +60 -0
- package/dist/commands/materialization/listruns.js.map +1 -0
- package/dist/commands/materialization/rerun.js +111 -0
- package/dist/commands/materialization/rerun.js.map +1 -0
- package/dist/commands/misc/login.js +14 -0
- package/dist/commands/misc/login.js.map +1 -1
- package/dist/commands/misc/login.spec.js +92 -0
- package/dist/commands/misc/login.spec.js.map +1 -1
- package/dist/commands/package/listpackages.js +7 -0
- package/dist/commands/package/listpackages.js.map +1 -1
- package/dist/commands/package/listpackages.spec.js +12 -0
- package/dist/commands/package/listpackages.spec.js.map +1 -1
- package/dist/commands/package/publish.js +34 -11
- package/dist/commands/package/publish.js.map +1 -1
- package/dist/commands/package/publish.spec.js +26 -28
- package/dist/commands/package/publish.spec.js.map +1 -1
- package/dist/commands/permission/createpermission.js +123 -0
- package/dist/commands/permission/createpermission.js.map +1 -0
- package/dist/commands/permission/createpermission.spec.js +360 -0
- package/dist/commands/permission/createpermission.spec.js.map +1 -0
- package/dist/commands/permission/deletepermission.js +86 -0
- package/dist/commands/permission/deletepermission.js.map +1 -0
- package/dist/commands/permission/getpermission.js +85 -0
- package/dist/commands/permission/getpermission.js.map +1 -0
- package/dist/commands/permission/listpermissions.js +75 -0
- package/dist/commands/permission/listpermissions.js.map +1 -0
- package/dist/commands/permission/scope.js +140 -0
- package/dist/commands/permission/scope.js.map +1 -0
- package/dist/commands/rm.js +11 -0
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/update.js +18 -12
- package/dist/commands/update.js.map +1 -1
- package/dist/common/UserGroupId.js +1 -1
- package/dist/common/UserGroupId.js.map +1 -1
- package/dist/common/UserGroupId.spec.js +2 -4
- package/dist/common/UserGroupId.spec.js.map +1 -1
- package/dist/constants.js +48 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.js +61 -6
- package/dist/index.js.map +1 -1
- package/dist/logger.js +5 -1
- package/dist/logger.js.map +1 -1
- package/openapitools.json +4 -1
- package/package.json +5 -3
- package/src/client/api.ts +6433 -1533
- package/src/clientconfiguration.spec.ts +126 -0
- package/src/clientconfiguration.ts +64 -4
- package/src/commands/add.ts +43 -0
- package/src/commands/environment/updateenvironment.spec.ts +167 -0
- package/src/commands/environment/updateenvironment.ts +85 -1
- package/src/commands/get.ts +110 -1
- package/src/commands/list.ts +171 -0
- package/src/commands/materialization/cancelrun.ts +70 -0
- package/src/commands/materialization/getindex.ts +55 -0
- package/src/commands/materialization/getmaterialization.ts +57 -0
- package/src/commands/materialization/getpreaggregate.ts +58 -0
- package/src/commands/materialization/getrun.ts +55 -0
- package/src/commands/materialization/listindexes.ts +69 -0
- package/src/commands/materialization/listmaterializations.ts +53 -0
- package/src/commands/materialization/listpreaggregates.ts +74 -0
- package/src/commands/materialization/listruns.ts +77 -0
- package/src/commands/materialization/rerun.ts +123 -0
- package/src/commands/misc/login.spec.ts +118 -2
- package/src/commands/misc/login.ts +14 -0
- package/src/commands/package/listpackages.spec.ts +16 -0
- package/src/commands/package/listpackages.ts +7 -0
- package/src/commands/package/publish.spec.ts +45 -30
- package/src/commands/package/publish.ts +31 -10
- package/src/commands/permission/createpermission.spec.ts +507 -0
- package/src/commands/permission/createpermission.ts +181 -0
- package/src/commands/permission/deletepermission.ts +117 -0
- package/src/commands/permission/getpermission.ts +115 -0
- package/src/commands/permission/listpermissions.ts +101 -0
- package/src/commands/permission/scope.ts +180 -0
- package/src/commands/rm.ts +29 -0
- package/src/commands/update.ts +50 -27
- package/src/common/UserGroupId.spec.ts +2 -4
- package/src/common/UserGroupId.ts +1 -1
- package/src/constants.ts +50 -1
- package/src/index.ts +94 -11
- package/src/logger.ts +5 -1
- package/bun.lock +0 -934
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import sinon from "sinon";
|
|
3
|
+
import {
|
|
4
|
+
EnvironmentPermissionsApi,
|
|
5
|
+
OrganizationPermissionsApi,
|
|
6
|
+
PackagePermissionsApi,
|
|
7
|
+
} from "../../client/api";
|
|
8
|
+
import { ClientConfiguration } from "../../clientconfiguration";
|
|
9
|
+
import { logger } from "../../logger";
|
|
10
|
+
import * as utils from "../../utils";
|
|
11
|
+
import { createPermission } from "./createpermission";
|
|
12
|
+
|
|
13
|
+
describe("add-permission", () => {
|
|
14
|
+
let sandbox: sinon.SinonSandbox;
|
|
15
|
+
let createOrgStub: sinon.SinonStub;
|
|
16
|
+
let createEnvStub: sinon.SinonStub;
|
|
17
|
+
let createPkgStub: sinon.SinonStub;
|
|
18
|
+
let loggerStub: sinon.SinonStubbedInstance<typeof logger>;
|
|
19
|
+
let consoleLogStub: sinon.SinonStub;
|
|
20
|
+
let processExitStub: sinon.SinonStub;
|
|
21
|
+
let confirmStub: sinon.SinonStub;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
sandbox = sinon.createSandbox();
|
|
25
|
+
createOrgStub = sandbox.stub().resolves({ data: {} });
|
|
26
|
+
createEnvStub = sandbox.stub().resolves({ data: {} });
|
|
27
|
+
createPkgStub = sandbox.stub().resolves({ data: {} });
|
|
28
|
+
loggerStub = sandbox.stub(logger);
|
|
29
|
+
consoleLogStub = sandbox.stub(console, "log");
|
|
30
|
+
processExitStub = sandbox.stub(process, "exit");
|
|
31
|
+
confirmStub = sandbox.stub(utils, "getConfirmation").resolves(true);
|
|
32
|
+
|
|
33
|
+
sandbox
|
|
34
|
+
.stub(ClientConfiguration.prototype, "getOrganization")
|
|
35
|
+
.returns("test-org");
|
|
36
|
+
sandbox
|
|
37
|
+
.stub(ClientConfiguration.prototype, "getProject")
|
|
38
|
+
.returns("configured-env");
|
|
39
|
+
sandbox
|
|
40
|
+
.stub(ClientConfiguration.prototype, "doesConfigExists")
|
|
41
|
+
.returns(true);
|
|
42
|
+
sandbox
|
|
43
|
+
.stub(ClientConfiguration.prototype, "getAuthenticationHeader")
|
|
44
|
+
.returns("Bearer test-token");
|
|
45
|
+
|
|
46
|
+
sandbox
|
|
47
|
+
.stub(
|
|
48
|
+
OrganizationPermissionsApi.prototype,
|
|
49
|
+
"createOrganizationPermission",
|
|
50
|
+
)
|
|
51
|
+
.callsFake(createOrgStub);
|
|
52
|
+
sandbox
|
|
53
|
+
.stub(
|
|
54
|
+
EnvironmentPermissionsApi.prototype,
|
|
55
|
+
"createEnvironmentPermission",
|
|
56
|
+
)
|
|
57
|
+
.callsFake(createEnvStub);
|
|
58
|
+
sandbox
|
|
59
|
+
.stub(PackagePermissionsApi.prototype, "createPackagePermission")
|
|
60
|
+
.callsFake(createPkgStub);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
sandbox.restore();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("grants a group an organization role", async () => {
|
|
68
|
+
await createPermission(
|
|
69
|
+
"http://test-server",
|
|
70
|
+
"group:ci-bot",
|
|
71
|
+
"admin",
|
|
72
|
+
false,
|
|
73
|
+
{
|
|
74
|
+
yes: true,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(createOrgStub.calledOnce).to.be.true;
|
|
79
|
+
expect(createOrgStub.firstCall.args[0]).to.equal("test-org");
|
|
80
|
+
// The subject must reach the API as the typed string. A group sent as a
|
|
81
|
+
// UserGroupId object or as a bare name addresses no tuple, so the grant
|
|
82
|
+
// would report success while leaving the group without the role.
|
|
83
|
+
expect(createOrgStub.firstCall.args[2]).to.deep.equal({
|
|
84
|
+
userGroupId: "group:ci-bot",
|
|
85
|
+
permission: "admin",
|
|
86
|
+
});
|
|
87
|
+
expect(createOrgStub.firstCall.args[3]).to.deep.include({
|
|
88
|
+
baseURL: "http://test-server",
|
|
89
|
+
headers: { Authorization: "Bearer test-token" },
|
|
90
|
+
});
|
|
91
|
+
expect(createEnvStub.called).to.be.false;
|
|
92
|
+
expect(createPkgStub.called).to.be.false;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("grants a user an organization role", async () => {
|
|
96
|
+
await createPermission(
|
|
97
|
+
"http://test-server",
|
|
98
|
+
"user:someone@example.com",
|
|
99
|
+
"member",
|
|
100
|
+
false,
|
|
101
|
+
{ yes: true },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(createOrgStub.firstCall.args[2]).to.deep.equal({
|
|
105
|
+
userGroupId: "user:someone@example.com",
|
|
106
|
+
permission: "member",
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("grants on an environment when one is named", async () => {
|
|
111
|
+
await createPermission(
|
|
112
|
+
"http://test-server",
|
|
113
|
+
"group:ci-bot",
|
|
114
|
+
"viewer",
|
|
115
|
+
false,
|
|
116
|
+
{
|
|
117
|
+
environment: "sales",
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(createEnvStub.calledOnce).to.be.true;
|
|
122
|
+
expect(createEnvStub.firstCall.args[0]).to.equal("test-org");
|
|
123
|
+
expect(createEnvStub.firstCall.args[1]).to.equal("sales");
|
|
124
|
+
expect(createEnvStub.firstCall.args[4]).to.deep.equal({
|
|
125
|
+
userGroupId: "group:ci-bot",
|
|
126
|
+
permission: "viewer",
|
|
127
|
+
});
|
|
128
|
+
expect(createOrgStub.called).to.be.false;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("grants on a package, taking the environment from the config", async () => {
|
|
132
|
+
await createPermission(
|
|
133
|
+
"http://test-server",
|
|
134
|
+
"group:ci-bot",
|
|
135
|
+
"viewer",
|
|
136
|
+
false,
|
|
137
|
+
{
|
|
138
|
+
package: "orders",
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(createPkgStub.calledOnce).to.be.true;
|
|
143
|
+
expect(createPkgStub.firstCall.args[1]).to.equal("configured-env");
|
|
144
|
+
expect(createPkgStub.firstCall.args[2]).to.equal("orders");
|
|
145
|
+
expect(createPkgStub.firstCall.args[5]).to.deep.equal({
|
|
146
|
+
userGroupId: "group:ci-bot",
|
|
147
|
+
permission: "viewer",
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("prefers an explicit environment over the configured one for a package", async () => {
|
|
152
|
+
await createPermission(
|
|
153
|
+
"http://test-server",
|
|
154
|
+
"group:ci-bot",
|
|
155
|
+
"viewer",
|
|
156
|
+
false,
|
|
157
|
+
{
|
|
158
|
+
environment: "explicit-env",
|
|
159
|
+
package: "orders",
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(createPkgStub.firstCall.args[1]).to.equal("explicit-env");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("rejects a role the organization does not define", async () => {
|
|
167
|
+
// viewer belongs to environments and packages; the organization ends in member.
|
|
168
|
+
await createPermission(
|
|
169
|
+
"http://test-server",
|
|
170
|
+
"group:ci-bot",
|
|
171
|
+
"viewer",
|
|
172
|
+
false,
|
|
173
|
+
{
|
|
174
|
+
yes: true,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(createOrgStub.called).to.be.false;
|
|
179
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
180
|
+
expect(loggerStub.error.calledWith(sinon.match("Invalid role 'viewer'")))
|
|
181
|
+
.to.be.true;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("rejects a role an environment does not define", async () => {
|
|
185
|
+
await createPermission(
|
|
186
|
+
"http://test-server",
|
|
187
|
+
"group:ci-bot",
|
|
188
|
+
"member",
|
|
189
|
+
false,
|
|
190
|
+
{
|
|
191
|
+
environment: "sales",
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(createEnvStub.called).to.be.false;
|
|
196
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("rejects public access as an environment admin", async () => {
|
|
200
|
+
await createPermission("http://test-server", "user:*", "admin", false, {
|
|
201
|
+
environment: "sales",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(createEnvStub.called).to.be.false;
|
|
205
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("allows public access as an environment viewer", async () => {
|
|
209
|
+
await createPermission("http://test-server", "user:*", "viewer", false, {
|
|
210
|
+
environment: "sales",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(createEnvStub.calledOnce).to.be.true;
|
|
214
|
+
expect(createEnvStub.firstCall.args[4]).to.deep.equal({
|
|
215
|
+
userGroupId: "user:*",
|
|
216
|
+
permission: "viewer",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("allows public access as an organization modeler", async () => {
|
|
221
|
+
// The one organization role the model admits for the wildcard.
|
|
222
|
+
await createPermission("http://test-server", "user:*", "modeler", false, {
|
|
223
|
+
yes: true,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(createOrgStub.calledOnce).to.be.true;
|
|
227
|
+
expect(createOrgStub.firstCall.args[2]).to.deep.equal({
|
|
228
|
+
userGroupId: "user:*",
|
|
229
|
+
permission: "modeler",
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("rejects every public-access role the model does not admit", async () => {
|
|
234
|
+
// Each of these reaches OpenFGA and fails there as a model validation
|
|
235
|
+
// error, so the refusal belongs before the request.
|
|
236
|
+
const cases: [string, Record<string, unknown>][] = [
|
|
237
|
+
["admin", { yes: true }],
|
|
238
|
+
["member", { yes: true }],
|
|
239
|
+
["modeler", { environment: "sales" }],
|
|
240
|
+
["admin", { environment: "sales" }],
|
|
241
|
+
["modeler", { environment: "sales", package: "orders" }],
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
for (const [role, options] of cases) {
|
|
245
|
+
createOrgStub.resetHistory();
|
|
246
|
+
createEnvStub.resetHistory();
|
|
247
|
+
createPkgStub.resetHistory();
|
|
248
|
+
processExitStub.resetHistory();
|
|
249
|
+
|
|
250
|
+
await createPermission(
|
|
251
|
+
"http://test-server",
|
|
252
|
+
"user:*",
|
|
253
|
+
role,
|
|
254
|
+
false,
|
|
255
|
+
options,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const sent =
|
|
259
|
+
createOrgStub.called ||
|
|
260
|
+
createEnvStub.called ||
|
|
261
|
+
createPkgStub.called;
|
|
262
|
+
expect(sent, `public access with ${role} must not be sent`).to.be
|
|
263
|
+
.false;
|
|
264
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("rejects an empty --environment instead of widening to the organization", async () => {
|
|
269
|
+
// `--environment "$UNSET"` must not be read as "no environment given":
|
|
270
|
+
// that would grant on the organization, which is wider than asked for.
|
|
271
|
+
await createPermission(
|
|
272
|
+
"http://test-server",
|
|
273
|
+
"group:ci-bot",
|
|
274
|
+
"admin",
|
|
275
|
+
false,
|
|
276
|
+
{
|
|
277
|
+
environment: "",
|
|
278
|
+
yes: true,
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(createOrgStub.called).to.be.false;
|
|
283
|
+
expect(createEnvStub.called).to.be.false;
|
|
284
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
285
|
+
expect(loggerStub.error.calledWith(sinon.match("--environment"))).to.be
|
|
286
|
+
.true;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("rejects an empty --package instead of widening to the environment", async () => {
|
|
290
|
+
await createPermission(
|
|
291
|
+
"http://test-server",
|
|
292
|
+
"group:ci-bot",
|
|
293
|
+
"viewer",
|
|
294
|
+
false,
|
|
295
|
+
{
|
|
296
|
+
environment: "sales",
|
|
297
|
+
package: "",
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(createPkgStub.called).to.be.false;
|
|
302
|
+
expect(createEnvStub.called).to.be.false;
|
|
303
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("rejects a package role the server cannot write", async () => {
|
|
307
|
+
// A package reports admin and modeler because they are inherited from the
|
|
308
|
+
// environment, but only viewer is directly assignable there.
|
|
309
|
+
for (const role of ["admin", "modeler"]) {
|
|
310
|
+
createPkgStub.resetHistory();
|
|
311
|
+
processExitStub.resetHistory();
|
|
312
|
+
|
|
313
|
+
await createPermission(
|
|
314
|
+
"http://test-server",
|
|
315
|
+
"group:ci-bot",
|
|
316
|
+
role,
|
|
317
|
+
false,
|
|
318
|
+
{
|
|
319
|
+
environment: "sales",
|
|
320
|
+
package: "orders",
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(createPkgStub.called, `role ${role} must not be sent`).to.be
|
|
325
|
+
.false;
|
|
326
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("rejects a subject with no type", async () => {
|
|
331
|
+
await createPermission("http://test-server", "ci-bot", "admin", false, {
|
|
332
|
+
yes: true,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(createOrgStub.called).to.be.false;
|
|
336
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("confirms before granting organization admin", async () => {
|
|
340
|
+
confirmStub.resolves(false);
|
|
341
|
+
|
|
342
|
+
await createPermission(
|
|
343
|
+
"http://test-server",
|
|
344
|
+
"group:ci-bot",
|
|
345
|
+
"admin",
|
|
346
|
+
false,
|
|
347
|
+
{},
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(confirmStub.calledOnce).to.be.true;
|
|
351
|
+
expect(createOrgStub.called).to.be.false;
|
|
352
|
+
expect(loggerStub.info.calledWith(sinon.match("Operation cancelled"))).to
|
|
353
|
+
.be.true;
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("does not confirm for a narrower organization role", async () => {
|
|
357
|
+
await createPermission(
|
|
358
|
+
"http://test-server",
|
|
359
|
+
"group:ci-bot",
|
|
360
|
+
"modeler",
|
|
361
|
+
false,
|
|
362
|
+
{},
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
expect(confirmStub.called).to.be.false;
|
|
366
|
+
expect(createOrgStub.calledOnce).to.be.true;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("refuses --notifyPeople at the organization scope", async () => {
|
|
370
|
+
// The organization endpoint has no notify parameter and always sends, so
|
|
371
|
+
// accepting the flag there would promise control that does not exist.
|
|
372
|
+
await createPermission(
|
|
373
|
+
"http://test-server",
|
|
374
|
+
"group:ci-bot",
|
|
375
|
+
"admin",
|
|
376
|
+
false,
|
|
377
|
+
{
|
|
378
|
+
notifyPeople: true,
|
|
379
|
+
yes: true,
|
|
380
|
+
},
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
expect(createOrgStub.called).to.be.false;
|
|
384
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
385
|
+
expect(loggerStub.error.calledWith(sinon.match("--notifyPeople"))).to.be
|
|
386
|
+
.true;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("accepts --notifyPeople on an environment", async () => {
|
|
390
|
+
await createPermission(
|
|
391
|
+
"http://test-server",
|
|
392
|
+
"group:ci-bot",
|
|
393
|
+
"viewer",
|
|
394
|
+
false,
|
|
395
|
+
{
|
|
396
|
+
environment: "sales",
|
|
397
|
+
notifyPeople: true,
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
expect(createEnvStub.calledOnce).to.be.true;
|
|
402
|
+
expect(createEnvStub.firstCall.args[3]).to.equal(true);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("defaults notifyPeople to false", async () => {
|
|
406
|
+
await createPermission(
|
|
407
|
+
"http://test-server",
|
|
408
|
+
"group:ci-bot",
|
|
409
|
+
"viewer",
|
|
410
|
+
false,
|
|
411
|
+
{
|
|
412
|
+
environment: "sales",
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
expect(createEnvStub.firstCall.args[3]).to.equal(false);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("handles missing config", async () => {
|
|
420
|
+
sandbox.restore();
|
|
421
|
+
sandbox = sinon.createSandbox();
|
|
422
|
+
createOrgStub = sandbox.stub();
|
|
423
|
+
loggerStub = sandbox.stub(logger);
|
|
424
|
+
sandbox.stub(console, "log");
|
|
425
|
+
processExitStub = sandbox.stub(process, "exit");
|
|
426
|
+
sandbox
|
|
427
|
+
.stub(ClientConfiguration.prototype, "doesConfigExists")
|
|
428
|
+
.returns(false);
|
|
429
|
+
sandbox
|
|
430
|
+
.stub(
|
|
431
|
+
OrganizationPermissionsApi.prototype,
|
|
432
|
+
"createOrganizationPermission",
|
|
433
|
+
)
|
|
434
|
+
.callsFake(createOrgStub);
|
|
435
|
+
|
|
436
|
+
await createPermission(
|
|
437
|
+
"http://test-server",
|
|
438
|
+
"group:ci-bot",
|
|
439
|
+
"admin",
|
|
440
|
+
false,
|
|
441
|
+
{
|
|
442
|
+
yes: true,
|
|
443
|
+
},
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
expect(createOrgStub.called).to.be.false;
|
|
447
|
+
expect(
|
|
448
|
+
loggerStub.error.calledWith(
|
|
449
|
+
sinon.match("Please, make sure that you are logged in"),
|
|
450
|
+
),
|
|
451
|
+
).to.be.true;
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("handles a missing organization", async () => {
|
|
455
|
+
sandbox.restore();
|
|
456
|
+
sandbox = sinon.createSandbox();
|
|
457
|
+
createOrgStub = sandbox.stub();
|
|
458
|
+
loggerStub = sandbox.stub(logger);
|
|
459
|
+
sandbox.stub(console, "log");
|
|
460
|
+
processExitStub = sandbox.stub(process, "exit");
|
|
461
|
+
sandbox
|
|
462
|
+
.stub(ClientConfiguration.prototype, "doesConfigExists")
|
|
463
|
+
.returns(true);
|
|
464
|
+
sandbox
|
|
465
|
+
.stub(ClientConfiguration.prototype, "getOrganization")
|
|
466
|
+
.returns(undefined);
|
|
467
|
+
sandbox
|
|
468
|
+
.stub(
|
|
469
|
+
OrganizationPermissionsApi.prototype,
|
|
470
|
+
"createOrganizationPermission",
|
|
471
|
+
)
|
|
472
|
+
.callsFake(createOrgStub);
|
|
473
|
+
|
|
474
|
+
await createPermission(
|
|
475
|
+
"http://test-server",
|
|
476
|
+
"group:ci-bot",
|
|
477
|
+
"admin",
|
|
478
|
+
false,
|
|
479
|
+
{
|
|
480
|
+
yes: true,
|
|
481
|
+
},
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(createOrgStub.called).to.be.false;
|
|
485
|
+
expect(loggerStub.error.calledWith(sinon.match("No organization set"))).to
|
|
486
|
+
.be.true;
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("exits non-zero when the API fails", async () => {
|
|
490
|
+
createOrgStub.rejects(new Error("API Error"));
|
|
491
|
+
|
|
492
|
+
await createPermission(
|
|
493
|
+
"http://test-server",
|
|
494
|
+
"group:ci-bot",
|
|
495
|
+
"admin",
|
|
496
|
+
false,
|
|
497
|
+
{
|
|
498
|
+
yes: true,
|
|
499
|
+
},
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
expect(processExitStub.calledWith(1)).to.be.true;
|
|
503
|
+
expect(
|
|
504
|
+
loggerStub.error.calledWith(sinon.match("Failed to grant permission")),
|
|
505
|
+
).to.be.true;
|
|
506
|
+
});
|
|
507
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EnvironmentPermissionPermissionEnum,
|
|
3
|
+
EnvironmentPermissionsApi,
|
|
4
|
+
OrganizationPermissionPermissionEnum,
|
|
5
|
+
OrganizationPermissionsApi,
|
|
6
|
+
PackagePermissionPermissionEnum,
|
|
7
|
+
PackagePermissionsApi,
|
|
8
|
+
} from "../../client/api";
|
|
9
|
+
import { Configuration } from "../../client/configuration";
|
|
10
|
+
import { ClientConfiguration } from "../../clientconfiguration";
|
|
11
|
+
import { logger } from "../../logger";
|
|
12
|
+
import { getConfirmation, printOutError } from "../../utils";
|
|
13
|
+
import {
|
|
14
|
+
describeScope,
|
|
15
|
+
parseSubject,
|
|
16
|
+
PermissionScope,
|
|
17
|
+
resolveScope,
|
|
18
|
+
ScopeOptions,
|
|
19
|
+
validateRole,
|
|
20
|
+
} from "./scope";
|
|
21
|
+
|
|
22
|
+
const organizationPermissionsApi = new OrganizationPermissionsApi(
|
|
23
|
+
new Configuration(),
|
|
24
|
+
);
|
|
25
|
+
const environmentPermissionsApi = new EnvironmentPermissionsApi(
|
|
26
|
+
new Configuration(),
|
|
27
|
+
);
|
|
28
|
+
const packagePermissionsApi = new PackagePermissionsApi(new Configuration());
|
|
29
|
+
|
|
30
|
+
export type CreatePermissionOptions = ScopeOptions & {
|
|
31
|
+
notifyPeople?: boolean;
|
|
32
|
+
yes?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export async function createPermission(
|
|
36
|
+
controlPlane: string,
|
|
37
|
+
userGroupIdValue: string,
|
|
38
|
+
role: string,
|
|
39
|
+
debug: boolean,
|
|
40
|
+
options: CreatePermissionOptions,
|
|
41
|
+
) {
|
|
42
|
+
const clientConfig = new ClientConfiguration();
|
|
43
|
+
|
|
44
|
+
if (!clientConfig.doesConfigExists()) {
|
|
45
|
+
logger.error("Please, make sure that you are logged in");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const organizationName = clientConfig.getOrganization();
|
|
50
|
+
if (!organizationName) {
|
|
51
|
+
logger.error("No organization set. Please login or set an organization.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resolved = resolveScope(options, clientConfig.getProject());
|
|
56
|
+
if ("error" in resolved) {
|
|
57
|
+
logger.error(resolved.error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const roleError = validateRole(resolved.scope, role);
|
|
63
|
+
if (roleError) {
|
|
64
|
+
logger.error(roleError);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The organization create endpoint takes no notify parameter and the service
|
|
70
|
+
// always sends, so honoring this flag there is not possible and ignoring it
|
|
71
|
+
// would be a false promise in both directions -- the default would email and
|
|
72
|
+
// passing it would not stop that.
|
|
73
|
+
if (
|
|
74
|
+
options.notifyPeople === true &&
|
|
75
|
+
resolved.scope === PermissionScope.ORGANIZATION
|
|
76
|
+
) {
|
|
77
|
+
logger.error(
|
|
78
|
+
"--notifyPeople applies to an environment or a package. An organization grant always notifies a user subject.",
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const subject = parseSubject(resolved.scope, role, userGroupIdValue);
|
|
85
|
+
if ("error" in subject) {
|
|
86
|
+
logger.error(subject.error);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const userGroupId = subject.userGroupId.getValue();
|
|
91
|
+
|
|
92
|
+
const target = describeScope(organizationName, resolved);
|
|
93
|
+
|
|
94
|
+
// Organization admin is the organization's broadest role: it reaches every
|
|
95
|
+
// environment and package, can read member details, and can mint further
|
|
96
|
+
// group access tokens. Granting it to a group grants it to every token that
|
|
97
|
+
// group holds, so it is confirmed rather than applied silently.
|
|
98
|
+
if (
|
|
99
|
+
resolved.scope === PermissionScope.ORGANIZATION &&
|
|
100
|
+
role === "admin" &&
|
|
101
|
+
!options.yes
|
|
102
|
+
) {
|
|
103
|
+
const confirmation = await getConfirmation(
|
|
104
|
+
`Grant ${userGroupId} the admin role on ${target}? This reaches every environment and package in the organization, and allows creating further group access tokens.`,
|
|
105
|
+
);
|
|
106
|
+
if (!confirmation) {
|
|
107
|
+
logger.info("Operation cancelled.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.info(`Granting ${role} permission to ${userGroupId} on ${target}`);
|
|
113
|
+
|
|
114
|
+
const requestOptions = {
|
|
115
|
+
baseURL: controlPlane,
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: clientConfig.getAuthenticationHeader(),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const notifyPeople = options.notifyPeople === true;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
let data: unknown;
|
|
124
|
+
switch (resolved.scope) {
|
|
125
|
+
case PermissionScope.PACKAGE: {
|
|
126
|
+
const response =
|
|
127
|
+
await packagePermissionsApi.createPackagePermission(
|
|
128
|
+
organizationName,
|
|
129
|
+
resolved.environmentName as string,
|
|
130
|
+
resolved.packageName as string,
|
|
131
|
+
false,
|
|
132
|
+
notifyPeople,
|
|
133
|
+
{
|
|
134
|
+
userGroupId,
|
|
135
|
+
permission: role as PackagePermissionPermissionEnum,
|
|
136
|
+
},
|
|
137
|
+
requestOptions,
|
|
138
|
+
);
|
|
139
|
+
data = response.data;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case PermissionScope.ENVIRONMENT: {
|
|
143
|
+
const response =
|
|
144
|
+
await environmentPermissionsApi.createEnvironmentPermission(
|
|
145
|
+
organizationName,
|
|
146
|
+
resolved.environmentName as string,
|
|
147
|
+
false,
|
|
148
|
+
notifyPeople,
|
|
149
|
+
{
|
|
150
|
+
userGroupId,
|
|
151
|
+
permission: role as EnvironmentPermissionPermissionEnum,
|
|
152
|
+
},
|
|
153
|
+
requestOptions,
|
|
154
|
+
);
|
|
155
|
+
data = response.data;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
default: {
|
|
159
|
+
const response =
|
|
160
|
+
await organizationPermissionsApi.createOrganizationPermission(
|
|
161
|
+
organizationName,
|
|
162
|
+
false,
|
|
163
|
+
{
|
|
164
|
+
userGroupId,
|
|
165
|
+
permission: role as OrganizationPermissionPermissionEnum,
|
|
166
|
+
},
|
|
167
|
+
requestOptions,
|
|
168
|
+
);
|
|
169
|
+
data = response.data;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(JSON.stringify(data, null, 2));
|
|
175
|
+
logger.info("Permission granted successfully");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logger.error("Failed to grant permission");
|
|
178
|
+
printOutError(error, debug);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|