mirror of
https://github.com/google/nomulus.git
synced 2025-08-22 09:11:08 +02:00
This CL adds transferredRegistrationExpirationTime as a TransferData field persisted to Datastore. It's only relevant for domains, and it represents the registration expiration time resulting from the approval of the most recent transfer request. For pending transfers, we assume the transfer will be server-approved, and thus in DomainTransferRequestFlow we set this field to the existing computed value serverApproveNewExpirationTime, which is what we use for setting up the server-approve autorenew billing event and poll message. In DomainTransferApproveFlow we overwrite this field with the freshly computed newExpirationTime, whereas in DomainTransferCancel/RejectFlow (and in the implicit cancel of DomainDeleteFlow during a pending transfer) we null it out. There are two key benefits to having this field, which are described in more detail in b/36405140. 1) b/25084229 - it allows storage of a frozen value to back the "exDate" field of DomainTransferResponse, which we can use to fix various errors with how exDate display currently works. 2) b/36354434 - it allows DomainResource.cloneProjectedAtTime() to just directly set the registrationExpirationTime to this value, without computing it de novo, which reduces duplicated logic and ensures that the new expiration time matches the autorenew child objects. This CL only starts writing the field on TransferData as persisted directly on the DomainResource itself. We'll then want to backfill the field for at least pending transfers, whether expired or not (so we can do (2) above), but I think we might as well backfill it for all pending and approved transfers so that we also fix (1) even for historical transfers. And then we can start actually reading the field for both purposes. (Note that for (1), this will only fix synchronous transfer responses served via DomainTransferQueryFlow, not async transfer responses served via poll messages, since these have already been persisted with a potentially bad exDate, but I don't think it's worth a backfill for those). One last naming note: I chose the verbose transferredRegistrationExpirationTime rather than the extendedRegistrationExpirationTime of DomainTransferResponse because (as is the case in autorenew grace, or for a superuser transfer) the new registration time isn't necessarily extended at all; it may be the same as the pre-transfer expiration time. Also, including "registration" helps clarify w.r.t. pendingTransferExpirationTime which refers confusingly to the expiry of the transfer itself, rather than the domain registration. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=171858083
272 lines
12 KiB
Java
272 lines
12 KiB
Java
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google.registry.rde.imports;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkState;
|
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
|
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
|
import static org.joda.time.DateTimeZone.UTC;
|
|
|
|
import com.google.common.base.Ascii;
|
|
import com.google.common.base.Function;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.net.InternetDomainName;
|
|
import com.googlecode.objectify.Key;
|
|
import google.registry.model.billing.BillingEvent;
|
|
import google.registry.model.contact.ContactResource;
|
|
import google.registry.model.domain.DesignatedContact;
|
|
import google.registry.model.domain.DomainAuthInfo;
|
|
import google.registry.model.domain.DomainResource;
|
|
import google.registry.model.domain.GracePeriod;
|
|
import google.registry.model.domain.rgp.GracePeriodStatus;
|
|
import google.registry.model.domain.secdns.DelegationSignerData;
|
|
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
|
import google.registry.model.eppcommon.StatusValue;
|
|
import google.registry.model.host.HostResource;
|
|
import google.registry.model.index.ForeignKeyIndex;
|
|
import google.registry.model.poll.PollMessage;
|
|
import google.registry.model.registry.Registries;
|
|
import google.registry.model.registry.Registry;
|
|
import google.registry.model.transfer.TransferData;
|
|
import google.registry.model.transfer.TransferStatus;
|
|
import google.registry.util.NonFinalForTesting;
|
|
import google.registry.util.RandomStringGenerator;
|
|
import google.registry.util.StringGenerator;
|
|
import google.registry.util.XmlToEnumMapper;
|
|
import google.registry.xjc.domain.XjcDomainContactType;
|
|
import google.registry.xjc.domain.XjcDomainNsType;
|
|
import google.registry.xjc.domain.XjcDomainStatusType;
|
|
import google.registry.xjc.rdedomain.XjcRdeDomain;
|
|
import google.registry.xjc.rdedomain.XjcRdeDomainElement;
|
|
import google.registry.xjc.rdedomain.XjcRdeDomainTransferDataType;
|
|
import google.registry.xjc.rgp.XjcRgpStatusType;
|
|
import google.registry.xjc.secdns.XjcSecdnsDsDataType;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.ProviderException;
|
|
import java.security.SecureRandom;
|
|
import java.util.Random;
|
|
import org.joda.time.DateTime;
|
|
|
|
/** Utility class that converts an {@link XjcRdeDomainElement} into a {@link DomainResource}. */
|
|
final class XjcToDomainResourceConverter extends XjcToEppResourceConverter {
|
|
|
|
@NonFinalForTesting
|
|
static StringGenerator stringGenerator = new RandomStringGenerator(
|
|
StringGenerator.Alphabets.BASE_64, getRandom());
|
|
|
|
static Random getRandom() {
|
|
try {
|
|
return SecureRandom.getInstance("NativePRNG");
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new ProviderException(e);
|
|
}
|
|
}
|
|
|
|
private static final XmlToEnumMapper<TransferStatus> TRANSFER_STATUS_MAPPER =
|
|
XmlToEnumMapper.create(TransferStatus.values());
|
|
|
|
private static final Function<String, Key<HostResource>> HOST_OBJ_CONVERTER =
|
|
fullyQualifiedHostName -> {
|
|
// host names are always lower case
|
|
fullyQualifiedHostName = canonicalizeDomainName(fullyQualifiedHostName);
|
|
Key<HostResource> key =
|
|
ForeignKeyIndex.loadAndGetKey(
|
|
HostResource.class, fullyQualifiedHostName, DateTime.now(UTC));
|
|
checkState(
|
|
key != null,
|
|
String.format("HostResource not found with name '%s'", fullyQualifiedHostName));
|
|
return key;
|
|
};
|
|
|
|
/** Converts {@link XjcRgpStatusType} to {@link GracePeriod} */
|
|
private static class GracePeriodConverter implements Function<XjcRgpStatusType, GracePeriod> {
|
|
|
|
private final XjcRdeDomain domain;
|
|
private final Key<BillingEvent.Recurring> autoRenewBillingEvent;
|
|
private final Registry tld;
|
|
|
|
GracePeriodConverter(XjcRdeDomain domain, Key<BillingEvent.Recurring> autoRenewBillingEvent) {
|
|
this.domain = domain;
|
|
this.autoRenewBillingEvent = autoRenewBillingEvent;
|
|
this.tld =
|
|
Registry.get(
|
|
Registries.findTldForNameOrThrow(InternetDomainName.from(domain.getName()))
|
|
.toString());
|
|
}
|
|
|
|
@Override
|
|
public GracePeriod apply(XjcRgpStatusType gracePeriodStatus) {
|
|
switch (gracePeriodStatus.getS()) {
|
|
case ADD_PERIOD:
|
|
return GracePeriod.createWithoutBillingEvent(
|
|
GracePeriodStatus.ADD,
|
|
domain.getCrDate().plus(this.tld.getAddGracePeriodLength()),
|
|
domain.getCrRr().getValue());
|
|
case AUTO_RENEW_PERIOD:
|
|
return GracePeriod.createForRecurring(
|
|
GracePeriodStatus.AUTO_RENEW,
|
|
domain.getUpDate().plus(this.tld.getAutoRenewGracePeriodLength()),
|
|
domain.getClID(),
|
|
autoRenewBillingEvent);
|
|
case PENDING_DELETE:
|
|
return GracePeriod.createWithoutBillingEvent(
|
|
GracePeriodStatus.PENDING_DELETE,
|
|
domain.getUpDate().plus(this.tld.getPendingDeleteLength()),
|
|
domain.getClID());
|
|
case REDEMPTION_PERIOD:
|
|
return GracePeriod.createWithoutBillingEvent(
|
|
GracePeriodStatus.REDEMPTION,
|
|
domain.getUpDate().plus(this.tld.getRedemptionGracePeriodLength()),
|
|
domain.getClID());
|
|
case RENEW_PERIOD:
|
|
return GracePeriod.createWithoutBillingEvent(
|
|
GracePeriodStatus.RENEW,
|
|
domain.getUpDate().plus(this.tld.getRenewGracePeriodLength()),
|
|
domain.getClID());
|
|
case TRANSFER_PERIOD:
|
|
return GracePeriod.createWithoutBillingEvent(
|
|
GracePeriodStatus.TRANSFER,
|
|
domain.getUpDate().plus(this.tld.getTransferGracePeriodLength()),
|
|
domain.getTrnData().getReRr().getValue());
|
|
default:
|
|
throw new IllegalArgumentException(
|
|
"Unsupported grace period status: " + gracePeriodStatus.getS());
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Converts {@link XjcRdeDomain} to {@link DomainResource}. */
|
|
static DomainResource convertDomain(
|
|
XjcRdeDomain domain,
|
|
BillingEvent.Recurring autoRenewBillingEvent,
|
|
PollMessage.Autorenew autoRenewPollMessage) {
|
|
GracePeriodConverter gracePeriodConverter =
|
|
new GracePeriodConverter(domain, Key.create(autoRenewBillingEvent));
|
|
DomainResource.Builder builder =
|
|
new DomainResource.Builder()
|
|
.setFullyQualifiedDomainName(canonicalizeDomainName(domain.getName()))
|
|
.setRepoId(domain.getRoid())
|
|
.setIdnTableName(domain.getIdnTableId())
|
|
.setPersistedCurrentSponsorClientId(domain.getClID())
|
|
.setCreationClientId(domain.getCrRr().getValue())
|
|
.setCreationTime(domain.getCrDate())
|
|
.setAutorenewPollMessage(Key.create(autoRenewPollMessage))
|
|
.setAutorenewBillingEvent(Key.create(autoRenewBillingEvent))
|
|
.setRegistrationExpirationTime(domain.getExDate())
|
|
.setLastEppUpdateTime(domain.getUpDate())
|
|
.setLastEppUpdateClientId(domain.getUpRr() == null ? null : domain.getUpRr().getValue())
|
|
.setLastTransferTime(domain.getTrDate())
|
|
.setStatusValues(
|
|
domain
|
|
.getStatuses()
|
|
.stream()
|
|
.map(XjcToDomainResourceConverter::convertStatusType)
|
|
.collect(toImmutableSet()))
|
|
.setNameservers(convertNameservers(domain.getNs()))
|
|
.setGracePeriods(
|
|
domain
|
|
.getRgpStatuses()
|
|
.stream()
|
|
.map(gracePeriodConverter)
|
|
.collect(toImmutableSet()))
|
|
.setContacts(
|
|
domain
|
|
.getContacts()
|
|
.stream()
|
|
.map(XjcToDomainResourceConverter::convertContactType)
|
|
.collect(toImmutableSet()))
|
|
.setDsData(
|
|
domain.getSecDNS() == null
|
|
? ImmutableSet.<DelegationSignerData>of()
|
|
: domain
|
|
.getSecDNS()
|
|
.getDsDatas()
|
|
.stream()
|
|
.map(XjcToDomainResourceConverter::convertSecdnsDsDataType)
|
|
.collect(toImmutableSet()))
|
|
.setTransferData(convertDomainTransferData(domain.getTrnData()))
|
|
// authInfo pw must be a token between 6 and 16 characters in length
|
|
// generate a token of 16 characters as the default authInfo pw
|
|
.setAuthInfo(
|
|
DomainAuthInfo.create(
|
|
PasswordAuth.create(stringGenerator.createString(16), domain.getRoid())));
|
|
checkArgumentNotNull(
|
|
domain.getRegistrant(), "Registrant is missing for domain '%s'", domain.getName());
|
|
builder = builder.setRegistrant(convertRegistrant(domain.getRegistrant()));
|
|
return builder.build();
|
|
}
|
|
|
|
/** Returns {@link Key} for registrant from foreign key */
|
|
private static Key<ContactResource> convertRegistrant(String contactId) {
|
|
Key<ContactResource> key =
|
|
ForeignKeyIndex.loadAndGetKey(ContactResource.class, contactId, DateTime.now(UTC));
|
|
checkState(key != null, "Registrant not found: '%s'", contactId);
|
|
return key;
|
|
}
|
|
|
|
/** Converts {@link XjcDomainNsType} to <code>ImmutableSet<Key<HostResource>></code>. */
|
|
private static ImmutableSet<Key<HostResource>> convertNameservers(XjcDomainNsType ns) {
|
|
// If no hosts are specified, return an empty set
|
|
if (ns == null || (ns.getHostAttrs() == null && ns.getHostObjs() == null)) {
|
|
return ImmutableSet.of();
|
|
}
|
|
// Domain linked hosts must be specified by host object, not host attributes.
|
|
checkArgument(
|
|
ns.getHostAttrs() == null || ns.getHostAttrs().isEmpty(),
|
|
"Host attributes are not yet supported");
|
|
return ns.getHostObjs().stream().map(HOST_OBJ_CONVERTER).collect(toImmutableSet());
|
|
}
|
|
|
|
/** Converts {@link XjcRdeDomainTransferDataType} to {@link TransferData}. */
|
|
private static TransferData convertDomainTransferData(XjcRdeDomainTransferDataType data) {
|
|
if (data == null) {
|
|
return TransferData.EMPTY;
|
|
}
|
|
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();
|
|
}
|
|
|
|
/** Converts {@link XjcDomainStatusType} to {@link StatusValue}. */
|
|
private static StatusValue convertStatusType(XjcDomainStatusType type) {
|
|
return StatusValue.fromXmlName(type.getS().value());
|
|
}
|
|
|
|
/** Converts {@link XjcSecdnsDsDataType} to {@link DelegationSignerData}. */
|
|
private static DelegationSignerData convertSecdnsDsDataType(XjcSecdnsDsDataType secdns) {
|
|
return DelegationSignerData.create(
|
|
secdns.getKeyTag(), secdns.getAlg(), secdns.getDigestType(), secdns.getDigest());
|
|
}
|
|
|
|
/** Converts {@link XjcDomainContactType} to {@link DesignatedContact}. */
|
|
private static DesignatedContact convertContactType(XjcDomainContactType contact) {
|
|
String contactId = contact.getValue();
|
|
Key<ContactResource> key =
|
|
ForeignKeyIndex.loadAndGetKey(ContactResource.class, contactId, DateTime.now(UTC));
|
|
checkState(key != null, "Contact not found: '%s'", contactId);
|
|
DesignatedContact.Type type =
|
|
DesignatedContact.Type.valueOf(Ascii.toUpperCase(contact.getType().toString()));
|
|
return DesignatedContact.create(type, key);
|
|
}
|
|
|
|
private XjcToDomainResourceConverter() {}
|
|
}
|