@backstage/plugin-catalog-backend 0.0.0-nightly-202201323044 → 0.0.0-nightly-202201422556

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,6 +1,15 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
- ## 0.0.0-nightly-202201323044
3
+ ## 0.0.0-nightly-202201422556
4
+
5
+ ### Patch Changes
6
+
7
+ - 68edbbeafd: Fix bug with resource loading in permission integration
8
+ - Updated dependencies
9
+ - @backstage/backend-common@0.0.0-nightly-202201422556
10
+ - @backstage/config@0.0.0-nightly-202201422556
11
+
12
+ ## 0.20.0
4
13
 
5
14
  ### Minor Changes
6
15
 
@@ -12,20 +21,19 @@
12
21
 
13
22
  - 0ae759dad4: Add catalog permission rules.
14
23
  - 3b4d8caff6: Allow a custom GithubCredentialsProvider to be passed to the GitHub processors.
24
+ - 6fd70f8bc8: Provide support for Bitbucket servers with custom BaseURLs.
15
25
  - 5333451def: Cleaned up API exports
16
26
  - 730d01ab1a: Add apply-conditions endpoint for evaluating conditional permissions in catalog backend.
27
+ - 0a6c68582a: Add authorization to catalog-backend entities GET endpoints
17
28
  - Updated dependencies
18
- - @backstage/config@0.0.0-nightly-202201323044
19
- - @backstage/integration@0.0.0-nightly-202201323044
20
- - @backstage/backend-common@0.0.0-nightly-202201323044
21
- - @backstage/plugin-permission-node@0.0.0-nightly-202201323044
22
- - @backstage/errors@0.0.0-nightly-202201323044
23
- - @backstage/catalog-client@0.0.0-nightly-202201323044
24
- - @backstage/catalog-model@0.0.0-nightly-202201323044
25
- - @backstage/search-common@0.0.0-nightly-202201323044
26
- - @backstage/types@0.0.0-nightly-202201323044
27
- - @backstage/plugin-catalog-common@0.0.0-nightly-202201323044
28
- - @backstage/plugin-permission-common@0.0.0-nightly-202201323044
29
+ - @backstage/config@0.1.12
30
+ - @backstage/integration@0.7.1
31
+ - @backstage/backend-common@0.10.3
32
+ - @backstage/plugin-permission-node@0.3.0
33
+ - @backstage/errors@0.2.0
34
+ - @backstage/catalog-client@0.5.4
35
+ - @backstage/catalog-model@0.9.9
36
+ - @backstage/plugin-permission-common@0.3.1
29
37
 
30
38
  ## 0.19.4
31
39
 
package/dist/index.cjs.js CHANGED
@@ -22,8 +22,6 @@ var backendCommon = require('@backstage/backend-common');
22
22
  var yaml = require('yaml');
23
23
  var uuid = require('uuid');
24
24
  var knexFactory = require('knex');
25
- var pluginCatalogCommon = require('@backstage/plugin-catalog-common');
26
- var pluginPermissionNode = require('@backstage/plugin-permission-node');
27
25
  var express = require('express');
28
26
  var Router = require('express-promise-router');
29
27
  var yn = require('yn');
@@ -32,7 +30,9 @@ var luxon = require('luxon');
32
30
  var promClient = require('prom-client');
33
31
  var stableStringify = require('fast-json-stable-stringify');
34
32
  var catalogClient = require('@backstage/catalog-client');
33
+ var pluginCatalogCommon = require('@backstage/plugin-catalog-common');
35
34
  var pluginPermissionCommon = require('@backstage/plugin-permission-common');
35
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
36
36
 
37
37
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
38
38
 
@@ -607,12 +607,13 @@ async function readBitbucketCloud(client, target) {
607
607
  }
608
608
  function parseUrl$3(urlString) {
609
609
  const url = new URL(urlString);
610
- const path = url.pathname.substr(1).split("/");
610
+ const indexOfProjectSegment = url.pathname.toLowerCase().indexOf("/projects/") + 1;
611
+ const path = url.pathname.substr(indexOfProjectSegment).split("/");
611
612
  if (path.length > 3 && path[1].length && path[3].length) {
612
613
  return {
613
614
  projectSearchPath: escapeRegExp$1(decodeURIComponent(path[1])),
614
615
  repoSearchPath: escapeRegExp$1(decodeURIComponent(path[3])),
615
- catalogPath: `/${decodeURIComponent(path.slice(4).join("/"))}`
616
+ catalogPath: `/${decodeURIComponent(path.slice(4).join("/") + url.search)}`
616
617
  };
617
618
  }
618
619
  throw new Error(`Failed to parse ${urlString}`);
@@ -3110,7 +3111,7 @@ async function createNextRouter(options) {
3110
3111
  refreshService,
3111
3112
  config,
3112
3113
  logger,
3113
- permissionRules
3114
+ permissionIntegrationRouter
3114
3115
  } = options;
3115
3116
  const router = Router__default["default"]();
3116
3117
  router.use(express__default["default"].json());
@@ -3126,16 +3127,16 @@ async function createNextRouter(options) {
3126
3127
  res.status(200).send();
3127
3128
  });
