Skip to content

Commit 8163de0

Browse files
authored
Config property guards for Delete operations (group, artifact, version) (#4932)
* Added dynamic config property guards for deleting groups, artifacts, versions * Fix syntax issue in ts code * Hide Delete Version action if that feature is not enabled * Fix settings page UI integration test * Enable deletes during integration tests
1 parent e701548 commit 8163de0

File tree

35 files changed

+327
-37
lines changed

35 files changed

+327
-37
lines changed

.github/workflows/integration-tests.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ jobs:
274274
- name: Run UI tests
275275
run: |
276276
echo "Starting Registry App (In Memory)"
277-
docker run -it -p 8080:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
277+
docker run -it -p 8080:8080 -e apicurio.rest.deletion.artifact.enabled=true -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
278278
echo "Starting Registry UI"
279279
docker run -it -p 8888:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry-ui:1d
280280
@@ -350,7 +350,7 @@ jobs:
350350
- name: Run Legacy Integration Tests (Core API v2)
351351
run: |
352352
echo "Starting Registry App (In Memory)"
353-
docker run -it -p 8181:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
353+
docker run -it -p 8181:8080 -e apicurio.rest.deletion.artifact.enabled=true -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
354354
cd registry-v2
355355
./mvnw -T 1.5C -Pintegration-tests clean install -DskipTests=true -DskipUiBuild=true -Dmaven.javadoc.skip=true
356356
cd integration-tests

app/src/main/java/io/apicurio/registry/rest/RestConfig.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@ public class RestConfig {
1818
@Info(category = "rest", description = "Skip SSL validation when downloading artifacts from URL", availableSince = "2.2.6-SNAPSHOT")
1919
boolean downloadSkipSSLValidation;
2020

21+
@Dynamic(label = "Delete group", description = "When selected, users are permitted to delete groups.")
22+
@ConfigProperty(name = "apicurio.rest.deletion.group.enabled", defaultValue = "false")
23+
@Info(category = "rest", description = "Enables group deletion", availableSince = "3.0.0")
24+
Supplier<Boolean> groupDeletionEnabled;
25+
26+
@Dynamic(label = "Delete artifact", description = "When selected, users are permitted to delete artifacts.")
27+
@ConfigProperty(name = "apicurio.rest.deletion.artifact.enabled", defaultValue = "false")
28+
@Info(category = "rest", description = "Enables artifact deletion", availableSince = "3.0.0")
29+
Supplier<Boolean> artifactDeletionEnabled;
30+
2131
@Dynamic(label = "Delete artifact version", description = "When selected, users are permitted to delete artifact versions.")
22-
@ConfigProperty(name = "apicurio.rest.artifact.deletion.enabled", defaultValue = "false")
32+
@ConfigProperty(name = "apicurio.rest.deletion.artifactVersion.enabled", defaultValue = "false")
2333
@Info(category = "rest", description = "Enables artifact version deletion", availableSince = "2.4.2-SNAPSHOT")
2434
Supplier<Boolean> artifactVersionDeletionEnabled;
2535

@@ -31,6 +41,14 @@ public boolean getDownloadSkipSSLValidation() {
3141
return this.downloadSkipSSLValidation;
3242
}
3343

44+
public boolean isGroupDeletionEnabled() {
45+
return groupDeletionEnabled.get();
46+
}
47+
48+
public boolean isArtifactDeletionEnabled() {
49+
return artifactDeletionEnabled.get();
50+
}
51+
3452
public boolean isArtifactVersionDeletionEnabled() {
3553
return artifactVersionDeletionEnabled.get();
3654
}

app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java

+15
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ private ArtifactMetaData updateArtifactWithRefs(String groupId, String artifactI
272272
@Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID })
273273
@Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write)
274274
public void deleteArtifact(String groupId, String artifactId) {
275+
if (!restConfig.isArtifactDeletionEnabled()) {
276+
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
277+
(String[]) null);
278+
}
279+
275280
requireParameter("groupId", groupId);
276281
requireParameter("artifactId", artifactId);
277282

