Skip to content

Commit

Permalink
Config property guards for Delete operations (group, artifact, versio…
Browse files Browse the repository at this point in the history
…n) (#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
  • Loading branch information
EricWittmann authored Jul 25, 2024
1 parent e701548 commit 8163de0
Show file tree
Hide file tree
Showing 35 changed files with 327 additions and 37 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ jobs:
- name: Run UI tests
run: |
echo "Starting Registry App (In Memory)"
docker run -it -p 8080:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
docker run -it -p 8080:8080 -e apicurio.rest.deletion.artifact.enabled=true -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
echo "Starting Registry UI"
docker run -it -p 8888:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry-ui:1d
Expand Down Expand Up @@ -350,7 +350,7 @@ jobs:
- name: Run Legacy Integration Tests (Core API v2)
run: |
echo "Starting Registry App (In Memory)"
docker run -it -p 8181:8080 -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
docker run -it -p 8181:8080 -e apicurio.rest.deletion.artifact.enabled=true -d ttl.sh/${{ github.sha }}/apicurio/apicurio-registry:1d
cd registry-v2
./mvnw -T 1.5C -Pintegration-tests clean install -DskipTests=true -DskipUiBuild=true -Dmaven.javadoc.skip=true
cd integration-tests
Expand Down
20 changes: 19 additions & 1 deletion app/src/main/java/io/apicurio/registry/rest/RestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,18 @@ public class RestConfig {
@Info(category = "rest", description = "Skip SSL validation when downloading artifacts from URL", availableSince = "2.2.6-SNAPSHOT")
boolean downloadSkipSSLValidation;

@Dynamic(label = "Delete group", description = "When selected, users are permitted to delete groups.")
@ConfigProperty(name = "apicurio.rest.deletion.group.enabled", defaultValue = "false")
@Info(category = "rest", description = "Enables group deletion", availableSince = "3.0.0")
Supplier<Boolean> groupDeletionEnabled;

@Dynamic(label = "Delete artifact", description = "When selected, users are permitted to delete artifacts.")
@ConfigProperty(name = "apicurio.rest.deletion.artifact.enabled", defaultValue = "false")
@Info(category = "rest", description = "Enables artifact deletion", availableSince = "3.0.0")
Supplier<Boolean> artifactDeletionEnabled;

@Dynamic(label = "Delete artifact version", description = "When selected, users are permitted to delete artifact versions.")
@ConfigProperty(name = "apicurio.rest.artifact.deletion.enabled", defaultValue = "false")
@ConfigProperty(name = "apicurio.rest.deletion.artifactVersion.enabled", defaultValue = "false")
@Info(category = "rest", description = "Enables artifact version deletion", availableSince = "2.4.2-SNAPSHOT")
Supplier<Boolean> artifactVersionDeletionEnabled;

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

public boolean isGroupDeletionEnabled() {
return groupDeletionEnabled.get();
}

public boolean isArtifactDeletionEnabled() {
return artifactDeletionEnabled.get();
}

public boolean isArtifactVersionDeletionEnabled() {
return artifactVersionDeletionEnabled.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ private ArtifactMetaData updateArtifactWithRefs(String groupId, String artifactI
@Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID })
@Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write)
public void deleteArtifact(String groupId, String artifactId) {
if (!restConfig.isArtifactDeletionEnabled()) {
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

requireParameter("groupId", groupId);
requireParameter("artifactId", artifactId);

Expand Down Expand Up @@ -363,6 +368,11 @@ public GroupMetaData getGroupById(String groupId) {
@Override
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
public void deleteGroupById(String groupId) {
if (!restConfig.isGroupDeletionEnabled()) {
throw new NotAllowedException("Group deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

storage.deleteGroup(groupId);
}

Expand Down Expand Up @@ -841,6 +851,11 @@ public ArtifactSearchResults listArtifactsInGroup(String groupId, BigInteger lim
@Audited(extractParameters = { "0", KEY_GROUP_ID })
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
public void deleteArtifactsInGroup(String groupId) {
if (!restConfig.isArtifactDeletionEnabled()) {
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

requireParameter("groupId", groupId);

storage.deleteArtifacts(defaultGroupIdToNull(groupId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ public List<ArtifactReference> getArtifactVersionReferences(String groupId, Stri
@Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID })
@Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write)
public void deleteArtifact(String groupId, String artifactId) {
if (!restConfig.isArtifactDeletionEnabled()) {
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

requireParameter("groupId", groupId);
requireParameter("artifactId", artifactId);

Expand Down Expand Up @@ -240,6 +245,11 @@ public GroupMetaData getGroupById(String groupId) {
@Override
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
public void deleteGroupById(String groupId) {
if (!restConfig.isGroupDeletionEnabled()) {
throw new NotAllowedException("Group deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

storage.deleteGroup(groupId);
}

Expand Down Expand Up @@ -631,6 +641,11 @@ public ArtifactSearchResults listArtifactsInGroup(String groupId, BigInteger lim
@Audited(extractParameters = { "0", KEY_GROUP_ID })
@Authorized(style = AuthorizedStyle.GroupOnly, level = AuthorizedLevel.Write)
public void deleteArtifactsInGroup(String groupId) {
if (!restConfig.isArtifactDeletionEnabled()) {
throw new NotAllowedException("Artifact deletion operation is not enabled.", HttpMethod.GET,
(String[]) null);
}

requireParameter("groupId", groupId);

storage.deleteArtifacts(new GroupId(groupId).getRawGroupIdWithNull());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.apicurio.registry.limits.RegistryLimitsConfiguration;
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
import io.apicurio.registry.rest.RestConfig;
import io.apicurio.registry.rest.v3.beans.Limits;
import io.apicurio.registry.rest.v3.beans.SystemInfo;
import io.apicurio.registry.rest.v3.beans.UserInterfaceConfig;
Expand Down Expand Up @@ -40,6 +41,9 @@ public class SystemResourceImpl implements SystemResource {
@Inject
RegistryLimitsConfiguration registryLimitsConfiguration;

@Inject
RestConfig restConfig;

/**
* @see io.apicurio.registry.rest.v3.SystemResource#getSystemInfo()
*/
Expand Down Expand Up @@ -91,6 +95,9 @@ public UserInterfaceConfig getUIConfig() {
.readOnly("true".equals(uiConfig.featureReadOnly))
.breadcrumbs("true".equals(uiConfig.featureBreadcrumbs))
.roleManagement(authConfig.isRbacEnabled())
.deleteGroup(restConfig.isGroupDeletionEnabled())
.deleteArtifact(restConfig.isArtifactDeletionEnabled())
.deleteVersion(restConfig.isArtifactVersionDeletionEnabled())
.settings("true".equals(uiConfig.featureSettings)).build())
.build();
}
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,12 @@ apicurio.ccompat.use-canonical-hash.dynamic.allow=${apicurio.config.dynamic.allo
apicurio.ccompat.max-subjects.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.download.href.ttl.seconds.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.ui.features.read-only.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.rest.artifact.deletion.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.storage.read-only.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.authn.basic-client-credentials.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.rest.deletion.group.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.rest.deletion.artifact.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}
apicurio.rest.deletion.artifactVersion.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all}


# Error
apicurio.api.errors.include-stack-in-response=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public abstract class AbstractResourceTestBase extends AbstractRegistryTestBase
protected static final String CT_PROTO = "application/x-protobuf";
protected static final String CT_YAML = "application/x-yaml";
protected static final String CT_XML = "application/xml";
public static final String CT_JSON_EXTENDED = "application/create.extended+json";

public String registryApiBaseUrl;
protected String registryV3ApiUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.apicurio.registry.rest.client.models.RuleType;
import io.apicurio.registry.support.HealthUtils;
import io.apicurio.registry.support.TestCmmn;
import io.apicurio.registry.utils.tests.DeletionEnabledProfile;
import io.apicurio.registry.utils.tests.TestUtils;
import io.confluent.connect.avro.AvroConverter;
import io.confluent.kafka.schemaregistry.CompatibilityLevel;
Expand Down Expand Up @@ -36,6 +37,7 @@
import io.confluent.kafka.serializers.protobuf.KafkaProtobufSerializer;
import io.confluent.kafka.serializers.protobuf.KafkaProtobufSerializerConfig;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import org.apache.avro.Schema;
import org.apache.avro.SchemaParseException;
import org.apache.avro.generic.GenericData;
Expand Down Expand Up @@ -72,6 +74,7 @@
import static org.junit.jupiter.api.Assertions.fail;

@QuarkusTest
@TestProfile(DeletionEnabledProfile.class)
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ConfluentClientTest extends AbstractResourceTestBase {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import io.apicurio.registry.types.ReferenceType;
import io.apicurio.registry.types.RuleType;
import io.apicurio.registry.types.VersionState;
import io.apicurio.registry.utils.tests.DeletionEnabledProfile;
import io.apicurio.registry.utils.tests.TestUtils;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.restassured.RestAssured;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
Expand Down Expand Up @@ -66,6 +68,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;

@QuarkusTest
@TestProfile(DeletionEnabledProfile.class)
public class GroupsResourceTest extends AbstractResourceTestBase {

private static final String GROUP = "GroupsResourceTest";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ public void testRoleMappingPaging() throws Exception {
@Test
public void testConfigProperties() throws Exception {
String property1Name = "apicurio.ccompat.legacy-id-mode.enabled";
String property2Name = "apicurio.rest.artifact.deletion.enabled";
String property2Name = "apicurio.ccompat.use-canonical-hash";

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

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

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

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

// Reset the other property
given().when().contentType(CT_JSON).body(update).pathParam("propertyName", property1Name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,7 @@ public void testRoleMappings() throws Exception {
@Test
public void testConfigProperties() throws Exception {
String property1Name = "apicurio.ccompat.legacy-id-mode.enabled";
String property2Name = "apicurio.rest.artifact.deletion.enabled";
String property2Name = "apicurio.rest.deletion.artifact.enabled";

// Start with all default values
List<ConfigurationProperty> configProperties = clientV3.admin().config().properties().get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class DisableApisTestProfile implements QuarkusTestProfile {
public Map<String, String> getConfigOverrides() {
Map<String, String> props = new HashMap<>();
props.put("apicurio.disable.apis", "/apis/ccompat/v7/subjects/[^/]+/versions.*,/ui/.*");
props.put("apicurio.rest.artifact.deletion.enabled", "false");
props.put("apicurio.rest.deletion.artifact.enabled", "false");
return props;
}

Expand Down
16 changes: 14 additions & 2 deletions common/src/main/resources/META-INF/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "Apicurio Registry API [v3]",
"version": "3.0.x",
"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",
"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",
"contact": {
"name": "Apicurio",
"url": "https://github.com/apicurio/apicurio-registry",
Expand Down Expand Up @@ -4387,6 +4387,18 @@
},
"settings": {
"type": "boolean"
},
"deleteGroup": {
"description": "",
"type": "boolean"
},
"deleteArtifact": {
"description": "",
"type": "boolean"
},
"deleteVersion": {
"description": "",
"type": "boolean"
}
},
"example": {
Expand Down Expand Up @@ -5028,7 +5040,7 @@
},
{
"name": "Search",
"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. "
"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."
},
{
"name": "Admin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,11 +502,6 @@ The following {registry} configuration options are available for each component
|Default
|Available from
|Description
|`apicurio.rest.artifact.deletion.enabled`
|`boolean [dynamic]`
|`false`
|`2.4.2-SNAPSHOT`
|Enables artifact version deletion
|`apicurio.rest.artifact.download.maxSize.bytes`
|`int`
|`1000000`
Expand All @@ -517,6 +512,21 @@ The following {registry} configuration options are available for each component
|`false`
|`2.2.6-SNAPSHOT`
|Skip SSL validation when downloading artifacts from URL
|`apicurio.rest.deletion.artifact.enabled`
|`boolean [dynamic]`
|`false`
|`3.0.0`
|Enables artifact deletion
|`apicurio.rest.deletion.artifactVersion.enabled`
|`boolean [dynamic]`
|`false`
|`2.4.2-SNAPSHOT`
|Enables artifact version deletion
|`apicurio.rest.deletion.group.enabled`
|`boolean [dynamic]`
|`false`
|`3.0.0`
|Enables group deletion
|===

== storage
Expand Down
2 changes: 1 addition & 1 deletion go-sdk/pkg/registryclient-v3/kiota-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"descriptionHash": "E09CC67C76497F1018A2E92618ADFA6F667C280644027CBCB55587F3942D873739E6A438C095E6F7FC41B5B573E1D9E462053338964821E478326575CCAAB743",
"descriptionHash": "15933487085F19A69D2E5D5A0755D74BD7941DBA966F14FC30BF15D9E4E1546224C0CB1C98C7A1C6485838DF13B98F9D69E2AE997E7E028CC5DF08ECE09EE3D3",
"descriptionLocation": "../../v3.json",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.10.1",
Expand Down
Loading

0 comments on commit 8163de0

Please sign in to comment.