mirror of
https://github.com/google/nomulus
synced 2026-07-06 08:06:33 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e70f14001c | |||
| 22d3612be3 | |||
| ad8bc05877 | |||
| a3537447ef | |||
| 4e66fed497 | |||
| 886cdfa39b | |||
| beefa9364b | |||
| 73210e4b09 | |||
| 08cec96a93 | |||
| 31ef402c50 | |||
| e89cc4406a | |||
| 48de5d8375 | |||
| 59abc1d154 | |||
| 6794c6fbd7 | |||
| 0c384adc22 | |||
| 3b679058b0 |
@@ -85,7 +85,7 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
|
||||
Optional<Lock> lock =
|
||||
Lock.acquire(
|
||||
this.getClass().getSimpleName(), null, LEASE_LENGTH, requestStatusChecker, false);
|
||||
if (lock.isEmpty()) {
|
||||
if (!lock.isPresent()) {
|
||||
String message = "Can't acquire SQL commit log replay lock, aborting.";
|
||||
logger.atSevere().log(message);
|
||||
// App Engine will retry on any non-2xx status code, which we don't want in this case.
|
||||
@@ -182,7 +182,7 @@ public class ReplayCommitLogsToSqlAction implements Runnable {
|
||||
}
|
||||
|
||||
private static int compareByWeight(VersionedEntity a, VersionedEntity b) {
|
||||
return getEntityPriority(a.key().getKind(), a.getEntity().isEmpty())
|
||||
- getEntityPriority(b.key().getKind(), b.getEntity().isEmpty());
|
||||
return getEntityPriority(a.key().getKind(), !a.getEntity().isPresent())
|
||||
- getEntityPriority(b.key().getKind(), !b.getEntity().isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static google.registry.request.RequestParameters.extractOptionalHeader;
|
||||
import static google.registry.util.X509Utils.loadCertificate;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -25,11 +26,18 @@ import com.google.common.net.InetAddresses;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.request.Header;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -42,6 +50,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||
* <dl>
|
||||
* <dt>X-SSL-Certificate
|
||||
* <dd>This field should contain a base64 encoded digest of the client's TLS certificate. It is
|
||||
* used only if the validation of the full certificate fails.
|
||||
* <dt>X-SSL-Full-Certificate
|
||||
* <dd>This field should contain a base64 encoding of the client's TLS certificate. It is
|
||||
* validated during an EPP login command against a known good value that is transmitted out of
|
||||
* band.
|
||||
* <dt>X-Forwarded-For
|
||||
@@ -55,16 +66,22 @@ public class TlsCredentials implements TransportCredentials {
|
||||
|
||||
private final boolean requireSslCertificates;
|
||||
private final Optional<String> clientCertificateHash;
|
||||
private final Optional<String> clientCertificate;
|
||||
private final Optional<InetAddress> clientInetAddr;
|
||||
private final CertificateChecker certificateChecker;
|
||||
|
||||
@Inject
|
||||
public TlsCredentials(
|
||||
@Config("requireSslCertificates") boolean requireSslCertificates,
|
||||
@Header("X-SSL-Certificate") Optional<String> clientCertificateHash,
|
||||
@Header("X-Forwarded-For") Optional<String> clientAddress) {
|
||||
@Header("X-SSL-Full-Certificate") Optional<String> clientCertificate,
|
||||
@Header("X-Forwarded-For") Optional<String> clientAddress,
|
||||
CertificateChecker certificateChecker) {
|
||||
this.requireSslCertificates = requireSslCertificates;
|
||||
this.clientCertificateHash = clientCertificateHash;
|
||||
this.clientCertificate = clientCertificate;
|
||||
this.clientInetAddr = clientAddress.map(TlsCredentials::parseInetAddress);
|
||||
this.certificateChecker = certificateChecker;
|
||||
}
|
||||
|
||||
static InetAddress parseInetAddress(String asciiAddr) {
|
||||
@@ -91,7 +108,7 @@ public class TlsCredentials implements TransportCredentials {
|
||||
ImmutableList<CidrAddressBlock> ipAddressAllowList = registrar.getIpAddressAllowList();
|
||||
if (ipAddressAllowList.isEmpty()) {
|
||||
logger.atInfo().log(
|
||||
"Skipping IP allow list check because %s doesn't have an IP allow list",
|
||||
"Skipping IP allow list check because %s doesn't have an IP allow list.",
|
||||
registrar.getClientId());
|
||||
return;
|
||||
}
|
||||
@@ -115,11 +132,80 @@ public class TlsCredentials implements TransportCredentials {
|
||||
/**
|
||||
* Verifies client SSL certificate is permitted to issue commands as {@code registrar}.
|
||||
*
|
||||
* @throws MissingRegistrarCertificateException if frontend didn't send certificate hash header
|
||||
* @throws MissingRegistrarCertificateException if frontend didn't send certificate header
|
||||
* @throws BadRegistrarCertificateException if registrar requires certificate and it didn't match
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateCertificate(Registrar registrar) throws AuthenticationErrorException {
|
||||
// Check that certificate is present in registrar object
|
||||
if (!registrar.getClientCertificate().isPresent()
|
||||
&& !registrar.getFailoverClientCertificate().isPresent()) {
|
||||
// Log an error and validate using certificate hash instead
|
||||
// TODO(sarahbot): throw a RegistrarCertificateNotConfiguredException once hash is no longer
|
||||
// used as failover
|
||||
logger.atWarning().log(
|
||||
"There is no certificate configured for registrar %s.", registrar.getClientId());
|
||||
} else if (!clientCertificate.isPresent()) {
|
||||
// Check that the request included the full certificate
|
||||
// Log an error and validate using certificate hash instead
|
||||
// TODO(sarahbot): throw a MissingRegistrarCertificateException once hash is no longer used as
|
||||
// failover
|
||||
logger.atWarning().log(
|
||||
"Request from registrar %s did not include X-SSL-Full-Certificate.",
|
||||
registrar.getClientId());
|
||||
} else {
|
||||
X509Certificate passedCert;
|
||||
Optional<X509Certificate> storedCert;
|
||||
Optional<X509Certificate> storedFailoverCert;
|
||||
|
||||
try {
|
||||
storedCert = deserializePemCert(registrar.getClientCertificate());
|
||||
storedFailoverCert = deserializePemCert(registrar.getFailoverClientCertificate());
|
||||
passedCert = decodeCertString(clientCertificate.get());
|
||||
} catch (Exception e) {
|
||||
// TODO(Sarahbot@): remove this catch once we know it's working
|
||||
logger.atWarning().log(
|
||||
"Error converting certificate string to certificate for %s: %s",
|
||||
registrar.getClientId(), e);
|
||||
validateCertificateHash(registrar);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the certificate is equal to the one on file for the registrar.
|
||||
if (passedCert.equals(storedCert.orElse(null))
|
||||
|| passedCert.equals(storedFailoverCert.orElse(null))) {
|
||||
// Check certificate for any requirement violations
|
||||
// TODO(Sarahbot@): Throw exceptions instead of just logging once requirement enforcement
|
||||
// begins
|
||||
try {
|
||||
certificateChecker.validateCertificate(passedCert);
|
||||
} catch (InsecureCertificateException e) {
|
||||
// throw exception in unit tests and Sandbox
|
||||
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||
|| RegistryEnvironment.get().equals(RegistryEnvironment.SANDBOX)) {
|
||||
throw new CertificateContainsSecurityViolationsException(e);
|
||||
}
|
||||
logger.atWarning().log(
|
||||
"Registrar certificate used for %s does not meet certificate requirements: %s",
|
||||
registrar.getClientId(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().log(
|
||||
"Error validating certificate for %s: %s", registrar.getClientId(), e);
|
||||
}
|
||||
// successfully validated, return here since hash validation is not necessary
|
||||
return;
|
||||
}
|
||||
// Log an error and validate using certificate hash instead
|
||||
// TODO(sarahbot): throw a BadRegistrarCertificateException once hash is no longer used as
|
||||
// failover
|
||||
logger.atWarning().log("Non-matching certificate for registrar %s.", registrar.getClientId());
|
||||
}
|
||||
validateCertificateHash(registrar);
|
||||
}
|
||||
|
||||
private void validateCertificateHash(Registrar registrar) throws AuthenticationErrorException {
|
||||
// Check the certificate hash as a failover
|
||||
// TODO(sarahbot): Remove hash checks once certificate checks are working.
|
||||
if (!registrar.getClientCertificateHash().isPresent()
|
||||
&& !registrar.getFailoverClientCertificateHash().isPresent()) {
|
||||
if (requireSslCertificates) {
|
||||
@@ -130,14 +216,17 @@ public class TlsCredentials implements TransportCredentials {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Check that the request included the certificate hash
|
||||
if (!clientCertificateHash.isPresent()) {
|
||||
logger.atInfo().log("Request did not include X-SSL-Certificate");
|
||||
logger.atInfo().log(
|
||||
"Request from registrar %s did not include X-SSL-Certificate.", registrar.getClientId());
|
||||
throw new MissingRegistrarCertificateException();
|
||||
}
|
||||
// Check if the certificate hash is equal to the one on file for the registrar.
|
||||
if (!clientCertificateHash.equals(registrar.getClientCertificateHash())
|
||||
&& !clientCertificateHash.equals(registrar.getFailoverClientCertificateHash())) {
|
||||
logger.atWarning().log(
|
||||
"bad certificate hash (%s) for %s, wanted either %s or %s",
|
||||
"Non-matching certificate hash (%s) for %s, wanted either %s or %s.",
|
||||
clientCertificateHash,
|
||||
registrar.getClientId(),
|
||||
registrar.getClientCertificateHash(),
|
||||
@@ -153,9 +242,26 @@ public class TlsCredentials implements TransportCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a PEM formatted certificate string into an X509Certificate
|
||||
private Optional<X509Certificate> deserializePemCert(Optional<String> certificateString)
|
||||
throws CertificateException {
|
||||
if (certificateString.isPresent()) {
|
||||
return Optional.of(loadCertificate(certificateString.get()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Decodes the string representation of an encoded certificate back into an X509Certificate
|
||||
private X509Certificate decodeCertString(String encodedCertString) throws CertificateException {
|
||||
byte decodedCert[] = Base64.getDecoder().decode(encodedCertString);
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedCert);
|
||||
return loadCertificate(inputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("clientCertificate", clientCertificate.orElse(null))
|
||||
.add("clientCertificateHash", clientCertificateHash.orElse(null))
|
||||
.add("clientAddress", clientInetAddr.orElse(null))
|
||||
.toString();
|
||||
@@ -168,6 +274,20 @@ public class TlsCredentials implements TransportCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar certificate contains the following security violations: ... */
|
||||
public static class CertificateContainsSecurityViolationsException
|
||||
extends AuthenticationErrorException {
|
||||
InsecureCertificateException exception;
|
||||
|
||||
CertificateContainsSecurityViolationsException(InsecureCertificateException exception) {
|
||||
super(
|
||||
String.format(
|
||||
"Registrar certificate contains the following security violations:\n%s",
|
||||
exception.getMessage()));
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar certificate not present. */
|
||||
public static class MissingRegistrarCertificateException extends AuthenticationErrorException {
|
||||
MissingRegistrarCertificateException() {
|
||||
@@ -202,6 +322,14 @@ public class TlsCredentials implements TransportCredentials {
|
||||
return extractOptionalHeader(req, "X-SSL-Certificate");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header("X-SSL-Full-Certificate")
|
||||
static Optional<String> provideClientCertificate(HttpServletRequest req) {
|
||||
// Note: This header is actually required, we just want to handle its absence explicitly
|
||||
// by throwing an EPP exception rather than a generic Bad Request exception.
|
||||
return extractOptionalHeader(req, "X-SSL-Full-Certificate");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header("X-Forwarded-For")
|
||||
static Optional<String> provideForwardedFor(HttpServletRequest req) {
|
||||
|
||||
@@ -89,14 +89,26 @@ public class CertificateChecker {
|
||||
* Checks the given certificate string for violations and throws an exception if any violations
|
||||
* exist.
|
||||
*/
|
||||
public void validateCertificate(String certificateString) {
|
||||
ImmutableSet<CertificateViolation> violations = checkCertificate(certificateString);
|
||||
public void validateCertificate(String certificateString) throws InsecureCertificateException {
|
||||
handleCertViolations(checkCertificate(certificateString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given certificate string for violations and throws an exception if any violations
|
||||
* exist.
|
||||
*/
|
||||
public void validateCertificate(X509Certificate certificate) throws InsecureCertificateException {
|
||||
handleCertViolations(checkCertificate(certificate));
|
||||
}
|
||||
|
||||
private void handleCertViolations(ImmutableSet<CertificateViolation> violations)
|
||||
throws InsecureCertificateException {
|
||||
if (!violations.isEmpty()) {
|
||||
String displayMessages =
|
||||
violations.stream()
|
||||
.map(violation -> getViolationDisplayMessage(violation))
|
||||
.collect(Collectors.joining("\n"));
|
||||
throw new IllegalArgumentException(displayMessages);
|
||||
throw new InsecureCertificateException(violations, displayMessages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,4 +270,14 @@ public class CertificateChecker {
|
||||
return certificateChecker.getViolationDisplayMessage(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception to throw when a certificate has security violations. */
|
||||
public static class InsecureCertificateException extends Exception {
|
||||
ImmutableSet<CertificateViolation> violations;
|
||||
|
||||
InsecureCertificateException(ImmutableSet<CertificateViolation> violations, String message) {
|
||||
super(message);
|
||||
this.violations = violations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.approvePendingTransfer;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
@@ -94,7 +94,8 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
|
||||
// Create a poll message for the gaining client.
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry);
|
||||
ofy().save().<Object>entities(newContact, historyEntry, gainingPollMessage);
|
||||
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -22,9 +22,9 @@ import static google.registry.flows.ResourceFlowUtils.verifyTransferInitiator;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
@@ -90,7 +90,8 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
|
||||
// Create a poll message for the losing client.
|
||||
PollMessage losingPollMessage =
|
||||
createLosingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry);
|
||||
ofy().save().<Object>entities(newContact, historyEntry, losingPollMessage);
|
||||
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), losingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -22,9 +22,9 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
@@ -87,7 +87,8 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
|
||||
.build();
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(targetId, newContact.getTransferData(), historyEntry);
|
||||
ofy().save().<Object>entities(newContact, historyEntry, gainingPollMessage);
|
||||
tm().insertAll(ImmutableSet.of(historyEntry.toChildHistoryEntity(), gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -23,7 +23,6 @@ import static google.registry.flows.contact.ContactFlowUtils.createGainingTransf
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -145,12 +144,13 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
|
||||
.setTransferData(pendingTransferData)
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
ofy().save().<Object>entities(
|
||||
newContact,
|
||||
historyEntry,
|
||||
requestPollMessage,
|
||||
serverApproveGainingPollMessage,
|
||||
serverApproveLosingPollMessage);
|
||||
tm().update(newContact);
|
||||
tm().insertAll(
|
||||
ImmutableSet.of(
|
||||
historyEntry.toChildHistoryEntity(),
|
||||
requestPollMessage,
|
||||
serverApproveGainingPollMessage,
|
||||
serverApproveLosingPollMessage));
|
||||
return responseBuilder
|
||||
.setResultFromCode(SUCCESS_WITH_ACTION_PENDING)
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
|
||||
@@ -154,7 +154,7 @@ public class AllocationTokenFlowUtils {
|
||||
}
|
||||
Optional<AllocationToken> maybeTokenEntity =
|
||||
tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token));
|
||||
if (maybeTokenEntity.isEmpty()) {
|
||||
if (!maybeTokenEntity.isPresent()) {
|
||||
throw new InvalidAllocationTokenException();
|
||||
}
|
||||
if (maybeTokenEntity.get().isRedeemed()) {
|
||||
|
||||
@@ -175,7 +175,7 @@ public class Address extends ImmutableObject implements Jsonifiable {
|
||||
* entity from Datastore.
|
||||
*
|
||||
* <p>This callback method is used by Objectify to set streetLine[1,2,3] fields as they are not
|
||||
* persisted in the Datastore. TODO(shicong): Delete this method after database migration.
|
||||
* persisted in the Datastore.
|
||||
*/
|
||||
void onLoad(@AlsoLoad("street") List<String> street) {
|
||||
mapStreetListToIndividualFields(street);
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.google.common.collect.Streams;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Result;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -292,6 +293,21 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
getOfy().clearSessionCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link Result} instance synchronously if not in a transaction.
|
||||
*
|
||||
* <p>The {@link Result} instance contains a task that will be executed by Objectify
|
||||
* asynchronously. If it is in a transaction, we don't need to execute the task immediately
|
||||
* because it is guaranteed to be done by the end of the transaction. However, if it is not in a
|
||||
* transaction, we need to execute it in case the following code expects that happens before
|
||||
* themselves.
|
||||
*/
|
||||
private void syncIfTransactionless(Result<?> result) {
|
||||
if (!inTransaction()) {
|
||||
result.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following three methods exist due to the migration to Cloud SQL.
|
||||
*
|
||||
@@ -309,34 +325,23 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
syncIfTransactionless(getOfy().save().entity(entity));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T toChildHistoryEntryIfPossible(@Nullable T obj) {
|
||||
// NB: The Key of the object in question may not necessarily be the resulting class that we
|
||||
// wish to have. Because all *History classes are @EntitySubclasses, their Keys will have type
|
||||
// HistoryEntry -- even if you create them based off the *History class.
|
||||
if (obj != null && HistoryEntry.class.isAssignableFrom(obj.getClass())) {
|
||||
return (T) ((HistoryEntry) obj).toChildHistoryEntity();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> T loadNullable(VKey<T> key) {
|
||||
return toChildHistoryEntryIfPossible(getOfy().load().key(key.getOfyKey()).now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link Result} instance synchronously if not in a transaction.
|
||||
*
|
||||
* <p>The {@link Result} instance contains a task that will be executed by Objectify
|
||||
* asynchronously. If it is in a transaction, we don't need to execute the task immediately
|
||||
* because it is guaranteed to be done by the end of the transaction. However, if it is not in a
|
||||
* transaction, we need to execute it in case the following code expects that happens before
|
||||
* themselves.
|
||||
*/
|
||||
private void syncIfTransactionless(Result<?> result) {
|
||||
if (!inTransaction()) {
|
||||
result.now();
|
||||
/** Converts a nonnull {@link HistoryEntry} to the child format, e.g. {@link DomainHistory} */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T toChildHistoryEntryIfPossible(@Nullable T obj) {
|
||||
// NB: The Key of the object in question may not necessarily be the resulting class that we
|
||||
// wish to have. Because all *History classes are @EntitySubclasses, their Keys will have type
|
||||
// HistoryEntry -- even if you create them based off the *History class.
|
||||
if (obj instanceof HistoryEntry
|
||||
&& !(obj instanceof ContactHistory)
|
||||
&& !(obj instanceof DomainHistory)
|
||||
&& !(obj instanceof HostHistory)) {
|
||||
return (T) ((HistoryEntry) obj).toChildHistoryEntity();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.model.poll;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
@@ -378,6 +379,47 @@ public abstract class PollMessage extends ImmutableObject
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnLoad
|
||||
void onLoad() {
|
||||
super.onLoad();
|
||||
if (!isNullOrEmpty(contactPendingActionNotificationResponses)) {
|
||||
pendingActionNotificationResponse = contactPendingActionNotificationResponses.get(0);
|
||||
}
|
||||
if (!isNullOrEmpty(contactTransferResponses)) {
|
||||
contactId = contactTransferResponses.get(0).getContactId();
|
||||
transferResponse = contactTransferResponses.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
super.postLoad();
|
||||
if (pendingActionNotificationResponse != null) {
|
||||
contactPendingActionNotificationResponses =
|
||||
ImmutableList.of(
|
||||
ContactPendingActionNotificationResponse.create(
|
||||
pendingActionNotificationResponse.nameOrId.value,
|
||||
pendingActionNotificationResponse.getActionResult(),
|
||||
pendingActionNotificationResponse.getTrid(),
|
||||
pendingActionNotificationResponse.processedDate));
|
||||
}
|
||||
if (contactId != null && transferResponse != null) {
|
||||
contactTransferResponses =
|
||||
ImmutableList.of(
|
||||
new ContactTransferResponse.Builder()
|
||||
.setContactId(contactId)
|
||||
.setGainingClientId(transferResponse.getGainingClientId())
|
||||
.setLosingClientId(transferResponse.getLosingClientId())
|
||||
.setTransferStatus(transferResponse.getTransferStatus())
|
||||
.setTransferRequestTime(transferResponse.getTransferRequestTime())
|
||||
.setPendingTransferExpirationTime(
|
||||
transferResponse.getPendingTransferExpirationTime())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
/** A builder for {@link OneTime} since it is immutable. */
|
||||
public static class Builder extends PollMessage.Builder<OneTime, Builder> {
|
||||
|
||||
@@ -396,6 +438,10 @@ public abstract class PollMessage extends ImmutableObject
|
||||
.filter(ContactPendingActionNotificationResponse.class::isInstance)
|
||||
.map(ContactPendingActionNotificationResponse.class::cast)
|
||||
.collect(toImmutableList()));
|
||||
if (getInstance().contactPendingActionNotificationResponses != null) {
|
||||
getInstance().pendingActionNotificationResponse =
|
||||
getInstance().contactPendingActionNotificationResponses.get(0);
|
||||
}
|
||||
getInstance().contactTransferResponses =
|
||||
forceEmptyToNull(
|
||||
responseData
|
||||
@@ -403,6 +449,11 @@ public abstract class PollMessage extends ImmutableObject
|
||||
.filter(ContactTransferResponse.class::isInstance)
|
||||
.map(ContactTransferResponse.class::cast)
|
||||
.collect(toImmutableList()));
|
||||
if (getInstance().contactTransferResponses != null) {
|
||||
getInstance().contactId = getInstance().contactTransferResponses.get(0).getContactId();
|
||||
getInstance().transferResponse = getInstance().contactTransferResponses.get(0);
|
||||
}
|
||||
|
||||
getInstance().domainPendingActionNotificationResponses =
|
||||
forceEmptyToNull(
|
||||
responseData
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2020 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.model.reporting;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Comparator;
|
||||
import javax.persistence.EntityManager;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Retrieves {@link HistoryEntry} descendants (e.g. {@link DomainHistory}).
|
||||
*
|
||||
* <p>This class is configured to retrieve either from Datastore or SQL, depending on which database
|
||||
* is currently considered the primary database.
|
||||
*/
|
||||
public class HistoryEntryDao {
|
||||
|
||||
/** Loads all history objects in the times specified, including all types. */
|
||||
public static Iterable<? extends HistoryEntry> loadAllHistoryObjects(
|
||||
DateTime afterTime, DateTime beforeTime) {
|
||||
if (tm().isOfy()) {
|
||||
return ofy()
|
||||
.load()
|
||||
.type(HistoryEntry.class)
|
||||
.order("modificationTime")
|
||||
.filter("modificationTime >=", afterTime)
|
||||
.filter("modificationTime <=", beforeTime);
|
||||
} else {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
Iterables.concat(
|
||||
loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime),
|
||||
loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime),
|
||||
loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime)));
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads all history objects corresponding to the given {@link EppResource}. */
|
||||
public static Iterable<? extends HistoryEntry> loadHistoryObjectsForResource(
|
||||
VKey<? extends EppResource> parentKey) {
|
||||
return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME);
|
||||
}
|
||||
|
||||
/** Loads all history objects in the time period specified for the given {@link EppResource}. */
|
||||
public static Iterable<? extends HistoryEntry> loadHistoryObjectsForResource(
|
||||
VKey<? extends EppResource> parentKey, DateTime afterTime, DateTime beforeTime) {
|
||||
if (tm().isOfy()) {
|
||||
return ofy()
|
||||
.load()
|
||||
.type(HistoryEntry.class)
|
||||
.ancestor(parentKey.getOfyKey())
|
||||
.order("modificationTime")
|
||||
.filter("modificationTime >=", afterTime)
|
||||
.filter("modificationTime <=", beforeTime);
|
||||
} else {
|
||||
return jpaTm()
|
||||
.transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime));
|
||||
}
|
||||
}
|
||||
|
||||
private static Iterable<? extends HistoryEntry> loadHistoryObjectsForResourceFromSql(
|
||||
VKey<? extends EppResource> parentKey, DateTime afterTime, DateTime beforeTime) {
|
||||
Class<? extends HistoryEntry> historyClass = getHistoryClassFromParent(parentKey.getKind());
|
||||
String repoIdFieldName = getRepoIdFieldNameFromHistoryClass(historyClass);
|
||||
EntityManager entityManager = jpaTm().getEntityManager();
|
||||
String tableName = entityManager.getMetamodel().entity(historyClass).getName();
|
||||
String queryString =
|
||||
String.format(
|
||||
"SELECT entry FROM %s entry WHERE entry.modificationTime >= :afterTime AND "
|
||||
+ "entry.modificationTime <= :beforeTime AND entry.%s = :parentKey",
|
||||
tableName, repoIdFieldName);
|
||||
return entityManager
|
||||
.createQuery(queryString, historyClass)
|
||||
.setParameter("afterTime", afterTime)
|
||||
.setParameter("beforeTime", beforeTime)
|
||||
.setParameter("parentKey", parentKey.getSqlKey().toString())
|
||||
.getResultStream()
|
||||
.sorted(Comparator.comparing(HistoryEntry::getModificationTime))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static Class<? extends HistoryEntry> getHistoryClassFromParent(
|
||||
Class<? extends EppResource> parent) {
|
||||
if (parent.equals(ContactResource.class)) {
|
||||
return ContactHistory.class;
|
||||
} else if (parent.equals(DomainBase.class)) {
|
||||
return DomainHistory.class;
|
||||
} else if (parent.equals(HostResource.class)) {
|
||||
return HostHistory.class;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Unknown history type for parent %s", parent.getName()));
|
||||
}
|
||||
|
||||
private static String getRepoIdFieldNameFromHistoryClass(
|
||||
Class<? extends HistoryEntry> historyClass) {
|
||||
return historyClass.equals(ContactHistory.class)
|
||||
? "contactRepoId"
|
||||
: historyClass.equals(DomainHistory.class) ? "domainRepoId" : "hostRepoId";
|
||||
}
|
||||
|
||||
private static Iterable<? extends HistoryEntry> loadAllHistoryObjectsFromSql(
|
||||
Class<? extends HistoryEntry> historyClass, DateTime afterTime, DateTime beforeTime) {
|
||||
EntityManager entityManager = jpaTm().getEntityManager();
|
||||
return entityManager
|
||||
.createQuery(
|
||||
String.format(
|
||||
"SELECT entry FROM %s entry WHERE entry.modificationTime >= :afterTime AND "
|
||||
+ "entry.modificationTime <= :beforeTime",
|
||||
entityManager.getMetamodel().entity(historyClass).getName()),
|
||||
historyClass)
|
||||
.setParameter("afterTime", afterTime)
|
||||
.setParameter("beforeTime", beforeTime)
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,25 @@
|
||||
|
||||
package google.registry.model.transfer;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.AlsoLoad;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.TypeUtils.TypeInstantiator;
|
||||
import java.util.Set;
|
||||
@@ -32,6 +42,7 @@ import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/**
|
||||
@@ -68,15 +79,27 @@ public abstract class TransferData<
|
||||
@IgnoreSave(IfNull.class)
|
||||
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities;
|
||||
|
||||
// The following 3 fields are the replacement for serverApproveEntities in Cloud SQL.
|
||||
// TODO(b/177589157): Make transfer flows work with Cloud SQL.
|
||||
@Ignore
|
||||
@Column(name = "transfer_gaining_poll_message_id")
|
||||
Long gainingTransferPollMessageId;
|
||||
@Column(name = "transfer_repo_id")
|
||||
String repoId;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_losing_poll_message_id")
|
||||
Long losingTransferPollMessageId;
|
||||
@Column(name = "transfer_history_entry_id")
|
||||
Long historyEntryId;
|
||||
|
||||
// The pollMessageId1 and pollMessageId2 are used to store the IDs for gaining and losing poll
|
||||
// messages in Cloud SQL, and they are added to replace the VKeys in serverApproveEntities.
|
||||
// Although we can distinguish which is which when we construct the TransferData instance from
|
||||
// the transfer request flow, when the instance is loaded from Datastore, we cannot make this
|
||||
// distinction because they are just VKeys. Also, the only way we use serverApproveEntities is to
|
||||
// just delete all the entities referenced by the VKeys, so we don't need to make the distinction.
|
||||
@Ignore
|
||||
@Column(name = "transfer_poll_message_id_1")
|
||||
Long pollMessageId1;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_poll_message_id_2")
|
||||
Long pollMessageId2;
|
||||
|
||||
public abstract boolean isEmpty();
|
||||
|
||||
@@ -116,6 +139,83 @@ public abstract class TransferData<
|
||||
return newBuilder;
|
||||
}
|
||||
|
||||
void onLoad(
|
||||
@AlsoLoad("serverApproveEntities")
|
||||
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
|
||||
mapServerApproveEntitiesToFields(serverApproveEntities, this);
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
mapFieldsToServerApproveEntities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs serverApproveEntities set from the individual fields, e.g. repoId, historyEntryId,
|
||||
* pollMessageId1.
|
||||
*/
|
||||
void mapFieldsToServerApproveEntities() {
|
||||
if (repoId == null) {
|
||||
return;
|
||||
}
|
||||
Key<? extends EppResource> eppKey;
|
||||
if (getClass().equals(DomainBase.class)) {
|
||||
eppKey = Key.create(DomainBase.class, repoId);
|
||||
} else {
|
||||
eppKey = Key.create(ContactResource.class, repoId);
|
||||
}
|
||||
Key<HistoryEntry> historyEntryKey = Key.create(eppKey, HistoryEntry.class, historyEntryId);
|
||||
ImmutableSet.Builder<VKey<? extends TransferServerApproveEntity>> entityKeysBuilder =
|
||||
new ImmutableSet.Builder<>();
|
||||
if (pollMessageId1 != null) {
|
||||
Key<PollMessage> ofyKey = Key.create(historyEntryKey, PollMessage.class, pollMessageId1);
|
||||
entityKeysBuilder.add(PollMessage.createVKey(ofyKey));
|
||||
}
|
||||
if (pollMessageId2 != null) {
|
||||
Key<PollMessage> ofyKey = Key.create(historyEntryKey, PollMessage.class, pollMessageId2);
|
||||
entityKeysBuilder.add(PollMessage.createVKey(ofyKey));
|
||||
}
|
||||
serverApproveEntities = entityKeysBuilder.build();
|
||||
}
|
||||
|
||||
/** Maps serverApproveEntities set to the individual fields. */
|
||||
static void mapServerApproveEntitiesToFields(
|
||||
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities,
|
||||
TransferData transferData) {
|
||||
if (isNullOrEmpty(serverApproveEntities)) {
|
||||
transferData.historyEntryId = null;
|
||||
transferData.repoId = null;
|
||||
transferData.pollMessageId1 = null;
|
||||
transferData.pollMessageId2 = null;
|
||||
return;
|
||||
}
|
||||
// Each element in serverApproveEntities should have the exact same Key<HistoryEntry> as its
|
||||
// parent. So, we can use any to set historyEntryId and repoId.
|
||||
Key<?> key = serverApproveEntities.iterator().next().getOfyKey();
|
||||
transferData.historyEntryId = key.getParent().getId();
|
||||
transferData.repoId = key.getParent().getParent().getName();
|
||||
|
||||
ImmutableList<Long> sortedPollMessageIds = getSortedPollMessageIds(serverApproveEntities);
|
||||
if (sortedPollMessageIds.size() >= 1) {
|
||||
transferData.pollMessageId1 = sortedPollMessageIds.get(0);
|
||||
}
|
||||
if (sortedPollMessageIds.size() >= 2) {
|
||||
transferData.pollMessageId2 = sortedPollMessageIds.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets poll message IDs from the given serverApproveEntities and sorted the IDs in natural order.
|
||||
*/
|
||||
private static ImmutableList<Long> getSortedPollMessageIds(
|
||||
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities) {
|
||||
return nullToEmpty(serverApproveEntities).stream()
|
||||
.filter(vKey -> PollMessage.class.isAssignableFrom(vKey.getKind()))
|
||||
.map(vKey -> (long) vKey.getSqlKey())
|
||||
.sorted()
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
/** Builder for {@link TransferData} because it is immutable. */
|
||||
public abstract static class Builder<T extends TransferData, B extends Builder<T, B>>
|
||||
extends BaseTransferObject.Builder<T, B> {
|
||||
@@ -141,6 +241,7 @@ public abstract class TransferData<
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
mapServerApproveEntitiesToFields(getInstance().serverApproveEntities, getInstance());
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
|
||||
+25
-9
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.ofy.DatastoreTransactionManager.toChildHistoryEntryIfPossible;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static java.util.AbstractMap.SimpleEntry;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
@@ -34,6 +35,7 @@ import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
|
||||
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||
import google.registry.persistence.JpaRetries;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
@@ -116,8 +118,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
@Override
|
||||
public <T> T transact(Supplier<T> work) {
|
||||
// TODO(shicong): Investigate removing transactNew functionality after migration as it may
|
||||
// be same as this one.
|
||||
return retrier.callWithRetry(
|
||||
() -> {
|
||||
if (inTransaction()) {
|
||||
@@ -197,6 +197,8 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(b/177674699): Remove all transactNew methods as they are same as transact after the
|
||||
// database migration.
|
||||
@Override
|
||||
public <T> T transactNew(Supplier<T> work) {
|
||||
return transact(work);
|
||||
@@ -250,8 +252,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return;
|
||||
}
|
||||
assertInTransaction();
|
||||
getEntityManager().persist(entity);
|
||||
transactionInfo.get().addUpdate(entity);
|
||||
// Necessary due to the changes in HistoryEntry representation during the migration to SQL
|
||||
Object toPersist = toChildHistoryEntryIfPossible(entity);
|
||||
getEntityManager().persist(toPersist);
|
||||
transactionInfo.get().addUpdate(toPersist);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -278,8 +282,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return;
|
||||
}
|
||||
assertInTransaction();
|
||||
getEntityManager().merge(entity);
|
||||
transactionInfo.get().addUpdate(entity);
|
||||
// Necessary due to the changes in HistoryEntry representation during the migration to SQL
|
||||
Object toPersist = toChildHistoryEntryIfPossible(entity);
|
||||
getEntityManager().merge(toPersist);
|
||||
transactionInfo.get().addUpdate(toPersist);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,8 +313,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
assertInTransaction();
|
||||
checkArgument(exists(entity), "Given entity does not exist");
|
||||
getEntityManager().merge(entity);
|
||||
transactionInfo.get().addUpdate(entity);
|
||||
// Necessary due to the changes in HistoryEntry representation during the migration to SQL
|
||||
Object toPersist = toChildHistoryEntryIfPossible(entity);
|
||||
getEntityManager().merge(toPersist);
|
||||
transactionInfo.get().addUpdate(toPersist);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -339,6 +347,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public boolean exists(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
entity = toChildHistoryEntryIfPossible(entity);
|
||||
EntityType<?> entityType = getEntityType(entity.getClass());
|
||||
ImmutableSet<EntityId> entityIds = getEntityIdsFromEntity(entityType, entity);
|
||||
return exists(entityType.getName(), entityIds);
|
||||
@@ -382,6 +391,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadByEntitiesIfPresent(Iterable<T> entities) {
|
||||
return Streams.stream(entities)
|
||||
.map(DatastoreTransactionManager::toChildHistoryEntryIfPossible)
|
||||
.filter(this::exists)
|
||||
.map(this::loadByEntity)
|
||||
.collect(toImmutableList());
|
||||
@@ -416,9 +426,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
public <T> T loadByEntity(T entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
assertInTransaction();
|
||||
entity = toChildHistoryEntryIfPossible(entity);
|
||||
// If the caller requested a HistoryEntry, load the corresponding *History class
|
||||
T possibleChild = toChildHistoryEntryIfPossible(entity);
|
||||
return (T)
|
||||
loadByKey(
|
||||
VKey.createSql(entity.getClass(), emf.getPersistenceUnitUtil().getIdentifier(entity)));
|
||||
VKey.createSql(
|
||||
possibleChild.getClass(),
|
||||
emf.getPersistenceUnitUtil().getIdentifier(possibleChild)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -469,6 +484,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return;
|
||||
}
|
||||
assertInTransaction();
|
||||
entity = toChildHistoryEntryIfPossible(entity);
|
||||
Object managedEntity = entity;
|
||||
if (!getEntityManager().contains(entity)) {
|
||||
managedEntity = getEntityManager().merge(entity);
|
||||
|
||||
@@ -52,6 +52,7 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.rdap.RdapDataStructures.Event;
|
||||
import google.registry.rdap.RdapDataStructures.EventAction;
|
||||
@@ -880,8 +881,9 @@ public class RdapJsonFormatter {
|
||||
// 2.3.2.3 An event of *eventAction* type *transfer*, with the last date and time that the
|
||||
// domain was transferred. The event of *eventAction* type *transfer* MUST be omitted if the
|
||||
// domain name has not been transferred since it was created.
|
||||
for (HistoryEntry historyEntry :
|
||||
ofy().load().type(HistoryEntry.class).ancestor(resource).order("modificationTime")) {
|
||||
Iterable<? extends HistoryEntry> historyEntries =
|
||||
HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey());
|
||||
for (HistoryEntry historyEntry : historyEntries) {
|
||||
EventAction rdapEventAction =
|
||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
|
||||
// Only save the historyEntries if this is a type we care about.
|
||||
@@ -930,13 +932,9 @@ public class RdapJsonFormatter {
|
||||
return eventsBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RDAP event object as defined by RFC 7483.
|
||||
*/
|
||||
/** Creates an RDAP event object as defined by RFC 7483. */
|
||||
private static Event makeEvent(
|
||||
EventAction eventAction,
|
||||
@Nullable String eventActor,
|
||||
DateTime eventDate) {
|
||||
EventAction eventAction, @Nullable String eventActor, DateTime eventDate) {
|
||||
Event.Builder builder = Event.builder()
|
||||
.setEventAction(eventAction)
|
||||
.setEventDate(eventDate);
|
||||
|
||||
@@ -76,7 +76,7 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||
// since the query ran. This also filters out per-domain tokens if they're not to be deleted.
|
||||
ImmutableSet<VKey<AllocationToken>> tokensToDelete =
|
||||
tm().loadByKeys(batch).values().stream()
|
||||
.filter(t -> withDomains || t.getDomainName().isEmpty())
|
||||
.filter(t -> withDomains || !t.getDomainName().isPresent())
|
||||
.filter(t -> SINGLE_USE.equals(t.getTokenType()))
|
||||
.filter(t -> !t.isRedeemed())
|
||||
.map(AllocationToken::createVKey)
|
||||
|
||||
@@ -59,7 +59,7 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
|
||||
if (loadedTokens.containsKey(token)) {
|
||||
AllocationToken loadedToken = loadedTokens.get(token);
|
||||
System.out.println(loadedToken.toString());
|
||||
if (loadedToken.getRedemptionHistoryEntry().isEmpty()) {
|
||||
if (!loadedToken.getRedemptionHistoryEntry().isPresent()) {
|
||||
System.out.printf("Token %s was not redeemed.\n", token);
|
||||
} else {
|
||||
Key<DomainBase> domainOfyKey =
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
@@ -25,14 +24,16 @@ import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tools.CommandUtilities.ResourceType;
|
||||
import google.registry.xml.XmlTransformer;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Command to show history entries. */
|
||||
@Parameters(separators = " =",
|
||||
commandDescription = "Show history entries that occurred in a given time range")
|
||||
@Parameters(
|
||||
separators = " =",
|
||||
commandDescription = "Show history entries that occurred in a given time range")
|
||||
final class GetHistoryEntriesCommand implements CommandWithRemoteApi {
|
||||
|
||||
@Parameter(
|
||||
@@ -45,33 +46,26 @@ final class GetHistoryEntriesCommand implements CommandWithRemoteApi {
|
||||
description = "Only show history entries that occurred at or before this time")
|
||||
private DateTime before = END_OF_TIME;
|
||||
|
||||
@Parameter(
|
||||
names = "--type",
|
||||
description = "Resource type.")
|
||||
@Parameter(names = "--type", description = "Resource type.")
|
||||
private ResourceType type;
|
||||
|
||||
@Parameter(
|
||||
names = "--id",
|
||||
description = "Foreign key of the resource.")
|
||||
@Parameter(names = "--id", description = "Foreign key of the resource.")
|
||||
private String uniqueId;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
VKey<? extends EppResource> parentKey = null;
|
||||
Iterable<? extends HistoryEntry> historyEntries;
|
||||
if (type != null || uniqueId != null) {
|
||||
checkArgument(
|
||||
type != null && uniqueId != null,
|
||||
"If either of 'type' or 'id' are set then both must be");
|
||||
parentKey = type.getKey(uniqueId, DateTime.now(UTC));
|
||||
VKey<? extends EppResource> parentKey = type.getKey(uniqueId, DateTime.now(UTC));
|
||||
checkArgumentNotNull(parentKey, "Invalid resource ID");
|
||||
historyEntries = HistoryEntryDao.loadHistoryObjectsForResource(parentKey, after, before);
|
||||
} else {
|
||||
historyEntries = HistoryEntryDao.loadAllHistoryObjects(after, before);
|
||||
}
|
||||
for (HistoryEntry entry :
|
||||
(parentKey == null
|
||||
? ofy().load().type(HistoryEntry.class)
|
||||
: ofy().load().type(HistoryEntry.class).ancestor(parentKey.getOfyKey()))
|
||||
.order("modificationTime")
|
||||
.filter("modificationTime >=", after)
|
||||
.filter("modificationTime <=", before)) {
|
||||
for (HistoryEntry entry : historyEntries) {
|
||||
System.out.printf(
|
||||
"Client: %s\nTime: %s\nClient TRID: %s\nServer TRID: %s\n%s\n",
|
||||
entry.getClientId(),
|
||||
|
||||
@@ -168,6 +168,8 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(ValidateEscrowDepositCommand command);
|
||||
|
||||
void inject(ValidateLoginCredentialsCommand command);
|
||||
|
||||
void inject(WhoisQueryCommand command);
|
||||
|
||||
AppEngineConnection appEngineConnection();
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
@@ -139,6 +140,11 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
+ " deleted at the end of its current registration period.")
|
||||
Boolean autorenews;
|
||||
|
||||
@Parameter(
|
||||
names = {"--force_in_pending_delete"},
|
||||
description = "Force a superuser update even on domains that are in pending delete")
|
||||
boolean forceInPendingDelete;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
if (!nameservers.isEmpty()) {
|
||||
@@ -186,6 +192,12 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
|
||||
+ "to make updates, and if so, use the domain_unlock command to enable updates.",
|
||||
domain);
|
||||
checkArgument(
|
||||
!domainBase.getStatusValues().contains(PENDING_DELETE) || forceInPendingDelete,
|
||||
"The domain '%s' has status PENDING_DELETE. Verify that you really are intending to "
|
||||
+ "update a domain in pending delete (this is uncommon), and if so, pass the "
|
||||
+ "--force_in_pending_delete parameter to allow this update.",
|
||||
domain);
|
||||
|
||||
// Use TreeSets so that the results are always in the same order (this makes testing easier).
|
||||
Set<String> addAdminsThisDomain = new TreeSet<>(addAdmins);
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static google.registry.util.X509Utils.loadCertificate;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
@@ -25,12 +26,14 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.flows.TlsCredentials;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** A command to test registrar login credentials. */
|
||||
@Parameters(separators = " =", commandDescription = "Test registrar login credentials")
|
||||
@@ -55,6 +58,7 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
|
||||
validateWith = PathParameter.InputFile.class)
|
||||
private Path clientCertificatePath;
|
||||
|
||||
// TODO(sarahbot@): Remove this after hash fallback is removed
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"-h", "--cert_hash"},
|
||||
@@ -67,20 +71,28 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
|
||||
description = "Client ip address to pretend to use")
|
||||
private String clientIpAddress = "10.0.0.1";
|
||||
|
||||
@Inject CertificateChecker certificateChecker;
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
checkArgument(
|
||||
clientCertificatePath == null || isNullOrEmpty(clientCertificateHash),
|
||||
"Can't specify both --cert_hash and --cert_file");
|
||||
String encodedCertificate = "";
|
||||
if (clientCertificatePath != null) {
|
||||
clientCertificateHash = getCertificateHash(
|
||||
loadCertificate(new String(Files.readAllBytes(clientCertificatePath), US_ASCII)));
|
||||
String certificateString = new String(Files.readAllBytes(clientCertificatePath), US_ASCII);
|
||||
encodedCertificate = encodeX509CertificateFromPemString(certificateString);
|
||||
clientCertificateHash = getCertificateHash(loadCertificate(clientCertificatePath));
|
||||
}
|
||||
Registrar registrar =
|
||||
checkArgumentPresent(
|
||||
Registrar.loadByClientId(clientId), "Registrar %s not found", clientId);
|
||||
new TlsCredentials(
|
||||
true, Optional.ofNullable(clientCertificateHash), Optional.ofNullable(clientIpAddress))
|
||||
true,
|
||||
Optional.ofNullable(clientCertificateHash),
|
||||
Optional.ofNullable(encodedCertificate),
|
||||
Optional.ofNullable(clientIpAddress),
|
||||
certificateChecker)
|
||||
.validate(registrar, password);
|
||||
checkState(
|
||||
registrar.isLive(), "Registrar %s has non-live state: %s", clientId, registrar.getState());
|
||||
|
||||
+16
-16
@@ -16,20 +16,24 @@ package google.registry.tools.javascrap;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.registry.RegistryLockDao;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import google.registry.tools.CommandWithRemoteApi;
|
||||
import google.registry.tools.ConfirmingCommand;
|
||||
@@ -130,7 +134,7 @@ public class BackfillRegistryLocksCommand extends ConfirmingCommand
|
||||
private DateTime getLockCompletionTimestamp(DomainBase domainBase, DateTime now) {
|
||||
// Best-effort, if a domain was URS-locked we should use that time
|
||||
// If we can't find that, return now.
|
||||
return ofy().load().type(HistoryEntry.class).ancestor(domainBase).list().stream()
|
||||
return Streams.stream(HistoryEntryDao.loadHistoryObjectsForResource(domainBase.createVKey()))
|
||||
// sort by modification time descending so we get the most recent one if it was locked twice
|
||||
.sorted(Comparator.comparing(HistoryEntry::getModificationTime).reversed())
|
||||
.filter(entry -> entry.getReason().equals("Uniform Rapid Suspension"))
|
||||
@@ -140,18 +144,14 @@ public class BackfillRegistryLocksCommand extends ConfirmingCommand
|
||||
}
|
||||
|
||||
private ImmutableList<DomainBase> getLockedDomainsWithoutLocks(DateTime now) {
|
||||
return ImmutableList.copyOf(
|
||||
ofy()
|
||||
.load()
|
||||
.keys(
|
||||
roids.stream()
|
||||
.map(roid -> Key.create(DomainBase.class, roid))
|
||||
.collect(toImmutableList()))
|
||||
.values()
|
||||
.stream()
|
||||
.filter(d -> d.getDeletionTime().isAfter(now))
|
||||
.filter(d -> d.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES))
|
||||
.filter(d -> !RegistryLockDao.getMostRecentByRepoId(d.getRepoId()).isPresent())
|
||||
.collect(toImmutableList()));
|
||||
ImmutableList<VKey<DomainBase>> domainKeys =
|
||||
roids.stream().map(roid -> VKey.create(DomainBase.class, roid)).collect(toImmutableList());
|
||||
ImmutableCollection<DomainBase> domains =
|
||||
transactIfJpaTm(() -> tm().loadByKeys(domainKeys)).values();
|
||||
return domains.stream()
|
||||
.filter(d -> d.getDeletionTime().isAfter(now))
|
||||
.filter(d -> d.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES))
|
||||
.filter(d -> RegistryLockDao.getMostRecentByRepoId(d.getRepoId()).isEmpty())
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.google.common.collect.Streams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.model.registrar.RegistrarContact.Type;
|
||||
@@ -337,7 +338,11 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
|
||||
private boolean validateCertificate(
|
||||
Optional<String> existingCertificate, String certificateString) {
|
||||
if (!existingCertificate.isPresent() || !existingCertificate.get().equals(certificateString)) {
|
||||
try {
|
||||
certificateChecker.validateCertificate(certificateString);
|
||||
} catch (InsecureCertificateException e) {
|
||||
throw new IllegalArgumentException(e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -16,16 +16,36 @@ package google.registry.flows;
|
||||
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.X509Utils.encodeX509Certificate;
|
||||
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.CertificateSamples;
|
||||
import google.registry.testing.SystemPropertyExtension;
|
||||
import google.registry.util.SelfSignedCaCertificate;
|
||||
import java.io.StringWriter;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
|
||||
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemObjectGenerator;
|
||||
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;
|
||||
|
||||
/** Test logging in with TLS credentials. */
|
||||
class EppLoginTlsTest extends EppTestCase {
|
||||
@@ -34,18 +54,40 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
|
||||
|
||||
void setClientCertificateHash(String clientCertificateHash) {
|
||||
@RegisterExtension
|
||||
@Order(value = Integer.MAX_VALUE)
|
||||
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
|
||||
|
||||
private final CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
private final Logger loggerToIntercept =
|
||||
Logger.getLogger(TlsCredentials.class.getCanonicalName());
|
||||
private final TestLogHandler handler = new TestLogHandler();
|
||||
|
||||
private String encodedCertString;
|
||||
|
||||
void setCredentials(String clientCertificateHash, String clientCertificate) {
|
||||
setTransportCredentials(
|
||||
new TlsCredentials(
|
||||
true, Optional.ofNullable(clientCertificateHash), Optional.of("192.168.1.100:54321")));
|
||||
true,
|
||||
Optional.ofNullable(clientCertificateHash),
|
||||
Optional.ofNullable(clientCertificate),
|
||||
Optional.of("192.168.1.100:54321"),
|
||||
certificateChecker));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
void beforeEach() throws CertificateException {
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, DateTime.now(UTC))
|
||||
.build());
|
||||
// Set a cert for the second registrar, or else any cert will be allowed for login.
|
||||
persistResource(
|
||||
@@ -53,18 +95,20 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT2, DateTime.now(UTC))
|
||||
.build());
|
||||
loggerToIntercept.addHandler(handler);
|
||||
encodedCertString = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoginLogout() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
setCredentials(null, encodedCertString);
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogin_wrongPasswordFails() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||
// For TLS login, we also check the epp xml password.
|
||||
assertThatLogin("NewRegistrar", "incorrect")
|
||||
.hasResponse(
|
||||
@@ -74,7 +118,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testMultiLogin() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertThatLogoutSucceeds();
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
@@ -88,7 +132,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testNonAuthedLogin_fails() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||
assertThatLogin("TheRegistrar", "password2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
@@ -98,7 +142,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testBadCertificate_failsBadCertificate2200() throws Exception {
|
||||
setClientCertificateHash("laffo");
|
||||
setCredentials("laffo", "cert");
|
||||
assertThatLogin("NewRegistrar", "foo-BAR2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
@@ -108,7 +152,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testGfeDidntProvideClientCertificate_failsMissingCertificate2200() throws Exception {
|
||||
setClientCertificateHash(null);
|
||||
setCredentials(null, null);
|
||||
assertThatLogin("NewRegistrar", "foo-BAR2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
@@ -117,12 +161,12 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testGoodPrimaryCertificate() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
setCredentials(null, encodedCertString);
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, now)
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||
.build());
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
@@ -130,33 +174,33 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testGoodFailoverCertificate() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT2_HASH);
|
||||
setCredentials(null, encodedCertString);
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
|
||||
.build());
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingPrimaryCertificateButHasFailover_usesFailover() throws Exception {
|
||||
setClientCertificateHash(CertificateSamples.SAMPLE_CERT2_HASH);
|
||||
setCredentials(null, encodedCertString);
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(null, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
|
||||
.build());
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarHasNoCertificatesOnFile_fails() throws Exception {
|
||||
setClientCertificateHash("laffo");
|
||||
setCredentials("laffo", "cert");
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
@@ -169,4 +213,145 @@ class EppLoginTlsTest extends EppTestCase {
|
||||
"response_error.xml",
|
||||
ImmutableMap.of("CODE", "2200", "MSG", "Registrar certificate is not configured"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCertificateDoesNotMeetRequirements_fails() throws Exception {
|
||||
String proxyEncoded = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT);
|
||||
// SAMPLE_CERT has a validity period that is too long
|
||||
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, proxyEncoded);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
|
||||
.build());
|
||||
assertThatLogin("NewRegistrar", "foo-BAR2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
ImmutableMap.of(
|
||||
"CODE",
|
||||
"2200",
|
||||
"MSG",
|
||||
"Registrar certificate contains the following security violations:\n"
|
||||
+ "Certificate validity period is too long; it must be less than or equal to"
|
||||
+ " 398 days."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCertificateDoesNotMeetMultipleRequirements_fails() throws Exception {
|
||||
X509Certificate certificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"test", clock.nowUtc().plusDays(100), clock.nowUtc().plusDays(5000))
|
||||
.cert();
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
try (PemWriter pw = new PemWriter(sw)) {
|
||||
PemObjectGenerator generator = new JcaMiscPEMGenerator(certificate);
|
||||
pw.writeObject(generator);
|
||||
}
|
||||
|
||||
String proxyEncoded = encodeX509Certificate(certificate);
|
||||
|
||||
// SAMPLE_CERT has a validity period that is too long
|
||||
setCredentials(null, proxyEncoded);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(sw.toString(), clock.nowUtc())
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
|
||||
.build());
|
||||
assertThatLogin("NewRegistrar", "foo-BAR2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
ImmutableMap.of(
|
||||
"CODE",
|
||||
"2200",
|
||||
"MSG",
|
||||
"Registrar certificate contains the following security violations:\n"
|
||||
+ "Certificate is expired.\n"
|
||||
+ "Certificate validity period is too long; it must be less than or equal to"
|
||||
+ " 398 days."));
|
||||
}
|
||||
|
||||
@Test
|
||||
// TODO(sarahbot@): Remove this test once requirements are enforced in production
|
||||
void testCertificateDoesNotMeetRequirementsInProduction_succeeds() throws Exception {
|
||||
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
|
||||
// SAMPLE_CERT has a validity period that is too long
|
||||
String proxyEncoded = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT);
|
||||
setCredentials(null, proxyEncoded);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
|
||||
.build());
|
||||
// Even though the certificate contains security violations, the login will succeed in
|
||||
// production
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertAboutLogs()
|
||||
.that(handler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.WARNING,
|
||||
"Registrar certificate used for NewRegistrar does not meet certificate requirements:"
|
||||
+ " Certificate validity period is too long; it must be less than or equal to 398"
|
||||
+ " days.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarCertificateContainsExtraMetadata_succeeds() throws Exception {
|
||||
String certPem =
|
||||
String.format(
|
||||
"Bag Attributes\n"
|
||||
+ " localKeyID: 1F 1C 3A 3A 4C 03 EC C4 BC 7A C3 21 A9 F2 13 66 21 B8 7B 26 \n"
|
||||
+ "subject=/C=US/ST=New York/L=New"
|
||||
+ " York/O=Test/OU=ABC/CN=tester.test/emailAddress=test-certificate@test.test\n"
|
||||
+ "issuer=/C=US/ST=NY/L=NYC/O=ABC/OU=TEST CA/CN=TEST"
|
||||
+ " CA/emailAddress=testing@test.test\n"
|
||||
+ "%s",
|
||||
CertificateSamples.SAMPLE_CERT3);
|
||||
|
||||
setCredentials(null, encodeX509CertificateFromPemString(certPem));
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(certPem, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||
.build());
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarCertificateContainsExtraMetadataAndViolations_fails() throws Exception {
|
||||
String certPem =
|
||||
String.format(
|
||||
"Bag Attributes\n"
|
||||
+ " localKeyID: 1F 1C 3A 3A 4C 03 EC C4 BC 7A C3 21 A9 F2 13 66 21 B8 7B 26 \n"
|
||||
+ "subject=/C=US/ST=New York/L=New"
|
||||
+ " York/O=Test/OU=ABC/CN=tester.test/emailAddress=test-certificate@test.test\n"
|
||||
+ "issuer=/C=US/ST=NY/L=NYC/O=ABC/OU=TEST CA/CN=TEST"
|
||||
+ " CA/emailAddress=testing@test.test\n"
|
||||
+ "%s",
|
||||
CertificateSamples.SAMPLE_CERT);
|
||||
|
||||
setCredentials(null, encodeX509CertificateFromPemString(certPem));
|
||||
DateTime now = DateTime.now(UTC);
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(certPem, now)
|
||||
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||
.build());
|
||||
assertThatLogin("NewRegistrar", "foo-BAR2")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
ImmutableMap.of(
|
||||
"CODE",
|
||||
"2200",
|
||||
"MSG",
|
||||
"Registrar certificate contains the following security violations:\n"
|
||||
+ "Certificate validity period is too long; it must be less than or equal to"
|
||||
+ " 398 days."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.testing.TestDataHelper.loadFile;
|
||||
import static google.registry.testing.TestLogHandlerUtils.findFirstLogMessageByPrefix;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -27,8 +28,10 @@ import static org.mockito.Mockito.verify;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.flogger.LoggerConfig;
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppOutput.ResponseOrGreeting;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
@@ -38,6 +41,7 @@ import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeHttpSession;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -54,6 +58,16 @@ class FlowRunnerTest {
|
||||
|
||||
private final TestLogHandler handler = new TestLogHandler();
|
||||
|
||||
protected final FakeClock clock = new FakeClock();
|
||||
|
||||
private final CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
static class TestCommandFlow implements Flow {
|
||||
@Override
|
||||
public ResponseOrGreeting run() {
|
||||
@@ -138,10 +152,17 @@ class FlowRunnerTest {
|
||||
@Test
|
||||
void testRun_loggingStatement_tlsCredentials() throws Exception {
|
||||
flowRunner.credentials =
|
||||
new TlsCredentials(true, Optional.of("abc123def"), Optional.of("127.0.0.1"));
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.of("abc123def"),
|
||||
Optional.of("cert046F5A3"),
|
||||
Optional.of("127.0.0.1"),
|
||||
certificateChecker);
|
||||
flowRunner.run(eppMetricBuilder);
|
||||
assertThat(Splitter.on("\n\t").split(findFirstLogMessageByPrefix(handler, "EPP Command\n\t")))
|
||||
.contains("TlsCredentials{clientCertificateHash=abc123def, clientAddress=/127.0.0.1}");
|
||||
.contains(
|
||||
"TlsCredentials{clientCertificate=cert046F5A3, clientCertificateHash=abc123def,"
|
||||
+ " clientAddress=/127.0.0.1}");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,16 +18,20 @@ import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
|
||||
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
|
||||
import google.registry.flows.TlsCredentials.RegistrarCertificateNotConfiguredException;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -42,6 +46,16 @@ final class TlsCredentialsTest {
|
||||
final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
|
||||
|
||||
protected final FakeClock clock = new FakeClock();
|
||||
|
||||
private final CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
|
||||
@Test
|
||||
void testProvideClientCertificateHash() {
|
||||
HttpServletRequest req = mock(HttpServletRequest.class);
|
||||
@@ -50,12 +64,18 @@ final class TlsCredentialsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClientCertificateHash_missing() {
|
||||
TlsCredentials tls = new TlsCredentials(true, Optional.empty(), Optional.of("192.168.1.1"));
|
||||
void testClientCertificateAndHash_missing() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of("192.168.1.1"),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(SAMPLE_CERT, DateTime.now(UTC))
|
||||
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
|
||||
.build());
|
||||
assertThrows(
|
||||
MissingRegistrarCertificateException.class,
|
||||
@@ -64,11 +84,13 @@ final class TlsCredentialsTest {
|
||||
|
||||
@Test
|
||||
void test_missingIpAddress_doesntAllowAccess() {
|
||||
TlsCredentials tls = new TlsCredentials(false, Optional.of("certHash"), Optional.empty());
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
false, Optional.of("certHash"), Optional.empty(), Optional.empty(), certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(SAMPLE_CERT, DateTime.now(UTC))
|
||||
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
|
||||
.setIpAddressAllowList(ImmutableSet.of(CidrAddressBlock.create("3.5.8.13")))
|
||||
.build());
|
||||
assertThrows(
|
||||
@@ -79,12 +101,59 @@ final class TlsCredentialsTest {
|
||||
@Test
|
||||
void test_validateCertificate_canBeConfiguredToBypassCertHashes() throws Exception {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(false, Optional.of("certHash"), Optional.of("192.168.1.1"));
|
||||
new TlsCredentials(
|
||||
false,
|
||||
Optional.of("certHash"),
|
||||
Optional.of("cert"),
|
||||
Optional.of("192.168.1.1"),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(null, DateTime.now(UTC))
|
||||
.setFailoverClientCertificate(null, DateTime.now(UTC))
|
||||
.setClientCertificate(null, clock.nowUtc())
|
||||
.setFailoverClientCertificate(null, clock.nowUtc())
|
||||
.build());
|
||||
// This would throw a RegistrarCertificateNotConfiguredException if cert hashes were not
|
||||
// bypassed
|
||||
tls.validateCertificate(Registrar.loadByClientId("TheRegistrar").get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProvideClientCertificate() {
|
||||
HttpServletRequest req = mock(HttpServletRequest.class);
|
||||
when(req.getHeader("X-SSL-Full-Certificate")).thenReturn("data");
|
||||
assertThat(TlsCredentials.EppTlsModule.provideClientCertificate(req)).hasValue("data");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClientCertificate_notConfigured() {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
true,
|
||||
Optional.of("hash"),
|
||||
Optional.of(SAMPLE_CERT),
|
||||
Optional.of("192.168.1.1"),
|
||||
certificateChecker);
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().build());
|
||||
assertThrows(
|
||||
RegistrarCertificateNotConfiguredException.class,
|
||||
() -> tls.validateCertificate(Registrar.loadByClientId("TheRegistrar").get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_validateCertificate_canBeConfiguredToBypassCerts() throws Exception {
|
||||
TlsCredentials tls =
|
||||
new TlsCredentials(
|
||||
false,
|
||||
Optional.of("certHash"),
|
||||
Optional.of("cert"),
|
||||
Optional.of("192.168.1.1"),
|
||||
certificateChecker);
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setClientCertificate(null, clock.nowUtc())
|
||||
.setFailoverClientCertificate(null, clock.nowUtc())
|
||||
.build());
|
||||
// This would throw a RegistrarCertificateNotConfiguredException if cert hashes wren't bypassed.
|
||||
tls.validateCertificate(Registrar.loadByClientId("TheRegistrar").get());
|
||||
|
||||
+19
-18
@@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -41,10 +40,12 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferApproveFlow}. */
|
||||
@DualDatabaseTest
|
||||
class ContactTransferApproveFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferApproveFlow, ContactResource> {
|
||||
|
||||
@@ -121,24 +122,24 @@ class ContactTransferApproveFlowTest
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_approve.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_approve_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_approve.xml", "contact_transfer_approve_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_approve_with_authinfo.xml",
|
||||
"contact_transfer_approve_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact = persistResource(
|
||||
@@ -152,7 +153,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
@@ -161,7 +162,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -170,7 +171,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
@@ -179,7 +180,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -188,7 +189,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -197,7 +198,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -206,7 +207,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_gainingClient() {
|
||||
setClientIdForFlow("NewRegistrar");
|
||||
EppException thrown =
|
||||
@@ -215,7 +216,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_unrelatedClient() {
|
||||
setClientIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
@@ -224,7 +225,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact = persistResource(
|
||||
contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
@@ -236,9 +237,9 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
contact = persistResource(
|
||||
contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
@@ -249,7 +250,7 @@ class ContactTransferApproveFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-approve");
|
||||
|
||||
+19
-18
@@ -18,7 +18,6 @@ import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -38,10 +37,12 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferCancelFlow}. */
|
||||
@DualDatabaseTest
|
||||
class ContactTransferCancelFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferCancelFlow, ContactResource> {
|
||||
|
||||
@@ -105,24 +106,24 @@ class ContactTransferCancelFlowTest
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_cancel.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_cancel_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_cancel.xml", "contact_transfer_cancel_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_cancel_with_authinfo.xml",
|
||||
"contact_transfer_cancel_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
@@ -138,7 +139,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
@@ -147,7 +148,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -156,7 +157,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
@@ -165,7 +166,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -174,7 +175,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -183,7 +184,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -192,7 +193,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_sponsoringClient() {
|
||||
setClientIdForFlow("TheRegistrar");
|
||||
EppException thrown =
|
||||
@@ -202,7 +203,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_unrelatedClient() {
|
||||
setClientIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
@@ -212,7 +213,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
@@ -224,9 +225,9 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
@@ -235,7 +236,7 @@ class ContactTransferCancelFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-cancel");
|
||||
|
||||
+21
-20
@@ -17,7 +17,6 @@ package google.registry.flows.contact;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -32,11 +31,13 @@ import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferQueryFlow}. */
|
||||
@DualDatabaseTest
|
||||
class ContactTransferQueryFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferQueryFlow, ContactResource> {
|
||||
|
||||
@@ -68,65 +69,65 @@ class ContactTransferQueryFlowTest
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_query.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_withContactRoid() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_query_with_roid.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_sponsoringClient() throws Exception {
|
||||
setClientIdForFlow("TheRegistrar");
|
||||
doSuccessfulTest("contact_transfer_query.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
setClientIdForFlow("ClientZ");
|
||||
doSuccessfulTest("contact_transfer_query_with_authinfo.xml",
|
||||
"contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_approved.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientRejected() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_rejected.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_serverApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_server_approved.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_serverCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_server_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_pendingDeleteContact() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
contact = persistResource(
|
||||
@@ -135,7 +136,7 @@ class ContactTransferQueryFlowTest
|
||||
"contact_transfer_query_response_server_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
@@ -151,7 +152,7 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badContactRoid() {
|
||||
// Set the contact to a different ROID, but don't persist it; this is just so the substitution
|
||||
// code above will write the wrong ROID into the file.
|
||||
@@ -163,7 +164,7 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
@@ -173,7 +174,7 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_unrelatedClient() {
|
||||
setClientIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
@@ -183,7 +184,7 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
@@ -194,9 +195,9 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class, () -> doFailingTest("contact_transfer_query.xml"));
|
||||
@@ -204,7 +205,7 @@ class ContactTransferQueryFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-query");
|
||||
|
||||
+19
-18
@@ -18,7 +18,6 @@ import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -40,10 +39,12 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferRejectFlow}. */
|
||||
@DualDatabaseTest
|
||||
class ContactTransferRejectFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferRejectFlow, ContactResource> {
|
||||
|
||||
@@ -120,24 +121,24 @@ class ContactTransferRejectFlowTest
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_reject.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_reject_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_reject.xml", "contact_transfer_reject_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_domainAuthInfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_reject_with_authinfo.xml",
|
||||
"contact_transfer_reject_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
@@ -153,7 +154,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
@@ -162,7 +163,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -171,7 +172,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
@@ -180,7 +181,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -189,7 +190,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
@@ -198,7 +199,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
@@ -207,7 +208,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_gainingClient() {
|
||||
setClientIdForFlow("NewRegistrar");
|
||||
EppException thrown =
|
||||
@@ -216,7 +217,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_unrelatedClient() {
|
||||
setClientIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
@@ -225,7 +226,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
@@ -237,9 +238,9 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
@@ -248,7 +249,7 @@ class ContactTransferRejectFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-reject");
|
||||
|
||||
+27
-29
@@ -20,7 +20,8 @@ import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessagesEqual;
|
||||
@@ -29,11 +30,11 @@ import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
@@ -50,12 +51,13 @@ import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferRequestFlow}. */
|
||||
@DualDatabaseTest
|
||||
class ContactTransferRequestFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferRequestFlow, ContactResource> {
|
||||
|
||||
@@ -102,7 +104,8 @@ class ContactTransferRequestFlowTest
|
||||
.setPendingTransferExpirationTime(afterTransfer)
|
||||
// Make the server-approve entities field a no-op comparison; it's easier to
|
||||
// do this comparison separately below.
|
||||
.setServerApproveEntities(contact.getTransferData().getServerApproveEntities())
|
||||
.setServerApproveEntities(
|
||||
forceEmptyToNull(contact.getTransferData().getServerApproveEntities()))
|
||||
.build());
|
||||
assertNoBillingEvents();
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc())).hasSize(1);
|
||||
@@ -126,13 +129,8 @@ class ContactTransferRequestFlowTest
|
||||
// poll messages, the approval notice ones for gaining and losing registrars.
|
||||
assertPollMessagesEqual(
|
||||
Iterables.filter(
|
||||
ofy()
|
||||
.load()
|
||||
// Use toArray() to coerce the type to something keys() will accept.
|
||||
.keys(
|
||||
contact.getTransferData().getServerApproveEntities().stream()
|
||||
.map(VKey::getOfyKey)
|
||||
.toArray(Key[]::new))
|
||||
transactIfJpaTm(
|
||||
() -> tm().loadByKeys(contact.getTransferData().getServerApproveEntities()))
|
||||
.values(),
|
||||
PollMessage.class),
|
||||
ImmutableList.of(gainingApproveMessage, losingApproveMessage));
|
||||
@@ -145,18 +143,18 @@ class ContactTransferRequestFlowTest
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_request.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_request_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_noAuthInfo() {
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
@@ -165,7 +163,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_badPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
@@ -181,37 +179,37 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientRejected() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_clientCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_serverApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_serverCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_pending() {
|
||||
contact =
|
||||
persistResource(
|
||||
@@ -232,7 +230,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_sponsoringClient() {
|
||||
setClientIdForFlow("TheRegistrar");
|
||||
EppException thrown =
|
||||
@@ -242,7 +240,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
@@ -254,7 +252,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
ResourceDoesNotExistException thrown =
|
||||
@@ -265,7 +263,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_clientTransferProhibited() {
|
||||
contact =
|
||||
persistResource(
|
||||
@@ -278,7 +276,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_serverTransferProhibited() {
|
||||
contact =
|
||||
persistResource(
|
||||
@@ -291,7 +289,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_pendingDelete() {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build());
|
||||
@@ -303,7 +301,7 @@ class ContactTransferRequestFlowTest
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-request");
|
||||
|
||||
@@ -525,8 +525,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
clock.advanceOneMilli();
|
||||
runFlow();
|
||||
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
|
||||
HistoryEntry historyEntry =
|
||||
ofy().load().type(HistoryEntry.class).ancestor(reloadResourceByForeignKey()).first().now();
|
||||
HistoryEntry historyEntry = getHistoryEntries(reloadResourceByForeignKey()).get(0);
|
||||
assertThat(ofy().load().entity(token).now().getRedemptionHistoryEntry())
|
||||
.hasValue(HistoryEntry.createVKey(Key.create(historyEntry)));
|
||||
}
|
||||
|
||||
@@ -919,7 +919,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
.addStatusValue(SERVER_UPDATE_PROHIBITED)
|
||||
.build());
|
||||
Exception e = assertThrows(ResourceStatusProhibitsOperationException.class, this::runFlow);
|
||||
assertThat(e).hasMessageThat().containsMatch("serverUpdateProhibited");
|
||||
assertThat(e).hasMessageThat().contains("serverUpdateProhibited");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -937,7 +937,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||
persistReferencedEntities();
|
||||
persistDomain();
|
||||
Exception e = assertThrows(StatusNotClientSettableException.class, this::runFlow);
|
||||
assertThat(e).hasMessageThat().containsMatch("serverUpdateProhibited");
|
||||
assertThat(e).hasMessageThat().contains("serverUpdateProhibited");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -174,9 +174,7 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
|
||||
void testFailure_noSuchMessage() throws Exception {
|
||||
assertTransactionalFlow(true);
|
||||
Exception e = assertThrows(MessageDoesNotExistException.class, this::runFlow);
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(String.format("(1-3-EXAMPLE-4-%d-2011)", MESSAGE_ID));
|
||||
assertThat(e).hasMessageThat().contains(String.format("(1-3-EXAMPLE-4-%d-2011)", MESSAGE_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -255,8 +253,6 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
|
||||
.build());
|
||||
assertTransactionalFlow(true);
|
||||
Exception e = assertThrows(MessageDoesNotExistException.class, this::runFlow);
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(String.format("(1-3-EXAMPLE-4-%d-2011)", MESSAGE_ID));
|
||||
assertThat(e).hasMessageThat().contains(String.format("(1-3-EXAMPLE-4-%d-2011)", MESSAGE_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,37 +15,66 @@
|
||||
package google.registry.flows.session;
|
||||
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.flows.TlsCredentials;
|
||||
import google.registry.flows.TlsCredentials.BadRegistrarCertificateException;
|
||||
import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
|
||||
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.testing.CertificateSamples;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.SelfSignedCaCertificate;
|
||||
import java.io.StringWriter;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
|
||||
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemObjectGenerator;
|
||||
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;
|
||||
|
||||
/** Unit tests for {@link LoginFlow} when accessed via a TLS transport. */
|
||||
public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
|
||||
private static final Optional<String> GOOD_CERT =
|
||||
Optional.of(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
private static final Optional<String> BAD_CERT =
|
||||
private static final Optional<String> GOOD_CERT = Optional.of(CertificateSamples.SAMPLE_CERT3);
|
||||
private static final Optional<String> GOOD_CERT_HASH =
|
||||
Optional.of(CertificateSamples.SAMPLE_CERT3_HASH);
|
||||
private static final Optional<String> BAD_CERT = Optional.of(CertificateSamples.SAMPLE_CERT2);
|
||||
private static final Optional<String> BAD_CERT_HASH =
|
||||
Optional.of(CertificateSamples.SAMPLE_CERT2_HASH);
|
||||
private static final Optional<String> GOOD_IP = Optional.of("192.168.1.1");
|
||||
private static final Optional<String> BAD_IP = Optional.of("1.1.1.1");
|
||||
private static final Optional<String> GOOD_IPV6 = Optional.of("2001:db8::1");
|
||||
private static final Optional<String> BAD_IPV6 = Optional.of("2001:db8::2");
|
||||
private final CertificateChecker certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
clock);
|
||||
private Optional<String> encodedCertString;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws CertificateException {
|
||||
encodedCertString = Optional.of(encodeX509CertificateFromPemString(GOOD_CERT.get()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Registrar.Builder getRegistrarBuilder() {
|
||||
return super.getRegistrarBuilder()
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
|
||||
.setClientCertificate(GOOD_CERT.get(), DateTime.now(UTC))
|
||||
.setIpAddressAllowList(
|
||||
ImmutableList.of(CidrAddressBlock.create(InetAddresses.forString(GOOD_IP.get()), 32)));
|
||||
}
|
||||
@@ -53,7 +82,36 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
@Test
|
||||
void testSuccess_withGoodCredentials() throws Exception {
|
||||
persistResource(getRegistrarBuilder().build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, GOOD_IP);
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IP, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withNewlyConstructedCertificate() throws Exception {
|
||||
X509Certificate certificate =
|
||||
SelfSignedCaCertificate.create(
|
||||
"test", clock.nowUtc().minusDays(100), clock.nowUtc().plusDays(150))
|
||||
.cert();
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
try (PemWriter pw = new PemWriter(sw)) {
|
||||
PemObjectGenerator generator = new JcaMiscPEMGenerator(certificate);
|
||||
pw.writeObject(generator);
|
||||
}
|
||||
|
||||
persistResource(
|
||||
super.getRegistrarBuilder()
|
||||
.setClientCertificate(sw.toString(), DateTime.now(UTC))
|
||||
.setIpAddressAllowList(
|
||||
ImmutableList.of(
|
||||
CidrAddressBlock.create(InetAddresses.forString(GOOD_IP.get()), 32)))
|
||||
.build());
|
||||
|
||||
String encodedCertificate = Base64.getEncoder().encodeToString(certificate.getEncoded());
|
||||
credentials =
|
||||
new TlsCredentials(
|
||||
true, Optional.empty(), Optional.of(encodedCertificate), GOOD_IP, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@@ -64,7 +122,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
.setIpAddressAllowList(
|
||||
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, GOOD_IPV6);
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IPV6, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@@ -75,7 +134,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
.setIpAddressAllowList(
|
||||
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, GOOD_IPV6);
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IPV6, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@@ -85,21 +145,35 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
getRegistrarBuilder()
|
||||
.setIpAddressAllowList(ImmutableList.of(CidrAddressBlock.create("192.168.1.255/24")))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, GOOD_IP);
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IP, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_incorrectClientCertificateHash() {
|
||||
void testFailure_incorrectClientCertificateHash() throws Exception {
|
||||
persistResource(getRegistrarBuilder().build());
|
||||
credentials = new TlsCredentials(true, BAD_CERT, GOOD_IP);
|
||||
String proxyEncoded = encodeX509CertificateFromPemString(BAD_CERT.get());
|
||||
credentials =
|
||||
new TlsCredentials(
|
||||
true, BAD_CERT_HASH, Optional.of(proxyEncoded), GOOD_IP, certificateChecker);
|
||||
doFailingTest("login_valid.xml", BadRegistrarCertificateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_missingClientCertificateHash() {
|
||||
// TODO(Sarahbot): This should fail once hash fallback is removed
|
||||
void testSuccess_missingClientCertificate() throws Exception {
|
||||
persistResource(getRegistrarBuilder().build());
|
||||
credentials = new TlsCredentials(true, Optional.empty(), GOOD_IP);
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, Optional.empty(), GOOD_IP, certificateChecker);
|
||||
doSuccessfulTest("login_valid.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_missingClientCertificateAndHash() {
|
||||
persistResource(getRegistrarBuilder().build());
|
||||
credentials =
|
||||
new TlsCredentials(true, Optional.empty(), Optional.empty(), GOOD_IP, certificateChecker);
|
||||
doFailingTest("login_valid.xml", MissingRegistrarCertificateException.class);
|
||||
}
|
||||
|
||||
@@ -112,7 +186,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
|
||||
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, Optional.empty());
|
||||
credentials =
|
||||
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, Optional.empty(), certificateChecker);
|
||||
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
|
||||
}
|
||||
|
||||
@@ -125,7 +200,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
|
||||
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, BAD_IP);
|
||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IP, certificateChecker);
|
||||
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
|
||||
}
|
||||
|
||||
@@ -138,7 +213,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
|
||||
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
|
||||
.build());
|
||||
credentials = new TlsCredentials(true, GOOD_CERT, BAD_IPV6);
|
||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IPV6, certificateChecker);
|
||||
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
|
||||
package google.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.truth.Correspondence;
|
||||
import com.google.common.truth.Correspondence.BinaryPredicate;
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
@@ -25,10 +29,15 @@ import com.google.common.truth.SimpleSubjectBuilder;
|
||||
import com.google.common.truth.Subject;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Truth subject for asserting things about ImmutableObjects that are not built in. */
|
||||
@@ -62,17 +71,295 @@ public final class ImmutableObjectSubject extends Subject {
|
||||
* <p>This is used to verify that entities stored in both cloud SQL and Datastore are identical.
|
||||
*/
|
||||
public void isEqualAcrossDatabases(@Nullable ImmutableObject expected) {
|
||||
ComparisonResult result =
|
||||
checkObjectAcrossDatabases(
|
||||
actual, expected, actual != null ? actual.getClass().getName() : "null");
|
||||
if (result.isFailure()) {
|
||||
throw new AssertionError(result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// The following "check" methods implement a recursive check of immutable object equality across
|
||||
// databases. All of them function in both assertive and predicate modes: if "path" is
|
||||
// provided (not null) then they throw AssertionError's with detailed error messages. If
|
||||
// it is null, they return true for equal objects and false for inequal ones.
|
||||
//
|
||||
// The reason for this dual-mode behavior is that all of these methods can either be used in the
|
||||
// context of a test assertion (in which case we want a detailed error message describing exactly
|
||||
// the location in a complex object where a difference was discovered) or in the context of a
|
||||
// membership check in a set (in which case we don't care about the specific location of the first
|
||||
// difference, we just want to be able to determine if the object "is equal to" another object as
|
||||
// efficiently as possible -- see checkSetAcrossDatabase()).
|
||||
|
||||
@VisibleForTesting
|
||||
static ComparisonResult checkObjectAcrossDatabases(
|
||||
@Nullable Object actual, @Nullable Object expected, @Nullable String path) {
|
||||
if (Objects.equals(actual, expected)) {
|
||||
return ComparisonResult.createSuccess();
|
||||
}
|
||||
|
||||
// They're different, do a more detailed comparison.
|
||||
|
||||
// Check for null first (we can assume both variables are not null at this point).
|
||||
if (actual == null) {
|
||||
assertThat(expected).isNull();
|
||||
return ComparisonResult.createFailure(path, "expected ", expected, "got null.");
|
||||
} else if (expected == null) {
|
||||
return ComparisonResult.createFailure(path, "expected null, got ", actual);
|
||||
|
||||
// For immutable objects, we have to recurse since the contained
|
||||
// object could have DoNotCompare fields, too.
|
||||
} else if (expected instanceof ImmutableObject) {
|
||||
// We only verify that actual is an ImmutableObject so we get a good error message instead
|
||||
// of a context-less ClassCastException.
|
||||
if (!(actual instanceof ImmutableObject)) {
|
||||
return ComparisonResult.createFailure(path, actual, " is not an immutable object.");
|
||||
}
|
||||
|
||||
return checkImmutableAcrossDatabases(
|
||||
(ImmutableObject) actual, (ImmutableObject) expected, path);
|
||||
} else if (expected instanceof Map) {
|
||||
if (!(actual instanceof Map)) {
|
||||
return ComparisonResult.createFailure(path, actual, " is not a Map.");
|
||||
}
|
||||
|
||||
// This would likely be more efficient if we could assume that keys can be compared across
|
||||
// databases using .equals(), however we cannot guarantee key equality so the simplest and
|
||||
// most correct way to accomplish this is by reusing the set comparison.
|
||||
return checkSetAcrossDatabases(
|
||||
((Map<?, ?>) actual).entrySet(), ((Map<?, ?>) expected).entrySet(), path, "Map");
|
||||
} else if (expected instanceof Set) {
|
||||
if (!(actual instanceof Set)) {
|
||||
return ComparisonResult.createFailure(path, actual, " is not a Set.");
|
||||
}
|
||||
|
||||
return checkSetAcrossDatabases((Set<?>) actual, (Set<?>) expected, path, "Set");
|
||||
} else if (expected instanceof Collection) {
|
||||
if (!(actual instanceof Collection)) {
|
||||
return ComparisonResult.createFailure(path, actual, " is not a Collection.");
|
||||
}
|
||||
|
||||
return checkListAcrossDatabases((Collection<?>) actual, (Collection<?>) expected, path);
|
||||
// Give Map.Entry special treatment to facilitate the use of Set comparison for verification
|
||||
// of Map.
|
||||
} else if (expected instanceof Map.Entry) {
|
||||
if (!(actual instanceof Map.Entry)) {
|
||||
return ComparisonResult.createFailure(path, actual, " is not a Map.Entry.");
|
||||
}
|
||||
|
||||
// Check both the key and value. We can always ignore the path here, this should only be
|
||||
// called from within a set comparison.
|
||||
ComparisonResult result;
|
||||
if ((result =
|
||||
checkObjectAcrossDatabases(
|
||||
((Map.Entry<?, ?>) actual).getKey(), ((Map.Entry<?, ?>) expected).getKey(), null))
|
||||
.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
if ((result =
|
||||
checkObjectAcrossDatabases(
|
||||
((Map.Entry<?, ?>) actual).getValue(),
|
||||
((Map.Entry<?, ?>) expected).getValue(),
|
||||
null))
|
||||
.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
assertThat(expected).isNotNull();
|
||||
// Since we know that the objects are not equal and since any other types can not be expected
|
||||
// to contain DoNotCompare elements, this condition is always a failure.
|
||||
return ComparisonResult.createFailure(path, actual, " is not equal to ", expected);
|
||||
}
|
||||
if (actual != null) {
|
||||
Map<Field, Object> actualFields = filterFields(actual, ImmutableObject.DoNotCompare.class);
|
||||
Map<Field, Object> expectedFields =
|
||||
filterFields(expected, ImmutableObject.DoNotCompare.class);
|
||||
assertThat(actualFields).containsExactlyEntriesIn(expectedFields);
|
||||
|
||||
return ComparisonResult.createSuccess();
|
||||
}
|
||||
|
||||
private static ComparisonResult checkSetAcrossDatabases(
|
||||
Set<?> actual, Set<?> expected, String path, String type) {
|
||||
// Unfortunately, we can't just check to see whether one set "contains" all of the elements of
|
||||
// the other, as the cross database checks don't require strict equality. Instead we have to do
|
||||
// an N^2 comparison to search for an equivalent element.
|
||||
|
||||
// Objects in expected that aren't in actual. We use "identity sets" here and below because we
|
||||
// want to keep track of the _objects themselves_ rather than rely upon any overridable notion
|
||||
// of equality.
|
||||
Set<Object> missing = path != null ? Sets.newIdentityHashSet() : null;
|
||||
|
||||
// Objects from actual that have matching elements in expected.
|
||||
Set<Object> found = Sets.newIdentityHashSet();
|
||||
|
||||
// Build missing and found.
|
||||
for (Object expectedElem : expected) {
|
||||
boolean gotMatch = false;
|
||||
for (Object actualElem : actual) {
|
||||
if (!checkObjectAcrossDatabases(actualElem, expectedElem, null).isFailure()) {
|
||||
gotMatch = true;
|
||||
|
||||
// Add the element to the set of expected elements that were "found" in actual. If the
|
||||
// element matches multiple elements in "expected," we have a basic problem with this
|
||||
// kind of set that we'll want to know about.
|
||||
if (!found.add(actualElem)) {
|
||||
return ComparisonResult.createFailure(
|
||||
path, "element ", actualElem, " matches multiple elements in ", expected);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotMatch) {
|
||||
if (path == null) {
|
||||
return ComparisonResult.createFailure();
|
||||
}
|
||||
missing.add(expectedElem);
|
||||
}
|
||||
}
|
||||
|
||||
if (path != null) {
|
||||
// Provide a detailed message consisting of any missing or unexpected items.
|
||||
|
||||
// Build a set of all objects in actual that don't have counterparts in expected.
|
||||
Set<Object> unexpected =
|
||||
actual.stream()
|
||||
.filter(actualElem -> !found.contains(actualElem))
|
||||
.collect(
|
||||
Collector.of(
|
||||
Sets::newIdentityHashSet,
|
||||
Set::add,
|
||||
(result, values) -> {
|
||||
result.addAll(values);
|
||||
return result;
|
||||
}));
|
||||
|
||||
if (!missing.isEmpty() || !unexpected.isEmpty()) {
|
||||
String message = type + " does not contain the expected contents.";
|
||||
if (!missing.isEmpty()) {
|
||||
message += " It is missing: " + formatItems(missing.iterator());
|
||||
}
|
||||
|
||||
if (!unexpected.isEmpty()) {
|
||||
message += " It contains additional elements: " + formatItems(unexpected.iterator());
|
||||
}
|
||||
|
||||
return ComparisonResult.createFailure(path, message);
|
||||
}
|
||||
|
||||
// We just need to check if there were any objects in "actual" that were not in "expected"
|
||||
// (where "found" is a proxy for "expected").
|
||||
} else if (actual.stream().anyMatch(Predicate.not(found::contains))) {
|
||||
return ComparisonResult.createFailure();
|
||||
}
|
||||
|
||||
return ComparisonResult.createSuccess();
|
||||
}
|
||||
|
||||
private static ComparisonResult checkListAcrossDatabases(
|
||||
Collection<?> actual, Collection<?> expected, @Nullable String path) {
|
||||
Iterator<?> actualIter = actual.iterator();
|
||||
Iterator<?> expectedIter = expected.iterator();
|
||||
int index = 0;
|
||||
while (actualIter.hasNext() && expectedIter.hasNext()) {
|
||||
Object actualItem = actualIter.next();
|
||||
Object expectedItem = expectedIter.next();
|
||||
ComparisonResult result =
|
||||
checkObjectAcrossDatabases(
|
||||
actualItem, expectedItem, path != null ? path + "[" + index + "]" : null);
|
||||
if (result.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
if (actualIter.hasNext()) {
|
||||
return ComparisonResult.createFailure(
|
||||
path, "has additional items: ", formatItems(actualIter));
|
||||
}
|
||||
|
||||
if (expectedIter.hasNext()) {
|
||||
return ComparisonResult.createFailure(path, "missing items: ", formatItems(expectedIter));
|
||||
}
|
||||
|
||||
return ComparisonResult.createSuccess();
|
||||
}
|
||||
|
||||
/** Recursive helper for isEqualAcrossDatabases. */
|
||||
private static ComparisonResult checkImmutableAcrossDatabases(
|
||||
ImmutableObject actual, ImmutableObject expected, String path) {
|
||||
Map<Field, Object> actualFields = filterFields(actual, ImmutableObject.DoNotCompare.class);
|
||||
Map<Field, Object> expectedFields = filterFields(expected, ImmutableObject.DoNotCompare.class);
|
||||
|
||||
for (Map.Entry<Field, Object> entry : expectedFields.entrySet()) {
|
||||
if (!actualFields.containsKey(entry.getKey())) {
|
||||
return ComparisonResult.createFailure(path, "is missing field ", entry.getKey().getName());
|
||||
}
|
||||
|
||||
// Verify that the field values are the same.
|
||||
Object expectedFieldValue = entry.getValue();
|
||||
Object actualFieldValue = actualFields.get(entry.getKey());
|
||||
ComparisonResult result =
|
||||
checkObjectAcrossDatabases(
|
||||
actualFieldValue,
|
||||
expectedFieldValue,
|
||||
path != null ? path + "." + entry.getKey().getName() : null);
|
||||
if (result.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for fields in actual that are not in expected.
|
||||
for (Map.Entry<Field, Object> entry : actualFields.entrySet()) {
|
||||
if (!expectedFields.containsKey(entry.getKey())) {
|
||||
return ComparisonResult.createFailure(
|
||||
path, "has additional field ", entry.getKey().getName());
|
||||
}
|
||||
}
|
||||
|
||||
return ComparisonResult.createSuccess();
|
||||
}
|
||||
|
||||
private static String formatItems(Iterator<?> iter) {
|
||||
return Joiner.on(", ").join(iter);
|
||||
}
|
||||
|
||||
/** Encapsulates success/failure result in recursive comparison with optional error string. */
|
||||
static class ComparisonResult {
|
||||
boolean succeeded;
|
||||
String message;
|
||||
|
||||
private ComparisonResult(boolean succeeded, @Nullable String message) {
|
||||
this.succeeded = succeeded;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
static ComparisonResult createFailure() {
|
||||
return new ComparisonResult(false, null);
|
||||
}
|
||||
|
||||
static ComparisonResult createFailure(@Nullable String path, Object... message) {
|
||||
return new ComparisonResult(false, "At " + path + ": " + Joiner.on("").join(message));
|
||||
}
|
||||
|
||||
static ComparisonResult createSuccess() {
|
||||
return new ComparisonResult(true, null);
|
||||
}
|
||||
|
||||
String getMessage() {
|
||||
checkNotNull(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
boolean isFailure() {
|
||||
return !succeeded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the hash value reported by {@code actual} is correct.
|
||||
*
|
||||
* <p>This is used in the replay tests to ensure that hibernate hasn't modified any fields that
|
||||
* are not marked as @Insignificant while loading.
|
||||
*/
|
||||
public void hasCorrectHashValue() {
|
||||
assertThat(Arrays.hashCode(actual.getSignificantFields().values().toArray()))
|
||||
.isEqualTo(actual.hashCode());
|
||||
}
|
||||
|
||||
public static Correspondence<ImmutableObject, ImmutableObject> immutableObjectCorrespondence(
|
||||
@@ -109,7 +396,8 @@ public final class ImmutableObjectSubject extends Subject {
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Field, Object> filterFields(ImmutableObject original, String... ignoredFields) {
|
||||
private static Map<Field, Object> filterFields(
|
||||
ImmutableObject original, String... ignoredFields) {
|
||||
ImmutableSet<String> ignoredFieldSet = ImmutableSet.copyOf(ignoredFields);
|
||||
Map<Field, Object> originalFields = ModelUtils.getFieldValues(original);
|
||||
// don't use ImmutableMap or a stream->collect model since we can have nulls
|
||||
@@ -123,7 +411,7 @@ public final class ImmutableObjectSubject extends Subject {
|
||||
}
|
||||
|
||||
/** Filter out fields with the given annotation. */
|
||||
public static Map<Field, Object> filterFields(
|
||||
private static Map<Field, Object> filterFields(
|
||||
ImmutableObject original, Class<? extends Annotation> annotation) {
|
||||
Map<Field, Object> originalFields = ModelUtils.getFieldValues(original);
|
||||
// don't use ImmutableMap or a stream->collect model since we can have nulls
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
// Copyright 2021 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.model;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.ComparisonResult;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.model.ImmutableObjectSubject.checkObjectAcrossDatabases;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ImmutableObjectSubjectTest {
|
||||
|
||||
// Unique id to assign to the "ignored" field so that it always gets a unique value.
|
||||
private static int uniqueId = 0;
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_nulls() {
|
||||
assertAboutImmutableObjects().that(null).isEqualAcrossDatabases(null);
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestAtom(null))
|
||||
.isEqualAcrossDatabases(makeTestAtom(null));
|
||||
|
||||
assertThat(checkObjectAcrossDatabases(null, makeTestAtom("foo"), null).isFailure()).isTrue();
|
||||
assertThat(checkObjectAcrossDatabases(null, makeTestAtom("foo"), null).isFailure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_equalObjects() {
|
||||
TestImmutableObject actual = makeTestObj();
|
||||
assertAboutImmutableObjects().that(actual).isEqualAcrossDatabases(actual);
|
||||
assertAboutImmutableObjects().that(actual).isEqualAcrossDatabases(makeTestObj());
|
||||
assertThat(checkObjectAcrossDatabases(makeTestObj(), makeTestObj(), null).isFailure())
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_simpleFieldFailure() {
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withStringField("bar")));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$TestImmutableObject.stringField:");
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(makeTestObj(), makeTestObj().withStringField(null), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_nestedImmutableFailure() {
|
||||
// Repeat the null checks to verify that the attribute path is preserved.
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withNested(null)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$TestImmutableObject.nested:"
|
||||
+ " expected null, got TestImmutableObject");
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj().withNested(null))
|
||||
.isEqualAcrossDatabases(makeTestObj()));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$TestImmutableObject.nested:"
|
||||
+ " expected TestImmutableObject");
|
||||
assertThat(e).hasMessageThat().contains("got null.");
|
||||
|
||||
// Test with a field difference.
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj().withNested(makeTestObj().withNested(null))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.nested.stringField:");
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(makeTestObj(), makeTestObj().withNested(null), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_listFailure() {
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withList(null)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$" + "TestImmutableObject.list:");
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj().withList(ImmutableList.of(makeTestAtom("wack")))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.list[0].stringField:");
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj()
|
||||
.withList(
|
||||
ImmutableList.of(
|
||||
makeTestAtom("baz"),
|
||||
makeTestAtom("bot"),
|
||||
makeTestAtom("boq")))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.list: missing items");
|
||||
// Make sure multiple additional items get formatted nicely.
|
||||
assertThat(e).hasMessageThat().contains("}, TestImmutableObject");
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withList(ImmutableList.of())));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.list: has additional items");
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(),
|
||||
makeTestObj()
|
||||
.withList(ImmutableList.of(makeTestAtom("baz"), makeTestAtom("gauze"))),
|
||||
null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(), makeTestObj().withList(ImmutableList.of()), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(),
|
||||
makeTestObj().withList(ImmutableList.of(makeTestAtom("gauze"))),
|
||||
null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_setFailure() {
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withSet(null)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.set: expected null, got ");
|
||||
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj().withSet(ImmutableSet.of(makeTestAtom("jim")))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(
|
||||
Pattern.compile(
|
||||
"Set does not contain the expected contents. "
|
||||
+ "It is missing: .*jim.* It contains additional elements: .*bob",
|
||||
Pattern.DOTALL));
|
||||
|
||||
// Trickery here to verify that multiple items that both match existing items in the set trigger
|
||||
// an error: we can add two of the same items because equality for purposes of the set includes
|
||||
// the DoNotCompare field.
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj()
|
||||
.withSet(ImmutableSet.of(makeTestAtom("bob"), makeTestAtom("bob")))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(
|
||||
Pattern.compile(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest\\$TestImmutableObject.set: "
|
||||
+ "element .*bob.* matches multiple elements in .*bob.*bob",
|
||||
Pattern.DOTALL));
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(
|
||||
makeTestObj()
|
||||
.withSet(ImmutableSet.of(makeTestAtom("bob"), makeTestAtom("bob"))))
|
||||
.isEqualAcrossDatabases(makeTestObj()));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(
|
||||
Pattern.compile(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest\\$TestImmutableObject.set: "
|
||||
+ "Set does not contain the expected contents. It contains additional "
|
||||
+ "elements: .*bob",
|
||||
Pattern.DOTALL));
|
||||
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(),
|
||||
makeTestObj()
|
||||
.withSet(ImmutableSet.of(makeTestAtom("bob"), makeTestAtom("robert"))),
|
||||
null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(), makeTestObj().withSet(ImmutableSet.of()), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(),
|
||||
makeTestObj()
|
||||
.withSet(ImmutableSet.of(makeTestAtom("bob"), makeTestAtom("bob"))),
|
||||
null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
// We don't test the case where actual's set contains multiple items matching the single item in
|
||||
// the expected set: that path is the same as the "additional contents" path.
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_mapFailure() {
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(makeTestObj().withMap(null)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$"
|
||||
+ "TestImmutableObject.map: expected null, got ");
|
||||
|
||||
e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(makeTestObj())
|
||||
.isEqualAcrossDatabases(
|
||||
makeTestObj()
|
||||
.withMap(ImmutableMap.of(makeTestAtom("difk"), makeTestAtom("difv")))));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch(
|
||||
Pattern.compile(
|
||||
"Map does not contain the expected contents. "
|
||||
+ "It is missing: .*difk.*difv.* It contains additional elements: .*key.*val",
|
||||
Pattern.DOTALL));
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(),
|
||||
makeTestObj()
|
||||
.withMap(
|
||||
ImmutableMap.of(
|
||||
makeTestAtom("key"), makeTestAtom("val"),
|
||||
makeTestAtom("otherk"), makeTestAtom("otherv"))),
|
||||
null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(
|
||||
makeTestObj(), makeTestObj().withMap(ImmutableMap.of()), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_typeChecks() {
|
||||
ComparisonResult result = checkObjectAcrossDatabases("blech", makeTestObj(), "xxx");
|
||||
assertThat(result.getMessage()).isEqualTo("At xxx: blech is not an immutable object.");
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(checkObjectAcrossDatabases("blech", makeTestObj(), null).isFailure()).isTrue();
|
||||
|
||||
result = checkObjectAcrossDatabases("blech", ImmutableMap.of(), "xxx");
|
||||
assertThat(result.getMessage()).isEqualTo("At xxx: blech is not a Map.");
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(checkObjectAcrossDatabases("blech", ImmutableMap.of(), null).isFailure()).isTrue();
|
||||
|
||||
result = checkObjectAcrossDatabases("blech", ImmutableList.of(), "xxx");
|
||||
assertThat(result.getMessage()).isEqualTo("At xxx: blech is not a Collection.");
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(checkObjectAcrossDatabases("blech", ImmutableList.of(), null).isFailure()).isTrue();
|
||||
|
||||
for (ImmutableMap.Entry<String, String> entry : ImmutableMap.of("foo", "bar").entrySet()) {
|
||||
result = checkObjectAcrossDatabases("blech", entry, "xxx");
|
||||
assertThat(result.getMessage()).isEqualTo("At xxx: blech is not a Map.Entry.");
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(checkObjectAcrossDatabases("blech", entry, "xxx").isFailure()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrossDatabase_checkAdditionalFields() {
|
||||
AssertionError e =
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertAboutImmutableObjects()
|
||||
.that(DerivedImmutableObject.create())
|
||||
.isEqualAcrossDatabases(makeTestAtom(null)));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
"At google.registry.model.ImmutableObjectSubjectTest$DerivedImmutableObject: "
|
||||
+ "has additional field extraField");
|
||||
|
||||
assertThat(
|
||||
checkObjectAcrossDatabases(DerivedImmutableObject.create(), makeTestAtom(null), null)
|
||||
.isFailure())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasCorrectHashValue() {
|
||||
TestImmutableObject object = makeTestObj();
|
||||
assertAboutImmutableObjects().that(object).hasCorrectHashValue();
|
||||
|
||||
object.stringField = "changed value!";
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() -> assertAboutImmutableObjects().that(object).hasCorrectHashValue());
|
||||
}
|
||||
|
||||
/** Make a test object with all fields set up. */
|
||||
TestImmutableObject makeTestObj() {
|
||||
return TestImmutableObject.create(
|
||||
"foo",
|
||||
makeTestAtom("bar"),
|
||||
ImmutableList.of(makeTestAtom("baz")),
|
||||
ImmutableSet.of(makeTestAtom("bob")),
|
||||
ImmutableMap.of(makeTestAtom("key"), makeTestAtom("val")));
|
||||
}
|
||||
|
||||
/** Make a test object without the collection fields. */
|
||||
TestImmutableObject makeTestAtom(String stringField) {
|
||||
return TestImmutableObject.create(stringField, null, null, null, null);
|
||||
}
|
||||
|
||||
static class TestImmutableObject extends ImmutableObject {
|
||||
String stringField;
|
||||
TestImmutableObject nested;
|
||||
ImmutableList<TestImmutableObject> list;
|
||||
ImmutableSet<TestImmutableObject> set;
|
||||
ImmutableMap<TestImmutableObject, TestImmutableObject> map;
|
||||
|
||||
@ImmutableObject.DoNotCompare int ignored;
|
||||
|
||||
static TestImmutableObject create(
|
||||
String stringField,
|
||||
TestImmutableObject nested,
|
||||
ImmutableList<TestImmutableObject> list,
|
||||
ImmutableSet<TestImmutableObject> set,
|
||||
ImmutableMap<TestImmutableObject, TestImmutableObject> map) {
|
||||
TestImmutableObject instance = new TestImmutableObject();
|
||||
instance.stringField = stringField;
|
||||
instance.nested = nested;
|
||||
instance.list = list;
|
||||
instance.set = set;
|
||||
instance.map = map;
|
||||
instance.ignored = ++uniqueId;
|
||||
return instance;
|
||||
}
|
||||
|
||||
TestImmutableObject withStringField(@Nullable String stringField) {
|
||||
TestImmutableObject result = ImmutableObject.clone(this);
|
||||
result.stringField = stringField;
|
||||
return result;
|
||||
}
|
||||
|
||||
TestImmutableObject withNested(@Nullable TestImmutableObject nested) {
|
||||
TestImmutableObject result = ImmutableObject.clone(this);
|
||||
result.nested = nested;
|
||||
return result;
|
||||
}
|
||||
|
||||
TestImmutableObject withList(@Nullable ImmutableList<TestImmutableObject> list) {
|
||||
TestImmutableObject result = ImmutableObject.clone(this);
|
||||
result.list = list;
|
||||
return result;
|
||||
}
|
||||
|
||||
TestImmutableObject withSet(@Nullable ImmutableSet<TestImmutableObject> set) {
|
||||
TestImmutableObject result = ImmutableObject.clone(this);
|
||||
result.set = set;
|
||||
return result;
|
||||
}
|
||||
|
||||
TestImmutableObject withMap(
|
||||
@Nullable ImmutableMap<TestImmutableObject, TestImmutableObject> map) {
|
||||
TestImmutableObject result = ImmutableObject.clone(this);
|
||||
result.map = map;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static class DerivedImmutableObject extends TestImmutableObject {
|
||||
String extraField;
|
||||
|
||||
static DerivedImmutableObject create() {
|
||||
return new DerivedImmutableObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class ContactCommandTest {
|
||||
EppLoader eppLoader = new EppLoader(this, "contact_update.xml");
|
||||
Update command =
|
||||
(Update)
|
||||
((ResourceCommandWrapper) (eppLoader.getEpp().getCommandWrapper().getCommand()))
|
||||
((ResourceCommandWrapper) eppLoader.getEpp().getCommandWrapper().getCommand())
|
||||
.getResourceCommand();
|
||||
Change change = command.getInnerChange();
|
||||
assertThat(change.getInternationalizedPostalInfo().getAddress())
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2020 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.model.reporting;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newDomainBase;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.EntityTestCase;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
@DualDatabaseTest
|
||||
public class HistoryEntryDaoTest extends EntityTestCase {
|
||||
|
||||
private DomainBase domain;
|
||||
private HistoryEntry historyEntry;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
|
||||
createTld("foobar");
|
||||
domain = persistActiveDomain("foo.foobar");
|
||||
DomainTransactionRecord transactionRecord =
|
||||
new DomainTransactionRecord.Builder()
|
||||
.setTld("foobar")
|
||||
.setReportingTime(fakeClock.nowUtc())
|
||||
.setReportField(TransactionReportField.NET_ADDS_1_YR)
|
||||
.setReportAmount(1)
|
||||
.build();
|
||||
// Set up a new persisted HistoryEntry entity.
|
||||
historyEntry =
|
||||
new DomainHistory.Builder()
|
||||
.setParent(domain)
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setPeriod(Period.create(1, Period.Unit.YEARS))
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setClientId("TheRegistrar")
|
||||
.setOtherClientId("otherClient")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(false)
|
||||
.setDomainTransactionRecords(ImmutableSet.of(transactionRecord))
|
||||
.build();
|
||||
persistResource(historyEntry);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSimpleLoadAll() {
|
||||
assertThat(HistoryEntryDao.loadAllHistoryObjects(START_OF_TIME, END_OF_TIME))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("nsHosts"))
|
||||
.containsExactly(historyEntry);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSkips_tooEarly() {
|
||||
assertThat(HistoryEntryDao.loadAllHistoryObjects(fakeClock.nowUtc().plusMillis(1), END_OF_TIME))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSkips_tooLate() {
|
||||
assertThat(
|
||||
HistoryEntryDao.loadAllHistoryObjects(START_OF_TIME, fakeClock.nowUtc().minusMillis(1)))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testLoadByResource() {
|
||||
transactIfJpaTm(
|
||||
() ->
|
||||
assertThat(HistoryEntryDao.loadHistoryObjectsForResource(domain.createVKey()))
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("nsHosts"))
|
||||
.containsExactly(historyEntry));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testLoadByResource_skips_tooEarly() {
|
||||
assertThat(
|
||||
HistoryEntryDao.loadHistoryObjectsForResource(
|
||||
domain.createVKey(), fakeClock.nowUtc().plusMillis(1), END_OF_TIME))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testLoadByResource_skips_tooLate() {
|
||||
assertThat(
|
||||
HistoryEntryDao.loadHistoryObjectsForResource(
|
||||
domain.createVKey(), START_OF_TIME, fakeClock.nowUtc().minusMillis(1)))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testLoadByResource_noEntriesForResource() {
|
||||
DomainBase newDomain = persistResource(newDomainBase("new.foobar"));
|
||||
assertThat(HistoryEntryDao.loadHistoryObjectsForResource(newDomain.createVKey())).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,29 @@
|
||||
|
||||
package google.registry.model.reporting;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newDomainBase;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.model.EntityTestCase;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.testing.TestOfyOnly;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link HistoryEntry}. */
|
||||
@DualDatabaseTest
|
||||
class HistoryEntryTest extends EntityTestCase {
|
||||
|
||||
private HistoryEntry historyEntry;
|
||||
@@ -37,6 +44,7 @@ class HistoryEntryTest extends EntityTestCase {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
createTld("foobar");
|
||||
DomainBase domain = persistActiveDomain("foo.foobar");
|
||||
DomainTransactionRecord transactionRecord =
|
||||
new DomainTransactionRecord.Builder()
|
||||
.setTld("foobar")
|
||||
@@ -46,13 +54,13 @@ class HistoryEntryTest extends EntityTestCase {
|
||||
.build();
|
||||
// Set up a new persisted HistoryEntry entity.
|
||||
historyEntry =
|
||||
new HistoryEntry.Builder()
|
||||
.setParent(newDomainBase("foo.foobar"))
|
||||
new DomainHistory.Builder()
|
||||
.setParent(domain)
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setPeriod(Period.create(1, Period.Unit.YEARS))
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(fakeClock.nowUtc())
|
||||
.setClientId("foo")
|
||||
.setClientId("TheRegistrar")
|
||||
.setOtherClientId("otherClient")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
@@ -63,13 +71,23 @@ class HistoryEntryTest extends EntityTestCase {
|
||||
persistResource(historyEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testPersistence() {
|
||||
assertThat(ofy().load().entity(historyEntry).now()).isEqualTo(historyEntry);
|
||||
transactIfJpaTm(
|
||||
() -> {
|
||||
HistoryEntry fromDatabase = tm().loadByEntity(historyEntry);
|
||||
assertAboutImmutableObjects()
|
||||
.that(fromDatabase)
|
||||
.isEqualExceptFields(historyEntry, "nsHosts", "domainTransactionRecords");
|
||||
assertAboutImmutableObjects()
|
||||
.that(Iterables.getOnlyElement(fromDatabase.getDomainTransactionRecords()))
|
||||
.isEqualExceptFields(
|
||||
Iterables.getOnlyElement(historyEntry.getDomainTransactionRecords()), "id");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyOnly
|
||||
void testIndexing() throws Exception {
|
||||
verifyIndexing(historyEntry, "modificationTime", "clientId");
|
||||
verifyIndexing(historyEntry.asHistoryEntry(), "modificationTime", "clientId");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,28 +54,28 @@ public class TransferDataTest {
|
||||
transferBillingEventKey =
|
||||
VKey.create(
|
||||
BillingEvent.OneTime.class,
|
||||
12345,
|
||||
Key.create(historyEntryKey, BillingEvent.OneTime.class, 12345));
|
||||
12345L,
|
||||
Key.create(historyEntryKey, BillingEvent.OneTime.class, 12345L));
|
||||
otherServerApproveBillingEventKey =
|
||||
VKey.create(
|
||||
BillingEvent.Cancellation.class,
|
||||
2468,
|
||||
Key.create(historyEntryKey, BillingEvent.Cancellation.class, 2468));
|
||||
2468L,
|
||||
Key.create(historyEntryKey, BillingEvent.Cancellation.class, 2468L));
|
||||
recurringBillingEventKey =
|
||||
VKey.create(
|
||||
BillingEvent.Recurring.class,
|
||||
13579,
|
||||
Key.create(historyEntryKey, BillingEvent.Recurring.class, 13579));
|
||||
13579L,
|
||||
Key.create(historyEntryKey, BillingEvent.Recurring.class, 13579L));
|
||||
autorenewPollMessageKey =
|
||||
VKey.create(
|
||||
PollMessage.Autorenew.class,
|
||||
67890,
|
||||
Key.create(historyEntryKey, PollMessage.Autorenew.class, 67890));
|
||||
67890L,
|
||||
Key.create(historyEntryKey, PollMessage.Autorenew.class, 67890L));
|
||||
otherServerApprovePollMessageKey =
|
||||
VKey.create(
|
||||
PollMessage.OneTime.class,
|
||||
314159,
|
||||
Key.create(historyEntryKey, PollMessage.OneTime.class, 314159));
|
||||
314159L,
|
||||
Key.create(historyEntryKey, PollMessage.OneTime.class, 314159L));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -95,7 +95,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||
registrarLol)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
|
||||
// deleted domain in lol
|
||||
@@ -128,7 +128,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||
registrarLol)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.setDeletionTime(clock.nowUtc().minusDays(1))
|
||||
.build());
|
||||
// cat.みんな
|
||||
@@ -168,7 +168,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||
registrarIdn)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
|
||||
// 1.tld
|
||||
@@ -208,7 +208,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||
registrar1Tld)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
|
||||
// history entries
|
||||
|
||||
@@ -176,7 +176,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.asBuilder()
|
||||
.setSubordinateHosts(ImmutableSet.of("ns1.cat.lol", "ns2.cat.lol"))
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
persistResource(
|
||||
hostNs1CatLol.asBuilder().setSuperordinateDomain(domainCatLol.createVKey()).build());
|
||||
@@ -216,7 +216,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
registrar)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
// cat.example
|
||||
createTld("example");
|
||||
@@ -255,7 +255,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
registrar)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
// cat.みんな
|
||||
createTld("xn--q9jyb4c");
|
||||
@@ -294,7 +294,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
registrar)
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
// cat.1.test
|
||||
createTld("1.test");
|
||||
@@ -334,7 +334,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.asBuilder()
|
||||
.setSubordinateHosts(ImmutableSet.of("ns1.cat.1.test"))
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo")
|
||||
.setCreationClientId("TheRegistrar")
|
||||
.build());
|
||||
|
||||
persistResource(makeRegistrar("otherregistrar", "other", Registrar.State.ACTIVE));
|
||||
@@ -430,7 +430,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.asBuilder()
|
||||
.setNameservers(hostKeys)
|
||||
.setCreationTimeForTest(clock.nowUtc().minusYears(3))
|
||||
.setCreationClientId("foo");
|
||||
.setCreationClientId("TheRegistrar");
|
||||
if (domainName.equals(mainDomainName)) {
|
||||
builder.setSubordinateHosts(subordinateHostnamesBuilder.build());
|
||||
}
|
||||
|
||||
@@ -191,7 +191,6 @@ class RdapJsonFormatterTest {
|
||||
hostResourceIpv6,
|
||||
registrar)
|
||||
.asBuilder()
|
||||
.setCreationClientId("foo")
|
||||
.setCreationTimeForTest(clock.nowUtc().minusMonths(4))
|
||||
.setLastEppUpdateTime(clock.nowUtc().minusMonths(3))
|
||||
.build());
|
||||
@@ -206,7 +205,6 @@ class RdapJsonFormatterTest {
|
||||
null,
|
||||
registrar)
|
||||
.asBuilder()
|
||||
.setCreationClientId("foo")
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.setLastEppUpdateTime(null)
|
||||
.build());
|
||||
|
||||
@@ -313,7 +313,7 @@ class RdapTestHelper {
|
||||
}
|
||||
builder.append(
|
||||
String.format(
|
||||
"Different: %s -> %s\ninstead of %s\n\n",
|
||||
"Actual: %s -> %s\nExpected: %s\n\n",
|
||||
name, jsonifyAndIndent(actual), jsonifyAndIndent(expected)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tmch.LordnTaskUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -116,7 +117,6 @@ import javax.annotation.Nullable;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeComparator;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/** Static utils for setting up test resources. */
|
||||
@@ -532,11 +532,14 @@ public class DatabaseHelper {
|
||||
DateTime requestTime,
|
||||
DateTime expirationTime,
|
||||
DateTime now) {
|
||||
HistoryEntry historyEntryContactTransfer = persistResource(
|
||||
new HistoryEntry.Builder()
|
||||
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
|
||||
.setParent(contact)
|
||||
.build());
|
||||
HistoryEntry historyEntryContactTransfer =
|
||||
persistResource(
|
||||
new HistoryEntry.Builder()
|
||||
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
|
||||
.setParent(persistResource(contact))
|
||||
.setModificationTime(now)
|
||||
.build()
|
||||
.toChildHistoryEntity());
|
||||
return persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
@@ -1115,7 +1118,8 @@ public class DatabaseHelper {
|
||||
historyEntry ->
|
||||
historyEntry.getParent().getName().equals(resource.getRepoId()))
|
||||
.collect(toImmutableList());
|
||||
return ImmutableList.sortedCopyOf(DateTimeComparator.getInstance(), filtered);
|
||||
return ImmutableList.sortedCopyOf(
|
||||
Comparator.comparing(HistoryEntry::getModificationTime), filtered);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@ public final class FullFieldsTestEntityHelper {
|
||||
.setPeriod(period)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(modificationTime)
|
||||
.setClientId("foo")
|
||||
.setClientId(resource.getPersistedCurrentSponsorClientId())
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason(reason)
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.testing;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -26,6 +25,7 @@ import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.ofy.ReplayQueue;
|
||||
import google.registry.model.ofy.TransactionInfo;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
@@ -106,16 +106,20 @@ public class ReplayExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since the object may have changed in datastore by the time we're doing the replay, we
|
||||
// have to compare the current value in SQL (which we just mutated) against the value that
|
||||
// we originally would have persisted (that being the object in the entry).
|
||||
VKey<?> vkey = VKey.from(entry.getKey());
|
||||
Optional<?> ofyValue = ofyTm().transact(() -> ofyTm().loadByKeyIfPresent(vkey));
|
||||
Optional<?> jpaValue = jpaTm().transact(() -> jpaTm().loadByKeyIfPresent(vkey));
|
||||
if (entry.getValue().equals(TransactionInfo.Delete.SENTINEL)) {
|
||||
assertThat(jpaValue.isPresent()).isFalse();
|
||||
assertThat(ofyValue.isPresent()).isFalse();
|
||||
} else {
|
||||
ImmutableObject immutJpaObject = (ImmutableObject) jpaValue.get();
|
||||
assertAboutImmutableObjects().that(immutJpaObject).hasCorrectHashValue();
|
||||
assertAboutImmutableObjects()
|
||||
.that((ImmutableObject) jpaValue.get())
|
||||
.isEqualAcrossDatabases((ImmutableObject) ofyValue.get());
|
||||
.that(immutJpaObject)
|
||||
.isEqualAcrossDatabases(
|
||||
(ImmutableObject) ((DatastoreEntity) entry.getValue()).toSqlEntity().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class TmchCertificateAuthorityTest {
|
||||
CertificateExpiredException e =
|
||||
assertThrows(
|
||||
CertificateExpiredException.class, tmchCertificateAuthority::getAndValidateRoot);
|
||||
assertThat(e).hasMessageThat().containsMatch("NotAfter: Sun Jul 23 23:59:59 UTC 2023");
|
||||
assertThat(e).hasMessageThat().contains("NotAfter: Sun Jul 23 23:59:59 UTC 2023");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +64,7 @@ class TmchCertificateAuthorityTest {
|
||||
CertificateNotYetValidException e =
|
||||
assertThrows(
|
||||
CertificateNotYetValidException.class, tmchCertificateAuthority::getAndValidateRoot);
|
||||
assertThat(e).hasMessageThat().containsMatch("NotBefore: Wed Jul 24 00:00:00 UTC 2013");
|
||||
assertThat(e).hasMessageThat().contains("NotBefore: Wed Jul 24 00:00:00 UTC 2013");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -61,7 +61,7 @@ public class CompareDbBackupsTest {
|
||||
URL backupRootFolder = Resources.getResource("google/registry/tools/datastore-export");
|
||||
CompareDbBackups.main(new String[] {backupRootFolder.getPath(), backupRootFolder.getPath()});
|
||||
String output = new String(stdout.toByteArray(), UTF_8);
|
||||
assertThat(output).containsMatch("Both sets have the same 41 entities");
|
||||
assertThat(output).contains("Both sets have the same 41 entities");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
@@ -389,9 +390,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||
@Test
|
||||
void testFail_clientCertFileFlagWithViolation() throws Exception {
|
||||
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--name=blobio",
|
||||
@@ -419,9 +420,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||
@Test
|
||||
void testFail_clientCertFileFlagWithMultipleViolations() throws Exception {
|
||||
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--name=blobio",
|
||||
@@ -476,9 +477,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||
@Test
|
||||
void testFail_failoverClientCertFileFlagWithViolations() throws Exception {
|
||||
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--name=blobio",
|
||||
@@ -506,9 +507,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||
@Test
|
||||
void testFail_failoverClientCertFileFlagWithMultipleViolations() throws Exception {
|
||||
fakeClock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--name=blobio",
|
||||
|
||||
@@ -22,12 +22,14 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeHistoryEntr
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link GetClaimsListCommand}. */
|
||||
@DualDatabaseTest
|
||||
class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesCommand> {
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01T00:00:00Z"));
|
||||
@@ -40,7 +42,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
|
||||
domain = persistActiveDomain("example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_works() throws Exception {
|
||||
persistResource(
|
||||
makeHistoryEntry(
|
||||
@@ -51,7 +53,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
|
||||
clock.nowUtc()));
|
||||
runCommand("--id=example.tld", "--type=DOMAIN");
|
||||
assertStdoutIs(
|
||||
"Client: foo\n"
|
||||
"Client: TheRegistrar\n"
|
||||
+ "Time: 2000-01-01T00:00:00.000Z\n"
|
||||
+ "Client TRID: ABC-123\n"
|
||||
+ "Server TRID: server-trid\n"
|
||||
@@ -60,7 +62,57 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
|
||||
+ "\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSuccess_nothingBefore() throws Exception {
|
||||
persistResource(
|
||||
makeHistoryEntry(
|
||||
domain,
|
||||
HistoryEntry.Type.DOMAIN_CREATE,
|
||||
Period.create(1, Period.Unit.YEARS),
|
||||
"created",
|
||||
clock.nowUtc()));
|
||||
runCommand("--before", clock.nowUtc().minusMinutes(1).toString());
|
||||
assertStdoutIs("");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_nothingAfter() throws Exception {
|
||||
persistResource(
|
||||
makeHistoryEntry(
|
||||
domain,
|
||||
HistoryEntry.Type.DOMAIN_CREATE,
|
||||
Period.create(1, Period.Unit.YEARS),
|
||||
"created",
|
||||
clock.nowUtc()));
|
||||
runCommand("--after", clock.nowUtc().plusMinutes(1).toString());
|
||||
assertStdoutIs("");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_withinRange() throws Exception {
|
||||
persistResource(
|
||||
makeHistoryEntry(
|
||||
domain,
|
||||
HistoryEntry.Type.DOMAIN_CREATE,
|
||||
Period.create(1, Period.Unit.YEARS),
|
||||
"created",
|
||||
clock.nowUtc()));
|
||||
runCommand(
|
||||
"--after",
|
||||
clock.nowUtc().minusMinutes(1).toString(),
|
||||
"--before",
|
||||
clock.nowUtc().plusMinutes(1).toString());
|
||||
assertStdoutIs(
|
||||
"Client: TheRegistrar\n"
|
||||
+ "Time: 2000-01-01T00:00:00.000Z\n"
|
||||
+ "Client TRID: ABC-123\n"
|
||||
+ "Server TRID: server-trid\n"
|
||||
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
|
||||
+ "<xml/>\n"
|
||||
+ "\n");
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testSuccess_noTrid() throws Exception {
|
||||
persistResource(
|
||||
makeHistoryEntry(
|
||||
@@ -74,7 +126,7 @@ class GetHistoryEntriesCommandTest extends CommandTestCase<GetHistoryEntriesComm
|
||||
.build());
|
||||
runCommand("--id=example.tld", "--type=DOMAIN");
|
||||
assertStdoutIs(
|
||||
"Client: foo\n"
|
||||
"Client: TheRegistrar\n"
|
||||
+ "Time: 2000-01-01T00:00:00.000Z\n"
|
||||
+ "Client TRID: null\n"
|
||||
+ "Server TRID: null\n"
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
@@ -334,6 +335,39 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||
assertThat(stdErr).contains("example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_canUpdatePendingDeleteDomain_whenSuperuserPassesOverrideFlag() throws Exception {
|
||||
ContactResource adminContact1 = persistResource(newContactResource("crr-admin1"));
|
||||
ContactResource adminContact2 = persistResource(newContactResource("crr-admin2"));
|
||||
ContactResource techContact1 = persistResource(newContactResource("crr-tech1"));
|
||||
ContactResource techContact2 = persistResource(newContactResource("crr-tech2"));
|
||||
VKey<ContactResource> adminResourceKey1 = adminContact1.createVKey();
|
||||
VKey<ContactResource> adminResourceKey2 = adminContact2.createVKey();
|
||||
VKey<ContactResource> techResourceKey1 = techContact1.createVKey();
|
||||
VKey<ContactResource> techResourceKey2 = techContact2.createVKey();
|
||||
|
||||
persistResource(
|
||||
newDomainBase("example.tld")
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(DesignatedContact.Type.ADMIN, adminResourceKey1),
|
||||
DesignatedContact.create(DesignatedContact.Type.ADMIN, adminResourceKey2),
|
||||
DesignatedContact.create(DesignatedContact.Type.TECH, techResourceKey1),
|
||||
DesignatedContact.create(DesignatedContact.Type.TECH, techResourceKey2)))
|
||||
.setStatusValues(ImmutableSet.of(PENDING_DELETE))
|
||||
.build());
|
||||
|
||||
runCommandForced(
|
||||
"--client=NewRegistrar",
|
||||
"--admins=crr-admin2,crr-admin3",
|
||||
"--techs=crr-tech2,crr-tech3",
|
||||
"--superuser",
|
||||
"--force_in_pending_delete",
|
||||
"example.tld");
|
||||
eppVerifier.expectSuperuser().verifySent("domain_update_set_contacts.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_cantUpdateRegistryLockedDomainEvenAsSuperuser() {
|
||||
HostResource host = persistActiveHost("ns1.zdns.google");
|
||||
@@ -356,7 +390,30 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||
"example.tld"));
|
||||
assertThat(e)
|
||||
.hasMessageThat()
|
||||
.containsMatch("The domain 'example.tld' has status SERVER_UPDATE_PROHIBITED");
|
||||
.contains("The domain 'example.tld' has status SERVER_UPDATE_PROHIBITED.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_cantUpdatePendingDeleteDomainEvenAsSuperuser_withoutPassingOverrideFlag() {
|
||||
HostResource host = persistActiveHost("ns1.zdns.google");
|
||||
ImmutableSet<VKey<HostResource>> nameservers = ImmutableSet.of(host.createVKey());
|
||||
persistResource(
|
||||
newDomainBase("example.tld")
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(PENDING_DELETE))
|
||||
.setNameservers(nameservers)
|
||||
.build());
|
||||
|
||||
Exception e =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandForced(
|
||||
"--client=NewRegistrar",
|
||||
"--statuses=clientRenewProhibited,serverHold",
|
||||
"--superuser",
|
||||
"example.tld"));
|
||||
assertThat(e).hasMessageThat().contains("The domain 'example.tld' has status PENDING_DELETE.");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.model.registrar.Registrar.Type;
|
||||
@@ -265,9 +266,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
assertThat(registrar.getClientCertificate()).isEmpty();
|
||||
assertThat(registrar.getClientCertificateHash()).isEmpty();
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||
assertThat(thrown.getMessage())
|
||||
.isEqualTo(
|
||||
@@ -282,9 +283,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
assertThat(registrar.getClientCertificate()).isEmpty();
|
||||
assertThat(registrar.getClientCertificateHash()).isEmpty();
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||
assertThat(thrown.getMessage())
|
||||
.isEqualTo(
|
||||
@@ -298,9 +299,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||
assertThat(thrown.getMessage())
|
||||
@@ -315,9 +316,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
|
||||
IllegalArgumentException thrown =
|
||||
InsecureCertificateException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
InsecureCertificateException.class,
|
||||
() ->
|
||||
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||
assertThat(thrown.getMessage())
|
||||
|
||||
@@ -20,14 +20,17 @@ import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.TransportCredentials.BadRegistrarPasswordException;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
import google.registry.testing.CertificateSamples;
|
||||
@@ -42,7 +45,7 @@ import org.junit.jupiter.api.Test;
|
||||
class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginCredentialsCommand> {
|
||||
|
||||
private static final String PASSWORD = "foo-BAR2";
|
||||
private static final String CERT_HASH = CertificateSamples.SAMPLE_CERT_HASH;
|
||||
private static final String CERT_HASH = CertificateSamples.SAMPLE_CERT3_HASH;
|
||||
private static final String CLIENT_IP = "1.2.3.4";
|
||||
|
||||
@BeforeEach
|
||||
@@ -52,11 +55,18 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setPassword(PASSWORD)
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
|
||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, DateTime.now(UTC))
|
||||
.setIpAddressAllowList(ImmutableList.of(new CidrAddressBlock(CLIENT_IP)))
|
||||
.setState(ACTIVE)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
command.certificateChecker =
|
||||
new CertificateChecker(
|
||||
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||
30,
|
||||
2048,
|
||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||
fakeClock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +74,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
|
||||
runCommand(
|
||||
"--client=NewRegistrar",
|
||||
"--password=" + PASSWORD,
|
||||
"--cert_hash=" + CERT_HASH,
|
||||
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
|
||||
"--ip_address=" + CLIENT_IP);
|
||||
}
|
||||
|
||||
@@ -83,7 +93,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
|
||||
runCommand(
|
||||
"--client=NewRegistrar",
|
||||
"--password=" + PASSWORD,
|
||||
"--cert_hash=" + CERT_HASH,
|
||||
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
|
||||
"--ip_address=" + CLIENT_IP));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
@@ -91,7 +101,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_loginWithBadPassword() {
|
||||
void testFailure_loginWithBadPassword() throws Exception {
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadRegistrarPasswordException.class,
|
||||
@@ -99,7 +109,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
|
||||
runCommand(
|
||||
"--client=NewRegistrar",
|
||||
"--password=" + new StringBuilder(PASSWORD).reverse(),
|
||||
"--cert_hash=" + CERT_HASH,
|
||||
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
|
||||
"--ip_address=" + CLIENT_IP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
+9
-7
@@ -37,15 +37,17 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import google.registry.testing.DeterministicStringGenerator;
|
||||
import google.registry.testing.DualDatabaseTest;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.tools.CommandTestCase;
|
||||
import google.registry.util.StringGenerator.Alphabets;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link BackfillRegistryLocksCommand}. */
|
||||
@DualDatabaseTest
|
||||
class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryLocksCommand> {
|
||||
|
||||
@BeforeEach
|
||||
@@ -57,7 +59,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
command.stringGenerator = new DeterministicStringGenerator(Alphabets.BASE_58);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testSimpleBackfill() throws Exception {
|
||||
DomainBase domain = persistLockedDomain("example.tld");
|
||||
Truth8.assertThat(getMostRecentRegistryLockByRepoId(domain.getRepoId())).isEmpty();
|
||||
@@ -69,7 +71,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
Truth8.assertThat(lockOptional.get().getLockCompletionTimestamp()).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testBackfill_onlyLockedDomains() throws Exception {
|
||||
DomainBase neverLockedDomain = persistActiveDomain("neverlocked.tld");
|
||||
DomainBase previouslyLockedDomain = persistLockedDomain("unlocked.tld");
|
||||
@@ -89,7 +91,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
assertThat(Iterables.getOnlyElement(locks).getDomainName()).isEqualTo("locked.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testBackfill_skipsDeletedDomains() throws Exception {
|
||||
DomainBase domain = persistDeletedDomain("example.tld", fakeClock.nowUtc());
|
||||
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
||||
@@ -98,7 +100,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
Truth8.assertThat(getMostRecentRegistryLockByRepoId(domain.getRepoId())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testBackfill_skipsDomains_ifLockAlreadyExists() throws Exception {
|
||||
DomainBase domain = persistLockedDomain("example.tld");
|
||||
|
||||
@@ -123,7 +125,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
.isEqualTo(previousLock.getLockCompletionTimestamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testBackfill_usesUrsTime_ifExists() throws Exception {
|
||||
DateTime ursTime = fakeClock.nowUtc();
|
||||
DomainBase ursDomain = persistLockedDomain("urs.tld");
|
||||
@@ -151,7 +153,7 @@ class BackfillRegistryLocksCommandTest extends CommandTestCase<BackfillRegistryL
|
||||
assertThat(nonUrsLock.getLockCompletionTimestamp()).hasValue(fakeClock.nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestOfyAndSql
|
||||
void testFailure_mustProvideDomainRoids() {
|
||||
assertThat(assertThrows(IllegalArgumentException.class, this::runCommandForced))
|
||||
.hasMessageThat()
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
{
|
||||
"eventAction": "deletion",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "evilregistrar",
|
||||
"eventDate": "1999-07-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1997-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "unicoderegistrar",
|
||||
"eventDate": "1999-09-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
{
|
||||
"eventAction": "transfer",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "unicoderegistrar",
|
||||
"eventDate": "1999-12-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "unicoderegistrar",
|
||||
"eventDate": "1999-09-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
{
|
||||
"eventAction": "transfer",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "unicoderegistrar",
|
||||
"eventDate": "1999-12-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "unicoderegistrar",
|
||||
"eventDate": "2000-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"events": [
|
||||
{
|
||||
"eventAction": "registration",
|
||||
"eventActor": "foo",
|
||||
"eventActor": "TheRegistrar",
|
||||
"eventDate": "1999-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -261,11 +261,11 @@ td.section {
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">generated on</td>
|
||||
<td class="property_value">2021-01-14 16:15:22.842637</td>
|
||||
<td class="property_value">2021-01-21 00:11:27.19594</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">last flyway file</td>
|
||||
<td id="lastFlywayFile" class="property_value">V84__add_vkey_columns_in_billing_cancellation.sql</td>
|
||||
<td id="lastFlywayFile" class="property_value">V85__add_required_columns_in_transfer_data.sql</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -274,19 +274,19 @@ td.section {
|
||||
<svg viewbox="0.00 0.00 4221.44 2624.18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="erDiagram" style="overflow: hidden; width: 100%; height: 800px"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 2620.18)">
|
||||
<title>SchemaCrawler_Diagram</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-2620.18 4217.44,-2620.18 4217.44,4 -4,4" />
|
||||
<text text-anchor="start" x="3944.94" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
<text text-anchor="start" x="3952.94" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
generated by
|
||||
</text>
|
||||
<text text-anchor="start" x="4027.94" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
<text text-anchor="start" x="4035.94" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
SchemaCrawler 16.10.1
|
||||
</text>
|
||||
<text text-anchor="start" x="3943.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
<text text-anchor="start" x="3951.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
generated on
|
||||
</text>
|
||||
<text text-anchor="start" x="4027.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
2021-01-14 16:15:22.842637
|
||||
<text text-anchor="start" x="4035.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
|
||||
2021-01-21 00:11:27.19594
|
||||
</text>
|
||||
<polygon fill="none" stroke="#888888" points="3940.44,-4 3940.44,-44 4205.44,-44 4205.44,-4 3940.44,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<polygon fill="none" stroke="#888888" points="3948.44,-4 3948.44,-44 4205.44,-44 4205.44,-4 3948.44,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<g id="node1" class="node">
|
||||
<title>allocationtoken_a08ccbef</title>
|
||||
<polygon fill="#ebcef2" stroke="transparent" points="2538.5,-1071.68 2538.5,-1090.68 2691.5,-1090.68 2691.5,-1071.68 2538.5,-1071.68" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -82,3 +82,4 @@ V81__drop_spec11_fkeys.sql
|
||||
V82__add_columns_to_restore_symmetric_billing_vkey.sql
|
||||
V83__add_indexes_on_domainhost.sql
|
||||
V84__add_vkey_columns_in_billing_cancellation.sql
|
||||
V85__add_required_columns_in_transfer_data.sql
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Copyright 2021 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.
|
||||
|
||||
alter table "Contact"
|
||||
add column if not exists "transfer_history_entry_id" int8;
|
||||
alter table "Contact"
|
||||
add column if not exists "transfer_repo_id" text;
|
||||
alter table "Contact"
|
||||
rename column "transfer_gaining_poll_message_id"
|
||||
to "transfer_poll_message_id_1";
|
||||
alter table "Contact"
|
||||
rename column "transfer_losing_poll_message_id"
|
||||
to "transfer_poll_message_id_2";
|
||||
|
||||
alter table "ContactHistory"
|
||||
add column if not exists "transfer_history_entry_id" int8;
|
||||
alter table "ContactHistory"
|
||||
add column if not exists "transfer_repo_id" text;
|
||||
alter table "ContactHistory"
|
||||
rename column "transfer_gaining_poll_message_id"
|
||||
to "transfer_poll_message_id_1";
|
||||
alter table "ContactHistory"
|
||||
rename column "transfer_losing_poll_message_id"
|
||||
to "transfer_poll_message_id_2";
|
||||
|
||||
alter table "Domain"
|
||||
add column if not exists "transfer_history_entry_id" int8;
|
||||
alter table "Domain"
|
||||
add column if not exists "transfer_repo_id" text;
|
||||
alter table "Domain"
|
||||
rename column "transfer_gaining_poll_message_id"
|
||||
to "transfer_poll_message_id_1";
|
||||
alter table "Domain"
|
||||
rename column "transfer_losing_poll_message_id"
|
||||
to "transfer_poll_message_id_2";
|
||||
|
||||
alter table "DomainHistory"
|
||||
add column if not exists "transfer_history_entry_id" int8;
|
||||
alter table "DomainHistory"
|
||||
add column if not exists "transfer_repo_id" text;
|
||||
alter table "DomainHistory"
|
||||
rename column "transfer_gaining_poll_message_id"
|
||||
to "transfer_poll_message_id_1";
|
||||
alter table "DomainHistory"
|
||||
rename column "transfer_losing_poll_message_id"
|
||||
to "transfer_poll_message_id_2";
|
||||
@@ -140,8 +140,10 @@
|
||||
addr_local_org text,
|
||||
addr_local_type text,
|
||||
search_name text,
|
||||
transfer_gaining_poll_message_id int8,
|
||||
transfer_losing_poll_message_id int8,
|
||||
transfer_history_entry_id int8,
|
||||
transfer_poll_message_id_1 int8,
|
||||
transfer_poll_message_id_2 int8,
|
||||
transfer_repo_id text,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -201,8 +203,10 @@
|
||||
addr_local_org text,
|
||||
addr_local_type text,
|
||||
search_name text,
|
||||
transfer_gaining_poll_message_id int8,
|
||||
transfer_losing_poll_message_id int8,
|
||||
transfer_history_entry_id int8,
|
||||
transfer_poll_message_id_1 int8,
|
||||
transfer_poll_message_id_2 int8,
|
||||
transfer_repo_id text,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -284,8 +288,10 @@
|
||||
transfer_renew_period_unit text,
|
||||
transfer_renew_period_value int4,
|
||||
transfer_registration_expiration_time timestamptz,
|
||||
transfer_gaining_poll_message_id int8,
|
||||
transfer_losing_poll_message_id int8,
|
||||
transfer_history_entry_id int8,
|
||||
transfer_poll_message_id_1 int8,
|
||||
transfer_poll_message_id_2 int8,
|
||||
transfer_repo_id text,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -353,8 +359,10 @@
|
||||
transfer_renew_period_unit text,
|
||||
transfer_renew_period_value int4,
|
||||
transfer_registration_expiration_time timestamptz,
|
||||
transfer_gaining_poll_message_id int8,
|
||||
transfer_losing_poll_message_id int8,
|
||||
transfer_history_entry_id int8,
|
||||
transfer_poll_message_id_1 int8,
|
||||
transfer_poll_message_id_2 int8,
|
||||
transfer_repo_id text,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
|
||||
@@ -210,8 +210,8 @@ CREATE TABLE public."Contact" (
|
||||
search_name text,
|
||||
voice_phone_extension text,
|
||||
voice_phone_number text,
|
||||
transfer_gaining_poll_message_id bigint,
|
||||
transfer_losing_poll_message_id bigint,
|
||||
transfer_poll_message_id_1 bigint,
|
||||
transfer_poll_message_id_2 bigint,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -219,7 +219,9 @@ CREATE TABLE public."Contact" (
|
||||
transfer_pending_expiration_time timestamp with time zone,
|
||||
transfer_request_time timestamp with time zone,
|
||||
transfer_status text,
|
||||
update_timestamp timestamp with time zone
|
||||
update_timestamp timestamp with time zone,
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text
|
||||
);
|
||||
|
||||
|
||||
@@ -273,8 +275,8 @@ CREATE TABLE public."ContactHistory" (
|
||||
addr_local_org text,
|
||||
addr_local_type text,
|
||||
search_name text,
|
||||
transfer_gaining_poll_message_id bigint,
|
||||
transfer_losing_poll_message_id bigint,
|
||||
transfer_poll_message_id_1 bigint,
|
||||
transfer_poll_message_id_2 bigint,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -292,7 +294,9 @@ CREATE TABLE public."ContactHistory" (
|
||||
last_epp_update_time timestamp with time zone,
|
||||
statuses text[],
|
||||
contact_repo_id text NOT NULL,
|
||||
update_timestamp timestamp with time zone
|
||||
update_timestamp timestamp with time zone,
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text
|
||||
);
|
||||
|
||||
|
||||
@@ -351,8 +355,8 @@ CREATE TABLE public."Domain" (
|
||||
billing_contact text,
|
||||
registrant_contact text,
|
||||
tech_contact text,
|
||||
transfer_gaining_poll_message_id bigint,
|
||||
transfer_losing_poll_message_id bigint,
|
||||
transfer_poll_message_id_1 bigint,
|
||||
transfer_poll_message_id_2 bigint,
|
||||
transfer_billing_cancellation_id bigint,
|
||||
transfer_billing_event_id bigint,
|
||||
transfer_billing_recurrence_id bigint,
|
||||
@@ -377,7 +381,9 @@ CREATE TABLE public."Domain" (
|
||||
deletion_poll_message_history_id bigint,
|
||||
transfer_billing_recurrence_history_id bigint,
|
||||
transfer_autorenew_poll_message_history_id bigint,
|
||||
transfer_billing_event_history_id bigint
|
||||
transfer_billing_event_history_id bigint,
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text
|
||||
);
|
||||
|
||||
|
||||
@@ -438,8 +444,8 @@ CREATE TABLE public."DomainHistory" (
|
||||
transfer_renew_period_unit text,
|
||||
transfer_renew_period_value integer,
|
||||
transfer_registration_expiration_time timestamp with time zone,
|
||||
transfer_gaining_poll_message_id bigint,
|
||||
transfer_losing_poll_message_id bigint,
|
||||
transfer_poll_message_id_1 bigint,
|
||||
transfer_poll_message_id_2 bigint,
|
||||
transfer_client_txn_id text,
|
||||
transfer_server_txn_id text,
|
||||
transfer_gaining_registrar_id text,
|
||||
@@ -465,7 +471,9 @@ CREATE TABLE public."DomainHistory" (
|
||||
deletion_poll_message_history_id bigint,
|
||||
transfer_billing_recurrence_history_id bigint,
|
||||
transfer_autorenew_poll_message_history_id bigint,
|
||||
transfer_billing_event_history_id bigint
|
||||
transfer_billing_event_history_id bigint,
|
||||
transfer_history_entry_id bigint,
|
||||
transfer_repo_id text
|
||||
);
|
||||
|
||||
|
||||
|
||||
Generated
+254
-293
@@ -4,12 +4,6 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -42,12 +36,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
@@ -67,15 +60,6 @@
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
@@ -95,30 +79,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=",
|
||||
"dev": true
|
||||
},
|
||||
"base64id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
|
||||
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"dev": true
|
||||
},
|
||||
"better-assert": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsite": "1.0.0"
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"blob": {
|
||||
@@ -182,12 +157,6 @@
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
|
||||
"dev": true
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
@@ -195,19 +164,19 @@
|
||||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
|
||||
"integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.1.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.3.0"
|
||||
"readdirp": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
@@ -249,9 +218,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"component-inherit": {
|
||||
@@ -297,9 +266,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
@@ -315,9 +284,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"date-format": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
|
||||
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
|
||||
"integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
@@ -378,45 +347,51 @@
|
||||
"dev": true
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
|
||||
"integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
|
||||
"integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "1.0.0",
|
||||
"cookie": "0.3.1",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-parser": "~2.1.0",
|
||||
"ws": "~3.3.1"
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"ws": "~7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
|
||||
"integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
|
||||
"integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-parser": "~2.1.1",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"ws": "~3.3.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"yeast": "0.1.2"
|
||||
},
|
||||
@@ -433,14 +408,14 @@
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
|
||||
"integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
|
||||
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
@@ -473,9 +448,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
||||
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true
|
||||
},
|
||||
"extend": {
|
||||
@@ -546,47 +521,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
|
||||
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs-access": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
|
||||
"integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"null-check": "^1.0.0"
|
||||
}
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
|
||||
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
@@ -598,9 +544,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
|
||||
"integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
|
||||
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -639,9 +585,9 @@
|
||||
"integrity": "sha512-B+Cdh2c3BbvSIONufK3yU/yKwhm7vxaqrAvxIBo3JmUAhA3WQPRSculbJPKC4ca7b/pjlsIR76KDpVqVrJd4dg=="
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
||||
"dev": true
|
||||
},
|
||||
"has-binary2": {
|
||||
@@ -811,44 +757,59 @@
|
||||
}
|
||||
},
|
||||
"karma": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/karma/-/karma-5.0.1.tgz",
|
||||
"integrity": "sha512-xrDGtZ0mykEQjx1BUHOP1ITi39MDsCGocmSvLJWHxUQpxuKwxk3ZUrC6HI2VWh1plLC6+7cA3B19m12yzO/FRw==",
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/karma/-/karma-5.2.3.tgz",
|
||||
"integrity": "sha512-tHdyFADhVVPBorIKCX8A37iLHxc6RBRphkSoQ+MLKdAtFn1k97tD8WUGi1KlEtDZKL3hui0qhsY9HXUfSNDYPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"body-parser": "^1.16.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"braces": "^3.0.2",
|
||||
"chokidar": "^3.0.0",
|
||||
"colors": "^1.1.0",
|
||||
"connect": "^3.6.0",
|
||||
"chokidar": "^3.4.2",
|
||||
"colors": "^1.4.0",
|
||||
"connect": "^3.7.0",
|
||||
"di": "^0.0.1",
|
||||
"dom-serialize": "^2.2.0",
|
||||
"flatted": "^2.0.0",
|
||||
"glob": "^7.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"http-proxy": "^1.13.0",
|
||||
"isbinaryfile": "^4.0.2",
|
||||
"lodash": "^4.17.14",
|
||||
"log4js": "^4.0.0",
|
||||
"mime": "^2.3.1",
|
||||
"minimatch": "^3.0.2",
|
||||
"qjobs": "^1.1.4",
|
||||
"range-parser": "^1.2.0",
|
||||
"rimraf": "^2.6.0",
|
||||
"socket.io": "2.1.1",
|
||||
"dom-serialize": "^2.2.1",
|
||||
"glob": "^7.1.6",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"http-proxy": "^1.18.1",
|
||||
"isbinaryfile": "^4.0.6",
|
||||
"lodash": "^4.17.19",
|
||||
"log4js": "^6.2.1",
|
||||
"mime": "^2.4.5",
|
||||
"minimatch": "^3.0.4",
|
||||
"qjobs": "^1.2.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io": "^2.3.0",
|
||||
"source-map": "^0.6.1",
|
||||
"tmp": "0.0.33",
|
||||
"ua-parser-js": "0.7.21",
|
||||
"tmp": "0.2.1",
|
||||
"ua-parser-js": "0.7.22",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz",
|
||||
"integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"karma-chrome-launcher": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz",
|
||||
"integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz",
|
||||
"integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs-access": "^1.0.0",
|
||||
"which": "^1.2.1"
|
||||
}
|
||||
},
|
||||
@@ -877,31 +838,31 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"dev": true
|
||||
},
|
||||
"log4js": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
|
||||
"integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz",
|
||||
"integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"date-format": "^2.0.0",
|
||||
"date-format": "^3.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"flatted": "^2.0.0",
|
||||
"flatted": "^2.0.1",
|
||||
"rfdc": "^1.1.4",
|
||||
"streamroller": "^1.0.6"
|
||||
"streamroller": "^2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
@@ -925,18 +886,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
|
||||
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
|
||||
"version": "1.45.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
|
||||
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.26",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
|
||||
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
|
||||
"version": "2.1.28",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
|
||||
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.43.0"
|
||||
"mime-db": "1.45.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
@@ -983,18 +944,6 @@
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"null-check": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
|
||||
"integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=",
|
||||
"dev": true
|
||||
},
|
||||
"object-component": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
|
||||
"dev": true
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
@@ -1013,12 +962,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
@@ -1044,22 +987,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
|
||||
"dev": true
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
|
||||
"dev": true
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
@@ -1211,12 +1148,12 @@
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
|
||||
"integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.0.7"
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
@@ -1238,9 +1175,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"rfdc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
|
||||
"integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz",
|
||||
"integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
@@ -1252,12 +1189,6 @@
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -1277,27 +1208,33 @@
|
||||
"dev": true
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
|
||||
"integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
|
||||
"integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "~3.1.0",
|
||||
"engine.io": "~3.2.0",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"socket.io-adapter": "~1.1.0",
|
||||
"socket.io-client": "2.1.1",
|
||||
"socket.io-parser": "~3.2.0"
|
||||
"socket.io-client": "2.4.0",
|
||||
"socket.io-parser": "~3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1308,24 +1245,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
|
||||
"integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
|
||||
"integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"backo2": "1.0.2",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "1.2.1",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-client": "~3.2.0",
|
||||
"engine.io-client": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"object-component": "0.0.3",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"socket.io-parser": "~3.2.0",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -1337,28 +1271,51 @@
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
|
||||
"integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
|
||||
"integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
|
||||
"integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~3.1.0",
|
||||
"debug": "~4.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1375,25 +1332,29 @@
|
||||
"dev": true
|
||||
},
|
||||
"streamroller": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
|
||||
"integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
|
||||
"integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.2",
|
||||
"date-format": "^2.0.0",
|
||||
"debug": "^3.2.6",
|
||||
"fs-extra": "^7.0.1",
|
||||
"lodash": "^4.17.14"
|
||||
"date-format": "^2.1.0",
|
||||
"debug": "^4.1.1",
|
||||
"fs-extra": "^8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-format": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
|
||||
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
@@ -1442,12 +1403,23 @@
|
||||
}
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
|
||||
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"to-array": {
|
||||
@@ -1488,15 +1460,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.21",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
||||
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ultron": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
|
||||
"version": "0.7.22",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==",
|
||||
"dev": true
|
||||
},
|
||||
"universalify": {
|
||||
@@ -1562,15 +1528,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
|
||||
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0",
|
||||
"safe-buffer": "~5.1.0",
|
||||
"ultron": "~1.1.0"
|
||||
}
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
|
||||
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
@@ -1579,15 +1540,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
|
||||
"integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
@@ -1600,13 +1561,13 @@
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.1"
|
||||
"yargs-parser": "^18.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz",
|
||||
"integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==",
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-core": "3.4.0",
|
||||
"karma": "^5.0.1",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma": "^5.2.3",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-closure": "0.1.3",
|
||||
"karma-jasmine": "2.0.1",
|
||||
"puppeteer": "2.0.0"
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.proxy.TestUtils.assertHttpRequestEquivalent;
|
||||
import static google.registry.proxy.TestUtils.makeEppHttpResponse;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static google.registry.util.X509Utils.loadCertificate;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -47,6 +48,7 @@ import io.netty.util.concurrent.Promise;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -239,6 +241,23 @@ class EppServiceHandlerTest {
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_requestContainsEncodedCertificate() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
// First inbound message is hello.
|
||||
channel.readInbound();
|
||||
String content = "<epp>stuff</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
|
||||
FullHttpRequest request = channel.readInbound();
|
||||
assertThat(request).isEqualTo(makeEppHttpRequestWithCertificate(content));
|
||||
String encodedCert = request.headers().get("X-SSL-Full-Certificate");
|
||||
assertThat(encodedCert).isNotEqualTo(SAMPLE_CERT);
|
||||
X509Certificate decodedCert =
|
||||
loadCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(encodedCert)));
|
||||
X509Certificate pemCert = loadCertificate(SAMPLE_CERT);
|
||||
assertThat(decodedCert).isEqualTo(pemCert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sendCertificateOnlyBeforeLogin() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.CRLReason;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
@@ -39,6 +40,7 @@ import java.security.cert.CertificateRevokedException;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509CRLEntry;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
@@ -177,5 +179,20 @@ public final class X509Utils {
|
||||
newCrl.verify(rootCert.getPublicKey());
|
||||
}
|
||||
|
||||
/** Constructs an X.509 certificate from a PEM string and encodes it. */
|
||||
public static String encodeX509CertificateFromPemString(String certificateString)
|
||||
throws CertificateException {
|
||||
return encodeX509Certificate(loadCertificate(certificateString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an X.509 certificate in the same form that the proxy encodes a certificate before
|
||||
* passing it via an HTTP header.
|
||||
*/
|
||||
public static String encodeX509Certificate(X509Certificate certificate)
|
||||
throws CertificateEncodingException {
|
||||
return Base64.getEncoder().encodeToString(certificate.getEncoded());
|
||||
}
|
||||
|
||||
private X509Utils() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user