@@ -363,6 +368,11 @@ public GroupMetaData getGroupById(String groupId) {
363368
@Override
364369
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
365370
public void deleteGroupById(String groupId) {
371+
if (!restConfig.isGroupDeletionEnabled()) {
372+
throw new NotAllowedException("Group deletion operation is not enabled.", HttpMethod.GET,
373+
(String[]) null);
374+
}
375+
366376
storage.deleteGroup(groupId);
367377
}
368378

@@ -841,6 +851,11 @@ public ArtifactSearchResults listArtifactsInGroup(String groupId, BigInteger lim
841851
@Audited(extractParameters = { "0", KEY_GROUP_ID })
842852
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
843853
public void deleteArtifactsInGroup(String groupId) {
854+
if (!restConfig.isArtifactDeletionEnabled()) {
855+
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
856+
(String[]) null);
857+
}
858+
844859
requireParameter("groupId", groupId);
845860

846861
storage.deleteArtifacts(defaultGroupIdToNull(groupId));

app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java

+15
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ public List<ArtifactReference> getArtifactVersionReferences(String groupId, Stri
181181
@Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID })
182182
@Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write)
183183
public void deleteArtifact(String groupId, String artifactId) {
184+
if (!restConfig.isArtifactDeletionEnabled()) {
185+
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
186+
(String[]) null);
187+
}
188+
184189
requireParameter("groupId", groupId);
185190
requireParameter("artifactId", artifactId);
186191

