mirror of
https://github.com/google/nomulus
synced 2026-02-06 04:51:10 +00:00
Remove WHOIS classes and configuration (#2859)
This is steps one and two of b/454947209 We already haven't been serving WHOIS for a while, so there's no point in keeping the old code around. This can simplify some code paths in the future (like, certain foreign-key-loads that are only used in WHOIS queries).
This commit is contained in:
@@ -55,9 +55,6 @@ def dockerIncompatibleTestPatterns = [
|
||||
// objects retained by frameworks.
|
||||
// TODO(weiminyu): identify cause and fix offending tests.
|
||||
def fragileTestPatterns = [
|
||||
// Changes cache timeouts and for some reason appears to have contention
|
||||
// with other tests.
|
||||
"google/registry/whois/WhoisCommandFactoryTest.*",
|
||||
// Breaks random other tests when running with standardTests.
|
||||
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
|
||||
// Currently changes a global configuration parameter that for some reason
|
||||
|
||||
@@ -976,17 +976,6 @@ public final class RegistryConfig {
|
||||
return config.misc.transientFailureRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of time public HTTP proxies are permitted to cache our WHOIS responses.
|
||||
*
|
||||
* @see google.registry.whois.WhoisHttpAction
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisHttpExpires")
|
||||
public static Duration provideWhoisHttpExpires() {
|
||||
return Duration.standardDays(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of results to return for an RDAP search query
|
||||
*
|
||||
@@ -998,39 +987,6 @@ public final class RegistryConfig {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redaction text for email address in WHOIS
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisRedactedEmailText")
|
||||
public static String provideWhoisRedactedEmailText(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisRedactedEmailText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disclaimer displayed at the end of WHOIS query results.
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("whoisDisclaimer")
|
||||
public static String provideWhoisDisclaimer(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisDisclaimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message template for whois response when queried domain is blocked by BSA.
|
||||
*
|
||||
* @see google.registry.whois.WhoisResponse
|
||||
*/
|
||||
@Provides
|
||||
@Config("domainBlockedByBsaTemplate")
|
||||
public static String provideDomainBlockedByBsaTemplate(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.domainBlockedByBsaTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be
|
||||
* adjusted by contacting Cloud Support.
|
||||
@@ -1105,12 +1061,6 @@ public final class RegistryConfig {
|
||||
return config.registryPolicy.customLogicFactoryClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("whoisCommandFactoryClass")
|
||||
public static String provideWhoisCommandFactoryClass(RegistryConfigSettings config) {
|
||||
return config.registryPolicy.whoisCommandFactoryClass;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsCountQueryCoordinatorClass")
|
||||
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {
|
||||
|
||||
@@ -90,7 +90,6 @@ public class RegistryConfigSettings {
|
||||
public String contactAndHostRoidSuffix;
|
||||
public String productName;
|
||||
public String customLogicFactoryClass;
|
||||
public String whoisCommandFactoryClass;
|
||||
public String dnsCountQueryCoordinatorClass;
|
||||
public int contactAutomaticTransferDays;
|
||||
public String greetingServerId;
|
||||
@@ -102,9 +101,6 @@ public class RegistryConfigSettings {
|
||||
public String registryAdminClientId;
|
||||
public String premiumTermsExportDisclaimer;
|
||||
public String reservedTermsExportDisclaimer;
|
||||
public String whoisRedactedEmailText;
|
||||
public String whoisDisclaimer;
|
||||
public String domainBlockedByBsaTemplate;
|
||||
public String rdapTos;
|
||||
public String rdapTosStaticUrl;
|
||||
public String registryName;
|
||||
|
||||
@@ -65,10 +65,6 @@ registryPolicy:
|
||||
# See flows/custom/CustomLogicFactory.java
|
||||
customLogicFactoryClass: google.registry.flows.custom.CustomLogicFactory
|
||||
|
||||
# WHOIS command factory fully-qualified class name.
|
||||
# See whois/WhoisCommandFactory.java
|
||||
whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory
|
||||
|
||||
# Custom logic class for handling DNS query count reporting for ICANN.
|
||||
# See reporting/icann/DnsCountQueryCoordinator.java
|
||||
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
|
||||
@@ -114,31 +110,6 @@ registryPolicy:
|
||||
to publish. This list is subject to change. The most up-to-date source
|
||||
is always the registry itself, by sending domain check EPP commands.
|
||||
|
||||
# Redaction text for email address in WHOIS
|
||||
whoisRedactedEmailText: |
|
||||
Please query the WHOIS server of the owning registrar identified in this
|
||||
output for information on how to contact the Registrant, Admin, or Tech
|
||||
contact of the queried domain name.
|
||||
|
||||
# Disclaimer at the top of WHOIS results.
|
||||
whoisDisclaimer: |
|
||||
WHOIS information is provided by the registry solely for query-based,
|
||||
informational purposes. Any information provided is "as is" without any
|
||||
guarantee of accuracy. You may not use such information to (a) allow,
|
||||
enable, or otherwise support the transmission of mass unsolicited,
|
||||
commercial advertising or solicitations; (b) enable high volume, automated,
|
||||
electronic processes that access the registry's systems or any
|
||||
ICANN-Accredited Registrar, except as reasonably necessary to register
|
||||
domain names or modify existing registrations; or (c) engage in or support
|
||||
unlawful behavior. We reserve the right to restrict or deny your access to
|
||||
the WHOIS database, and may modify these terms at any time.
|
||||
|
||||
# BSA blocked domain name template.
|
||||
domainBlockedByBsaTemplate: |
|
||||
Domain Name: %s
|
||||
>>> This name is not available for registration.
|
||||
>>> This name has been blocked by a GlobalBlock service.
|
||||
|
||||
# RDAP Terms of Service text displayed at the /rdap/help/tos endpoint.
|
||||
rdapTos: >
|
||||
By querying our Domain Database as part of the RDAP pilot program (RDAP
|
||||
|
||||
@@ -57,11 +57,6 @@
|
||||
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
|
||||
<url-pattern>/assets/sources/*</url-pattern>
|
||||
|
||||
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
|
||||
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
|
||||
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
|
||||
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
|
||||
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>admin</role-name>
|
||||
|
||||
@@ -130,7 +130,7 @@ public final class EppResourceUtils {
|
||||
* past.
|
||||
*
|
||||
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
||||
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
|
||||
* only be used when it's OK if the data is potentially being out of date, e.g. RDAP.
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param foreignKey id to match
|
||||
@@ -181,7 +181,7 @@ public final class EppResourceUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
// When setting status values based on a time, choose the greater of "now" and the resource's
|
||||
// UpdateAutoTimestamp. For non-mutating uses (info, whois, etc.), this is equivalent to rolling
|
||||
// UpdateAutoTimestamp. For non-mutating uses (info, RDAP, etc.), this is equivalent to rolling
|
||||
// "now" forward to at least the last update on the resource, so that a read right after a write
|
||||
// doesn't appear stale. For mutating flows, if we had to roll now forward then the flow will
|
||||
// fail when it tries to save anything, since "now" is needed to be > the last update time for
|
||||
|
||||
@@ -492,7 +492,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
|
||||
return LIVE_STATES.contains(state);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if registrar should be visible in WHOIS results. */
|
||||
/** Returns {@code true} if registrar should be visible in RDAP results. */
|
||||
public boolean isLiveAndPubliclyVisible() {
|
||||
return LIVE_STATES.contains(state) && PUBLICLY_VISIBLE_TYPES.contains(type);
|
||||
}
|
||||
|
||||
@@ -131,9 +131,6 @@ import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
|
||||
import google.registry.ui.server.console.settings.ContactAction;
|
||||
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
|
||||
import google.registry.ui.server.console.settings.SecurityAction;
|
||||
import google.registry.whois.WhoisAction;
|
||||
import google.registry.whois.WhoisHttpAction;
|
||||
import google.registry.whois.WhoisModule;
|
||||
|
||||
/** Dagger component with per-request lifetime. */
|
||||
@RequestScope
|
||||
@@ -161,8 +158,7 @@ import google.registry.whois.WhoisModule;
|
||||
Spec11Module.class,
|
||||
TmchModule.class,
|
||||
ToolsServerModule.class,
|
||||
WhiteboxModule.class,
|
||||
WhoisModule.class,
|
||||
WhiteboxModule.class
|
||||
})
|
||||
interface RequestComponent {
|
||||
FlowComponent.Builder flowComponentBuilder();
|
||||
@@ -343,10 +339,6 @@ interface RequestComponent {
|
||||
|
||||
VerifyOteAction verifyOteAction();
|
||||
|
||||
WhoisAction whoisAction();
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
|
||||
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
|
||||
@@ -36,9 +36,6 @@ import google.registry.rdap.RdapNameserverSearchAction;
|
||||
import google.registry.request.RequestComponentBuilder;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.RequestScope;
|
||||
import google.registry.whois.WhoisAction;
|
||||
import google.registry.whois.WhoisHttpAction;
|
||||
import google.registry.whois.WhoisModule;
|
||||
|
||||
/** Dagger component with per-request lifetime for "pubapi" App Engine module. */
|
||||
@RequestScope
|
||||
@@ -49,8 +46,7 @@ import google.registry.whois.WhoisModule;
|
||||
EppTlsModule.class,
|
||||
RdapModule.class,
|
||||
RequestModule.class,
|
||||
WhiteboxModule.class,
|
||||
WhoisModule.class,
|
||||
WhiteboxModule.class
|
||||
})
|
||||
public interface PubApiRequestComponent {
|
||||
CheckApiAction checkApiAction();
|
||||
@@ -67,9 +63,6 @@ public interface PubApiRequestComponent {
|
||||
|
||||
ReadinessProbeActionPubApi readinessProbeActionPubApi();
|
||||
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
WhoisAction whoisAction();
|
||||
|
||||
@Subcomponent.Builder
|
||||
abstract class Builder implements RequestComponentBuilder<PubApiRequestComponent> {
|
||||
@Override public abstract Builder requestModule(RequestModule requestModule);
|
||||
|
||||
@@ -67,15 +67,13 @@ class RequestMetrics {
|
||||
private static String truncatePath(String path) {
|
||||
// We want to bucket RDAP requests by type to use less metric space,
|
||||
// e.g. "/rdap/domains" rather than "/rdap/domains/foo.tld"
|
||||
|
||||
if (path.startsWith("/rdap")) {
|
||||
List<String> splitPath = Splitter.on("/").omitEmptyStrings().splitToList(path);
|
||||
return Streams.stream(Iterables.limit(splitPath, 2))
|
||||
.collect(Collectors.joining("/", "/", "/"));
|
||||
// Similarly, we put all web WHOIS requests under the same path because otherwise its
|
||||
// cardinality is unbound, and it is possible to generate a huge amount of metrics with all
|
||||
// the different paths.
|
||||
} else if (path.startsWith("/whois")) {
|
||||
// Though we don't serve WHOIS requests anymore, bucket any non-served WHOIS requests together
|
||||
// to avoid an unbound cardinality
|
||||
return "/whois";
|
||||
}
|
||||
return path;
|
||||
|
||||
@@ -23,7 +23,7 @@ public enum Auth {
|
||||
/**
|
||||
* Allows anyone to access.
|
||||
*
|
||||
* <p>This is used for public HTML endpoints like RDAP, the check API, and web WHOIS.
|
||||
* <p>This is used for public HTML endpoints like RDAP and the check API.
|
||||
*/
|
||||
AUTH_PUBLIC(AuthLevel.NONE, UserPolicy.PUBLIC),
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ public final class RegistryTool {
|
||||
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
|
||||
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
|
||||
.put("verify_ote", VerifyOteCommand.class)
|
||||
.put("whois_query", WhoisQueryCommand.class)
|
||||
.build();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
@@ -37,7 +37,6 @@ import google.registry.request.Modules.GsonModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
import google.registry.tools.AuthModule.LocalCredentialModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
import google.registry.whois.NonCachingWhoisModule;
|
||||
import jakarta.inject.Singleton;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -67,8 +66,7 @@ import javax.annotation.Nullable;
|
||||
RequestFactoryModule.class,
|
||||
SecretManagerModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UtilsModule.class,
|
||||
NonCachingWhoisModule.class
|
||||
UtilsModule.class
|
||||
})
|
||||
interface RegistryToolComponent {
|
||||
void inject(AckPollMessagesCommand command);
|
||||
@@ -163,8 +161,6 @@ interface RegistryToolComponent {
|
||||
|
||||
void inject(ValidateLoginCredentialsCommand command);
|
||||
|
||||
void inject(WhoisQueryCommand command);
|
||||
|
||||
ServiceConnection serviceConnection();
|
||||
|
||||
@LocalCredentialJson
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import google.registry.whois.Whois;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/** Command to execute a WHOIS query. */
|
||||
@Parameters(separators = " =", commandDescription = "Manually perform a WHOIS query")
|
||||
final class WhoisQueryCommand implements Command {
|
||||
|
||||
@Parameter(
|
||||
description = "WHOIS query string",
|
||||
required = true)
|
||||
private List<String> mainParameters;
|
||||
|
||||
@Parameter(
|
||||
names = "--unicode",
|
||||
description = "When set, output will be Unicode")
|
||||
private boolean unicode;
|
||||
|
||||
@Parameter(names = "--full_output", description = "When set, the full output will be displayed")
|
||||
private boolean fullOutput;
|
||||
|
||||
@Inject
|
||||
Whois whois;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println(whois.lookup(Joiner.on(' ').join(mainParameters), unicode, fullOutput));
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server;
|
||||
|
||||
import static com.google.common.collect.Range.atLeast;
|
||||
import static com.google.common.collect.Range.atMost;
|
||||
import static com.google.common.collect.Range.closed;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.re2j.Pattern;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.ui.forms.FormField;
|
||||
import google.registry.ui.forms.FormFieldException;
|
||||
import google.registry.ui.forms.FormFields;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.X509Utils;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Form fields for validating input for the {@code Registrar} class. */
|
||||
public final class RegistrarFormFields {
|
||||
|
||||
public static final Pattern BASE64_PATTERN = Pattern.compile("[+/a-zA-Z0-9]*");
|
||||
public static final Pattern ASCII_PATTERN = Pattern.compile("[[:ascii:]]*");
|
||||
public static final String ASCII_ERROR = "Please only use ASCII-US characters.";
|
||||
|
||||
public static final FormField<String, String> NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("registrarName")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, DateTime> LAST_UPDATE_TIME =
|
||||
FormFields.LABEL
|
||||
.asBuilderNamed("lastUpdateTime")
|
||||
.transform(DateTime.class, RegistrarFormFields::parseDateTime)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_REQUIRED =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_OPTIONAL =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> ICANN_REFERRAL_EMAIL_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("icannReferralEmail")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder()
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, Set<String>> ALLOWED_TLDS_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("allowedTlds")
|
||||
.asSet()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> WHOIS_SERVER_FIELD =
|
||||
FormFields.LABEL.asBuilderNamed("whoisServer")
|
||||
.transform(RegistrarFormFields::parseHostname)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_HASH_FIELD =
|
||||
FormField.named("clientCertificateHash")
|
||||
.emptyToNull()
|
||||
.matches(BASE64_PATTERN, "Field must contain a base64 value.")
|
||||
.range(closed(1, 255))
|
||||
.build();
|
||||
|
||||
private static final FormField<String, String> X509_PEM_CERTIFICATE =
|
||||
FormField.named("certificate")
|
||||
.emptyToNull()
|
||||
.transform(
|
||||
input -> {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
X509Utils.loadCertificate(input);
|
||||
} catch (CertificateParsingException e) {
|
||||
throw new FormFieldException("Invalid X.509 PEM certificate");
|
||||
}
|
||||
return input;
|
||||
})
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("clientCertificate").build();
|
||||
|
||||
public static final FormField<String, String> FAILOVER_CLIENT_CERTIFICATE_FIELD =
|
||||
X509_PEM_CERTIFICATE.asBuilderNamed("failoverClientCertificate").build();
|
||||
|
||||
public static final FormField<Long, Long> BILLING_IDENTIFIER_FIELD =
|
||||
FormField.named("billingIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<Long, Long> IANA_IDENTIFIER_FIELD =
|
||||
FormField.named("ianaIdentifier", Long.class)
|
||||
.range(atLeast(1))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> URL_FIELD =
|
||||
FormFields.MIN_TOKEN.asBuilderNamed("url")
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<CidrAddressBlock>> IP_ADDRESS_ALLOW_LIST_FIELD =
|
||||
FormField.named("ipAddressAllowList")
|
||||
.emptyToNull()
|
||||
.transform(CidrAddressBlock.class, RegistrarFormFields::parseCidr)
|
||||
.asList()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PASSWORD_FIELD =
|
||||
FormFields.PASSWORD.asBuilderNamed("password")
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> PHONE_PASSCODE_FIELD =
|
||||
FormField.named("phonePasscode")
|
||||
.emptyToNull()
|
||||
.matches(Registrar.PHONE_PASSCODE_PATTERN)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_NAME_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("name")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("emailAddress")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> REGISTRY_LOCK_EMAIL_ADDRESS_FIELD =
|
||||
FormFields.EMAIL.asBuilderNamed("registryLockEmailAddress").build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
|
||||
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD =
|
||||
FormField.named("visibleInWhoisAsTech", Boolean.class)
|
||||
.build();
|
||||
|
||||
public static final FormField<Boolean, Boolean>
|
||||
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD =
|
||||
FormField.named("visibleInDomainWhoisAsAbuse", Boolean.class).build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_PHONE_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilder().build();
|
||||
|
||||
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
|
||||
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
|
||||
|
||||
public static final FormField<String, Set<RegistrarPoc.Type>> CONTACT_TYPES =
|
||||
FormField.named("types")
|
||||
.uppercased()
|
||||
.asEnum(RegistrarPoc.Type.class)
|
||||
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
|
||||
.build();
|
||||
|
||||
public static final FormField<List<Map<String, ?>>, List<Map<String, ?>>> CONTACTS_AS_MAPS =
|
||||
FormField.mapNamed("contacts").asList().build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> I18N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<List<String>, List<String>> L10N_STREET_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
|
||||
.range(closed(1, 255))
|
||||
.asList()
|
||||
.range(closed(1, 3))
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_CITY_FIELD =
|
||||
FormFields.NAME.asBuilderNamed("city")
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_STATE_FIELD =
|
||||
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
|
||||
.range(atMost(255))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> I18N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.matches(ASCII_PATTERN, ASCII_ERROR)
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> L10N_ZIP_FIELD =
|
||||
FormFields.XS_TOKEN.asBuilderNamed("zip")
|
||||
.range(atMost(16))
|
||||
.build();
|
||||
|
||||
public static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
FormFields.COUNTRY_CODE.asBuilder()
|
||||
.required()
|
||||
.build();
|
||||
|
||||
public static final FormField<Map<String, ?>, RegistrarAddress> L10N_ADDRESS_FIELD =
|
||||
FormField.mapNamed("localizedAddress")
|
||||
.transform(
|
||||
RegistrarAddress.class,
|
||||
args ->
|
||||
toNewAddress(
|
||||
args, L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD))
|
||||
.build();
|
||||
|
||||
@Nullable
|
||||
private static RegistrarAddress toNewAddress(
|
||||
@Nullable Map<String, ?> args,
|
||||
final FormField<List<String>, List<String>> streetField,
|
||||
final FormField<String, String> cityField,
|
||||
final FormField<String, String> stateField,
|
||||
final FormField<String, String> zipField) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
RegistrarAddress.Builder builder = new RegistrarAddress.Builder();
|
||||
String countryCode = COUNTRY_CODE_FIELD.extractUntyped(args).get();
|
||||
builder.setCountryCode(countryCode);
|
||||
streetField
|
||||
.extractUntyped(args)
|
||||
.ifPresent(streets -> builder.setStreet(ImmutableList.copyOf(streets)));
|
||||
cityField.extractUntyped(args).ifPresent(builder::setCity);
|
||||
Optional<String> stateFieldValue = stateField.extractUntyped(args);
|
||||
if (stateFieldValue.isPresent()) {
|
||||
String state = stateFieldValue.get();
|
||||
if ("US".equals(countryCode)) {
|
||||
state = Ascii.toUpperCase(state);
|
||||
if (!StateCode.US_MAP.containsKey(state)) {
|
||||
throw new FormFieldException(stateField, "Unknown US state code.");
|
||||
}
|
||||
}
|
||||
builder.setState(state);
|
||||
}
|
||||
zipField.extractUntyped(args).ifPresent(builder::setZip);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static CidrAddressBlock parseCidr(String input) {
|
||||
try {
|
||||
return input != null ? CidrAddressBlock.create(input) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException("Not a valid CIDR notation IP-address block.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String parseHostname(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (!InternetDomainName.isValid(input)) {
|
||||
throw new FormFieldException("Not a valid hostname.");
|
||||
}
|
||||
return canonicalizeHostname(input);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static DateTime parseDateTime(@Nullable String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateTime.parse(input);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormFieldException("Not a valid ISO date-time string.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableList<RegistrarPoc.Builder> getRegistrarContactBuilders(
|
||||
ImmutableSet<RegistrarPoc> existingContacts, @Nullable Map<String, ?> args) {
|
||||
if (args == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
Optional<List<Map<String, ?>>> contactsAsMaps = CONTACTS_AS_MAPS.extractUntyped(args);
|
||||
if (contactsAsMaps.isEmpty()) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
ImmutableList.Builder<RegistrarPoc.Builder> result = new ImmutableList.Builder<>();
|
||||
for (Map<String, ?> contactAsMap : contactsAsMaps.get()) {
|
||||
String emailAddress =
|
||||
CONTACT_EMAIL_ADDRESS_FIELD
|
||||
.extractUntyped(contactAsMap)
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Contacts from UI must have email addresses"));
|
||||
// Start with a new builder if the contact didn't previously exist
|
||||
RegistrarPoc.Builder contactBuilder =
|
||||
existingContacts.stream()
|
||||
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
|
||||
.findFirst()
|
||||
.map(RegistrarPoc::asBuilder)
|
||||
.orElse(new RegistrarPoc.Builder());
|
||||
applyRegistrarContactArgs(contactBuilder, contactAsMap);
|
||||
result.add(contactBuilder);
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
|
||||
private static void applyRegistrarContactArgs(RegistrarPoc.Builder builder, Map<String, ?> args) {
|
||||
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setVisibleInWhoisAsAdmin(
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setVisibleInWhoisAsTech(
|
||||
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setVisibleInDomainWhoisAsAbuse(
|
||||
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD.extractUntyped(args).orElse(false));
|
||||
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
|
||||
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,10 @@ import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
|
||||
* Console action for editing fields on a registrar that are visible in RDAP.
|
||||
*
|
||||
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in
|
||||
* WHOIS/RDAP and don't have any other obvious means of edit.
|
||||
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in RDAP
|
||||
* and don't have any other obvious means of edit.
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// 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.whois;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Dagger base module for the whois package.
|
||||
*
|
||||
* <p>Provides whois objects common to both the normal ({@link WhoisModule}) and non-caching ({@link
|
||||
* NonCachingWhoisModule}) implementations.
|
||||
*/
|
||||
@Module
|
||||
class BaseWhoisModule {
|
||||
|
||||
@Provides
|
||||
@SuppressWarnings("CloseableProvides")
|
||||
static Reader provideHttpInputReader(HttpServletRequest req) {
|
||||
try {
|
||||
return req.getReader();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link WhoisMetrics.WhoisMetric.Builder} with the startTimestamp already
|
||||
* initialized.
|
||||
*/
|
||||
@Provides
|
||||
static WhoisMetric.Builder provideEppMetricBuilder(Clock clock) {
|
||||
return WhoisMetric.builderForRequest(clock);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.isBlockedByBsa;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a domain name (i.e. SLD). */
|
||||
public class DomainLookupCommand implements WhoisCommand {
|
||||
|
||||
private static final String ERROR_PREFIX = "Domain";
|
||||
|
||||
@VisibleForTesting final InternetDomainName domainName;
|
||||
|
||||
private final boolean fullOutput;
|
||||
private final boolean cached;
|
||||
private final String whoisRedactedEmailText;
|
||||
private final String domainBlockedByBsaTemplate;
|
||||
|
||||
public DomainLookupCommand(
|
||||
InternetDomainName domainName,
|
||||
boolean fullOutput,
|
||||
boolean cached,
|
||||
String whoisRedactedEmailText,
|
||||
String domainBlockedByBsaTemplate) {
|
||||
this.domainName = checkNotNull(domainName, "domainOrHostName");
|
||||
this.fullOutput = fullOutput;
|
||||
this.cached = cached;
|
||||
this.whoisRedactedEmailText = whoisRedactedEmailText;
|
||||
this.domainBlockedByBsaTemplate = domainBlockedByBsaTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
|
||||
Optional<InternetDomainName> tld = findTldForName(domainName);
|
||||
// Google Registry Policy: Do not return records under TLDs for which we're not
|
||||
// authoritative.
|
||||
if (tld.isEmpty() || !getTlds().contains(tld.get().toString())) {
|
||||
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
|
||||
}
|
||||
// Include `getResponse` and `isBlockedByBsa` in one transaction to reduce latency.
|
||||
// Must pass the exceptions outside to throw.
|
||||
ResponseOrException result =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
final Optional<WhoisResponse> response = getResponse(domainName, now);
|
||||
if (response.isPresent()) {
|
||||
return ResponseOrException.of(response.get());
|
||||
}
|
||||
|
||||
String label = domainName.parts().get(0);
|
||||
String tldStr = tld.get().toString();
|
||||
if (isBlockedByBsa(label, Tld.get(tldStr), now)) {
|
||||
return ResponseOrException.of(
|
||||
new WhoisException(
|
||||
now,
|
||||
SC_NOT_FOUND,
|
||||
String.format(domainBlockedByBsaTemplate, domainName)));
|
||||
}
|
||||
|
||||
return ResponseOrException.of(
|
||||
new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found."));
|
||||
});
|
||||
return result.returnOrThrow();
|
||||
}
|
||||
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
|
||||
Optional<Domain> domainResource =
|
||||
cached
|
||||
? loadByForeignKeyByCache(Domain.class, domainName.toString(), now)
|
||||
: loadByForeignKey(Domain.class, domainName.toString(), now);
|
||||
return domainResource.map(
|
||||
domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
|
||||
}
|
||||
|
||||
record ResponseOrException(
|
||||
Optional<WhoisResponse> whoisResponse, Optional<WhoisException> exception) {
|
||||
|
||||
WhoisResponse returnOrThrow() throws WhoisException {
|
||||
Verify.verify(
|
||||
whoisResponse().isPresent() || exception().isPresent(),
|
||||
"Response and exception must not both be missing.");
|
||||
return whoisResponse().orElseThrow(() -> exception().get());
|
||||
}
|
||||
|
||||
static ResponseOrException of(WhoisResponse response) {
|
||||
return new ResponseOrException(Optional.of(response), Optional.empty());
|
||||
}
|
||||
|
||||
static ResponseOrException of(WhoisException exception) {
|
||||
return new ResponseOrException(Optional.empty(), Optional.of(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
import static google.registry.xml.UtcDateTimeAdapter.getFormattedString;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS response to a domain query. */
|
||||
final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Prefix for status value URLs. */
|
||||
private static final String ICANN_STATUS_URL_PREFIX = "https://icann.org/epp#";
|
||||
|
||||
/** Message required to be appended to all domain WHOIS responses. */
|
||||
private static final String ICANN_AWIP_INFO_MESSAGE =
|
||||
"For more information on Whois status codes, please visit https://icann.org/epp\r\n";
|
||||
|
||||
/** Domain which was the target of this WHOIS command. */
|
||||
private final Domain domain;
|
||||
|
||||
/** Whether the full WHOIS output is to be displayed. */
|
||||
private final boolean fullOutput;
|
||||
|
||||
/** When fullOutput is false, the text to display for the registrant's email fields. */
|
||||
private final String whoisRedactedEmailText;
|
||||
|
||||
/** Creates new WHOIS domain response on the given domain. */
|
||||
DomainWhoisResponse(
|
||||
Domain domain, boolean fullOutput, String whoisRedactedEmailText, DateTime timestamp) {
|
||||
super(timestamp);
|
||||
this.domain = checkNotNull(domain, "domain");
|
||||
this.fullOutput = fullOutput;
|
||||
this.whoisRedactedEmailText = whoisRedactedEmailText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(final boolean preferUnicode, String disclaimer) {
|
||||
Optional<Registrar> registrarOptional =
|
||||
Registrar.loadByRegistrarIdCached(domain.getCurrentSponsorRegistrarId());
|
||||
checkState(
|
||||
registrarOptional.isPresent(),
|
||||
"Could not load registrar %s",
|
||||
domain.getCurrentSponsorRegistrarId());
|
||||
Registrar registrar = registrarOptional.get();
|
||||
Optional<RegistrarPoc> abuseContact =
|
||||
registrar.getPocsFromReplica().stream()
|
||||
.filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse)
|
||||
.findFirst();
|
||||
return WhoisResponseResults.create(
|
||||
new DomainEmitter()
|
||||
.emitField("Domain Name", maybeFormatHostname(domain.getDomainName(), preferUnicode))
|
||||
.emitField("Registry Domain ID", domain.getRepoId())
|
||||
.emitField("Registrar WHOIS Server", registrar.getWhoisServer())
|
||||
.emitField("Registrar URL", registrar.getUrl())
|
||||
.emitFieldIfDefined("Updated Date", getFormattedString(domain.getLastEppUpdateTime()))
|
||||
.emitField("Creation Date", getFormattedString(domain.getCreationTime()))
|
||||
.emitField(
|
||||
"Registry Expiry Date", getFormattedString(domain.getRegistrationExpirationTime()))
|
||||
.emitField("Registrar", registrar.getRegistrarName())
|
||||
.emitField("Registrar IANA ID", Objects.toString(registrar.getIanaIdentifier(), ""))
|
||||
// Email address is a required field for registrar contacts. Therefore as long as there
|
||||
// is an abuse contact, we can get an email address from it.
|
||||
.emitField(
|
||||
"Registrar Abuse Contact Email",
|
||||
abuseContact.map(RegistrarPoc::getEmailAddress).orElse(""))
|
||||
.emitField(
|
||||
"Registrar Abuse Contact Phone",
|
||||
abuseContact.map(RegistrarPoc::getPhoneNumber).orElse(""))
|
||||
.emitStatusValues(domain.getStatusValues(), domain.getGracePeriods())
|
||||
// TODO(mcilwain): Investigate if the WHOIS spec requires us to always output REDACTED
|
||||
// text in WHOIS even if the contact is not present, and if so, do so.
|
||||
.emitContact("Registrant", domain.getRegistrant(), preferUnicode)
|
||||
.emitContact("Admin", getContactReference(Type.ADMIN), preferUnicode)
|
||||
.emitContact("Tech", getContactReference(Type.TECH), preferUnicode)
|
||||
.emitContact("Billing", getContactReference(Type.BILLING), preferUnicode)
|
||||
.emitSet(
|
||||
"Name Server",
|
||||
domain.loadNameserverHostNames(),
|
||||
hostName -> maybeFormatHostname(hostName, preferUnicode))
|
||||
.emitField(
|
||||
"DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
|
||||
.emitWicfLink()
|
||||
.emitLastUpdated(getTimestamp())
|
||||
.emitAwipMessage()
|
||||
.emitFooter(disclaimer)
|
||||
.toString(),
|
||||
1);
|
||||
}
|
||||
|
||||
/** Returns the contact of the given type. */
|
||||
private Optional<VKey<Contact>> getContactReference(Type type) {
|
||||
Optional<DesignatedContact> contactOfType =
|
||||
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
|
||||
return contactOfType.map(DesignatedContact::getContactKey);
|
||||
}
|
||||
|
||||
/** Output emitter with logic for domains. */
|
||||
class DomainEmitter extends Emitter<DomainEmitter> {
|
||||
DomainEmitter emitPhone(
|
||||
String contactType, String title, @Nullable ContactPhoneNumber phoneNumber) {
|
||||
if (phoneNumber == null) {
|
||||
return this;
|
||||
}
|
||||
return emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, title), phoneNumber.getPhoneNumber(), fullOutput)
|
||||
.emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, title, "Ext"), phoneNumber.getExtension(), fullOutput);
|
||||
}
|
||||
|
||||
/** Emit the contact entry of the given type. */
|
||||
DomainEmitter emitContact(
|
||||
String contactType, Optional<VKey<Contact>> contact, boolean preferUnicode) {
|
||||
if (contact.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
|
||||
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
|
||||
// someone's attention.
|
||||
Contact contact1 = EppResource.loadByCache(contact.get());
|
||||
if (contact1 == null) {
|
||||
logger.atSevere().log(
|
||||
"(BUG) Broken reference found from domain %s to contact %s.",
|
||||
domain.getDomainName(), contact);
|
||||
return this;
|
||||
}
|
||||
PostalInfo postalInfo =
|
||||
chooseByUnicodePreference(
|
||||
preferUnicode,
|
||||
contact1.getLocalizedPostalInfo(),
|
||||
contact1.getInternationalizedPostalInfo());
|
||||
// ICANN Consistent Labeling & Display policy requires that this be the ROID.
|
||||
emitField(ImmutableList.of("Registry", contactType, "ID"), contact1.getRepoId(), fullOutput);
|
||||
if (postalInfo != null) {
|
||||
emitFieldIfDefined(ImmutableList.of(contactType, "Name"), postalInfo.getName(), fullOutput);
|
||||
emitFieldIfDefined(
|
||||
ImmutableList.of(contactType, "Organization"),
|
||||
postalInfo.getOrg(),
|
||||
fullOutput || contactType.equals("Registrant"));
|
||||
emitAddress(contactType, postalInfo.getAddress(), fullOutput);
|
||||
}
|
||||
emitPhone(contactType, "Phone", contact1.getVoiceNumber());
|
||||
emitPhone(contactType, "Fax", contact1.getFaxNumber());
|
||||
String emailFieldContent = fullOutput ? contact1.getEmailAddress() : whoisRedactedEmailText;
|
||||
emitField(ImmutableList.of(contactType, "Email"), emailFieldContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Emits status values and grace periods as a set, in the AWIP format. */
|
||||
DomainEmitter emitStatusValues(
|
||||
Set<StatusValue> statusValues, Set<GracePeriod> gracePeriods) {
|
||||
ImmutableSet.Builder<EppEnum> combinedStatuses = new ImmutableSet.Builder<>();
|
||||
combinedStatuses.addAll(statusValues);
|
||||
for (GracePeriod gracePeriod : gracePeriods) {
|
||||
combinedStatuses.add(gracePeriod.getType());
|
||||
}
|
||||
return emitSet(
|
||||
"Domain Status",
|
||||
combinedStatuses.build(),
|
||||
status -> {
|
||||
String xmlName = status.getXmlName();
|
||||
return String.format("%s %s%s", xmlName, ICANN_STATUS_URL_PREFIX, xmlName);
|
||||
});
|
||||
}
|
||||
|
||||
/** Emits the message that AWIP requires accompany all domain WHOIS responses. */
|
||||
DomainEmitter emitAwipMessage() {
|
||||
return emitRawLine(ICANN_AWIP_INFO_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.host.Host;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a nameserver based on its hostname. */
|
||||
public class NameserverLookupByHostCommand implements WhoisCommand {
|
||||
|
||||
private static final String ERROR_PREFIX = "Nameserver";
|
||||
|
||||
@VisibleForTesting final InternetDomainName hostName;
|
||||
|
||||
boolean cached;
|
||||
|
||||
NameserverLookupByHostCommand(InternetDomainName hostName, boolean cached) {
|
||||
this.hostName = checkNotNull(hostName, "hostName");
|
||||
this.cached = cached;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
|
||||
Optional<InternetDomainName> tld = findTldForName(hostName);
|
||||
// Google Registry Policy: Do not return records under TLDs for which we're not authoritative.
|
||||
if (tld.isPresent() && getTlds().contains(tld.get().toString())) {
|
||||
final Optional<WhoisResponse> response = getResponse(hostName, now);
|
||||
if (response.isPresent()) {
|
||||
return response.get();
|
||||
}
|
||||
}
|
||||
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
|
||||
}
|
||||
|
||||
private Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
|
||||
Optional<Host> host =
|
||||
cached
|
||||
? loadByForeignKeyByCache(Host.class, hostName.toString(), now)
|
||||
: loadByForeignKey(Host.class, hostName.toString(), now);
|
||||
return host.map(h -> new NameserverWhoisResponse(h, now));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Tlds;
|
||||
import java.net.InetAddress;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Represents a WHOIS lookup for a nameserver based on its IP.
|
||||
*
|
||||
* <p>Both IPv4 and IPv6 addresses are supported. Unlike other WHOIS commands, this is an eventually
|
||||
* consistent query.
|
||||
*
|
||||
* <p><b>Note:</b> There may be multiple nameservers with the same IP.
|
||||
*/
|
||||
final class NameserverLookupByIpCommand implements WhoisCommand {
|
||||
|
||||
@VisibleForTesting
|
||||
final InetAddress ipAddress;
|
||||
|
||||
NameserverLookupByIpCommand(InetAddress ipAddress) {
|
||||
this.ipAddress = checkNotNull(ipAddress, "ipAddress");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
|
||||
Iterable<Host> hostsFromDb;
|
||||
hostsFromDb =
|
||||
replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
// We cannot query @Convert-ed fields in HQL, so we must use native Postgres.
|
||||
replicaTm()
|
||||
.getEntityManager()
|
||||
/*
|
||||
* Using array_operator <@ (contained-by) with gin index on inet_address.
|
||||
* Without gin index, this is slightly slower than the alternative form of
|
||||
* ':address = ANY(inet_address)'.
|
||||
*/
|
||||
.createNativeQuery(
|
||||
"SELECT * From \"Host\" WHERE "
|
||||
+ "ARRAY[ CAST(:address AS TEXT) ] <@ inet_addresses AND "
|
||||
+ "deletion_time > CAST(:now AS timestamptz)",
|
||||
Host.class)
|
||||
.setParameter("address", InetAddresses.toAddrString(ipAddress))
|
||||
.setParameter("now", now.toString())
|
||||
.getResultList());
|
||||
ImmutableList<Host> hosts =
|
||||
Streams.stream(hostsFromDb)
|
||||
.filter(
|
||||
host ->
|
||||
Tlds.findTldForName(InternetDomainName.from(host.getHostName())).isPresent())
|
||||
.collect(toImmutableList());
|
||||
if (hosts.isEmpty()) {
|
||||
throw new WhoisException(now, SC_NOT_FOUND, "No nameservers found.");
|
||||
}
|
||||
return new NameserverWhoisResponse(hosts, now);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Container for WHOIS responses to a nameserver lookup queries. */
|
||||
final class NameserverWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
/** Nameserver(s) which were the target of this WHOIS command. */
|
||||
private final ImmutableList<Host> hosts;
|
||||
|
||||
/** Creates new WHOIS nameserver response on the given host. */
|
||||
NameserverWhoisResponse(Host host, DateTime timestamp) {
|
||||
this(ImmutableList.of(checkNotNull(host, "host")), timestamp);
|
||||
}
|
||||
|
||||
/** Creates new WHOIS nameserver response on the given list of hosts. */
|
||||
NameserverWhoisResponse(ImmutableList<Host> hosts, DateTime timestamp) {
|
||||
super(timestamp);
|
||||
this.hosts = checkNotNull(hosts, "hosts");
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
|
||||
// If we have subordinate hosts, load their registrar ids in a single transaction up-front.
|
||||
ImmutableList<Host> subordinateHosts =
|
||||
hosts.stream().filter(Host::isSubordinate).collect(toImmutableList());
|
||||
ImmutableMap<Host, String> hostRegistrars =
|
||||
subordinateHosts.isEmpty()
|
||||
? ImmutableMap.of()
|
||||
: replicaTm()
|
||||
.transact(
|
||||
() ->
|
||||
Maps.toMap(
|
||||
subordinateHosts.iterator(),
|
||||
host ->
|
||||
replicaTm()
|
||||
.loadByKey(host.getSuperordinateDomain())
|
||||
.cloneProjectedAtTime(getTimestamp())
|
||||
.getCurrentSponsorRegistrarId()));
|
||||
|
||||
BasicEmitter emitter = new BasicEmitter();
|
||||
for (int i = 0; i < hosts.size(); i++) {
|
||||
Host host = hosts.get(i);
|
||||
String registrarId =
|
||||
host.isSubordinate()
|
||||
? hostRegistrars.get(host)
|
||||
: host.getPersistedCurrentSponsorRegistrarId();
|
||||
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(registrarId);
|
||||
checkState(registrar.isPresent(), "Could not load registrar %s", registrarId);
|
||||
emitter
|
||||
.emitField("Server Name", maybeFormatHostname(host.getHostName(), preferUnicode))
|
||||
.emitSet("IP Address", host.getInetAddresses(), InetAddresses::toAddrString)
|
||||
.emitField("Registrar", registrar.get().getRegistrarName())
|
||||
.emitField("Registrar WHOIS Server", registrar.get().getWhoisServer())
|
||||
.emitField("Registrar URL", registrar.get().getUrl());
|
||||
if (i < hosts.size() - 1) {
|
||||
emitter.emitNewline();
|
||||
}
|
||||
}
|
||||
String plaintext = emitter.emitLastUpdated(getTimestamp()).emitFooter(disclaimer).toString();
|
||||
return WhoisResponseResults.create(plaintext, hosts.size());
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// 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.whois;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
|
||||
/**
|
||||
* Whois module for systems that require that we not cache EPP resources (e.g. the nomulus tool).
|
||||
*
|
||||
* <h2>Dependencies</h2>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link google.registry.request.RequestModule RequestModule}
|
||||
* </ul>
|
||||
*
|
||||
* @see "google.registry.tools.RegistryToolComponent"
|
||||
*/
|
||||
@Module
|
||||
public final class NonCachingWhoisModule extends BaseWhoisModule {
|
||||
@Provides
|
||||
@Config("whoisCommandFactory")
|
||||
static WhoisCommandFactory provideWhoisCommandFactory() {
|
||||
return WhoisCommandFactory.createNonCached();
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
|
||||
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup for a registrar by its name. */
|
||||
final class RegistrarLookupCommand implements WhoisCommand {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** True if the command should return cached responses. */
|
||||
private boolean cached;
|
||||
|
||||
/**
|
||||
* Cache of a map from a stripped-down (letters and digits only) name to the registrar. This map
|
||||
* includes only active, publicly visible registrars, because the others should be invisible to
|
||||
* WHOIS.
|
||||
*/
|
||||
private static final Supplier<Map<String, Registrar>> REGISTRAR_BY_NORMALIZED_NAME_CACHE =
|
||||
memoizeWithShortExpiration(RegistrarLookupCommand::loadRegistrarMap);
|
||||
|
||||
@VisibleForTesting
|
||||
final String registrarName;
|
||||
|
||||
RegistrarLookupCommand(String registrarName, boolean cached) {
|
||||
checkArgument(!isNullOrEmpty(registrarName), "registrarName");
|
||||
this.registrarName = registrarName;
|
||||
this.cached = cached;
|
||||
}
|
||||
|
||||
static Map<String, Registrar> loadRegistrarMap() {
|
||||
Map<String, Registrar> map = new HashMap<>();
|
||||
// Use the normalized registrar name as a key, and ignore inactive and hidden
|
||||
// registrars.
|
||||
for (Registrar registrar : Registrar.loadAll()) {
|
||||
if (!registrar.isLiveAndPubliclyVisible() || registrar.getRegistrarName() == null) {
|
||||
continue;
|
||||
}
|
||||
String normalized = normalizeRegistrarName(registrar.getRegistrarName());
|
||||
if (map.put(normalized, registrar) != null) {
|
||||
logger.atWarning().log(
|
||||
"%s appeared as a normalized registrar name for more than one registrar.", normalized);
|
||||
}
|
||||
}
|
||||
// Use the normalized registrar name without its last word as a key, assuming there are
|
||||
// multiple words in the name. This allows searches without LLC or INC, etc. Only insert
|
||||
// if there isn't already a mapping for this string, so that if there's a registrar with
|
||||
// a
|
||||
// two word name (Go Daddy) and no business-type suffix and another registrar with just
|
||||
// that first word as its name (Go), the latter will win.
|
||||
for (Registrar registrar : ImmutableList.copyOf(map.values())) {
|
||||
if (registrar.getRegistrarName() == null) {
|
||||
continue;
|
||||
}
|
||||
List<String> words =
|
||||
Splitter.on(CharMatcher.whitespace()).splitToList(registrar.getRegistrarName());
|
||||
if (words.size() > 1) {
|
||||
String normalized =
|
||||
normalizeRegistrarName(Joiner.on("").join(words.subList(0, words.size() - 1)));
|
||||
map.putIfAbsent(normalized, registrar);
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
|
||||
Map<String, Registrar> registrars =
|
||||
cached ? REGISTRAR_BY_NORMALIZED_NAME_CACHE.get() : loadRegistrarMap();
|
||||
Registrar registrar = registrars.get(normalizeRegistrarName(registrarName));
|
||||
// If a registrar is in the cache, we know it must be active and publicly visible.
|
||||
if (registrar == null) {
|
||||
throw new WhoisException(now, SC_NOT_FOUND, "No registrar found.");
|
||||
}
|
||||
return new RegistrarWhoisResponse(registrar, now);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Container for WHOIS responses to registrar lookup queries. */
|
||||
class RegistrarWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
/** Registrar which was the target of this WHOIS command. */
|
||||
private final Registrar registrar;
|
||||
|
||||
/**
|
||||
* Used in the emitter below to signal either admin or tech
|
||||
* contacts. NB, this is purposely distinct from the
|
||||
* RegistrarContact.Type.{ADMIN,TECH} as they don't carry equivalent
|
||||
* meaning in our system. Sigh.
|
||||
*/
|
||||
private enum AdminOrTech { ADMIN, TECH }
|
||||
|
||||
/** Creates a new WHOIS registrar response on the given registrar object. */
|
||||
RegistrarWhoisResponse(Registrar registrar, DateTime timestamp) {
|
||||
super(timestamp);
|
||||
this.registrar = checkNotNull(registrar, "registrar");
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
|
||||
Set<RegistrarPoc> contacts = registrar.getPocsFromReplica();
|
||||
String plaintext =
|
||||
new RegistrarEmitter()
|
||||
.emitField("Registrar", registrar.getRegistrarName())
|
||||
.emitAddress(
|
||||
null,
|
||||
chooseByUnicodePreference(
|
||||
preferUnicode,
|
||||
registrar.getLocalizedAddress(),
|
||||
registrar.getInternationalizedAddress()),
|
||||
true)
|
||||
.emitPhonesAndEmail(
|
||||
registrar.getPhoneNumber(), registrar.getFaxNumber(), registrar.getEmailAddress())
|
||||
.emitField("Registrar WHOIS Server", registrar.getWhoisServer())
|
||||
.emitField("Registrar URL", registrar.getUrl())
|
||||
.emitRegistrarPocs("Admin", contacts, AdminOrTech.ADMIN)
|
||||
.emitRegistrarPocs("Technical", contacts, AdminOrTech.TECH)
|
||||
.emitLastUpdated(getTimestamp())
|
||||
.emitFooter(disclaimer)
|
||||
.toString();
|
||||
return WhoisResponseResults.create(plaintext, 1);
|
||||
}
|
||||
|
||||
/** An emitter with logic for registrars. */
|
||||
static class RegistrarEmitter extends Emitter<RegistrarEmitter> {
|
||||
/** Emits the registrar contact of the given type. */
|
||||
RegistrarEmitter emitRegistrarPocs(
|
||||
String contactLabel, Iterable<RegistrarPoc> contacts, AdminOrTech type) {
|
||||
for (RegistrarPoc contact : contacts) {
|
||||
if ((type == AdminOrTech.ADMIN && contact.getVisibleInWhoisAsAdmin())
|
||||
|| (type == AdminOrTech.TECH && contact.getVisibleInWhoisAsTech())) {
|
||||
emitField(contactLabel + " Contact", contact.getName())
|
||||
.emitPhonesAndEmail(
|
||||
contact.getPhoneNumber(),
|
||||
contact.getFaxNumber(),
|
||||
contact.getEmailAddress());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Emits the registrar contact of the given type. */
|
||||
RegistrarEmitter emitPhonesAndEmail(
|
||||
@Nullable String phone, @Nullable String fax, @Nullable String email) {
|
||||
return emitField("Phone Number", phone)
|
||||
.emitField("Fax Number", fax)
|
||||
.emitField("Email", email);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** High-level WHOIS API for other packages. */
|
||||
public final class Whois {
|
||||
|
||||
private final Clock clock;
|
||||
private final String disclaimer;
|
||||
private final WhoisReader whoisReader;
|
||||
|
||||
@Inject
|
||||
public Whois(Clock clock, @Config("whoisDisclaimer") String disclaimer, WhoisReader whoisReader) {
|
||||
this.clock = clock;
|
||||
this.disclaimer = disclaimer;
|
||||
this.whoisReader = whoisReader;
|
||||
}
|
||||
|
||||
/** Performs a WHOIS lookup on a plaintext query string. */
|
||||
public String lookup(String query, boolean preferUnicode, boolean fullOutput) {
|
||||
DateTime now = clock.nowUtc();
|
||||
try {
|
||||
return whoisReader
|
||||
.readCommand(new StringReader(query), fullOutput, now)
|
||||
.executeQuery(now)
|
||||
.getResponse(preferUnicode, disclaimer)
|
||||
.plainTextOutput();
|
||||
} catch (WhoisException e) {
|
||||
return e.getResponse(preferUnicode, disclaimer).plainTextOutput();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.whois.WhoisException.UncheckedWhoisException;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import google.registry.whois.WhoisResponse.WhoisResponseResults;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.Reader;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* HTTP request handler for WHOIS protocol requests sent to us by a proxy.
|
||||
*
|
||||
* <p>All commands and responses conform to the WHOIS spec as defined in RFC 3912. Commands must be
|
||||
* sent via an HTTP POST in the request body.
|
||||
*
|
||||
* <p>This action is meant to serve as a low level interface for the proxy app which forwards us
|
||||
* requests received on port 43. However this interface is technically higher level because it sends
|
||||
* back proper HTTP error codes such as 200, 400, 500, etc. These are discarded by the proxy because
|
||||
* WHOIS specifies no manner for differentiating successful and erroneous requests.
|
||||
*
|
||||
* @see WhoisHttpAction
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3912.txt">RFC 3912: WHOIS Protocol Specification</a>
|
||||
*/
|
||||
@Action(service = GaeService.PUBAPI, path = "/_dr/whois", method = POST, auth = Auth.AUTH_ADMIN)
|
||||
public class WhoisAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** WHOIS doesn't define an encoding, nor any way to specify an encoding in the protocol. */
|
||||
static final MediaType CONTENT_TYPE = MediaType.PLAIN_TEXT_UTF_8;
|
||||
|
||||
/**
|
||||
* As stated above, this is the low level interface intended for port 43, and as such, it
|
||||
* always prefers ASCII.
|
||||
*/
|
||||
static final boolean PREFER_UNICODE = false;
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject Reader input;
|
||||
@Inject Response response;
|
||||
@Inject Retrier retrier;
|
||||
@Inject WhoisReader whoisReader;
|
||||
@Inject @Config("whoisDisclaimer") String disclaimer;
|
||||
@Inject WhoisMetric.Builder metricBuilder;
|
||||
@Inject WhoisMetrics whoisMetrics;
|
||||
|
||||
@Inject
|
||||
WhoisAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String responseText;
|
||||
final DateTime now = clock.nowUtc();
|
||||
try {
|
||||
final WhoisCommand command = whoisReader.readCommand(input, false, now);
|
||||
metricBuilder.setCommand(command);
|
||||
WhoisResponseResults results;
|
||||
try {
|
||||
results = command.executeQuery(now).getResponse(PREFER_UNICODE, disclaimer);
|
||||
} catch (WhoisException e) {
|
||||
throw new UncheckedWhoisException(e);
|
||||
}
|
||||
responseText = results.plainTextOutput();
|
||||
setWhoisMetrics(metricBuilder, results.numResults(), SC_OK);
|
||||
} catch (UncheckedWhoisException u) {
|
||||
WhoisException e = (WhoisException) u.getCause();
|
||||
WhoisResponseResults results = e.getResponse(PREFER_UNICODE, disclaimer);
|
||||
responseText = results.plainTextOutput();
|
||||
setWhoisMetrics(metricBuilder, 0, e.getStatus());
|
||||
} catch (WhoisException e) {
|
||||
WhoisResponseResults results = e.getResponse(PREFER_UNICODE, disclaimer);
|
||||
responseText = results.plainTextOutput();
|
||||
setWhoisMetrics(metricBuilder, 0, e.getStatus());
|
||||
} catch (Throwable t) {
|
||||
logger.atSevere().withCause(t).log("WHOIS request crashed.");
|
||||
responseText = "Internal Server Error";
|
||||
setWhoisMetrics(metricBuilder, 0, SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
// Note that we always return 200 (OK) even if an error was hit. This is because returning an
|
||||
// non-OK HTTP status code will cause the proxy server to silently close the connection. Since
|
||||
// WHOIS has no way to return errors, it's better to convert any such errors into strings and
|
||||
// return them directly.
|
||||
response.setStatus(SC_OK);
|
||||
response.setContentType(CONTENT_TYPE);
|
||||
response.setPayload(responseText);
|
||||
whoisMetrics.recordWhoisMetric(metricBuilder.build());
|
||||
}
|
||||
|
||||
private static void setWhoisMetrics(
|
||||
WhoisMetric.Builder metricBuilder, int numResults, int status) {
|
||||
metricBuilder.setNumResults(numResults);
|
||||
metricBuilder.setStatus(status);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS command request from a client. */
|
||||
public interface WhoisCommand {
|
||||
|
||||
/**
|
||||
* Executes a WHOIS query and returns the resultant data.
|
||||
*
|
||||
* @return An object representing the response to the WHOIS command.
|
||||
* @throws WhoisException If some error occured while executing the command.
|
||||
*/
|
||||
WhoisResponse executeQuery(DateTime now) throws WhoisException;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* A class used to configure WHOIS commands.
|
||||
*
|
||||
* <p>To add custom commands, extend this class, then configure it in
|
||||
* {@link ConfigModule#provideWhoisCommandFactoryClass}.
|
||||
*/
|
||||
public class WhoisCommandFactory {
|
||||
|
||||
/** True if the commands should return cached responses. */
|
||||
private boolean cached = true;
|
||||
|
||||
/** Public default constructor, needed so we can construct this from the class name. */
|
||||
public WhoisCommandFactory() {}
|
||||
|
||||
private WhoisCommandFactory(boolean cached) {
|
||||
this.cached = cached;
|
||||
}
|
||||
|
||||
/** Create a command factory that does not rely on entity caches. */
|
||||
static WhoisCommandFactory createNonCached() {
|
||||
return new WhoisCommandFactory(false);
|
||||
}
|
||||
|
||||
/** Create a command factory that may rely on entity caches. */
|
||||
static WhoisCommandFactory createCached() {
|
||||
return new WhoisCommandFactory(true);
|
||||
}
|
||||
|
||||
/** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */
|
||||
public WhoisCommand domainLookup(
|
||||
InternetDomainName domainName,
|
||||
boolean fullOutput,
|
||||
String whoisRedactedEmailText,
|
||||
String domainBlockedByBsaTemplate) {
|
||||
return new DomainLookupCommand(
|
||||
domainName, fullOutput, cached, whoisRedactedEmailText, domainBlockedByBsaTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified IP address.
|
||||
*/
|
||||
public WhoisCommand nameserverLookupByIp(InetAddress inetAddress) {
|
||||
return new NameserverLookupByIpCommand(inetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified host name.
|
||||
*/
|
||||
public WhoisCommand nameserverLookupByHost(InternetDomainName hostName) {
|
||||
return new NameserverLookupByHostCommand(hostName, cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a registrar lookup on the specified registrar
|
||||
* name.
|
||||
*/
|
||||
public WhoisCommand registrarLookup(String registrar) {
|
||||
return new RegistrarLookupCommand(registrar, cached);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Exception that gets thrown when WHOIS command isn't successful. */
|
||||
public final class WhoisException extends Exception implements WhoisResponse {
|
||||
|
||||
private final DateTime timestamp;
|
||||
private final int status;
|
||||
|
||||
/** @see #WhoisException(DateTime, int, String, Throwable) */
|
||||
WhoisException(DateTime timestamp, int status, String message) {
|
||||
this(timestamp, status, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an exception explaining why a WHOIS request has failed.
|
||||
*
|
||||
* @param timestamp should be set to the time at which this request was processed.
|
||||
* @param status A non-2xx HTTP status code to indicate type of failure.
|
||||
* @param message is displayed to the user so you should be careful about tainted data.
|
||||
* @param cause the original exception or {@code null}.
|
||||
* @throws IllegalArgumentException if {@code !(300 <= status < 700)}
|
||||
*/
|
||||
WhoisException(DateTime timestamp, int status, String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
checkArgument(300 <= status && status < 700,
|
||||
"WhoisException status must be a non-2xx HTTP status code: %s", status);
|
||||
this.timestamp = checkNotNull(timestamp, "timestamp");
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/** Returns the time at which this WHOIS request was processed. */
|
||||
@Override
|
||||
public DateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/** Returns a non-2xx HTTP status code to differentiate types of failure. */
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
|
||||
String plaintext = new WhoisResponseImpl.BasicEmitter()
|
||||
.emitRawLine(getMessage())
|
||||
.emitLastUpdated(getTimestamp())
|
||||
.emitFooter(disclaimer)
|
||||
.toString();
|
||||
return WhoisResponseResults.create(plaintext, 0);
|
||||
}
|
||||
|
||||
/** Exception that wraps WhoisExceptions returned from Retrier. */
|
||||
public static final class UncheckedWhoisException extends RuntimeException {
|
||||
UncheckedWhoisException(WhoisException whoisException) {
|
||||
super(whoisException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
import static com.google.common.net.HttpHeaders.CACHE_CONTROL;
|
||||
import static com.google.common.net.HttpHeaders.EXPIRES;
|
||||
import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
|
||||
import static com.google.common.net.HttpHeaders.X_CONTENT_TYPE_OPTIONS;
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.RequestPath;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import google.registry.whois.WhoisResponse.WhoisResponseResults;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.StringReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* Human-Friendly HTTP WHOIS API
|
||||
*
|
||||
* <p>This API uses easy to understand paths rather than {@link WhoisAction} which requires a POST
|
||||
* request containing a WHOIS command. Because the typical WHOIS command is along the lines of
|
||||
* {@code "domain google.lol"} or the equivalent {@code "google.lol}, this action is just going to
|
||||
* replace the slashes with spaces and let {@link WhoisReader} figure out what to do.
|
||||
*
|
||||
* <p>This action accepts requests from any origin.
|
||||
*
|
||||
* <p>You can send AJAX requests to our WHOIS API from your <em>very own</em> website using the
|
||||
* following embed code:
|
||||
*
|
||||
* <pre>{@code
|
||||
* <input id="query-input" placeholder="Domain, Nameserver, IP, etc." autofocus>
|
||||
* <button id="search-button">Lookup</button>
|
||||
* <pre id="whois-results"></pre>
|
||||
* <script>
|
||||
* (function() {
|
||||
* var WHOIS_API_URL = 'https://domain-registry-alpha.appspot.com/whois/';
|
||||
* function OnKeyPressQueryInput(ev) {
|
||||
* if (typeof ev == 'undefined' && window.event) {
|
||||
* ev = window.event;
|
||||
* }
|
||||
* if (ev.keyCode == 13) {
|
||||
* document.getElementById('search-button').click();
|
||||
* }
|
||||
* }
|
||||
* function OnClickSearchButton() {
|
||||
* var query = document.getElementById('query-input').value;
|
||||
* var req = new XMLHttpRequest();
|
||||
* req.onreadystatechange = function() {
|
||||
* if (req.readyState == 4) {
|
||||
* var results = document.getElementById('whois-results');
|
||||
* results.textContent = req.responseText;
|
||||
* }
|
||||
* };
|
||||
* req.open('GET', WHOIS_API_URL + escape(query), true);
|
||||
* req.send();
|
||||
* }
|
||||
* document.getElementById('search-button').onclick = OnClickSearchButton;
|
||||
* document.getElementById('query-input').onkeypress = OnKeyPressQueryInput;
|
||||
* })();
|
||||
* </script>
|
||||
* }</pre>
|
||||
*
|
||||
* @see WhoisAction
|
||||
*/
|
||||
@Action(
|
||||
service = GaeService.PUBAPI,
|
||||
path = WhoisHttpAction.PATH,
|
||||
isPrefix = true,
|
||||
auth = Auth.AUTH_PUBLIC)
|
||||
public final class WhoisHttpAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/whois/";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/**
|
||||
* Cross-origin resource sharing (CORS) allowed origins policy.
|
||||
*
|
||||
* <p>This field specifies the value of the {@code Access-Control-Allow-Origin} response header.
|
||||
* Without this header, other domains such as charlestonroadregistry.com would not be able to
|
||||
* send requests to our WHOIS interface.
|
||||
*
|
||||
* <p>Our policy shall be to allow requests from pretty much anywhere using a wildcard policy.
|
||||
* The reason this is safe is because our WHOIS interface doesn't allow clients to modify data,
|
||||
* nor does it allow them to fetch user data. Only publicly available information is returned.
|
||||
*
|
||||
* @see <a href="http://www.w3.org/TR/cors/#access-control-allow-origin-response-header">
|
||||
* W3C CORS § 5.1 Access-Control-Allow-Origin Response Header</a>
|
||||
*/
|
||||
private static final String CORS_ALLOW_ORIGIN = "*";
|
||||
|
||||
/** We're going to let any HTTP proxy in the world cache our responses. */
|
||||
private static final String CACHE_CONTROL_VALUE = "public";
|
||||
|
||||
/** Responses may be cached for up to a day. */
|
||||
private static final String X_CONTENT_NO_SNIFF = "nosniff";
|
||||
|
||||
/** Splitter that turns information on HTTP into a list of tokens. */
|
||||
private static final Splitter SLASHER = Splitter.on('/').trimResults().omitEmptyStrings();
|
||||
|
||||
/** Joiner that turns {@link #SLASHER} tokens into a normal WHOIS query. */
|
||||
private static final Joiner JOINER = Joiner.on(' ');
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject Response response;
|
||||
@Inject @Config("whoisDisclaimer") String disclaimer;
|
||||
@Inject @Config("whoisHttpExpires") Duration expires;
|
||||
@Inject WhoisReader whoisReader;
|
||||
@Inject @RequestPath String requestPath;
|
||||
@Inject WhoisMetric.Builder metricBuilder;
|
||||
@Inject WhoisMetrics whoisMetrics;
|
||||
|
||||
@Inject
|
||||
WhoisHttpAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
verify(requestPath.startsWith(PATH));
|
||||
String path = nullToEmpty(requestPath);
|
||||
try {
|
||||
// Extremely permissive parsing that turns stuff like "/hello/world/" into "hello world".
|
||||
String commandText =
|
||||
decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n";
|
||||
DateTime now = clock.nowUtc();
|
||||
WhoisCommand command = whoisReader.readCommand(new StringReader(commandText), false, now);
|
||||
metricBuilder.setCommand(command);
|
||||
sendResponse(SC_OK, command.executeQuery(now));
|
||||
} catch (WhoisException e) {
|
||||
metricBuilder.setStatus(e.getStatus());
|
||||
metricBuilder.setNumResults(0);
|
||||
sendResponse(e.getStatus(), e);
|
||||
} catch (Throwable e) {
|
||||
metricBuilder.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
metricBuilder.setNumResults(0);
|
||||
Throwables.throwIfUnchecked(e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
whoisMetrics.recordWhoisMetric(metricBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendResponse(int status, WhoisResponse whoisResponse) {
|
||||
response.setStatus(status);
|
||||
metricBuilder.setStatus(status);
|
||||
response.setDateHeader(LAST_MODIFIED, whoisResponse.getTimestamp());
|
||||
response.setDateHeader(EXPIRES, whoisResponse.getTimestamp().plus(expires));
|
||||
response.setHeader(CACHE_CONTROL, CACHE_CONTROL_VALUE);
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, CORS_ALLOW_ORIGIN);
|
||||
response.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_NO_SNIFF);
|
||||
response.setContentType(PLAIN_TEXT_UTF_8);
|
||||
WhoisResponseResults results = whoisResponse.getResponse(true, disclaimer);
|
||||
metricBuilder.setNumResults(results.numResults());
|
||||
response.setPayload(results.plainTextOutput());
|
||||
}
|
||||
|
||||
/** Removes {@code %xx} escape codes from request path components. */
|
||||
private String decode(String pathData)
|
||||
throws UnsupportedEncodingException, WhoisException {
|
||||
try {
|
||||
return URLDecoder.decode(pathData, "UTF-8");
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.atInfo().log("Malformed WHOIS request path: %s (%s)", requestPath, pathData);
|
||||
throw new WhoisException(clock.nowUtc(), SC_BAD_REQUEST, "Malformed path query.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.monitoring.metrics.EventMetric.DEFAULT_FITTER;
|
||||
|
||||
import com.google.auto.value.AutoBuilder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.monitoring.metrics.EventMetric;
|
||||
import com.google.monitoring.metrics.IncrementableMetric;
|
||||
import com.google.monitoring.metrics.LabelDescriptor;
|
||||
import com.google.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Instrumentation for WHOIS requests.
|
||||
*
|
||||
* @see WhoisAction
|
||||
*/
|
||||
public class WhoisMetrics {
|
||||
|
||||
private static final ImmutableSet<LabelDescriptor> LABEL_DESCRIPTORS =
|
||||
ImmutableSet.of(
|
||||
LabelDescriptor.create("command_name", "The name of the WHOIS command."),
|
||||
LabelDescriptor.create(
|
||||
"num_results", "The number of results returned by the WHOIS command."),
|
||||
LabelDescriptor.create("status", "The return status of the WHOIS command."));
|
||||
|
||||
private static final IncrementableMetric whoisRequests =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/whois/requests", "Count of WHOIS Requests", "count", LABEL_DESCRIPTORS);
|
||||
|
||||
private static final EventMetric processingTime =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newEventMetric(
|
||||
"/whois/processing_time",
|
||||
"WHOIS Processing Time",
|
||||
"milliseconds",
|
||||
LABEL_DESCRIPTORS,
|
||||
DEFAULT_FITTER);
|
||||
|
||||
@Inject
|
||||
public WhoisMetrics() {}
|
||||
|
||||
/** Records the given {@link WhoisMetric} and its associated processing time. */
|
||||
public void recordWhoisMetric(WhoisMetric metric) {
|
||||
whoisRequests.increment(
|
||||
metric.commandName().orElse(""),
|
||||
Integer.toString(metric.numResults()),
|
||||
Integer.toString(metric.status()));
|
||||
processingTime.record(
|
||||
metric.endTimestamp().getMillis() - metric.startTimestamp().getMillis(),
|
||||
metric.commandName().orElse(""),
|
||||
Integer.toString(metric.numResults()),
|
||||
Integer.toString(metric.status()));
|
||||
}
|
||||
|
||||
/** A value class for recording attributes of a WHOIS metric. */
|
||||
public record WhoisMetric(
|
||||
Optional<String> commandName,
|
||||
int numResults,
|
||||
int status,
|
||||
DateTime startTimestamp,
|
||||
DateTime endTimestamp) {
|
||||
|
||||
/**
|
||||
* Create a {@link WhoisMetric.Builder} for a request context, with the start and end timestamps
|
||||
* taken from the given clock.
|
||||
*
|
||||
* <p>The start timestamp is recorded now, and the end timestamp at {@code build()}.
|
||||
*/
|
||||
public static WhoisMetric.Builder builderForRequest(Clock clock) {
|
||||
return WhoisMetric.builder().setStartTimestamp(clock.nowUtc()).setClock(clock);
|
||||
}
|
||||
|
||||
/** Create a {@link WhoisMetric.Builder}. */
|
||||
public static Builder builder() {
|
||||
return new AutoBuilder_WhoisMetrics_WhoisMetric_Builder();
|
||||
}
|
||||
|
||||
/** A builder to create instances of {@link WhoisMetric}. */
|
||||
@AutoBuilder
|
||||
public abstract static class Builder {
|
||||
|
||||
boolean wasBuilt = false;
|
||||
|
||||
/** Builder-only clock to support automatic recording of endTimestamp on {@link #build()}. */
|
||||
private Clock clock = null;
|
||||
|
||||
public Builder setCommand(WhoisCommand command) {
|
||||
// All WHOIS command class names share the "Command" suffix, so strip it out in order to
|
||||
// have shorter labels.
|
||||
return setCommandName(command.getClass().getSimpleName().replaceFirst("Command$", ""));
|
||||
}
|
||||
|
||||
public abstract Builder setCommandName(String commandName);
|
||||
|
||||
public abstract Builder setNumResults(int numResults);
|
||||
|
||||
public abstract Builder setStatus(int status);
|
||||
|
||||
abstract Builder setStartTimestamp(DateTime startTimestamp);
|
||||
|
||||
abstract Builder setEndTimestamp(DateTime endTimestamp);
|
||||
|
||||
Builder setClock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an instance of {@link WhoisMetric} using this builder.
|
||||
*
|
||||
* <p>If a clock was provided with {@code setClock()}, the end timestamp will be set to the
|
||||
* current timestamp of the clock; otherwise end timestamp must have been previously set.
|
||||
*/
|
||||
public WhoisMetric build() {
|
||||
checkState(
|
||||
!wasBuilt, "WhoisMetric was already built; building it again is most likely an error");
|
||||
if (clock != null) {
|
||||
setEndTimestamp(clock.nowUtc());
|
||||
}
|
||||
wasBuilt = true;
|
||||
return autoBuild();
|
||||
}
|
||||
|
||||
abstract WhoisMetric autoBuild();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static google.registry.util.TypeUtils.getClassFromString;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
|
||||
/**
|
||||
* Dagger module for the whois package.
|
||||
*
|
||||
* <h2>Dependencies</h2>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link google.registry.request.RequestModule RequestModule}
|
||||
* </ul>
|
||||
*
|
||||
* @see "google.registry.module.frontend.FrontendComponent"
|
||||
*/
|
||||
@Module
|
||||
public final class WhoisModule extends BaseWhoisModule {
|
||||
@Provides
|
||||
@Config("whoisCommandFactory")
|
||||
static WhoisCommandFactory provideWhoisCommandFactory(
|
||||
@Config("whoisCommandFactoryClass") String factoryClass) {
|
||||
return instantiate(getClassFromString(factoryClass, WhoisCommandFactory.class));
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* The WhoisReader class understands how to read the WHOIS command from some source, parse it, and
|
||||
* produce a new WhoisCommand instance. The command syntax of WHOIS is generally undefined, so we
|
||||
* adopt the following rules:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>domain <FQDN><dd>
|
||||
* Looks up the domain record for the fully qualified domain name.
|
||||
* <dt>nameserver <FQDN><dd>
|
||||
* Looks up the nameserver record for the fully qualified domain name.
|
||||
* <dt>nameserver <IP><dd>
|
||||
* Looks up the nameserver record at the given IP address.
|
||||
* <dt>registrar <IANA ID><dd>
|
||||
* Looks up the registrar record with the given IANA ID.
|
||||
* <dt>registrar <NAME><dd>
|
||||
* Looks up the registrar record with the given name.
|
||||
* <dt><IP><dd>
|
||||
* Looks up the nameserver record with the given IP address.
|
||||
* <dt><FQDN><dd>
|
||||
* Looks up the nameserver or domain record for the fully qualified domain name.
|
||||
* <dt><IANA ID><dd>
|
||||
* Looks up the registrar record with the given IANA ID.
|
||||
* </dl>
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc3912">RFC 3912</a>
|
||||
* @see <a href="http://www.iana.org/assignments/registrar-ids">IANA Registrar IDs</a>
|
||||
*/
|
||||
class WhoisReader {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/**
|
||||
* These are strings that will always trigger a specific query type when they are sent at
|
||||
* the beginning of a command.
|
||||
*/
|
||||
static final String DOMAIN_LOOKUP_COMMAND = "domain";
|
||||
static final String NAMESERVER_LOOKUP_COMMAND = "nameserver";
|
||||
static final String REGISTRAR_LOOKUP_COMMAND = "registrar";
|
||||
|
||||
private final WhoisCommandFactory commandFactory;
|
||||
private final String whoisRedactedEmailText;
|
||||
private final String domainBlockedByBsaTemplate;
|
||||
|
||||
/** Creates a new WhoisReader that extracts its command from the specified Reader. */
|
||||
@Inject
|
||||
WhoisReader(
|
||||
@Config("whoisCommandFactory") WhoisCommandFactory commandFactory,
|
||||
@Config("whoisRedactedEmailText") String whoisRedactedEmailText,
|
||||
@Config("domainBlockedByBsaTemplate") String domainBlockedByBsaTemplate) {
|
||||
this.commandFactory = commandFactory;
|
||||
this.whoisRedactedEmailText = whoisRedactedEmailText;
|
||||
this.domainBlockedByBsaTemplate = domainBlockedByBsaTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a command from some source to produce a new instance of WhoisCommand.
|
||||
*
|
||||
* @throws IOException If the command could not be read from the reader.
|
||||
* @throws WhoisException If the command could not be parsed as a WhoisCommand.
|
||||
*/
|
||||
WhoisCommand readCommand(Reader reader, boolean fullOutput, DateTime now)
|
||||
throws IOException, WhoisException {
|
||||
return parseCommand(CharStreams.toString(checkNotNull(reader, "reader")), fullOutput, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a WHOIS command string, parse it into its command type and target string. See class level
|
||||
* comments for a full description of the command syntax accepted.
|
||||
*/
|
||||
private WhoisCommand parseCommand(String command, boolean fullOutput, DateTime now)
|
||||
throws WhoisException {
|
||||
// Split the string into tokens based on whitespace.
|
||||
List<String> tokens = filterEmptyStrings(command.split("\\s"));
|
||||
if (tokens.isEmpty()) {
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, "No WHOIS command specified.");
|
||||
}
|
||||
|
||||
final String arg1 = tokens.get(0);
|
||||
|
||||
// Check if the first token is equal to the domain lookup command.
|
||||
if (arg1.equalsIgnoreCase(DOMAIN_LOOKUP_COMMAND)) {
|
||||
if (tokens.size() != 2) {
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Wrong number of arguments to '%s' command.", DOMAIN_LOOKUP_COMMAND));
|
||||
}
|
||||
|
||||
// Try to parse the argument as a domain name.
|
||||
try {
|
||||
logger.atInfo().log(
|
||||
"Attempting domain lookup command using domain name '%s'.", tokens.get(1));
|
||||
return commandFactory.domainLookup(
|
||||
InternetDomainName.from(canonicalizeHostname(tokens.get(1))),
|
||||
fullOutput,
|
||||
whoisRedactedEmailText,
|
||||
domainBlockedByBsaTemplate);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// If we can't interpret the argument as a host name, then return an error.
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Could not parse argument to '%s' command", DOMAIN_LOOKUP_COMMAND));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the first token is equal to the nameserver lookup command.
|
||||
if (arg1.equalsIgnoreCase(NAMESERVER_LOOKUP_COMMAND)) {
|
||||
if (tokens.size() != 2) {
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Wrong number of arguments to '%s' command.", NAMESERVER_LOOKUP_COMMAND));
|
||||
}
|
||||
|
||||
// Try to parse the argument as an IP address.
|
||||
try {
|
||||
logger.atInfo().log(
|
||||
"Attempting nameserver lookup command using %s as an IP address.", tokens.get(1));
|
||||
return commandFactory.nameserverLookupByIp(InetAddresses.forString(tokens.get(1)));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
||||
// Try to parse the argument as a host name.
|
||||
try {
|
||||
logger.atInfo().log(
|
||||
"Attempting nameserver lookup command using %s as a hostname.", tokens.get(1));
|
||||
return commandFactory.nameserverLookupByHost(
|
||||
InternetDomainName.from(canonicalizeHostname(tokens.get(1))));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
||||
// If we can't interpret the argument as either a host name or IP address, return an error.
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Could not parse argument to '%s' command", NAMESERVER_LOOKUP_COMMAND));
|
||||
}
|
||||
|
||||
// Check if the first token is equal to the registrar lookup command.
|
||||
if (arg1.equalsIgnoreCase(REGISTRAR_LOOKUP_COMMAND)) {
|
||||
if (tokens.size() == 1) {
|
||||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Too few arguments to '%s' command.", REGISTRAR_LOOKUP_COMMAND));
|
||||
}
|
||||
String registrarLookupArgument = Joiner.on(' ').join(tokens.subList(1, tokens.size()));
|
||||
logger.atInfo().log(
|
||||
"Attempting registrar lookup command using registrar %s.", registrarLookupArgument);
|
||||
return commandFactory.registrarLookup(registrarLookupArgument);
|
||||
}
|
||||
|
||||
// If we have a single token, then try to interpret that in various ways.
|
||||
if (tokens.size() == 1) {
|
||||
// Try to parse it as an IP address. If successful, then this is a lookup on a nameserver.
|
||||
try {
|
||||
logger.atInfo().log("Attempting nameserver lookup using %s as an IP address.", arg1);
|
||||
return commandFactory.nameserverLookupByIp(InetAddresses.forString(arg1));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
||||
// Try to parse it as a domain name or host name.
|
||||
try {
|
||||
final InternetDomainName targetName = InternetDomainName.from(canonicalizeHostname(arg1));
|
||||
|
||||
// We don't know at this point whether we have a domain name or a host name. We have to
|
||||
// search through our configured TLDs to see if there's one that prefixes the name.
|
||||
Optional<InternetDomainName> tld = findTldForName(targetName);
|
||||
if (tld.isEmpty()) {
|
||||
// This target is not under any configured TLD, so just try it as a registrar name.
|
||||
logger.atInfo().log("Attempting registrar lookup using %s as a registrar.", arg1);
|
||||
return commandFactory.registrarLookup(arg1);
|
||||
}
|
||||
|
||||
// If the target is exactly one level above the TLD, then this is a second level domain
|
||||
// (SLD) and we should do a domain lookup on it.
|
||||
if (targetName.parent().equals(tld.get())) {
|
||||
logger.atInfo().log("Attempting domain lookup using %s as a domain name.", targetName);
|
||||
return commandFactory.domainLookup(
|
||||
targetName, fullOutput, whoisRedactedEmailText, domainBlockedByBsaTemplate);
|
||||
}
|
||||
|
||||
// The target is more than one level above the TLD, so we'll assume it's a nameserver.
|
||||
logger.atInfo().log("Attempting nameserver lookup using %s as a hostname.", targetName);
|
||||
return commandFactory.nameserverLookupByHost(targetName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
||||
// Purposefully fall through to code below.
|
||||
}
|
||||
|
||||
// The only case left is that there are multiple tokens with no particular command given. We'll
|
||||
// assume this is a registrar lookup, since there's really nothing else it could be.
|
||||
String registrarLookupArgument = Joiner.on(' ').join(tokens);
|
||||
logger.atInfo().log(
|
||||
"Attempting registrar lookup employing %s as a registrar.", registrarLookupArgument);
|
||||
return commandFactory.registrarLookup(registrarLookupArgument);
|
||||
}
|
||||
|
||||
/** Returns an ArrayList containing the contents of the String array minus any empty strings. */
|
||||
private static List<String> filterEmptyStrings(String[] strings) {
|
||||
List<String> list = new ArrayList<>(strings.length);
|
||||
for (String str : strings) {
|
||||
if (!isNullOrEmpty(str)) {
|
||||
list.add(str);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Representation of a WHOIS query response. */
|
||||
public interface WhoisResponse {
|
||||
|
||||
/**
|
||||
* Returns the WHOIS response.
|
||||
*
|
||||
* @param preferUnicode if {@code false} will cause the output to be converted to ASCII whenever
|
||||
* possible; for example, converting IDN hostname labels to punycode. However certain things
|
||||
* (like a domain registrant name with accent marks) will be returned "as is". If the WHOIS
|
||||
* client has told us they're able to receive UTF-8 (such as with HTTP) then this field should
|
||||
* be set to {@code true}.
|
||||
* @param disclaimer text to show at bottom of output
|
||||
*/
|
||||
WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer);
|
||||
|
||||
/** Returns the time at which this response was created. */
|
||||
DateTime getTimestamp();
|
||||
|
||||
/** A wrapper class for the plaintext response of a WHOIS command and its number of results. */
|
||||
record WhoisResponseResults(String plainTextOutput, int numResults) {
|
||||
|
||||
static WhoisResponseResults create(String plainTextOutput, int numResults) {
|
||||
return new WhoisResponseResults(plainTextOutput, numResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import google.registry.model.eppcommon.Address;
|
||||
import google.registry.util.Idn;
|
||||
import google.registry.xml.UtcDateTimeAdapter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Base class for responses to WHOIS queries. */
|
||||
abstract class WhoisResponseImpl implements WhoisResponse {
|
||||
|
||||
/** Field name for ICANN problem reporting URL appended to all WHOIS responses. */
|
||||
private static final String ICANN_REPORTING_URL_FIELD =
|
||||
"URL of the ICANN Whois Inaccuracy Complaint Form";
|
||||
|
||||
/** ICANN problem reporting URL appended to all WHOIS responses. */
|
||||
private static final String ICANN_REPORTING_URL = "https://www.icann.org/wicf/";
|
||||
|
||||
/** Text to display when the field is redacted for privacy. */
|
||||
static final String REDACT_TEXT = "REDACTED FOR PRIVACY";
|
||||
|
||||
/** The time at which this response was created. */
|
||||
private final DateTime timestamp;
|
||||
|
||||
WhoisResponseImpl(DateTime timestamp) {
|
||||
this.timestamp = checkNotNull(timestamp, "timestamp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a hostname to its unicode representation if desired.
|
||||
*
|
||||
* @param hostname is assumed to be in its canonical ASCII form from the database.
|
||||
*/
|
||||
static String maybeFormatHostname(String hostname, boolean preferUnicode) {
|
||||
return preferUnicode ? Idn.toUnicode(hostname) : hostname;
|
||||
}
|
||||
|
||||
static <T> T chooseByUnicodePreference(
|
||||
boolean preferUnicode, @Nullable T localized, @Nullable T internationalized) {
|
||||
if (preferUnicode) {
|
||||
return Optional.ofNullable(localized).orElse(internationalized);
|
||||
} else {
|
||||
return Optional.ofNullable(internationalized).orElse(localized);
|
||||
}
|
||||
}
|
||||
|
||||
/** Writer for outputting data in the WHOIS format. */
|
||||
abstract static class Emitter<E extends Emitter<E>> {
|
||||
|
||||
private final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private E thisCastToDerived() {
|
||||
return (E) this;
|
||||
}
|
||||
|
||||
E emitNewline() {
|
||||
stringBuilder.append("\r\n");
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that loops over a set of values and calls {@link #emitField}. This method will
|
||||
* turn each value into a string using the provided callback and then sort those strings so the
|
||||
* textual output is deterministic (which is important for unit tests). The ideal solution would
|
||||
* be to use {@link java.util.SortedSet} but that would require reworking the models.
|
||||
*/
|
||||
<T> E emitSet(String title, Set<T> values, Function<T, String> transform) {
|
||||
return emitList(title, values.stream().map(transform).sorted().collect(toImmutableList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that loops over a list of values and calls {@link #emitField}.
|
||||
*
|
||||
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
|
||||
*/
|
||||
E emitList(String title, Iterable<String> values, boolean fullOutput) {
|
||||
for (String value : values) {
|
||||
emitField(title, value, fullOutput);
|
||||
}
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Helper method that loops over a list of values and calls {@link #emitField}. */
|
||||
E emitList(String title, Iterable<String> values) {
|
||||
return emitList(title, values, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the field name and value followed by a newline, but only if a value exists.
|
||||
*
|
||||
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
|
||||
*/
|
||||
E emitFieldIfDefined(String name, @Nullable String value, boolean fullOutput) {
|
||||
if (isNullOrEmpty(value)) {
|
||||
return thisCastToDerived();
|
||||
}
|
||||
stringBuilder.append(cleanse(name)).append(':');
|
||||
stringBuilder.append(' ').append(fullOutput ? cleanse(value) : REDACT_TEXT);
|
||||
return emitNewline();
|
||||
}
|
||||
|
||||
/** Emit the field name and value followed by a newline, but only if a value exists. */
|
||||
E emitFieldIfDefined(String name, @Nullable String value) {
|
||||
return emitFieldIfDefined(name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a multi-part field name and value followed by a newline, but only if a value exists.
|
||||
*
|
||||
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
|
||||
*/
|
||||
E emitFieldIfDefined(List<String> nameParts, String value, boolean fullOutput) {
|
||||
if (isNullOrEmpty(value)) {
|
||||
return thisCastToDerived();
|
||||
}
|
||||
return emitField(nameParts, value, fullOutput);
|
||||
}
|
||||
|
||||
/** Emit a multi-part field name and value followed by a newline, but only if a value exists. */
|
||||
E emitFieldIfDefined(List<String> nameParts, String value) {
|
||||
return emitFieldIfDefined(nameParts, value, true);
|
||||
}
|
||||
/**
|
||||
* Emit the field name and value followed by a newline. /*
|
||||
*
|
||||
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
|
||||
*/
|
||||
E emitField(String name, @Nullable String value, boolean fullOutput) {
|
||||
stringBuilder.append(cleanse(name)).append(':');
|
||||
if (!isNullOrEmpty(value)) {
|
||||
stringBuilder.append(' ').append(fullOutput ? cleanse(value) : REDACT_TEXT);
|
||||
}
|
||||
return emitNewline();
|
||||
}
|
||||
|
||||
/** Emit the field name and value followed by a newline. */
|
||||
E emitField(String name, @Nullable String value) {
|
||||
return emitField(name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a multi-part field name and value followed by a newline.
|
||||
*
|
||||
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
|
||||
*/
|
||||
E emitField(List<String> nameParts, String value, boolean fullOutput) {
|
||||
return emitField(Joiner.on(' ').join(nameParts), value, fullOutput);
|
||||
}
|
||||
|
||||
/** Emit a multi-part field name and value followed by a newline. */
|
||||
E emitField(List<String> nameParts, String value) {
|
||||
return emitField(nameParts, value, true);
|
||||
}
|
||||
|
||||
/** Emit a contact address. */
|
||||
E emitAddress(@Nullable String prefix, @Nullable Address address, boolean fullOutput) {
|
||||
prefix = isNullOrEmpty(prefix) ? "" : prefix + " ";
|
||||
if (address != null) {
|
||||
emitList(prefix + "Street", address.getStreet(), fullOutput);
|
||||
emitField(prefix + "City", address.getCity(), fullOutput);
|
||||
emitField(
|
||||
prefix + "State/Province",
|
||||
address.getState(),
|
||||
(fullOutput || prefix.equals("Registrant ")));
|
||||
emitField(prefix + "Postal Code", address.getZip(), fullOutput);
|
||||
emitField(
|
||||
prefix + "Country",
|
||||
address.getCountryCode(),
|
||||
fullOutput || prefix.equals("Registrant "));
|
||||
}
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Emit Whois Inaccuracy Complaint Form link. Only used for domain queries. */
|
||||
E emitWicfLink() {
|
||||
emitField(ICANN_REPORTING_URL_FIELD, ICANN_REPORTING_URL);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
|
||||
E emitLastUpdated(DateTime timestamp) {
|
||||
// We are assuming that our WHOIS database is always completely up to date, since it's
|
||||
// querying the live backend database.
|
||||
stringBuilder
|
||||
.append(">>> Last update of WHOIS database: ")
|
||||
.append(UtcDateTimeAdapter.getFormattedString(timestamp))
|
||||
.append(" <<<\r\n\r\n");
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
|
||||
E emitFooter(String disclaimer) {
|
||||
stringBuilder.append(disclaimer.replaceAll("\r?\n", "\r\n").trim()).append("\r\n");
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
/** Emits a string directly, followed by a newline. */
|
||||
protected E emitRawLine(String string) {
|
||||
stringBuilder.append(string);
|
||||
return emitNewline();
|
||||
}
|
||||
|
||||
/** Remove ASCII control characters like {@code \n} which could be used to forge output. */
|
||||
private String cleanse(String value) {
|
||||
return value.replaceAll("[\\x00-\\x1f]", " ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** An emitter that needs no special logic. */
|
||||
static class BasicEmitter extends Emitter<BasicEmitter> {}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package google.registry.whois;
|
||||
@@ -47,14 +47,12 @@ public final class RegistryTestServer {
|
||||
public static final ImmutableList<Route> ROUTES =
|
||||
ImmutableList.of(
|
||||
// Frontend Services
|
||||
route("/whois/*", FrontendServlet.class),
|
||||
route("/rdap/*", FrontendServlet.class),
|
||||
route("/check", FrontendServlet.class),
|
||||
route("/console-api/*", FrontendTestServlet.class),
|
||||
|
||||
// Proxy Services
|
||||
route("/_dr/epp", FrontendServlet.class),
|
||||
route("/_dr/whois", FrontendServlet.class),
|
||||
|
||||
// Registry Data Escrow (RDE)
|
||||
route("/_dr/cron/rdeCreate", BackendServlet.class),
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.ui.server;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.testing.CertificateSamples;
|
||||
import google.registry.ui.forms.FormFieldException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link RegistrarFormFields}. */
|
||||
class RegistrarFormFieldsTest {
|
||||
|
||||
@Test
|
||||
void testValidCertificate_doesntThrowError() {
|
||||
assertThat(RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.convert(CertificateSamples.SAMPLE_CERT))
|
||||
.hasValue(CertificateSamples.SAMPLE_CERT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadCertificate_throwsFfe() {
|
||||
FormFieldException thrown =
|
||||
assertThrows(
|
||||
FormFieldException.class,
|
||||
() -> RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.convert("palfun"));
|
||||
assertThat(
|
||||
thrown,
|
||||
equalTo(
|
||||
new FormFieldException("Invalid X.509 PEM certificate")
|
||||
.propagate("clientCertificate")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidCertificateHash_doesntThrowError() {
|
||||
assertThat(
|
||||
RegistrarFormFields.CLIENT_CERTIFICATE_HASH_FIELD.convert(
|
||||
CertificateSamples.SAMPLE_CERT_HASH))
|
||||
.hasValue(CertificateSamples.SAMPLE_CERT_HASH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadCertificateHash_throwsFfe() {
|
||||
FormFieldException thrown =
|
||||
assertThrows(
|
||||
FormFieldException.class,
|
||||
() -> RegistrarFormFields.CLIENT_CERTIFICATE_HASH_FIELD.convert("~~~"));
|
||||
assertThat(
|
||||
thrown,
|
||||
equalTo(
|
||||
new FormFieldException("Field must contain a base64 value.")
|
||||
.propagate("clientCertificateHash")));
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
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.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.whois.WhoisTestData.loadFile;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.whois.WhoisResponse.WhoisResponseResults;
|
||||
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;
|
||||
|
||||
/** Unit tests for {@link DomainWhoisResponse}. */
|
||||
class DomainWhoisResponseTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private Host host1;
|
||||
private Host host2;
|
||||
private RegistrarPoc abuseContact;
|
||||
private Contact adminContact;
|
||||
private Contact registrant;
|
||||
private Contact techContact;
|
||||
private Domain domain;
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
// Update the registrar to have an IANA ID.
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
loadRegistrar("NewRegistrar")
|
||||
.asBuilder()
|
||||
.setUrl("http://my.fake.url")
|
||||
.setIanaIdentifier(5555555L)
|
||||
.build());
|
||||
|
||||
abuseContact =
|
||||
persistResource(
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jake Doe")
|
||||
.setEmailAddress("jakedoe@theregistrar.com")
|
||||
.setPhoneNumber("+1.2125551216")
|
||||
.setVisibleInDomainWhoisAsAbuse(true)
|
||||
.build());
|
||||
|
||||
createTld("tld");
|
||||
|
||||
host1 =
|
||||
persistResource(
|
||||
new Host.Builder()
|
||||
.setHostName("ns01.exampleregistrar.tld")
|
||||
.setRepoId("1-ROID")
|
||||
.build());
|
||||
|
||||
host2 =
|
||||
persistResource(
|
||||
new Host.Builder()
|
||||
.setHostName("ns02.exampleregistrar.tld")
|
||||
.setRepoId("2-ROID")
|
||||
.build());
|
||||
|
||||
registrant =
|
||||
persistResource(
|
||||
new Contact.Builder()
|
||||
.setContactId("5372808-ERL")
|
||||
.setRepoId("4-ROID")
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.LOCALIZED)
|
||||
.setName("SHOULD NOT BE USED")
|
||||
.setOrg("SHOULD NOT BE USED")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.INTERNATIONALIZED)
|
||||
.setName("EXAMPLE REGISTRANT")
|
||||
.setOrg("Tom & Jerry Corp.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.5555551212")
|
||||
.setExtension("1234")
|
||||
.build())
|
||||
.setFaxNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.5555551213")
|
||||
.setExtension("4321")
|
||||
.build())
|
||||
.setEmailAddress("EMAIL@EXAMPLE.tld")
|
||||
.build());
|
||||
|
||||
adminContact =
|
||||
persistResource(
|
||||
new Contact.Builder()
|
||||
.setContactId("5372809-ERL")
|
||||
.setRepoId("5-ROID")
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.LOCALIZED)
|
||||
.setName("SHOULD NOT BE USED")
|
||||
.setOrg("SHOULD NOT BE USED")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.INTERNATIONALIZED)
|
||||
.setName("EXAMPLE REGISTRANT ADMINISTRATIVE")
|
||||
.setOrg("EXAMPLE REGISTRANT ORGANIZATION")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.5555551212")
|
||||
.setExtension("1234")
|
||||
.build())
|
||||
.setFaxNumber(
|
||||
new ContactPhoneNumber.Builder().setPhoneNumber("+1.5555551213").build())
|
||||
.setEmailAddress("EMAIL@EXAMPLE.tld")
|
||||
.build());
|
||||
|
||||
techContact =
|
||||
persistResource(
|
||||
new Contact.Builder()
|
||||
.setContactId("5372811-ERL")
|
||||
.setRepoId("6-ROID")
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.LOCALIZED)
|
||||
.setName("SHOULD NOT BE USED")
|
||||
.setOrg("SHOULD NOT BE USED")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(PostalInfo.Type.INTERNATIONALIZED)
|
||||
.setName("EXAMPLE REGISTRAR TECHNICAL")
|
||||
.setOrg("EXAMPLE REGISTRAR LLC")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
|
||||
.setCity("ANYTOWN")
|
||||
.setState("AP")
|
||||
.setZip("A1A1A1")
|
||||
.setCountryCode("EX")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.1235551234")
|
||||
.setExtension("1234")
|
||||
.build())
|
||||
.setFaxNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.5555551213")
|
||||
.setExtension("93")
|
||||
.build())
|
||||
.setEmailAddress("EMAIL@EXAMPLE.tld")
|
||||
.build());
|
||||
|
||||
VKey<Host> host1VKey = host1.createVKey();
|
||||
VKey<Host> host2VKey = host2.createVKey();
|
||||
VKey<Contact> registrantResourceKey = registrant.createVKey();
|
||||
VKey<Contact> adminResourceKey = adminContact.createVKey();
|
||||
VKey<Contact> techResourceKey = techContact.createVKey();
|
||||
|
||||
String repoId = "3-TLD";
|
||||
domain =
|
||||
persistResource(
|
||||
new Domain.Builder()
|
||||
.setDomainName("example.tld")
|
||||
.setRepoId(repoId)
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z"))
|
||||
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
|
||||
.setRegistrationExpirationTime(DateTime.parse("2010-10-08T00:44:59Z"))
|
||||
.setStatusValues(
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.CLIENT_RENEW_PROHIBITED,
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED))
|
||||
.setRegistrant(Optional.of(registrantResourceKey))
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(DesignatedContact.Type.ADMIN, adminResourceKey),
|
||||
DesignatedContact.create(DesignatedContact.Type.TECH, techResourceKey)))
|
||||
.setNameservers(ImmutableSet.of(host1VKey, host2VKey))
|
||||
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, "deadface")))
|
||||
.setGracePeriods(
|
||||
ImmutableSet.of(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.ADD, repoId, END_OF_TIME, "NewRegistrar", null),
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.TRANSFER, repoId, END_OF_TIME, "NewRegistrar", null)))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlainTextOutputTest() {
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(domain, false, "Please contact registrar", clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlainTextOutputTest_noRegistrant() {
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(
|
||||
domain.asBuilder().setRegistrant(Optional.empty()).build(),
|
||||
false,
|
||||
"Please contact registrar",
|
||||
clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_no_registrant.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlainTextOutputTest_noContacts() {
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setRegistrant(Optional.empty())
|
||||
.setContacts(ImmutableSet.of())
|
||||
.build(),
|
||||
false,
|
||||
"Please contact registrar",
|
||||
clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_no_contacts.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlainTextOutputTest_registrarAbuseInfoMissing() {
|
||||
persistResource(abuseContact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build());
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(domain, false, "Please contact registrar", clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse.getResponse(false, "Footer"))
|
||||
.isEqualTo(
|
||||
WhoisResponseResults.create(
|
||||
loadFile("whois_domain_registrar_abuse_info_missing.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlainTextOutputTest_fullOutput() {
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(domain, true, "Please contact registrar", clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_full_output.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addImplicitOkStatusTest() {
|
||||
DomainWhoisResponse domainWhoisResponse =
|
||||
new DomainWhoisResponse(
|
||||
domain.asBuilder().setStatusValues(null).build(),
|
||||
false,
|
||||
"Contact the registrar",
|
||||
clock.nowUtc());
|
||||
assertThat(
|
||||
domainWhoisResponse
|
||||
.getResponse(
|
||||
false,
|
||||
"""
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.""")
|
||||
.plainTextOutput())
|
||||
.contains("Domain Status: ok");
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.whois.WhoisTestData.loadFile;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.whois.WhoisResponse.WhoisResponseResults;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link NameserverWhoisResponse}. */
|
||||
class NameserverWhoisResponseTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private Host host1;
|
||||
private Host host2;
|
||||
private Host host3;
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
persistNewRegistrar("example", "Hänsel & Gretel Registrar, Inc.", Registrar.Type.REAL, 8L);
|
||||
persistResource(loadRegistrar("example").asBuilder().setUrl("http://my.fake.url").build());
|
||||
createTld("tld");
|
||||
Domain domain = persistResource(DatabaseHelper.newDomain("zobo.tld"));
|
||||
|
||||
host1 =
|
||||
new Host.Builder()
|
||||
.setHostName("ns1.example.tld")
|
||||
.setPersistedCurrentSponsorRegistrarId("example")
|
||||
.setInetAddresses(
|
||||
ImmutableSet.of(
|
||||
InetAddresses.forString("192.0.2.123"),
|
||||
InetAddresses.forString("2001:0DB8::1")))
|
||||
.setRepoId("1-EXAMPLE")
|
||||
.build();
|
||||
|
||||
host2 =
|
||||
new Host.Builder()
|
||||
.setHostName("ns2.example.tld")
|
||||
.setPersistedCurrentSponsorRegistrarId("example")
|
||||
.setInetAddresses(
|
||||
ImmutableSet.of(
|
||||
InetAddresses.forString("192.0.2.123"),
|
||||
InetAddresses.forString("2001:0DB8::1")))
|
||||
.setRepoId("2-EXAMPLE")
|
||||
.build();
|
||||
|
||||
host3 =
|
||||
new Host.Builder()
|
||||
.setHostName("ns1.zobo.tld")
|
||||
.setSuperordinateDomain(domain.createVKey())
|
||||
.setPersistedCurrentSponsorRegistrarId("example")
|
||||
.setInetAddresses(
|
||||
ImmutableSet.of(
|
||||
InetAddresses.forString("192.0.2.123"),
|
||||
InetAddresses.forString("2001:0DB8::1")))
|
||||
.setRepoId("3-EXAMPLE")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTextOutput() {
|
||||
NameserverWhoisResponse nameserverWhoisResponse =
|
||||
new NameserverWhoisResponse(host1, clock.nowUtc());
|
||||
assertThat(
|
||||
nameserverWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_nameserver.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMultipleNameserversResponse() {
|
||||
NameserverWhoisResponse nameserverWhoisResponse =
|
||||
new NameserverWhoisResponse(ImmutableList.of(host1, host2), clock.nowUtc());
|
||||
assertThat(
|
||||
nameserverWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_multiple_nameservers.txt"), 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubordinateDomains() {
|
||||
NameserverWhoisResponse nameserverWhoisResponse =
|
||||
new NameserverWhoisResponse(host3, clock.nowUtc());
|
||||
assertThat(
|
||||
nameserverWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_subord_nameserver.txt"), 1));
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResources;
|
||||
import static google.registry.whois.WhoisTestData.loadFile;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.whois.WhoisResponse.WhoisResponseResults;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link RegistrarWhoisResponse}. */
|
||||
class RegistrarWhoisResponseTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
|
||||
|
||||
@Test
|
||||
void getTextOutputTest() {
|
||||
Registrar registrar =
|
||||
new Registrar.Builder()
|
||||
.setRegistrarId("exregistrar")
|
||||
.setRegistrarName("Example Registrar, Inc.")
|
||||
.setType(Registrar.Type.REAL)
|
||||
.setIanaIdentifier(8L)
|
||||
.setState(Registrar.State.ACTIVE)
|
||||
.setLocalizedAddress(
|
||||
new RegistrarAddress.Builder()
|
||||
.setStreet(ImmutableList.of("1234 Admiralty Way"))
|
||||
.setCity("Marina del Rey")
|
||||
.setState("CA")
|
||||
.setZip("90292")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.setPhoneNumber("+1.3105551212")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setEmailAddress("registrar@example.tld")
|
||||
.setWhoisServer("whois.example-registrar.tld")
|
||||
.setUrl("http://my.fake.url")
|
||||
.build();
|
||||
// Use the registrar key for contacts' parent.
|
||||
ImmutableList<RegistrarPoc> contacts =
|
||||
ImmutableList.of(
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Joe Registrar")
|
||||
.setEmailAddress("joeregistrar@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551213")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.setVisibleInWhoisAsTech(false)
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("John Doe")
|
||||
.setEmailAddress("johndoe@example-registrar.tld")
|
||||
.setPhoneNumber("+1.1111111111")
|
||||
.setFaxNumber("+1.1111111111")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jane Registrar")
|
||||
.setEmailAddress("janeregistrar@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551214")
|
||||
.setFaxNumber("+1.3105551213")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
|
||||
.setVisibleInWhoisAsAdmin(true)
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Jane Doe")
|
||||
.setEmailAddress("janedoe@example-registrar.tld")
|
||||
.setPhoneNumber("+1.1111111112")
|
||||
.setFaxNumber("+1.1111111112")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.build(),
|
||||
new RegistrarPoc.Builder()
|
||||
.setRegistrar(registrar)
|
||||
.setName("Bonnie & Clyde")
|
||||
.setEmailAddress("johngeek@example-registrar.tld")
|
||||
.setPhoneNumber("+1.3105551215")
|
||||
.setFaxNumber("+1.3105551216")
|
||||
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
|
||||
.setVisibleInWhoisAsTech(true)
|
||||
.build());
|
||||
persistResource(registrar);
|
||||
persistResources(contacts);
|
||||
|
||||
RegistrarWhoisResponse registrarWhoisResponse =
|
||||
new RegistrarWhoisResponse(registrar, clock.nowUtc());
|
||||
assertThat(
|
||||
registrarWhoisResponse.getResponse(
|
||||
false,
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
|
||||
.isEqualTo(WhoisResponseResults.create(loadFile("whois_registrar.txt"), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetOfFields() {
|
||||
Registrar registrar =
|
||||
persistNewRegistrar("exregistrar", "Ex-Registrar", Registrar.Type.REAL, 8L);
|
||||
|
||||
RegistrarWhoisResponse registrarWhoisResponse =
|
||||
new RegistrarWhoisResponse(registrar, clock.nowUtc());
|
||||
// Just make sure this doesn't NPE.
|
||||
registrarWhoisResponse.getResponse(
|
||||
false, "Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.");
|
||||
}
|
||||
}
|
||||
@@ -1,679 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
|
||||
import static google.registry.model.registrar.Registrar.State.ACTIVE;
|
||||
import static google.registry.model.registrar.Registrar.Type.PDT;
|
||||
import static google.registry.model.tld.Tlds.getTlds;
|
||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResources;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeDomain;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPocs;
|
||||
import static google.registry.whois.WhoisTestData.loadFile;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.FullFieldsTestEntityHelper;
|
||||
import google.registry.testing.TestCacheExtension;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link WhoisAction}. */
|
||||
public class WhoisActionTest {
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2009-06-29T20:13:00Z"));
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@RegisterExtension
|
||||
public final TestCacheExtension testCacheExtension =
|
||||
new TestCacheExtension.Builder()
|
||||
.withEppResourceCache(Duration.ofDays(1))
|
||||
.withForeignKeyCache(Duration.ofDays(1))
|
||||
.build();
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
|
||||
private WhoisAction newWhoisAction(String input) {
|
||||
WhoisAction whoisAction = new WhoisAction();
|
||||
whoisAction.clock = clock;
|
||||
whoisAction.input = new StringReader(input);
|
||||
whoisAction.response = response;
|
||||
whoisAction.whoisReader =
|
||||
new WhoisReader(
|
||||
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA: %s");
|
||||
whoisAction.whoisMetrics = new WhoisMetrics();
|
||||
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
|
||||
whoisAction.disclaimer =
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.";
|
||||
whoisAction.retrier = new Retrier(new FakeSleeper(clock), 3);
|
||||
return whoisAction;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
createTlds("lol", "xn--q9jyb4c", "1.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_badRequest_stillSends200() {
|
||||
newWhoisAction("\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_no_command.txt"));
|
||||
}
|
||||
|
||||
private static Domain makeDomainWithRegistrar(Registrar registrar) {
|
||||
return makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "Goblin Market", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
registrar);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_works() {
|
||||
Registrar registrar =
|
||||
persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(makeDomainWithRegistrar(registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_usesCache() {
|
||||
Registrar registrar =
|
||||
persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(makeDomainWithRegistrar(registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
// Populate the cache for both the domain and contact.
|
||||
Domain domain = loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc()).get();
|
||||
Contact contact =
|
||||
loadByForeignKeyByCacheIfEnabled(Contact.class, "5372808-ERL", clock.nowUtc()).get();
|
||||
// Make a change to the domain and contact that won't be seen because the cache will be hit.
|
||||
persistResource(domain.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setInternationalizedPostalInfo(
|
||||
contact
|
||||
.getInternationalizedPostalInfo()
|
||||
.asBuilder()
|
||||
.setOrg("Two by Two, Hands Blue Inc.")
|
||||
.build())
|
||||
.build());
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_registeredDomainUnaffectedByBsa() {
|
||||
persistResource(
|
||||
Tld.get("lol")
|
||||
.asBuilder()
|
||||
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
|
||||
.build());
|
||||
persistBsaLabel("cat");
|
||||
|
||||
Registrar registrar = persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(makeDomainWithRegistrar(registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_unregisteredDomainShowBsaMessage() {
|
||||
persistResource(
|
||||
Tld.get("lol")
|
||||
.asBuilder()
|
||||
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
|
||||
.build());
|
||||
persistBsaLabel("cat");
|
||||
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_blocked_by_bsa.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainAfterTransfer_hasUpdatedEppTimeAndClientId() {
|
||||
Registrar registrar = persistResource(makeRegistrar("TheRegistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(
|
||||
makeDomainWithRegistrar(registrar)
|
||||
.asBuilder()
|
||||
.setTransferData(
|
||||
new DomainTransferData.Builder()
|
||||
.setGainingRegistrarId("TheRegistrar")
|
||||
.setLosingRegistrarId("NewRegistrar")
|
||||
.setTransferRequestTime(DateTime.parse("2009-05-29T20:13:00Z"))
|
||||
.setPendingTransferExpirationTime(DateTime.parse("2010-03-01T00:00:00Z"))
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setTransferRequestTrid(Trid.create("client-trid", "server-trid"))
|
||||
.build())
|
||||
.build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
clock.setTo(DateTime.parse("2011-01-01T00:00:00Z"));
|
||||
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_transferred_domain.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_idnDomain_works() {
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.みんな\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_punycode.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_punycodeDomain_works() {
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.xn--q9jyb4c\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_punycode.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainNotFound_returns200OkAndPlainTextResponse() {
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainNotFound_usesCache() {
|
||||
// Populate the cache with the nonexistence of this domain.
|
||||
assertThat(loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc())).isEmpty();
|
||||
// Add a new valid cat.lol domain that won't be found because the cache will be hit instead.
|
||||
persistActiveDomain("cat.lol");
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
// todo (b/27378695): reenable or delete this test
|
||||
@Disabled
|
||||
@Test
|
||||
void testRun_domainInTestTld_isConsideredNotFound() {
|
||||
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainFlaggedAsDeletedInDatabase_isConsideredNotFound() {
|
||||
Registrar registrar;
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(registrar = makeRegistrar("example", "Example Registrar", ACTIVE)))
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().minusDays(1))
|
||||
.build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deleted domain and an active domain with the same label, and make sure only the active
|
||||
* one is returned.
|
||||
*/
|
||||
@Test
|
||||
void testRun_domainDeletedThenRecreated_isFound() {
|
||||
Registrar registrar;
|
||||
Domain domain1 =
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost(
|
||||
"ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(makeRegistrar("example", "Example Registrar", ACTIVE)))
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc().minusDays(2))
|
||||
.setDeletionTime(clock.nowUtc().minusDays(1))
|
||||
.build());
|
||||
Domain domain2 =
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372809-ERL", "Mrs. Alice Crypto", "alice@example.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372809-IRL", "Mr. Bob Crypto", "bob@example.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372809-TRL", "Dr. Pablo", "pmy@example.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns1.google.lol", "9.9.9.9")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.google.lol", "4311::f143")),
|
||||
persistResource(
|
||||
registrar = makeRegistrar("example", "Example Registrar", ACTIVE)))
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(clock.nowUtc())
|
||||
.build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
assertThat(domain1.getRepoId()).isNotEqualTo(domain2.getRepoId());
|
||||
newWhoisAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.google.lol");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverQuery_works() {
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setUrl("http://my.fake.url").build());
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipv6_displaysInCollapsedReadableFormat() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "bad:f00d:cafe::15:beef"));
|
||||
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.lol");
|
||||
// The most important thing this tests is that the outputted address is compressed!
|
||||
assertThat(response.getPayload()).contains("bad:f00d:cafe::15:beef");
|
||||
assertThat(response.getPayload()).doesNotContain("bad:f00d:cafe:0:0:0:15:beef");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_idnNameserver_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.みんな\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserver_usesCache() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.xn--q9jyb4c", "1.2.3.4"));
|
||||
// Populate the cache.
|
||||
Host host =
|
||||
loadByForeignKeyByCacheIfEnabled(Host.class, "ns1.cat.xn--q9jyb4c", clock.nowUtc()).get();
|
||||
// Make a change to the persisted host that won't be seen because the cache will be hit.
|
||||
persistResource(
|
||||
host.asBuilder()
|
||||
.setInetAddresses(ImmutableSet.of(InetAddresses.forString("8.8.8.8")))
|
||||
.build());
|
||||
newWhoisAction("nameserver ns1.cat.みんな\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_punycodeNameserver_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.xn--q9jyb4c\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverNotFound_returns200AndText() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.lulz\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverFlaggedAsDeletedInDatabase_doesntGetLeaked() {
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().minusDays(1))
|
||||
.build());
|
||||
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipNameserverLookup_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisAction("nameserver 1.2.3.4").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.lol");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipMapsToMultipleNameservers_theyAllGetReturned() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "1.2.3.4"));
|
||||
newWhoisAction("nameserver 1.2.3.4").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.lol");
|
||||
assertThat(response.getPayload()).contains("ns2.cat.lol");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipMapsToMultipleNameserverInDifferentTlds_showsThemAll() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.xn--q9jyb4c", "1.2.3.4"));
|
||||
newWhoisAction("nameserver 1.2.3.4").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.lol");
|
||||
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipNameserverEntityDoesNotExist_returns200NotFound() {
|
||||
newWhoisAction("nameserver feed:a:bee::acab\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_ip_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_ipMapsToNameserverUnderNonAuthoritativeTld_notFound() {
|
||||
assertThat(getTlds()).doesNotContain("com");
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.google.com", "1.2.3.4"));
|
||||
newWhoisAction("nameserver 1.2.3.4").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_ip_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverUnderNonAuthoritativeTld_notFound() {
|
||||
assertThat(getTlds()).doesNotContain("com");
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.google.com", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.google.com").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
|
||||
}
|
||||
|
||||
// todo (b/27378695): reenable or delete this test
|
||||
@Disabled
|
||||
@Test
|
||||
void testRun_nameserverInTestTld_notFound() {
|
||||
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.lol").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookup_works() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
// Notice the partial search without "inc".
|
||||
newWhoisAction("registrar example registrar").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_pdtRegistrarLookup_works() {
|
||||
Registrar registrar =
|
||||
persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE)
|
||||
.asBuilder()
|
||||
.setIanaIdentifier(9995L)
|
||||
.setType(PDT)
|
||||
.build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
// Notice the partial search without "inc".
|
||||
newWhoisAction("registrar example registrar").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookupInPendingState_returnsNotFound() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.PENDING));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("registrar Example Registrar, Inc.").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookupWithTestType_returnsNotFound() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE)
|
||||
.asBuilder()
|
||||
.setIanaIdentifier(null)
|
||||
.setType(Registrar.Type.TEST)
|
||||
.build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisAction("registrar Example Registrar, Inc.").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_multilevelDomain_isNotConsideredAHostname() {
|
||||
Registrar registrar =
|
||||
persistResource(makeRegistrar("example", "Example Registrar", ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.1.test",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.1.test")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.1.test")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "The Raven", "bog@cat.1.test")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.1.test", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.1.test", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
|
||||
newWhoisAction("domain cat.1.test\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("Domain Name: cat.1.test\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_hostnameWithMultilevelTld_isStillConsideredHostname() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.1.test", "1.2.3.4"));
|
||||
newWhoisAction("nameserver ns1.cat.1.test\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("ns1.cat.1.test");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForSuccessfulCommand() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "1.2.3.4"));
|
||||
WhoisAction action = newWhoisAction("nameserver 1.2.3.4");
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
action.run();
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock)
|
||||
.setCommandName("NameserverLookupByIp")
|
||||
.setNumResults(2)
|
||||
.setStatus(SC_OK)
|
||||
.build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForUnsuccessfulCommand() {
|
||||
WhoisAction action = newWhoisAction("domain cat.lol\r\n");
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
action.run();
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock)
|
||||
.setCommandName("DomainLookup")
|
||||
.setNumResults(0)
|
||||
.setStatus(SC_NOT_FOUND)
|
||||
.build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForInternalServerError() throws Exception {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
WhoisAction action = newWhoisAction("ns1.cat.lol");
|
||||
action.whoisReader = mock(WhoisReader.class);
|
||||
when(action.whoisReader.readCommand(any(Reader.class), eq(false), any(DateTime.class)))
|
||||
.thenThrow(new IOException("missing cat interface"));
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
|
||||
action.run();
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock)
|
||||
.setNumResults(0)
|
||||
.setStatus(SC_INTERNAL_SERVER_ERROR)
|
||||
.build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
assertThat(response.getPayload()).isEqualTo("Internal Server Error");
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
// 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.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.newHost;
|
||||
import static google.registry.testing.DatabaseHelper.newTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.TestCacheExtension;
|
||||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
class WhoisCommandFactoryTest {
|
||||
|
||||
FakeClock clock = new FakeClock();
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@RegisterExtension
|
||||
final TestCacheExtension testCacheExtension =
|
||||
new TestCacheExtension.Builder().withEppResourceCache(Duration.ofSeconds(1000000)).build();
|
||||
|
||||
WhoisCommandFactory noncachedFactory = WhoisCommandFactory.createNonCached();
|
||||
WhoisCommandFactory cachedFactory = WhoisCommandFactory.createCached();
|
||||
Domain domain;
|
||||
Host host;
|
||||
Registrar otherRegistrar;
|
||||
|
||||
int origSingletonCacheRefreshSeconds;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
persistResource(newTld("tld", "TLD"));
|
||||
host =
|
||||
newHost("ns.example.tld")
|
||||
.asBuilder()
|
||||
.setInetAddresses(ImmutableSet.of(InetAddress.getByName("1.2.3.4")))
|
||||
.build();
|
||||
persistResource(host);
|
||||
domain = DatabaseHelper.newDomain("example.tld", host);
|
||||
persistResource(domain);
|
||||
otherRegistrar = persistNewRegistrar("OtherRegistrar");
|
||||
otherRegistrar =
|
||||
persistResource(
|
||||
otherRegistrar
|
||||
.asBuilder()
|
||||
.setState(Registrar.State.ACTIVE)
|
||||
.setPhoneNumber("+1.2223334444")
|
||||
.build());
|
||||
|
||||
// In addition to the TestCacheExtension, we have to set a long singleton cache timeout.
|
||||
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 1000000;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
// Restore the singleton cache timeout. For some reason, this doesn't work if we store the
|
||||
// original value in an instance variable (I suspect there may be some overlap in test
|
||||
// execution) so just restore to zero.
|
||||
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonCached_NameserverLookupByHostCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
noncachedFactory
|
||||
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
|
||||
// Note that we can't use persistResource() for these as that clears the cache.
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().put(
|
||||
host.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
|
||||
.build()));
|
||||
response =
|
||||
noncachedFactory
|
||||
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: OtherRegistrar name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCached_NameserverLookupByHostCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
cachedFactory
|
||||
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().put(
|
||||
host.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
|
||||
.build()));
|
||||
response =
|
||||
cachedFactory
|
||||
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonCached_DomainLookupCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
noncachedFactory
|
||||
.domainLookup(
|
||||
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().put(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
|
||||
.build()));
|
||||
response =
|
||||
noncachedFactory
|
||||
.domainLookup(
|
||||
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: OtherRegistrar name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCached_DomainLookupCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
cachedFactory
|
||||
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED", "Not tested")
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().put(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
|
||||
.build()));
|
||||
response =
|
||||
cachedFactory
|
||||
.domainLookup(
|
||||
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonCached_RegistrarLookupCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Phone Number: +1.2223334444");
|
||||
|
||||
tm().transact(
|
||||
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
|
||||
response = noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Phone Number: +1.2345677890");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCached_RegistrarLookupCommand() throws Exception {
|
||||
WhoisResponse response =
|
||||
cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Phone Number: +1.2223334444");
|
||||
|
||||
tm().transact(
|
||||
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
|
||||
response = cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Phone Number: +1.2223334444");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonCached_NameserverLookupByIpCommand() throws Exception {
|
||||
// Note that this lookup currently doesn't cache the hosts, so there's no point in testing the
|
||||
// "cached" case. This test is here so that it will fail if anyone adds caching.
|
||||
WhoisResponse response =
|
||||
noncachedFactory
|
||||
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: The Registrar");
|
||||
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().put(
|
||||
host.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
|
||||
.build()));
|
||||
response =
|
||||
noncachedFactory
|
||||
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
|
||||
.executeQuery(clock.nowUtc());
|
||||
assertThat(response.getResponse(false, "").plainTextOutput())
|
||||
.contains("Registrar: OtherRegistrar");
|
||||
}
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
|
||||
import static google.registry.model.registrar.Registrar.State.ACTIVE;
|
||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResources;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeDomain;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
|
||||
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPocs;
|
||||
import static google.registry.whois.WhoisTestData.loadFile;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.FullFieldsTestEntityHelper;
|
||||
import google.registry.whois.WhoisMetrics.WhoisMetric;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
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.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WhoisHttpAction}.
|
||||
*
|
||||
* <p>This class should be limited to testing the HTTP interface, as the bulk of the WHOIS testing
|
||||
* can be found in {@link WhoisActionTest}.
|
||||
*/
|
||||
class WhoisHttpActionTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2009-06-29T20:13:00Z"));
|
||||
|
||||
private WhoisHttpAction newWhoisHttpAction(String pathInfo) {
|
||||
WhoisHttpAction whoisAction = new WhoisHttpAction();
|
||||
whoisAction.clock = clock;
|
||||
whoisAction.expires = Duration.standardHours(1);
|
||||
whoisAction.requestPath = WhoisHttpAction.PATH + pathInfo;
|
||||
whoisAction.response = response;
|
||||
whoisAction.whoisReader =
|
||||
new WhoisReader(
|
||||
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA: %s");
|
||||
whoisAction.whoisMetrics = new WhoisMetrics();
|
||||
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
|
||||
whoisAction.disclaimer =
|
||||
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.";
|
||||
return whoisAction;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTlds("lol", "xn--q9jyb4c", "1.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_emptyQuery_returns400BadRequestWithPlainTextOutput() {
|
||||
newWhoisHttpAction("").run();
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_no_command.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_badUrlEncoding_returns400BadRequestWithPlainTextOutput() {
|
||||
newWhoisHttpAction("nic.%u307F%u3093%u306A").run();
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_malformed_path.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainNotFound_returns404StatusAndPlainTextResponse() {
|
||||
newWhoisHttpAction("/domain/cat.lol").run();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
// todo (b/27378695): reenable or delete this test
|
||||
@Disabled
|
||||
@Test
|
||||
void testRun_domainInTestTld_isConsideredNotFound() {
|
||||
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("/domain/cat.lol").run();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQueryIdn_works() {
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("/domain/cat.みんな").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_utf8.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_wickedLineFeedForgeryInDatabase_crlfSubstitutedWithSpace() {
|
||||
Contact trl =
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "Eric Schmidt", "bog@cat.みんな");
|
||||
trl =
|
||||
persistResource(
|
||||
trl.asBuilder()
|
||||
.setInternationalizedPostalInfo(
|
||||
trl.getInternationalizedPostalInfo()
|
||||
.asBuilder()
|
||||
.setOrg("Galactic\r\nEmpire")
|
||||
.build())
|
||||
.build());
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
trl,
|
||||
trl,
|
||||
trl,
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(
|
||||
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
|
||||
newWhoisHttpAction("/domain/cat.みんな").run();
|
||||
assertThat(response.getPayload()).contains("Galactic Empire");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainOnly_works() {
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "Eric Schmidt", "bog@cat.みんな")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(
|
||||
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
|
||||
newWhoisHttpAction("cat.みんな").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).contains("Domain Name: cat.みんな\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_registeredDomainUnaffectedByBsa() {
|
||||
persistResource(
|
||||
Tld.get("lol")
|
||||
.asBuilder()
|
||||
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
|
||||
.build());
|
||||
persistBsaLabel("cat");
|
||||
|
||||
Registrar registrar = persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQuery_unregisteredDomainShowBsaMessage() {
|
||||
persistResource(
|
||||
Tld.get("lol")
|
||||
.asBuilder()
|
||||
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
|
||||
.build());
|
||||
persistBsaLabel("cat");
|
||||
|
||||
newWhoisHttpAction("domain cat.lol\r\n").run();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_blocked_by_bsa.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_hostnameOnly_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisHttpAction("ns1.cat.みんな").run();
|
||||
assertThat(response.getPayload()).contains("Server Name: ns1.cat.みんな\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_domainQueryPunycode_works() {
|
||||
Registrar registrar = persistResource(makeRegistrar(
|
||||
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.みんな",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
|
||||
registrar));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("/domain/cat.xn--q9jyb4c").run();
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_utf8.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverQuery_works() {
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setUrl("http://my.fake.url").build());
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
|
||||
}
|
||||
|
||||
// todo (b/27378695): reenable or delete this test
|
||||
@Disabled
|
||||
@Test
|
||||
void testRun_nameserverQueryInTestTld_notFound() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_lastUpdateTimestamp_isPresentInResponse() {
|
||||
clock.setTo(DateTime.parse("2020-07-12T23:52:43Z"));
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
|
||||
assertThat(response.getPayload())
|
||||
.contains(">>> Last update of WHOIS database: 2020-07-12T23:52:43Z <<<");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverQueryIdn_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.みんな").run();
|
||||
assertThat(response.getPayload()).contains("ns1.cat.みんな");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_nameserverQueryPunycode_works() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.xn--q9jyb4c").run();
|
||||
assertThat(response.getPayload()).contains("ns1.cat.みんな");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_trailingSlashInPath_getsIgnored() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
|
||||
newWhoisHttpAction("/nameserver/ns1.cat.xn--q9jyb4c/").run();
|
||||
assertThat(response.getPayload()).contains("ns1.cat.みんな");
|
||||
assertThat(response.getPayload()).contains("1.2.3.4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_uppercaseDomain_ignoresCasing() {
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "Eric Schmidt", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(
|
||||
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
|
||||
newWhoisHttpAction("/domain/cat.LOL").run();
|
||||
assertThat(response.getPayload()).contains("Domain Name: cat.lol\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_hairyPath_getsDecoded() {
|
||||
persistResource(
|
||||
makeDomain(
|
||||
"cat.lol",
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.lol")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeContact(
|
||||
"5372808-TRL", "Eric Schmidt", "bog@cat.lol")),
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
|
||||
persistResource(
|
||||
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
|
||||
persistResource(
|
||||
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
|
||||
// python -c "print ''.join('%' + hex(ord(c))[2:] for c in 'cat.lol')"
|
||||
newWhoisHttpAction("/domain/%63%61%74%2e%6c%6f%6c").run();
|
||||
assertThat(response.getPayload()).contains("Domain Name: cat.lol\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookup_works() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.ACTIVE));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
// Notice the partial search without "inc".
|
||||
newWhoisHttpAction("/registrar/Example%20Registrar").run();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookupInPendingState_returnsNotFound() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.PENDING));
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("/registrar/Example%20Registrar,%20Inc.").run();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_registrarLookupWithTestType_returnsNotFound() {
|
||||
Registrar registrar = persistResource(
|
||||
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.ACTIVE)
|
||||
.asBuilder().setType(Registrar.Type.TEST).setIanaIdentifier(null).build());
|
||||
persistResources(makeRegistrarPocs(registrar));
|
||||
newWhoisHttpAction("/registrar/Example%20Registrar,%20Inc.").run();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForSuccessfulCommand() {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
WhoisHttpAction action = newWhoisHttpAction("/nameserver/ns1.cat.lol");
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
action.run();
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock)
|
||||
.setCommandName("NameserverLookupByHost")
|
||||
.setNumResults(1)
|
||||
.setStatus(SC_OK)
|
||||
.build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForUnsuccessfulCommand() {
|
||||
WhoisHttpAction action = newWhoisHttpAction("nic.%u307F%u3093%u306A");
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
action.run();
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock).setNumResults(0).setStatus(SC_BAD_REQUEST).build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_metricsLoggedForInternalServerError() throws Exception {
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
WhoisHttpAction action = newWhoisHttpAction("ns1.cat.lol");
|
||||
action.whoisReader = mock(WhoisReader.class);
|
||||
when(action.whoisReader.readCommand(any(Reader.class), eq(false), any(DateTime.class)))
|
||||
.thenThrow(new IOException("missing cat interface"));
|
||||
action.whoisMetrics = mock(WhoisMetrics.class);
|
||||
|
||||
RuntimeException thrown = assertThrows(RuntimeException.class, action::run);
|
||||
assertThat(thrown).hasCauseThat().hasMessageThat().isEqualTo("missing cat interface");
|
||||
WhoisMetric expected =
|
||||
WhoisMetric.builderForRequest(clock)
|
||||
.setNumResults(0)
|
||||
.setStatus(SC_INTERNAL_SERVER_ERROR)
|
||||
.build();
|
||||
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.testing.FullFieldsTestEntityHelper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for Dagger injection of the whois package. */
|
||||
final class WhoisInjectionTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final HttpServletRequest req = mock(HttpServletRequest.class);
|
||||
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
|
||||
private final StringWriter httpOutput = new StringWriter();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
when(rsp.getWriter()).thenReturn(new PrintWriter(httpOutput));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhoisAction_injectsAndWorks() throws Exception {
|
||||
createTld("lol");
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
when(req.getReader()).thenReturn(new BufferedReader(new StringReader("ns1.cat.lol\r\n")));
|
||||
DaggerWhoisTestComponent.builder()
|
||||
.requestModule(new RequestModule(req, rsp))
|
||||
.build()
|
||||
.whoisAction()
|
||||
.run();
|
||||
verify(rsp).setStatus(200);
|
||||
assertThat(httpOutput.toString()).contains("ns1.cat.lol");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhoisHttpAction_injectsAndWorks() {
|
||||
createTld("lol");
|
||||
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
|
||||
when(req.getRequestURI()).thenReturn("/whois/ns1.cat.lol");
|
||||
DaggerWhoisTestComponent.builder()
|
||||
.requestModule(new RequestModule(req, rsp))
|
||||
.build()
|
||||
.whoisHttpAction()
|
||||
.run();
|
||||
verify(rsp).setStatus(200);
|
||||
assertThat(httpOutput.toString()).contains("ns1.cat.lol");
|
||||
}
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.testing.TestLogHandler;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.util.JdkLoggerConfig;
|
||||
import java.io.StringReader;
|
||||
import java.util.logging.Level;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Unit tests for {@link WhoisReader}. */
|
||||
class WhoisReaderTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final FakeClock clock = new FakeClock();
|
||||
private final TestLogHandler testLogHandler = new TestLogHandler();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTlds("tld", "xn--kgbechtv", "1.test");
|
||||
JdkLoggerConfig.getConfig(WhoisReader.class).addHandler(testLogHandler);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
|
||||
<T> T readCommand(String commandStr) throws Exception {
|
||||
return (T)
|
||||
new WhoisReader(
|
||||
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA")
|
||||
.readCommand(new StringReader(commandStr), false, clock.nowUtc());
|
||||
}
|
||||
|
||||
void assertLoadsExampleTld(String commandString) throws Exception {
|
||||
DomainLookupCommand command = readCommand(commandString);
|
||||
assertThat(command.domainName.toString()).isEqualTo("example.tld");
|
||||
}
|
||||
|
||||
void assertLoadsIDN(String commandString) throws Exception {
|
||||
DomainLookupCommand command = readCommand(commandString);
|
||||
assertThat(command.domainName.toString()).isEqualTo("xn--mgbh0fb.xn--kgbechtv");
|
||||
}
|
||||
|
||||
void assertLoadsExampleNs(String commandString) throws Exception {
|
||||
NameserverLookupByHostCommand command = readCommand(commandString);
|
||||
assertThat(command.hostName.toString()).isEqualTo("ns.example.tld");
|
||||
}
|
||||
|
||||
void assertLoadsIDNNs(String commandString) throws Exception {
|
||||
NameserverLookupByHostCommand command = readCommand(commandString);
|
||||
assertThat(command.hostName.toString()).isEqualTo("ns.xn--mgbh0fb.xn--kgbechtv");
|
||||
}
|
||||
|
||||
void assertNsLookup(String commandString, String expectedIpAddress) throws Exception {
|
||||
assertThat(
|
||||
this.<NameserverLookupByIpCommand>readCommand(commandString).ipAddress.getHostAddress())
|
||||
.isEqualTo(expectedIpAddress);
|
||||
}
|
||||
|
||||
void assertLoadsRegistrar(String commandString) throws Exception {
|
||||
assertThat(this.<RegistrarLookupCommand>readCommand(commandString).registrarName)
|
||||
.isEqualTo("Example Registrar, Inc.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupWithOneToken() throws Exception {
|
||||
assertThat(this.<RegistrarLookupCommand>readCommand("Example").registrarName)
|
||||
.isEqualTo("Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainLookupWithoutCRLF() throws Exception {
|
||||
assertLoadsExampleTld("example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhitespaceOnDomainLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleTld(" \t domain \t \t example.tld \r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainLookup() throws Exception {
|
||||
assertLoadsExampleTld("example.tld\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleTld("domain example.tld\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCaseInsensitiveDomainLookup() throws Exception {
|
||||
assertLoadsExampleTld("EXAMPLE.TLD\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCaseInsensitiveDomainLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleTld("DOMAIN EXAMPLE.TLD\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNULabelDomainLookup() throws Exception {
|
||||
assertLoadsIDN("مثال.إختبار\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNULabelDomainLookupWithCommand() throws Exception {
|
||||
assertLoadsIDN("domain مثال.إختبار\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNALabelDomainLookupWithCommand() throws Exception {
|
||||
assertLoadsIDN("domain xn--mgbh0fb.xn--kgbechtv\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNALabelDomainLookup() throws Exception {
|
||||
assertLoadsIDN("xn--mgbh0fb.xn--kgbechtv\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooManyArgsDomainLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("domain example.tld foo.bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooFewArgsDomainLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("domain"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegalArgDomainLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("domain 1.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupWithoutCRLF() throws Exception {
|
||||
assertLoadsExampleNs("ns.example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhitespaceOnNameserverLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleNs(" \t nameserver \t \t ns.example.tld \r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookup() throws Exception {
|
||||
assertLoadsExampleNs("ns.example.tld\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeepNameserverLookup() throws Exception {
|
||||
NameserverLookupByHostCommand command = readCommand("ns.foo.bar.baz.example.tld\r\n");
|
||||
assertThat(command.hostName.toString()).isEqualTo("ns.foo.bar.baz.example.tld");
|
||||
assertThat(command.hostName.toString()).isEqualTo("ns.foo.bar.baz.example.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleNs("nameserver ns.example.tld\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCaseInsensitiveNameserverLookup() throws Exception {
|
||||
assertLoadsExampleNs("NS.EXAMPLE.TLD\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCaseInsensitiveNameserverLookupWithCommand() throws Exception {
|
||||
assertLoadsExampleNs("NAMESERVER NS.EXAMPLE.TLD\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNULabelNameserverLookup() throws Exception {
|
||||
assertLoadsIDNNs("ns.مثال.إختبار\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNULabelNameserverLookupWithCommand() throws Exception {
|
||||
assertLoadsIDNNs("nameserver ns.مثال.إختبار\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNALabelNameserverLookupWithCommand() throws Exception {
|
||||
assertLoadsIDNNs("nameserver ns.xn--mgbh0fb.xn--kgbechtv\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIDNALabelNameserverLookup() throws Exception {
|
||||
assertLoadsIDNNs("ns.xn--mgbh0fb.xn--kgbechtv\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooManyArgsNameserverLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("nameserver ns.example.tld foo.bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooFewArgsNameserverLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("nameserver"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegalArgNameserverLookup() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("nameserver 1.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookup() throws Exception {
|
||||
assertLoadsRegistrar("registrar Example Registrar, Inc.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupCaseInsensitive() throws Exception {
|
||||
assertLoadsRegistrar("REGISTRAR Example Registrar, Inc.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupWhitespace() throws Exception {
|
||||
assertLoadsRegistrar(" \t registrar \t \tExample Registrar, Inc. ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupByDefault() throws Exception {
|
||||
assertLoadsRegistrar("Example Registrar, Inc.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupOnTLD() throws Exception {
|
||||
assertThat(this.<RegistrarLookupCommand>readCommand("com").registrarName).isEqualTo("com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegistrarLookupNoArgs() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("registrar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIp() throws Exception {
|
||||
assertNsLookup("43.34.12.213", "43.34.12.213");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpv6() throws Exception {
|
||||
assertNsLookup("1080:0:0:0:8:800:200c:417a", "1080:0:0:0:8:800:200c:417a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByCompressedIpv6() throws Exception {
|
||||
assertNsLookup("1080::8:800:200c:417a", "1080:0:0:0:8:800:200c:417a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByNoncanonicalIpv6() throws Exception {
|
||||
assertNsLookup("1080:0:0:0:8:800:200C:417A", "1080:0:0:0:8:800:200c:417a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByBackwardsCompatibleIpv6() throws Exception {
|
||||
assertNsLookup("::FFFF:129.144.52.38", "129.144.52.38");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpWithCommand() throws Exception {
|
||||
assertNsLookup("nameserver 43.34.12.213", "43.34.12.213");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpv6WithCommand() throws Exception {
|
||||
assertNsLookup("nameserver 1080:0:0:0:8:800:200C:417a", "1080:0:0:0:8:800:200c:417a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpCaseInsenstive() throws Exception {
|
||||
assertNsLookup("NAMESERVER 43.34.12.213", "43.34.12.213");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpWhitespace() throws Exception {
|
||||
assertNsLookup(" \t\t NAMESERVER \t 43.34.12.213 \r\n", "43.34.12.213");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverLookupByIpTooManyArgs() {
|
||||
assertThrows(WhoisException.class, () -> readCommand("nameserver 43.34.12.213 43.34.12.213"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultilevelDomainLookup() throws Exception {
|
||||
this.<DomainLookupCommand>readCommand("example.1.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultilevelNameserverLookup() throws Exception {
|
||||
this.<NameserverLookupByHostCommand>readCommand("ns.example.1.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeepMultilevelNameserverLookup() throws Exception {
|
||||
this.<NameserverLookupByHostCommand>readCommand("ns.corp.example.1.test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnconfiguredTld() throws Exception {
|
||||
this.<RegistrarLookupCommand>readCommand("example.test");
|
||||
this.<RegistrarLookupCommand>readCommand("1.example.test");
|
||||
this.<RegistrarLookupCommand>readCommand("ns.example.2.test");
|
||||
this.<RegistrarLookupCommand>readCommand("test");
|
||||
this.<RegistrarLookupCommand>readCommand("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoArgs() {
|
||||
assertThrows(WhoisException.class, () -> readCommand(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsDomainLookupCommand() throws Exception {
|
||||
readCommand("domain example.tld");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting domain lookup command using domain name 'example.tld'.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsNameserverLookupCommandWithIpAddress() throws Exception {
|
||||
readCommand("nameserver 43.34.12.213");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO,
|
||||
"Attempting nameserver lookup command using 43.34.12.213 as an IP address.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsNameserverLookupCommandWithHostname() throws Exception {
|
||||
readCommand("nameserver ns.example.tld");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting nameserver lookup command using ns.example.tld as a hostname.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsRegistrarLookupCommand() throws Exception {
|
||||
readCommand("registrar Example Registrar, Inc.");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO,
|
||||
"Attempting registrar lookup command using registrar Example Registrar, Inc.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsSingleArgumentNameserverLookupUsingIpAddress() throws Exception {
|
||||
readCommand("43.34.12.213");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting nameserver lookup using 43.34.12.213 as an IP address.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsSingleArgumentRegistrarLookup() throws Exception {
|
||||
readCommand("test");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting registrar lookup using test as a registrar.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsSingleArgumentDomainLookup() throws Exception {
|
||||
readCommand("example.tld");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting domain lookup using example.tld as a domain name.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsSingleArgumentNameserverLookupUsingHostname() throws Exception {
|
||||
readCommand("ns.example.tld");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO, "Attempting nameserver lookup using ns.example.tld as a hostname.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogsMultipleArgumentsButNoParticularCommand() throws Exception {
|
||||
readCommand("Example Registrar, Inc.");
|
||||
assertAboutLogs()
|
||||
.that(testLogHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO,
|
||||
"Attempting registrar lookup employing Example Registrar, Inc. as a registrar.");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import dagger.Component;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.util.UtilsModule;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Component(
|
||||
modules = {
|
||||
ConfigModule.class,
|
||||
RequestModule.class,
|
||||
UtilsModule.class,
|
||||
WhoisModule.class,
|
||||
})
|
||||
interface WhoisTestComponent {
|
||||
WhoisHttpAction whoisHttpAction();
|
||||
|
||||
WhoisAction whoisAction();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.whois;
|
||||
|
||||
import google.registry.testing.TestDataHelper;
|
||||
|
||||
/** Test helper methods for the whois package. */
|
||||
final class WhoisTestData {
|
||||
|
||||
/**
|
||||
* Loads test data from file in {@code testdata/} directory, "fixing" newlines to have the ending
|
||||
* that WHOIS requires.
|
||||
*/
|
||||
static String loadFile(String filename) {
|
||||
return TestDataHelper.loadFile(WhoisTestData.class, filename).replaceAll("\r?\n", "\r\n");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
|
||||
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
|
||||
PUBAPI /check CheckApiAction GET n NONE PUBLIC
|
||||
PUBAPI /rdap/ RdapEmptyAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
|
||||
@@ -12,4 +11,3 @@ PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /ready/pubapi ReadinessProbeActionPubApi GET n NONE PUBLIC
|
||||
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
|
||||
|
||||
@@ -56,7 +56,6 @@ BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction
|
||||
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
|
||||
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
|
||||
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
|
||||
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
|
||||
PUBAPI /check CheckApiAction GET n NONE PUBLIC
|
||||
PUBAPI /rdap/ RdapEmptyAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
|
||||
@@ -69,7 +68,6 @@ PUBAPI /rdap/ip/(*) RdapIpAction
|
||||
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
|
||||
PUBAPI /ready/pubapi ReadinessProbeActionPubApi GET n NONE PUBLIC
|
||||
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
|
||||
CONSOLE /console-api/bulk-domain ConsoleBulkDomainAction POST n USER PUBLIC
|
||||
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
|
||||
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
Domain Name: cat.lol
|
||||
Registry Domain ID: 9-LOL
|
||||
Registrar WHOIS Server: whois.example.com
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2110-10-08T00:44:59Z
|
||||
Registrar: Yes Virginia
|
||||
Registrar IANA ID: 1
|
||||
Registrar Abuse Contact Email: jakedoe@example.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: GOOGLE INCORPORATED <script>
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: BM
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: US
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns1.cat.lol
|
||||
Name Server: ns2.cat.lol
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
Blocked by BSA: cat.lol
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
Domain not found.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,59 +0,0 @@
|
||||
Domain Name: cat.xn--q9jyb4c
|
||||
Registry Domain ID: 9-Q9JYB4C
|
||||
Registrar WHOIS Server: whois.example.com
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2110-10-08T00:44:59Z
|
||||
Registrar: Yes Virginia
|
||||
Registrar IANA ID: 1
|
||||
Registrar Abuse Contact Email: jakedoe@example.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: GOOGLE INCORPORATED <script>
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: BM
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: US
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns1.cat.xn--q9jyb4c
|
||||
Name Server: ns2.cat.xn--q9jyb4c
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,59 +0,0 @@
|
||||
Domain Name: cat.みんな
|
||||
Registry Domain ID: 9-Q9JYB4C
|
||||
Registrar WHOIS Server: whois.example.com
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2110-10-08T00:44:59Z
|
||||
Registrar: Yes Virginia
|
||||
Registrar IANA ID: 1
|
||||
Registrar Abuse Contact Email: jakedoe@example.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: GOOGLE INCORPORATED <script>
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: BM
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: US
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns1.cat.みんな
|
||||
Name Server: ns2.cat.みんな
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
No nameservers found.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
Malformed path query.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,10 +0,0 @@
|
||||
Server Name: ns1.cat.lol
|
||||
IP Address: 1.2.3.4
|
||||
Registrar: The Registrar
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
Nameserver not found.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
No WHOIS command specified.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,24 +0,0 @@
|
||||
Registrar: Example Registrar, Inc.
|
||||
Street: 123 Example Boulevard <script>
|
||||
City: Williamsburg <script>
|
||||
State/Province: NY
|
||||
Postal Code: 11211
|
||||
Country: US
|
||||
Phone Number: +1.2125551212
|
||||
Fax Number: +1.2125551213
|
||||
Email: contact-us@example.com
|
||||
Registrar WHOIS Server: whois.example.com
|
||||
Registrar URL: http://my.fake.url
|
||||
Admin Contact: Jane Doe
|
||||
Phone Number: +1.2125551215
|
||||
Fax Number: +1.2125551216
|
||||
Email: janedoe@example.com
|
||||
Technical Contact: John Doe
|
||||
Phone Number: +1.2125551213
|
||||
Fax Number: +1.2125551213
|
||||
Email: johndoe@example.com
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,6 +0,0 @@
|
||||
No registrar found.
|
||||
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,59 +0,0 @@
|
||||
Domain Name: cat.lol
|
||||
Registry Domain ID: 9-LOL
|
||||
Registrar WHOIS Server: whois.example.com
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2010-03-06T00:00:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2020-03-01T00:00:00Z
|
||||
Registrar: Yes Virginia
|
||||
Registrar IANA ID: 1
|
||||
Registrar Abuse Contact Email: jakedoe@example.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: GOOGLE INCORPORATED <script>
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: BM
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: US
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns1.cat.lol
|
||||
Name Server: ns2.cat.lol
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2011-01-01T00:00:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,66 +0,0 @@
|
||||
Domain Name: example.tld
|
||||
Registry Domain ID: 3-TLD
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2010-10-08T00:44:59Z
|
||||
Registrar: New Registrar
|
||||
Registrar IANA ID: 5555555
|
||||
Registrar Abuse Contact Email: jakedoe@theregistrar.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: addPeriod https://icann.org/epp#addPeriod
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: Tom & Jerry Corp.
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: AP
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: EX
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Phone Ext: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Fax Ext: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Phone Ext: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Phone Ext: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Fax Ext: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns01.exampleregistrar.tld
|
||||
Name Server: ns02.exampleregistrar.tld
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,66 +0,0 @@
|
||||
Domain Name: example.tld
|
||||
Registry Domain ID: 3-TLD
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2010-10-08T00:44:59Z
|
||||
Registrar: New Registrar
|
||||
Registrar IANA ID: 5555555
|
||||
Registrar Abuse Contact Email: jakedoe@theregistrar.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: addPeriod https://icann.org/epp#addPeriod
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
|
||||
Registry Registrant ID: 4-ROID
|
||||
Registrant Name: EXAMPLE REGISTRANT
|
||||
Registrant Organization: Tom & Jerry Corp.
|
||||
Registrant Street: 123 EXAMPLE STREET
|
||||
Registrant City: ANYTOWN
|
||||
Registrant State/Province: AP
|
||||
Registrant Postal Code: A1A1A1
|
||||
Registrant Country: EX
|
||||
Registrant Phone: +1.5555551212
|
||||
Registrant Phone Ext: 1234
|
||||
Registrant Fax: +1.5555551213
|
||||
Registrant Fax Ext: 4321
|
||||
Registrant Email: EMAIL@EXAMPLE.tld
|
||||
Registry Admin ID: 5-ROID
|
||||
Admin Name: EXAMPLE REGISTRANT ADMINISTRATIVE
|
||||
Admin Organization: EXAMPLE REGISTRANT ORGANIZATION
|
||||
Admin Street: 123 EXAMPLE STREET
|
||||
Admin City: ANYTOWN
|
||||
Admin State/Province: AP
|
||||
Admin Postal Code: A1A1A1
|
||||
Admin Country: EX
|
||||
Admin Phone: +1.5555551212
|
||||
Admin Phone Ext: 1234
|
||||
Admin Fax: +1.5555551213
|
||||
Admin Email: EMAIL@EXAMPLE.tld
|
||||
Registry Tech ID: 6-ROID
|
||||
Tech Name: EXAMPLE REGISTRAR TECHNICAL
|
||||
Tech Organization: EXAMPLE REGISTRAR LLC
|
||||
Tech Street: 123 EXAMPLE STREET
|
||||
Tech City: ANYTOWN
|
||||
Tech State/Province: AP
|
||||
Tech Postal Code: A1A1A1
|
||||
Tech Country: EX
|
||||
Tech Phone: +1.1235551234
|
||||
Tech Phone Ext: 1234
|
||||
Tech Fax: +1.5555551213
|
||||
Tech Fax Ext: 93
|
||||
Tech Email: EMAIL@EXAMPLE.tld
|
||||
Name Server: ns01.exampleregistrar.tld
|
||||
Name Server: ns02.exampleregistrar.tld
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,28 +0,0 @@
|
||||
Domain Name: example.tld
|
||||
Registry Domain ID: 3-TLD
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2010-10-08T00:44:59Z
|
||||
Registrar: New Registrar
|
||||
Registrar IANA ID: 5555555
|
||||
Registrar Abuse Contact Email: jakedoe@theregistrar.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: addPeriod https://icann.org/epp#addPeriod
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
|
||||
Name Server: ns01.exampleregistrar.tld
|
||||
Name Server: ns02.exampleregistrar.tld
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,53 +0,0 @@
|
||||
Domain Name: example.tld
|
||||
Registry Domain ID: 3-TLD
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2010-10-08T00:44:59Z
|
||||
Registrar: New Registrar
|
||||
Registrar IANA ID: 5555555
|
||||
Registrar Abuse Contact Email: jakedoe@theregistrar.com
|
||||
Registrar Abuse Contact Phone: +1.2125551216
|
||||
Domain Status: addPeriod https://icann.org/epp#addPeriod
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Phone Ext: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Phone Ext: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Fax Ext: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns01.exampleregistrar.tld
|
||||
Name Server: ns02.exampleregistrar.tld
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,64 +0,0 @@
|
||||
Domain Name: example.tld
|
||||
Registry Domain ID: 3-TLD
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
Updated Date: 2009-05-29T20:13:00Z
|
||||
Creation Date: 2000-10-08T00:45:00Z
|
||||
Registry Expiry Date: 2010-10-08T00:44:59Z
|
||||
Registrar: New Registrar
|
||||
Registrar IANA ID: 5555555
|
||||
Registrar Abuse Contact Email:
|
||||
Registrar Abuse Contact Phone:
|
||||
Domain Status: addPeriod https://icann.org/epp#addPeriod
|
||||
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
|
||||
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
|
||||
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
|
||||
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
|
||||
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
|
||||
Registry Registrant ID: REDACTED FOR PRIVACY
|
||||
Registrant Name: REDACTED FOR PRIVACY
|
||||
Registrant Organization: Tom & Jerry Corp.
|
||||
Registrant Street: REDACTED FOR PRIVACY
|
||||
Registrant City: REDACTED FOR PRIVACY
|
||||
Registrant State/Province: AP
|
||||
Registrant Postal Code: REDACTED FOR PRIVACY
|
||||
Registrant Country: EX
|
||||
Registrant Phone: REDACTED FOR PRIVACY
|
||||
Registrant Phone Ext: REDACTED FOR PRIVACY
|
||||
Registrant Fax: REDACTED FOR PRIVACY
|
||||
Registrant Fax Ext: REDACTED FOR PRIVACY
|
||||
Registrant Email: Please contact registrar
|
||||
Registry Admin ID: REDACTED FOR PRIVACY
|
||||
Admin Name: REDACTED FOR PRIVACY
|
||||
Admin Organization: REDACTED FOR PRIVACY
|
||||
Admin Street: REDACTED FOR PRIVACY
|
||||
Admin City: REDACTED FOR PRIVACY
|
||||
Admin State/Province: REDACTED FOR PRIVACY
|
||||
Admin Postal Code: REDACTED FOR PRIVACY
|
||||
Admin Country: REDACTED FOR PRIVACY
|
||||
Admin Phone: REDACTED FOR PRIVACY
|
||||
Admin Phone Ext: REDACTED FOR PRIVACY
|
||||
Admin Fax: REDACTED FOR PRIVACY
|
||||
Admin Email: Please contact registrar
|
||||
Registry Tech ID: REDACTED FOR PRIVACY
|
||||
Tech Name: REDACTED FOR PRIVACY
|
||||
Tech Organization: REDACTED FOR PRIVACY
|
||||
Tech Street: REDACTED FOR PRIVACY
|
||||
Tech City: REDACTED FOR PRIVACY
|
||||
Tech State/Province: REDACTED FOR PRIVACY
|
||||
Tech Postal Code: REDACTED FOR PRIVACY
|
||||
Tech Country: REDACTED FOR PRIVACY
|
||||
Tech Phone: REDACTED FOR PRIVACY
|
||||
Tech Phone Ext: REDACTED FOR PRIVACY
|
||||
Tech Fax: REDACTED FOR PRIVACY
|
||||
Tech Fax Ext: REDACTED FOR PRIVACY
|
||||
Tech Email: Please contact registrar
|
||||
Name Server: ns01.exampleregistrar.tld
|
||||
Name Server: ns02.exampleregistrar.tld
|
||||
DNSSEC: signedDelegation
|
||||
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
For more information on Whois status codes, please visit https://icann.org/epp
|
||||
|
||||
Footer
|
||||
@@ -1,18 +0,0 @@
|
||||
Server Name: ns1.example.tld
|
||||
IP Address: 192.0.2.123
|
||||
IP Address: 2001:db8::1
|
||||
Registrar: Hänsel & Gretel Registrar, Inc.
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
|
||||
Server Name: ns2.example.tld
|
||||
IP Address: 192.0.2.123
|
||||
IP Address: 2001:db8::1
|
||||
Registrar: Hänsel & Gretel Registrar, Inc.
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,11 +0,0 @@
|
||||
Server Name: ns1.example.tld
|
||||
IP Address: 192.0.2.123
|
||||
IP Address: 2001:db8::1
|
||||
Registrar: Hänsel & Gretel Registrar, Inc.
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,28 +0,0 @@
|
||||
Registrar: Example Registrar, Inc.
|
||||
Street: 1234 Admiralty Way
|
||||
City: Marina del Rey
|
||||
State/Province: CA
|
||||
Postal Code: 90292
|
||||
Country: US
|
||||
Phone Number: +1.3105551212
|
||||
Fax Number: +1.3105551213
|
||||
Email: registrar@example.tld
|
||||
Registrar WHOIS Server: whois.example-registrar.tld
|
||||
Registrar URL: http://my.fake.url
|
||||
Admin Contact: Jane Registrar
|
||||
Phone Number: +1.3105551214
|
||||
Fax Number: +1.3105551213
|
||||
Email: janeregistrar@example-registrar.tld
|
||||
Admin Contact: Joe Registrar
|
||||
Phone Number: +1.3105551213
|
||||
Fax Number: +1.3105551213
|
||||
Email: joeregistrar@example-registrar.tld
|
||||
Technical Contact: Bonnie & Clyde
|
||||
Phone Number: +1.3105551215
|
||||
Fax Number: +1.3105551216
|
||||
Email: johngeek@example-registrar.tld
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -1,11 +0,0 @@
|
||||
Server Name: ns1.zobo.tld
|
||||
IP Address: 192.0.2.123
|
||||
IP Address: 2001:db8::1
|
||||
Registrar: The Registrar
|
||||
Registrar WHOIS Server: whois.nic.fakewhois.example
|
||||
Registrar URL: http://my.fake.url
|
||||
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
|
||||
|
||||
Doodle Disclaimer
|
||||
I exist so that carriage return
|
||||
in disclaimer can be tested.
|
||||
@@ -29,7 +29,6 @@ public class Prober {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Obtains WebWhois Sequence provided by proberComponent
|
||||
ImmutableSet<ProbingSequence> sequences = ImmutableSet.copyOf(proberComponent.sequences());
|
||||
|
||||
// Tells Sequences to start running
|
||||
|
||||
@@ -20,7 +20,6 @@ import dagger.Provides;
|
||||
import google.registry.monitoring.blackbox.connection.ProbingAction;
|
||||
import google.registry.monitoring.blackbox.module.CertificateModule;
|
||||
import google.registry.monitoring.blackbox.module.EppModule;
|
||||
import google.registry.monitoring.blackbox.module.WebWhoisModule;
|
||||
import google.registry.networking.handler.SslClientInitializer;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.SystemClock;
|
||||
@@ -102,13 +101,10 @@ public class ProberModule {
|
||||
@Component(
|
||||
modules = {
|
||||
ProberModule.class,
|
||||
WebWhoisModule.class,
|
||||
EppModule.class,
|
||||
CertificateModule.class
|
||||
})
|
||||
public interface ProberComponent {
|
||||
|
||||
// Standard WebWhois sequence
|
||||
Set<ProbingSequence> sequences();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.monitoring.blackbox.connection.ProbingAction;
|
||||
import google.registry.monitoring.blackbox.connection.Protocol;
|
||||
import google.registry.monitoring.blackbox.exception.ConnectionException;
|
||||
import google.registry.monitoring.blackbox.exception.FailureException;
|
||||
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
|
||||
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
|
||||
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
|
||||
import google.registry.monitoring.blackbox.message.InboundMessageType;
|
||||
import google.registry.monitoring.blackbox.module.WebWhoisModule.HttpWhoisProtocol;
|
||||
import google.registry.monitoring.blackbox.module.WebWhoisModule.HttpsWhoisProtocol;
|
||||
import google.registry.monitoring.blackbox.module.WebWhoisModule.WebWhoisProtocol;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* Subclass of {@link ActionHandler} that deals with the WebWhois Sequence
|
||||
*
|
||||
* <p>Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response
|
||||
* implies a redirection it follows the redirection until either an Error Response is received, or
|
||||
* {@link HttpResponseStatus#OK} is received
|
||||
*/
|
||||
public class WebWhoisActionHandler extends ActionHandler {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/* Dagger injected components necessary for redirect responses: */
|
||||
|
||||
/** {@link Bootstrap} necessary for remaking connection on redirect response. */
|
||||
private final Bootstrap bootstrap;
|
||||
|
||||
/** {@link Protocol} for when redirected to http endpoint. */
|
||||
private final Protocol httpWhoisProtocol;
|
||||
|
||||
/** {@link Protocol} for when redirected to https endpoint. */
|
||||
private final Protocol httpsWhoisProtocol;
|
||||
|
||||
/** {@link HttpRequestMessage} that represents default GET message to be sent on redirect. */
|
||||
private final HttpRequestMessage requestMessage;
|
||||
|
||||
@Inject
|
||||
public WebWhoisActionHandler(
|
||||
@WebWhoisProtocol Bootstrap bootstrap,
|
||||
@HttpWhoisProtocol Protocol httpWhoisProtocol,
|
||||
@HttpsWhoisProtocol Protocol httpsWhoisProtocol,
|
||||
HttpRequestMessage requestMessage) {
|
||||
|
||||
this.bootstrap = bootstrap;
|
||||
this.httpWhoisProtocol = httpWhoisProtocol;
|
||||
this.httpsWhoisProtocol = httpsWhoisProtocol;
|
||||
this.requestMessage = requestMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* After receiving {@link HttpResponseMessage}, either notes success and marks future as finished,
|
||||
* notes an error in the received {@link URL} and throws a {@link ConnectionException}, received a
|
||||
* response indicating a Failure, or receives a redirection response, where it follows the
|
||||
* redirects until receiving one of the previous three responses.
|
||||
*/
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, InboundMessageType msg)
|
||||
throws FailureException, UndeterminedStateException {
|
||||
|
||||
HttpResponseMessage response = (HttpResponseMessage) msg;
|
||||
|
||||
if (response.status().equals(HttpResponseStatus.OK)) {
|
||||
logger.atInfo().log("Received Successful HttpResponseStatus");
|
||||
logger.atInfo().log("Response Received: %s", response);
|
||||
|
||||
// On success, we always pass message to ActionHandler's channelRead0 method.
|
||||
super.channelRead0(ctx, msg);
|
||||
|
||||
} else if (response.status().equals(HttpResponseStatus.MOVED_PERMANENTLY)
|
||||
|| response.status().equals(HttpResponseStatus.FOUND)) {
|
||||
// TODO - Fix checker to better determine when we have encountered a redirection response.
|
||||
|
||||
// Obtain url to be redirected to
|
||||
URL url;
|
||||
try {
|
||||
url = new URI(response.headers().get("Location")).toURL();
|
||||
} catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {
|
||||
// in case of error, log it, and let ActionHandler's exceptionThrown method deal with it
|
||||
throw new FailureException(
|
||||
"Redirected Location was invalid. Given Location was: "
|
||||
+ response.headers().get("Location"));
|
||||
}
|
||||
// From url, extract new host, port, and path
|
||||
String newHost = url.getHost();
|
||||
String newPath = url.getPath();
|
||||
|
||||
logger.atInfo().log(
|
||||
String.format(
|
||||
"Redirected to %s with host: %s, port: %d, and path: %s",
|
||||
url, newHost, url.getDefaultPort(), newPath));
|
||||
|
||||
// Construct new Protocol to reflect redirected host, path, and port
|
||||
Protocol newProtocol;
|
||||
if (url.getProtocol().equals(httpWhoisProtocol.name())) {
|
||||
newProtocol = httpWhoisProtocol;
|
||||
} else if (url.getProtocol().equals(httpsWhoisProtocol.name())) {
|
||||
newProtocol = httpsWhoisProtocol;
|
||||
} else {
|
||||
throw new FailureException(
|
||||
"Redirection Location port was invalid. Given protocol name was: " + url.getProtocol());
|
||||
}
|
||||
|
||||
// Obtain HttpRequestMessage with modified headers to reflect new host and path.
|
||||
HttpRequestMessage httpRequest = requestMessage.modifyMessage(newHost, newPath);
|
||||
|
||||
// Create new probingAction that takes in the new Protocol and HttpRequestMessage with no
|
||||
// delay
|
||||
ProbingAction redirectedAction =
|
||||
ProbingAction.builder()
|
||||
.setBootstrap(bootstrap)
|
||||
.setProtocol(newProtocol)
|
||||
.setOutboundMessage(httpRequest)
|
||||
.setDelay(Duration.ZERO)
|
||||
.setHost(newHost)
|
||||
.build();
|
||||
|
||||
// close this channel as we no longer need it
|
||||
ChannelFuture future = ctx.close();
|
||||
future.addListener(
|
||||
f -> {
|
||||
if (f.isSuccess()) {
|
||||
logger.atInfo().log("Successfully closed connection.");
|
||||
} else {
|
||||
logger.atWarning().log("Channel was unsuccessfully closed.");
|
||||
}
|
||||
|
||||
// Once channel is closed, establish new connection to redirected host, and repeat
|
||||
// same actions
|
||||
ChannelFuture secondFuture = redirectedAction.call();
|
||||
|
||||
// Once we have a successful call, set original ChannelPromise as success to tell
|
||||
// ProbingStep we can move on
|
||||
secondFuture.addListener(
|
||||
f2 -> {
|
||||
if (f2.isSuccess()) {
|
||||
super.channelRead0(ctx, msg);
|
||||
} else {
|
||||
if (f2 instanceof FailureException) {
|
||||
throw new FailureException(f2.cause());
|
||||
} else {
|
||||
throw new UndeterminedStateException(f2.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Add in metrics Handling that informs MetricsCollector the response was a FAILURE
|
||||
logger.atWarning().log(String.format("Received unexpected response: %s", response.status()));
|
||||
throw new FailureException("Response received from remote site was: " + response.status());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
|
||||
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
|
||||
import google.registry.monitoring.blackbox.message.InboundMessageType;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link io.netty.channel.ChannelHandler} that converts inbound {@link FullHttpResponse} to custom
|
||||
* type {@link HttpResponseMessage} and retains {@link HttpRequestMessage} in case of reuse for
|
||||
* redirection.
|
||||
*/
|
||||
public class WebWhoisMessageHandler extends ChannelDuplexHandler {
|
||||
|
||||
@Inject
|
||||
public WebWhoisMessageHandler() {}
|
||||
|
||||
/** Retains {@link HttpRequestMessage} and calls super write method. */
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
HttpRequestMessage request = (HttpRequestMessage) msg;
|
||||
request.retain();
|
||||
super.write(ctx, request, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@link FullHttpResponse} to {@link HttpResponseMessage}, so it is an {@link
|
||||
* InboundMessageType} instance.
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
FullHttpResponse originalResponse = (FullHttpResponse) msg;
|
||||
HttpResponseMessage response = new HttpResponseMessage(originalResponse);
|
||||
super.channelRead(ctx, response);
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.module;
|
||||
|
||||
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.networking.handler.SslClientInitializer.createSslClientInitializerWithSystemTrustStore;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.monitoring.blackbox.ProbingSequence;
|
||||
import google.registry.monitoring.blackbox.ProbingStep;
|
||||
import google.registry.monitoring.blackbox.connection.Protocol;
|
||||
import google.registry.monitoring.blackbox.handler.WebWhoisActionHandler;
|
||||
import google.registry.monitoring.blackbox.handler.WebWhoisMessageHandler;
|
||||
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
|
||||
import google.registry.monitoring.blackbox.metric.MetricsCollector;
|
||||
import google.registry.monitoring.blackbox.token.WebWhoisToken;
|
||||
import google.registry.networking.handler.SslClientInitializer;
|
||||
import google.registry.util.Clock;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* A module that provides the components necessary for and the overall {@link ProbingSequence} to
|
||||
* probe WebWHOIS.
|
||||
*/
|
||||
@Module
|
||||
public class WebWhoisModule {
|
||||
|
||||
private static final String HTTP_PROTOCOL_NAME = "http";
|
||||
private static final String HTTPS_PROTOCOL_NAME = "https";
|
||||
private static final int HTTP_WHOIS_PORT = 80;
|
||||
private static final int HTTPS_WHOIS_PORT = 443;
|
||||
|
||||
/** Standard length of messages used by Proxy. Equates to 0.5 MB. */
|
||||
private static final int maximumMessageLengthBytes = 512 * 1024;
|
||||
|
||||
/** {@link Provides} only step used in WebWhois sequence. */
|
||||
@Provides
|
||||
@WebWhoisProtocol
|
||||
static ProbingStep provideWebWhoisStep(
|
||||
@HttpWhoisProtocol Protocol httpWhoisProtocol,
|
||||
@WebWhoisProtocol Bootstrap bootstrap,
|
||||
HttpRequestMessage messageTemplate,
|
||||
Duration duration) {
|
||||
|
||||
return ProbingStep.builder()
|
||||
.setProtocol(httpWhoisProtocol)
|
||||
.setBootstrap(bootstrap)
|
||||
.setMessageTemplate(messageTemplate)
|
||||
.setDuration(duration)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** {@link Provides} the {@link Protocol} that corresponds to http connection. */
|
||||
@Singleton
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static Protocol provideHttpWhoisProtocol(
|
||||
@HttpWhoisProtocol int httpWhoisPort,
|
||||
@HttpWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.builder()
|
||||
.setName(HTTP_PROTOCOL_NAME)
|
||||
.setPort(httpWhoisPort)
|
||||
.setHandlerProviders(handlerProviders)
|
||||
.setPersistentConnection(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** {@link Provides} the {@link Protocol} that corresponds to https connection. */
|
||||
@Singleton
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static Protocol provideHttpsWhoisProtocol(
|
||||
@HttpsWhoisProtocol int httpsWhoisPort,
|
||||
@HttpsWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.builder()
|
||||
.setName(HTTPS_PROTOCOL_NAME)
|
||||
.setPort(httpsWhoisPort)
|
||||
.setHandlerProviders(handlerProviders)
|
||||
.setPersistentConnection(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for http
|
||||
* protocol.
|
||||
*/
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpWhoisHandlerProviders(
|
||||
Provider<HttpClientCodec> httpClientCodecProvider,
|
||||
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
|
||||
Provider<WebWhoisMessageHandler> messageHandlerProvider,
|
||||
Provider<WebWhoisActionHandler> webWhoisActionHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
httpClientCodecProvider,
|
||||
httpObjectAggregatorProvider,
|
||||
messageHandlerProvider,
|
||||
webWhoisActionHandlerProvider);
|
||||
}
|
||||
/**
|
||||
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for https
|
||||
* protocol.
|
||||
*/
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpsWhoisHandlerProviders(
|
||||
@HttpsWhoisProtocol
|
||||
Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
|
||||
Provider<HttpClientCodec> httpClientCodecProvider,
|
||||
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
|
||||
Provider<WebWhoisMessageHandler> messageHandlerProvider,
|
||||
Provider<WebWhoisActionHandler> webWhoisActionHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
sslClientInitializerProvider,
|
||||
httpClientCodecProvider,
|
||||
httpObjectAggregatorProvider,
|
||||
messageHandlerProvider,
|
||||
webWhoisActionHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpClientCodec provideHttpClientCodec() {
|
||||
return new HttpClientCodec();
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpObjectAggregator provideHttpObjectAggregator(@WebWhoisProtocol int maxContentLength) {
|
||||
return new HttpObjectAggregator(maxContentLength);
|
||||
}
|
||||
|
||||
/** {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}. */
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
|
||||
SslProvider sslProvider) {
|
||||
return createSslClientInitializerWithSystemTrustStore(
|
||||
sslProvider,
|
||||
channel -> channel.attr(REMOTE_ADDRESS_KEY).get(),
|
||||
channel -> channel.attr(PROTOCOL_KEY).get().port());
|
||||
}
|
||||
|
||||
/** {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. */
|
||||
@Singleton
|
||||
@Provides
|
||||
@WebWhoisProtocol
|
||||
static Bootstrap provideBootstrap(
|
||||
EventLoopGroup eventLoopGroup, Class<? extends Channel> channelClazz) {
|
||||
return new Bootstrap().group(eventLoopGroup).channel(channelClazz);
|
||||
}
|
||||
|
||||
/** {@link Provides} standard WebWhois sequence. */
|
||||
@Provides
|
||||
@Singleton
|
||||
@IntoSet
|
||||
ProbingSequence provideWebWhoisSequence(
|
||||
@WebWhoisProtocol ProbingStep probingStep,
|
||||
WebWhoisToken webWhoisToken,
|
||||
MetricsCollector metrics,
|
||||
Clock clock) {
|
||||
return new ProbingSequence.Builder(webWhoisToken, metrics, clock).add(probingStep).build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WebWhoisProtocol
|
||||
int provideMaximumMessageLengthBytes() {
|
||||
return maximumMessageLengthBytes;
|
||||
}
|
||||
|
||||
/** {@link Provides} the list of top level domains to be probed */
|
||||
@Singleton
|
||||
@Provides
|
||||
@WebWhoisProtocol
|
||||
ImmutableList<String> provideTopLevelDomains() {
|
||||
return ImmutableList.of("how", "soy", "xn--q9jyb4c");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
int provideHttpWhoisPort() {
|
||||
return HTTP_WHOIS_PORT;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
int provideHttpsWhoisPort() {
|
||||
return HTTPS_WHOIS_PORT;
|
||||
}
|
||||
|
||||
/** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
public @interface HttpWhoisProtocol {}
|
||||
|
||||
/** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
public @interface HttpsWhoisProtocol {}
|
||||
|
||||
/** Dagger qualifier to provide any WebWhois related bindings. */
|
||||
@Qualifier
|
||||
public @interface WebWhoisProtocol {}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.token;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.PeekingIterator;
|
||||
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
|
||||
import google.registry.monitoring.blackbox.message.OutboundMessageType;
|
||||
import google.registry.monitoring.blackbox.module.WebWhoisModule.WebWhoisProtocol;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link Token} subtype designed for WebWhois sequence.
|
||||
*
|
||||
* <p>Between loops of a WebWhois sequence the only thing changing is the tld we are probing. As a
|
||||
* result, we maintain the list of {@code topLevelDomains} and on each call to next, have our index
|
||||
* looking at the next {@code topLevelDomain}.
|
||||
*/
|
||||
public class WebWhoisToken extends Token {
|
||||
|
||||
/** For each top level domain (tld), we probe "prefix.tld". */
|
||||
private static final String PREFIX = "whois.nic.";
|
||||
|
||||
/** {@link PeekingIterator} over a cycle of all top level domains to be probed. */
|
||||
private final PeekingIterator<String> tldCycleIterator;
|
||||
|
||||
@Inject
|
||||
public WebWhoisToken(@WebWhoisProtocol ImmutableList<String> topLevelDomainsList) {
|
||||
checkArgument(!topLevelDomainsList.isEmpty(), "topLevelDomainsList must not be empty.");
|
||||
|
||||
this.tldCycleIterator =
|
||||
Iterators.peekingIterator(Iterables.cycle(topLevelDomainsList).iterator());
|
||||
}
|
||||
|
||||
/** Moves on to next top level domain in {@code tldCycleIterator}. */
|
||||
@Override
|
||||
public WebWhoisToken next() {
|
||||
tldCycleIterator.next();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Modifies message to reflect the new host coming from the new top level domain. */
|
||||
@Override
|
||||
public OutboundMessageType modifyMessage(OutboundMessageType original)
|
||||
throws UndeterminedStateException {
|
||||
return original.modifyMessage(host());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns host as the concatenation of fixed {@code prefix} and current value of {@code
|
||||
* topLevelDomains}.
|
||||
*/
|
||||
@Override
|
||||
public String host() {
|
||||
return PREFIX + tldCycleIterator.peek();
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY;
|
||||
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpGetRequest;
|
||||
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
|
||||
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.connection.Protocol;
|
||||
import google.registry.monitoring.blackbox.exception.FailureException;
|
||||
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
|
||||
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
|
||||
import google.registry.monitoring.blackbox.testserver.TestServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import jakarta.inject.Provider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WebWhoisActionHandler}.
|
||||
*
|
||||
* <p>Attempts to test how well {@link WebWhoisActionHandler} works when responding to all possible
|
||||
* types of responses
|
||||
*/
|
||||
class WebWhoisActionHandlerTest {
|
||||
|
||||
private static final int HTTP_PORT = 80;
|
||||
private static final String HTTP_REDIRECT = "http://";
|
||||
private static final String TARGET_HOST = "whois.nic.tld";
|
||||
private static final String DUMMY_URL = "__WILL_NOT_WORK__";
|
||||
private final Protocol standardProtocol =
|
||||
Protocol.builder()
|
||||
.setHandlerProviders(
|
||||
ImmutableList.of(() -> new WebWhoisActionHandler(null, null, null, null)))
|
||||
.setName("http")
|
||||
.setPersistentConnection(false)
|
||||
.setPort(HTTP_PORT)
|
||||
.build();
|
||||
|
||||
private EmbeddedChannel channel;
|
||||
private ActionHandler actionHandler;
|
||||
private Provider<? extends ChannelHandler> actionHandlerProvider;
|
||||
private Protocol initialProtocol;
|
||||
private HttpRequestMessage msg;
|
||||
|
||||
/** Creates default protocol with empty list of handlers and specified other inputs */
|
||||
private Protocol createProtocol(String name, int port, boolean persistentConnection) {
|
||||
return Protocol.builder()
|
||||
.setName(name)
|
||||
.setPort(port)
|
||||
.setHandlerProviders(ImmutableList.of(actionHandlerProvider))
|
||||
.setPersistentConnection(persistentConnection)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Initializes new WebWhoisActionHandler */
|
||||
private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) {
|
||||
actionHandler =
|
||||
new WebWhoisActionHandler(bootstrap, standardProtocol, standardProtocol, messageTemplate);
|
||||
actionHandlerProvider = () -> actionHandler;
|
||||
}
|
||||
|
||||
/** Sets up testing channel with requisite attributes */
|
||||
private void setupChannel(Protocol protocol) {
|
||||
channel = new EmbeddedChannel(actionHandler);
|
||||
channel.attr(PROTOCOL_KEY).set(protocol);
|
||||
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
|
||||
}
|
||||
|
||||
private Bootstrap makeBootstrap(EventLoopGroup group) {
|
||||
return new Bootstrap().group(group).channel(LocalChannel.class);
|
||||
}
|
||||
|
||||
private void setup(String hostName, Bootstrap bootstrap, boolean persistentConnection) {
|
||||
msg = new HttpRequestMessage(makeHttpGetRequest(hostName, ""));
|
||||
setupActionHandler(bootstrap, msg);
|
||||
initialProtocol = createProtocol("testProtocol", 0, persistentConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasic_responseOk() {
|
||||
// setup
|
||||
setup("", null, true);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
|
||||
|
||||
// assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
// assesses that we successfully received good response and protocol is unchanged
|
||||
assertThat(future.isSuccess()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasic_responseFailure_badRequest() {
|
||||
// setup
|
||||
setup("", null, false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// Stores future that informs when action is completed.
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response =
|
||||
new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
|
||||
|
||||
// Assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
// Assesses that listener is triggered, but event is not success
|
||||
assertThat(future.isDone()).isTrue();
|
||||
assertThat(future.isSuccess()).isFalse();
|
||||
|
||||
// Ensures that we fail as a result of a FailureException.
|
||||
assertThat(future.cause() instanceof FailureException).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasic_responseFailure_badURL() {
|
||||
// setup
|
||||
setup("", null, false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response =
|
||||
new HttpResponseMessage(
|
||||
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true));
|
||||
|
||||
// assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
// assesses that listener is triggered, and event is success
|
||||
assertThat(future.isDone()).isTrue();
|
||||
assertThat(future.isSuccess()).isFalse();
|
||||
|
||||
// Ensures that we fail as a result of a FailureException.
|
||||
assertThat(future.cause()).isInstanceOf(FailureException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdvanced_redirect() {
|
||||
// Sets up EventLoopGroup with 1 thread to be blocking.
|
||||
EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
|
||||
// Sets up embedded channel.
|
||||
setup("", makeBootstrap(group), false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// Initializes LocalAddress with unique String.
|
||||
LocalAddress address = new LocalAddress(TARGET_HOST);
|
||||
|
||||
// stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
// Path that we test WebWhoisActionHandler uses.
|
||||
String path = "/test";
|
||||
|
||||
// Sets up the local server that the handler will be redirected to.
|
||||
TestServer.webWhoisServer(group, address, "", TARGET_HOST, path);
|
||||
|
||||
FullHttpResponse response =
|
||||
new HttpResponseMessage(
|
||||
makeRedirectResponse(
|
||||
HttpResponseStatus.MOVED_PERMANENTLY, HTTP_REDIRECT + TARGET_HOST + path, true));
|
||||
|
||||
// checks that future has not been set to successful or a failure
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
// makes sure old channel is shut down when attempting redirection
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
|
||||
// assesses that we successfully received good response and protocol is unchanged
|
||||
assertThat(future.syncUninterruptibly().isSuccess()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.testserver;
|
||||
|
||||
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
|
||||
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.exception.FailureException;
|
||||
import google.registry.monitoring.blackbox.message.EppMessage;
|
||||
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to
|
||||
* perform
|
||||
*/
|
||||
public class TestServer {
|
||||
|
||||
public TestServer(LocalAddress localAddress, ImmutableList<? extends ChannelHandler> handlers) {
|
||||
this(new NioEventLoopGroup(1), localAddress, handlers);
|
||||
}
|
||||
|
||||
private TestServer(
|
||||
EventLoopGroup eventLoopGroup,
|
||||
LocalAddress localAddress,
|
||||
ImmutableList<? extends ChannelHandler> handlers) {
|
||||
// Creates ChannelInitializer with handlers specified
|
||||
ChannelInitializer<LocalChannel> serverInitializer =
|
||||
new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(LocalChannel ch) {
|
||||
for (ChannelHandler handler : handlers) {
|
||||
ch.pipeline().addLast(handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Sets up serverBootstrap with specified initializer, eventLoopGroup, and using
|
||||
// LocalServerChannel class
|
||||
ServerBootstrap serverBootstrap =
|
||||
new ServerBootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(LocalServerChannel.class)
|
||||
.childHandler(serverInitializer);
|
||||
|
||||
ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly();
|
||||
}
|
||||
|
||||
public static TestServer webWhoisServer(
|
||||
EventLoopGroup eventLoopGroup,
|
||||
LocalAddress localAddress,
|
||||
String redirectInput,
|
||||
String destinationInput,
|
||||
String destinationPath) {
|
||||
return new TestServer(
|
||||
eventLoopGroup,
|
||||
localAddress,
|
||||
ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath)));
|
||||
}
|
||||
|
||||
public static TestServer eppServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress) {
|
||||
// TODO - add LengthFieldBasedFrameDecoder to handlers in pipeline if necessary for future
|
||||
// tests.
|
||||
return new TestServer(eventLoopGroup, localAddress, ImmutableList.of(new EppHandler()));
|
||||
}
|
||||
|
||||
/** Handler that will wither redirect client, give successful response, or give error messge */
|
||||
@Sharable
|
||||
static class RedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
|
||||
|
||||
private String redirectInput;
|
||||
private String destinationInput;
|
||||
private String destinationPath;
|
||||
|
||||
/**
|
||||
* @param redirectInput - Server will send back redirect to {@code destinationInput} when
|
||||
* receiving a request with this host location
|
||||
* @param destinationInput - Server will send back an {@link HttpResponseStatus} OK response
|
||||
* when receiving a request with this host location
|
||||
*/
|
||||
RedirectHandler(String redirectInput, String destinationInput, String destinationPath) {
|
||||
this.redirectInput = redirectInput;
|
||||
this.destinationInput = destinationInput;
|
||||
this.destinationPath = destinationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads input {@link HttpRequest}, and creates appropriate {@link HttpResponseMessage} based on
|
||||
* what header location is
|
||||
*/
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
|
||||
HttpResponse response;
|
||||
if (request.headers().get("host").equals(redirectInput)) {
|
||||
response =
|
||||
new HttpResponseMessage(
|
||||
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, destinationInput, true));
|
||||
} else if (request.headers().get("host").equals(destinationInput)
|
||||
&& request.uri().equals(destinationPath)) {
|
||||
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
|
||||
} else {
|
||||
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
|
||||
}
|
||||
ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EppHandler extends ChannelDuplexHandler {
|
||||
|
||||
private ChannelPromise future;
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
// TODO - pass EppMessage into future to easily read attributes of message.
|
||||
future = ctx.newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
|
||||
byte[] messageBytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(messageBytes);
|
||||
|
||||
// TODO - Break ByteBuf into multiple pieces to test how well it works with
|
||||
// LengthFieldBasedFrameDecoder.
|
||||
|
||||
try {
|
||||
EppMessage.byteArrayToXmlDoc(messageBytes);
|
||||
ChannelFuture unusedFuture = future.setSuccess();
|
||||
} catch (FailureException e) {
|
||||
ChannelFuture unusedFuture = future.setFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import io.netty.channel.Channel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/** Unit Tests for each {@link Token} subtype (just {@link WebWhoisToken} for now) */
|
||||
/** Unit Tests for each {@link Token} subtype (just {@link EppToken} for now) */
|
||||
class EppTokenTest {
|
||||
|
||||
private static String TEST_HOST = "host";
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.token;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
|
||||
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit Tests for {@link WebWhoisToken} */
|
||||
class WebWhoisTokenTest {
|
||||
|
||||
private static final String PREFIX = "whois.nic.";
|
||||
private static final String HOST = "starter";
|
||||
private static final String FIRST_TLD = "first_test";
|
||||
private static final String SECOND_TLD = "second_test";
|
||||
private static final String THIRD_TLD = "third_test";
|
||||
private final ImmutableList<String> testDomains =
|
||||
ImmutableList.of(FIRST_TLD, SECOND_TLD, THIRD_TLD);
|
||||
|
||||
private Token webToken = new WebWhoisToken(testDomains);
|
||||
|
||||
@Test
|
||||
void testMessageModification() throws UndeterminedStateException {
|
||||
// creates Request message with header
|
||||
HttpRequestMessage message = new HttpRequestMessage();
|
||||
message.headers().set("host", HOST);
|
||||
|
||||
// attempts to use Token's method for modifying the method based on its stored host
|
||||
HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message);
|
||||
assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + FIRST_TLD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHostOfToken() {
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNextToken() {
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||
webToken = webToken.next();
|
||||
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + SECOND_TLD);
|
||||
webToken = webToken.next();
|
||||
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
|
||||
webToken = webToken.next();
|
||||
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2019 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.monitoring.blackbox.util;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
/** Houses static utility functions for testing WebWHOIS components of Prober. */
|
||||
public class WebWhoisUtils {
|
||||
|
||||
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||
request.headers().set("host", host).setInt("content-length", 0);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeHttpResponse(String content, HttpResponseStatus status) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
|
||||
response.headers().setInt("content-length", buf.readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeHttpResponse(HttpResponseStatus status) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
|
||||
response.headers().setInt("content-length", 0);
|
||||
return response;
|
||||
}
|
||||
|
||||
/** Creates HttpResponse given status, redirection location, and other necessary inputs */
|
||||
public static FullHttpResponse makeRedirectResponse(
|
||||
HttpResponseStatus status, String location, boolean keepAlive) {
|
||||
FullHttpResponse response = makeHttpResponse("", status);
|
||||
response.headers().set("content-type", "text/plain");
|
||||
if (location != null) {
|
||||
response.headers().set("location", location);
|
||||
}
|
||||
if (keepAlive) {
|
||||
response.headers().set("connection", "keep-alive");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import javax.annotation.Nullable;
|
||||
*
|
||||
* <p>Only a builder is provided because the client protocol itself depends on the remote host
|
||||
* address, which is provided in the server protocol module that relays to this client protocol
|
||||
* module, e.g., {@link WhoisProtocolModule}.
|
||||
* module, e.g., {@link EppProtocolModule}.
|
||||
*
|
||||
* <p>The protocol can be configured without TLS. In this case, the remote host has to be
|
||||
* "localhost". Plan HTTP is only expected to be used when communication with Nomulus is via local
|
||||
|
||||
@@ -47,9 +47,7 @@ public class ProxyConfig {
|
||||
public Gcs gcs;
|
||||
public Kms kms;
|
||||
public Epp epp;
|
||||
public Whois whois;
|
||||
public HealthCheck healthCheck;
|
||||
public WebWhois webWhois;
|
||||
public HttpsRelay httpsRelay;
|
||||
public Metrics metrics;
|
||||
|
||||
@@ -77,16 +75,6 @@ public class ProxyConfig {
|
||||
public Quota quota;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to WHOIS protocol. */
|
||||
public static class Whois {
|
||||
public int port;
|
||||
public String relayHost;
|
||||
public String relayPath;
|
||||
public int maxMessageLengthBytes;
|
||||
public int readTimeoutSeconds;
|
||||
public Quota quota;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to GCP load balancer health check protocol. */
|
||||
public static class HealthCheck {
|
||||
public int port;
|
||||
@@ -94,13 +82,6 @@ public class ProxyConfig {
|
||||
public String checkResponse;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to web WHOIS redirects. */
|
||||
public static class WebWhois {
|
||||
public int httpPort;
|
||||
public int httpsPort;
|
||||
public String redirectHost;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to HTTPS relay protocol. */
|
||||
public static class HttpsRelay {
|
||||
public int port;
|
||||
|
||||
@@ -40,9 +40,6 @@ import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.ProxyConfig.Environment;
|
||||
import google.registry.proxy.WebWhoisProtocolsModule.HttpWhoisProtocol;
|
||||
import google.registry.proxy.WebWhoisProtocolsModule.HttpsWhoisProtocol;
|
||||
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
|
||||
import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.GcpJsonFormatter;
|
||||
@@ -79,25 +76,16 @@ import java.util.logging.Level;
|
||||
@Module
|
||||
public class ProxyModule {
|
||||
|
||||
@Parameter(names = "--whois", description = "Port for WHOIS")
|
||||
private Integer whoisPort;
|
||||
|
||||
@Parameter(names = "--epp", description = "Port for EPP")
|
||||
private Integer eppPort;
|
||||
|
||||
@Parameter(names = "--health_check", description = "Port for health check")
|
||||
private Integer healthCheckPort;
|
||||
|
||||
@Parameter(names = "--http_whois", description = "Port for HTTP WHOIS")
|
||||
private Integer httpWhoisPort;
|
||||
|
||||
@Parameter(names = "--https_whois", description = "Port for HTTPS WHOIS")
|
||||
private Integer httpsWhoisPort;
|
||||
|
||||
@Parameter(
|
||||
names = "--local",
|
||||
description =
|
||||
"Whether EPP/WHOIS traffic should be forwarded to localhost using HTTP on port defined in"
|
||||
"Whether EPP traffic should be forwarded to localhost using HTTP on port defined in"
|
||||
+ " httpsRelay.localPort")
|
||||
private boolean local = false;
|
||||
|
||||
@@ -184,12 +172,6 @@ public class ProxyModule {
|
||||
return local;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
int provideWhoisPort(ProxyConfig config) {
|
||||
return Optional.ofNullable(whoisPort).orElse(config.whois.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
int provideEppPort(ProxyConfig config) {
|
||||
@@ -202,18 +184,6 @@ public class ProxyModule {
|
||||
return Optional.ofNullable(healthCheckPort).orElse(config.healthCheck.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
int provideHttpWhoisProtocol(ProxyConfig config) {
|
||||
return Optional.ofNullable(httpWhoisPort).orElse(config.webWhois.httpPort);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
int provideHttpsWhoisProtocol(ProxyConfig config) {
|
||||
return Optional.ofNullable(httpsWhoisPort).orElse(config.webWhois.httpsPort);
|
||||
}
|
||||
|
||||
@Provides
|
||||
Environment provideEnvironment() {
|
||||
return env;
|
||||
@@ -423,8 +393,6 @@ public class ProxyModule {
|
||||
ProxyModule.class,
|
||||
CertificateSupplierModule.class,
|
||||
HttpsRelayProtocolModule.class,
|
||||
WhoisProtocolModule.class,
|
||||
WebWhoisProtocolsModule.class,
|
||||
EppProtocolModule.class,
|
||||
HealthCheckProtocolModule.class,
|
||||
MetricsModule.class
|
||||
|
||||
@@ -93,8 +93,7 @@ public class ProxyServer implements Runnable {
|
||||
addHandlers(inboundChannel.pipeline(), inboundProtocol.handlerProviders());
|
||||
|
||||
if (!inboundProtocol.hasBackend()) {
|
||||
// If the frontend has no backend to relay to (health check, web WHOIS redirect, etc), start
|
||||
// reading immediately.
|
||||
// If the frontend has no backend to relay to (e.g. health check) start reading immediately.
|
||||
inboundChannel.config().setAutoRead(true);
|
||||
} else {
|
||||
logger.atInfo().log(
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright 2018 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.proxy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.networking.handler.SslServerInitializer;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.handler.WebWhoisRedirectHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol}s to redirect HTTP(S) web WHOIS requests. */
|
||||
@Module
|
||||
public final class WebWhoisProtocolsModule {
|
||||
|
||||
private WebWhoisProtocolsModule() {}
|
||||
|
||||
/** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HttpWhoisProtocol {}
|
||||
|
||||
/** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HttpsWhoisProtocol {}
|
||||
|
||||
private static final String HTTP_PROTOCOL_NAME = "whois_http";
|
||||
private static final String HTTPS_PROTOCOL_NAME = "whois_https";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideHttpWhoisProtocol(
|
||||
@HttpWhoisProtocol int httpWhoisPort,
|
||||
@HttpWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(HTTP_PROTOCOL_NAME)
|
||||
.port(httpWhoisPort)
|
||||
.hasBackend(false)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideHttpsWhoisProtocol(
|
||||
@HttpsWhoisProtocol int httpsWhoisPort,
|
||||
@HttpsWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(HTTPS_PROTOCOL_NAME)
|
||||
.port(httpsWhoisPort)
|
||||
.hasBackend(false)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpWhoisHandlerProviders(
|
||||
Provider<HttpServerCodec> httpServerCodecProvider,
|
||||
Provider<HttpServerExpectContinueHandler> httpServerExpectContinueHandlerProvider,
|
||||
@HttpWhoisProtocol Provider<WebWhoisRedirectHandler> webWhoisRedirectHandlerProvides) {
|
||||
return ImmutableList.of(
|
||||
httpServerCodecProvider,
|
||||
httpServerExpectContinueHandlerProvider,
|
||||
webWhoisRedirectHandlerProvides);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpsWhoisHandlerProviders(
|
||||
@HttpsWhoisProtocol
|
||||
Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
Provider<HttpServerCodec> httpServerCodecProvider,
|
||||
Provider<HttpServerExpectContinueHandler> httpServerExpectContinueHandlerProvider,
|
||||
@HttpsWhoisProtocol Provider<WebWhoisRedirectHandler> webWhoisRedirectHandlerProvides) {
|
||||
return ImmutableList.of(
|
||||
sslServerInitializerProvider,
|
||||
httpServerCodecProvider,
|
||||
httpServerExpectContinueHandlerProvider,
|
||||
webWhoisRedirectHandlerProvides);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpServerCodec provideHttpServerCodec() {
|
||||
return new HttpServerCodec();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static WebWhoisRedirectHandler provideHttpRedirectHandler(ProxyConfig config) {
|
||||
return new WebWhoisRedirectHandler(false, config.webWhois.redirectHost);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static WebWhoisRedirectHandler provideHttpsRedirectHandler(ProxyConfig config) {
|
||||
return new WebWhoisRedirectHandler(true, config.webWhois.redirectHost);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpServerExpectContinueHandler provideHttpServerExpectContinueHandler() {
|
||||
return new HttpServerExpectContinueHandler();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
|
||||
SslProvider sslProvider,
|
||||
Supplier<PrivateKey> privateKeySupplier,
|
||||
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
|
||||
return new SslServerInitializer<>(
|
||||
false, false, sslProvider, privateKeySupplier, certificatesSupplier);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.proxy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.handler.FrontendMetricsHandler;
|
||||
import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||
import google.registry.proxy.handler.QuotaHandler.WhoisQuotaHandler;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
|
||||
import google.registry.proxy.handler.WhoisServiceHandler;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.proxy.quota.QuotaConfig;
|
||||
import google.registry.proxy.quota.QuotaManager;
|
||||
import google.registry.proxy.quota.TokenStore;
|
||||
import google.registry.util.Clock;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol} used for whois protocol. */
|
||||
@Module
|
||||
public final class WhoisProtocolModule {
|
||||
|
||||
private WhoisProtocolModule() {}
|
||||
|
||||
/** Dagger qualifier to provide whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
public @interface WhoisProtocol {}
|
||||
|
||||
private static final String PROTOCOL_NAME = "whois";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
ProxyConfig config,
|
||||
@WhoisProtocol int whoisPort,
|
||||
@WhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders,
|
||||
@HttpsRelayProtocol BackendProtocol.Builder backendProtocolBuilder,
|
||||
@HttpsRelayProtocol boolean localRelay) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(whoisPort)
|
||||
.handlerProviders(handlerProviders)
|
||||
.relayProtocol(
|
||||
backendProtocolBuilder.host(localRelay ? "localhost" : config.whois.relayHost).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<ProxyProtocolHandler> proxyProtocolHandlerProvider,
|
||||
@WhoisProtocol Provider<ReadTimeoutHandler> readTimeoutHandlerProvider,
|
||||
Provider<LineBasedFrameDecoder> lineBasedFrameDecoderProvider,
|
||||
Provider<WhoisServiceHandler> whoisServiceHandlerProvider,
|
||||
Provider<FrontendMetricsHandler> frontendMetricsHandlerProvider,
|
||||
Provider<WhoisQuotaHandler> whoisQuotaHandlerProvider,
|
||||
Provider<FullHttpRequestRelayHandler> relayHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
proxyProtocolHandlerProvider,
|
||||
readTimeoutHandlerProvider,
|
||||
lineBasedFrameDecoderProvider,
|
||||
whoisServiceHandlerProvider,
|
||||
frontendMetricsHandlerProvider,
|
||||
whoisQuotaHandlerProvider,
|
||||
relayHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||
ProxyConfig config,
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
@Named("canary") boolean canary,
|
||||
FrontendMetrics metrics,
|
||||
@HttpsRelayProtocol boolean localRelay) {
|
||||
return new WhoisServiceHandler(
|
||||
localRelay ? "localhost" : config.whois.relayHost,
|
||||
config.whois.relayPath,
|
||||
canary,
|
||||
idTokenSupplier,
|
||||
metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static LineBasedFrameDecoder provideLineBasedFrameDecoder(ProxyConfig config) {
|
||||
return new LineBasedFrameDecoder(config.whois.maxMessageLengthBytes);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
static ReadTimeoutHandler provideReadTimeoutHandler(ProxyConfig config) {
|
||||
return new ReadTimeoutHandler(config.whois.readTimeoutSeconds);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
static TokenStore provideTokenStore(
|
||||
ProxyConfig config, ScheduledExecutorService refreshExecutor, Clock clock) {
|
||||
return new TokenStore(
|
||||
new QuotaConfig(config.whois.quota, PROTOCOL_NAME), refreshExecutor, clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@WhoisProtocol
|
||||
static QuotaManager provideQuotaManager(
|
||||
@WhoisProtocol TokenStore tokenStore, ExecutorService executorService) {
|
||||
return new QuotaManager(tokenStore, executorService);
|
||||
}
|
||||
}
|
||||
@@ -118,54 +118,6 @@ epp:
|
||||
# defaultQuota for list entries.
|
||||
customQuota: []
|
||||
|
||||
whois:
|
||||
port: 30001
|
||||
relayHost: registry-project-id.appspot.com
|
||||
relayPath: /_dr/whois
|
||||
|
||||
# Maximum input message length in bytes.
|
||||
#
|
||||
# Domain name cannot be longer than 256 characters. 512-character message
|
||||
# length should be safe for most cases, including registrar queries.
|
||||
#
|
||||
# See also: RFC 1035 2.3.4 Size limits
|
||||
# (http://www.freesoft.org/CIE/RFC/1035/9.htm).
|
||||
maxMessageLengthBytes: 512
|
||||
|
||||
# Whois protocol is transient, the client should not establish a long-lasting
|
||||
# idle connection.
|
||||
readTimeoutSeconds: 60
|
||||
|
||||
# Quota configuration for WHOIS
|
||||
quota:
|
||||
|
||||
# Token database refresh period. Set to 0 to disable refresh.
|
||||
#
|
||||
# After the set time period, inactive token buckets will be deleted.
|
||||
refreshSeconds: 3600
|
||||
|
||||
# Default quota for any userId not matched in customQuota.
|
||||
defaultQuota:
|
||||
|
||||
# List of identifiers, e.g. IP address, certificate hash.
|
||||
#
|
||||
# userId for defaultQuota should always be an empty list.
|
||||
userId: []
|
||||
|
||||
# Number of tokens allotted to the matched user. Set to -1 to allow
|
||||
# infinite quota.
|
||||
tokenAmount: 100
|
||||
|
||||
# Token refill period. Set to 0 to disable refill.
|
||||
#
|
||||
# After the set time period, the token for the given user will be
|
||||
# reset to tokenAmount.
|
||||
refillSeconds: 600
|
||||
|
||||
# List of custom quotas for specific userId. Use the same schema as
|
||||
# defaultQuota for list entries.
|
||||
customQuota: []
|
||||
|
||||
healthCheck:
|
||||
port: 30000
|
||||
|
||||
@@ -181,14 +133,6 @@ httpsRelay:
|
||||
# Maximum size of an HTTP message in bytes.
|
||||
maxMessageLengthBytes: 524288
|
||||
|
||||
webWhois:
|
||||
httpPort: 30010
|
||||
httpsPort: 30011
|
||||
|
||||
# The 302 redirect destination of HTTPS web WHOIS GET requests.
|
||||
# HTTP web WHOIS GET requests will be 301 redirected to HTTPS first.
|
||||
redirectHost: whois.yourdomain.tld
|
||||
|
||||
metrics:
|
||||
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
|
||||
# API. The limit can be adjusted by contacting Cloud Support.
|
||||
|
||||
@@ -104,7 +104,6 @@ public class BackendMetricsHandler extends ChannelDuplexHandler {
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||
checkArgument(msg instanceof FullHttpRequest, "Outgoing request must be FullHttpRequest.");
|
||||
// For WHOIS, client certificate hash is always set to "none".
|
||||
// For EPP, the client hash attribute is set upon handshake completion, before the first HELLO
|
||||
// is sent to the server. Therefore the first call to write() with HELLO payload has access to
|
||||
// the hash in its channel attribute.
|
||||
|
||||
@@ -52,18 +52,14 @@ public class FrontendMetricsHandler extends ChannelDuplexHandler {
|
||||
*
|
||||
* <p>This queue is used to calculate frontend request-response latency.
|
||||
*
|
||||
* <p>For the WHOIS protocol, the TCP connection closes after one request-response round trip and
|
||||
* the request always comes first. The queue for WHOIS therefore only need to store one value.
|
||||
*
|
||||
* <p>For the EPP protocol, the specification allows for pipelining, in which a client can sent
|
||||
* multiple requests without waiting for each responses. Therefore a queue is needed to record all
|
||||
* multiple requests without waiting for each response. Therefore, a queue is needed to record all
|
||||
* the requests that are sent but have not yet received a response.
|
||||
*
|
||||
* <p>A server must send its response in the same order it receives requests. This invariance
|
||||
* guarantees that the request time at the head of the queue always corresponds to the response
|
||||
* received in {@link #channelRead}.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc3912">RFC 3912 WHOIS Protocol Specification</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5734#section-3">RFC 5734 Extensible Provisioning
|
||||
* Protocol (EPP) Transport over TCP</a>
|
||||
*/
|
||||
@@ -97,7 +93,6 @@ public class FrontendMetricsHandler extends ChannelDuplexHandler {
|
||||
// increase in size if more requests are received from the client, but that does not invalidate
|
||||
// this check.
|
||||
checkState(!requestReceivedTimeQueue.isEmpty(), "Response sent before request is received.");
|
||||
// For WHOIS, client certificate hash is always set to "none".
|
||||
// For EPP, the client hash attribute is set upon handshake completion, before the first HELLO
|
||||
// is sent to the server. Therefore the first call to write() with HELLO payload has access to
|
||||
// the hash in its channel attribute.
|
||||
|
||||
@@ -17,10 +17,8 @@ package google.registry.proxy.handler;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.proxy.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.proxy.handler.EppServiceHandler.CLIENT_CERTIFICATE_HASH_KEY;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
|
||||
import google.registry.proxy.EppProtocolModule.EppProtocol;
|
||||
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.proxy.quota.QuotaManager;
|
||||
import google.registry.proxy.quota.QuotaManager.QuotaRebate;
|
||||
@@ -83,42 +81,6 @@ public abstract class QuotaHandler extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/** Quota Handler for WHOIS protocol. */
|
||||
public static class WhoisQuotaHandler extends QuotaHandler {
|
||||
|
||||
@Inject
|
||||
WhoisQuotaHandler(@WhoisProtocol QuotaManager quotaManager, FrontendMetrics metrics) {
|
||||
super(quotaManager, metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads user ID from channel attribute {@code REMOTE_ADDRESS_KEY}.
|
||||
*
|
||||
* <p>This attribute is set by {@link ProxyProtocolHandler} when the first frame of message is
|
||||
* read.
|
||||
*/
|
||||
@Override
|
||||
String getUserId(ChannelHandlerContext ctx) {
|
||||
return ctx.channel().attr(REMOTE_ADDRESS_KEY).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUserIdPii() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing when connection terminates.
|
||||
*
|
||||
* <p>WHOIS protocol is configured with a QPS type quota, there is no need to return the tokens
|
||||
* back to the quota store because the quota store will auto-refill tokens based on the QPS.
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
}
|
||||
|
||||
/** Quota Handler for EPP protocol. */
|
||||
public static class EppQuotaHandler extends QuotaHandler {
|
||||
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2018 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.proxy.handler;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
|
||||
import static io.netty.handler.codec.http.HttpMethod.GET;
|
||||
import static io.netty.handler.codec.http.HttpMethod.HEAD;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.MOVED_PERMANENTLY;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Handler that redirects web WHOIS requests to a canonical website.
|
||||
*
|
||||
* <p>ICANN requires that port 43 and web-based WHOIS are both available on whois.nic.TLD. Since we
|
||||
* expose a single IPv4/IPv6 anycast external IP address for the proxy, we need the load balancer to
|
||||
* router port 80/443 traffic to the proxy to support web WHOIS.
|
||||
*
|
||||
* <p>HTTP (port 80) traffic is simply upgraded to HTTPS (port 443) on the same host, while HTTPS
|
||||
* requests are redirected to the {@code redirectHost}, which is the canonical website that provide
|
||||
* the web WHOIS service.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-31jul17-en.html">
|
||||
* REGISTRY AGREEMENT</a>
|
||||
*/
|
||||
public class WebWhoisRedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/**
|
||||
* HTTP health check sent by GCP HTTP load balancer is set to use this host name.
|
||||
*
|
||||
* <p>Status 200 must be returned in order for a health check to be considered successful.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://cloud.google.com/load-balancing/docs/health-check-concepts#http_https_and_http2_health_checks">
|
||||
* HTTP, HTTPS, and HTTP/2 health checks</a>
|
||||
*/
|
||||
private static final String HEALTH_CHECK_HOST = "health-check.invalid";
|
||||
|
||||
private static final String HSTS_HEADER_NAME = "Strict-Transport-Security";
|
||||
private static final Duration HSTS_MAX_AGE = Duration.ofDays(365);
|
||||
private static final ImmutableList<HttpMethod> ALLOWED_METHODS = ImmutableList.of(GET, HEAD);
|
||||
|
||||
private final boolean isHttps;
|
||||
private final String redirectHost;
|
||||
|
||||
public WebWhoisRedirectHandler(boolean isHttps, String redirectHost) {
|
||||
this.isHttps = isHttps;
|
||||
this.redirectHost = redirectHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {
|
||||
FullHttpResponse response;
|
||||
if (!ALLOWED_METHODS.contains(msg.method())) {
|
||||
response = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED);
|
||||
} else if (Strings.isNullOrEmpty(msg.headers().get(HOST))) {
|
||||
response = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST);
|
||||
} else {
|
||||
// All HTTP/1.1 request must contain a Host header with the format "host:[port]".
|
||||
// See https://tools.ietf.org/html/rfc2616#section-14.23
|
||||
String host = Splitter.on(':').split(msg.headers().get(HOST)).iterator().next();
|
||||
if (host.equals(HEALTH_CHECK_HOST)) {
|
||||
// The health check request should always be sent to the HTTP port.
|
||||
response =
|
||||
isHttps
|
||||
? new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)
|
||||
: new DefaultFullHttpResponse(HTTP_1_1, OK);
|
||||
} else {
|
||||
// HTTP -> HTTPS is a 301 redirect, whereas HTTPS -> web WHOIS site is 302 redirect.
|
||||
response = new DefaultFullHttpResponse(HTTP_1_1, isHttps ? FOUND : MOVED_PERMANENTLY);
|
||||
String redirectUrl = String.format("https://%s/", isHttps ? redirectHost : host);
|
||||
response.headers().set(LOCATION, redirectUrl);
|
||||
// Add HSTS header to HTTPS response.
|
||||
if (isHttps) {
|
||||
response
|
||||
.headers()
|
||||
.set(HSTS_HEADER_NAME, String.format("max-age=%d", HSTS_MAX_AGE.getSeconds()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common headers that need to be set on any response.
|
||||
response
|
||||
.headers()
|
||||
.set(CONTENT_TYPE, TEXT_PLAIN)
|
||||
.setInt(CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
||||
// Close the connection if keep-alive is not set in the request.
|
||||
if (!HttpUtil.isKeepAlive(msg)) {
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
response.headers().set(CONNECTION, KEEP_ALIVE);
|
||||
ChannelFuture unusedFuture = ctx.writeAndFlush(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
logger.atWarning().withCause(cause).log(
|
||||
(isHttps ? "HTTPS" : "HTTP") + " WHOIS inbound exception caught for channel %s",
|
||||
ctx.channel());
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.proxy.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Handler that processes WHOIS protocol logic. */
|
||||
public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
||||
|
||||
private String clientAddress;
|
||||
|
||||
public WhoisServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
boolean canary,
|
||||
Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, canary, idTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
metrics.registerActiveConnection("whois", "none", ctx.channel());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
clientAddress = ctx.channel().attr(REMOTE_ADDRESS_KEY).get();
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
FullHttpRequest request = super.decodeFullHttpRequest(byteBuf);
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
|
||||
.set(HttpHeaderNames.ACCEPT, HttpHeaderValues.TEXT_PLAIN);
|
||||
if (clientAddress != null) {
|
||||
request
|
||||
.headers()
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
// Close connection after a response is received, per RFC-3912
|
||||
// https://tools.ietf.org/html/rfc3912
|
||||
checkArgument(msg instanceof HttpResponse);
|
||||
promise.addListener(ChannelFutureListener.CLOSE);
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,8 @@ public abstract class BaseMetrics {
|
||||
/**
|
||||
* Labels to register metrics with.
|
||||
*
|
||||
* <p>The client certificate hash value is only used for EPP metrics. For WHOIS metrics, it will
|
||||
* always be {@code "none"}. In order to get the actual registrar name, one can use the {@code
|
||||
* nomulus} tool:
|
||||
* <p>The client certificate hash value is only used for EPP metrics. In order to get the actual
|
||||
* registrar name, one can use the {@code nomulus} tool:
|
||||
*
|
||||
* <pre>
|
||||
* nomulus -e production list_registrars -f clientCertificateHash | grep $HASH
|
||||
|
||||
@@ -33,16 +33,12 @@ import google.registry.proxy.EppProtocolModule.EppProtocol;
|
||||
import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.ProxyConfig.Environment;
|
||||
import google.registry.proxy.WebWhoisProtocolsModule.HttpWhoisProtocol;
|
||||
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
|
||||
import google.registry.proxy.handler.BackendMetricsHandler;
|
||||
import google.registry.proxy.handler.FrontendMetricsHandler;
|
||||
import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||
import google.registry.proxy.handler.QuotaHandler.EppQuotaHandler;
|
||||
import google.registry.proxy.handler.QuotaHandler.WhoisQuotaHandler;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpResponseRelayHandler;
|
||||
import google.registry.proxy.handler.WebWhoisRedirectHandler;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.util.Clock;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -102,18 +98,12 @@ public abstract class ProtocolModuleTest {
|
||||
// tested separately in their respective unit tests.
|
||||
FullHttpRequestRelayHandler.class,
|
||||
FullHttpResponseRelayHandler.class,
|
||||
// This handler is tested in its own unit tests. It is installed in web whois redirect
|
||||
// protocols. The end-to-end tests for the rest of the handlers in its pipeline need to
|
||||
// be able to emit incoming requests out of the channel for assertions. Therefore, this
|
||||
// handler is removed from the pipeline.
|
||||
WebWhoisRedirectHandler.class,
|
||||
// The rest are not part of business logic and do not need to be tested, obviously.
|
||||
LoggingHandler.class,
|
||||
// Metrics instrumentation is tested separately.
|
||||
BackendMetricsHandler.class,
|
||||
FrontendMetricsHandler.class,
|
||||
// Quota management is tested separately.
|
||||
WhoisQuotaHandler.class,
|
||||
EppQuotaHandler.class,
|
||||
ReadTimeoutHandler.class);
|
||||
|
||||
@@ -190,17 +180,12 @@ public abstract class ProtocolModuleTest {
|
||||
modules = {
|
||||
TestModule.class,
|
||||
CertificateSupplierModule.class,
|
||||
WhoisProtocolModule.class,
|
||||
WebWhoisProtocolsModule.class,
|
||||
EppProtocolModule.class,
|
||||
HealthCheckProtocolModule.class,
|
||||
HttpsRelayProtocolModule.class
|
||||
})
|
||||
interface TestComponent {
|
||||
|
||||
@WhoisProtocol
|
||||
ImmutableList<Provider<? extends ChannelHandler>> whoisHandlers();
|
||||
|
||||
@EppProtocol
|
||||
ImmutableList<Provider<? extends ChannelHandler>> eppHandlers();
|
||||
|
||||
@@ -209,9 +194,6 @@ public abstract class ProtocolModuleTest {
|
||||
|
||||
@HttpsRelayProtocol
|
||||
ImmutableList<Provider<? extends ChannelHandler>> httpsRelayHandlers();
|
||||
|
||||
@HttpWhoisProtocol
|
||||
ImmutableList<Provider<? extends ChannelHandler>> httpWhoisHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,14 +33,9 @@ class ProxyModuleTest {
|
||||
void testSuccess_parseArgs_defaultArgs() {
|
||||
String[] args = {};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideWhoisPort(PROXY_CONFIG)).isEqualTo(PROXY_CONFIG.whois.port);
|
||||
assertThat(proxyModule.provideEppPort(PROXY_CONFIG)).isEqualTo(PROXY_CONFIG.epp.port);
|
||||
assertThat(proxyModule.provideHealthCheckPort(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.healthCheck.port);
|
||||
assertThat(proxyModule.provideHttpWhoisProtocol(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.webWhois.httpPort);
|
||||
assertThat(proxyModule.provideHttpsWhoisProtocol(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.webWhois.httpsPort);
|
||||
assertThat(proxyModule.provideEnvironment()).isEqualTo(LOCAL);
|
||||
assertThat(proxyModule.log).isFalse();
|
||||
}
|
||||
@@ -74,13 +69,6 @@ class ProxyModuleTest {
|
||||
assertThat(proxyModule.log).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseArgs_customWhoisPort() {
|
||||
String[] args = {"--whois", "12345"};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideWhoisPort(PROXY_CONFIG)).isEqualTo(12345);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseArgs_customEppPort() {
|
||||
String[] args = {"--epp", "22222"};
|
||||
@@ -95,20 +83,6 @@ class ProxyModuleTest {
|
||||
assertThat(proxyModule.provideHealthCheckPort(PROXY_CONFIG)).isEqualTo(23456);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseArgs_customhttpWhoisPort() {
|
||||
String[] args = {"--http_whois", "12121"};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideHttpWhoisProtocol(PROXY_CONFIG)).isEqualTo(12121);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseArgs_customhttpsWhoisPort() {
|
||||
String[] args = {"--https_whois", "21212"};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideHttpsWhoisProtocol(PROXY_CONFIG)).isEqualTo(21212);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseArgs_customEnvironment() {
|
||||
String[] args = {"--env", "ALpHa"};
|
||||
|
||||
@@ -88,22 +88,6 @@ public final class TestUtils {
|
||||
return response;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content, String host, String path, boolean canary, String idToken) throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path, canary);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + idToken)
|
||||
.set("content-type", "text/plain")
|
||||
.set("accept", "text/plain");
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content, String host, String path, String idToken) throws IOException {
|
||||
return makeWhoisHttpRequest(content, host, path, false, idToken);
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeEppHttpRequest(
|
||||
String content,
|
||||
String host,
|
||||
@@ -142,12 +126,6 @@ public final class TestUtils {
|
||||
content, host, path, false, idToken, sslClientCertificateHash, clientAddress, cookies);
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeWhoisHttpResponse(String content, HttpResponseStatus status) {
|
||||
FullHttpResponse response = makeHttpResponse(content, status);
|
||||
response.headers().set("content-type", "text/plain");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeEppHttpResponse(
|
||||
String content, HttpResponseStatus status, Cookie... cookies) {
|
||||
FullHttpResponse response = makeHttpResponse(content, status);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user