3128
3129
  }
3130
+ if (permissionIntegrationRouter) {
3131
+ router.use(permissionIntegrationRouter);
3132
+ }
3129
3133
  if (entitiesCatalog) {
3130
- router.use(pluginPermissionNode.createPermissionIntegrationRouter({
3131
- resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
3132
- getResource: (resourceRef) => getEntityResource(resourceRef, entitiesCatalog),
3133
- rules: permissionRules != null ? permissionRules : []
3134
- })).get("/entities", async (req, res) => {
3134
+ router.get("/entities", async (req, res) => {
3135
3135
  const { entities, pageInfo } = await entitiesCatalog.entities({
3136
3136
  filter: parseEntityFilterParams(req.query),
3137
3137
  fields: parseEntityTransformParams(req.query),
3138
- pagination: parseEntityPaginationParams(req.query)
3138
+ pagination: parseEntityPaginationParams(req.query),
3139
+ authorizationToken: getBearerToken(req.header("authorization"))
3139
3140
  });
3140
3141
  if (pageInfo.hasNextPage) {
3141
3142
  const url = new URL(`http://ignored${req.url}`);
@@ -3147,7 +3148,8 @@ async function createNextRouter(options) {
3147
3148
  }).get("/entities/by-uid/:uid", async (req, res) => {
3148
3149
  const { uid } = req.params;
3149
3150
  const { entities } = await entitiesCatalog.entities({
3150
- filter: basicEntityFilter({ "metadata.uid": uid })
3151
+ filter: basicEntityFilter({ "metadata.uid": uid }),
3152
+ authorizationToken: getBearerToken(req.header("authorization"))
3151
3153
  });
3152
3154
  if (!entities.length) {
3153
3155
  throw new errors.NotFoundError(`No entity with uid ${uid}`);
@@ -3164,7 +3166,8 @@ async function createNextRouter(options) {
3164
3166
  kind,
3165
3167
  "metadata.namespace": namespace,
3166
3168
  "metadata.name": name
3167
- })
3169
+ }),
3170
+ authorizationToken: getBearerToken(req.header("authorization"))
3168
3171
  });
3169
3172
  if (!entities.length) {
3170
3173
  throw new errors.NotFoundError(`No entity named '${name}' found, with kind '${kind}' in namespace '${namespace}'`);
@@ -3210,17 +3213,6 @@ async function createNextRouter(options) {
3210
3213
  router.use(backendCommon.errorHandler());
3211
3214
  return router;
3212
3215
  }
3213
- async function getEntityResource(resourceRef, entitiesCatalog) {
3214
- const parsed = catalogModel.parseEntityRef(resourceRef);
3215
- const { entities } = await entitiesCatalog.entities({
3216
- filter: basicEntityFilter({
3217
- kind: parsed.kind,
3218
- "metadata.namespace": parsed.namespace,
3219
- "metadata.name": parsed.name
3220
- })
3221
- });
3222
- return entities[0];
3223
- }
3224
3216
  function getBearerToken(authorizationHeader) {
3225
3217
  if (typeof authorizationHeader !== "string") {
3226
3218
  return void 0;
@@ -5042,6 +5034,37 @@ const permissionRules = {
5042
5034
  isEntityOwner
5043
5035
  };
5044
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
+
5045
5068
  class NextCatalogBuilder {
5046
5069
  constructor(env) {
5047
5070
  this.refreshInterval = createRandomRefreshInterval({
@@ -5161,7 +5184,28 @@ class NextCatalogBuilder {
5161
5184
  parser,
5162
5185
  policy
5163
5186
  });
5164
- 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 unauthorizedEntitiesCatalog.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
+ });
5165
5209
  const stitcher = new Stitcher(dbClient, logger);
5166
5210
  const locationStore = new DefaultLocationStore(dbClient);
5167
5211
  const configLocationProvider = new ConfigLocationEntityProvider(config);
@@ -5178,7 +5222,7 @@ class NextCatalogBuilder {
5178
5222
  refreshService,
5179
5223
  logger,
5180
5224
  config,
5181
- permissionRules: this.permissionRules
5225
+ permissionIntegrationRouter
5182
5226
  });
5183
5227
  await connectEntityProviders(processingDatabase, entityProviders);
5184
5228
  return {