@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 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 path = url.pathname.substr(1).split("/");
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 integration.GithubCredentialsProvider.create(gitHubConfig).getCredentials({ url: orgUrl });
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 credentialsProvider = integration.GithubCredentialsProvider.create(gitHubConfig);
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 credentialsProvider.getCredentials({
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 (!project.archived) {
1517
- result.matches.push(project);
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.credentialsProvider = integration.GithubCredentialsProvider.create(options.gitHubConfig);
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.credentialsProvider.getCredentials({
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 entity = locationSpecToLocationEntity(i.location, this.parentEntity);
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: "required"
4588
- }, false, context.collector.onEmit, this.options.parser, context.cache.forProcessor(processor, target));
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, { logger }),
4970
- GithubOrgReaderProcessor.fromConfig(config, { logger }),
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 entitiesCatalog = new NextEntitiesCatalog(dbClient);
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, { logger }), AzureDevOpsDiscoveryProcessor.fromConfig(config, { logger }), GithubOrgReaderProcessor.fromConfig(config, { logger }), GitLabDiscoveryProcessor.fromConfig(config, { logger }), new UrlReaderProcessor({ reader, logger }), CodeOwnersProcessor.fromConfig(config, { logger, reader }), new LocationEntityProcessor({ integrations }), new AnnotateLocationEntityProcessor({ integrations }));
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