@kuadrant/kuadrant-backstage-plugin-backend 0.0.2-dev-0b744dd → 0.2.0
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/index.cjs.js +22 -6
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +46 -4
- package/dist/{alpha.cjs.js → module.cjs.js} +1 -1
- package/dist/module.cjs.js.map +1 -0
- package/dist/permissions.cjs.js +19 -1
- package/dist/permissions.cjs.js.map +1 -1
- package/dist/providers/APIProductEntityProvider.cjs.js.map +1 -1
- package/dist/{rbac.cjs.js → rbac-module.cjs.js} +1 -1
- package/dist/rbac-module.cjs.js.map +1 -0
- package/dist/router.cjs.js +183 -63
- package/dist/router.cjs.js.map +1 -1
- package/package.json +26 -38
- package/alpha/module.cjs.js +0 -31
- package/alpha/package.json +0 -11
- package/dist/alpha.cjs.js.map +0 -1
- package/dist/alpha.d.ts +0 -27
- package/dist/rbac.cjs.js.map +0 -1
- package/dist/rbac.d.ts +0 -11
- package/rbac/index.ts +0 -1
- package/rbac/package.json +0 -11
package/dist/router.cjs.js
CHANGED
|
@@ -9,7 +9,7 @@ var Router = require('express-promise-router');
|
|
|
9
9
|
var cors = require('cors');
|
|
10
10
|
var crypto = require('crypto');
|
|
11
11
|
var k8sClient = require('./k8s-client.cjs.js');
|
|
12
|
-
var
|
|
12
|
+
var module$1 = require('./module.cjs.js');
|
|
13
13
|
var permissions = require('./permissions.cjs.js');
|
|
14
14
|
|
|
15
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
@@ -36,6 +36,38 @@ async function getUserIdentity(req, httpAuth, userInfo) {
|
|
|
36
36
|
groups
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
async function verifyApiKeyUpdatePermission(credentials, userEntityRef, namespace, apiProductName, k8sClient, permissions$1) {
|
|
40
|
+
const updateAllDecision = await permissions$1.authorize(
|
|
41
|
+
[{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
|
|
42
|
+
{ credentials }
|
|
43
|
+
);
|
|
44
|
+
if (updateAllDecision[0].result === pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const updateOwnDecision = await permissions$1.authorize(
|
|
48
|
+
[{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
|
|
49
|
+
{ credentials }
|
|
50
|
+
);
|
|
51
|
+
if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
52
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
53
|
+
}
|
|
54
|
+
let apiProduct;
|
|
55
|
+
try {
|
|
56
|
+
apiProduct = await k8sClient.getCustomResource(
|
|
57
|
+
"devportal.kuadrant.io",
|
|
58
|
+
"v1alpha1",
|
|
59
|
+
namespace,
|
|
60
|
+
"apiproducts",
|
|
61
|
+
apiProductName
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new errors.InputError(`APIProduct '${apiProductName}' not found in namespace '${namespace}' - cannot verify ownership`);
|
|
65
|
+
}
|
|
66
|
+
const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
|
|
67
|
+
if (owner !== userEntityRef) {
|
|
68
|
+
throw new errors.NotAllowedError("you can only update requests for your own api products");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
39
71
|
async function createRouter({
|
|
40
72
|
httpAuth,
|
|
41
73
|
userInfo,
|
|
@@ -155,7 +187,7 @@ async function createRouter({
|
|
|
155
187
|
"apiproducts",
|
|
156
188
|
apiProduct
|
|
157
189
|
);
|
|
158
|
-
const provider =
|
|
190
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
159
191
|
if (provider) {
|
|
160
192
|
await provider.refresh();
|
|
161
193
|
}
|
|
@@ -239,7 +271,7 @@ async function createRouter({
|
|
|
239
271
|
"apiproducts",
|
|
240
272
|
name
|
|
241
273
|
);
|
|
242
|
-
const provider =
|
|
274
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
243
275
|
if (provider) {
|
|
244
276
|
await provider.refresh();
|
|
245
277
|
}
|
|
@@ -257,7 +289,7 @@ async function createRouter({
|
|
|
257
289
|
try {
|
|
258
290
|
const credentials = await httpAuth.credentials(req);
|
|
259
291
|
const decision = await permissions$1.authorize(
|
|
260
|
-
[{ permission: permissions.
|
|
292
|
+
[{ permission: permissions.kuadrantHttpRouteListPermission }],
|
|
261
293
|
{ credentials }
|
|
262
294
|
);
|
|
263
295
|
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
@@ -274,8 +306,37 @@ async function createRouter({
|
|
|
274
306
|
}
|
|
275
307
|
}
|
|
276
308
|
});
|
|
309
|
+
router.get("/httproutes/:namespace/:name", async (req, res) => {
|
|
310
|
+
try {
|
|
311
|
+
const credentials = await httpAuth.credentials(req);
|
|
312
|
+
const decision = await permissions$1.authorize(
|
|
313
|
+
[{ permission: permissions.kuadrantHttpRouteListPermission }],
|
|
314
|
+
{ credentials }
|
|
315
|
+
);
|
|
316
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
317
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
318
|
+
}
|
|
319
|
+
const { namespace, name } = req.params;
|
|
320
|
+
const data = await k8sClient$1.getCustomResource("gateway.networking.k8s.io", "v1", namespace, "httproutes", name);
|
|
321
|
+
res.json(data);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error("error fetching httproute:", error);
|
|
324
|
+
if (error instanceof errors.NotAllowedError) {
|
|
325
|
+
res.status(403).json({ error: error.message });
|
|
326
|
+
} else {
|
|
327
|
+
res.status(500).json({ error: "failed to fetch httproute" });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
277
331
|
router.patch("/apiproducts/:namespace/:name", async (req, res) => {
|
|
278
332
|
const patchSchema = zod.z.object({
|
|
333
|
+
metadata: zod.z.object({
|
|
334
|
+
labels: zod.z.object({
|
|
335
|
+
// allow updating lifecycle phase via edit apiproduct dialog
|
|
336
|
+
// synced to backstage catalog entity's lifecycle field
|
|
337
|
+
lifecycle: zod.z.enum(["experimental", "production", "deprecated", "retired"]).optional()
|
|
338
|
+
}).partial().optional()
|
|
339
|
+
}).partial().optional(),
|
|
279
340
|
spec: zod.z.object({
|
|
280
341
|
displayName: zod.z.string().optional(),
|
|
281
342
|
description: zod.z.string().optional(),
|
|
@@ -290,7 +351,7 @@ async function createRouter({
|
|
|
290
351
|
}).partial().optional(),
|
|
291
352
|
documentation: zod.z.object({
|
|
292
353
|
docsURL: zod.z.string().optional(),
|
|
293
|
-
|
|
354
|
+
openAPISpecURL: zod.z.string().optional()
|
|
294
355
|
}).partial().optional()
|
|
295
356
|
}).partial()
|
|
296
357
|
});
|
|
@@ -326,6 +387,18 @@ async function createRouter({
|
|
|
326
387
|
if (req.body.metadata?.annotations) {
|
|
327
388
|
delete req.body.metadata.annotations["backstage.io/owner"];
|
|
328
389
|
}
|
|
390
|
+
if (parsed.data.spec?.publishStatus === "Published" && parsed.data.metadata?.labels?.lifecycle === "retired") {
|
|
391
|
+
throw new errors.InputError("cannot publish a retired API product");
|
|
392
|
+
}
|
|
393
|
+
if (parsed.data.metadata?.labels?.lifecycle === "retired") {
|
|
394
|
+
const existing = await k8sClient$1.getCustomResource("devportal.kuadrant.io", "v1alpha1", namespace, "apiproducts", name);
|
|
395
|
+
if (existing.spec?.publishStatus === "Published") {
|
|
396
|
+
if (!parsed.data.spec) {
|
|
397
|
+
parsed.data.spec = {};
|
|
398
|
+
}
|
|
399
|
+
parsed.data.spec.publishStatus = "Draft";
|
|
400
|
+
}
|
|
401
|
+
}
|
|
329
402
|
const updated = await k8sClient$1.patchCustomResource(
|
|
330
403
|
"devportal.kuadrant.io",
|
|
331
404
|
"v1alpha1",
|
|
@@ -334,7 +407,7 @@ async function createRouter({
|
|
|
334
407
|
name,
|
|
335
408
|
parsed.data
|
|
336
409
|
);
|
|
337
|
-
const provider =
|
|
410
|
+
const provider = module$1.getAPIProductEntityProvider();
|
|
338
411
|
if (provider) {
|
|
339
412
|
await provider.refresh();
|
|
340
413
|
}
|
|
@@ -368,18 +441,20 @@ async function createRouter({
|
|
|
368
441
|
name: policy.metadata.name,
|
|
369
442
|
namespace: policy.metadata.namespace
|
|
370
443
|
},
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
444
|
+
spec: policy.spec ? {
|
|
445
|
+
// expose targetRef to allow UI to match PlanPolicy -> HTTPRoute
|
|
446
|
+
targetRef: policy.spec.targetRef ? {
|
|
447
|
+
kind: policy.spec.targetRef.kind,
|
|
448
|
+
name: policy.spec.targetRef.name,
|
|
449
|
+
namespace: policy.spec.targetRef.namespace
|
|
450
|
+
} : void 0,
|
|
451
|
+
plans: (policy.spec.plans || []).map((plan) => ({
|
|
452
|
+
tier: plan.tier,
|
|
453
|
+
description: plan.description,
|
|
454
|
+
limits: plan.limits
|
|
455
|
+
}))
|
|
456
|
+
} : {},
|
|
457
|
+
status: policy.status
|
|
383
458
|
}))
|
|
384
459
|
};
|
|
385
460
|
res.json(filtered);
|
|
@@ -479,7 +554,8 @@ async function createRouter({
|
|
|
479
554
|
if (error instanceof errors.NotAllowedError) {
|
|
480
555
|
res.status(403).json({ error: error.message });
|
|
481
556
|
} else {
|
|
482
|
-
|
|
557
|
+
const errorMessage = error instanceof Error ? error.message : "failed to create api key request";
|
|
558
|
+
res.status(500).json({ error: errorMessage });
|
|
483
559
|
}
|
|
484
560
|
}
|
|
485
561
|
});
|
|
@@ -592,30 +668,14 @@ async function createRouter({
|
|
|
592
668
|
if (!apiProductName) {
|
|
593
669
|
throw new errors.InputError("apiProductRef.name is required in APIKey spec");
|
|
594
670
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
671
|
+
await verifyApiKeyUpdatePermission(
|
|
672
|
+
credentials,
|
|
673
|
+
userEntityRef,
|
|
598
674
|
namespace,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
|
|
603
|
-
const updateAllDecision = await permissions$1.authorize(
|
|
604
|
-
[{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
|
|
605
|
-
{ credentials }
|
|
675
|
+
apiProductName,
|
|
676
|
+
k8sClient$1,
|
|
677
|
+
permissions$1
|
|
606
678
|
);
|
|
607
|
-
if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
608
|
-
const updateOwnDecision = await permissions$1.authorize(
|
|
609
|
-
[{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
|
|
610
|
-
{ credentials }
|
|
611
|
-
);
|
|
612
|
-
if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
613
|
-
throw new errors.NotAllowedError("unauthorised");
|
|
614
|
-
}
|
|
615
|
-
if (owner !== userEntityRef) {
|
|
616
|
-
throw new errors.NotAllowedError("you can only approve requests for your own api products");
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
679
|
const status = {
|
|
620
680
|
phase: "Approved",
|
|
621
681
|
reviewedBy,
|
|
@@ -661,30 +721,14 @@ async function createRouter({
|
|
|
661
721
|
if (!apiProductName) {
|
|
662
722
|
throw new errors.InputError("apiProductRef.name is required in APIKey spec");
|
|
663
723
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
724
|
+
await verifyApiKeyUpdatePermission(
|
|
725
|
+
credentials,
|
|
726
|
+
userEntityRef,
|
|
667
727
|
namespace,
|
|
668
|
-
|
|
669
|
-
|
|
728
|
+
apiProductName,
|
|
729
|
+
k8sClient$1,
|
|
730
|
+
permissions$1
|
|
670
731
|
);
|
|
671
|
-
const owner = apiProduct.metadata?.annotations?.["backstage.io/owner"];
|
|
672
|
-
const updateAllDecision = await permissions$1.authorize(
|
|
673
|
-
[{ permission: permissions.kuadrantApiKeyUpdateAllPermission }],
|
|
674
|
-
{ credentials }
|
|
675
|
-
);
|
|
676
|
-
if (updateAllDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
677
|
-
const updateOwnDecision = await permissions$1.authorize(
|
|
678
|
-
[{ permission: permissions.kuadrantApiKeyUpdateOwnPermission }],
|
|
679
|
-
{ credentials }
|
|
680
|
-
);
|
|
681
|
-
if (updateOwnDecision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
682
|
-
throw new errors.NotAllowedError("unauthorised");
|
|
683
|
-
}
|
|
684
|
-
if (owner !== userEntityRef) {
|
|
685
|
-
throw new errors.NotAllowedError("you can only reject requests for your own api products");
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
732
|
const status = {
|
|
689
733
|
phase: "Rejected",
|
|
690
734
|
reviewedBy,
|
|
@@ -1141,6 +1185,82 @@ async function createRouter({
|
|
|
1141
1185
|
}
|
|
1142
1186
|
}
|
|
1143
1187
|
});
|
|
1188
|
+
router.get("/authpolicies", async (req, res) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const credentials = await httpAuth.credentials(req);
|
|
1191
|
+
const decision = await permissions$1.authorize(
|
|
1192
|
+
[{ permission: permissions.kuadrantAuthPolicyListPermission }],
|
|
1193
|
+
{ credentials }
|
|
1194
|
+
);
|
|
1195
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
1196
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
1197
|
+
}
|
|
1198
|
+
const data = await k8sClient$1.listCustomResources("kuadrant.io", "v1", "authpolicies");
|
|
1199
|
+
const filtered = {
|
|
1200
|
+
items: (data.items || []).map((policy) => ({
|
|
1201
|
+
metadata: {
|
|
1202
|
+
name: policy.metadata.name,
|
|
1203
|
+
namespace: policy.metadata.namespace
|
|
1204
|
+
},
|
|
1205
|
+
spec: policy.spec ? {
|
|
1206
|
+
// expose targetRef to allow UI to match AuthPolicy -> HTTPRoute
|
|
1207
|
+
targetRef: policy.spec.targetRef ? {
|
|
1208
|
+
kind: policy.spec.targetRef.kind,
|
|
1209
|
+
name: policy.spec.targetRef.name,
|
|
1210
|
+
namespace: policy.spec.targetRef.namespace
|
|
1211
|
+
} : void 0
|
|
1212
|
+
} : {},
|
|
1213
|
+
status: policy.status
|
|
1214
|
+
}))
|
|
1215
|
+
};
|
|
1216
|
+
res.json(filtered);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
console.error("error fetching authpolicies:", error);
|
|
1219
|
+
if (error instanceof errors.NotAllowedError) {
|
|
1220
|
+
res.status(403).json({ error: error.message });
|
|
1221
|
+
} else {
|
|
1222
|
+
res.status(500).json({ error: "failed to fetch authpolicies" });
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
router.get("/ratelimitpolicies", async (req, res) => {
|
|
1227
|
+
try {
|
|
1228
|
+
const credentials = await httpAuth.credentials(req);
|
|
1229
|
+
const decision = await permissions$1.authorize(
|
|
1230
|
+
[{ permission: permissions.kuadrantRateLimitPolicyListPermission }],
|
|
1231
|
+
{ credentials }
|
|
1232
|
+
);
|
|
1233
|
+
if (decision[0].result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
1234
|
+
throw new errors.NotAllowedError("unauthorised");
|
|
1235
|
+
}
|
|
1236
|
+
const data = await k8sClient$1.listCustomResources("kuadrant.io", "v1", "ratelimitpolicies");
|
|
1237
|
+
const filtered = {
|
|
1238
|
+
items: (data.items || []).map((policy) => ({
|
|
1239
|
+
metadata: {
|
|
1240
|
+
name: policy.metadata.name,
|
|
1241
|
+
namespace: policy.metadata.namespace
|
|
1242
|
+
},
|
|
1243
|
+
spec: policy.spec ? {
|
|
1244
|
+
// expose targetRef to allow UI to match AuthPolicy -> HTTPRoute
|
|
1245
|
+
targetRef: policy.spec.targetRef ? {
|
|
1246
|
+
kind: policy.spec.targetRef.kind,
|
|
1247
|
+
name: policy.spec.targetRef.name,
|
|
1248
|
+
namespace: policy.spec.targetRef.namespace
|
|
1249
|
+
} : void 0
|
|
1250
|
+
} : {},
|
|
1251
|
+
status: policy.status
|
|
1252
|
+
}))
|
|
1253
|
+
};
|
|
1254
|
+
res.json(filtered);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
console.error("error fetching ratelimitpolicies:", error);
|
|
1257
|
+
if (error instanceof errors.NotAllowedError) {
|
|
1258
|
+
res.status(403).json({ error: error.message });
|
|
1259
|
+
} else {
|
|
1260
|
+
res.status(500).json({ error: "failed to fetch ratelimitpolicies" });
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1144
1264
|
router.use(pluginPermissionNode.createPermissionIntegrationRouter({
|
|
1145
1265
|
permissions: permissions.kuadrantPermissions
|
|
1146
1266
|
}));
|