diff --git a/java/google/registry/config/ConfigModule.java b/java/google/registry/config/ConfigModule.java index c818bfb6e..66adc7c12 100644 --- a/java/google/registry/config/ConfigModule.java +++ b/java/google/registry/config/ConfigModule.java @@ -314,6 +314,15 @@ public final class ConfigModule { return projectId + "-rde"; } + /** + * Returns the Google Cloud Storage bucket for importing escrow files. + */ + @Provides + @Config("rdeImportBucket") + public String provideRdeImportBucket(@Config("projectId") String projectId) { + return projectId + "-rde-import"; + } + /** * Size of Ghostryde buffer in bytes for each layer in the pipeline. * diff --git a/java/google/registry/config/ProductionRegistryConfigExample.java b/java/google/registry/config/ProductionRegistryConfigExample.java index ccabf8735..20a688d2d 100644 --- a/java/google/registry/config/ProductionRegistryConfigExample.java +++ b/java/google/registry/config/ProductionRegistryConfigExample.java @@ -98,11 +98,6 @@ public final class ProductionRegistryConfigExample implements RegistryConfig { return getProjectId() + "-zonefiles"; } - @Override - public String getEscrowFileImportBucket() { - return getProjectId() + "-escrow-import"; - } - @Override public boolean getTmchCaTestingMode() { switch (environment) { diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 85c2c57a8..c66ab572e 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -89,11 +89,6 @@ public interface RegistryConfig { */ public String getZoneFilesBucket(); - /** - * Returns the Google Cloud Storage bucket for importing escrow files. - */ - public String getEscrowFileImportBucket(); - /** * Returns {@code true} if TMCH certificate authority should be in testing mode. * diff --git a/java/google/registry/config/TestRegistryConfig.java b/java/google/registry/config/TestRegistryConfig.java index 3ec49256b..e01a140d7 100644 --- a/java/google/registry/config/TestRegistryConfig.java +++ b/java/google/registry/config/TestRegistryConfig.java @@ -65,11 +65,6 @@ public class TestRegistryConfig implements RegistryConfig { return getProjectId() + "-zonefiles"; } - @Override - public String getEscrowFileImportBucket() { - return getProjectId() + "-escrow-import"; - } - @Override public boolean getTmchCaTestingMode() { return true; diff --git a/java/google/registry/gcs/GcsUtils.java b/java/google/registry/gcs/GcsUtils.java index 554413ebb..97e70a485 100644 --- a/java/google/registry/gcs/GcsUtils.java +++ b/java/google/registry/gcs/GcsUtils.java @@ -35,7 +35,7 @@ import javax.annotation.CheckReturnValue; import javax.inject.Inject; /** Utilities for working with Google Cloud Storage. */ -public final class GcsUtils { +public class GcsUtils { private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); diff --git a/java/google/registry/rde/RdeImportUtils.java b/java/google/registry/rde/RdeImportUtils.java index c615997a7..4253909d0 100644 --- a/java/google/registry/rde/RdeImportUtils.java +++ b/java/google/registry/rde/RdeImportUtils.java @@ -14,17 +14,30 @@ package google.registry.rde; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import com.google.appengine.tools.cloudstorage.GcsFilename; import com.googlecode.objectify.Key; import com.googlecode.objectify.Work; +import google.registry.config.ConfigModule.Config; +import google.registry.gcs.GcsUtils; import google.registry.model.contact.ContactResource; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.ForeignKeyIndex; import google.registry.model.ofy.Ofy; +import google.registry.model.registrar.Registrar; +import google.registry.model.registry.Registry; +import google.registry.model.registry.Registry.RegistryNotFoundException; +import google.registry.model.registry.Registry.TldState; import google.registry.util.Clock; import google.registry.util.FormattingLogger; +import google.registry.xjc.rderegistrar.XjcRdeRegistrar; +import java.io.IOException; +import java.io.InputStream; import javax.inject.Inject; +import javax.xml.stream.XMLStreamException; +import org.joda.time.DateTime; /** Utility functions for escrow file import. */ public final class RdeImportUtils { @@ -33,11 +46,16 @@ public final class RdeImportUtils { private final Ofy ofy; private final Clock clock; + private final String escrowBucketName; + private final GcsUtils gcsUtils; @Inject - public RdeImportUtils(Ofy ofy, Clock clock) { + public RdeImportUtils( + Ofy ofy, Clock clock, @Config("rdeImportBucket") String escrowBucketName, GcsUtils gcsUtils) { this.ofy = ofy; this.clock = clock; + this.gcsUtils = gcsUtils; + this.escrowBucketName = escrowBucketName; } /** @@ -84,4 +102,55 @@ public final class RdeImportUtils { } }); } + + /** + * Validates an escrow file for import. + * + *
Before an escrow file is imported into the registry, the following conditions must be met: + * + *
If any of the above conditions is not true, an {@link IllegalStateException} will be thrown.
+ *
+ * @param escrowFilePath Path to the escrow file to validate
+ * @throws IOException If the escrow file cannot be read
+ * @throws IllegalArgumentException if the escrow file cannot be imported
+ */
+ public void validateEscrowFileForImport(String escrowFilePath) throws IOException {
+ // TODO (wolfgang): Add validation method for IDN tables
+ try (InputStream input =
+ gcsUtils.openInputStream(new GcsFilename(escrowBucketName, escrowFilePath))) {
+ try {
+ RdeParser parser = new RdeParser(input);
+ // validate that tld exists and is in PREDELEGATION state
+ String tld = parser.getHeader().getTld();
+ try {
+ Registry registry = Registry.get(tld);
+ TldState currentState = registry.getTldState(DateTime.now());
+ checkArgument(
+ currentState == TldState.PREDELEGATION,
+ String.format("Tld '%s' is in state %s and cannot be imported", tld, currentState));
+ } catch (RegistryNotFoundException e) {
+ throw new IllegalArgumentException(
+ String.format("Tld '%s' not found in the registry", tld));
+ }
+ // validate that all registrars exist
+ while (parser.nextRegistrar()) {
+ XjcRdeRegistrar registrar = parser.getRegistrar();
+ if (Registrar.loadByClientId(registrar.getId()) == null) {
+ throw new IllegalArgumentException(
+ String.format("Registrar '%s' not found in the registry", registrar.getId()));
+ }
+ }
+ } catch (XMLStreamException e) {
+ throw new IllegalArgumentException(
+ String.format("Invalid XML file: '%s'", escrowFilePath), e);
+ }
+ }
+ }
}
diff --git a/javatests/google/registry/rde/RdeImportUtilsTest.java b/javatests/google/registry/rde/RdeImportUtilsTest.java
index 92f933ca1..c5906f53b 100644
--- a/javatests/google/registry/rde/RdeImportUtilsTest.java
+++ b/javatests/google/registry/rde/RdeImportUtilsTest.java
@@ -16,36 +16,62 @@ package google.registry.rde;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
+import static google.registry.testing.DatastoreHelper.createTld;
+import static google.registry.testing.DatastoreHelper.persistNewRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteSource;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
+import google.registry.gcs.GcsUtils;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.index.ForeignKeyIndex;
+import google.registry.model.registry.Registry.TldState;
import google.registry.testing.AppEngineRule;
+import google.registry.testing.ExceptionRule;
import google.registry.testing.FakeClock;
import google.registry.testing.ShardableTestCase;
+import java.io.IOException;
+import java.io.InputStream;
import org.joda.time.DateTime;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
/** Unit tests for {@link RdeImportUtils} */
-@RunWith(JUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
public class RdeImportUtilsTest extends ShardableTestCase {
+ private static final ByteSource DEPOSIT_XML = RdeTestData.get("deposit_full.xml");
+ private static final ByteSource DEPOSIT_BADTLD_XML = RdeTestData.get("deposit_full_badtld.xml");
+ private static final ByteSource DEPOSIT_GETLD_XML = RdeTestData.get("deposit_full_getld.xml");
+ private static final ByteSource DEPOSIT_BADREGISTRAR_XML =
+ RdeTestData.get("deposit_full_badregistrar.xml");
+
+ private InputStream xmlInput;
+
@Rule
- public final AppEngineRule appEngine = AppEngineRule.builder()
- .withDatastore()
- .build();
+ public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
+
+ @Rule
+ public final ExceptionRule thrown = new ExceptionRule();
+
+ @Mock
+ private GcsUtils gcsUtils;
private RdeImportUtils rdeImportUtils;
private FakeClock clock;
@@ -54,7 +80,17 @@ public class RdeImportUtilsTest extends ShardableTestCase {
public void before() {
clock = new FakeClock();
clock.setTo(DateTime.now());
- rdeImportUtils = new RdeImportUtils(ofy(), clock);
+ rdeImportUtils = new RdeImportUtils(ofy(), clock, "import-bucket", gcsUtils);
+ createTld("test", TldState.PREDELEGATION);
+ createTld("getld", TldState.GENERAL_AVAILABILITY);
+ persistNewRegistrar("RegistrarX", 1L);
+ }
+
+ @After
+ public void after() throws IOException {
+ if (xmlInput != null) {
+ xmlInput.close();
+ }
}
/** Verifies import of a contact that has not been previously imported */
@@ -78,9 +114,11 @@ public class RdeImportUtilsTest extends ShardableTestCase {
public void testImportExistingContact() {
ContactResource newContact = buildNewContact();
persistResource(newContact);
- ContactResource updatedContact = newContact.asBuilder()
- .setLastEppUpdateTime(newContact.getLastEppUpdateTime().plusSeconds(1))
- .build();
+ ContactResource updatedContact =
+ newContact
+ .asBuilder()
+ .setLastEppUpdateTime(newContact.getLastEppUpdateTime().plusSeconds(1))
+ .build();
assertThat(rdeImportUtils.importContact(updatedContact)).isFalse();
// verify the updated contact was saved
@@ -100,27 +138,62 @@ public class RdeImportUtilsTest extends ShardableTestCase {
.build();
}
+ /** Verifies that no errors are thrown when a valid escrow file is validated */
+ @Test
+ public void testValidateEscrowFile_valid() throws Exception {
+ xmlInput = DEPOSIT_XML.openBufferedStream();
+ when(gcsUtils.openInputStream(any(GcsFilename.class))).thenReturn(xmlInput);
+ rdeImportUtils.validateEscrowFileForImport("valid-deposit-file.xml");
+ verify(gcsUtils).openInputStream(new GcsFilename("import-bucket", "valid-deposit-file.xml"));
+ }
+
+ /** Verifies thrown error when tld in escrow file is not in the registry */
+ @Test
+ public void testValidateEscrowFile_tldNotFound() throws Exception {
+ thrown.expect(IllegalArgumentException.class, "Tld 'badtld' not found in the registry");
+ xmlInput = DEPOSIT_BADTLD_XML.openBufferedStream();
+ when(gcsUtils.openInputStream(any(GcsFilename.class))).thenReturn(xmlInput);
+ rdeImportUtils.validateEscrowFileForImport("invalid-deposit-badtld.xml");
+ }
+
+ /** Verifies thrown errer when tld in escrow file is not in PREDELEGATION state */
+ @Test
+ public void testValidateEscrowFile_tldWrongState() throws Exception {
+ thrown.expect(
+ IllegalArgumentException.class,
+ "Tld 'getld' is in state GENERAL_AVAILABILITY and cannot be imported");
+ xmlInput = DEPOSIT_GETLD_XML.openBufferedStream();
+ when(gcsUtils.openInputStream(any(GcsFilename.class))).thenReturn(xmlInput);
+ rdeImportUtils.validateEscrowFileForImport("invalid-deposit-getld.xml");
+ }
+
+ /** Verifies thrown error when registrar in escrow file is not in the registry */
+ @Test
+ public void testValidateEscrowFile_badRegistrar() throws Exception {
+ thrown.expect(
+ IllegalArgumentException.class, "Registrar 'RegistrarY' not found in the registry");
+ xmlInput = DEPOSIT_BADREGISTRAR_XML.openBufferedStream();
+ when(gcsUtils.openInputStream(any(GcsFilename.class))).thenReturn(xmlInput);
+ rdeImportUtils.validateEscrowFileForImport("invalid-deposit-badregistrar.xml");
+ }
+
+ /** Gets the contact with the specified ROID */
private static ContactResource getContact(String repoId) {
final Key