diff --git a/core/src/main/java/google/registry/model/common/FeatureFlag.java b/core/src/main/java/google/registry/model/common/FeatureFlag.java index efaa8bee718..48b10c2dce3 100644 --- a/core/src/main/java/google/registry/model/common/FeatureFlag.java +++ b/core/src/main/java/google/registry/model/common/FeatureFlag.java @@ -68,6 +68,10 @@ public enum FeatureName { /** Feature flag name used for testing only. */ TEST_FEATURE(FeatureStatus.INACTIVE), + /** True if Fee Extension 1.0 (RFC 8748) is enabled in production. */ + // TODO(b/159033801) Delete this flag after 1.0 is hardened in prod. + FEE_EXTENSION_1_DOT_0_IN_PROD(FeatureStatus.INACTIVE), + /** If we're not requiring the presence of contact data on domain EPP commands. */ MINIMUM_DATASET_CONTACTS_OPTIONAL(FeatureStatus.INACTIVE), diff --git a/core/src/main/java/google/registry/model/eppcommon/ProtocolDefinition.java b/core/src/main/java/google/registry/model/eppcommon/ProtocolDefinition.java index e1bb39dce7b..1e26fbcb796 100644 --- a/core/src/main/java/google/registry/model/eppcommon/ProtocolDefinition.java +++ b/core/src/main/java/google/registry/model/eppcommon/ProtocolDefinition.java @@ -16,11 +16,14 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Maps.uniqueIndex; +import static google.registry.model.common.FeatureFlag.FeatureName.FEE_EXTENSION_1_DOT_0_IN_PROD; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import google.registry.model.common.FeatureFlag; import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06; import google.registry.model.domain.fee06.FeeCheckResponseExtensionV06; import google.registry.model.domain.fee11.FeeCheckCommandExtensionV11; @@ -58,7 +61,7 @@ public class ProtocolDefinition { /** Enum representing which environments should have which service extensions enabled. */ private enum ServiceExtensionVisibility { ALL, - ONLY_IN_NON_PRODUCTION, + FEE_1_DOT_0_EXTENSION_VISIBILITY, NONE } @@ -82,7 +85,7 @@ public enum ServiceExtension { FEE_1_00( FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, - ServiceExtensionVisibility.ONLY_IN_NON_PRODUCTION), + ServiceExtensionVisibility.FEE_1_DOT_0_EXTENSION_VISIBILITY), METADATA_1_0(MetadataExtension.class, null, ServiceExtensionVisibility.NONE); private final Class commandExtensionClass; @@ -138,8 +141,9 @@ public static String getCommandExtensionXmlTag(Class public boolean isVisible() { return switch (visibility) { case ALL -> true; - case ONLY_IN_NON_PRODUCTION -> - !RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION); + case FEE_1_DOT_0_EXTENSION_VISIBILITY -> + !RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION) + || tm().transact(() -> FeatureFlag.isActiveNow(FEE_EXTENSION_1_DOT_0_IN_PROD)); case NONE -> false; }; } diff --git a/core/src/main/java/google/registry/tools/RegistryToolEnvironment.java b/core/src/main/java/google/registry/tools/RegistryToolEnvironment.java index f26ae0b7ab5..03de377754b 100644 --- a/core/src/main/java/google/registry/tools/RegistryToolEnvironment.java +++ b/core/src/main/java/google/registry/tools/RegistryToolEnvironment.java @@ -91,7 +91,7 @@ void setup() { /** Sets up execution environment. Call this method before any classes are loaded. */ @VisibleForTesting - void setup(SystemPropertySetter systemPropertySetter) { + public void setup(SystemPropertySetter systemPropertySetter) { instance = this; actualEnvironment.setup(systemPropertySetter); for (Map.Entry entry : extraProperties.entrySet()) { diff --git a/core/src/test/java/google/registry/flows/domain/ProductionSimulatingFeeExtensionsTest.java b/core/src/test/java/google/registry/flows/domain/ProductionSimulatingFeeExtensionsTest.java index baed836a592..c6b78057acb 100644 --- a/core/src/test/java/google/registry/flows/domain/ProductionSimulatingFeeExtensionsTest.java +++ b/core/src/test/java/google/registry/flows/domain/ProductionSimulatingFeeExtensionsTest.java @@ -15,15 +15,21 @@ package google.registry.flows.domain; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.common.FeatureFlag.FeatureName.FEE_EXTENSION_1_DOT_0_IN_PROD; +import static google.registry.tools.RegistryToolEnvironment.PRODUCTION; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import google.registry.model.eppcommon.ProtocolDefinition; +import google.registry.tools.CommandTestCase; +import google.registry.tools.ConfigureFeatureFlagCommand; import google.registry.util.RegistryEnvironment; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** Class for testing the XML extension definitions loaded in the prod environment. */ -public class ProductionSimulatingFeeExtensionsTest { +public class ProductionSimulatingFeeExtensionsTest + extends CommandTestCase { private RegistryEnvironment previousEnvironment; @@ -59,7 +65,7 @@ void testNonProdEnvironments() { } @Test - void testProdEnvironment() { + void testProdEnvironment_feeExtensionFeatureNotSet() { RegistryEnvironment.PRODUCTION.setup(); ProtocolDefinition.reloadServiceExtensionUris(); // prod shouldn't have the fee extension version 1.0 @@ -72,4 +78,47 @@ void testProdEnvironment() { "urn:ietf:params:xml:ns:fee-0.11", "urn:ietf:params:xml:ns:fee-0.12"); } + + @Test + void testProdEnvironment_feeExtensionFeatureActiveInTheFuture() throws Exception { + runCommandInEnvironment( + PRODUCTION, + FEE_EXTENSION_1_DOT_0_IN_PROD.name(), + "--force", + "--status_map", + String.format("%s=INACTIVE,%s=ACTIVE", START_OF_TIME, fakeClock.nowUtc().plusMillis(1))); + RegistryEnvironment.PRODUCTION.setup(); + ProtocolDefinition.reloadServiceExtensionUris(); + // prod shouldn't have the fee extension version 1.0 + assertThat(ProtocolDefinition.getVisibleServiceExtensionUris()) + .containsExactly( + "urn:ietf:params:xml:ns:launch-1.0", + "urn:ietf:params:xml:ns:rgp-1.0", + "urn:ietf:params:xml:ns:secDNS-1.1", + "urn:ietf:params:xml:ns:fee-0.6", + "urn:ietf:params:xml:ns:fee-0.11", + "urn:ietf:params:xml:ns:fee-0.12"); + } + + @Test + void testProdEnvironment_feeExtensionFeatureActiveInThePast() throws Exception { + runCommandInEnvironment( + PRODUCTION, + FEE_EXTENSION_1_DOT_0_IN_PROD.name(), + "--force", + "--status_map", + String.format("%s=INACTIVE,%s=ACTIVE", START_OF_TIME, fakeClock.nowUtc().minusMillis(1))); + RegistryEnvironment.PRODUCTION.setup(); + ProtocolDefinition.reloadServiceExtensionUris(); + // prod should have the fee extension version 1.0 + assertThat(ProtocolDefinition.getVisibleServiceExtensionUris()) + .containsExactly( + "urn:ietf:params:xml:ns:launch-1.0", + "urn:ietf:params:xml:ns:rgp-1.0", + "urn:ietf:params:xml:ns:secDNS-1.1", + "urn:ietf:params:xml:ns:fee-0.6", + "urn:ietf:params:xml:ns:fee-0.11", + "urn:ietf:params:xml:ns:fee-0.12", + "urn:ietf:params:xml:ns:epp:fee-1.0"); + } } diff --git a/core/src/test/java/google/registry/tools/CommandTestCase.java b/core/src/test/java/google/registry/tools/CommandTestCase.java index 2cb7f4bea35..e2060506d3f 100644 --- a/core/src/test/java/google/registry/tools/CommandTestCase.java +++ b/core/src/test/java/google/registry/tools/CommandTestCase.java @@ -94,7 +94,8 @@ public final void afterEachCommandTestCase() { System.setErr(oldStderr); } - void runCommandInEnvironment(RegistryToolEnvironment env, String... args) throws Exception { + protected void runCommandInEnvironment(RegistryToolEnvironment env, String... args) + throws Exception { env.setup(systemPropertyExtension); try { JCommander jcommander = new JCommander(command); diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index ce08baf2545..80e69f50dbf 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -333,7 +333,7 @@ ); create table "FeatureFlag" ( - feature_name text not null check (feature_name in ('TEST_FEATURE','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED','INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS','PROHIBIT_CONTACT_OBJECTS_ON_LOGIN')), + feature_name text not null check (feature_name in ('TEST_FEATURE','FEE_EXTENSION_1_DOT_0_IN_PROD','MINIMUM_DATASET_CONTACTS_OPTIONAL','MINIMUM_DATASET_CONTACTS_PROHIBITED','INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS','PROHIBIT_CONTACT_OBJECTS_ON_LOGIN')), status hstore not null, primary key (feature_name) );