diff --git a/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/java/google/registry/flows/domain/DomainTransferApproveFlow.java index 69e78b2d4..a7be9b598 100644 --- a/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -172,9 +172,18 @@ public final class DomainTransferApproveFlow implements TransactionalFlow { .setMsg("Domain was auto-renewed.") .setParent(historyEntry) .build(); + // Construct the post-transfer domain. + DomainResource partiallyApprovedDomain = + approvePendingTransfer(existingDomain, TransferStatus.CLIENT_APPROVED, now); DomainResource newDomain = - approvePendingTransfer(existingDomain, TransferStatus.CLIENT_APPROVED, now) + partiallyApprovedDomain .asBuilder() + // Update the transferredRegistrationExpirationTime here since approvePendingTransfer() + // doesn't know what to set it to and leaves it null. + .setTransferData( + partiallyApprovedDomain.getTransferData().asBuilder() + .setTransferredRegistrationExpirationTime(newExpirationTime) + .build()) .setRegistrationExpirationTime(newExpirationTime) .setAutorenewBillingEvent(Key.create(autorenewEvent)) .setAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage)) diff --git a/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/java/google/registry/flows/domain/DomainTransferRequestFlow.java index 588dd6ceb..bd33cea82 100644 --- a/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -69,7 +69,6 @@ import google.registry.model.reporting.DomainTransactionRecord.TransactionReport import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import google.registry.model.transfer.TransferData; -import google.registry.model.transfer.TransferData.Builder; import google.registry.model.transfer.TransferData.TransferServerApproveEntity; import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.model.transfer.TransferStatus; @@ -206,7 +205,13 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { // Create the transfer data that represents the pending transfer. TransferData pendingTransferData = createPendingTransferData( - createTransferDataBuilder(existingDomain, automaticTransferTime, now), + new TransferData.Builder() + .setTransferRequestTrid(trid) + .setTransferRequestTime(now) + .setGainingClientId(gainingClientId) + .setLosingClientId(existingDomain.getCurrentSponsorClientId()) + .setPendingTransferExpirationTime(automaticTransferTime) + .setTransferredRegistrationExpirationTime(serverApproveNewExpirationTime), serverApproveEntities, period); // Create a poll message to notify the losing registrar that a transfer was requested. @@ -316,16 +321,6 @@ public final class DomainTransferRequestFlow implements TransactionalFlow { .build(); } - private Builder createTransferDataBuilder( - DomainResource existingDomain, DateTime automaticTransferTime, DateTime now) { - return new TransferData.Builder() - .setTransferRequestTrid(trid) - .setTransferRequestTime(now) - .setGainingClientId(gainingClientId) - .setLosingClientId(existingDomain.getCurrentSponsorClientId()) - .setPendingTransferExpirationTime(automaticTransferTime); - } - private DomainTransferResponse createResponse( Period period, DomainResource existingDomain, DomainResource newDomain, DateTime now) { // If the registration were approved this instant, this is what the new expiration would be, diff --git a/java/google/registry/flows/domain/DomainTransferUtils.java b/java/google/registry/flows/domain/DomainTransferUtils.java index 9b977055a..697f4312b 100644 --- a/java/google/registry/flows/domain/DomainTransferUtils.java +++ b/java/google/registry/flows/domain/DomainTransferUtils.java @@ -35,7 +35,6 @@ import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData; -import google.registry.model.transfer.TransferData.Builder; import google.registry.model.transfer.TransferData.TransferServerApproveEntity; import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.model.transfer.TransferStatus; @@ -115,8 +114,13 @@ public final class DomainTransferUtils { String targetId = existingDomain.getFullyQualifiedDomainName(); // Create a TransferData for the server-approve case to use for the speculative poll messages. TransferData serverApproveTransferData = - createTransferDataBuilder( - existingDomain, trid, gainingClientId, automaticTransferTime, now) + new TransferData.Builder() + .setTransferRequestTrid(trid) + .setTransferRequestTime(now) + .setGainingClientId(gainingClientId) + .setLosingClientId(existingDomain.getCurrentSponsorClientId()) + .setPendingTransferExpirationTime(automaticTransferTime) + .setTransferredRegistrationExpirationTime(serverApproveNewExpirationTime) .setTransferStatus(TransferStatus.SERVER_APPROVED) .build(); Registry registry = Registry.get(existingDomain.getTld()); @@ -291,19 +295,5 @@ public final class DomainTransferUtils { .build(); } - private static Builder createTransferDataBuilder( - DomainResource existingDomain, - Trid trid, - String gainingClientId, - DateTime automaticTransferTime, - DateTime now) { - return new TransferData.Builder() - .setTransferRequestTrid(trid) - .setTransferRequestTime(now) - .setGainingClientId(gainingClientId) - .setLosingClientId(existingDomain.getCurrentSponsorClientId()) - .setPendingTransferExpirationTime(automaticTransferTime); - } - private DomainTransferUtils() {} } diff --git a/java/google/registry/model/transfer/TransferData.java b/java/google/registry/model/transfer/TransferData.java index 97e75df71..d2dd5aeeb 100644 --- a/java/google/registry/model/transfer/TransferData.java +++ b/java/google/registry/model/transfer/TransferData.java @@ -30,6 +30,8 @@ import google.registry.model.domain.Period.Unit; import google.registry.model.eppcommon.Trid; import google.registry.model.poll.PollMessage; import java.util.Set; +import javax.annotation.Nullable; +import org.joda.time.DateTime; /** * Common transfer data for {@link EppResource} types. Only applies to domains and contacts; @@ -41,6 +43,34 @@ public class TransferData extends BaseTransferObject implements Buildable { public static final TransferData EMPTY = new TransferData(); + /** The transaction id of the most recent transfer request (or null if there never was one). */ + Trid transferRequestTrid; + + /** + * The period to extend the registration upon completion of the transfer. + * + *

By default, domain transfers are for one year. This can be changed to zero by using the + * superuser EPP extension. + */ + Period transferPeriod = Period.create(1, Unit.YEARS); + + /** + * The registration expiration time resulting from the approval - speculative or actual - of the + * most recent transfer request, applicable for domains only. + * + *

For pending transfers, this is the expiration time that will take effect under a projected + * server approval. For approved transfers, this is the actual expiration time of the domain as + * of the moment of transfer completion. For rejected or cancelled transfers, this field will be + * reset to null. + * + *

Note that even when this field is set, it does not necessarily mean that the post-transfer + * domain has a new expiration time. Superuser transfers may not include a bundled 1 year renewal + * at all, or even when a renewal is bundled, for a transfer during the autorenew grace period the + * bundled renewal simply subsumes the recent autorenewal, resulting in the same expiration time. + */ + // TODO(b/36405140): backfill this field for existing domains to which it should apply. + DateTime transferredRegistrationExpirationTime; + /** * The billing event and poll messages associated with a server-approved transfer. * @@ -80,33 +110,7 @@ public class TransferData extends BaseTransferObject implements Buildable { @IgnoreSave(IfNull.class) Key serverApproveAutorenewPollMessage; - /** The transaction id of the most recent transfer request (or null if there never was one). */ - Trid transferRequestTrid; - - /** - * The period to extend the registration upon completion of the transfer. - * - *

By default, domain transfers are for one year. This can be changed to zero by using the - * superuser EPP extension. - */ - Period transferPeriod = Period.create(1, Unit.YEARS); - - public ImmutableSet> getServerApproveEntities() { - return nullToEmptyImmutableCopy(serverApproveEntities); - } - - public Key getServerApproveBillingEvent() { - return serverApproveBillingEvent; - } - - public Key getServerApproveAutorenewEvent() { - return serverApproveAutorenewEvent; - } - - public Key getServerApproveAutorenewPollMessage() { - return serverApproveAutorenewPollMessage; - } - + @Nullable public Trid getTransferRequestTrid() { return transferRequestTrid; } @@ -115,6 +119,30 @@ public class TransferData extends BaseTransferObject implements Buildable { return transferPeriod; } + @Nullable + public DateTime getTransferredRegistrationExpirationTime() { + return transferredRegistrationExpirationTime; + } + + public ImmutableSet> getServerApproveEntities() { + return nullToEmptyImmutableCopy(serverApproveEntities); + } + + @Nullable + public Key getServerApproveBillingEvent() { + return serverApproveBillingEvent; + } + + @Nullable + public Key getServerApproveAutorenewEvent() { + return serverApproveAutorenewEvent; + } + + @Nullable + public Key getServerApproveAutorenewPollMessage() { + return serverApproveAutorenewPollMessage; + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -153,6 +181,22 @@ public class TransferData extends BaseTransferObject implements Buildable { super(instance); } + public Builder setTransferRequestTrid(Trid transferRequestTrid) { + getInstance().transferRequestTrid = transferRequestTrid; + return this; + } + + public Builder setTransferPeriod(Period transferPeriod) { + getInstance().transferPeriod = transferPeriod; + return this; + } + + public Builder setTransferredRegistrationExpirationTime( + DateTime transferredRegistrationExpirationTime) { + getInstance().transferredRegistrationExpirationTime = transferredRegistrationExpirationTime; + return this; + } + public Builder setServerApproveEntities( ImmutableSet> serverApproveEntities) { getInstance().serverApproveEntities = serverApproveEntities; @@ -176,16 +220,6 @@ public class TransferData extends BaseTransferObject implements Buildable { getInstance().serverApproveAutorenewPollMessage = serverApproveAutorenewPollMessage; return this; } - - public Builder setTransferRequestTrid(Trid transferRequestTrid) { - getInstance().transferRequestTrid = transferRequestTrid; - return this; - } - - public Builder setTransferPeriod(Period transferPeriod) { - getInstance().transferPeriod = transferPeriod; - return this; - } } /** diff --git a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java index 7838df602..3fac19418 100644 --- a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java @@ -236,13 +236,13 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { if (data == null) { return TransferData.EMPTY; } - // TODO(b/25084229): read in the exDate and store it somewhere. return new TransferData.Builder() .setTransferStatus(TRANSFER_STATUS_MAPPER.xmlToEnum(data.getTrStatus().value())) .setGainingClientId(data.getReRr().getValue()) .setLosingClientId(data.getAcRr().getValue()) .setTransferRequestTime(data.getReDate()) .setPendingTransferExpirationTime(data.getAcDate()) + .setTransferredRegistrationExpirationTime(data.getExDate()) .build(); } diff --git a/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java index 8cf5211c4..433e360db 100644 --- a/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferApproveFlowTest.java @@ -116,6 +116,7 @@ public class DomainTransferApproveFlowTest oldTransferData.copyConstantFieldsToBuilder() .setTransferStatus(TransferStatus.CLIENT_APPROVED) .setPendingTransferExpirationTime(clock.nowUtc()) + .setTransferredRegistrationExpirationTime(domain.getRegistrationExpirationTime()) .build()); } diff --git a/javatests/google/registry/flows/domain/DomainTransferCancelFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferCancelFlowTest.java index 07c4502ff..c027e754b 100644 --- a/javatests/google/registry/flows/domain/DomainTransferCancelFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferCancelFlowTest.java @@ -98,7 +98,8 @@ public class DomainTransferCancelFlowTest "NewRegistrar", TRANSFER_REQUEST_TIME, TRANSFER_EXPIRATION_TIME, - TRANSFER_REQUEST_TIME)); + TRANSFER_REQUEST_TIME, + EXTENDED_REGISTRATION_EXPIRATION_TIME)); assertPollMessages( "TheRegistrar", createPollMessageForImplicitTransfer( @@ -107,7 +108,8 @@ public class DomainTransferCancelFlowTest "TheRegistrar", TRANSFER_REQUEST_TIME, TRANSFER_EXPIRATION_TIME, - TRANSFER_REQUEST_TIME)); + TRANSFER_REQUEST_TIME, + EXTENDED_REGISTRATION_EXPIRATION_TIME)); clock.advanceOneMilli(); // Setup done; run the test. diff --git a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java index 187399cc2..aeb729e0f 100644 --- a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java @@ -130,8 +130,10 @@ public class DomainTransferRequestFlowTest } private void assertTransferRequested( - DomainResource domain, DateTime automaticTransferTime, Period expectedPeriod) - throws Exception { + DomainResource domain, + DateTime automaticTransferTime, + Period expectedPeriod, + DateTime expectedExpirationTime) throws Exception { assertAboutDomains().that(domain) .hasCurrentSponsorClientId("TheRegistrar").and() .hasStatusValue(StatusValue.PENDING_TRANSFER); @@ -152,6 +154,7 @@ public class DomainTransferRequestFlowTest .setTransferPeriod(expectedPeriod) .setTransferStatus(TransferStatus.PENDING) .setPendingTransferExpirationTime(automaticTransferTime) + .setTransferredRegistrationExpirationTime(expectedExpirationTime) // Don't compare the server-approve entity fields; they're hard to reconstruct // and logic later will check them. .build()); @@ -178,6 +181,7 @@ public class DomainTransferRequestFlowTest .setTransferPeriod(expectedPeriod) .setTransferStatus(TransferStatus.SERVER_APPROVED) .setPendingTransferExpirationTime(automaticTransferTime) + .setTransferredRegistrationExpirationTime(domain.getRegistrationExpirationTime()) // Server-approve entity fields should all be nulled out. .build()); } @@ -219,7 +223,8 @@ public class DomainTransferRequestFlowTest .and() .hasOtherClientId("TheRegistrar"); // Verify correct fields were set. - assertTransferRequested(domain, implicitTransferTime, Period.create(1, Unit.YEARS)); + assertTransferRequested( + domain, implicitTransferTime, Period.create(1, Unit.YEARS), expectedExpirationTime); subordinateHost = reloadResourceAndCloneAtTime(subordinateHost, clock.nowUtc()); assertAboutHosts().that(subordinateHost).hasNoHistoryEntries(); @@ -530,7 +535,7 @@ public class DomainTransferRequestFlowTest .and() .hasOtherClientId("TheRegistrar"); // Verify correct fields were set. - assertTransferRequested(domain, implicitTransferTime, expectedPeriod); + assertTransferRequested(domain, implicitTransferTime, expectedPeriod, expectedExpirationTime); subordinateHost = reloadResourceAndCloneAtTime(subordinateHost, clock.nowUtc()); assertAboutHosts().that(subordinateHost).hasNoHistoryEntries(); diff --git a/javatests/google/registry/model/domain/DomainResourceTest.java b/javatests/google/registry/model/domain/DomainResourceTest.java index 031b8ea8f..5f1e55a7c 100644 --- a/javatests/google/registry/model/domain/DomainResourceTest.java +++ b/javatests/google/registry/model/domain/DomainResourceTest.java @@ -139,6 +139,7 @@ public class DomainResourceTest extends EntityTestCase { .setTransferRequestTime(clock.nowUtc().plusDays(1)) .setTransferStatus(TransferStatus.SERVER_APPROVED) .setTransferRequestTrid(Trid.create("client-trid", "server-trid")) + .setTransferredRegistrationExpirationTime(clock.nowUtc().plusYears(2)) .build()) .setDeletePollMessage(onetimePollKey) .setAutorenewBillingEvent(recurringBillKey) diff --git a/javatests/google/registry/model/schema.txt b/javatests/google/registry/model/schema.txt index 327f2a4f9..8897d7620 100644 --- a/javatests/google/registry/model/schema.txt +++ b/javatests/google/registry/model/schema.txt @@ -913,6 +913,7 @@ class google.registry.model.transfer.TransferData { java.util.Set> serverApproveEntities; org.joda.time.DateTime pendingTransferExpirationTime; org.joda.time.DateTime transferRequestTime; + org.joda.time.DateTime transferredRegistrationExpirationTime; } class google.registry.model.transfer.TransferResponse$ContactTransferResponse { google.registry.model.transfer.TransferStatus transferStatus; diff --git a/javatests/google/registry/rdap/RdapJsonFormatterTest.java b/javatests/google/registry/rdap/RdapJsonFormatterTest.java index e196f8205..2def5aaf3 100644 --- a/javatests/google/registry/rdap/RdapJsonFormatterTest.java +++ b/javatests/google/registry/rdap/RdapJsonFormatterTest.java @@ -164,6 +164,8 @@ public class RdapJsonFormatterTest { .setTransferRequestTime(clock.nowUtc().minusDays(1)) .setLosingClientId("TheRegistrar") .setPendingTransferExpirationTime(clock.nowUtc().plusYears(100)) + .setTransferredRegistrationExpirationTime( + DateTime.parse("2111-10-08T00:44:59Z")) .build()) .build()))) .build()); diff --git a/javatests/google/registry/testing/DatastoreHelper.java b/javatests/google/registry/testing/DatastoreHelper.java index 87e0fc468..929e6cceb 100644 --- a/javatests/google/registry/testing/DatastoreHelper.java +++ b/javatests/google/registry/testing/DatastoreHelper.java @@ -103,6 +103,7 @@ import google.registry.model.transfer.TransferStatus; import google.registry.tmch.LordnTask; import java.util.Arrays; import java.util.List; +import javax.annotation.Nullable; import org.joda.money.Money; import org.joda.time.DateTime; @@ -457,20 +458,22 @@ public class DatastoreHelper { } public static PollMessage.OneTime createPollMessageForImplicitTransfer( - EppResource contact, + EppResource resource, HistoryEntry historyEntry, String clientId, DateTime requestTime, DateTime expirationTime, - DateTime now) { + DateTime now, + @Nullable DateTime extendedRegistrationExpirationTime) { + TransferData transferData = + createTransferDataBuilder(requestTime, expirationTime) + .setTransferredRegistrationExpirationTime(extendedRegistrationExpirationTime) + .build(); return new PollMessage.OneTime.Builder() .setClientId(clientId) .setEventTime(expirationTime) .setMsg("Transfer server approved.") - .setResponseData(ImmutableList.of( - createTransferResponse(contact, createTransferDataBuilder(requestTime, expirationTime) - .build(), - now))) + .setResponseData(ImmutableList.of(createTransferResponse(resource, transferData, now))) .setParent(historyEntry) .build(); } @@ -519,7 +522,8 @@ public class DatastoreHelper { "NewRegistrar", requestTime, expirationTime, - now))), + now, + null))), Key.create(persistResource( createPollMessageForImplicitTransfer( contact, @@ -527,7 +531,8 @@ public class DatastoreHelper { "TheRegistrar", requestTime, expirationTime, - now))))) + now, + null))))) .setTransferRequestTrid(Trid.create("transferClient-trid", "transferServer-trid")) .build()) .build()); @@ -591,6 +596,7 @@ public class DatastoreHelper { .addStatusValue(StatusValue.PENDING_TRANSFER) .setTransferData(transferDataBuilder .setPendingTransferExpirationTime(expirationTime) + .setTransferredRegistrationExpirationTime(extendedRegistrationExpirationTime) .setServerApproveBillingEvent(Key.create(transferBillingEvent)) .setServerApproveAutorenewEvent(Key.create(gainingClientAutorenewEvent)) .setServerApproveAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage)) @@ -605,7 +611,8 @@ public class DatastoreHelper { "NewRegistrar", requestTime, expirationTime, - now))), + now, + extendedRegistrationExpirationTime))), Key.create(persistResource( createPollMessageForImplicitTransfer( domain, @@ -613,7 +620,8 @@ public class DatastoreHelper { "TheRegistrar", requestTime, expirationTime, - now))))) + now, + extendedRegistrationExpirationTime))))) .setTransferRequestTrid(Trid.create("transferClient-trid", "transferServer-trid")) .build()) .build());