@@ -240,6 +245,11 @@ public GroupMetaData getGroupById(String groupId) {
240245
@Override
241246
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
242247
public void deleteGroupById(String groupId) {
248+
if (!restConfig.isGroupDeletionEnabled()) {
249+
throw new NotAllowedException("Group deletion operation is not enabled.", HttpMethod.GET,
250+
(String[]) null);
251+
}
252+
243253
storage.deleteGroup(groupId);
244254
}
245255

@@ -631,6 +641,11 @@ public ArtifactSearchResults listArtifactsInGroup(String groupId, BigInteger lim
631641
@Audited(extractParameters = { "0", KEY_GROUP_ID })
632642
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
633643
public void deleteArtifactsInGroup(String groupId) {
644+
if (!restConfig.isArtifactDeletionEnabled()) {
645+
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
646+
(String[]) null);
647+
}
648+
634649
requireParameter("groupId", groupId);
635650

636651
storage.deleteArtifacts(new GroupId(groupId).getRawGroupIdWithNull());

app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.apicurio.registry.limits.RegistryLimitsConfiguration;
1010
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
1111
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
12+
import io.apicurio.registry.rest.RestConfig;
1213
import io.apicurio.registry.rest.v3.beans.Limits;
1314
import io.apicurio.registry.rest.v3.beans.SystemInfo;
1415
import io.apicurio.registry.rest.v3.beans.UserInterfaceConfig;
@@ -40,6 +41,9 @@ public class SystemResourceImpl implements SystemResource {
4041
@Inject
4142
RegistryLimitsConfiguration registryLimitsConfiguration;
4243

44+
@Inject
45+
RestConfig restConfig;
46+
4347
/**
4448
* @see io.apicurio.registry.rest.v3.SystemResource#getSystemInfo()
4549
*/
@@ -91,6 +95,9 @@ public UserInterfaceConfig getUIConfig() {
9195
.readOnly("true".equals(uiConfig.featureReadOnly))
9296
.breadcrumbs("true".equals(uiConfig.featureBreadcrumbs))
9397
.roleManagement(authConfig.isRbacEnabled())
98+
.deleteGroup(restConfig.isGroupDeletionEnabled())
99+
.deleteArtifact(restConfig.isArtifactDeletionEnabled())
100+
.deleteVersion(restConfig.isArtifactVersionDeletionEnabled())
94101
.settings("true".equals(uiConfig.featureSettings)).build())
95102
.build();
96103
}

app/src/main/resources/application.properties

+4-1
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,12 @@ apicurio.ccompat.use-canonical-hash.dynamic.allow=${apicurio.config.dynamic.allo
122122
apicurio.ccompat.max-subjects.dynamic.allow=${apicurio.config.dynamic.allow-all}
123123
apicurio.download.href.ttl.seconds.dynamic.allow=${apicurio.config.dynamic.allow-all}
124124
apicurio.ui.features.read-only.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
125-
apicurio.rest.artifact.deletion.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
126125
apicurio.storage.read-only.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
127126
apicurio.authn.basic-client-credentials.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
127+
apicurio.rest.deletion.group.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
128+
apicurio.rest.deletion.artifact.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
129+
apicurio.rest.deletion.artifactVersion.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
130+
128131

129132
# Error
130133
apicurio.api.errors.include-stack-in-response=false

app/src/test/java/io/apicurio/registry/AbstractResourceTestBase.java

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public abstract class AbstractResourceTestBase extends AbstractRegistryTestBase
5151
protected static final String CT_PROTO = "application/x-protobuf";
5252
protected static final String CT_YAML = "application/x-yaml";
5353
protected static final String CT_XML = "application/xml";
54-
public static final String CT_JSON_EXTENDED = "application/create.extended+json";
5554

5655
public String registryApiBaseUrl;
5756
protected String registryV3ApiUrl;

app/src/test/java/io/apicurio/registry/noprofile/ccompat/rest/v7/ConfluentClientTest.java

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.apicurio.registry.rest.client.models.RuleType;
1010
import io.apicurio.registry.support.HealthUtils;
1111
import io.apicurio.registry.support.TestCmmn;
12+
import io.apicurio.registry.utils.tests.DeletionEnabledProfile;
1213
import io.apicurio.registry.utils.tests.TestUtils;
1314
import io.confluent.connect.avro.AvroConverter;
1415
import io.confluent.kafka.schemaregistry.CompatibilityLevel;
@@ -36,6 +37,7 @@
3637
import io.confluent.kafka.serializers.protobuf.KafkaProtobufSerializer;
3738
import io.confluent.kafka.serializers.protobuf.KafkaProtobufSerializerConfig;
3839
import io.quarkus.test.junit.QuarkusTest;
40+
import io.quarkus.test.junit.TestProfile;
3941
import org.apache.avro.Schema;
4042
import org.apache.avro.SchemaParseException;
4143
import org.apache.avro.generic.GenericData;
@@ -72,6 +74,7 @@
7274
import static org.junit.jupiter.api.Assertions.fail;
7375

7476
@QuarkusTest
77+
@TestProfile(DeletionEnabledProfile.class)
7578
@SuppressWarnings({ "unchecked", "rawtypes" })
7679
public class ConfluentClientTest extends AbstractResourceTestBase {
7780

app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java

+3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import io.apicurio.registry.types.ReferenceType;
2222
import io.apicurio.registry.types.RuleType;
2323
import io.apicurio.registry.types.VersionState;
24+
import io.apicurio.registry.utils.tests.DeletionEnabledProfile;
2425
import io.apicurio.registry.utils.tests.TestUtils;
2526
import io.quarkus.test.junit.QuarkusTest;
27+
import io.quarkus.test.junit.TestProfile;
2628
import io.restassured.RestAssured;
2729
import io.restassured.common.mapper.TypeRef;
2830
import io.restassured.http.ContentType;
@@ -66,6 +68,7 @@
6668
import static org.junit.jupiter.api.Assertions.assertThrows;
6769

6870
@QuarkusTest
71+
@TestProfile(DeletionEnabledProfile.class)
6972
public class GroupsResourceTest extends AbstractResourceTestBase {
7073

7174
private static final String GROUP = "GroupsResourceTest";

app/src/test/java/io/apicurio/registry/rbac/AdminResourceTest.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ public void testRoleMappingPaging() throws Exception {
547547
@Test
548548
public void testConfigProperties() throws Exception {
549549
String property1Name = "apicurio.ccompat.legacy-id-mode.enabled";
550-
String property2Name = "apicurio.rest.artifact.deletion.enabled";
550+
String property2Name = "apicurio.ccompat.use-canonical-hash";
551551

552552
// Start with default mappings
553553
given().when().get("/registry/v3/admin/config/properties").then().statusCode(200)
@@ -563,7 +563,7 @@ public void testConfigProperties() throws Exception {
563563
given().when().pathParam("propertyName", property2Name)
564564
.get("/registry/v3/admin/config/properties/{propertyName}").then().statusCode(200)
565565
.contentType(ContentType.JSON).body("name", equalTo(property2Name))
566-
.body("value", equalTo("true"));
566+
.body("value", equalTo("false"));
567567

568568
// Set value for property 1
569569
UpdateConfigurationProperty update = new UpdateConfigurationProperty();
@@ -580,7 +580,7 @@ public void testConfigProperties() throws Exception {
580580

581581
// Set value for property 2
582582
update = new UpdateConfigurationProperty();
583-
update.setValue("false");
583+
update.setValue("true");
584584
given().when().contentType(CT_JSON).body(update).pathParam("propertyName", property2Name)
585585
.put("/registry/v3/admin/config/properties/{propertyName}").then().statusCode(204)
586586
.body(anything());
@@ -589,7 +589,7 @@ public void testConfigProperties() throws Exception {
589589
given().when().pathParam("propertyName", property2Name)
590590
.get("/registry/v3/admin/config/properties/{propertyName}").then().statusCode(200)
591591
.contentType(ContentType.JSON).body("name", equalTo(property2Name))
592-
.body("value", equalTo("false"));
592+
.body("value", equalTo("true"));
593593

594594
// Reset a config property
595595
given().when().pathParam("propertyName", property2Name)
@@ -600,7 +600,7 @@ public void testConfigProperties() throws Exception {
600600
given().when().pathParam("propertyName", property2Name)
601601
.get("/registry/v3/admin/config/properties/{propertyName}").then().statusCode(200)
602602
.contentType(ContentType.JSON).body("name", equalTo(property2Name))
603-
.body("value", equalTo("true"));
603+
.body("value", equalTo("false"));
604604

605605
// Reset the other property
606606
given().when().contentType(CT_JSON).body(update).pathParam("propertyName", property1Name)

app/src/test/java/io/apicurio/registry/rbac/RegistryClientTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1455,7 +1455,7 @@ public void testRoleMappings() throws Exception {
14551455
@Test
14561456
public void testConfigProperties() throws Exception {
14571457
String property1Name = "apicurio.ccompat.legacy-id-mode.enabled";
1458-
String property2Name = "apicurio.rest.artifact.deletion.enabled";
1458+
String property2Name = "apicurio.rest.deletion.artifact.enabled";
14591459

14601460
// Start with all default values
14611461
List<ConfigurationProperty> configProperties = clientV3.admin().config().properties().get();

app/src/test/java/io/apicurio/registry/rest/DisableApisTestProfile.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class DisableApisTestProfile implements QuarkusTestProfile {
1111
public Map<String, String> getConfigOverrides() {
1212
Map<String, String> props = new HashMap<>();
1313
props.put("apicurio.disable.apis", "/apis/ccompat/v7/subjects/[^/]+/versions.*,/ui/.*");
14-
props.put("apicurio.rest.artifact.deletion.enabled", "false");
14+
props.put("apicurio.rest.deletion.artifact.enabled", "false");
1515
return props;
1616
}
1717

common/src/main/resources/META-INF/openapi.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"info": {
44
"title": "Apicurio Registry API [v3]",
55
"version": "3.0.x",
6-
"description": "Apicurio Registry is a datastore for standard event schemas and API designs. Apicurio Registry enables developers to manage and share the structure of their data using a REST interface. For example, client applications can dynamically push or pull the latest updates to or from the registry without needing to redeploy. Apicurio Registry also enables developers to create rules that govern how registry content can evolve over time. For example, this includes rules for content validation and version compatibility.\n\nThe Apicurio Registry REST API enables client applications to manage the artifacts in the registry. This API provides create, read, update, and delete operations for schema and API artifacts, rules, versions, and metadata. \n\nThe supported artifact types include:\n- Apache Avro schema\n- AsyncAPI specification\n- Google protocol buffers\n- GraphQL schema\n- JSON Schema\n- Kafka Connect schema\n- OpenAPI specification\n- Web Services Description Language\n- XML Schema Definition\n\n\n**Important**: The Apicurio Registry REST API is available from `https://MY-REGISTRY-URL/apis/registry/v3` by default. Therefore you must prefix all API operation paths with `../apis/registry/v3` in this case. For example: `../apis/registry/v3/ids/globalIds/{globalId}`.\n",
6+
"description": "Apicurio Registry is a datastore for standard event schemas and API designs. Apicurio Registry enables developers to manage and share the structure of their data using a REST interface. For example, client applications can dynamically push or pull the latest updates to or from the registry without needing to redeploy. Apicurio Registry also enables developers to create rules that govern how registry content can evolve over time. For example, this includes rules for content validation and version compatibility.\n\nThe Apicurio Registry REST API enables client applications to manage the artifacts in the registry. This API provides create, read, update, and delete operations for schema and API artifacts, rules, versions, and metadata. \n\nThe supported artifact types include:\n- Apache Avro schema\n- AsyncAPI specification\n- Google protocol buffers\n- GraphQL schema\n- JSON Schema\n- Kafka Connect schema\n- OpenAPI specification\n- Web Services Description Language\n- XML Schema Definition\n\n\n**Important**: The Apicurio Registry REST API is available from `https://MY-REGISTRY-URL/apis/registry/v3` by default. Therefore you must prefix all API operation paths with `/apis/registry/v3` in this case. For example: `/apis/registry/v3/ids/globalIds/{globalId}`.\n",
77
"contact": {
88
"name": "Apicurio",
99
"url": "https://github.com/apicurio/apicurio-registry",
@@ -4387,6 +4387,18 @@
43874387
},
43884388
"settings": {
43894389
"type": "boolean"
4390+
},
4391+
"deleteGroup": {
4392+
"description": "",
4393+
"type": "boolean"
4394+
},
4395+
"deleteArtifact": {
4396+
"description": "",
4397+
"type": "boolean"
4398+
},
4399+
"deleteVersion": {
4400+
"description": "",
4401+
"type": "boolean"
43904402
}
43914403
},
43924404
"example": {
@@ -5028,7 +5040,7 @@
50285040
},
50295041
{
50305042
"name": "Search",
5031-
"description": "The search API is used to browse or find artifacts in the registry. This section describes the operations for searching for artifacts and versions. "
5043+
"description": "The search API is used to browse or find artifacts in the registry. This section describes the operations for searching for artifacts and versions."
50325044
},
50335045
{
50345046
"name": "Admin",

docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc

+15-5
Original file line numberDiff line numberDiff line change
@@ -502,11 +502,6 @@ The following {registry} configuration options are available for each component
502502
|Default
503503
|Available from
504504
|Description
505-
|`apicurio.rest.artifact.deletion.enabled`
506-
|`boolean [dynamic]`
507-
|`false`
508-
|`2.4.2-SNAPSHOT`
509-
|Enables artifact version deletion
510505
|`apicurio.rest.artifact.download.maxSize.bytes`
511506
|`int`
512507
|`1000000`
@@ -517,6 +512,21 @@ The following {registry} configuration options are available for each component
517512
|`false`
518513
|`2.2.6-SNAPSHOT`
519514
|Skip SSL validation when downloading artifacts from URL
515+
|`apicurio.rest.deletion.artifact.enabled`
516+
|`boolean [dynamic]`
517+
|`false`
518+
|`3.0.0`
519+
|Enables artifact deletion
520+
|`apicurio.rest.deletion.artifactVersion.enabled`
521+
|`boolean [dynamic]`
522+
|`false`
523+
|`2.4.2-SNAPSHOT`
524+
|Enables artifact version deletion
525+
|`apicurio.rest.deletion.group.enabled`
526+
|`boolean [dynamic]`
527+
|`false`
528+
|`3.0.0`
529+
|Enables group deletion
520530
|===
521531

522532
== storage

go-sdk/pkg/registryclient-v3/kiota-lock.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"descriptionHash": "E09CC67C76497F1018A2E92618ADFA6F667C280644027CBCB55587F3942D873739E6A438C095E6F7FC41B5B573E1D9E462053338964821E478326575CCAAB743",
2+
"descriptionHash": "15933487085F19A69D2E5D5A0755D74BD7941DBA966F14FC30BF15D9E4E1546224C0CB1C98C7A1C6485838DF13B98F9D69E2AE997E7E028CC5DF08ECE09EE3D3",
33
"descriptionLocation": "../../v3.json",
44
"lockFileVersion": "1.0.0",
55
"kiotaVersion": "1.10.1",

0 commit comments

Comments
 (0)