@backstage/plugin-catalog-backend 1.7.2-next.1 → 1.7.2

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,56 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 1.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 071354eb7d: Add additional validation as security precations for output entities.
8
+ - b977c2e69f: Minor improvements to the descriptions provided with permission rules schemas
9
+ - 2380506364: The process of adding or modifying fields in the software-catalog search index has been simplified. For more details, see [how to customize fields in the Software Catalog index](https://backstage.io/docs/features/search/how-to-guides#how-to-customize-fields-in-the-software-catalog-index).
10
+ - 9573651919: The previous migration that adds the `search.original_value` column may leave some of the entities not updated. Add a migration script to trigger a reprocessing of the entities.
11
+ - 9f71a2fd20: Location rule target patterns now also match hidden files, i.e. path components with a leading dot.
12
+ - e716946103: Updated usage of the lifecycle service.
13
+ - 1aec041c34: Fixed an issue where entities sometimes were not properly deleted during a full mutation.
14
+ - 0ff03319be: Updated usage of `createBackendPlugin`.
15
+ - fc73f6aae5: Switched the order of reprocessing statements retroactively in migrations. This only improves the experience for those who at a later time perform a large upgrade of an old Backstage installation.
16
+ - Updated dependencies
17
+ - @backstage/backend-plugin-api@0.4.0
18
+ - @backstage/backend-common@0.18.2
19
+ - @backstage/catalog-model@1.2.0
20
+ - @backstage/plugin-catalog-node@1.3.3
21
+ - @backstage/catalog-client@1.3.1
22
+ - @backstage/config@1.0.6
23
+ - @backstage/errors@1.1.4
24
+ - @backstage/integration@1.4.2
25
+ - @backstage/types@1.0.2
26
+ - @backstage/plugin-catalog-common@1.0.11
27
+ - @backstage/plugin-permission-common@0.7.3
28
+ - @backstage/plugin-permission-node@0.7.5
29
+ - @backstage/plugin-scaffolder-common@1.2.5
30
+ - @backstage/plugin-search-common@1.2.1
31
+
32
+ ## 1.7.2-next.2
33
+
34
+ ### Patch Changes
35
+
36
+ - e716946103: Updated usage of the lifecycle service.
37
+ - 0ff03319be: Updated usage of `createBackendPlugin`.
38
+ - Updated dependencies
39
+ - @backstage/backend-plugin-api@0.4.0-next.2
40
+ - @backstage/backend-common@0.18.2-next.2
41
+ - @backstage/catalog-model@1.2.0-next.1
42
+ - @backstage/plugin-catalog-node@1.3.3-next.2
43
+ - @backstage/plugin-permission-node@0.7.5-next.2
44
+ - @backstage/catalog-client@1.3.1-next.1
45
+ - @backstage/config@1.0.6
46
+ - @backstage/errors@1.1.4
47
+ - @backstage/integration@1.4.2
48
+ - @backstage/types@1.0.2
49
+ - @backstage/plugin-catalog-common@1.0.11-next.1
50
+ - @backstage/plugin-permission-common@0.7.3
51
+ - @backstage/plugin-scaffolder-common@1.2.5-next.1
52
+ - @backstage/plugin-search-common@1.2.1
53
+
3
54
  ## 1.7.2-next.1
4
55
 
5
56
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "1.7.2-next.1",
3
+ "version": "1.7.2",
4
4
  "main": "../dist/index.cjs.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
package/dist/index.cjs.js CHANGED
@@ -823,7 +823,7 @@ const createCatalogPermissionRule = pluginPermissionNode.makeCreatePermissionRul
823
823
 
824
824
  const hasAnnotation = createCatalogPermissionRule({
825
825
  name: "HAS_ANNOTATION",
826
- description: "Allow entities which are annotated with the specified annotation",
826
+ description: "Allow entities with the specified annotation",
827
827
  resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
828
828
  paramsSchema: zod.z.object({
829
829
  annotation: zod.z.string().describe("Name of the annotation to match on"),
@@ -843,7 +843,7 @@ const hasAnnotation = createCatalogPermissionRule({
843
843
 
844
844
  const isEntityKind = createCatalogPermissionRule({
845
845
  name: "IS_ENTITY_KIND",
846
- description: "Allow entities with the specified kind",
846
+ description: "Allow entities matching a specified kind",
847
847
  resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
848
848
  paramsSchema: zod.z.object({
849
849
  kinds: zod.z.array(zod.z.string()).describe("List of kinds to match at least one of")
@@ -862,7 +862,7 @@ const isEntityKind = createCatalogPermissionRule({
862
862
 
863
863
  const isEntityOwner = createCatalogPermissionRule({
864
864
  name: "IS_ENTITY_OWNER",
865
- description: "Allow entities owned by the current user",
865
+ description: "Allow entities owned by a specified claim",
866
866
  resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
867
867
  paramsSchema: zod.z.object({
868
868
  claims: zod.z.array(zod.z.string()).describe(
@@ -883,10 +883,10 @@ const isEntityOwner = createCatalogPermissionRule({
883
883
 
884
884
  const hasLabel = createCatalogPermissionRule({
885
885
  name: "HAS_LABEL",
886
- description: "Allow entities which have the specified label metadata.",
886
+ description: "Allow entities with the specified label",
887
887
  resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
888
888
  paramsSchema: zod.z.object({
889
- label: zod.z.string().describe("Name of the label to match one")
889
+ label: zod.z.string().describe("Name of the label to match on")
890
890
  }),
891
891
  apply: (resource, { label }) => {
892
892
  var _a;
@@ -899,7 +899,7 @@ const hasLabel = createCatalogPermissionRule({
899
899
 
900
900
  const createPropertyRule = (propertyType) => createCatalogPermissionRule({
901
901
  name: `HAS_${propertyType.toUpperCase()}`,
902
- description: `Allow entities which have the specified ${propertyType} subfield.`,
902
+ description: `Allow entities with the specified ${propertyType} subfield`,
903
903
  resourceType: pluginCatalogCommon.RESOURCE_TYPE_CATALOG_ENTITY,
904
904
  paramsSchema: zod.z.object({
905
905
  key: zod.z.string().describe(`Property within the entities ${propertyType} to match on`),
@@ -3056,6 +3056,10 @@ function generateStableHash(entity) {
3056
3056
  return crypto.createHash("sha1").update(stableStringify__default["default"]({ ...entity })).digest("hex");
3057
3057
  }
3058
3058
 
3059
+ const scriptProtocolPattern = (
3060
+ // eslint-disable-next-line no-control-regex
3061
+ /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i
3062
+ );
3059
3063
  class Stitcher {
3060
3064
  constructor(database, logger) {
3061
3065
  this.database = database;
@@ -3073,7 +3077,7 @@ class Stitcher {
3073
3077
  }
3074
3078
  }
3075
3079
  async stitchOne(entityRef) {
3076
- var _a, _b;
3080
+ var _a, _b, _c;
3077
3081
  const entityResult = await this.database("refresh_state").where({ entity_ref: entityRef }).limit(1).select("entity_id");
3078
3082
  if (!entityResult.length) {
3079
3083
  return;
@@ -3141,6 +3145,12 @@ class Stitcher {
3141
3145
  }));
3142
3146
  }
3143
3147
  }
3148
+ for (const annotation of [catalogModel.ANNOTATION_VIEW_URL, catalogModel.ANNOTATION_EDIT_URL]) {
3149
+ const value = (_a = entity.metadata.annotations) == null ? void 0 : _a[annotation];
3150
+ if (typeof value === "string" && scriptProtocolPattern.test(value)) {
3151
+ entity.metadata.annotations[annotation] = "https://backstage.io/annotation-rejected-for-security-reasons";
3152
+ }
3153
+ }
3144
3154
  entity.relations = relationsResult.filter(
3145
3155
  (row) => row.relationType
3146
3156
  /* exclude null row, if relevant */
@@ -3151,7 +3161,7 @@ class Stitcher {
3151
3161
  if (statusItems.length) {
3152
3162
  entity.status = {
3153
3163
  ...entity.status,
3154
- items: [...(_b = (_a = entity.status) == null ? void 0 : _a.items) != null ? _b : [], ...statusItems]
3164
+ items: [...(_c = (_b = entity.status) == null ? void 0 : _b.items) != null ? _c : [], ...statusItems]
3155
3165
  };
3156
3166
  }
3157
3167
  const hash = generateStableHash(entity);
@@ -3766,7 +3776,10 @@ const _DefaultCatalogRulesEnforcer = class {
3766
3776
  if (matcher.exact && matcher.exact !== (location == null ? void 0 : location.target)) {
3767
3777
  continue;
3768
3778
  }
3769
- if (matcher.pattern && !minimatch__default["default"](location == null ? void 0 : location.target, matcher.pattern, { nocase: true })) {
3779
+ if (matcher.pattern && !minimatch__default["default"](location == null ? void 0 : location.target, matcher.pattern, {
3780
+ nocase: true,
3781
+ dot: true
3782
+ })) {
3770
3783
  continue;
3771
3784
  }
3772
3785
  return true;
@@ -4076,44 +4089,55 @@ class AuthorizedLocationService {
4076
4089
  async function deleteWithEagerPruningOfChildren(options) {
4077
4090
  const { tx, entityRefs, sourceKey } = options;
4078
4091
  let removedCount = 0;
4079
- const rootId = () => tx.raw(
4080
- tx.client.config.client.includes("mysql") ? "CAST(NULL as UNSIGNED INT)" : "CAST(NULL as INT)",
4081
- []
4082
- );
4083
4092
  for (const refs of lodash__default["default"].chunk(entityRefs, 1e3)) {
4084
- removedCount += await tx("refresh_state").whereIn("entity_ref", function orphanedEntityRefs(orphans) {
4085
- return orphans.withRecursive("descendants", function descendants(outer) {
4086
- return outer.select({ root_id: "id", entity_ref: "target_entity_ref" }).from("refresh_state_references").where("source_key", sourceKey).whereIn("target_entity_ref", refs).union(function recursive(inner) {
4087
- return inner.select({
4088
- root_id: "descendants.root_id",
4089
- entity_ref: "refresh_state_references.target_entity_ref"
4090
- }).from("descendants").join("refresh_state_references", {
4091
- "descendants.entity_ref": "refresh_state_references.source_entity_ref"
4092
- });
4093
- });
4094
- }).withRecursive("ancestors", function ancestors(outer) {
4095
- return outer.select({
4096
- root_id: rootId(),
4097
- via_entity_ref: "entity_ref",
4098
- to_entity_ref: "entity_ref"
4099
- }).from("descendants").union(function recursive(inner) {
4100
- return inner.select({
4101
- root_id: tx.raw(
4102
- "CASE WHEN source_key IS NOT NULL THEN id ELSE NULL END",
4103
- []
4104
- ),
4105
- via_entity_ref: "source_entity_ref",
4106
- to_entity_ref: "ancestors.to_entity_ref"
4107
- }).from("ancestors").join("refresh_state_references", {
4108
- target_entity_ref: "ancestors.via_entity_ref"
4109
- });
4110
- });
4111
- }).select("descendants.entity_ref").from("descendants").leftOuterJoin("ancestors", function keepaliveRoots() {
4112
- this.on("ancestors.to_entity_ref", "=", "descendants.entity_ref");
4113
- this.andOnNotNull("ancestors.root_id");
4114
- this.andOn("ancestors.root_id", "!=", "descendants.root_id");
4115
- }).whereNull("ancestors.root_id");
4116
- }).delete();
4093
+ removedCount += await tx.delete().from("refresh_state").whereIn(
4094
+ "entity_ref",
4095
+ (orphans) => orphans.withRecursive(
4096
+ "descendants",
4097
+ ["entity_ref"],
4098
+ (initial) => initial.select("target_entity_ref").from("refresh_state_references").where("source_key", "=", sourceKey).whereIn("target_entity_ref", refs).union(
4099
+ (recursive) => recursive.select("refresh_state_references.target_entity_ref").from("descendants").join(
4100
+ "refresh_state_references",
4101
+ "descendants.entity_ref",
4102
+ "refresh_state_references.source_entity_ref"
4103
+ )
4104
+ )
4105
+ ).withRecursive(
4106
+ "ancestors",
4107
+ ["source_key", "source_entity_ref", "target_entity_ref", "subject"],
4108
+ (initial) => initial.select(
4109
+ "refresh_state_references.source_key",
4110
+ "refresh_state_references.source_entity_ref",
4111
+ "refresh_state_references.target_entity_ref",
4112
+ "descendants.entity_ref"
4113
+ ).from("descendants").join(
4114
+ "refresh_state_references",
4115
+ "refresh_state_references.target_entity_ref",
4116
+ "descendants.entity_ref"
4117
+ ).union(
4118
+ (recursive) => recursive.select(
4119
+ "refresh_state_references.source_key",
4120
+ "refresh_state_references.source_entity_ref",
4121
+ "refresh_state_references.target_entity_ref",
4122
+ "ancestors.subject"
4123
+ ).from("ancestors").join(
4124
+ "refresh_state_references",
4125
+ "refresh_state_references.target_entity_ref",
4126
+ "ancestors.source_entity_ref"
4127
+ )
4128
+ )
4129
+ ).with(
4130
+ "retained",
4131
+ ["entity_ref"],
4132
+ (notPartOfDeletion) => notPartOfDeletion.select("subject").from("ancestors").whereNotNull("ancestors.source_key").where(
4133
+ (foreignKeyOrRef) => foreignKeyOrRef.where("ancestors.source_key", "!=", sourceKey).orWhereNotIn("ancestors.target_entity_ref", refs)
4134
+ )
4135
+ ).select("descendants.entity_ref").from("descendants").leftOuterJoin(
4136
+ "retained",
4137
+ "retained.entity_ref",
4138
+ "descendants.entity_ref"
4139
+ ).whereNull("retained.entity_ref")
4140
+ );
4117
4141
  await tx("refresh_state_references").where("source_key", "=", sourceKey).whereIn("target_entity_ref", refs).delete();
4118
4142
  }
4119
4143
  return removedCount;
@@ -4893,7 +4917,7 @@ class CatalogExtensionPointImpl {
4893
4917
  _processors = new WeakMap();
4894
4918
  _entityProviders = new WeakMap();
4895
4919
  const catalogPlugin = backendPluginApi.createBackendPlugin({
4896
- id: "catalog",
4920
+ pluginId: "catalog",
4897
4921
  register(env) {
4898
4922
  const processingExtensions = new CatalogExtensionPointImpl();
4899
4923
  env.registerExtensionPoint(
@@ -4931,11 +4955,7 @@ const catalogPlugin = backendPluginApi.createBackendPlugin({
4931
4955
  builder.addEntityProvider(...processingExtensions.entityProviders);
4932
4956
  const { processingEngine, router } = await builder.build();
4933
4957
  await processingEngine.start();
4934
- lifecycle.addShutdownHook({
4935
- fn: async () => {
4936
- await processingEngine.stop();
4937
- }
4938
- });
4958
+ lifecycle.addShutdownHook(() => processingEngine.stop());
4939
4959
  httpRouter.use(router);
4940
4960
  }
4941
4961
  });