@backstage/plugin-catalog-backend 0.19.1 → 0.20.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/CHANGELOG.md +53 -0
- package/dist/index.cjs.js +242 -34
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +89 -43
- package/package.json +15 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cd529c4094: In order to integrate the permissions system with the refresh endpoint in catalog-backend, a new AuthorizedRefreshService was created as a thin wrapper around the existing refresh service which performs authorization and handles the case when authorization is denied. In order to instantiate AuthorizedRefreshService, a permission client is required, which was added as a new field to `CatalogEnvironment`.
|
|
8
|
+
|
|
9
|
+
The new `permissions` field in `CatalogEnvironment` should already receive the permission client from the `PluginEnvrionment`, so there should be no changes required to the catalog backend setup. See [the create-app changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md) for more details.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0ae759dad4: Add catalog permission rules.
|
|
14
|
+
- 3b4d8caff6: Allow a custom GithubCredentialsProvider to be passed to the GitHub processors.
|
|
15
|
+
- 6fd70f8bc8: Provide support for Bitbucket servers with custom BaseURLs.
|
|
16
|
+
- 5333451def: Cleaned up API exports
|
|
17
|
+
- 730d01ab1a: Add apply-conditions endpoint for evaluating conditional permissions in catalog backend.
|
|
18
|
+
- 0a6c68582a: Add authorization to catalog-backend entities GET endpoints
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @backstage/config@0.1.12
|
|
21
|
+
- @backstage/integration@0.7.1
|
|
22
|
+
- @backstage/backend-common@0.10.3
|
|
23
|
+
- @backstage/plugin-permission-node@0.3.0
|
|
24
|
+
- @backstage/errors@0.2.0
|
|
25
|
+
- @backstage/catalog-client@0.5.4
|
|
26
|
+
- @backstage/catalog-model@0.9.9
|
|
27
|
+
- @backstage/plugin-permission-common@0.3.1
|
|
28
|
+
|
|
29
|
+
## 0.19.4
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- 7d4b4e937c: Uptake changes to the GitHub Credentials Provider interface.
|
|
34
|
+
- 3a63491c5f: Filter out projects with missing `default_branch` from GitLab Discovery.
|
|
35
|
+
- Updated dependencies
|
|
36
|
+
- @backstage/backend-common@0.10.1
|
|
37
|
+
- @backstage/integration@0.7.0
|
|
38
|
+
|
|
39
|
+
## 0.19.3
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- Updated dependencies
|
|
44
|
+
- @backstage/backend-common@0.10.0
|
|
45
|
+
- @backstage/catalog-client@0.5.3
|
|
46
|
+
|
|
47
|
+
## 0.19.2
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- 3368f27aef: Fixed the handling of optional locations so that the catalog no longer logs `NotFoundError`s for missing optional locations.
|
|
52
|
+
- Updated dependencies
|
|
53
|
+
- @backstage/backend-common@0.9.14
|
|
54
|
+
- @backstage/catalog-model@0.9.8
|
|
55
|
+
|
|
3
56
|
## 0.19.1
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -30,6 +30,9 @@ var luxon = require('luxon');
|
|
|
30
30
|
var promClient = require('prom-client');
|
|
31
31
|
var stableStringify = require('fast-json-stable-stringify');
|
|
32
32
|
var catalogClient = require('@backstage/catalog-client');
|
|
33
|
+
var pluginCatalogCommon = require('@backstage/plugin-catalog-common');
|
|
34
|
+
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
35
|
+
var pluginPermissionNode = require('@backstage/plugin-permission-node');
|
|
33
36
|
|
|
34
37
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
35
38
|
|
|
@@ -472,7 +475,8 @@ async function* paginated20(request, options) {
|
|
|
472
475
|
const defaultRepositoryParser = async function* defaultRepositoryParser2({ target }) {
|
|
473
476
|
yield location({
|
|
474
477
|
type: "url",
|
|
475
|
-
target
|
|
478
|
+
target,
|
|
479
|
+
presence: "optional"
|
|
476
480
|
}, true);
|
|
477
481
|
};
|
|
478
482
|
|
|
@@ -603,12 +607,13 @@ async function readBitbucketCloud(client, target) {
|
|
|
603
607
|
}
|
|
604
608
|
function parseUrl$3(urlString) {
|
|
605
609
|
const url = new URL(urlString);
|
|
606
|
-
const
|
|
610
|
+
const indexOfProjectSegment = url.pathname.toLowerCase().indexOf("/projects/") + 1;
|
|
611
|
+
const path = url.pathname.substr(indexOfProjectSegment).split("/");
|
|
607
612
|
if (path.length > 3 && path[1].length && path[3].length) {
|
|
608
613
|
return {
|
|
609
614
|
projectSearchPath: escapeRegExp$1(decodeURIComponent(path[1])),
|
|
610
615
|
repoSearchPath: escapeRegExp$1(decodeURIComponent(path[3])),
|
|
611
|
-
catalogPath: `/${decodeURIComponent(path.slice(4).join("/"))}`
|
|
616
|
+
catalogPath: `/${decodeURIComponent(path.slice(4).join("/") + url.search)}`
|
|
612
617
|
};
|
|
613
618
|
}
|
|
614
619
|
throw new Error(`Failed to parse ${urlString}`);
|
|
@@ -1103,6 +1108,7 @@ class GithubDiscoveryProcessor {
|
|
|
1103
1108
|
constructor(options) {
|
|
1104
1109
|
this.integrations = options.integrations;
|
|
1105
1110
|
this.logger = options.logger;
|
|
1111
|
+
this.githubCredentialsProvider = options.githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
|
|
1106
1112
|
}
|
|
1107
1113
|
async readLocation(location$1, _optional, emit) {
|
|
1108
1114
|
var _a, _b;
|
|
@@ -1115,7 +1121,9 @@ class GithubDiscoveryProcessor {
|
|
|
1115
1121
|
}
|
|
1116
1122
|
const { org, repoSearchPath, catalogPath, branch, host } = parseUrl$2(location$1.target);
|
|
1117
1123
|
const orgUrl = `https://${host}/${org}`;
|
|
1118
|
-
const { headers } = await
|
|
1124
|
+
const { headers } = await this.githubCredentialsProvider.getCredentials({
|
|
1125
|
+
url: orgUrl
|
|
1126
|
+
});
|
|
1119
1127
|
const client = graphql.graphql.defaults({
|
|
1120
1128
|
baseUrl: gitHubConfig.apiBaseUrl,
|
|
1121
1129
|
headers
|
|
@@ -1135,7 +1143,8 @@ class GithubDiscoveryProcessor {
|
|
|
1135
1143
|
const path = `/blob/${branchName}${catalogPath}`;
|
|
1136
1144
|
emit(location({
|
|
1137
1145
|
type: "url",
|
|
1138
|
-
target: `${repository.url}${path}
|
|
1146
|
+
target: `${repository.url}${path}`,
|
|
1147
|
+
presence: "optional"
|
|
1139
1148
|
}, true));
|
|
1140
1149
|
}
|
|
1141
1150
|
return true;
|
|
@@ -1224,7 +1233,8 @@ class AzureDevOpsDiscoveryProcessor {
|
|
|
1224
1233
|
for (const file of files) {
|
|
1225
1234
|
emit(location({
|
|
1226
1235
|
type: "url",
|
|
1227
|
-
target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}
|
|
1236
|
+
target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`,
|
|
1237
|
+
presence: "optional"
|
|
1228
1238
|
}, true));
|
|
1229
1239
|
}
|
|
1230
1240
|
return true;
|
|
@@ -1298,6 +1308,7 @@ class GithubOrgReaderProcessor {
|
|
|
1298
1308
|
}
|
|
1299
1309
|
constructor(options) {
|
|
1300
1310
|
this.integrations = options.integrations;
|
|
1311
|
+
this.githubCredentialsProvider = options.githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
|
|
1301
1312
|
this.logger = options.logger;
|
|
1302
1313
|
}
|
|
1303
1314
|
async readLocation(location, _optional, emit) {
|
|
@@ -1328,8 +1339,7 @@ class GithubOrgReaderProcessor {
|
|
|
1328
1339
|
if (!gitHubConfig) {
|
|
1329
1340
|
throw new Error(`There is no GitHub Org provider that matches ${orgUrl}. Please add a configuration for an integration.`);
|
|
1330
1341
|
}
|
|
1331
|
-
const
|
|
1332
|
-
const { headers, type: tokenType } = await credentialsProvider.getCredentials({
|
|
1342
|
+
const { headers, type: tokenType } = await this.githubCredentialsProvider.getCredentials({
|
|
1333
1343
|
url: orgUrl
|
|
1334
1344
|
});
|
|
1335
1345
|
const client = graphql.graphql.defaults({
|
|
@@ -1354,6 +1364,7 @@ class GithubMultiOrgReaderProcessor {
|
|
|
1354
1364
|
this.integrations = options.integrations;
|
|
1355
1365
|
this.logger = options.logger;
|
|
1356
1366
|
this.orgs = options.orgs;
|
|
1367
|
+
this.githubCredentialsProvider = options.githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
|
|
1357
1368
|
}
|
|
1358
1369
|
async readLocation(location, _optional, emit) {
|
|
1359
1370
|
var _a, _b;
|
|
@@ -1366,11 +1377,10 @@ class GithubMultiOrgReaderProcessor {
|
|
|
1366
1377
|
}
|
|
1367
1378
|
const allUsersMap = /* @__PURE__ */ new Map();
|
|
1368
1379
|
const baseUrl = new URL(location.target).origin;
|
|
1369
|
-
const credentialsProvider = integration.GithubCredentialsProvider.create(gitHubConfig);
|
|
1370
1380
|
const orgsToProcess = this.orgs.length ? this.orgs : await this.getAllOrgs(gitHubConfig);
|
|
1371
1381
|
for (const orgConfig of orgsToProcess) {
|
|
1372
1382
|
try {
|
|
1373
|
-
const { headers, type: tokenType } = await
|
|
1383
|
+
const { headers, type: tokenType } = await this.githubCredentialsProvider.getCredentials({
|
|
1374
1384
|
url: `${baseUrl}/${orgConfig.name}`
|
|
1375
1385
|
});
|
|
1376
1386
|
const client = graphql.graphql.defaults({
|
|
@@ -1513,15 +1523,20 @@ class GitLabDiscoveryProcessor {
|
|
|
1513
1523
|
};
|
|
1514
1524
|
for await (const project of projects) {
|
|
1515
1525
|
result.scanned++;
|
|
1516
|
-
if (
|
|
1517
|
-
|
|
1526
|
+
if (project.archived) {
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
if (branch === "*" && project.default_branch === void 0) {
|
|
1530
|
+
continue;
|
|
1518
1531
|
}
|
|
1532
|
+
result.matches.push(project);
|
|
1519
1533
|
}
|
|
1520
1534
|
for (const project of result.matches) {
|
|
1521
1535
|
const project_branch = branch === "*" ? project.default_branch : branch;
|
|
1522
1536
|
emit(location({
|
|
1523
1537
|
type: "url",
|
|
1524
|
-
target: `${project.web_url}/-/blob/${project_branch}/${catalogPath}
|
|
1538
|
+
target: `${project.web_url}/-/blob/${project_branch}/${catalogPath}`,
|
|
1539
|
+
presence: "optional"
|
|
1525
1540
|
}, true));
|
|
1526
1541
|
}
|
|
1527
1542
|
const duration = ((Date.now() - startTimestamp) / 1e3).toFixed(1);
|
|
@@ -1831,7 +1846,7 @@ const defaultEntityDataParser = async function* defaultEntityDataParser2({ data,
|
|
|
1831
1846
|
class GitHubOrgEntityProvider {
|
|
1832
1847
|
constructor(options) {
|
|
1833
1848
|
this.options = options;
|
|
1834
|
-
this.
|
|
1849
|
+
this.githubCredentialsProvider = options.githubCredentialsProvider || integration.SingleInstanceGithubCredentialsProvider.create(options.gitHubConfig);
|
|
1835
1850
|
}
|
|
1836
1851
|
static fromConfig(config, options) {
|
|
1837
1852
|
var _a;
|
|
@@ -1847,7 +1862,8 @@ class GitHubOrgEntityProvider {
|
|
|
1847
1862
|
id: options.id,
|
|
1848
1863
|
orgUrl: options.orgUrl,
|
|
1849
1864
|
logger,
|
|
1850
|
-
gitHubConfig
|
|
1865
|
+
gitHubConfig,
|
|
1866
|
+
githubCredentialsProvider: options.githubCredentialsProvider || integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations)
|
|
1851
1867
|
});
|
|
1852
1868
|
}
|
|
1853
1869
|
getProviderName() {
|
|
@@ -1861,7 +1877,7 @@ class GitHubOrgEntityProvider {
|
|
|
1861
1877
|
throw new Error("Not initialized");
|
|
1862
1878
|
}
|
|
1863
1879
|
const { markReadComplete } = trackProgress(this.options.logger);
|
|
1864
|
-
const { headers, type: tokenType } = await this.
|
|
1880
|
+
const { headers, type: tokenType } = await this.githubCredentialsProvider.getCredentials({
|
|
1865
1881
|
url: this.options.orgUrl
|
|
1866
1882
|
});
|
|
1867
1883
|
const client = graphql.graphql.defaults({
|
|
@@ -3094,7 +3110,8 @@ async function createNextRouter(options) {
|
|
|
3094
3110
|
locationService,
|
|
3095
3111
|
refreshService,
|
|
3096
3112
|
config,
|
|
3097
|
-
logger
|
|
3113
|
+
logger,
|
|
3114
|
+
permissionIntegrationRouter
|
|
3098
3115
|
} = options;
|
|
3099
3116
|
const router = Router__default["default"]();
|
|
3100
3117
|
router.use(express__default["default"].json());
|
|
@@ -3105,16 +3122,21 @@ async function createNextRouter(options) {
|
|
|
3105
3122
|
if (refreshService) {
|
|
3106
3123
|
router.post("/refresh", async (req, res) => {
|
|
3107
3124
|
const refreshOptions = req.body;
|
|
3125
|
+
refreshOptions.authorizationToken = getBearerToken(req.header("authorization"));
|
|
3108
3126
|
await refreshService.refresh(refreshOptions);
|
|
3109
3127
|
res.status(200).send();
|
|
3110
3128
|
});
|
|
3111
3129
|
}
|
|
3130
|
+
if (permissionIntegrationRouter) {
|
|
3131
|
+
router.use(permissionIntegrationRouter);
|
|
3132
|
+
}
|
|
3112
3133
|
if (entitiesCatalog) {
|
|
3113
3134
|
router.get("/entities", async (req, res) => {
|
|
3114
3135
|
const { entities, pageInfo } = await entitiesCatalog.entities({
|
|
3115
3136
|
filter: parseEntityFilterParams(req.query),
|
|
3116
3137
|
fields: parseEntityTransformParams(req.query),
|
|
3117
|
-
pagination: parseEntityPaginationParams(req.query)
|
|
3138
|
+
pagination: parseEntityPaginationParams(req.query),
|
|
3139
|
+
authorizationToken: getBearerToken(req.header("authorization"))
|
|
3118
3140
|
});
|
|
3119
3141
|
if (pageInfo.hasNextPage) {
|
|
3120
3142
|
const url = new URL(`http://ignored${req.url}`);
|
|
@@ -3126,7 +3148,8 @@ async function createNextRouter(options) {
|
|
|
3126
3148
|
}).get("/entities/by-uid/:uid", async (req, res) => {
|
|
3127
3149
|
const { uid } = req.params;
|
|
3128
3150
|
const { entities } = await entitiesCatalog.entities({
|
|
3129
|
-
filter: basicEntityFilter({ "metadata.uid": uid })
|
|
3151
|
+
filter: basicEntityFilter({ "metadata.uid": uid }),
|
|
3152
|
+
authorizationToken: getBearerToken(req.header("authorization"))
|
|
3130
3153
|
});
|
|
3131
3154
|
if (!entities.length) {
|
|
3132
3155
|
throw new errors.NotFoundError(`No entity with uid ${uid}`);
|
|
@@ -3143,7 +3166,8 @@ async function createNextRouter(options) {
|
|
|
3143
3166
|
kind,
|
|
3144
3167
|
"metadata.namespace": namespace,
|
|
3145
3168
|
"metadata.name": name
|
|
3146
|
-
})
|
|
3169
|
+
}),
|
|
3170
|
+
authorizationToken: getBearerToken(req.header("authorization"))
|
|
3147
3171
|
});
|
|
3148
3172
|
if (!entities.length) {
|
|
3149
3173
|
throw new errors.NotFoundError(`No entity named '${name}' found, with kind '${kind}' in namespace '${namespace}'`);
|
|
@@ -3189,6 +3213,13 @@ async function createNextRouter(options) {
|
|
|
3189
3213
|
router.use(backendCommon.errorHandler());
|
|
3190
3214
|
return router;
|
|
3191
3215
|
}
|
|
3216
|
+
function getBearerToken(authorizationHeader) {
|
|
3217
|
+
if (typeof authorizationHeader !== "string") {
|
|
3218
|
+
return void 0;
|
|
3219
|
+
}
|
|
3220
|
+
const matches = authorizationHeader.match(/Bearer\s+(\S+)/i);
|
|
3221
|
+
return matches == null ? void 0 : matches[1];
|
|
3222
|
+
}
|
|
3192
3223
|
|
|
3193
3224
|
function isLocationEntity(entity) {
|
|
3194
3225
|
return entity.kind === "Location";
|
|
@@ -3270,7 +3301,8 @@ function locationSpecToLocationEntity(location, parentEntity) {
|
|
|
3270
3301
|
},
|
|
3271
3302
|
spec: {
|
|
3272
3303
|
type: location.type,
|
|
3273
|
-
target: location.target
|
|
3304
|
+
target: location.target,
|
|
3305
|
+
presence: location.presence
|
|
3274
3306
|
}
|
|
3275
3307
|
};
|
|
3276
3308
|
return result;
|
|
@@ -4366,7 +4398,8 @@ class ProcessorOutputCollector {
|
|
|
4366
4398
|
}
|
|
4367
4399
|
this.deferredEntities.push({ entity, locationKey: location });
|
|
4368
4400
|
} else if (i.type === "location") {
|
|
4369
|
-
const
|
|
4401
|
+
const presence = i.optional ? "optional" : "required";
|
|
4402
|
+
const entity = locationSpecToLocationEntity({ presence, ...i.location }, this.parentEntity);
|
|
4370
4403
|
const locationKey = getEntityLocationRef(entity);
|
|
4371
4404
|
this.deferredEntities.push({ entity, locationKey });
|
|
4372
4405
|
} else if (i.type === "relation") {
|
|
@@ -4563,7 +4596,7 @@ class DefaultCatalogProcessingOrchestrator {
|
|
|
4563
4596
|
}
|
|
4564
4597
|
}
|
|
4565
4598
|
async runSpecialLocationStep(entity, context) {
|
|
4566
|
-
const { type = context.location.type } = entity.spec;
|
|
4599
|
+
const { type = context.location.type, presence = "required" } = entity.spec;
|
|
4567
4600
|
const targets = new Array();
|
|
4568
4601
|
if (entity.spec.target) {
|
|
4569
4602
|
targets.push(entity.spec.target);
|
|
@@ -4584,8 +4617,8 @@ class DefaultCatalogProcessingOrchestrator {
|
|
|
4584
4617
|
const read = await processor.readLocation({
|
|
4585
4618
|
type,
|
|
4586
4619
|
target,
|
|
4587
|
-
presence
|
|
4588
|
-
},
|
|
4620
|
+
presence
|
|
4621
|
+
}, presence === "optional", context.collector.onEmit, this.options.parser, context.cache.forProcessor(processor, target));
|
|
4589
4622
|
if (read) {
|
|
4590
4623
|
didRead = true;
|
|
4591
4624
|
break;
|
|
@@ -4848,6 +4881,25 @@ class DefaultRefreshService {
|
|
|
4848
4881
|
}
|
|
4849
4882
|
}
|
|
4850
4883
|
|
|
4884
|
+
class AuthorizedRefreshService {
|
|
4885
|
+
constructor(service, permissionApi) {
|
|
4886
|
+
this.service = service;
|
|
4887
|
+
this.permissionApi = permissionApi;
|
|
4888
|
+
}
|
|
4889
|
+
async refresh(options) {
|
|
4890
|
+
const authorizeResponse = (await this.permissionApi.authorize([
|
|
4891
|
+
{
|
|
4892
|
+
permission: pluginCatalogCommon.catalogEntityRefreshPermission,
|
|
4893
|
+
resourceRef: options.entityRef
|
|
4894
|
+
}
|
|
4895
|
+
], { token: options.authorizationToken }))[0];
|
|
4896
|
+
if (authorizeResponse.result !== pluginPermissionCommon.AuthorizeResult.ALLOW) {
|
|
4897
|
+
throw new errors.NotAllowedError();
|
|
4898
|
+
}
|
|
4899
|
+
await this.service.refresh(options);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4851
4903
|
class Connection {
|
|
4852
4904
|
constructor(config) {
|
|
4853
4905
|
this.config = config;
|
|
@@ -4897,6 +4949,122 @@ async function connectEntityProviders(db, providers) {
|
|
|
4897
4949
|
}));
|
|
4898
4950
|
}
|
|
4899
4951
|
|
|
4952
|
+
const createCatalogPermissionRule = pluginPermissionNode.makeCreatePermissionRule();
|
|
4953
|
+
|
|
4954
|
+
const hasAnnotation = createCatalogPermissionRule({
|
|
4955
|
+
name: "HAS_ANNOTATION",
|
|
4956
|
+
description: "Allow entities which are annotated with the specified annotation",
|
|
4957
|
+
apply: (resource, annotation) => {
|
|
4958
|
+
var _a;
|
|
4959
|
+
return !!((_a = resource.metadata.annotations) == null ? void 0 : _a.hasOwnProperty(annotation));
|
|
4960
|
+
},
|
|
4961
|
+
toQuery: (annotation) => ({
|
|
4962
|
+
key: `metadata.annotations.${annotation}`
|
|
4963
|
+
})
|
|
4964
|
+
});
|
|
4965
|
+
|
|
4966
|
+
const isEntityKind = createCatalogPermissionRule({
|
|
4967
|
+
name: "IS_ENTITY_KIND",
|
|
4968
|
+
description: "Allow entities with the specified kind",
|
|
4969
|
+
apply(resource, kinds) {
|
|
4970
|
+
const resourceKind = resource.kind.toLocaleLowerCase("en-US");
|
|
4971
|
+
return kinds.some((kind) => kind.toLocaleLowerCase("en-US") === resourceKind);
|
|
4972
|
+
},
|
|
4973
|
+
toQuery(kinds) {
|
|
4974
|
+
return {
|
|
4975
|
+
key: "kind",
|
|
4976
|
+
values: kinds.map((kind) => kind.toLocaleLowerCase("en-US"))
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
});
|
|
4980
|
+
|
|
4981
|
+
const isEntityOwner = createCatalogPermissionRule({
|
|
4982
|
+
name: "IS_ENTITY_OWNER",
|
|
4983
|
+
description: "Allow entities owned by the current user",
|
|
4984
|
+
apply: (resource, claims) => {
|
|
4985
|
+
if (!resource.relations) {
|
|
4986
|
+
return false;
|
|
4987
|
+
}
|
|
4988
|
+
return resource.relations.filter((relation) => relation.type === catalogModel.RELATION_OWNED_BY).some((relation) => claims.includes(catalogModel.stringifyEntityRef(relation.target)));
|
|
4989
|
+
},
|
|
4990
|
+
toQuery: (claims) => ({
|
|
4991
|
+
key: "relations.ownedBy",
|
|
4992
|
+
values: claims
|
|
4993
|
+
})
|
|
4994
|
+
});
|
|
4995
|
+
|
|
4996
|
+
const hasLabel = createCatalogPermissionRule({
|
|
4997
|
+
name: "HAS_LABEL",
|
|
4998
|
+
description: "Allow entities which have the specified label metadata.",
|
|
4999
|
+
apply: (resource, label) => {
|
|
5000
|
+
var _a;
|
|
5001
|
+
return !!((_a = resource.metadata.labels) == null ? void 0 : _a.hasOwnProperty(label));
|
|
5002
|
+
},
|
|
5003
|
+
toQuery: (label) => ({
|
|
5004
|
+
key: `metadata.labels.${label}`
|
|
5005
|
+
})
|
|
5006
|
+
});
|
|
5007
|
+
|
|
5008
|
+
const createPropertyRule = (propertyType) => createCatalogPermissionRule({
|
|
5009
|
+
name: `HAS_${propertyType.toUpperCase()}`,
|
|
5010
|
+
description: `Allow entities which have the specified ${propertyType} subfield.`,
|
|
5011
|
+
apply: (resource, key, value) => {
|
|
5012
|
+
const foundValue = lodash.get(resource[propertyType], key);
|
|
5013
|
+
if (value !== void 0) {
|
|
5014
|
+
return value === foundValue;
|
|
5015
|
+
}
|
|
5016
|
+
return !!foundValue;
|
|
5017
|
+
},
|
|
5018
|
+
toQuery: (key, value) => ({
|
|
5019
|
+
key: `${propertyType}.${key}`,
|
|
5020
|
+
...value !== void 0 && { values: [value] }
|
|
5021
|
+
})
|
|
5022
|
+
});
|
|
5023
|
+
|
|
5024
|
+
const hasMetadata = createPropertyRule("metadata");
|
|
5025
|
+
|
|
5026
|
+
const hasSpec = createPropertyRule("spec");
|
|
5027
|
+
|
|
5028
|
+
const permissionRules = {
|
|
5029
|
+
hasAnnotation,
|
|
5030
|
+
hasLabel,
|
|
5031
|
+
hasMetadata,
|
|
5032
|
+
hasSpec,
|
|
5033
|
+
isEntityKind,
|
|
5034
|
+
isEntityOwner
|
|
5035
|
+
};
|
|
5036
|
+
|
|
5037
|
+
class AuthorizedEntitiesCatalog {
|
|
5038
|
+
constructor(entitiesCatalog, permissionApi, transformConditions) {
|
|
5039
|
+
this.entitiesCatalog = entitiesCatalog;
|
|
5040
|
+
this.permissionApi = permissionApi;
|
|
5041
|
+
this.transformConditions = transformConditions;
|
|
5042
|
+
}
|
|
5043
|
+
async entities(request) {
|
|
5044
|
+
const authorizeResponse = (await this.permissionApi.authorize([{ permission: pluginCatalogCommon.catalogEntityReadPermission }], { token: request == null ? void 0 : request.authorizationToken }))[0];
|
|
5045
|
+
if (authorizeResponse.result === pluginPermissionCommon.AuthorizeResult.DENY) {
|
|
5046
|
+
return {
|
|
5047
|
+
entities: [],
|
|
5048
|
+
pageInfo: { hasNextPage: false }
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
if (authorizeResponse.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
|
|
5052
|
+
const permissionFilter = this.transformConditions(authorizeResponse.conditions);
|
|
5053
|
+
return this.entitiesCatalog.entities({
|
|
5054
|
+
...request,
|
|
5055
|
+
filter: (request == null ? void 0 : request.filter) ? { allOf: [permissionFilter, request.filter] } : permissionFilter
|
|
5056
|
+
});
|
|
5057
|
+
}
|
|
5058
|
+
return this.entitiesCatalog.entities(request);
|
|
5059
|
+
}
|
|
5060
|
+
removeEntityByUid(uid) {
|
|
5061
|
+
return this.entitiesCatalog.removeEntityByUid(uid);
|
|
5062
|
+
}
|
|
5063
|
+
entityAncestry(entityRef) {
|
|
5064
|
+
return this.entitiesCatalog.entityAncestry(entityRef);
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
|
|
4900
5068
|
class NextCatalogBuilder {
|
|
4901
5069
|
constructor(env) {
|
|
4902
5070
|
this.refreshInterval = createRandomRefreshInterval({
|
|
@@ -4913,6 +5081,7 @@ class NextCatalogBuilder {
|
|
|
4913
5081
|
this.processors = [];
|
|
4914
5082
|
this.processorsReplace = false;
|
|
4915
5083
|
this.parser = void 0;
|
|
5084
|
+
this.permissionRules = Object.values(permissionRules);
|
|
4916
5085
|
}
|
|
4917
5086
|
addEntityPolicy(...policies) {
|
|
4918
5087
|
this.entityPolicies.push(...policies);
|
|
@@ -4962,12 +5131,19 @@ class NextCatalogBuilder {
|
|
|
4962
5131
|
getDefaultProcessors() {
|
|
4963
5132
|
const { config, logger, reader } = this.env;
|
|
4964
5133
|
const integrations = integration.ScmIntegrations.fromConfig(config);
|
|
5134
|
+
const githubCredentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
|
4965
5135
|
return [
|
|
4966
5136
|
new FileReaderProcessor(),
|
|
4967
5137
|
BitbucketDiscoveryProcessor.fromConfig(config, { logger }),
|
|
4968
5138
|
AzureDevOpsDiscoveryProcessor.fromConfig(config, { logger }),
|
|
4969
|
-
GithubDiscoveryProcessor.fromConfig(config, {
|
|
4970
|
-
|
|
5139
|
+
GithubDiscoveryProcessor.fromConfig(config, {
|
|
5140
|
+
logger,
|
|
5141
|
+
githubCredentialsProvider
|
|
5142
|
+
}),
|
|
5143
|
+
GithubOrgReaderProcessor.fromConfig(config, {
|
|
5144
|
+
logger,
|
|
5145
|
+
githubCredentialsProvider
|
|
5146
|
+
}),
|
|
4971
5147
|
GitLabDiscoveryProcessor.fromConfig(config, { logger }),
|
|
4972
5148
|
new UrlReaderProcessor({ reader, logger }),
|
|
4973
5149
|
CodeOwnersProcessor.fromConfig(config, { logger, reader }),
|
|
@@ -4978,9 +5154,12 @@ class NextCatalogBuilder {
|
|
|
4978
5154
|
this.parser = parser;
|
|
4979
5155
|
return this;
|
|
4980
5156
|
}
|
|
5157
|
+
addPermissionRules(...permissionRules) {
|
|
5158
|
+
this.permissionRules.push(...permissionRules);
|
|
5159
|
+
}
|
|
4981
5160
|
async build() {
|
|
4982
5161
|
var _a, _b;
|
|
4983
|
-
const { config, database, logger } = this.env;
|
|
5162
|
+
const { config, database, logger, permissions } = this.env;
|
|
4984
5163
|
const policy = this.buildEntityPolicy();
|
|
4985
5164
|
const processors = this.buildProcessors();
|
|
4986
5165
|
const parser = this.parser || defaultEntityDataParser;
|
|
@@ -5005,7 +5184,28 @@ class NextCatalogBuilder {
|
|
|
5005
5184
|
parser,
|
|
5006
5185
|
policy
|
|
5007
5186
|
});
|
|
5008
|
-
const
|
|
5187
|
+
const unauthorizedEntitiesCatalog = new NextEntitiesCatalog(dbClient);
|
|
5188
|
+
const entitiesCatalog = new AuthorizedEntitiesCatalog(unauthorizedEntitiesCatalog, permissions, pluginPermissionNode.createConditionTransformer(this.permissionRules));
|
|
5189
|
+
const permissionIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
|
|
5190
|
+
resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
|
|
5191
|
+
getResources: async (resourceRefs) => {
|
|
5192
|
+
const { entities } = await entitiesCatalog.entities({
|
|
5193
|
+
filter: {
|
|
5194
|
+
anyOf: resourceRefs.map((resourceRef) => {
|
|
5195
|
+
const { kind, namespace, name } = catalogModel.parseEntityRef(resourceRef);
|
|
5196
|
+
return basicEntityFilter({
|
|
5197
|
+
kind,
|
|
5198
|
+
"metadata.namespace": namespace,
|
|
5199
|
+
"metadata.name": name
|
|
5200
|
+
});
|
|
5201
|
+
})
|
|
5202
|
+
}
|
|
5203
|
+
});
|
|
5204
|
+
const entitiesByRef = lodash.keyBy(entities, catalogModel.stringifyEntityRef);
|
|
5205
|
+
return resourceRefs.map((resourceRef) => entitiesByRef[catalogModel.stringifyEntityRef(catalogModel.parseEntityRef(resourceRef))]);
|
|
5206
|
+
},
|
|
5207
|
+
rules: this.permissionRules
|
|
5208
|
+
});
|
|
5009
5209
|
const stitcher = new Stitcher(dbClient, logger);
|
|
5010
5210
|
const locationStore = new DefaultLocationStore(dbClient);
|
|
5011
5211
|
const configLocationProvider = new ConfigLocationEntityProvider(config);
|
|
@@ -5014,16 +5214,15 @@ class NextCatalogBuilder {
|
|
|
5014
5214
|
const locationsCatalog = new DatabaseLocationsCatalog(db);
|
|
5015
5215
|
const locationAnalyzer = (_b = this.locationAnalyzer) != null ? _b : new RepoLocationAnalyzer(logger, integrations);
|
|
5016
5216
|
const locationService = new DefaultLocationService(locationStore, orchestrator);
|
|
5017
|
-
const refreshService = new DefaultRefreshService({
|
|
5018
|
-
database: processingDatabase
|
|
5019
|
-
});
|
|
5217
|
+
const refreshService = new AuthorizedRefreshService(new DefaultRefreshService({ database: processingDatabase }), permissions);
|
|
5020
5218
|
const router = await createNextRouter({
|
|
5021
5219
|
entitiesCatalog,
|
|
5022
5220
|
locationAnalyzer,
|
|
5023
5221
|
locationService,
|
|
5024
5222
|
refreshService,
|
|
5025
5223
|
logger,
|
|
5026
|
-
config
|
|
5224
|
+
config,
|
|
5225
|
+
permissionIntegrationRouter
|
|
5027
5226
|
});
|
|
5028
5227
|
await connectEntityProviders(processingDatabase, entityProviders);
|
|
5029
5228
|
return {
|
|
@@ -5170,6 +5369,7 @@ class CatalogBuilder {
|
|
|
5170
5369
|
buildProcessors() {
|
|
5171
5370
|
const { config, logger, reader } = this.env;
|
|
5172
5371
|
const integrations = integration.ScmIntegrations.fromConfig(config);
|
|
5372
|
+
const githubCredentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
|
5173
5373
|
this.checkDeprecatedReaderProcessors();
|
|
5174
5374
|
const placeholderResolvers = {
|
|
5175
5375
|
json: jsonPlaceholderResolver,
|
|
@@ -5187,7 +5387,13 @@ class CatalogBuilder {
|
|
|
5187
5387
|
new BuiltinKindsEntityProcessor()
|
|
5188
5388
|
];
|
|
5189
5389
|
if (!this.processorsReplace) {
|
|
5190
|
-
processors.push(new FileReaderProcessor(), BitbucketDiscoveryProcessor.fromConfig(config, { logger }), GithubDiscoveryProcessor.fromConfig(config, {
|
|
5390
|
+
processors.push(new FileReaderProcessor(), BitbucketDiscoveryProcessor.fromConfig(config, { logger }), GithubDiscoveryProcessor.fromConfig(config, {
|
|
5391
|
+
logger,
|
|
5392
|
+
githubCredentialsProvider
|
|
5393
|
+
}), AzureDevOpsDiscoveryProcessor.fromConfig(config, { logger }), GithubOrgReaderProcessor.fromConfig(config, {
|
|
5394
|
+
logger,
|
|
5395
|
+
githubCredentialsProvider
|
|
5396
|
+
}), GitLabDiscoveryProcessor.fromConfig(config, { logger }), new UrlReaderProcessor({ reader, logger }), CodeOwnersProcessor.fromConfig(config, { logger, reader }), new LocationEntityProcessor({ integrations }), new AnnotateLocationEntityProcessor({ integrations }));
|
|
5191
5397
|
}
|
|
5192
5398
|
processors.push(...this.processors);
|
|
5193
5399
|
return processors;
|
|
@@ -5441,11 +5647,13 @@ exports.NextCatalogBuilder = NextCatalogBuilder;
|
|
|
5441
5647
|
exports.PlaceholderProcessor = PlaceholderProcessor;
|
|
5442
5648
|
exports.StaticLocationProcessor = StaticLocationProcessor;
|
|
5443
5649
|
exports.UrlReaderProcessor = UrlReaderProcessor;
|
|
5650
|
+
exports.createCatalogPermissionRule = createCatalogPermissionRule;
|
|
5444
5651
|
exports.createNextRouter = createNextRouter;
|
|
5445
5652
|
exports.createRandomRefreshInterval = createRandomRefreshInterval;
|
|
5446
5653
|
exports.createRouter = createRouter;
|
|
5447
5654
|
exports.durationText = durationText;
|
|
5448
5655
|
exports.parseEntityYaml = parseEntityYaml;
|
|
5656
|
+
exports.permissionRules = permissionRules;
|
|
5449
5657
|
exports.results = results;
|
|
5450
5658
|
exports.runPeriodically = runPeriodically;
|
|
5451
5659
|
//# sourceMappingURL=index.cjs.js.map
|