mirror of
https://github.com/google/nomulus.git
synced 2025-05-13 07:57:13 +02:00
Add framework for customizable WHOIS commands
With some additional changes by Ben McIlwain. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=145447136
This commit is contained in:
parent
d3fe6be385
commit
bb3a0c78c5
15 changed files with 155 additions and 47 deletions
|
@ -990,6 +990,13 @@ public final class RegistryConfig {
|
|||
return "google.registry.flows.custom.CustomLogicFactory";
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("whoisCommandFactoryClass")
|
||||
public static String provideWhoisCommandFactoryClass() {
|
||||
// TODO(b/32875427): This will be converted to YAML configuration in a future refactor.
|
||||
return "google.registry.whois.WhoisCommandFactory";
|
||||
}
|
||||
|
||||
private static final String RESERVED_TERMS_EXPORT_DISCLAIMER = ""
|
||||
+ "# This list contains reserve terms for the TLD. Other terms may be reserved\n"
|
||||
+ "# but not included in this list, including terms EXAMPLE REGISTRY chooses not\n"
|
||||
|
|
|
@ -26,6 +26,7 @@ import google.registry.request.Modules.Jackson2Module;
|
|||
import google.registry.request.Modules.URLFetchServiceModule;
|
||||
import google.registry.util.SystemClock.SystemClockModule;
|
||||
import google.registry.util.SystemSleeper.SystemSleeperModule;
|
||||
import google.registry.whois.WhoisModule;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +51,7 @@ import javax.inject.Singleton;
|
|||
SystemSleeperModule.class,
|
||||
URLFetchServiceModule.class,
|
||||
VoidDnsWriterModule.class,
|
||||
WhoisModule.class,
|
||||
},
|
||||
dependencies = {
|
||||
HttpRequestFactoryComponent.class,
|
||||
|
|
|
@ -14,13 +14,16 @@
|
|||
|
||||
package google.registry.whois;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.domain.DomainResource;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a domain name (i.e. SLD). */
|
||||
class DomainLookupCommand extends DomainOrHostLookupCommand<DomainResource> {
|
||||
public class DomainLookupCommand extends DomainOrHostLookupCommand {
|
||||
|
||||
DomainLookupCommand(InternetDomainName domainName) {
|
||||
this(domainName, null);
|
||||
|
@ -31,7 +34,10 @@ class DomainLookupCommand extends DomainOrHostLookupCommand<DomainResource> {
|
|||
}
|
||||
|
||||
@Override
|
||||
WhoisResponse getSuccessResponse(DomainResource domain, DateTime now) {
|
||||
return new DomainWhoisResponse(domain, now);
|
||||
protected Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
|
||||
final DomainResource domainResource =
|
||||
loadByForeignKey(DomainResource.class, domainName.toString(), now);
|
||||
return Optional.fromNullable(
|
||||
domainResource == null ? null : new DomainWhoisResponse(domainResource, now));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package google.registry.whois;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.registry.Registries.findTldForName;
|
||||
import static google.registry.model.registry.Registries.getTlds;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
|
@ -23,16 +22,13 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.util.TypeUtils.TypeInstantiator;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a domain name (i.e. SLD) or a nameserver. */
|
||||
abstract class DomainOrHostLookupCommand<T extends EppResource> implements WhoisCommand {
|
||||
public abstract class DomainOrHostLookupCommand implements WhoisCommand {
|
||||
|
||||
@VisibleForTesting
|
||||
final InternetDomainName domainOrHostName;
|
||||
@VisibleForTesting final InternetDomainName domainOrHostName;
|
||||
|
||||
private final String errorPrefix;
|
||||
|
||||
|
@ -52,17 +48,15 @@ abstract class DomainOrHostLookupCommand<T extends EppResource> implements Whois
|
|||
}
|
||||
// Google Policy: Do not return records under TLDs for which we're not authoritative.
|
||||
if (tld.isPresent() && getTlds().contains(tld.get().toString())) {
|
||||
T domainOrHost = loadByForeignKey(
|
||||
new TypeInstantiator<T>(getClass()){}.getExactType(),
|
||||
domainOrHostName.toString(),
|
||||
now);
|
||||
if (domainOrHost != null) {
|
||||
return getSuccessResponse(domainOrHost, now);
|
||||
final Optional<WhoisResponse> response = getResponse(domainOrHostName, now);
|
||||
if (response.isPresent()) {
|
||||
return response.get();
|
||||
}
|
||||
}
|
||||
throw new WhoisException(now, SC_NOT_FOUND, errorPrefix + " not found.");
|
||||
}
|
||||
|
||||
/** Renders a response record, provided its successfully retrieved datastore entity. */
|
||||
abstract WhoisResponse getSuccessResponse(T domainOrHost, DateTime now);
|
||||
protected abstract Optional<WhoisResponse> getResponse(
|
||||
InternetDomainName domainName, DateTime now);
|
||||
}
|
||||
|
|
|
@ -14,13 +14,16 @@
|
|||
|
||||
package google.registry.whois;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.model.host.HostResource;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS lookup on a nameserver based on its hostname. */
|
||||
final class NameserverLookupByHostCommand extends DomainOrHostLookupCommand<HostResource> {
|
||||
public class NameserverLookupByHostCommand extends DomainOrHostLookupCommand {
|
||||
|
||||
NameserverLookupByHostCommand(InternetDomainName hostName) {
|
||||
this(hostName, null);
|
||||
|
@ -31,7 +34,10 @@ final class NameserverLookupByHostCommand extends DomainOrHostLookupCommand<Host
|
|||
}
|
||||
|
||||
@Override
|
||||
WhoisResponse getSuccessResponse(HostResource host, DateTime now) {
|
||||
return new NameserverWhoisResponse(host, now);
|
||||
protected Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
|
||||
final HostResource hostResource =
|
||||
loadByForeignKey(HostResource.class, hostName.toString(), now);
|
||||
return Optional.fromNullable(
|
||||
hostResource == null ? null : new NameserverWhoisResponse(hostResource, now));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,18 +26,23 @@ public final class Whois {
|
|||
|
||||
private final Clock clock;
|
||||
private final String disclaimer;
|
||||
private final WhoisCommandFactory commandFactory;
|
||||
|
||||
@Inject
|
||||
public Whois(Clock clock, @Config("whoisDisclaimer") String disclaimer) {
|
||||
public Whois(
|
||||
Clock clock,
|
||||
@Config("whoisDisclaimer") String disclaimer,
|
||||
@Config("whoisCommandFactory") WhoisCommandFactory commandFactory) {
|
||||
this.clock = clock;
|
||||
this.disclaimer = disclaimer;
|
||||
this.commandFactory = commandFactory;
|
||||
}
|
||||
|
||||
/** Performs a WHOIS lookup on a plaintext query string. */
|
||||
public String lookup(String query, boolean preferUnicode) {
|
||||
DateTime now = clock.nowUtc();
|
||||
try {
|
||||
return new WhoisReader(new StringReader(query), now)
|
||||
return new WhoisReader(new StringReader(query), commandFactory, now)
|
||||
.readCommand()
|
||||
.executeQuery(now)
|
||||
.getPlainTextOutput(preferUnicode, disclaimer);
|
||||
|
|
|
@ -17,7 +17,7 @@ package google.registry.whois;
|
|||
import org.joda.time.DateTime;
|
||||
|
||||
/** Represents a WHOIS command request from a client. */
|
||||
interface WhoisCommand {
|
||||
public interface WhoisCommand {
|
||||
|
||||
/**
|
||||
* Executes a WHOIS query and returns the resultant data.
|
||||
|
|
74
java/google/registry/whois/WhoisCommandFactory.java
Normal file
74
java/google/registry/whois/WhoisCommandFactory.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2016 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;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */
|
||||
public final WhoisCommand domainLookup(InternetDomainName domainName) {
|
||||
return domainLookup(domainName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name in
|
||||
* the specified TLD.
|
||||
*/
|
||||
public WhoisCommand domainLookup(
|
||||
InternetDomainName domainName, @Nullable InternetDomainName tld) {
|
||||
return new DomainLookupCommand(domainName, tld);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 final WhoisCommand nameserverLookupByHost(InternetDomainName hostName) {
|
||||
return nameserverLookupByHost(hostName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified host name in
|
||||
* the specified TLD.
|
||||
*/
|
||||
public WhoisCommand nameserverLookupByHost(
|
||||
InternetDomainName hostName, @Nullable InternetDomainName tld) {
|
||||
return new NameserverLookupByHostCommand(hostName, tld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WhoisCommand} to perform a registrar lookup on the specified registrar
|
||||
* name.
|
||||
*/
|
||||
public WhoisCommand registrarLookup(String registrar) {
|
||||
return new RegistrarLookupCommand(registrar);
|
||||
}
|
||||
}
|
|
@ -132,6 +132,7 @@ public final class WhoisHttpServer implements Runnable {
|
|||
@Inject Response response;
|
||||
@Inject @Config("whoisDisclaimer") String disclaimer;
|
||||
@Inject @Config("whoisHttpExpires") Duration expires;
|
||||
@Inject @Config("whoisCommandFactory") WhoisCommandFactory commandFactory;
|
||||
@Inject @RequestPath String requestPath;
|
||||
@Inject WhoisHttpServer() {}
|
||||
|
||||
|
@ -144,7 +145,8 @@ public final class WhoisHttpServer implements Runnable {
|
|||
String command = decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n";
|
||||
Reader reader = new StringReader(command);
|
||||
DateTime now = clock.nowUtc();
|
||||
sendResponse(SC_OK, new WhoisReader(reader, now).readCommand().executeQuery(now));
|
||||
sendResponse(
|
||||
SC_OK, new WhoisReader(reader, commandFactory, now).readCommand().executeQuery(now));
|
||||
} catch (WhoisException e) {
|
||||
sendResponse(e.getStatus(), e);
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -14,8 +14,12 @@
|
|||
|
||||
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;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -42,4 +46,11 @@ public final class WhoisModule {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("whoisCommandFactory")
|
||||
static WhoisCommandFactory provideWhoisCommandFactory(
|
||||
@Config("whoisCommandFactoryClass") String factoryClass) {
|
||||
return instantiate(getClassFromString(factoryClass, WhoisCommandFactory.class));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,10 +70,12 @@ class WhoisReader {
|
|||
|
||||
private final Reader reader;
|
||||
private final DateTime now;
|
||||
private final WhoisCommandFactory commandFactory;
|
||||
|
||||
/** Creates a new WhoisReader that extracts its command from the specified Reader. */
|
||||
WhoisReader(Reader reader, DateTime now) {
|
||||
WhoisReader(Reader reader, WhoisCommandFactory commandFactory, DateTime now) {
|
||||
this.reader = checkNotNull(reader, "reader");
|
||||
this.commandFactory = checkNotNull(commandFactory, "commandFactory");
|
||||
this.now = checkNotNull(now, "now");
|
||||
}
|
||||
|
||||
|
@ -111,7 +113,7 @@ class WhoisReader {
|
|||
|
||||
// Try to parse the argument as a domain name.
|
||||
try {
|
||||
return new DomainLookupCommand(InternetDomainName.from(
|
||||
return commandFactory.domainLookup(InternetDomainName.from(
|
||||
canonicalizeDomainName(tokens.get(1))));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// If we can't interpret the argument as a host name, then return an error.
|
||||
|
@ -129,14 +131,14 @@ class WhoisReader {
|
|||
|
||||
// Try to parse the argument as an IP address.
|
||||
try {
|
||||
return new NameserverLookupByIpCommand(InetAddresses.forString(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 {
|
||||
return new NameserverLookupByHostCommand(InternetDomainName.from(
|
||||
return commandFactory.nameserverLookupByHost(InternetDomainName.from(
|
||||
canonicalizeDomainName(tokens.get(1))));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Silently ignore this exception.
|
||||
|
@ -153,14 +155,14 @@ class WhoisReader {
|
|||
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
|
||||
"Too few arguments to '%s' command.", REGISTRAR_LOOKUP_COMMAND));
|
||||
}
|
||||
return new RegistrarLookupCommand(Joiner.on(' ').join(tokens.subList(1, tokens.size())));
|
||||
return commandFactory.registrarLookup(Joiner.on(' ').join(tokens.subList(1, tokens.size())));
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return new NameserverLookupByIpCommand(InetAddresses.forString(arg1));
|
||||
return commandFactory.nameserverLookupByIp(InetAddresses.forString(arg1));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
@ -180,11 +182,11 @@ class WhoisReader {
|
|||
// If the target is exactly one level above the TLD, then this is an second level domain
|
||||
// (SLD) and we should do a domain lookup on it.
|
||||
if (targetName.parent().equals(tld.get())) {
|
||||
return new DomainLookupCommand(targetName, tld.get());
|
||||
return commandFactory.domainLookup(targetName, tld.get());
|
||||
}
|
||||
|
||||
// The target is more than one level above the TLD, so we'll assume it's a nameserver.
|
||||
return new NameserverLookupByHostCommand(targetName, tld.get());
|
||||
return commandFactory.nameserverLookupByHost(targetName, tld.get());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Silently ignore this exception.
|
||||
}
|
||||
|
@ -194,7 +196,7 @@ class WhoisReader {
|
|||
|
||||
// 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.
|
||||
return new RegistrarLookupCommand(Joiner.on(' ').join(tokens));
|
||||
return commandFactory.registrarLookup(Joiner.on(' ').join(tokens));
|
||||
}
|
||||
|
||||
/** Returns an ArrayList containing the contents of the String array minus any empty strings. */
|
||||
|
|
|
@ -58,9 +58,8 @@ public class WhoisServer implements Runnable {
|
|||
@Inject Clock clock;
|
||||
@Inject Reader input;
|
||||
@Inject Response response;
|
||||
@Inject
|
||||
@Config("whoisDisclaimer")
|
||||
String disclaimer;
|
||||
@Inject @Config("whoisCommandFactory") WhoisCommandFactory commandFactory;
|
||||
@Inject @Config("whoisDisclaimer") String disclaimer;
|
||||
@Inject WhoisServer() {}
|
||||
|
||||
@Override
|
||||
|
@ -69,7 +68,7 @@ public class WhoisServer implements Runnable {
|
|||
DateTime now = clock.nowUtc();
|
||||
try {
|
||||
responseText =
|
||||
new WhoisReader(input, now)
|
||||
new WhoisReader(input, commandFactory, now)
|
||||
.readCommand()
|
||||
.executeQuery(now)
|
||||
.getPlainTextOutput(PREFER_UNICODE, disclaimer);
|
||||
|
|
|
@ -71,6 +71,7 @@ public class WhoisHttpServerTest {
|
|||
result.requestPath = WhoisHttpServer.PATH + pathInfo;
|
||||
result.response = response;
|
||||
result.disclaimer = "Doodle Disclaimer";
|
||||
result.commandFactory = new WhoisCommandFactory();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,13 +31,9 @@ import org.junit.runners.JUnit4;
|
|||
@RunWith(JUnit4.class)
|
||||
public class WhoisReaderTest {
|
||||
|
||||
@Rule
|
||||
public final AppEngineRule appEngine = AppEngineRule.builder()
|
||||
.withDatastore()
|
||||
.build();
|
||||
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
|
||||
|
||||
@Rule
|
||||
public final ExceptionRule thrown = new ExceptionRule();
|
||||
@Rule public final ExceptionRule thrown = new ExceptionRule();
|
||||
|
||||
private final FakeClock clock = new FakeClock();
|
||||
|
||||
|
@ -48,7 +44,9 @@ public class WhoisReaderTest {
|
|||
|
||||
@SuppressWarnings("unchecked") // XXX: Generic abuse ftw.
|
||||
<T> T readCommand(String commandStr) throws Exception {
|
||||
return (T) new WhoisReader(new StringReader(commandStr), clock.nowUtc()).readCommand();
|
||||
return (T)
|
||||
new WhoisReader(new StringReader(commandStr), new WhoisCommandFactory(), clock.nowUtc())
|
||||
.readCommand();
|
||||
}
|
||||
|
||||
void assertLoadsExampleTld(String commandString) throws Exception {
|
||||
|
|
|
@ -66,6 +66,7 @@ public class WhoisServerTest {
|
|||
result.input = new StringReader(input);
|
||||
result.response = response;
|
||||
result.disclaimer = "Doodle Disclaimer";
|
||||
result.commandFactory = new WhoisCommandFactory();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue