mv com/google/domain/registry google/registry

This change renames directories in preparation for the great package
rename. The repository is now in a broken state because the code
itself hasn't been updated. However this should ensure that git
correctly preserves history for each file.
This commit is contained in:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,187 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.domain.registry.flows.EppXmlTransformer.unmarshal;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.tools.CommandUtilities.addHeader;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.flows.EppException;
import com.google.domain.registry.flows.EppXmlTransformer;
import com.google.domain.registry.model.domain.DesignatedContact;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.DomainCommand;
import com.google.domain.registry.model.domain.Period;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.model.domain.launch.LaunchNotice;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.eppinput.EppInput;
import com.google.domain.registry.model.eppinput.EppInput.ResourceCommandWrapper;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.model.smd.SignedMark;
import com.google.domain.registry.tools.soy.DomainAllocateSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.Work;
import java.util.ArrayList;
import java.util.List;
/** Command to allocated a domain from a domain application. */
@Parameters(separators = " =", commandDescription = "Allocate a domain application")
final class AllocateDomainCommand extends MutatingEppToolCommand {
@Parameter(
names = "--ids",
description = "Comma-delimited list of application IDs to update.",
required = true)
String ids;
private final List<Key<DomainApplication>> applicationKeys = new ArrayList<>();
@Override
protected String postExecute() throws Exception {
StringBuilder builder = new StringBuilder();
// Check to see that we allocated everything.
return builder.append(ofy().transactNewReadOnly(new Work<String>() {
@Override
public String run() {
String failureMessage = FluentIterable
.from(ofy().load().keys(applicationKeys).values())
.transform(new Function<DomainApplication, String>() {
@Override
public String apply(DomainApplication application) {
return application.getApplicationStatus() == ApplicationStatus.ALLOCATED
? null : application.getFullyQualifiedDomainName();
}})
.join(Joiner.on('\n').skipNulls());
return failureMessage.isEmpty() ? "ALL SUCCEEDED" : addHeader("FAILURES", failureMessage);
}})).toString();
}
/** Extract the registration period from the XML used to create the domain application. */
private static Period extractPeriodFromXml(byte[] xmlBytes) throws EppException {
EppInput eppInput = unmarshal(xmlBytes);
return ((DomainCommand.Create)
((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
.getResourceCommand()).getPeriod();
}
@Override
void initMutatingEppToolCommand() {
checkArgument(superuser, "This command MUST be run as --superuser.");
setSoyTemplate(DomainAllocateSoyInfo.getInstance(), DomainAllocateSoyInfo.CREATE);
ofy().transactNewReadOnly(new VoidWork() {
@Override
public void vrun() {
Iterable<Key<DomainApplication>> keys = transform(
Splitter.on(',').split(ids),
new Function<String, Key<DomainApplication>>() {
@Override
public Key<DomainApplication> apply(String applicationId) {
return Key.create(DomainApplication.class, applicationId);
}});
for (DomainApplication application : ofy().load().keys(keys).values()) {
// If the application is already allocated print a warning but do not fail.
if (application.getApplicationStatus() == ApplicationStatus.ALLOCATED) {
System.err.printf(
"Application %s has already been allocated\n", application.getRepoId());
continue;
}
// Ensure domain doesn't already have a final status which it shouldn't be updated from.
checkState(
!application.getApplicationStatus().isFinalStatus(),
"Application has final status %s",
application.getApplicationStatus());
try {
HistoryEntry history = checkNotNull(
ofy().load()
.type(HistoryEntry.class)
.ancestor(checkNotNull(application))
.order("modificationTime")
.first()
.now(),
"Could not find any history entries for domain application %s",
application.getRepoId());
String clientTransactionId =
emptyToNull(history.getTrid().getClientTransactionId());
Period period = checkNotNull(extractPeriodFromXml(history.getXmlBytes()));
checkArgument(period.getUnit() == Period.Unit.YEARS);
ImmutableMap.Builder<String, String> contactsMapBuilder = new ImmutableMap.Builder<>();
for (DesignatedContact contact : application.getContacts()) {
contactsMapBuilder.put(
contact.getType().toString().toLowerCase(),
contact.getContactId().getLinked().get().getForeignKey());
}
LaunchNotice launchNotice = application.getLaunchNotice();
addSoyRecord(application.getCurrentSponsorClientId(), new SoyMapData(
"name", application.getFullyQualifiedDomainName(),
"period", period.getValue(),
"nameservers", FluentIterable.from(application.loadNameservers())
.transform(new Function<HostResource, String>() {
@Override
public String apply(HostResource host) {
return host.getForeignKey();
}})
.toList(),
"registrant", application.loadRegistrant().getForeignKey(),
"contacts", contactsMapBuilder.build(),
"authInfo", application.getAuthInfo().getPw().getValue(),
"smdId", application.getEncodedSignedMarks().isEmpty()
? null : EppXmlTransformer.<SignedMark>unmarshal(
application.getEncodedSignedMarks().get(0).getBytes()).getId(),
"applicationRoid", application.getRepoId(),
"applicationTime", application.getCreationTime().toString(),
"launchNotice", launchNotice == null ? null : ImmutableMap.of(
"noticeId", launchNotice.getNoticeId().getTcnId(),
"expirationTime", launchNotice.getExpirationTime().toString(),
"acceptedTime", launchNotice.getAcceptedTime().toString()),
"dsRecords", FluentIterable.from(application.getDsData())
.transform(new Function<DelegationSignerData, ImmutableMap<String, ?>>() {
@Override
public ImmutableMap<String, ?> apply(DelegationSignerData dsData) {
return ImmutableMap.of(
"keyTag", dsData.getKeyTag(),
"algorithm", dsData.getAlgorithm(),
"digestType", dsData.getDigestType(),
"digest", base16().encode(dsData.getDigest()));
}})
.toList(),
"clTrid", clientTransactionId));
applicationKeys.add(Key.create(application));
} catch (EppException e) {
throw new RuntimeException(e);
}
}
}
});
}
}

View file

@ -0,0 +1,179 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.HttpHeaders.X_REQUESTED_WITH;
import static com.google.common.net.MediaType.JSON_UTF_8;
import static com.google.domain.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
import static com.google.domain.registry.security.XsrfTokenManager.X_CSRF_TOKEN;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import com.google.common.net.HostAndPort;
import com.google.common.net.MediaType;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.security.XsrfTokenManager;
import com.google.domain.registry.tools.ServerSideCommand.Connection;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.Duration;
import org.json.simple.JSONValue;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** An http connection to the appengine server. */
@Parameters(separators = " =")
class AppEngineConnection implements Connection {
/** Pattern to heuristically extract title tag contents in HTML responses. */
private static final Pattern HTML_TITLE_TAG_PATTERN = Pattern.compile("<title>(.*?)</title>");
@Parameter(
names = "--server",
description = "HOST[:PORT] to which remote commands are sent.")
private HostAndPort server = RegistryEnvironment.get().config().getServer();
@Parameter(
names = "--remote_server_spec",
description = "Combined server spec for the backend to connect to for remote logging.")
private String remoteServerSpec = "gslb:apphosting-frontend:4";
@Parameter(
names = "--remote_connection_timeout",
description = "How long to wait for the remote logger server before giving up.")
private Duration remoteConnectionTimeout = Duration.standardSeconds(30);
/**
* Memoized XSRF security token.
*
* <p>Computing this is expensive since it needs to load {@code ServerSecret} so do it once.
*/
private final Supplier<String> xsrfToken =
memoize(new Supplier<String>() {
@Override
public String get() {
return XsrfTokenManager.generateToken("admin", getUserId());
}});
@Override
public void prefetchXsrfToken() throws IOException {
// Cause XSRF token to be fetched, and then stay resident in cache (since it's memoized).
xsrfToken.get();
}
/** Returns the contents of the title tag in the given HTML, or null if not found. */
private static String extractHtmlTitle(String html) {
Matcher matcher = HTML_TITLE_TAG_PATTERN.matcher(html);
return (matcher.find() ? matcher.group(1) : null);
}
@Override
public String send(
String endpoint, Map<String, ?> params, MediaType contentType, byte[] payload)
throws IOException {
HttpURLConnection connection = getHttpURLConnection(
new URL(String.format("http://%s%s?%s", getServer(), endpoint, encodeParams(params))));
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setRequestProperty(CONTENT_TYPE, contentType.toString());
connection.setRequestProperty(X_CSRF_TOKEN, xsrfToken.get());
connection.setRequestProperty(X_REQUESTED_WITH, "RegistryTool");
connection.setDoOutput(true);
connection.connect();
try (OutputStream output = connection.getOutputStream()) {
output.write(payload);
}
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String errorTitle = extractHtmlTitle(
CharStreams.toString(new InputStreamReader(connection.getErrorStream(), UTF_8)));
throw new IOException(String.format(
"Error from %s: %d %s%s",
connection.getURL(),
connection.getResponseCode(),
connection.getResponseMessage(),
(errorTitle == null ? "" : ": " + errorTitle)));
}
return CharStreams.toString(new InputStreamReader(connection.getInputStream(), UTF_8));
}
private String encodeParams(Map<String, ?> params) {
return Joiner.on('&').join(Iterables.transform(
params.entrySet(),
new Function<Entry<String, ?>, String>() {
@Override
public String apply(Entry<String, ?> entry) {
try {
return entry.getKey()
+ "=" + URLEncoder.encode(entry.getValue().toString(), UTF_8.name());
} catch (Exception e) { // UnsupportedEncodingException
throw new RuntimeException(e);
}
}}));
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> sendJson(String endpoint, Map<String, ?> object) throws IOException {
String response = send(
endpoint,
ImmutableMap.<String, Object>of(),
JSON_UTF_8,
JSONValue.toJSONString(object).getBytes(UTF_8));
return (Map<String, Object>) JSONValue.parse(response.substring(JSON_SAFETY_PREFIX.length()));
}
private HttpURLConnection getHttpURLConnection(URL remoteUrl) throws IOException {
// TODO: Figure out authentication.
return (HttpURLConnection) remoteUrl.openConnection();
}
@Override
public String getServerUrl() {
return "https://" + getServer().toString().replaceFirst("\\.", "-dot-");
}
HostAndPort getServer() {
return server;
}
boolean isLocalhost() {
return server.getHostText().equals("localhost");
}
private String getUserId() {
return isLocalhost()
? UserIdProvider.getTestUserId()
: UserIdProvider.getProdUserId();
}
}

View file

@ -0,0 +1,121 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.findTldForName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Work;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/** Command to check the status of domain applications. */
@Parameters(separators = " =", commandDescription = "Check auction status")
final class AuctionStatusCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
description = "Domains(s) to check",
required = true)
private List<String> mainArguments;
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Override
public void run() throws Exception {
final ImmutableSet<String> domains = ImmutableSet.copyOf(mainArguments);
Files.write(output, FluentIterable
.from(domains)
.transformAndConcat(new Function<String, Iterable<String>>() {
@Override
public Iterable<String> apply(String fullyQualifiedDomainName) {
checkState(
findTldForName(InternetDomainName.from(fullyQualifiedDomainName)).isPresent(),
"No tld found for %s", fullyQualifiedDomainName);
return ofy().transactNewReadOnly(new Work<Iterable<String>>() {
@Override
public Iterable<String> run() {
ImmutableList.Builder<DomainApplication> applications =
new ImmutableList.Builder<>();
for (String domain : domains) {
applications.addAll(
loadActiveApplicationsByDomainName(domain, ofy().getTransactionTime()));
}
return Lists.transform(
FluentIterable.from(applications.build()).toSortedList(ORDERING),
APPLICATION_FORMATTER);
}});
}}), UTF_8);
}
private static final Ordering<DomainApplication> ORDERING = new Ordering<DomainApplication>() {
@Override
public int compare(DomainApplication left, DomainApplication right) {
return ComparisonChain.start()
.compare(left.getFullyQualifiedDomainName(), right.getFullyQualifiedDomainName())
.compareTrueFirst(
left.getEncodedSignedMarks().isEmpty(), right.getEncodedSignedMarks().isEmpty())
.compare(left.getApplicationStatus(), right.getApplicationStatus())
.compare(left.getCreationTime(), right.getCreationTime())
.result();
}};
private static final Function<DomainApplication, String> APPLICATION_FORMATTER =
new Function<DomainApplication, String>() {
@Override
public String apply(DomainApplication app) {
ContactResource registrant = checkNotNull(app.loadRegistrant());
Object[] keysAndValues = new Object[] {
"Domain", app.getFullyQualifiedDomainName(),
"Type", app.getEncodedSignedMarks().isEmpty() ? "Landrush" : "Sunrise",
"Application Status", app.getApplicationStatus(),
"Application ID", app.getForeignKey(),
"Application Timestamp", app.getCreationTime(),
"Last Update", app.getLastEppUpdateTime(),
"Registrar Name", app.getCurrentSponsorClientId(),
"Registrant Email", registrant.getEmailAddress(),
"Registrant Phone", registrant.getVoiceNumber().getPhoneNumber()
};
return String.format(
Strings.repeat("%-25s= %s\n", keysAndValues.length / 2), keysAndValues);
}};
}

View file

@ -0,0 +1,91 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
# Restrict visibility to :tools because :remoteapi-internal (and transitively
# :appengine-api-link) should never be linked into an App Engine deploy jar,
# since the App Engine API is provided by the runtime environment.
package_group(
name = "allowed-tools",
packages = [
"//java/com/google/domain/registry/testing",
"//java/com/google/domain/registry/tools",
"//java/com/google/domain/registry/eclipse",
"//javatests/com/google/domain/registry/tools",
],
)
java_library(
name = "tools",
srcs = glob([
"*.java",
"javascrap/*.java",
]),
resources = glob([
"*.properties",
"sql/*.sql",
]),
visibility = [":allowed-tools"],
deps = [
"//apiserving/discoverydata/bigquery:bigqueryv2",
"//java/com/google/api/client/googleapis/auth/oauth2",
"//java/com/google/api/client/http",
"//java/com/google/api/client/http/javanet",
"//java/com/google/api/client/json",
"//java/com/google/api/client/json/jackson2",
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//java/com/google/common/hash",
"//java/com/google/common/io",
"//java/com/google/common/net",
"//java/com/google/common/util/concurrent",
"//java/com/google/domain/registry/bigquery",
"//java/com/google/domain/registry/config",
"//java/com/google/domain/registry/export",
"//java/com/google/domain/registry/flows",
"//java/com/google/domain/registry/keyring/api",
"//java/com/google/domain/registry/model",
"//java/com/google/domain/registry/rde",
"//java/com/google/domain/registry/security",
"//java/com/google/domain/registry/request:modules",
"//java/com/google/domain/registry/tldconfig/idn",
"//java/com/google/domain/registry/tmch",
"//java/com/google/domain/registry/tools/params",
"//java/com/google/domain/registry/tools/server",
"//java/com/google/domain/registry/tools/soy:soy_java_wrappers",
"//java/com/google/domain/registry/util",
"//java/com/google/domain/registry/whois",
"//java/com/google/domain/registry/xjc",
"//java/com/google/domain/registry/xml",
"//third_party/java/appengine:appengine-api",
"//third_party/java/bouncycastle",
"//third_party/java/bouncycastle_bcpg",
"//third_party/java/dagger",
"//third_party/java/jcommander",
"//third_party/java/joda_money",
"//third_party/java/joda_time",
"//third_party/java/json",
"//third_party/java/json_simple",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/objectify:objectify-v4_1",
"//third_party/java/appengine:appengine-remote-api",
"//third_party/closure/templates",
],
)
java_binary(
name = "registry_tool",
create_executable = 1,
main_class = "com.google.domain.registry.tools.RegistryTool",
runtime_deps = [
":tools",
"//third_party/java/appengine:appengine-api-link",
"//third_party/java/appengine:appengine-remote-api-link",
],
)

View file

@ -0,0 +1,46 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.bigquery.BigqueryConnection;
import com.beust.jcommander.ParametersDelegate;
/** A {@link Command} that uses the bigquery client API. */
abstract class BigqueryCommand implements Command {
/** Parameter delegate for encapsulating flags needed to set up the {@link BigqueryConnection}. */
@ParametersDelegate
private BigqueryParameters bigqueryParameters = new BigqueryParameters();
/** Connection object for interacting with the Bigquery API. */
private BigqueryConnection bigquery;
@Override
public void run() throws Exception {
try (BigqueryConnection autoClosingBigquery = bigqueryParameters.newConnection()) {
bigquery = autoClosingBigquery;
runWithBigquery();
}
}
/** Returns the {@link BigqueryConnection} object that has been initialized for use. */
BigqueryConnection bigquery() {
return bigquery;
}
/** Subclasses must override this to define command behavior. */
abstract void runWithBigquery() throws Exception;
}

View file

@ -0,0 +1,54 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.domain.registry.bigquery.BigqueryConnection.DestinationTable;
import java.util.concurrent.ExecutionException;
/** Container class for static utility methods for Bigquery commands. */
final class BigqueryCommandUtilities {
/**
* Handler that takes a DestinationTable future and waits on its completion, printing generic
* success/failure messages and wrapping any exception thrown in a TableCreationException.
*/
static void handleTableCreation(
String tableDescription,
ListenableFuture<DestinationTable> tableFuture) throws TableCreationException {
System.err.printf("Creating %s...\n", tableDescription);
try {
DestinationTable table = tableFuture.get();
System.err.printf(" - Success: created %s.\n", table.getStringReference());
} catch (Exception e) {
Throwable error = e;
if (e instanceof ExecutionException) {
error = e.getCause();
}
String errorMessage =
String.format("Failed to create %s: %s", tableDescription, error.getMessage());
System.err.printf(" - %s\n", errorMessage);
throw new TableCreationException(errorMessage, error);
}
}
/** Exception thrown if any error occurs during a table creation stage. */
static class TableCreationException extends Exception {
TableCreationException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,103 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.bigquery.BigqueryScopes;
import com.google.domain.registry.bigquery.BigqueryConnection;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.Duration;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.Executors;
/** Parameter delegate class to handle flag settings for a command's BigqueryConnection object. */
@Parameters(separators = " =")
final class BigqueryParameters {
/**
* Default to 20 threads to stay within Bigquery's rate limit of 20 concurrent queries.
*
* @see "https://cloud.google.com/bigquery/quota-policy"
*/
private static final int DEFAULT_NUM_THREADS = 20;
@Parameter(
names = "--bigquery_service_account",
description = "Email for the Google APIs service account to use.")
private String bigqueryServiceAccountEmail =
"1080941367941-ic4pknfqcj1q7hhc9ob0bls920v80unu@developer.gserviceaccount.com";
@Parameter(
names = "--bigquery_service_account_key",
description = "PKCS file (.p12) containing the private key for the service account.",
validateWith = PathParameter.InputFile.class)
private Path bigqueryServiceAccountKeyFile = Paths.get("key.p12");
@Parameter(
names = "--bigquery_dataset",
description = "Name of the default dataset to use, for reading and writing.")
private String bigqueryDataset = BigqueryConnection.DEFAULT_DATASET_NAME;
@Parameter(
names = "--bigquery_overwrite",
description = "Whether to automatically overwrite existing tables and views.")
private boolean bigqueryOverwrite;
@Parameter(
names = "--bigquery_poll_interval",
description = "Interval in milliseconds to wait between polls for job status.")
private Duration bigqueryPollInterval = Duration.standardSeconds(1);
@Parameter(
names = "--bigquery_num_threads",
description = "Number of threads for running simultaneous BigQuery operations.")
private int bigqueryNumThreads = DEFAULT_NUM_THREADS;
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Returns a new BigqueryConnection constructed according to the delegate's flag settings. */
BigqueryConnection newConnection() throws Exception {
BigqueryConnection connection = new BigqueryConnection.Builder()
.setExecutorService(Executors.newFixedThreadPool(bigqueryNumThreads))
.setCredential(newCredential())
.setDatasetId(bigqueryDataset)
.setOverwrite(bigqueryOverwrite)
.setPollInterval(bigqueryPollInterval)
.build();
connection.initialize();
return connection;
}
/** Creates a credential object for the Bigquery client service using a service account. */
private GoogleCredential newCredential() throws Exception {
return new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(bigqueryServiceAccountEmail)
.setServiceAccountScopes(BigqueryScopes.all())
.setServiceAccountPrivateKeyFromP12File(bigqueryServiceAccountKeyFile.toFile())
.build();
}
}

View file

@ -0,0 +1,91 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.util.DomainNameUtils;
import com.google.domain.registry.util.Idn;
import com.google.domain.registry.util.NonFinalForTesting;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/** Command to clean up a set of labels and turn them into punycode. */
@Parameters(commandDescription = "Canonicalize domain labels")
final class CanonicalizeLabelsCommand implements Command, GtechCommand {
@Parameter(
description = "Filename of file containing domain labels, one per line",
required = true)
private List<String> mainParameters;
@NonFinalForTesting
private static InputStream stdin = System.in;
@Override
public void run() throws IOException {
Set<String> labels = new TreeSet<>();
for (String label : mainParameters.isEmpty()
? CharStreams.readLines(new InputStreamReader(stdin))
: Files.readLines(new File(mainParameters.get(0)), UTF_8)) {
label = label.trim();
if (label.startsWith("-")) {
label = label.substring(1);
}
if (label.endsWith("-")) {
label = label.substring(0, label.length() - 1);
}
String canonical = canonicalize(label);
if (canonical.startsWith(DomainNameUtils.ACE_PREFIX)
&& Idn.toUnicode(canonical).equals(canonical)) {
System.err.println("Bad IDN: " + label);
continue; // Bad IDN code points.
}
labels.add(canonical);
if (!canonical.startsWith("xn--")) {
// Using both "" and "-" to canonicalize labels.
labels.add(canonicalize(label.replaceAll(" ", "")));
labels.add(canonicalize(label.replaceAll(" ", "-")));
labels.add(canonicalize(label.replaceAll("_", "")));
labels.add(canonicalize(label.replaceAll("_", "-")));
}
}
labels.remove(""); // We used "" for invalid labels.
System.out.println(Joiner.on('\n').join(labels));
}
private String canonicalize(String rawLabel) {
try {
return canonicalizeDomainName(rawLabel.replaceAll(" ", ""));
} catch (Exception e) {
System.err.printf("Error canonicalizing %s: %s\n", rawLabel, e.getMessage());
return "";
}
}
}

View file

@ -0,0 +1,49 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.Iterables;
import com.google.domain.registry.export.DatastoreBackupInfo;
import com.google.domain.registry.export.DatastoreBackupService;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/**
* Command to check the status of a datastore backup, or "snapshot".
*/
@Parameters(separators = " =", commandDescription = "Check the status of a datastore snapshot")
public class CheckSnapshotCommand implements RemoteApiCommand {
@Parameter(
names = {"-s", "--snapshot"},
description = "Unique prefix of the snapshot to check",
required = true)
private String snapshotName;
@Override
public void run() throws Exception {
Iterable<DatastoreBackupInfo> backups =
DatastoreBackupService.get().findAllByNamePrefix(snapshotName);
if (Iterables.isEmpty(backups)) {
System.err.println("No snapshot found with name: " + snapshotName);
return;
}
for (DatastoreBackupInfo backup : backups) {
System.out.println(backup.getInformation());
}
}
}

View file

@ -0,0 +1,35 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
/** Interface of all commands. */
public interface Command {
/** Performs the command. */
void run() throws Exception;
/**
* Marker interface for commands that use the remote api.
* <p>
* Just implementing this is sufficient to use the remote api; {@link RegistryTool} will install
* it as needed.
*/
public interface RemoteApiCommand extends Command {}
/**
* Marker interface for commands that are gTech safe.
*/
public interface GtechCommand extends Command {}
}

View file

@ -0,0 +1,68 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.flows.EppXmlTransformer.marshalWithLenientRetry;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.domain.registry.flows.EppException;
import com.google.domain.registry.flows.FlowRunner;
import com.google.domain.registry.flows.FlowRunner.CommitMode;
import com.google.domain.registry.flows.FlowRunner.UserPrivileges;
/** Container class for static utility methods. */
class CommandUtilities {
static String addHeader(String header, String body) {
return String.format("%s:\n%s\n%s", header, Strings.repeat("-", header.length() + 1), body);
}
/** Prompts for yes/no input using promptText, defaulting to no. */
static boolean promptForYes(String promptText) {
return promptForYesOrNo(promptText, false);
}
/**
* Prompts for yes/no input using promptText and returns true for yes and false for no, using
* defaultResponse as the response for empty input.
*/
static boolean promptForYesOrNo(String promptText, boolean defaultResponse) {
String options = defaultResponse ? "Y/n" : "y/N";
while (true) {
String line = System.console().readLine(String.format("%s (%s): ", promptText, options));
if (line.isEmpty()) {
return defaultResponse;
} else if ("Y".equalsIgnoreCase(line.substring(0, 1))) {
return true;
} else if ("N".equalsIgnoreCase(line.substring(0, 1))) {
return false;
}
}
}
/** Prints the provided text with a trailing newline, if text is not null or empty. */
static void printLineIfNotEmpty(String text) {
if (!Strings.isNullOrEmpty(text)) {
System.out.println(text);
}
}
static String runFlow(
FlowRunner flowRunner, CommitMode commitMode, UserPrivileges userPrivileges)
throws EppException {
return new String(marshalWithLenientRetry(flowRunner.run(commitMode, userPrivileges)), UTF_8);
}
}

View file

@ -0,0 +1,67 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.tools.CommandUtilities.printLineIfNotEmpty;
import static com.google.domain.registry.tools.CommandUtilities.promptForYes;
import com.beust.jcommander.Parameter;
/** A {@link Command} that implements a confirmation step before executing. */
public abstract class ConfirmingCommand implements Command {
@Parameter(
names = {"-f", "--force"},
description = "Do not prompt before executing")
boolean force;
@Override
public final void run() throws Exception {
if (checkExecutionState()) {
init();
printLineIfNotEmpty(prompt());
if (force || promptForYes("Perform this command?")) {
System.out.println(execute());
printLineIfNotEmpty(postExecute());
} else {
System.out.println("Command aborted.");
}
}
}
/** Run any pre-execute command checks and return true if they all pass. */
protected boolean checkExecutionState() throws Exception {
return true;
}
/** Initializes the command. */
protected void init() throws Exception {}
/** Returns the optional extra confirmation prompt for the command. */
protected String prompt() throws Exception {
return "";
}
/** Perform the command and return a result description. */
protected abstract String execute() throws Exception;
/**
* Perform any post-execution steps (e.g. verifying the result), and return a description String
* to be printed if non-empty.
*/
protected String postExecute() throws Exception {
return "";
}
}

View file

@ -0,0 +1,48 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.util.DomainNameUtils.ACE_PREFIX;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.util.Idn;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.IOException;
import java.util.List;
/** Command to convert IDN labels to/from punycode. */
@Parameters(commandDescription = "Convert IDNs to/from punycode")
final class ConvertIdnCommand implements Command, GtechCommand {
@Parameter(
description = "Labels to convert",
required = true)
private List<String> mainParameters;
@Override
public void run() throws IOException {
for (String label : mainParameters) {
if (label.startsWith(ACE_PREFIX)) {
System.out.println(Idn.toUnicode(label.toLowerCase()));
} else {
System.out.println(canonicalizeDomainName(label));
}
}
}
}

View file

@ -0,0 +1,102 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.domain.registry.model.registry.Registries.findTldForNameOrThrow;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.CreateAnchorTenantSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.Money;
import javax.inject.Inject;
/** A command to create a new anchor tenant domain. */
@Parameters(separators = " =", commandDescription = "Provision a domain for an anchor tenant.")
final class CreateAnchorTenantCommand extends MutatingEppToolCommand implements GtechCommand {
private static final int PASSWORD_LENGTH = 16;
private static final int DEFAULT_ANCHOR_TENANT_PERIOD_YEARS = 2;
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
names = {"-n", "--domain_name"},
description = "Domain to create.",
required = true)
private String domainName;
@Parameter(
names = {"--contact"},
description = "Contact ID for the request. This will be used for registrant, admin contact,"
+ "and tech contact.",
required = true)
private String contact;
@Parameter(
names = {"--reason"},
description = "Reason for the change.")
private String reason;
@Parameter(
names = {"--password"},
description = "Password. Optional, randomly generated if not provided.")
private String password;
@Parameter(
names = {"--fee"},
description = "Include fee extension in EPP (required for premium domains).")
private boolean fee;
@Inject
PasswordGenerator passwordGenerator;
@Override
void initMutatingEppToolCommand() {
checkArgument(superuser, "This command must be run as a superuser.");
String tld = findTldForNameOrThrow(InternetDomainName.from(domainName)).toString();
if (isNullOrEmpty(password)) {
password = passwordGenerator.createPassword(PASSWORD_LENGTH);
}
Money cost = null;
if (fee) {
cost = Registry.get(tld).getDomainCreateCost(domainName, DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
}
setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(),
CreateAnchorTenantSoyInfo.CREATEANCHORTENANT);
addSoyRecord(clientIdentifier, new SoyMapData(
"domainName", domainName,
"contactId", contact,
"reason", reason,
"password", password,
"period", DEFAULT_ANCHOR_TENANT_PERIOD_YEARS,
"feeCurrency", cost != null ? cost.getCurrencyUnit().toString() : null,
"fee", cost != null ? cost.getAmount().toString() : null));
}
}

View file

@ -0,0 +1,215 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static java.util.Arrays.asList;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCredit.CreditType;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registry.Registry;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Command for creating new auction credits based on a CSV file from Pool.
* <p>
* The CSV file from the auction provider uses double-quotes around every field, so in order to
* extract the raw field value we strip off the quotes after splitting each line by commas. We are
* using a simple parsing strategy that does not support embedded quotation marks, commas, or
* newlines.
* <p>
* TODO(b/16009815): Switch this file to using a real CSV parser.
* <p>
* Example file format:
* <pre>
* "Affiliate","DomainName","Email","BidderId","BidderStatus","UpdatedAt",
* "SalePrice","Commissions","CurrencyCode"
* "reg1","foo.xn--q9jyb4c","email1@example.com","???_300","INACTIVE","4/3/2014 7:13:09 PM",
* "1000.0000","0.0000","JPY"
* "reg2","foo.xn--q9jyb4c","email2@example.net","???_64","WIN","4/3/2014 7:13:09 PM",
* "1000.0000","40.0000","JPY"
* </pre>
* We only care about three fields: 1) the "Affiliate" field which corresponds to the registrar
* clientId stored in datastore, and which we use to determine which registrar gets the credit,
* 2) the "Commissions" field which contains the amount of the auction credit (as determined by
* logic on the auction provider's side, see the Finance Requirements Doc for more information), and
* 3) the "CurrencyCode" field, which we validate matches the TLD-wide currency for this TLD.
*/
@Parameters(separators = " =", commandDescription = "Create new auction credits based on CSV")
final class CreateAuctionCreditsCommand extends MutatingCommand {
@Parameter(
names = "--input_file",
description = "CSV file for the Pool.com commissions report",
required = true)
private Path inputFile;
@Parameter(
names = {"-t", "--tld"},
description = "The TLD corresponding to this commissions report",
required = true)
private String tld;
@Parameter(
names = "--effective_time",
description = "The time at which these auction credits should become effective",
required = true)
private DateTime effectiveTime;
/** Enum containing the headers we expect in the Pool.com CSV file, in order. */
private enum CsvHeader {
AFFILIATE,
DOMAIN_NAME,
EMAIL,
BIDDER_ID,
BIDDER_STATUS,
UPDATED_AT,
SALE_PRICE,
COMMISSIONS,
CURRENCY_CODE;
public static List<String> getHeaders() {
return FluentIterable.from(asList(values()))
.transform(new Function<CsvHeader, String>() {
@Override
public String apply(CsvHeader header) {
// Returns the name of the header as it appears in the CSV file.
return UPPER_UNDERSCORE.to(UPPER_CAMEL, header.name());
}})
.toList();
}
}
private static final Pattern QUOTED_STRING = Pattern.compile("\"(.*)\"");
/** Helper function to unwrap a quoted string, failing if the string is not quoted. */
private static final Function<String, String> UNQUOTER = new Function<String, String>() {
@Override
public String apply(String input) {
Matcher matcher = QUOTED_STRING.matcher(input);
checkArgument(matcher.matches(), "Input not quoted");
return matcher.group(1);
}};
/** Returns the input string of quoted CSV values split into the list of unquoted values. */
private static List<String> splitCsvLine(String line) {
return FluentIterable.from(Splitter.on(',').split(line)).transform(UNQUOTER).toList();
}
@Override
protected void init() throws Exception {
assertTldExists(tld);
ImmutableMultimap<Registrar, BigMoney> creditMap = parseCreditsFromCsv(inputFile, tld);
stageCreditCreations(creditMap);
}
/**
* Parses the provided CSV file of data from the auction provider and returns a multimap mapping
* each registrar to the collection of auction credit amounts from this TLD's auctions that should
* be awarded to this registrar, and validating that every credit amount's currency is in the
* specified TLD-wide currency.
*/
private static ImmutableMultimap<Registrar, BigMoney> parseCreditsFromCsv(
Path csvFile, String tld) throws IOException {
List<String> lines = Files.readAllLines(csvFile, StandardCharsets.UTF_8);
checkArgument(CsvHeader.getHeaders().equals(splitCsvLine(lines.get(0))),
"Expected CSV header line not present");
ImmutableMultimap.Builder<Registrar, BigMoney> builder = new ImmutableMultimap.Builder<>();
for (String line : Iterables.skip(lines, 1)) {
List<String> fields = splitCsvLine(line);
checkArgument(CsvHeader.getHeaders().size() == fields.size(), "Wrong number of fields");
try {
String registrarId = fields.get(CsvHeader.AFFILIATE.ordinal());
Registrar registrar = checkNotNull(
Registrar.loadByClientId(registrarId), "Registrar %s not found", registrarId);
CurrencyUnit tldCurrency = Registry.get(tld).getCurrency();
CurrencyUnit currency = CurrencyUnit.of((fields.get(CsvHeader.CURRENCY_CODE.ordinal())));
checkArgument(tldCurrency.equals(currency),
"Credit in wrong currency (%s should be %s)", currency, tldCurrency);
// We use BigDecimal and BigMoney to preserve fractional currency units when computing the
// total amount of each credit (since auction credits are percentages of winning bids).
BigDecimal creditAmount = new BigDecimal(fields.get(CsvHeader.COMMISSIONS.ordinal()));
BigMoney credit = BigMoney.of(currency, creditAmount);
builder.put(registrar, credit);
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
throw new IllegalArgumentException("Error in line: " + line, e);
}
}
return builder.build();
}
/**
* Stages the creation of RegistrarCredit and RegistrarCreditBalance instances for each
* registrar in the provided multimap of credit amounts by registrar. The balance instance
* created is the total of all the credit amounts for a given registrar.
*/
private void stageCreditCreations(ImmutableMultimap<Registrar, BigMoney> creditMap) {
DateTime now = DateTime.now(UTC);
CurrencyUnit currency = Registry.get(tld).getCurrency();
for (Registrar registrar : creditMap.keySet()) {
// Use RoundingMode.UP to be nice and give registrars the extra fractional units.
Money totalAmount =
BigMoney.total(currency, creditMap.get(registrar)).toMoney(RoundingMode.UP);
System.out.printf("Total auction credit balance for %s: %s\n",
registrar.getClientIdentifier(), totalAmount);
// Create the actual credit and initial credit balance.
RegistrarCredit credit = new RegistrarCredit.Builder()
.setParent(registrar)
.setType(CreditType.AUCTION)
.setCreationTime(now)
.setCurrency(currency)
.setTld(tld)
.build();
RegistrarCreditBalance creditBalance = new RegistrarCreditBalance.Builder()
.setParent(credit)
.setEffectiveTime(effectiveTime)
.setWrittenTime(now)
.setAmount(totalAmount)
.build();
stageEntityChange(null, credit);
stageEntityChange(null, creditBalance);
}
}
}

View file

@ -0,0 +1,137 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.params.PhoneNumberParameter;
import com.google.domain.registry.tools.soy.CreateContactSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
import javax.inject.Inject;
/** A command to create a new contact via EPP. */
@Parameters(separators = " =", commandDescription = "Create a new contact via EPP.")
final class CreateContactCommand extends MutatingEppToolCommand implements GtechCommand {
// TODO(b/19016175): Expand to allow full suite of contact flows.
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
names = {"--id"},
description = "Contact ID.")
private String id;
@Parameter(
names = {"--name"},
description = "Contact name.")
private String name;
@Parameter(
names = {"--org"},
description = "Organization")
private String org;
@Parameter(
names = {"--street"},
description = "Street lines of address. Can take up to 3 lines.",
variableArity = true)
private List<String> street;
@Parameter(
names = {"--city"},
description = "City of address.")
private String city;
@Parameter(
names = {"--state"},
description = "State of address.")
private String state;
@Parameter(
names = {"--zip"},
description = "Postal code of address.")
private String zip;
@Parameter(
names = {"--cc"},
description = "Country code of address.")
private String cc;
@Parameter(
names = "--phone",
description = "E.164 phone number, e.g. +1.2125650666",
converter = PhoneNumberParameter.class,
validateWith = PhoneNumberParameter.class)
String phone;
@Parameter(
names = "--fax",
description = "E.164 fax number, e.g. +1.2125650666",
converter = PhoneNumberParameter.class,
validateWith = PhoneNumberParameter.class)
String fax;
@Parameter(
names = {"--email"},
description = "Email address.")
private String email;
@Parameter(
names = {"--password"},
description = "Password. Optional, randomly generated if not provided.")
private String password;
@Inject
PasswordGenerator passwordGenerator;
private static final int PASSWORD_LENGTH = 16;
@Override
void initMutatingEppToolCommand() {
if (isNullOrEmpty(password)) {
password = passwordGenerator.createPassword(PASSWORD_LENGTH);
}
checkArgument(street == null || street.size() <= 3,
"Addresses must contain at most 3 street lines.");
setSoyTemplate(CreateContactSoyInfo.getInstance(),
CreateContactSoyInfo.CREATECONTACT);
addSoyRecord(clientIdentifier, new SoyMapData(
"id", id,
"name", name,
"org", org,
"street", street,
"city", city,
"state", state,
"zip", zip,
"cc", cc,
"phone", phone,
"fax", fax,
"email", email,
"password", password));
}
}

View file

@ -0,0 +1,78 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.util.SystemClock;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** Command for creating a new balance for a registrar credit. */
@Parameters(separators = " =", commandDescription = "Create a new registrar credit balance")
final class CreateCreditBalanceCommand extends MutatingCommand implements GtechCommand {
@Parameter(
names = "--registrar",
description = "Client ID of the registrar owning the credit to create a new balance for",
required = true)
private String registrarId;
@Parameter(
names = "--credit_id",
description = "ID of credit to create a new balance for",
required = true)
private long creditId;
@Parameter(
names = "--balance",
description = "The new balance amount",
required = true)
private Money balance;
@Parameter(
names = "--effective_time",
description = "Point in time at which the new balance amount becomes effective",
required = true)
private DateTime effectiveTime;
@Override
public void init() throws Exception {
Registrar registrar = checkNotNull(
Registrar.loadByClientId(registrarId), "Registrar %s not found", registrarId);
RegistrarCredit credit = ofy().load()
.type(RegistrarCredit.class)
.parent(registrar)
.id(creditId)
.now();
checkNotNull(credit, "Registrar credit for %s with ID %s not found", registrarId, creditId);
RegistrarCreditBalance newBalance = new RegistrarCreditBalance.Builder()
.setParent(credit)
.setEffectiveTime(effectiveTime)
.setWrittenTime(new SystemClock().nowUtc())
.setAmount(balance)
.build();
stageEntityChange(null, newBalance);
}
}

View file

@ -0,0 +1,96 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.joda.time.DateTimeZone.UTC;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCredit.CreditType;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.Money;
import org.joda.time.DateTime;
import javax.annotation.Nullable;
/** Command for creating a registrar credit object with an initial balance. */
@Parameters(separators = " =", commandDescription = "Create a new registrar credit")
final class CreateCreditCommand extends MutatingCommand implements GtechCommand {
@Parameter(
names = "--registrar",
description = "Client ID of the registrar who will be awarded this credit",
required = true)
private String registrarId;
@Parameter(
names = "--type",
description = "Type of credit (AUCTION or PROMOTION)",
required = true)
private CreditType type;
@Nullable
@Parameter(
names = "--description",
description = "Custom description that will appear on invoice line for this credit")
private String description;
@Parameter(
names = "--tld",
description = "TLD for which this credit applies",
required = true)
private String tld;
@Parameter(
names = "--balance",
description = "Initial balance of this credit",
required = true)
private Money balance;
@Parameter(
names = "--effective_time",
description = "Point in time at which the initial balance becomes effective",
required = true)
private DateTime effectiveTime;
@Override
protected void init() throws Exception {
DateTime now = DateTime.now(UTC);
Registrar registrar = checkNotNull(
Registrar.loadByClientId(registrarId), "Registrar %s not found", registrarId);
RegistrarCredit credit = new RegistrarCredit.Builder()
.setParent(registrar)
.setType(type)
.setCreationTime(now)
.setCurrency(balance.getCurrencyUnit())
.setDescription(description)
.setTld(tld)
.build();
RegistrarCreditBalance creditBalance = new RegistrarCreditBalance.Builder()
.setParent(credit)
.setEffectiveTime(effectiveTime)
.setWrittenTime(now)
.setAmount(balance)
.build();
stageEntityChange(null, credit);
stageEntityChange(null, creditBalance);
}
}

View file

@ -0,0 +1,143 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.domain.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
import static com.google.domain.registry.tools.server.CreateOrUpdatePremiumListAction.INPUT_PARAM;
import static com.google.domain.registry.tools.server.CreateOrUpdatePremiumListAction.NAME_PARAM;
import static com.google.domain.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import org.json.simple.JSONValue;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Base class for specification of command line parameters common to creating and updating premium
* lists.
*/
abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
implements ServerSideCommand {
@Nullable
@Parameter(
names = {"-n", "--name"},
description = "The name of this premium list (defaults to filename if not specified). "
+ "This is almost always the name of the TLD this premium list will be used on.")
String name;
@Parameter(
names = {"-i", "--input"},
description = "Filename of premium list to create or update.",
validateWith = PathParameter.InputFile.class,
required = true)
Path inputFile;
protected Connection connection;
protected int inputLineCount;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
abstract String getCommandPath();
ImmutableMap<String, ? extends Object> getParameterMap() {
return ImmutableMap.of();
}
@Override
protected void init() throws Exception {
name = isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
List<String> lines = Files.readAllLines(inputFile, UTF_8);
// Try constructing the premium list locally to check up front for validation errors.
new PremiumList.Builder()
.setName(name)
.setPremiumListMapFromLines(lines)
.build();
inputLineCount = lines.size();
}
@Override
protected String prompt() throws Exception {
return String.format(
"You are about to save the premium list %s with %d items: ", name, inputLineCount);
}
@Override
public String execute() throws Exception {
ImmutableMap.Builder<String, Object> params = new ImmutableMap.Builder<>();
params.put(NAME_PARAM, name);
String inputFileContents = new String(Files.readAllBytes(inputFile), UTF_8);
String requestBody =
Joiner.on('&').withKeyValueSeparator("=").join(
ImmutableMap.of(INPUT_PARAM, URLEncoder.encode(inputFileContents, UTF_8.toString())));
ImmutableMap<String, ?> extraParams = getParameterMap();
if (extraParams != null) {
params.putAll(extraParams);
}
// Call the server and get the response data
String response = connection.send(
getCommandPath(),
params.build(),
MediaType.FORM_DATA,
requestBody.getBytes());
return extractServerResponse(response);
}
// TODO(tjb): refactor this behavior into a better general-purpose
// response validation that can be re-used across the new client/server commands.
String extractServerResponse(String response) {
Map<String, Object> responseMap = toMap(JSONValue.parse(stripJsonPrefix(response)));
// TODO(tjb): consider using jart's FormField Framework.
// See: j/c/g/d/r/ui/server/RegistrarFormFields.java
String status = (String) responseMap.get("status");
Verify.verify(!status.equals("error"), "Server error: %s", responseMap.get("error"));
return String.format("Successfully saved premium list %s\n", name);
}
@SuppressWarnings("unchecked")
static Map<String, Object> toMap(Object obj) {
Verify.verify(obj instanceof Map<?, ?>, "JSON object is not a Map: %s", obj);
return (Map<String, Object>) obj;
}
// TODO(tjb): figure out better place to put this method to make it re-usable
static String stripJsonPrefix(String json) {
Verify.verify(json.startsWith(JSON_SAFETY_PREFIX));
return json.substring(JSON_SAFETY_PREFIX.length());
}
}

View file

@ -0,0 +1,408 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import static com.google.domain.registry.util.RegistrarUtils.normalizeRegistrarName;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.domain.registry.model.billing.RegistrarBillingUtils;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.Registrar.BillingMethod;
import com.google.domain.registry.model.registrar.RegistrarAddress;
import com.google.domain.registry.tools.params.OptionalLongParameter;
import com.google.domain.registry.tools.params.OptionalPhoneNumberParameter;
import com.google.domain.registry.tools.params.OptionalStringParameter;
import com.google.domain.registry.tools.params.PathParameter;
import com.google.domain.registry.util.CidrAddressBlock;
import com.beust.jcommander.Parameter;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Shared base class for commands to create or update a {@link Registrar}. */
abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
@Parameter(
description = "Client identifier of the registrar account",
required = true)
List<String> mainParameters;
@Parameter(
names = "--registrar_type",
description = "Type of the registrar")
Registrar.Type registrarType;
@Nullable
@Parameter(
names = "--registrar_state",
description = "Initial state of the registrar")
Registrar.State registrarState;
@Parameter(
names = "--allowed_tlds",
description = "Comma-delimited list of TLDs which the registrar is allowed to use")
List<String> allowedTlds = new ArrayList<>();
@Parameter(
names = "--add_allowed_tlds",
description = "Comma-delimited list of TLDs to add to TLDs a registrar is allowed to use")
List<String> addAllowedTlds = new ArrayList<>();
@Nullable
@Parameter(
names = "--password",
description = "Password for the registrar account")
String password;
@Nullable
@Parameter(
names = "--name",
description = "Name of the registrar")
String registrarName;
@Nullable
@Parameter(
names = "--email",
description = "Email address of registrar",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
Optional<String> email;
@Nullable
@Parameter(
names = "--icann_referral_email",
description = "ICANN referral email, as specified in registrar contract")
String icannReferralEmail;
@Nullable
@Parameter(
names = "--url",
description = "URL of registrar's website",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
private Optional<String> url;
@Nullable
@Parameter(
names = "--phone",
description = "E.164 phone number, e.g. +1.2125650666",
converter = OptionalPhoneNumberParameter.class,
validateWith = OptionalPhoneNumberParameter.class)
Optional<String> phone;
@Nullable
@Parameter(
names = "--fax",
description = "E.164 fax number, e.g. +1.2125650666",
converter = OptionalPhoneNumberParameter.class,
validateWith = OptionalPhoneNumberParameter.class)
Optional<String> fax;
@Nullable
@Parameter(
names = "--cert_file",
description = "File containing client certificate (X.509 PEM)",
validateWith = PathParameter.InputFile.class)
Path clientCertificateFilename;
@Nullable
@Parameter(
names = "--cert_hash",
description = "Hash of client certificate (SHA256 base64 no padding). Do not use this unless "
+ "you want to store ONLY the hash and not the full certificate")
private String clientCertificateHash;
@Nullable
@Parameter(
names = "--failover_cert_file",
description = "File containing failover client certificate (X.509 PEM)",
validateWith = PathParameter.InputFile.class)
Path failoverClientCertificateFilename;
@Parameter(
names = "--ip_whitelist",
description = "Comma-delimited list of IP ranges")
List<String> ipWhitelist = new ArrayList<>();
@Nullable
@Parameter(
names = "--iana_id",
description = "Registrar IANA ID",
converter = OptionalLongParameter.class,
validateWith = OptionalLongParameter.class)
Optional<Long> ianaId;
@Nullable
@Parameter(
names = "--billing_id",
description = "Registrar Billing ID (i.e. Oracle #)",
converter = OptionalLongParameter.class,
validateWith = OptionalLongParameter.class)
private Optional<Long> billingId;
@Nullable
@Parameter(
names = "--billing_method",
description = "Method by which registry bills this registrar customer")
private BillingMethod billingMethod;
@Nullable
@Parameter(
names = "--street",
variableArity = true,
description = "Street lines of address. Can take up to 3 lines.")
List<String> street;
@Nullable
@Parameter(
names = "--city",
description = "City of address")
String city;
@Nullable
@Parameter(
names = "--state",
description = "State/Province of address. The value \"null\" clears this field.")
String state;
@Nullable
@Parameter(
names = "--zip",
description = "Postal code of address. The value \"null\" clears this field.")
String zip;
@Nullable
@Parameter(
names = "--cc",
description = "Country code of address")
String countryCode;
@Nullable
@Parameter(
names = "--block_premium",
description = "Whether premium name registration should be blocked on this registrar",
arity = 1)
private Boolean blockPremiumNames;
@Nullable
@Parameter(
names = "--sync_groups",
description = "Whether this registrar's groups should be updated at the next scheduled sync",
arity = 1)
private Boolean contactsRequireSyncing;
@Nullable
@Parameter(
names = "--drive_id",
description = "Id of this registrar's folder in Drive",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
Optional<String> driveFolderId;
@Nullable
@Parameter(
names = "--passcode",
description = "Telephone support passcode")
String phonePasscode;
@Nullable
@Parameter(
names = "--whois",
description = "Hostname of registrar WHOIS server. (Default: whois.nic.google)")
String whoisServer;
/** Returns the existing registrar (for update) or null (for creates). */
@Nullable
abstract Registrar getOldRegistrar(String clientIdentifier);
protected void initRegistrarCommand() throws Exception {}
@Override
protected final void init() throws Exception {
initRegistrarCommand();
DateTime now = DateTime.now(UTC);
for (String clientIdentifier : mainParameters) {
Registrar oldRegistrar = getOldRegistrar(clientIdentifier);
Registrar.Builder builder = (oldRegistrar == null)
? new Registrar.Builder().setClientIdentifier(clientIdentifier)
: oldRegistrar.asBuilder();
if (!isNullOrEmpty(password)) {
builder.setPassword(password);
}
if (!isNullOrEmpty(registrarName)) {
builder.setRegistrarName(registrarName);
}
if (email != null) {
builder.setEmailAddress(email.orNull());
}
if (url != null) {
builder.setUrl(url.orNull());
}
if (phone != null) {
builder.setPhoneNumber(phone.orNull());
}
if (fax != null) {
builder.setFaxNumber(fax.orNull());
}
if (registrarType != null) {
builder.setType(registrarType);
}
if (registrarState != null) {
builder.setState(registrarState);
}
if (driveFolderId != null) {
builder.setDriveFolderId(driveFolderId.orNull());
}
if (!allowedTlds.isEmpty()) {
checkArgument(addAllowedTlds.isEmpty(),
"Can't specify both --allowedTlds and --addAllowedTlds");
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
for (String allowedTld : allowedTlds) {
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
}
builder.setAllowedTlds(allowedTldsBuilder.build());
}
if (!addAllowedTlds.isEmpty()) {
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
if (oldRegistrar != null) {
allowedTldsBuilder.addAll(oldRegistrar.getAllowedTlds());
}
for (String allowedTld : addAllowedTlds) {
allowedTldsBuilder.add(canonicalizeDomainName(allowedTld));
}
builder.setAllowedTlds(allowedTldsBuilder.build());
}
if (!ipWhitelist.isEmpty()) {
ImmutableList.Builder<CidrAddressBlock> ipWhitelistBuilder = new ImmutableList.Builder<>();
if (!(ipWhitelist.size() == 1 && ipWhitelist.get(0).contains("null"))) {
for (String ipRange : ipWhitelist) {
ipWhitelistBuilder.add(CidrAddressBlock.create(ipRange));
}
}
builder.setIpAddressWhitelist(ipWhitelistBuilder.build());
}
if (clientCertificateFilename != null) {
String asciiCert = new String(Files.readAllBytes(clientCertificateFilename), US_ASCII);
builder.setClientCertificate(asciiCert, now);
}
if (failoverClientCertificateFilename != null) {
String asciiCert =
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
builder.setFailoverClientCertificate(asciiCert, now);
}
if (!isNullOrEmpty(clientCertificateHash)) {
checkArgument(clientCertificateFilename == null,
"Can't specify both --cert_hash and --cert_file");
if ("null".equals(clientCertificateHash)) {
clientCertificateHash = null;
}
builder.setClientCertificateHash(clientCertificateHash);
}
if (ianaId != null) {
builder.setIanaIdentifier(ianaId.orNull());
}
if (billingId != null) {
builder.setBillingIdentifier(billingId.orNull());
}
if (billingMethod != null) {
if (oldRegistrar != null && !billingMethod.equals(oldRegistrar.getBillingMethod())) {
Map<CurrencyUnit, Money> balances = RegistrarBillingUtils.loadBalance(oldRegistrar);
for (Money balance : balances.values()) {
checkState(balance.isZero(),
"Refusing to change billing method on Registrar '%s' from %s to %s"
+ " because current balance is non-zero: %s",
clientIdentifier, oldRegistrar.getBillingMethod(), billingMethod, balances);
}
}
builder.setBillingMethod(billingMethod);
}
List<Object> streetAddressFields = Arrays.asList(street, city, state, zip, countryCode);
checkArgument(Iterables.any(streetAddressFields, isNull())
== Iterables.all(streetAddressFields, isNull()),
"Must specify all fields of address");
if (street != null) {
// We always set the localized address for now. That should be safe to do since it supports
// unrestricted UTF-8.
builder.setLocalizedAddress(new RegistrarAddress.Builder()
.setStreet(ImmutableList.copyOf(street))
.setCity(city)
.setState("null".equals(state) ? null : state)
.setZip("null".equals(zip) ? null : zip)
.setCountryCode(countryCode)
.build());
}
if (blockPremiumNames != null) {
builder.setBlockPremiumNames(blockPremiumNames);
}
if (contactsRequireSyncing != null) {
builder.setContactsRequireSyncing(contactsRequireSyncing);
}
// When creating a new REAL registrar or changing the type to REAL, a passcode is required.
// Leave existing REAL registrars alone.
if (Registrar.Type.REAL.equals(registrarType)
&& (oldRegistrar == null || oldRegistrar.getPhonePasscode() == null)) {
checkArgument(phonePasscode != null, "--passcode is required for REAL registrars.");
}
if (phonePasscode != null) {
builder.setPhonePasscode(phonePasscode);
}
if (icannReferralEmail != null) {
builder.setIcannReferralEmail(icannReferralEmail);
}
if (whoisServer != null) {
builder.setWhoisServer(whoisServer);
}
// If the registrarName is being set, verify that it is either null or it normalizes uniquely.
String oldRegistrarName = (oldRegistrar == null) ? null : oldRegistrar.getRegistrarName();
if (registrarName != null && !registrarName.equals(oldRegistrarName)) {
String normalizedName = normalizeRegistrarName(registrarName);
for (Registrar registrar : Registrar.loadAll()) {
if (registrar.getRegistrarName() != null) {
checkArgument(
!normalizedName.equals(normalizeRegistrarName(registrar.getRegistrarName())),
"The registrar name %s normalizes identically to existing registrar name %s",
registrarName,
registrar.getRegistrarName());
}
}
}
stageEntityChange(oldRegistrar, builder.build());
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import java.nio.file.Path;
import javax.annotation.Nullable;
/**
* Base class for specification of command line parameters common to creating and updating reserved
* lists.
*/
public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand {
@Nullable
@Parameter(
names = {"-n", "--name"},
description = "The name of this reserved list (defaults to filename if not specified).")
String name;
@Parameter(
names = {"-i", "--input"},
description = "Filename of new reserved list.",
validateWith = PathParameter.InputFile.class,
required = true)
Path input;
@Nullable
@Parameter(
names = "--should_publish",
description =
"Whether the list is published to the concatenated list on Drive (defaults to true).",
arity = 1)
Boolean shouldPublish;
}

View file

@ -0,0 +1,453 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static com.google.domain.registry.model.RoidSuffixes.isRoidSuffixUsed;
import static com.google.domain.registry.util.CollectionUtils.findDuplicates;
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.domain.registry.model.registry.Registries;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldState;
import com.google.domain.registry.model.registry.Registry.TldType;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.google.domain.registry.tools.params.OptionalStringParameter;
import com.google.domain.registry.tools.params.TransitionListParameter.BillingCostTransitions;
import com.google.domain.registry.tools.params.TransitionListParameter.TldStateTransitions;
import com.beust.jcommander.Parameter;
import com.googlecode.objectify.Key;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/** Shared base class for commands to create or update a TLD. */
abstract class CreateOrUpdateTldCommand extends MutatingCommand {
@Parameter(
description = "Names of the TLDs",
required = true)
List<String> mainParameters;
@Parameter(
names = "--escrow",
description = "Whether to enable nightly RDE escrow deposits",
arity = 1)
private Boolean escrow;
@Parameter(
names = "--dns",
description = "Set to false to pause writing to the DNS queue",
arity = 1)
private Boolean dns;
@Nullable
@Parameter(
names = "--add_grace_period",
description = "Length of the add grace period")
Duration addGracePeriod;
@Nullable
@Parameter(
names = "--redemption_grace_period",
description = "Length of the redemption grace period")
Duration redemptionGracePeriod;
@Nullable
@Parameter(
names = "--pending_delete_length",
description = "Length of the pending delete period")
Duration pendingDeleteLength;
@Nullable
@Parameter(
names = "--automatic_transfer_length",
description = "Length of the automatic transfer period")
private Duration automaticTransferLength;
@Nullable
@Parameter(
names = "--restore_billing_cost",
description = "One-time billing cost for restoring a domain")
private Money restoreBillingCost;
@Nullable
@Parameter(
names = "--roid_suffix",
description = "The suffix to be used for ROIDs, e.g. COM for .com domains (which then "
+ "creates roids looking like 123ABC-COM)")
String roidSuffix;
@Nullable
@Parameter(
names = "--server_status_change_cost",
description = "One-time billing cost for a server status change")
private Money serverStatusChangeCost;
@Nullable
@Parameter(
names = "--tld_type",
description = "Tld type (REAL or TEST)")
private TldType tldType;
@Nullable
@Parameter(
names = "--premium_price_ack_required",
description = "Whether operations on premium domains require explicit ack of prices",
arity = 1)
private Boolean premiumPriceAckRequired;
@Nullable
@Parameter(
names = "--create_billing_cost",
description = "Per-year billing cost for creating a domain")
Money createBillingCost;
@Nullable
@Parameter(
names = "--drive_folder_id",
description = "Id of the folder in drive used to publish information for this TLD",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
Optional<String> driveFolderId;
@Nullable
@Parameter(
names = "--lordn_username",
description = "Username for LORDN uploads",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
Optional<String> lordnUsername;
@Nullable
@Parameter(
names = "--premium_list",
description = "The name of the premium list to apply to the TLD",
converter = OptionalStringParameter.class,
validateWith = OptionalStringParameter.class)
Optional<String> premiumListName;
@Parameter(
names = "--tld_state_transitions",
converter = TldStateTransitions.class,
validateWith = TldStateTransitions.class,
description = "Comma-delimited list of TLD state transitions, of the form "
+ "<time>=<tld-state>[,<time>=<tld-state>]*")
ImmutableSortedMap<DateTime, TldState> tldStateTransitions = ImmutableSortedMap.of();
@Parameter(
names = "--renew_billing_cost_transitions",
converter = BillingCostTransitions.class,
validateWith = BillingCostTransitions.class,
description = "Comma-delimited list of renew billing cost transitions, of the form "
+ "<time>=<money-amount>[,<time>=<money-amount>]* where each amount "
+ "represents the per-year billing cost for renewing a domain")
ImmutableSortedMap<DateTime, Money> renewBillingCostTransitions =
ImmutableSortedMap.of();
@Nullable
@Parameter(
names = "--reserved_lists",
description = "A comma-separated list of reserved list names to be applied to the TLD")
List<String> reservedListNames;
@Nullable
@Parameter(
names = "--allowed_registrants",
description = "A comma-separated list of allowed registrants for the TLD")
List<String> allowedRegistrants;
@Nullable
@Parameter(
names = "--allowed_nameservers",
description = "A comma-separated list of allowed nameservers for the TLD")
List<String> allowedNameservers;
@Parameter(
names = {"-o", "--override_reserved_list_rules"},
description = "Override restrictions on reserved list naming")
boolean overrideReservedListRules;
@Nullable
@Parameter(
names = "--claims_period_end",
description = "The end of the claims period")
DateTime claimsPeriodEnd;
@Nullable
Set<String> reservedListNamesToAdd;
@Nullable
Set<String> reservedListNamesToRemove;
@Nullable
Set<String> allowedRegistrantsToAdd;
@Nullable
Set<String> allowedRegistrantsToRemove;
@Nullable
Set<String> allowedNameserversToAdd;
@Nullable
Set<String> allowedNameserversToRemove;
/** Returns the existing registry (for update) or null (for creates). */
@Nullable
abstract Registry getOldRegistry(String tld);
/** Subclasses can override this to set their own properties. */
void setCommandSpecificProperties(@SuppressWarnings("unused") Registry.Builder builder) {}
/** Subclasses can override this to assert that the command can be run in this environment. */
void assertAllowedEnvironment() {}
protected abstract void initTldCommand() throws Exception;
@Override
protected final void init() throws Exception {
assertAllowedEnvironment();
initTldCommand();
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
checkArgument(duplicates.isEmpty(), "Duplicate arguments found: \"%s\"", duplicates);
Set<String> tlds = ImmutableSet.copyOf(mainParameters);
checkArgument(roidSuffix == null || tlds.size() == 1,
"Can't update roid suffixes on multiple TLDs simultaneously");
for (String tld : tlds) {
checkArgument(tld.equals(canonicalizeDomainName(tld)));
checkArgument(
!CharMatcher.javaDigit().matches(tld.charAt(0)),
"TLDs cannot begin with a number.");
Registry oldRegistry = getOldRegistry(tld);
if (roidSuffix != null) {
checkArgument(
!isRoidSuffixUsed(roidSuffix)
|| (oldRegistry != null && roidSuffix.equals(oldRegistry.getRoidSuffix())),
"The roid suffix %s is already in use",
roidSuffix);
}
Registry.Builder builder = oldRegistry == null
? new Registry.Builder().setTldStr(tld) : oldRegistry.asBuilder();
if (escrow != null) {
builder.setEscrowEnabled(escrow);
}
if (dns != null) {
builder.setDnsPaused(!dns);
}
if (!tldStateTransitions.isEmpty()) {
builder.setTldStateTransitions(tldStateTransitions);
}
if (!renewBillingCostTransitions.isEmpty()) {
// TODO(b/20764952): need invoicing support for multiple renew billing costs.
if (renewBillingCostTransitions.size() > 1) {
System.err.println(
"----------------------\n"
+ "WARNING: Do not set multiple renew cost transitions until b/20764952 is fixed.\n"
+ "----------------------\n");
}
builder.setRenewBillingCostTransitions(renewBillingCostTransitions);
}
if (addGracePeriod != null) {
builder.setAddGracePeriodLength(addGracePeriod);
}
if (redemptionGracePeriod != null) {
builder.setRedemptionGracePeriodLength(redemptionGracePeriod);
}
if (pendingDeleteLength != null) {
builder.setPendingDeleteLength(pendingDeleteLength);
}
if (automaticTransferLength != null) {
builder.setAutomaticTransferLength(automaticTransferLength);
}
if (driveFolderId != null) {
builder.setDriveFolderId(driveFolderId.orNull());
}
if (createBillingCost != null) {
builder.setCreateBillingCost(createBillingCost);
}
if (restoreBillingCost != null) {
builder.setRestoreBillingCost(restoreBillingCost);
}
if (roidSuffix != null) {
builder.setRoidSuffix(roidSuffix);
}
if (serverStatusChangeCost != null) {
builder.setServerStatusChangeBillingCost(serverStatusChangeCost);
}
if (tldType != null) {
builder.setTldType(tldType);
}
if (premiumPriceAckRequired != null) {
builder.setPremiumPriceAckRequired(premiumPriceAckRequired);
}
if (lordnUsername != null) {
builder.setLordnUsername(lordnUsername.orNull());
}
if (claimsPeriodEnd != null) {
builder.setClaimsPeriodEnd(claimsPeriodEnd);
}
if (premiumListName != null) {
if (premiumListName.isPresent()) {
Optional<PremiumList> premiumList = PremiumList.get(premiumListName.get());
checkArgument(premiumList.isPresent(),
String.format("The premium list '%s' doesn't exist", premiumListName.get()));
builder.setPremiumList(premiumList.get());
} else {
builder.setPremiumList(null);
}
}
ImmutableSet<String> newReservedListNames =
formUpdatedList(
"reserved lists",
oldRegistry == null ? ImmutableSet.<String>of() : FluentIterable
.from(oldRegistry.getReservedLists())
.transform(
new Function<Key<ReservedList>, String>() {
@Override
public String apply(Key<ReservedList> key) {
return key.getName();
}})
.toSet(),
reservedListNames,
reservedListNamesToAdd,
reservedListNamesToRemove);
checkReservedListValidityForTld(tld, newReservedListNames);
builder.setReservedListsByName(newReservedListNames);
builder.setAllowedRegistrantContactIds(
formUpdatedList(
"allowed registrants",
oldRegistry == null
? ImmutableSet.<String>of()
: oldRegistry.getAllowedRegistrantContactIds(),
allowedRegistrants,
allowedRegistrantsToAdd,
allowedRegistrantsToRemove));
builder.setAllowedFullyQualifiedHostNames(
formUpdatedList(
"allowed nameservers",
oldRegistry == null
? ImmutableSet.<String>of()
: oldRegistry.getAllowedFullyQualifiedHostNames(),
allowedNameservers,
allowedNameserversToAdd,
allowedNameserversToRemove));
// Update the Registry object.
setCommandSpecificProperties(builder);
stageEntityChange(oldRegistry, builder.build());
}
}
private ImmutableSet<String> formUpdatedList(
String description,
ImmutableSet<String> originals,
List<String> toReplace,
Set<String> toAdd,
Set<String> toRemove) {
if (toReplace != null) {
return ImmutableSet.copyOf(toReplace);
}
toAdd = nullToEmpty(toAdd);
toRemove = nullToEmpty(toRemove);
checkIsEmpty(
intersection(toAdd, toRemove),
String.format(
"Adding and removing the same %s simultaneously doesn't make sense", description));
checkIsEmpty(
intersection(originals, toAdd),
String.format("Cannot add %s that were previously present", description));
checkIsEmpty(
difference(toRemove, originals),
String.format("Cannot remove %s that were not previously present", description));
return ImmutableSet.copyOf(difference(union(originals, toAdd), toRemove));
}
private void checkIsEmpty(Set<String> set, String errorString) {
checkArgument(set.isEmpty(), String.format("%s: %s", errorString, set));
}
@Override
public String execute() throws Exception {
try {
return super.execute();
} finally {
// Manually reset the cache here so that subsequent commands (e.g. in SetupOteCommand) see
// the latest version of the data.
// TODO(b/24903801): change all those places to use uncached code paths to get Registries.
Registries.resetCache();
}
}
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames) {
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
for (String reservedListName : reservedListNames) {
if (!reservedListName.startsWith("common_") && !reservedListName.startsWith(tld + "_")) {
builder.add(reservedListName);
}
}
ImmutableList<String> invalidNames = builder.build();
if (!invalidNames.isEmpty()) {
String errMsg = String.format("The reserved list(s) %s cannot be applied to the tld %s",
Joiner.on(", ").join(invalidNames),
tld);
if (overrideReservedListRules) {
System.err.println("Error overriden: " + errMsg);
} else {
throw new IllegalArgumentException(errMsg);
}
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.tools.server.CreatePremiumListAction;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import javax.annotation.Nullable;
/** Command to create a {@link PremiumList} on Datastore. */
@Parameters(separators = " =", commandDescription = "Create a PremiumList in Datastore.")
public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
@Nullable
@Parameter(
names = {"-o", "--override"},
description = "Override restrictions on premium list naming")
boolean override;
/** Returns the path to the servlet task. */
@Override
public String getCommandPath() {
return CreatePremiumListAction.PATH;
}
@Override
ImmutableMap<String, ? extends Object> getParameterMap() {
if (override) {
return ImmutableMap.of("override", override);
} else {
return ImmutableMap.of();
}
}
}

View file

@ -0,0 +1,131 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.domain.registry.model.registrar.Registrar.State.ACTIVE;
import static com.google.domain.registry.tools.RegistryToolEnvironment.PRODUCTION;
import static com.google.domain.registry.tools.RegistryToolEnvironment.SANDBOX;
import static com.google.domain.registry.tools.RegistryToolEnvironment.UNITTEST;
import static com.google.domain.registry.util.RegistrarUtils.normalizeClientId;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
import javax.annotation.Nullable;
/** Command to create a Registrar. */
@Parameters(separators = " =", commandDescription = "Create new registrar account(s)")
final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand
implements GtechCommand, ServerSideCommand {
private static final ImmutableSet<RegistryToolEnvironment> ENVIRONMENTS_ALLOWING_GROUP_CREATION =
ImmutableSet.of(PRODUCTION, SANDBOX, UNITTEST);
// Allows test cases to be cleaner.
@VisibleForTesting
static boolean requireAddress = true;
@Parameter(
names = "--create_groups",
description = "Whether the Google Groups for this registrar should be created",
arity = 1)
boolean createGoogleGroups = true;
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
protected void initRegistrarCommand() throws Exception {
checkArgument(mainParameters.size() == 1, "Must specify exactly one client identifier.");
checkNotNull(emptyToNull(password), "--password is a required field");
checkNotNull(registrarName, "--name is a required field");
checkNotNull(icannReferralEmail, "--icann_referral_email is a required field");
if (requireAddress) {
checkNotNull(street, "Address fields are required when creating a registrar");
}
// Default new registrars to active.
registrarState = Optional.fromNullable(registrarState).or(ACTIVE);
}
@Nullable
@Override
Registrar getOldRegistrar(final String clientIdentifier) {
checkArgument(clientIdentifier.length() >= 3,
String.format("Client identifier (%s) is too short", clientIdentifier));
checkArgument(clientIdentifier.length() <= 16,
String.format("Client identifier (%s) is too long", clientIdentifier));
if (Registrar.Type.REAL.equals(registrarType)) {
checkArgument(clientIdentifier.equals(normalizeClientId(clientIdentifier)),
String.format(
"Client identifier (%s) can only contain lowercase letters, numbers, and hyphens",
clientIdentifier));
}
checkState(Registrar.loadByClientId(clientIdentifier) == null,
"Registrar %s already exists", clientIdentifier);
List<Registrar> collisions =
newArrayList(filter(Registrar.loadAll(), new Predicate<Registrar>() {
@Override
public boolean apply(Registrar registrar) {
return normalizeClientId(registrar.getClientIdentifier()).equals(clientIdentifier);
}}));
if (!collisions.isEmpty()) {
throw new IllegalArgumentException(String.format(
"The registrar client identifier %s normalizes identically to existing registrar %s",
clientIdentifier,
collisions.get(0).getClientIdentifier()));
}
return null;
}
@Override
protected String postExecute() throws Exception {
if (!createGoogleGroups) {
return "";
}
// Allow prod and sandbox because they actually have Groups, and UNITTEST for testing.
if (!ENVIRONMENTS_ALLOWING_GROUP_CREATION.contains(RegistryToolEnvironment.get())) {
return "\nSkipping registrar groups creation because only production and sandbox support it.";
}
try {
// We know it is safe to use the only main parameter here because initRegistrarCommand has
// already verified that there is only one, and getOldRegistrar has already verified that a
// registrar with this clientIdentifier doesn't already exist.
CreateRegistrarGroupsCommand.executeOnServer(connection, getOnlyElement(mainParameters));
} catch (Exception e) {
return "\nRegistrar created, but groups creation failed with error:\n" + e;
}
return "\nRegistrar groups created successfully.";
}
}

View file

@ -0,0 +1,99 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.collect.Iterables.transform;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.server.CreateGroupsAction;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Command to create groups in Google Groups for all contact types for a registrar.
*/
@Parameters(separators = " =", commandDescription = "Create groups for a registrar.")
public class CreateRegistrarGroupsCommand extends ConfirmingCommand
implements ServerSideCommand, GtechCommand {
@Parameter(
description = "Client identifier(s) of the registrar(s) to create groups for",
required = true)
private List<String> clientIds;
private List<Registrar> registrars = new ArrayList<>();
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
protected void init() throws IOException {
for (String clientId : clientIds) {
Registrar registrar = Registrar.loadByClientId(clientId);
checkArgumentNotNull(registrar, "Could not load registrar with id " + clientId);
registrars.add(registrar);
}
}
@Override
protected String prompt() {
return String.format(
"Create registrar contact groups for registrar(s) %s?",
Joiner.on(", ").join(transform(registrars, new Function<Registrar, String>() {
@Override
public String apply(Registrar registrar) {
return registrar.getRegistrarName();
}})));
}
/** Calls the server endpoint to create groups for the specified registrar client id. */
static void executeOnServer(Connection connection, String clientIdentifier) throws IOException {
connection.send(
CreateGroupsAction.PATH,
ImmutableMap.of(CreateGroupsAction.CLIENT_ID_PARAM, clientIdentifier),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
}
@Override
protected String execute() throws IOException {
for (Registrar registrar : registrars) {
connection.send(
CreateGroupsAction.PATH,
ImmutableMap.of(CreateGroupsAction.CLIENT_ID_PARAM, registrar.getClientIdentifier()),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
}
// Note: If any of the calls fail, then a 5XX response code is returned inside of send(), which
// throws an exception yielding a stack trace. If we get to this next line then we succeeded.
return "Success!";
}
}

View file

@ -0,0 +1,80 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import java.nio.file.Files;
import java.util.List;
/** Command to create a {@link ReservedList} on Datastore. */
@Parameters(separators = " =", commandDescription = "Create a ReservedList in datastore.")
final class CreateReservedListCommand extends CreateOrUpdateReservedListCommand {
@VisibleForTesting
static final String INVALID_FORMAT_ERROR_MESSAGE =
"The name must be in the format {tld|common}_list-name "
+ "and contain only letters, numbers, and hyphens, plus a single underscore delimiter";
@Parameter(
names = {"-o", "--override"},
description = "Override restrictions on reserved list naming")
boolean override;
@Override
protected void init() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name;
checkArgument(
!ReservedList.get(name).isPresent(),
"A reserved list already exists by this name");
if (!override) {
validateListName(name);
}
DateTime now = DateTime.now(UTC);
ReservedList reservedList =
new ReservedList.Builder()
.setName(name)
.setReservedListMapFromLines(Files.readAllLines(input, UTF_8))
.setShouldPublish(shouldPublish == null || shouldPublish)
.setCreationTime(now)
.setLastUpdateTime(now)
.build();
stageEntityChange(null, reservedList);
}
private static void validateListName(String name) {
List<String> nameParts = Splitter.on('_').splitToList(name);
checkArgument(nameParts.size() == 2, INVALID_FORMAT_ERROR_MESSAGE);
String tld = nameParts.get(0);
if (!tld.equals("common")) {
assertTldExists(tld);
}
checkArgument(nameParts.get(1).matches("[-a-zA-Z0-9]+"), INVALID_FORMAT_ERROR_MESSAGE);
}
}

View file

@ -0,0 +1,34 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameters;
/** Command to create a TLD in sandbox, separated out for Gtech use. */
@Parameters(separators = " =", commandDescription = "Create new sandbox TLD(s)")
final class CreateSandboxTldCommand extends CreateTldCommand implements GtechCommand {
@Override
void assertAllowedEnvironment() {
checkArgument(
RegistryEnvironment.get() == RegistryEnvironment.SANDBOX,
"This command can only be run in the sandbox environment");
}
}

View file

@ -0,0 +1,83 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.registry.Registries.getTlds;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSortedMap;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldState;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.Money;
import javax.annotation.Nullable;
/** Command to create a TLD. */
@Parameters(separators = " =", commandDescription = "Create new TLD(s)")
class CreateTldCommand extends CreateOrUpdateTldCommand {
@Nullable
@Parameter(
names = "--initial_tld_state",
description = "Initial state of the TLD (cannot be combined with a transitions list)")
TldState initialTldState;
@Nullable
@Parameter(
names = "--initial_renew_billing_cost",
description = "Initial per-year billing cost for renewing a domain "
+ "(cannot be combined with a transitions list)")
private Money initialRenewBillingCost;
@Override
protected void initTldCommand() throws Exception {
checkArgument(initialTldState == null || tldStateTransitions.isEmpty(),
"Don't pass both --initial_tld_state and --tld_state_transitions");
if (initialTldState != null) {
tldStateTransitions = ImmutableSortedMap.of(START_OF_TIME, initialTldState);
}
checkArgument(initialRenewBillingCost == null || renewBillingCostTransitions.isEmpty(),
"Don't pass both --initial_renew_billing_cost and --renew_billing_cost_transitions");
if (initialRenewBillingCost != null) {
renewBillingCostTransitions = ImmutableSortedMap.of(START_OF_TIME, initialRenewBillingCost);
}
checkArgument(mainParameters.size() == 1, "Can't create more than one TLD at a time");
checkArgument(
!Strings.isNullOrEmpty(roidSuffix),
"The roid suffix is required when creating a TLD");
}
@Override
void setCommandSpecificProperties(Registry.Builder builder) {
// Pick up the currency from the create cost. Since all costs must be in one currency, and that
// condition is enforced by the builder, it doesn't matter which cost we choose it from.
builder.setCurrency(createBillingCost != null
? createBillingCost.getCurrencyUnit()
: Registry.DEFAULT_CURRENCY);
}
@Override
Registry getOldRegistry(String tld) {
checkState(!getTlds().contains(tld), "TLD already exists");
return null;
}
}

View file

@ -0,0 +1,60 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.registrar.Registrar;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/** Command for deleting a registrar credit object and all of its child balances. */
@Parameters(separators = " =", commandDescription = "Delete a registrar credit")
final class DeleteCreditCommand extends MutatingCommand {
@Parameter(
names = "--registrar",
description = "Client ID of the registrar owning the credit to delete",
required = true)
private String registrarId;
@Parameter(
names = "--credit_id",
description = "ID of credit to delete",
required = true)
private long creditId;
@Override
protected void init() throws Exception {
Registrar registrar =
checkNotNull(Registrar.loadByClientId(registrarId), "Registrar %s not found", registrarId);
RegistrarCredit credit = ofy().load()
.type(RegistrarCredit.class)
.parent(registrar)
.id(creditId)
.now();
checkNotNull(credit, "Registrar credit for %s with ID %s not found", registrarId, creditId);
stageEntityChange(credit, null);
for (RegistrarCreditBalance balance :
ofy().load().type(RegistrarCreditBalance.class).ancestor(credit)) {
stageEntityChange(balance, null);
}
}
}

View file

@ -0,0 +1,60 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.DeleteDomainSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/** A command to delete a domain via EPP. */
@Parameters(separators = " =", commandDescription = "Delete domain")
final class DeleteDomainCommand extends MutatingEppToolCommand implements GtechCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
names = {"-n", "--domain_name"},
description = "Domain to delete.",
required = true)
private String domainName;
@Parameter(
names = {"--reason"},
description = "Reason for the change.",
required = true)
private String reason;
@Parameter(
names = {"--registrar_request"},
description = "Whether the change was requested by a registrar.",
arity = 1)
private boolean requestedByRegistrar = false;
@Override
void initMutatingEppToolCommand() {
setSoyTemplate(DeleteDomainSoyInfo.getInstance(), DeleteDomainSoyInfo.DELETEDOMAIN);
addSoyRecord(clientIdentifier, new SoyMapData(
"domainName", domainName,
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import com.google.domain.registry.tools.server.DeleteEntityAction;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/**
* Command to delete an entity (or entities) in Datastore specified by raw key ids, which can be
* found in Datastore Viewer in the AppEngine console -- it's the really long alphanumeric key that
* is labeled "Entity key" on the page for an individual entity.
*
* <p><b>WARNING:</b> This command can be dangerous if used incorrectly as it can bypass checks on
* deletion (including whether the entity is referenced by other entities) and it does not write
* commit log entries for non-registered types. It should mainly be used for deleting testing or
* malformed data that cannot be properly deleted using existing tools. Generally, if there already
* exists an entity-specific deletion command, then use that one instead.
*/
@Parameters(separators = " =", commandDescription = "Delete entities from Datastore by raw key.")
public class DeleteEntityCommand extends ConfirmingCommand implements ServerSideCommand {
@Parameter(description = "One or more raw keys of entities to delete.", required = true)
private List<String> rawKeyStrings;
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
protected String prompt() {
if (rawKeyStrings.size() == 1) {
return "You are about to delete the entity: \n" + rawKeyStrings.get(0);
} else {
return "You are about to delete the entities: \n" + rawKeyStrings;
}
}
@Override
protected String execute() throws Exception {
String rawKeysJoined = Joiner.on(",").join(rawKeyStrings);
return connection.send(
DeleteEntityAction.PATH,
ImmutableMap.of(DeleteEntityAction.PARAM_RAW_KEYS, rawKeysJoined),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
}
}

View file

@ -0,0 +1,114 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.tools.params.EppResourceTypeParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import org.joda.time.DateTime;
import java.util.List;
import java.util.Map;
/** Command to soft-delete a list of EppResources of a given type specified by ROIDs. */
@Parameters(separators = " =", commandDescription = "Soft-delete EPP resources.")
final class DeleteEppResourceCommand extends MutatingCommand {
private static final String DEFAULT_DELETION_REASON = "Deleted using registry_tool.";
@Parameter(
description = "List of EppResource ROIDs to soft-delete.",
required = true)
private List<String> roids;
@Parameter(
names = {"-t", "--type"},
description = "EPP resource type.",
required = true)
private EppResourceTypeParameter resourceType;
@Parameter(
names = {"-r", "--reason"},
description = "Deletion reason message.")
private String reason;
@Override
protected void init() throws Exception {
DateTime now = DateTime.now(UTC);
ImmutableList.Builder<Key<EppResource>> builder = new ImmutableList.Builder<>();
for (String roid : roids) {
builder.add(Key.create(resourceType.getType(), roid));
}
ImmutableList<Key<EppResource>> keys = builder.build();
Map<Key<EppResource>, EppResource> resources = ofy().load().keys(keys);
for (Key<EppResource> key : keys) {
if (resources.containsKey(key)) {
EppResource resource = resources.get(key);
if (isBeforeOrAt(resource.getDeletionTime(), now)) {
System.out.printf("Resource already deleted: %s\n", key);
} else {
stageEntityChange(resource, resource.asBuilder().setDeletionTime(now).build());
HistoryEntry deletionRecord = new HistoryEntry.Builder()
.setModificationTime(now)
.setBySuperuser(true)
.setClientId(resource.getCurrentSponsorClientId())
.setParent(key)
.setReason(MoreObjects.firstNonNull(reason, DEFAULT_DELETION_REASON))
.setType(HistoryEntry.Type.SYNTHETIC)
.build();
stageEntityChange(null, deletionRecord);
handleForeignKeyIndex(key, resource, now);
flushTransaction();
}
} else {
System.out.printf("Resource does not exist: %s\n", key);
}
}
}
private void handleForeignKeyIndex(Key<EppResource> key, EppResource resource, DateTime now) {
ForeignKeyIndex<?> fki = ofy().load().key(ForeignKeyIndex.createKey(resource)).now();
if (fki == null) {
System.out.printf("Creating non-existent ForeignKeyIndex for: %s\n", key);
stageEntityChange(null, ForeignKeyIndex.create(resource, now));
} else {
if (fki.getReference().key().equals(key)) {
if (isBeforeOrAt(fki.getDeletionTime(), now)) {
System.out.printf("ForeignKeyIndex already deleted for: %s\n", key);
} else {
// Theoretically this is the only code path that should ever be taken, as there should
// always be exactly one non-soft-deleted FKI for every non-soft-deleted EppResource. The
// other paths exist to correctly handle potential situations caused by bad data.
stageEntityChange(fki, ForeignKeyIndex.create(resource, now));
}
} else {
System.out.printf("Found ForeignKeyIndex pointing to different resource for: %s\n", key);
System.out.printf("It was: %s\n", fki);
}
}
}
}

View file

@ -0,0 +1,73 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import javax.annotation.Nullable;
/**
* Command to delete a {@link PremiumList} in Datastore. This command will fail if the premium
* list is currently in use on a tld.
*/
@Parameters(separators = " =", commandDescription = "Delete a PremiumList from Datastore.")
final class DeletePremiumListCommand extends ConfirmingCommand implements RemoteApiCommand {
@Nullable
PremiumList premiumList;
@Parameter(
names = {"-n", "--name"},
description = "The name of the premium list to delete.",
required = true)
private String name;
@Override
protected void init() throws Exception {
checkArgument(
PremiumList.exists(name),
"Cannot delete the premium list %s because it doesn't exist.",
name);
premiumList = PremiumList.get(name).get();
ImmutableSet<String> tldsUsedOn = premiumList.getReferencingTlds();
checkArgument(
tldsUsedOn.isEmpty(),
"Cannot delete premium list because it is used on these tld(s): %s",
Joiner.on(", ").join(tldsUsedOn));
}
@Override
protected String prompt() {
return "You are about to delete the premium list: \n" + premiumList;
}
@Override
protected String execute() throws Exception {
premiumList.delete();
return String.format(
"Deleted premium list %s with %d entries.\n",
premiumList.getName(),
premiumList.getPremiumListEntries().size());
}
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/**
* Command to delete a {@link ReservedList} in Datastore. This command will fail if the reserved
* list is currently in use on a tld.
*/
@Parameters(separators = " =", commandDescription = "Deletes a ReservedList in Datastore.")
final class DeleteReservedListCommand extends MutatingCommand {
@Parameter(
names = {"-n", "--name"},
description = "The name of the reserved list to delete.",
required = true)
private String name;
@Override
protected void init() throws Exception {
checkArgument(
ReservedList.get(name).isPresent(),
"Cannot delete the reserved list %s because it doesn't exist.",
name);
ReservedList existing = ReservedList.get(name).get();
ImmutableSet<String> tldsUsedOn = existing.getReferencingTlds();
checkArgument(
tldsUsedOn.isEmpty(),
"Cannot delete reserved list because it is used on these tld(s): %s",
Joiner.on(", ").join(tldsUsedOn));
stageEntityChange(existing, null);
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.domain.registry.model.domain.launch.LaunchPhase;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.DomainApplicationInfoSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/** A command to execute a domain application info EPP command. */
@Parameters(separators = " =", commandDescription = "Get domain application EPP info")
final class DomainApplicationInfoCommand extends EppToolCommand implements GtechCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
names = {"--id"},
description = "ID of the application.",
required = true)
private String id;
@Parameter(
names = {"-n", "--domain_name"},
description = "Domain name to query.",
required = true)
private String domainName;
@Parameter(
names = {"--phase"},
description = "Phase of the application to query.",
required = true)
private String phase;
@Override
void initEppToolCommand() {
LaunchPhase launchPhase =
checkArgumentNotNull(LaunchPhase.fromValue(phase.toLowerCase()), "Illegal launch phase.");
setSoyTemplate(
DomainApplicationInfoSoyInfo.getInstance(),
DomainApplicationInfoSoyInfo.DOMAINAPPLICATIONINFO);
addSoyRecord(clientIdentifier, new SoyMapData(
"domainName", domainName,
"id", id,
"phase", launchPhase.getPhase(),
"subphase", launchPhase.getSubphase()));
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.Multimap;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.DomainCheckClaimsSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.Collection;
import java.util.List;
/** A command to execute a domain check claims epp command. */
@Parameters(separators = " =", commandDescription = "Check claims on domain(s)")
final class DomainCheckClaimsCommand extends EppToolCommand implements GtechCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
description = "Domain(s) to check.",
required = true)
private List<String> mainParameters;
@Override
void initEppToolCommand() {
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(
DomainCheckClaimsSoyInfo.getInstance(), DomainCheckClaimsSoyInfo.DOMAINCHECKCLAIMS);
addSoyRecord(clientIdentifier, new SoyMapData("domainNames", values));
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.Multimap;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.DomainCheckSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.Collection;
import java.util.List;
/** A command to execute a domain check epp command. */
@Parameters(separators = " =", commandDescription = "Check domain availability")
final class DomainCheckCommand extends EppToolCommand implements GtechCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
description = "List of domains to check.",
required = true)
private List<String> mainParameters;
@Override
void initEppToolCommand() {
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(DomainCheckSoyInfo.getInstance(), DomainCheckSoyInfo.DOMAINCHECK);
addSoyRecord(clientIdentifier, new SoyMapData("domainNames", values));
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.Multimap;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.soy.DomainCheckFeeSoyInfo;
import com.google.template.soy.data.SoyMapData;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.Collection;
import java.util.List;
/** A command to execute a domain check fees epp command. */
@Parameters(separators = " =", commandDescription = "Check domain fees (for a 1-year create)")
final class DomainCheckFeeCommand extends EppToolCommand implements GtechCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@Parameter(
description = "Domain(s) to check.",
required = true)
List<String> mainParameters;
@Override
void initEppToolCommand() {
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(DomainCheckFeeSoyInfo.getInstance(), DomainCheckFeeSoyInfo.DOMAINCHECKFEE);
addSoyRecord(clientIdentifier, new SoyMapData("domainNames", values));
}
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.inject.Inject;
/** Command to encrypt an escrow deposit. */
@Parameters(separators = " =", commandDescription = "Encrypt an escrow deposit")
class EncryptEscrowDepositCommand implements Command {
@Parameter(
names = {"-t", "--tld"},
description = "Top level domain.",
required = true)
private String tld;
@Parameter(
names = {"-i", "--input"},
description = "Input XML file that was outputted by GenerateEscrowDepositCommand.",
validateWith = PathParameter.InputFile.class,
required = true)
private Path input;
@Parameter(
names = {"-o", "--outdir"},
description = "Specify output directory. Default is current directory.",
validateWith = PathParameter.OutputDirectory.class)
private Path outdir = Paths.get(".");
@Inject
EscrowDepositEncryptor encryptor;
@Override
public final void run() throws Exception {
encryptor.encrypt(canonicalizeDomainName(tld), input, outdir);
}
}

View file

@ -0,0 +1,166 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Maps.filterValues;
import static com.google.common.io.Resources.getResource;
import static com.google.domain.registry.flows.EppServletUtils.APPLICATION_EPP_XML_UTF8;
import static com.google.domain.registry.model.registry.Registries.findTldForNameOrThrow;
import static com.google.domain.registry.tools.CommandUtilities.addHeader;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static com.google.domain.registry.xml.XmlTransformer.prettyPrint;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.parseinfo.SoyFileInfo;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import com.beust.jcommander.Parameter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** A command to execute an epp command. */
abstract class EppToolCommand extends ConfirmingCommand implements ServerSideCommand {
@Parameter(
names = {"-u", "--superuser"},
description = "Run in superuser mode")
boolean superuser = false;
private SoyFileInfo soyFileInfo;
private SoyTemplateInfo soyRenderer;
private List<XmlEppParameters> commands = new ArrayList<>();
private Connection connection;
static class XmlEppParameters {
final String clientId;
final String xml;
XmlEppParameters(String clientId, String xml) {
this.clientId = clientId;
this.xml = xml;
}
@Override
public String toString() {
return prettyPrint(xml);
}
}
/**
* Helper function for grouping sets of domain names into respective TLDs. Useful for batched
* EPP calls when invoking commands (i.e. domain check) with sets of domains across multiple TLDs.
*/
protected static Multimap<String, String> validateAndGroupDomainNamesByTld(List<String> names) {
ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
for (String name : names) {
InternetDomainName tld = findTldForNameOrThrow(InternetDomainName.from(name));
builder.put(tld.toString(), name);
}
return builder.build();
}
protected void setSoyTemplate(SoyFileInfo soyFileInfo, SoyTemplateInfo soyRenderer) {
this.soyFileInfo = soyFileInfo;
this.soyRenderer = soyRenderer;
}
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
protected void addXmlCommand(String clientId, String xml) {
checkArgumentNotNull(Registrar.loadByClientId(clientId),
"Registrar with client ID %s not found", clientId);
commands.add(new XmlEppParameters(clientId, xml));
}
protected void addSoyRecord(String clientId, SoyRecord record) {
checkNotNull(soyFileInfo, "SoyFileInfo is missing, cannot add record.");
checkNotNull(soyRenderer, "SoyRenderer is missing, cannot add record.");
addXmlCommand(clientId, SoyFileSet.builder()
.add(getResource(soyFileInfo.getClass(), soyFileInfo.getFileName()))
.build()
.compileToTofu()
.newRenderer(soyRenderer)
.setData(record)
.render());
}
/** Subclasses can override to implement a dry run flag. False by default. */
protected boolean isDryRun() {
return false;
}
@Override
protected boolean checkExecutionState() throws Exception {
checkArgument(!(force && isDryRun()), "--force and --dry_run are incompatible");
return true;
}
@Override
public String prompt() throws IOException {
String prompt = addHeader("Command(s)", Joiner.on("\n").join(commands)
+ (force ? "" : addHeader("Dry Run", Joiner.on("\n").join(processCommands(true)))));
force = force || isDryRun();
return prompt.toString();
}
private List<String> processCommands(boolean dryRun) throws IOException {
ImmutableList.Builder<String> responses = new ImmutableList.Builder<>();
for (XmlEppParameters command : commands) {
Map<String, Object> params = new HashMap<>();
params.put("dryRun", dryRun);
params.put("clientIdentifier", command.clientId);
params.put("superuser", superuser);
responses.add(nullToEmpty(connection.send(
"/_dr/epptool",
filterValues(params, notNull()),
APPLICATION_EPP_XML_UTF8,
command.xml.getBytes(UTF_8))));
}
return responses.build();
}
@Override
public String execute() throws Exception {
return isDryRun() ? "" : addHeader("Response", Joiner.on("\n").join(processCommands(false)));
}
@Override
protected final void init() throws Exception {
initEppToolCommand();
}
abstract void initEppToolCommand() throws Exception;
}

View file

@ -0,0 +1,96 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.rde.RdeMode.FULL;
import com.google.common.io.ByteStreams;
import com.google.domain.registry.keyring.api.KeyModule.Key;
import com.google.domain.registry.model.rde.RdeNamingUtils;
import com.google.domain.registry.rde.RdeUtil;
import com.google.domain.registry.rde.RydePgpCompressionOutputStream;
import com.google.domain.registry.rde.RydePgpCompressionOutputStreamFactory;
import com.google.domain.registry.rde.RydePgpEncryptionOutputStream;
import com.google.domain.registry.rde.RydePgpEncryptionOutputStreamFactory;
import com.google.domain.registry.rde.RydePgpFileOutputStream;
import com.google.domain.registry.rde.RydePgpFileOutputStreamFactory;
import com.google.domain.registry.rde.RydePgpSigningOutputStream;
import com.google.domain.registry.rde.RydePgpSigningOutputStreamFactory;
import com.google.domain.registry.rde.RydeTarOutputStream;
import com.google.domain.registry.rde.RydeTarOutputStreamFactory;
import com.google.domain.registry.xml.XmlException;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.joda.time.DateTime;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.inject.Inject;
/** Utility for encrypting an RDE RyDE deposit on the Java 7 NIO file system. */
final class EscrowDepositEncryptor {
private static final int PEEK_BUFFER_SIZE = 64 * 1024;
@Inject RydePgpCompressionOutputStreamFactory pgpCompressionFactory;
@Inject RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory;
@Inject RydePgpFileOutputStreamFactory pgpFileFactory;
@Inject RydePgpSigningOutputStreamFactory pgpSigningFactory;
@Inject RydeTarOutputStreamFactory tarFactory;
@Inject @Key("rdeSigningKey") PGPKeyPair rdeSigningKey;
@Inject @Key("rdeReceiverKey") PGPPublicKey rdeReceiverKey;
@Inject EscrowDepositEncryptor() {}
/** Creates a {@code .ryde} and {@code .sig} file, provided an XML deposit file. */
void encrypt(String tld, Path xmlFile, Path outdir)
throws IOException, PGPException, XmlException {
try (InputStream xmlFileInput = Files.newInputStream(xmlFile);
BufferedInputStream xmlInput = new BufferedInputStream(xmlFileInput, PEEK_BUFFER_SIZE)) {
DateTime watermark = RdeUtil.peekWatermark(xmlInput);
String name = RdeNamingUtils.makeRydeFilename(tld, watermark, FULL, 1, 0);
Path rydePath = outdir.resolve(name + ".ryde");
Path sigPath = outdir.resolve(name + ".sig");
Path pubPath = outdir.resolve(tld + ".pub");
PGPKeyPair signingKey = rdeSigningKey;
try (OutputStream rydeOutput = Files.newOutputStream(rydePath);
RydePgpSigningOutputStream signLayer =
pgpSigningFactory.create(rydeOutput, signingKey)) {
try (RydePgpEncryptionOutputStream encryptLayer =
pgpEncryptionFactory.create(signLayer, rdeReceiverKey);
RydePgpCompressionOutputStream compressLayer =
pgpCompressionFactory.create(encryptLayer);
RydePgpFileOutputStream fileLayer =
pgpFileFactory.create(compressLayer, watermark, name + ".tar");
RydeTarOutputStream tarLayer =
tarFactory.create(fileLayer, Files.size(xmlFile), watermark, name + ".xml")) {
ByteStreams.copy(xmlInput, tarLayer);
}
Files.write(sigPath, signLayer.getSignature());
try (OutputStream pubOutput = Files.newOutputStream(pubPath);
ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) {
signingKey.getPublicKey().encode(ascOutput);
}
}
}
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.google.domain.registry.util.NonFinalForTesting;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/** A command to execute an arbitrary epp command from file or stdin. */
@Parameters(separators = " =", commandDescription = "Execute an epp command")
final class ExecuteEppCommand extends MutatingEppToolCommand {
@Parameter(description = "Epp command filename")
private List<String> mainParameters = new ArrayList<>();
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientIdentifier;
@NonFinalForTesting
private static InputStream stdin = System.in;
@Override
void initMutatingEppToolCommand() throws IOException {
if (mainParameters.isEmpty()) {
addXmlCommand(
clientIdentifier, CharStreams.toString(new InputStreamReader(stdin, UTF_8)));
} else {
for (String command : mainParameters) {
addXmlCommand(clientIdentifier, Files.toString(new File(command), UTF_8));
}
}
}
}

View file

@ -0,0 +1,182 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.domain.registry.flows.EppXmlTransformer.unmarshal;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static com.google.domain.registry.util.DomainNameUtils.ACE_PREFIX;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.flows.EppException;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.smd.EncodedSignedMark;
import com.google.domain.registry.model.smd.SignedMark;
import com.google.domain.registry.model.smd.SignedMarkRevocationList;
import com.google.domain.registry.model.tmch.ClaimsListShard;
import com.google.domain.registry.tmch.TmchXmlSignature;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.google.domain.registry.util.Clock;
import com.google.domain.registry.util.Idn;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.cmd.LoadType;
import com.googlecode.objectify.cmd.Query;
import org.joda.time.DateTime;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.CheckReturnValue;
import javax.inject.Inject;
/** Command to generate a report of all domain applications. */
@Parameters(separators = " =", commandDescription = "Generate report of all domain applications.")
final class GenerateApplicationsReportCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
names = {"-t", "--tld"},
description = "TLD which contains the applications.")
private String tld;
@Parameter(
names = "--ids",
description = "Comma-delimited list of application IDs to include. "
+ "If not provided, all active application will be included.")
private List<Long> ids = emptyList();
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Inject
Clock clock;
@Override
public void run() throws Exception {
if (tld != null) {
assertTldExists(tld);
}
DateTime now = clock.nowUtc();
List<String> result = new ArrayList<>();
if (!ids.isEmpty()) {
for (Long applicationId : ids) {
DomainApplication domainApplication = ofy().load()
.type(DomainApplication.class)
.id(applicationId)
.now();
if (domainApplication == null) {
System.err.printf("Application ID %d not found\n", applicationId);
continue;
}
result.addAll(validate(domainApplication, now).asSet());
}
} else {
LoadType<DomainApplication> loader = ofy().load().type(DomainApplication.class);
Query<DomainApplication> domainApplications =
(tld == null) ? loader : loader.filter("tld", tld);
for (DomainApplication domainApplication : domainApplications) {
result.addAll(validate(domainApplication, now).asSet());
}
}
Files.write(output, result, UTF_8);
}
/** Processes a domain application and adds report lines to {@code result}. */
Optional<String> validate(DomainApplication domainApplication, DateTime now) {
// Validate the label.
List<String> nameParts =
InternetDomainName.from(domainApplication.getFullyQualifiedDomainName()).parts();
String label = nameParts.get(0);
// Ignore deleted applications.
if (isBeforeOrAt(domainApplication.getDeletionTime(), now)) {
return Optional.absent();
}
// Defensive invalid punycode check.
if (label.startsWith(ACE_PREFIX) && label.equals(Idn.toUnicode(label))) {
return Optional.of(makeLine(domainApplication, "Invalid punycode"));
}
// Validate the SMD.
for (EncodedSignedMark encodedSignedMark : domainApplication.getEncodedSignedMarks()) {
byte[] signedMarkData;
try {
signedMarkData = encodedSignedMark.getBytes();
} catch (IllegalStateException e) {
return Optional.of(makeLine(domainApplication, "Incorrectly encoded SMD data"));
}
SignedMark signedMark;
try {
signedMark = unmarshal(signedMarkData);
} catch (EppException e) {
return Optional.of(makeLine(domainApplication, "Unparseable SMD"));
}
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(),
domainApplication.getCreationTime())) {
return Optional.of(makeLine(domainApplication, "SMD revoked"));
}
try {
TmchXmlSignature.verify(signedMarkData);
} catch (Exception e) {
return Optional.of(
makeLine(domainApplication, String.format("Invalid SMD (%s)", e.getMessage())));
}
}
// If this is a landrush application and has no claims notice, check to see if it should have
// one.
if (domainApplication.getEncodedSignedMarks().isEmpty()
&& (domainApplication.getLaunchNotice() == null
|| domainApplication.getLaunchNotice().getNoticeId() == null
|| isNullOrEmpty(domainApplication.getLaunchNotice().getNoticeId().getTcnId()))
&& ClaimsListShard.get().getClaimKey(label) != null) {
return Optional.of(makeLine(domainApplication, "Missing claims notice"));
}
return Optional.of(makeLine(domainApplication, "Valid"));
}
@CheckReturnValue
private String makeLine(DomainApplication domainApplication, String validityMessage) {
return Joiner.on(',').join(
domainApplication.getRepoId(),
domainApplication.getFullyQualifiedDomainName(),
domainApplication.getEncodedSignedMarks().isEmpty() ? "landrush" : "sunrise",
domainApplication.getApplicationStatus(),
domainApplication.getCurrentSponsorClientId(),
domainApplication.loadRegistrant().getEmailAddress(),
validityMessage);
}
}

View file

@ -0,0 +1,257 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.domain.registry.model.domain.launch.ApplicationStatus.REJECTED;
import static com.google.domain.registry.model.domain.launch.ApplicationStatus.VALIDATED;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import com.google.domain.registry.model.contact.ContactAddress;
import com.google.domain.registry.model.contact.ContactPhoneNumber;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.contact.PostalInfo;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarAddress;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/** Command to generate the auction data for a TLD. */
@Parameters(separators = " =", commandDescription = "Generate auction data")
final class GenerateAuctionDataCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
description = "TLD(s) to generate auction data for",
required = true)
private List<String> mainParameters;
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Parameter(
names = "--skip_validated_check",
description = "Skip the check that all contended applications are already validated.")
private boolean skipValidatedCheck;
/** This is the date format expected in the output file. */
final DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss");
@Override
public void run() throws Exception {
checkArgument(mainParameters.size() == 1,
"Expected a single parameter with the TLD name. Actual: %s",
Joiner.on(' ').join(mainParameters));
String tld = mainParameters.get(0);
assertTldExists(tld);
List<String> result = new ArrayList<>();
Set<String> registrars = new TreeSet<>();
for (Map.Entry<String, Collection<DomainApplication>> entry :
getDomainApplicationMap(tld).asMap().entrySet()) {
String domainName = entry.getKey();
List<DomainApplication> domainApplications = filterApplications(entry.getValue());
// Skip the domain if there are no contentions. This can happen if there is only a single
// sunrise applicant, or if there are no sunrise applicants and just a single landrush
// application.
if (domainApplications.size() < 2) {
continue;
}
Set<String> emailAddresses = new HashSet<>();
for (DomainApplication domainApplication : domainApplications) {
checkState(skipValidatedCheck || domainApplication.getApplicationStatus() == VALIDATED, ""
+ "Can't process contending applications for %s because some applications "
+ "are not yet validated.", domainName);
ContactResource registrant = checkNotNull(domainApplication.loadRegistrant());
result.add(emitApplication(domainApplication, registrant));
// Ensure the registrant's email address is unique across the contending applications.
if (!emailAddresses.add(registrant.getEmailAddress())) {
System.err.printf(
"Warning: Multiple applications found with email address %s for domain %s\n",
registrant.getEmailAddress(),
domainName);
}
// Add registrar for this application our set of registrars that we must output at the end.
registrars.add(domainApplication.getCurrentSponsorClientId());
}
}
// Output records for the registrars of any applications we emitted above.
for (String clientId : registrars) {
Registrar registrar =
checkNotNull(Registrar.loadByClientId(clientId), "Registrar %s does not exist", clientId);
result.add(emitRegistrar(registrar));
}
Files.write(output, result, UTF_8);
}
/** Return a map of all fully-qualified domain names mapped to the applications for that name. */
private static Multimap<String, DomainApplication> getDomainApplicationMap(final String tld) {
DateTime now = DateTime.now(UTC);
Multimap<String, DomainApplication> domainApplicationMap = TreeMultimap.create(
Ordering.natural(), new Comparator<DomainApplication>() {
@Override
public int compare(DomainApplication o1, DomainApplication o2) {
return o1.getForeignKey().compareTo(o2.getForeignKey());
}});
Iterable<DomainApplication> domainApplications =
ofy().load().type(DomainApplication.class).filter("tld", tld);
for (DomainApplication domainApplication : domainApplications) {
// Ignore deleted and rejected applications. They aren't under consideration.
ApplicationStatus applicationStatus = domainApplication.getApplicationStatus();
DateTime deletionTime = domainApplication.getDeletionTime();
if (applicationStatus == REJECTED || isAtOrAfter(now, deletionTime)) {
continue;
}
boolean result = domainApplicationMap.put(
domainApplication.getFullyQualifiedDomainName(), domainApplication);
checkState(result, "Domain application not added to map: %s", domainApplication);
}
return domainApplicationMap;
}
/**
* Filter applications by their priority. If there are any sunrise applications, then those will
* be returned; otherwise just the landrush applications will be returned.
*/
private static List<DomainApplication> filterApplications(
Iterable<DomainApplication> domainApplications) {
// Sort the applications into sunrise and landrush applications.
List<DomainApplication> sunriseApplications = new ArrayList<>();
List<DomainApplication> landrushApplications = new ArrayList<>();
for (DomainApplication domainApplication : domainApplications) {
if (!domainApplication.getEncodedSignedMarks().isEmpty()) {
sunriseApplications.add(domainApplication);
} else {
landrushApplications.add(domainApplication);
}
}
return !sunriseApplications.isEmpty() ? sunriseApplications : landrushApplications;
}
/** Return a record line for the given application. */
private String emitApplication(DomainApplication domainApplication, ContactResource registrant) {
Optional<PostalInfo> postalInfo =
Optional.fromNullable(registrant.getInternationalizedPostalInfo())
.or(Optional.fromNullable(registrant.getLocalizedPostalInfo()));
Optional<ContactAddress> address =
Optional.fromNullable(postalInfo.isPresent() ? postalInfo.get().getAddress() : null);
List<String> street =
address.isPresent() ? address.get().getStreet() : ImmutableList.<String>of();
Optional<ContactPhoneNumber> phoneNumber = Optional.fromNullable(registrant.getVoiceNumber());
// Each line containing an auction participant has the following format:
//
// Domain|Application ID|Application timestamp|Last update date|Registrar Name|
// Registrant Name|Registrant Company|Registrant Address 1|Registrant Address 2|
// Registrant City|Registrant Province|Registrant Postal Code|Registrant Country|
// Registrant Email|Registrant Telephone|Reserve|Application Type
return Joiner.on('|').join(ImmutableList.of(
domainApplication.getFullyQualifiedDomainName(),
domainApplication.getForeignKey(),
formatter.print(domainApplication.getCreationTime()),
domainApplication.getLastEppUpdateTime() != null
? formatter.print(domainApplication.getLastEppUpdateTime()) : "",
domainApplication.getCurrentSponsorClientId(),
nullToEmpty(postalInfo.isPresent() ? postalInfo.get().getName() : ""),
nullToEmpty(postalInfo.isPresent() ? postalInfo.get().getOrg() : ""),
Iterables.getFirst(street, ""),
Joiner.on(' ').skipNulls().join(Iterables.skip(street, 1)),
nullToEmpty(address.isPresent() ? address.get().getCity() : ""),
nullToEmpty(address.isPresent() ? address.get().getState() : ""),
nullToEmpty(address.isPresent() ? address.get().getZip() : ""),
nullToEmpty(address.isPresent() ? address.get().getCountryCode() : ""),
nullToEmpty(registrant.getEmailAddress()),
nullToEmpty(phoneNumber.isPresent() ? phoneNumber.get().toPhoneString() : ""),
"",
domainApplication.getEncodedSignedMarks().isEmpty() ? "Landrush" : "Sunrise"));
}
/** Return a record line for the given registrar. */
private static String emitRegistrar(Registrar registrar) {
// TODO(b/19016140): Determine if this set-up is required.
Optional<RegistrarContact> contact =
Optional.fromNullable(Iterables.getFirst(registrar.getContacts(), null));
Optional<RegistrarAddress> address = Optional.fromNullable(registrar.getLocalizedAddress())
.or(Optional.fromNullable(registrar.getInternationalizedAddress()));
List<String> street =
address.isPresent() ? address.get().getStreet() : ImmutableList.<String>of();
// Each line containing the registrar of an auction participant has the following format:
//
// Registrar Name|Registrar Contact Name|Registrar Full Company|Registrar Address 1|
// Registrar Address 2|Registrar City|Registrar Province|Registrar Postal Code|
// Registrar Country|Registrar Email|Registrar Telephone
return Joiner.on('|').join(ImmutableList.of(
registrar.getClientIdentifier(),
contact.isPresent() ? contact.get().getName() : "N/A",
nullToEmpty(registrar.getRegistrarName()),
Iterables.getFirst(street, ""),
Iterables.get(street, 1, ""),
address.isPresent() ? nullToEmpty(address.get().getCity()) : "",
address.isPresent() ? nullToEmpty(address.get().getState()) : "",
address.isPresent() ? nullToEmpty(address.get().getZip()) : "",
address.isPresent() ? nullToEmpty(address.get().getCountryCode()) : "",
nullToEmpty(registrar.getEmailAddress()),
nullToEmpty(registrar.getPhoneNumber())));
}
}

View file

@ -0,0 +1,158 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.google.domain.registry.util.Clock;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import javax.inject.Inject;
/** Command to generate a report of all DNS data. */
@Parameters(separators = " =", commandDescription = "Generate report of all DNS data in a TLD.")
final class GenerateDnsReportCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
names = {"-t", "--tld"},
description = "Target TLD.",
required = true)
private String tld;
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Inject
Clock clock;
@Override
public void run() throws Exception {
assertTldExists(tld);
Files.write(output, new Generator().generate().getBytes(US_ASCII));
}
private class Generator {
private final DateTime now = clock.nowUtc();
private final StringBuilder result = new StringBuilder();
private boolean first = true;
String generate() {
result.append("[\n");
Iterable<DomainResource> domains = ofy().load().type(DomainResource.class).filter("tld", tld);
for (DomainResource domain : domains) {
// Skip deleted domains and domains that don't get published to DNS.
if (isBeforeOrAt(domain.getDeletionTime(), now) || !domain.shouldPublishToDns()) {
continue;
}
write(domain);
}
Iterable<HostResource> nameservers = ofy().load().type(HostResource.class);
for (HostResource nameserver : nameservers) {
// Skip deleted hosts and external hosts.
if (isBeforeOrAt(nameserver.getDeletionTime(), now)
|| nameserver.getInetAddresses().isEmpty()) {
continue;
}
write(nameserver);
}
return result.append("\n]\n").toString();
}
private void write(DomainResource domain) {
ImmutableList<String> nameservers = FluentIterable.from(domain.loadNameservers())
.transform(new Function<HostResource, String>() {
@Override
public String apply(HostResource host) {
return host.getForeignKey();
}})
.toSortedList(Ordering.natural());
ImmutableList<Map<String, ?>> dsData = FluentIterable.from(domain.getDsData())
.transform(new Function<DelegationSignerData, Map<String, ?>>() {
@Override
public Map<String, ?> apply(DelegationSignerData dsData) {
return ImmutableMap.of(
"keyTag", dsData.getKeyTag(),
"algorithm", dsData.getAlgorithm(),
"digestType", dsData.getDigestType(),
"digest", base16().encode(dsData.getDigest()));
}})
.toList();
ImmutableMap.Builder<String, Object> mapBuilder = new ImmutableMap.Builder<>();
mapBuilder.put("domain", domain.getFullyQualifiedDomainName());
if (!nameservers.isEmpty()) {
mapBuilder.put("nameservers", nameservers);
}
if (!dsData.isEmpty()) {
mapBuilder.put("dsData", dsData);
}
writeJson(mapBuilder.build());
}
private void write(HostResource nameserver) {
ImmutableList<String> ipAddresses = FluentIterable.from(nameserver.getInetAddresses())
.transform(new Function<InetAddress, String>() {
@Override
public String apply(InetAddress inetAddress) {
return inetAddress.getHostAddress();
}})
.toSortedList(Ordering.natural());
ImmutableMap<String, ?> map = ImmutableMap.of(
"host", nameserver.getFullyQualifiedHostName(),
"ips", ipAddresses);
writeJson(map);
}
private void writeJson(Map<String, ?> map) {
if (first) {
first = false;
} else {
result.append(",\n");
}
result.append(JSONValue.toJSONString(map));
}
}
}

View file

@ -0,0 +1,243 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.domain.registry.config.ConfigModule.Config;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.EppResourceUtils;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.index.EppResourceIndex;
import com.google.domain.registry.model.index.EppResourceIndexBucket;
import com.google.domain.registry.model.rde.RdeMode;
import com.google.domain.registry.model.rde.RdeNamingUtils;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.rde.DepositFragment;
import com.google.domain.registry.rde.RdeCounter;
import com.google.domain.registry.rde.RdeMarshaller;
import com.google.domain.registry.rde.RdeResourceType;
import com.google.domain.registry.rde.RdeUtil;
import com.google.domain.registry.tldconfig.idn.IdnTableEnum;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.DateTimeParameter;
import com.google.domain.registry.tools.params.PathParameter;
import com.google.domain.registry.xjc.rdeheader.XjcRdeHeader;
import com.google.domain.registry.xjc.rdeheader.XjcRdeHeaderElement;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import org.joda.time.DateTime;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import javax.inject.Inject;
/** Command to generate an XML RDE escrow deposit (with relevant files) in current directory. */
@Parameters(separators = " =", commandDescription = "Generate an XML escrow deposit.")
final class GenerateEscrowDepositCommand implements RemoteApiCommand {
@Parameter(
names = {"-t", "--tld"},
description = "Top level domain for which deposit should be generated.",
required = true)
private String tld;
@Parameter(
names = {"-w", "--watermark"},
description = "Point-in-time timestamp for snapshotting the datastore.",
validateWith = DateTimeParameter.class)
private DateTime watermark = DateTime.now(UTC);
@Parameter(
names = {"-m", "--mode"},
description = "RDE/BRDA mode of operation.")
private RdeMode mode = RdeMode.FULL;
@Parameter(
names = {"-r", "--revision"},
description = "Revision number. Use >0 for resends.")
private int revision = 0;
@Parameter(
names = {"-o", "--outdir"},
description = "Specify output directory. Default is current directory.",
validateWith = PathParameter.OutputDirectory.class)
private Path outdir = Paths.get(".");
@Inject
EscrowDepositEncryptor encryptor;
@Inject
RdeCounter counter;
@Inject
@Config("eppResourceIndexBucketCount")
int eppResourceIndexBucketCount;
@Override
public void run() throws Exception {
RdeMarshaller marshaller = new RdeMarshaller();
assertTldExists(tld);
String suffix = String.format("-%s-%s.tmp.xml", tld, watermark);
Path xmlPath = outdir.resolve("deposit" + suffix);
Path reportPath = outdir.resolve("report" + suffix);
try {
String id = RdeUtil.timestampToId(watermark);
XjcRdeHeader header;
try (OutputStream xmlOutputBytes = Files.newOutputStream(xmlPath);
Writer xmlOutput = new OutputStreamWriter(xmlOutputBytes, UTF_8)) {
xmlOutput.write(
marshaller.makeHeader(id, watermark, RdeResourceType.getUris(mode), revision));
for (ImmutableObject resource
: Iterables.concat(Registrar.loadAll(), load(scan()))) {
DepositFragment frag;
if (resource instanceof Registrar) {
frag = marshaller.marshalRegistrar((Registrar) resource);
} else if (resource instanceof ContactResource) {
frag = marshaller.marshalContact((ContactResource) resource);
} else if (resource instanceof DomainResource) {
DomainResource domain = (DomainResource) resource;
if (!domain.getTld().equals(tld)) {
continue;
}
frag = marshaller.marshalDomain(domain, mode);
} else if (resource instanceof HostResource) {
frag = marshaller.marshalHost((HostResource) resource);
} else {
continue; // Surprise polymorphic entities, e.g. DomainApplication.
}
if (!frag.xml().isEmpty()) {
xmlOutput.write(frag.xml());
counter.increment(frag.type());
}
if (!frag.error().isEmpty()) {
System.err.print(frag.error());
}
}
for (IdnTableEnum idn : IdnTableEnum.values()) {
xmlOutput.write(marshaller.marshalIdn(idn.getTable()));
counter.increment(RdeResourceType.IDN);
}
header = counter.makeHeader(tld, mode);
xmlOutput.write(marshaller.marshalStrictlyOrDie(new XjcRdeHeaderElement(header)));
xmlOutput.write(marshaller.makeFooter());
}
try (OutputStream reportOutputBytes = Files.newOutputStream(reportPath)) {
counter.makeReport(id, watermark, header, revision).marshal(reportOutputBytes, UTF_8);
}
String name = RdeNamingUtils.makeRydeFilename(tld, watermark, mode, 1, revision);
encryptor.encrypt(tld, xmlPath, outdir);
Files.move(xmlPath, outdir.resolve(name + ".xml"), REPLACE_EXISTING);
Files.move(reportPath, outdir.resolve(name + "-report.xml"), REPLACE_EXISTING);
} finally {
Files.deleteIfExists(xmlPath);
Files.deleteIfExists(reportPath);
}
}
private Iterable<EppResource> scan() {
return Iterables.concat(
Iterables.transform(
getEppResourceIndexBuckets(),
new Function<Key<EppResourceIndexBucket>, Iterable<EppResource>>() {
@Override
public Iterable<EppResource> apply(Key<EppResourceIndexBucket> bucket) {
System.err.printf("Scanning EppResourceIndexBucket %d of %d...\n",
bucket.getId(), eppResourceIndexBucketCount);
return scanBucket(bucket);
}}));
}
private Iterable<EppResource> scanBucket(final Key<EppResourceIndexBucket> bucket) {
return ofy().load()
.keys(FluentIterable
.from(mode == RdeMode.FULL
? Arrays.asList(
Key.getKind(ContactResource.class),
Key.getKind(DomainResource.class),
Key.getKind(HostResource.class))
: Arrays.asList(
Key.getKind(DomainResource.class)))
.transformAndConcat(new Function<String, Iterable<EppResourceIndex>>() {
@Override
public Iterable<EppResourceIndex> apply(String kind) {
return ofy().load()
.type(EppResourceIndex.class)
.ancestor(bucket)
.filter("kind", kind)
.iterable();
}})
.transform(new Function<EppResourceIndex, Key<EppResource>>() {
@Override
@SuppressWarnings("unchecked")
public Key<EppResource> apply(EppResourceIndex index) {
return (Key<EppResource>) index.getReference().getKey();
}}))
.values();
}
private <T extends EppResource> Iterable<T> load(final Iterable<T> resources) {
return FluentIterable
.from(Iterables.partition(
Iterables.transform(resources,
new Function<T, Result<T>>() {
@Override
public Result<T> apply(T resource) {
return EppResourceUtils.loadAtPointInTime(resource, watermark);
}}),
1000))
.transformAndConcat(new Function<Iterable<Result<T>>, Iterable<T>>() {
@Override
public Iterable<T> apply(Iterable<Result<T>> results) {
return Iterables.transform(results,
new Function<Result<T>, T>() {
@Override
public T apply(Result<T> result) {
return result.now();
}});
}})
.filter(Predicates.notNull());
}
private ImmutableList<Key<EppResourceIndexBucket>> getEppResourceIndexBuckets() {
ImmutableList.Builder<Key<EppResourceIndexBucket>> builder = new ImmutableList.Builder<>();
for (int i = 1; i <= eppResourceIndexBucketCount; i++) {
builder.add(Key.create(EppResourceIndexBucket.class, i));
}
return builder.build();
}
}

View file

@ -0,0 +1,91 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.tmch.LordnTask;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
/** Command to generate a LORDN CSV file for an entire TLD. */
@Parameters(separators = " =", commandDescription = "Generate LORDN CSV file")
final class GenerateLordnCommand implements RemoteApiCommand {
@Parameter(
names = {"-t", "--tld"},
description = "TLD to generate LORDN for",
required = true)
private String tld;
@Parameter(
names = {"-c", "--claims_file"},
description = "Claims CSV output file.",
validateWith = PathParameter.OutputFile.class,
required = true)
private Path claimsOutputPath;
@Parameter(
names = {"-s", "--sunrise_file"},
description = "Sunrise CSV output file.",
validateWith = PathParameter.OutputFile.class,
required = true)
private Path sunriseOutputPath;
@Override
public void run() throws IOException {
DateTime now = DateTime.now(UTC);
ImmutableList.Builder<String> claimsCsv = new ImmutableList.Builder<>();
ImmutableList.Builder<String> sunriseCsv = new ImmutableList.Builder<>();
for (DomainResource domain : ofy().load().type(DomainResource.class).filter("tld", tld)) {
String status = " ";
if (domain.getLaunchNotice() == null && domain.getSmdId() != null) {
sunriseCsv.add(LordnTask.getCsvLineForSunriseDomain(domain, domain.getCreationTime()));
status = "S";
} else if (domain.getLaunchNotice() != null || domain.getSmdId() != null) {
claimsCsv.add(LordnTask.getCsvLineForClaimsDomain(domain, domain.getCreationTime()));
status = "C";
}
System.out.printf("%s[%s] ", domain.getFullyQualifiedDomainName(), status);
}
ImmutableList<String> claimsRows = claimsCsv.build();
ImmutableList<String> claimsAll = new ImmutableList.Builder<String>()
.add(String.format("1,%s,%d", now, claimsRows.size()))
.add(LordnTask.COLUMNS_CLAIMS)
.addAll(claimsRows)
.build();
ImmutableList<String> sunriseRows = sunriseCsv.build();
ImmutableList<String> sunriseAll = new ImmutableList.Builder<String>()
.add(String.format("1,%s,%d", now.plusMillis(1), sunriseRows.size()))
.add(LordnTask.COLUMNS_SUNRISE)
.addAll(sunriseRows)
.build();
Files.write(claimsOutputPath, claimsAll, UTF_8);
Files.write(sunriseOutputPath, sunriseAll, UTF_8);
}
}

View file

@ -0,0 +1,77 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static org.joda.time.DateTimeZone.UTC;
import static org.joda.time.Duration.standardMinutes;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.tools.params.DateTimeParameter;
import com.google.domain.registry.tools.server.GenerateZoneFilesAction;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/** Command to generate zone files. */
@Parameters(separators = " =", commandDescription = "Generate zone files")
final class GenerateZoneFilesCommand implements ServerSideCommand {
@Parameter(
description = "A comma-separated list of TLD to generate zone files for",
required = true)
private List<String> mainParameters;
// Default to latest midnight that's at least 2 minutes ago.
@Parameter(
names = "--export_time",
description = "The (midnight UTC) time to generate the file for (defaults to last midnight).",
validateWith = DateTimeParameter.class)
private DateTime exportTime = DateTime.now(UTC).minus(standardMinutes(2)).withTimeAtStartOfDay();
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
public void run() throws IOException {
for (String tld : mainParameters) {
assertTldExists(tld);
}
ImmutableMap<String, Object> params = ImmutableMap.of(
"tlds", mainParameters,
"exportTime", exportTime.toString());
Map<String, Object> response = connection.sendJson(GenerateZoneFilesAction.PATH, params);
System.out.printf(
"Job started at %s%s\n",
connection.getServerUrl(),
response.get("jobPath"));
System.out.println("Output files:");
@SuppressWarnings("unchecked")
List<String> filenames = (List<String>) response.get("filenames");
for (String filename : filenames) {
System.out.println(filename);
}
}
}

View file

@ -0,0 +1,41 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show a domain application. */
@Parameters(separators = " =", commandDescription = "Show domain application record(s)")
final class GetApplicationCommand extends GetEppResourceCommand<DomainApplication>
implements GtechCommand {
@Parameter(
description = "Domain application id(s)",
required = true)
private List<String> mainParameters;
@Override
public void processParameters() {
for (String applicationId : mainParameters) {
printResource(applicationId);
}
}
}

View file

@ -0,0 +1,73 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.model.registry.Registries.findTldForNameOrThrow;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to generate a list of all applications for a given domain name(s). */
@Parameters(separators = " =",
commandDescription = "Generate list of application IDs and sponsors for given domain name(s)")
final class GetApplicationIdsCommand extends GetEppResourceCommand<DomainApplication>
implements RemoteApiCommand, GtechCommand {
@Parameter(
description = "Fully qualified domain name(s)",
required = true)
private List<String> mainParameters;
@Override
void processParameters() {
// Note that this function explicitly performs its own resource load and print, ignoring
// GetEppResourceCommand.printResource(), because we do NOT want to display the entire
// application to gTech users -- just the ID# and sponsor.
for (String domainName : mainParameters) {
InternetDomainName tld = findTldForNameOrThrow(InternetDomainName.from(domainName));
assertTldExists(tld.toString());
System.out.printf("%s:%n", domainName);
// Sample output:
// example.tld:
// 1 (NewRegistrar)
// 2 (OtherRegistrar)
// example2.tld:
// No applications exist for 'example2.tld'.
ImmutableList<DomainApplication> applications = ImmutableList.copyOf(
loadActiveApplicationsByDomainName(domainName, readTimestamp));
if (applications.isEmpty()) {
System.out.printf(" No applications exist for \'%s\'.%n", domainName);
} else {
for (DomainApplication application : applications) {
System.out.printf(
" %s (%s)%n",
application.getForeignKey(),
application.getCurrentSponsorClientId());
}
}
}
}
}

View file

@ -0,0 +1,96 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.domain.launch.ApplicationStatus.REJECTED;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.google.domain.registry.util.Idn;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.internal.Sets;
import com.googlecode.objectify.Work;
import org.joda.time.DateTime;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/** Command to generate a list of all slds in a tld that have open applications. */
@Parameters(separators = " =", commandDescription = "Generate applied-for domains list")
final class GetAppliedLabelsCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
names = {"-t", "--tld"},
description = "TLD to generate list for.",
required = true)
private String tld;
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Override
public void run() throws Exception {
List<String> lines = new ArrayList<>();
for (String label : getDomainApplicationMap(assertTldExists(tld))) {
label = label.substring(0, label.lastIndexOf('.'));
try {
lines.add(Idn.toUnicode(label.toLowerCase()));
} catch (IllegalArgumentException e) {
// An invalid punycode label that we need to reject later.
lines.add(label + " (invalid)");
}
}
Files.write(output, lines, UTF_8);
}
/** Return a set of all fully-qualified domain names with open applications. */
private static Set<String> getDomainApplicationMap(final String tld) {
return ofy().transact(new Work<Set<String>>() {
@Override
public Set<String> run() {
Set<String> labels = Sets.newHashSet();
List<DomainApplication> domainApplications;
domainApplications = ofy().load().type(DomainApplication.class).filter("tld", tld).list();
for (DomainApplication domainApplication : domainApplications) {
// Ignore deleted and rejected applications. They aren't under consideration.
ApplicationStatus applicationStatus = domainApplication.getApplicationStatus();
DateTime deletionTime = domainApplication.getDeletionTime();
if (applicationStatus == REJECTED
|| isAtOrAfter(ofy().getTransactionTime(), deletionTime)) {
continue;
}
labels.add(domainApplication.getFullyQualifiedDomainName());
}
return labels;
}});
}
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.common.io.Files;
import com.google.domain.registry.model.tmch.ClaimsListShard;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* A command to download the current claims list.
*
* <p>This is not the original file we fetched from TMCH. It is just a representation of what we
* are currently storing in datastore.
*/
@Parameters(separators = " =", commandDescription = "Download the current claims list")
final class GetClaimsListCommand implements RemoteApiCommand {
@Parameter(
names = {"-o", "--output"},
description = "Output file.",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Override
public void run() throws Exception {
ClaimsListShard cl = checkNotNull(ClaimsListShard.get(), "Couldn't load ClaimsList");
String csv = Joiner.on('\n').withKeyValueSeparator(",").join(cl.getLabelsToKeys()) + "\n";
Files.write(csv, output.toFile(), UTF_8);
}
}

View file

@ -0,0 +1,41 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show one or more contacts. */
@Parameters(separators = " =", commandDescription = "Show contact record(s)")
final class GetContactCommand extends GetEppResourceCommand<ContactResource>
implements GtechCommand {
@Parameter(
description = "Contact id(s)",
required = true)
private List<String> mainParameters;
@Override
public void processParameters() {
for (String contactNameString : mainParameters) {
printResource(contactNameString);
}
}
}

View file

@ -0,0 +1,41 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show a domain resource. */
@Parameters(separators = " =", commandDescription = "Show domain record(s)")
final class GetDomainCommand extends GetEppResourceCommand<DomainResource>
implements GtechCommand {
@Parameter(
description = "Fully qualified domain name(s)",
required = true)
private List<String> mainParameters;
@Override
public void processParameters() {
for (String domainName : mainParameters) {
printResource(domainName);
}
}
}

View file

@ -0,0 +1,78 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
import static org.joda.time.DateTimeZone.UTC;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import org.joda.time.DateTime;
/**
* Abstract command to show a resource.
*
* @param <R> {@link EppResource} subclass.
*/
@Parameters(separators = " =")
abstract class GetEppResourceCommand<R extends EppResource>
implements RemoteApiCommand {
private final DateTime now = DateTime.now(UTC);
private Class<R> clazz = new TypeInstantiator<R>(getClass()){}.getExactType();
@Parameter(
names = "--read_timestamp",
description = "Timestamp to use when reading. May not be in the past.")
protected DateTime readTimestamp = now;
@Parameter(
names = "--expand",
description = "Fully expand the requested resource. NOTE: Output may be lengthy.")
boolean expand;
/** Resolve any parameters into ids for loadResource. */
abstract void processParameters();
/**
* Load a resource by ID and output. Append the websafe key to the output for use in e.g.
* manual mapreduce calls.
*/
void printResource(String uniqueId) {
R resource = loadByUniqueId(clazz, uniqueId, readTimestamp);
System.out.println(resource != null
? String.format("%s\n\nWebsafe key: %s",
expand ? resource.toHydratedString() : resource,
Key.create(resource).getString())
: String.format(
"%s '%s' does not exist or is deleted\n",
clazz.getSimpleName().replace("Resource", ""),
uniqueId));
}
@Override
public void run() {
checkArgument(!readTimestamp.isBefore(now), "--read_timestamp may not be in the past");
processParameters();
}
}

View file

@ -0,0 +1,63 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.xml.XmlTransformer;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
/** Command to show history entries. */
@Parameters(separators = " =",
commandDescription = "Show history entries that occurred in a given time range")
final class GetHistoryEntriesCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
names = {"-a", "--after"},
description = "Only show history entries that occurred at or after this time")
private DateTime after = START_OF_TIME;
@Parameter(
names = {"-b", "--before"},
description = "Only show history entries that occurred at or before this time")
private DateTime before = END_OF_TIME;
@Override
public void run() {
for (HistoryEntry entry : ofy().load().type(HistoryEntry.class)
.order("modificationTime")
.filter("modificationTime >=", after)
.filter("modificationTime <=", before)) {
System.out.printf(
"Client: %s\nTime: %s\nClient TRID: %s\nServer TRID: %s\n%s\n",
entry.getClientId(),
entry.getModificationTime(),
entry.getTrid().getClientTransactionId(),
entry.getTrid().getServerTransactionId(),
entry.getXmlBytes() == null
? String.format("[no XML stored for %s]\n", entry.getType())
: XmlTransformer.prettyPrint(entry.getXmlBytes()));
}
}
}

View file

@ -0,0 +1,41 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show one or more host resources. */
@Parameters(separators = " =", commandDescription = "Show host record(s)")
final class GetHostCommand extends GetEppResourceCommand<HostResource>
implements GtechCommand {
@Parameter(
description = "Fully qualified host name(s)",
required = true)
private List<String> mainParameters;
@Override
public void processParameters() {
for (String hostName : mainParameters) {
printResource(hostName);
}
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkState;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show a registrar record. */
@Parameters(separators = " =", commandDescription = "Show registrar record(s)")
final class GetRegistrarCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
description = "Client identifier of the registrar account(s)",
required = true)
private List<String> mainParameters;
@Override
public void run() {
for (String clientIdentifier : mainParameters) {
Registrar registrar = Registrar.loadByClientId(clientIdentifier);
checkState(registrar != null, "Registrar does not exist");
System.out.println(registrar);
}
}
}

View file

@ -0,0 +1,56 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import java.util.List;
/**
* Command to get info on a datastore resource by websafe key.
*/
@Parameters(separators = " =")
final class GetResourceByKeyCommand implements RemoteApiCommand {
@Parameter(
description = "Websafe key string(s)",
required = true)
private List<String> mainParameters;
@Parameter(
names = "--expand",
description = "Fully expand the requested resource. NOTE: Output may be lengthy.")
boolean expand;
@Override
public void run() {
for (String keyString : mainParameters) {
System.out.println("\n");
Key<EppResource> resourceKey = checkNotNull(
Key.<EppResource>create(keyString), "Could not parse key string: " + keyString);
EppResource resource = checkNotNull(
ofy().load().key(resourceKey).now(), "Could not load resource for key: " + resourceKey);
System.out.println(expand ? resource.toHydratedString() : resource.toString());
}
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.model.SchemaVersion;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameters;
/** Generates the schema file used for model versioning. */
@Parameters(commandDescription = "Generate a model schema file")
final class GetSchemaCommand implements GtechCommand {
@Override
public void run() throws Exception {
System.out.println(SchemaVersion.getSchema());
}
}

View file

@ -0,0 +1,167 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.collect.Ordering.arbitrary;
import static com.google.domain.registry.model.EntityClasses.ALL_CLASSES;
import static java.lang.ClassLoader.getSystemClassLoader;
import static java.lang.reflect.Modifier.isAbstract;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import com.google.domain.registry.model.BackupGroupRoot;
import com.google.domain.registry.model.annotations.NotBackedUp;
import com.google.domain.registry.model.annotations.VirtualEntity;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.EntitySubclass;
import com.googlecode.objectify.annotation.Parent;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/** Visualizes the schema parentage tree. */
@Parameters(commandDescription = "Generate a model schema file")
final class GetSchemaTreeCommand implements GtechCommand {
/** Mapping from parent classes in the Datastore sense to child classes. */
private final Multimap<Class<?>, Class<?>> hierarchy =
TreeMultimap.create(arbitrary(), new PrintableNameOrdering());
/** Mapping from superclasses used in parentage to concrete subclasses. */
private Multimap<Class<?>, Class<?>> superclassToSubclasses;
@Override
public void run() throws Exception {
// Get the @Parent type for each class.
Map<Class<?>, Class<?>> entityToParentType = new HashMap<>();
for (Class<?> clazz : ALL_CLASSES) {
entityToParentType.put(clazz, getParentType(clazz));
}
// Find super types like EppResource that are used as parents in place of actual entity types.
Set<Class<?>> superclasses = new HashSet<>();
for (Class<?> clazz : ALL_CLASSES) {
Class<?> parentType = entityToParentType.get(clazz);
if (!ALL_CLASSES.contains(parentType) && !Object.class.equals(parentType)) {
superclasses.add(parentType);
}
}
// Find the subclasses for each superclass we just found, and map them to their superclasses.
Map<Class<?>, Class<?>> subclassToSuperclass = new HashMap<>();
for (Class<?> clazz : ALL_CLASSES) {
for (Class<?> superclass : superclasses) {
if (superclass.isAssignableFrom(clazz)) {
subclassToSuperclass.put(clazz, superclass);
break;
}
}
}
// Map @EntitySubclass classes to their superclasses.
for (Class<?> clazz : ALL_CLASSES) {
if (clazz.isAnnotationPresent(EntitySubclass.class)) {
Class<?> entityClass = clazz;
while (!entityClass.isAnnotationPresent(Entity.class)) {
entityClass = entityClass.getSuperclass();
}
if (subclassToSuperclass.containsKey(clazz)) {
subclassToSuperclass.put(entityClass, subclassToSuperclass.get(clazz));
}
subclassToSuperclass.put(clazz, entityClass);
}
}
// Build the parentage hierarchy, replacing subclasses with superclasses wherever possible.
for (Class<?> clazz : ALL_CLASSES) {
Class<?> superclass = clazz;
while (subclassToSuperclass.containsKey(superclass)) {
superclass = subclassToSuperclass.get(superclass);
}
hierarchy.put(entityToParentType.get(clazz), superclass == null ? clazz : superclass);
}
// Build up the superclass to subclass mapping.
superclassToSubclasses = Multimaps.invertFrom(
Multimaps.forMap(subclassToSuperclass),
TreeMultimap.<Class<?>, Class<?>>create(arbitrary(), new PrintableNameOrdering()));
printTree(Object.class, 0);
}
private Class<?> getParentType(Class<?> clazz) {
for (; clazz != null; clazz = clazz.getSuperclass()) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Parent.class)) {
try {
return getSystemClassLoader().loadClass(
((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]
.toString()
.replace("? extends ", "")
.replace("class ", ""));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
return Object.class;
}
private void printTree(Class<?> parent, int indent) {
for (Class<?> clazz : hierarchy.get(parent)) {
System.out.println(new StringBuilder(Strings.repeat(" ", indent))
.append(indent == 0 ? "" : "")
.append(getPrintableName(clazz))
.append(isAbstract(clazz.getModifiers()) ? " (abstract)" : "")
.append(clazz.isAnnotationPresent(VirtualEntity.class) ? " (virtual)" : "")
.append(clazz.isAnnotationPresent(NotBackedUp.class) ? " (not backed up)" : "")
.append(BackupGroupRoot.class.isAssignableFrom(clazz) ? " (bgr)" : ""));
printSubclasses(clazz, indent + 2);
printTree(clazz, indent + 2);
if (indent == 0) {
System.out.println(); // Separate the entity groups with a line.
}
}
}
private void printSubclasses(Class<?> parent, int indent) {
for (Class<?> clazz : superclassToSubclasses.get(parent)) {
System.out.println(new StringBuilder(Strings.repeat(" ", indent))
.append("- ")
.append(getPrintableName(clazz))
.append(clazz.isAnnotationPresent(EntitySubclass.class) ? " (subclass)" : ""));
printSubclasses(clazz, indent + 2);
}
}
static String getPrintableName(Class<?> clazz) {
return clazz.isMemberClass()
? getPrintableName(clazz.getDeclaringClass()) + "." + clazz.getSimpleName()
: clazz.getSimpleName();
}
static class PrintableNameOrdering extends Ordering<Class<?>> implements Serializable {
@Override
public int compare(Class<?> left, Class<?> right) {
return getPrintableName(left).compareTo(getPrintableName(right));
}
}
}

View file

@ -0,0 +1,43 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to show a TLD record. */
@Parameters(separators = " =", commandDescription = "Show TLD record(s)")
final class GetTldCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
description = "TLD(s) to show",
required = true)
private List<String> mainParameters;
@Override
public void run() {
for (String tld : mainParameters) {
System.out.println(Registry.get(assertTldExists(tld)));
}
}
}

View file

@ -0,0 +1,124 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.io.ByteStreams;
import com.google.domain.registry.keyring.api.KeyModule.Key;
import com.google.domain.registry.rde.Ghostryde;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.joda.time.DateTime;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import javax.inject.Inject;
/** Command to encrypt/decrypt {@code .ghostryde} files. */
@Parameters(separators = " =", commandDescription = "Encrypt/decrypt a ghostryde file.")
final class GhostrydeCommand implements Command {
@Parameter(
names = {"-e", "--encrypt"},
description = "Encrypt mode.")
private boolean encrypt;
@Parameter(
names = {"-d", "--decrypt"},
description = "Decrypt mode.")
private boolean decrypt;
@Parameter(
names = {"-i", "--input"},
description = "Input file.",
validateWith = PathParameter.InputFile.class)
private Path input = Paths.get("/dev/stdin");
@Parameter(
names = {"-o", "--output"},
description = "Output file. If this is a directory, then in --encrypt mode, the output "
+ "filename will be the input filename with '.ghostryde' appended, and in --decrypt "
+ "mode, the output filename will be determined based on the name stored within the "
+ "archive.",
validateWith = PathParameter.class)
private Path output = Paths.get("/dev/stdout");
@Inject
Ghostryde ghostryde;
@Inject
@Key("rdeStagingEncryptionKey")
PGPPublicKey rdeStagingEncryptionKey;
@Inject
@Key("rdeStagingDecryptionKey")
PGPPrivateKey rdeStagingDecryptionKey;
@Override
public final void run() throws Exception {
checkArgument(encrypt ^ decrypt, "Please specify either --encrypt or --decrypt");
if (encrypt) {
runEncrypt();
} else {
runDecrypt();
}
}
private void runEncrypt() throws IOException, PGPException {
Path outFile = Files.isDirectory(output)
? output.resolve(input.getFileName() + ".ghostryde")
: output;
try (OutputStream out = Files.newOutputStream(outFile);
Ghostryde.Encryptor encryptor =
ghostryde.openEncryptor(out, rdeStagingEncryptionKey);
Ghostryde.Compressor kompressor = ghostryde.openCompressor(encryptor);
Ghostryde.Output ghostOutput =
ghostryde.openOutput(kompressor, input.getFileName().toString(),
new DateTime(Files.getLastModifiedTime(input).toMillis(), UTC));
InputStream in = Files.newInputStream(input)) {
ByteStreams.copy(in, ghostOutput);
}
}
private void runDecrypt() throws IOException, PGPException {
try (InputStream in = Files.newInputStream(input);
Ghostryde.Decryptor decryptor =
ghostryde.openDecryptor(in, rdeStagingDecryptionKey);
Ghostryde.Decompressor decompressor = ghostryde.openDecompressor(decryptor);
Ghostryde.Input ghostInput = ghostryde.openInput(decompressor)) {
Path outFile = Files.isDirectory(output)
? output.resolve(ghostInput.getName())
: output;
Files.copy(ghostInput, outFile, REPLACE_EXISTING);
Files.setLastModifiedTime(outFile,
FileTime.fromMillis(ghostInput.getModified().getMillis()));
}
}
}

View file

@ -0,0 +1,81 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.domain.registry.tools.Command.GtechCommand;
/** Command line interface with a subset of commands that are safe for tech support to run. */
public final class GtechTool {
/**
* Commands that exist in both {@link GtechTool} and {@link RegistryTool}.
*
* <p><b>Note:</b> If changing the command-line name of any commands below, remember to resolve
* any invocations in scripts (e.g. PDT, ICANN reporting).
*/
static final ImmutableMap<String, Class<? extends GtechCommand>> COMMAND_MAP =
ImmutableSortedMap.<String, Class<? extends GtechCommand>>naturalOrder()
.put("auction_status", AuctionStatusCommand.class)
.put("canonicalize_labels", CanonicalizeLabelsCommand.class)
.put("convert_idn", ConvertIdnCommand.class)
.put("create_anchor_tenant", CreateAnchorTenantCommand.class)
.put("create_contact", CreateContactCommand.class)
.put("create_credit", CreateCreditCommand.class)
.put("create_credit_balance", CreateCreditBalanceCommand.class)
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
.put("create_registrar", CreateRegistrarCommand.class)
.put("create_sandbox_tld", CreateSandboxTldCommand.class)
.put("delete_domain", DeleteDomainCommand.class)
.put("domain_application_info", DomainApplicationInfoCommand.class)
.put("domain_check", DomainCheckCommand.class)
.put("domain_check_claims", DomainCheckClaimsCommand.class)
.put("domain_check_fee", DomainCheckFeeCommand.class)
.put("generate_applications_report", GenerateApplicationsReportCommand.class)
.put("generate_auction_data", GenerateAuctionDataCommand.class)
.put("generate_dns_report", GenerateDnsReportCommand.class)
.put("get_application", GetApplicationCommand.class)
.put("get_application_ids", GetApplicationIdsCommand.class)
.put("get_applied_labels", GetAppliedLabelsCommand.class)
.put("get_contact", GetContactCommand.class)
.put("get_domain", GetDomainCommand.class)
.put("get_history_entries", GetHistoryEntriesCommand.class)
.put("get_host", GetHostCommand.class)
.put("get_registrar", GetRegistrarCommand.class)
.put("get_schema", GetSchemaCommand.class)
.put("get_schema_tree", GetSchemaTreeCommand.class)
.put("get_tld", GetTldCommand.class)
.put("hash_certificate", HashCertificateCommand.class)
.put("list_credits", ListCreditsCommand.class)
.put("list_registrars", ListRegistrarsCommand.class)
.put("list_tlds", ListTldsCommand.class)
.put("publish_detail_report", PublishDetailReportCommand.class)
.put("registrar_activity_report", RegistrarActivityReportCommand.class)
.put("registrar_contact", RegistrarContactCommand.class)
.put("setup_ote", SetupOteCommand.class)
.put("update_registrar", UpdateRegistrarCommand.class)
.put("update_sandbox_tld", UpdateSandboxTldCommand.class)
.put("update_server_locks", UpdateServerLocksCommand.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 {
RegistryToolEnvironment.parseFromArgs(args).setup();
new RegistryCli().run("gtech_tool", args, COMMAND_MAP);
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.X509Utils.getCertificateHash;
import static com.google.domain.registry.util.X509Utils.loadCertificate;
import com.google.common.base.Joiner;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.cert.CertificateParsingException;
import java.util.ArrayList;
import java.util.List;
/** Command to hash a client certificate. */
@Parameters(commandDescription = "Hash a client certificate")
final class HashCertificateCommand implements GtechCommand {
@Parameter(description = "Certificate filename")
List<String> mainParameters = new ArrayList<>();
@Override
public void run() throws IOException, CertificateParsingException {
checkArgument(mainParameters.size() <= 1,
"Expected at most one argument with the certificate filename. Actual: %s",
Joiner.on(' ').join(mainParameters));
if (mainParameters.isEmpty()) {
System.out.println(getCertificateHash(loadCertificate(System.in)));
} else {
System.out.println(getCertificateHash(loadCertificate(Paths.get(mainParameters.get(0)))));
}
}
}

View file

@ -0,0 +1,48 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.collect.Iterables.getOnlyElement;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.ArrayList;
import java.util.List;
/** A command to display per-command usage. */
@Parameters(commandDescription = "Get usage for a command")
final class HelpCommand implements Command {
private final JCommander jcommander;
HelpCommand(JCommander jcommander) {
this.jcommander = jcommander;
}
@Parameter(description = "<command>")
private List<String> mainParameters = new ArrayList<>();
@Override
public void run() throws Exception {
String target = getOnlyElement(mainParameters, null);
if (target == null) {
jcommander.usage();
} else {
jcommander.usage(target);
}
}
}

View file

@ -0,0 +1,56 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Utilities for dependency injection using Dagger2.
*/
final class Injector {
/**
* Reflectively injects the dependencies of an object instance using Dagger2.
*
* <p>There must exist a method named {@code inject} on your {@code component} accepting a single
* parameter whose type is identical to the class of {@code object}.
*
* <p><b>Note:</b> This is obviously slow since it uses reflection, which goes against the entire
* philosophy of Dagger2. This method is only useful if you want to avoid avoid the boilerplate of
* a super long chain of instanceof if statements, and you aren't concerned about performance.
*
* @return {@code true} if an appropriate injection method existed
*/
static <T> boolean injectReflectively(Class<T> componentType, T component, Object object) {
for (Method method : componentType.getMethods()) {
if (!method.getName().equals("inject")) {
continue;
}
Class<?> type = method.getParameterTypes()[0];
if (type == object.getClass()) {
try {
method.invoke(component, object);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return true;
}
}
return false;
}
private Injector() {}
}

View file

@ -0,0 +1,76 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCreditBalance.BalanceMap;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Work;
import org.joda.money.Money;
/** Command to list registrar credits and balances. */
@Parameters(commandDescription = "List registrar credits and balances")
final class ListCreditsCommand implements RemoteApiCommand, GtechCommand {
@Parameter(
names = {"-a", "--all"},
description = "Pass this flag to show all credits, even those with a zero active balance")
private boolean showAll;
@Override
public void run() {
for (Registrar registrar : Registrar.loadAll()) {
ImmutableList<String> creditStrings = createCreditStrings(registrar);
if (!creditStrings.isEmpty()) {
System.out.println(registrar.getClientIdentifier());
System.out.print(Joiner.on("").join(creditStrings));
System.out.println();
}
}
}
private ImmutableList<String> createCreditStrings(final Registrar registrar) {
return ofy()
.transactNewReadOnly(
new Work<ImmutableList<String>>() {
@Override
public ImmutableList<String> run() {
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
for (RegistrarCredit credit : RegistrarCredit.loadAllForRegistrar(registrar)) {
BalanceMap balanceMap = BalanceMap.createForCredit(credit);
Optional<Money> activeBalance =
balanceMap.getActiveBalanceAtTime(ofy().getTransactionTime());
// Unless showAll is true, only show credits with a positive active balance (which
// excludes just zero-balance credits since credit balances cannot be negative).
if (showAll || (activeBalance.isPresent() && activeBalance.get().isPositive())) {
builder.add(credit.getSummary() + "\n" + balanceMap);
}
}
return builder.build();
}
});
}
}

View file

@ -0,0 +1,72 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.base.Optional;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.registry.Registries;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldType;
import com.google.domain.registry.model.registry.RegistryCursor;
import com.google.domain.registry.model.registry.RegistryCursor.CursorType;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import java.util.ArrayList;
import java.util.List;
/** Lists {@link RegistryCursor} timestamps used by locking rolling cursor tasks, like in RDE. */
@Parameters(separators = " =", commandDescription = "Lists cursor timestamps used by LRC tasks")
final class ListCursorsCommand implements RemoteApiCommand {
@Parameter(
names = "--type",
description = "Which cursor to list.",
required = true)
private CursorType cursorType;
@Parameter(
names = "--tld_type",
description = "Filter TLDs of a certain type (REAL or TEST.)")
private TldType filterTldType = TldType.REAL;
@Parameter(
names = "--escrow_enabled",
description = "Filter TLDs to only include those with RDE escrow enabled.")
private boolean filterEscrowEnabled;
@Override
public void run() throws Exception {
List<String> lines = new ArrayList<>();
for (String tld : Registries.getTlds()) {
Registry registry = Registry.get(tld);
if (filterTldType != registry.getTldType()) {
continue;
}
if (filterEscrowEnabled && !registry.getEscrowEnabled()) {
continue;
}
Optional<DateTime> cursor = RegistryCursor.load(registry, cursorType);
lines.add(String.format("%-25s%s", cursor.isPresent() ? cursor.get() : "absent", tld));
}
for (String line : Ordering.natural().sortedCopy(lines)) {
System.out.println(line);
}
}
}

View file

@ -0,0 +1,45 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.tools.server.ListDomainsAction;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
/** Command to list all second-level domains associated with a TLD. */
@Parameters(separators = " =", commandDescription = "List domains associated with a TLD.")
final class ListDomainsCommand extends ListObjectsCommand {
@Parameter(
names = {"-t", "--tld"},
description = "Top level domain whose second-level domains shall be listed.",
required = true)
private String tld;
@Override
String getCommandPath() {
return ListDomainsAction.PATH;
}
/** Returns a map of parameters to be sent to the server
* (in addition to the usual ones). */
@Override
ImmutableMap<String, Object> getParameterMap() {
return ImmutableMap.<String, Object>of("tld", tld);
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.server.ListHostsAction;
import com.beust.jcommander.Parameters;
/** Command to list all HostResource entities associated with a TLD. */
@Parameters(separators = " =", commandDescription = "List hosts associated with a TLD.")
final class ListHostsCommand extends ListObjectsCommand {
@Override
String getCommandPath() {
return ListHostsAction.PATH;
}
}

View file

@ -0,0 +1,141 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
import static com.google.domain.registry.tools.server.ListObjectsAction.FIELDS_PARAM;
import static com.google.domain.registry.tools.server.ListObjectsAction.FULL_FIELD_NAMES_PARAM;
import static com.google.domain.registry.tools.server.ListObjectsAction.PRINT_HEADER_ROW_PARAM;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import org.json.simple.JSONValue;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Abstract base class for commands that list objects by calling a server task.
*
* <p>The formatting is done on the server side; this class just dumps the results to the screen.
*/
abstract class ListObjectsCommand implements RemoteApiCommand, ServerSideCommand {
@Nullable
@Parameter(
names = {"-f", "--fields"},
description = "Comma-separated list of fields to show for each object listed")
private String fields;
@Nullable
@Parameter(
names = {"--header"},
description = "Whether or not to print a header row for the resulting output - default is to "
+ "only print headers when more than one column is output",
arity = 1)
private Boolean printHeaderRow;
@Parameter(
names = {"--full_field_names"},
description = "Whether to print full field names in header row (as opposed to aliases)")
private boolean fullFieldNames = false;
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
/** Returns the path to the servlet task. */
abstract String getCommandPath();
/** Returns a map of parameters to be sent to the server
* (in addition to the usual ones). */
@Nullable
ImmutableMap<String, Object> getParameterMap() {
return null;
}
@Override
public void run() throws Exception {
ImmutableMap.Builder<String, Object> params = ImmutableMap.<String, Object>builder();
if (fields != null) {
params.put(FIELDS_PARAM, fields);
}
if (printHeaderRow != null) {
params.put(PRINT_HEADER_ROW_PARAM, printHeaderRow);
}
if (fullFieldNames) {
params.put(FULL_FIELD_NAMES_PARAM, Boolean.TRUE);
}
ImmutableMap<String, Object> extraParams = getParameterMap();
if (extraParams != null) {
params.putAll(extraParams);
}
// Call the server and get the response data.
String response = connection.send(
getCommandPath(),
params.build(),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
// Parse the returned JSON and make sure it's a map.
Object obj = JSONValue.parse(response.substring(JSON_SAFETY_PREFIX.length()));
if (!(obj instanceof Map<?, ?>)) {
throw new VerifyException("Server returned unexpected JSON: " + response);
}
@SuppressWarnings("unchecked")
Map<String, Object> responseMap = (Map<String, Object>) obj;
// Get the status.
obj = responseMap.get("status");
if (obj == null) {
throw new VerifyException("Server returned no status");
}
if (!(obj instanceof String)) {
throw new VerifyException("Server returned non-string status");
}
String status = (String) obj;
// Handle errors.
if (status.equals("error")) {
obj = responseMap.get("error");
if (obj == null) {
throw new VerifyException("Server returned no error message");
}
System.out.println(obj);
// Handle success.
} else if (status.equals("success")) {
obj = responseMap.get("lines");
if (obj == null) {
throw new VerifyException("Server returned no response data");
}
if (!(obj instanceof List<?>)) {
throw new VerifyException("Server returned unexpected response data");
}
for (Object lineObj : (List<?>) obj) {
System.out.println(lineObj);
}
// Handle unexpected status values.
} else {
throw new VerifyException("Server returned unexpected status");
}
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.server.ListPremiumListsAction;
import com.beust.jcommander.Parameters;
/** Command to list all premium lists. */
@Parameters(separators = " =", commandDescription = "List all premium lists.")
final class ListPremiumListsCommand extends ListObjectsCommand {
@Override
String getCommandPath() {
return ListPremiumListsAction.PATH;
}
}

View file

@ -0,0 +1,30 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.server.ListRegistrarsAction;
import com.beust.jcommander.Parameters;
/** Command to list all registrars. */
@Parameters(separators = " =", commandDescription = "List all registrars.")
final class ListRegistrarsCommand extends ListObjectsCommand implements GtechCommand {
@Override
String getCommandPath() {
return ListRegistrarsAction.PATH;
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.server.ListReservedListsAction;
import com.beust.jcommander.Parameters;
/** Command to list all reserved lists. */
@Parameters(separators = " =", commandDescription = "List all reserved lists.")
final class ListReservedListsCommand extends ListObjectsCommand {
@Override
String getCommandPath() {
return ListReservedListsAction.PATH;
}
}

View file

@ -0,0 +1,30 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.server.ListTldsAction;
import com.beust.jcommander.Parameters;
/** Command to list all top-level domains. */
@Parameters(separators = " =", commandDescription = "List all top-level domains.")
final class ListTldsCommand extends ListObjectsCommand implements GtechCommand {
@Override
String getCommandPath() {
return ListTldsAction.PATH;
}
}

View file

@ -0,0 +1,126 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Predicates.notNull;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.domain.registry.bigquery.BigqueryUtils.SourceFormat;
import com.google.domain.registry.export.ExportConstants;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Command to load datastore snapshots into Bigquery. */
@Parameters(separators = " =", commandDescription = "Load datastore snapshot into Bigquery")
final class LoadSnapshotCommand extends BigqueryCommand {
@Parameter(
names = "--snapshot",
description = "Common filename prefix of the specific snapshot series to import.")
private String snapshotPrefix = null;
@Parameter(
names = "--gcs_bucket",
description = "Name of the GCS bucket from which to import datastore snapshots.")
private String snapshotGcsBucket = "domain-registry/snapshots/testing";
@Parameter(
names = "--kinds",
description = "List of datastore kinds for which to import snapshot data.")
private List<String> kindNames = new ArrayList<>(ExportConstants.getReportingKinds());
/** Runs the main snapshot import logic. */
@Override
public void runWithBigquery() throws Exception {
kindNames.removeAll(ImmutableList.of("")); // Filter out any empty kind names.
if (snapshotPrefix == null || kindNames.isEmpty()) {
System.err.println("Nothing to import; specify --snapshot and at least one kind.");
return;
}
Map<String, ListenableFuture<?>> loadJobs = loadSnapshotKinds(kindNames);
waitForLoadJobs(loadJobs);
}
/**
* Starts load jobs for the given snapshot kinds, and returns a map of kind name to
* ListenableFuture representing the result of the load job for that kind.
*/
private Map<String, ListenableFuture<?>> loadSnapshotKinds(List<String> kindNames)
throws Exception {
ImmutableMap.Builder<String, ListenableFuture<?>> builder = new ImmutableMap.Builder<>();
for (String kind : kindNames) {
String filename = String.format(
"gs://%s/%s.%s.backup_info", snapshotGcsBucket, snapshotPrefix, kind);
builder.put(kind, loadSnapshotFile(filename, kind));
System.err.println("Started load job for kind: " + kind);
}
return builder.build();
}
/** Starts a load job for the specified kind name, sourcing data from the given GCS file. */
private ListenableFuture<?> loadSnapshotFile(String filename, String kindName) throws Exception {
return bigquery().load(
bigquery().buildDestinationTable(kindName)
.description("Datastore snapshot import for " + kindName + ".")
.build(),
SourceFormat.DATASTORE_BACKUP,
ImmutableList.of(filename));
}
/**
* Block on the completion of the load jobs in the provided map, printing out information on
* each job's success or failure.
*/
private void waitForLoadJobs(Map<String, ListenableFuture<?>> loadJobs) throws Exception {
final long startTime = System.currentTimeMillis();
System.err.println("Waiting for load jobs...");
// Add callbacks to each load job that print information on successful completion or failure.
for (final String jobId : loadJobs.keySet()) {
final String jobName = "load-" + jobId;
Futures.addCallback(loadJobs.get(jobId), new FutureCallback<Object>() {
private double elapsedSeconds() {
return (System.currentTimeMillis() - startTime) / 1000.0;
}
@Override
public void onSuccess(Object unused) {
System.err.printf("Job %s succeeded (%.3fs)\n", jobName, elapsedSeconds());
}
@Override
public void onFailure(Throwable error) {
System.err.printf(
"Job %s failed (%.3fs): %s\n", jobName, elapsedSeconds(), error.getMessage());
}
});
}
// Block on the completion of all the load jobs.
List<?> results = Futures.successfulAsList(loadJobs.values()).get();
int numSucceeded = FluentIterable.from(results).filter(notNull()).size();
System.err.printf(
"All load jobs have terminated: %d/%d successful.\n",
numSucceeded, loadJobs.size());
}
}

View file

@ -0,0 +1,78 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.domain.registry.util.ResourceUtils.readResourceBytes;
import com.google.common.base.Joiner;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogManager;
import javax.annotation.Nullable;
/** Parameter delegate class to handle logging configuration for {@link RegistryCli}. */
@Parameters(separators = " =")
final class LoggingParameters {
@Nullable
@Parameter(
names = "--log_level",
description = "Default level at which to log messages")
private Level logLevel;
@Parameter(
names = "--logging_configs",
description = "Comma-delimited list of logging properties to add to the logging.properties "
+ "file, e.g. com.example.level=WARNING,com.example.FooClass.level=SEVERE")
private List<String> configLines = new ArrayList<>();
@Nullable
@Parameter(
names = "--logging_properties_file",
description = "File from which to read custom logging properties",
validateWith = PathParameter.InputFile.class)
private Path configFile;
private static final ByteSource DEFAULT_LOG_CONFIG =
readResourceBytes(LoggingParameters.class, "logging.properties");
void configureLogging() throws IOException {
ByteSource baseConfig = (configFile != null)
? Files.asByteSource(configFile.toFile())
: DEFAULT_LOG_CONFIG;
if (logLevel != null) {
configLines.add(".level = " + logLevel);
}
// Add an extra leading newline in case base properties file does not end in a newline.
String customProperties = "\n" + Joiner.on('\n').join(configLines);
ByteSource logConfig =
ByteSource.concat(baseConfig, ByteSource.wrap(customProperties.getBytes()));
try (InputStream input = logConfig.openStream()) {
LogManager.getLogManager().readConfiguration(input);
}
}
}

View file

@ -0,0 +1,219 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.tools.BigqueryCommandUtilities.handleTableCreation;
import static com.google.domain.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.domain.registry.bigquery.BigqueryUtils.TableType;
import com.google.domain.registry.tools.BigqueryCommandUtilities.TableCreationException;
import com.google.domain.registry.util.SqlTemplate;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.List;
/** Command to make synthetic billing tables and views in Bigquery. */
@Parameters(separators = " =", commandDescription = "Make synthetic billing tables in Bigquery")
final class MakeBillingTablesCommand extends BigqueryCommand {
@Parameter(
names = "--source_dataset",
description = "Name of the dataset containing the source entity tables.")
private String sourceDatasetId;
// TODO(b/14297938): we should be picking up the TLDs to bill automatically based on the tldType
// and tldState rather than having to manually pass them in.
@Parameter(
names = "--tlds",
description = "TLD(s) to include in billing data table",
required = true)
private List<String> tlds;
private static final SqlTemplate CURRENCY_TABLE_SQL = getSql("currency_table.sql");
private static final SqlTemplate REGISTRAR_DATA_SQL = getSql("registrar_data_view.sql");
private static final SqlTemplate REGISTRY_DATA_SQL = getSql("registry_data_view.sql");
private static final SqlTemplate CREDIT_DATA_SQL = getSql("credit_data_view.sql");
private static final SqlTemplate CREDIT_BALANCE_DATA_SQL = getSql("credit_balance_data_view.sql");
private static final SqlTemplate PREMIUM_LIST_DATA_SQL = getSql("premium_list_data_view.sql");
private static final SqlTemplate RECURRING_DATA_SQL = getSql("recurring_event_data_view.sql");
private static final SqlTemplate BILLING_DATA_SQL = getSql("billing_data_view.sql");
/** Runs the main billing table/view creation logic. */
@Override
public void runWithBigquery() throws Exception {
// Make the source dataset default to the default destination dataset if it has not been set.
sourceDatasetId = Optional.fromNullable(sourceDatasetId).or(bigquery().getDatasetId());
checkArgument(!tlds.isEmpty(), "Must specify at least 1 TLD to include in billing data table");
// TODO(b/19016191): Should check that input tables exist up front, and avoid later errors.
try {
makeCurrencyTable();
makeRegistrarView();
makeRegistryView();
makeCreditView();
makeCreditBalanceView();
makePremiumListView();
makeRecurringEventView();
makeBillingView();
} catch (TableCreationException e) {
// Swallow since we already will have printed an error message.
}
}
/** Generates a table of currency information. */
// TODO(b/19016720): once there is a registry-wide currency, this method should compute the set of
// currencies needed for the active registries, look up the exponent for each currency from Joda
// CurrencyUnit data, and then auto-generate the query to construct this table.
// NB: "exponent" is the ISO 4217 name for the number of decimal places used by a currency.
private void makeCurrencyTable() throws Exception {
handleTableCreation(
"currency table",
bigquery().query(
CURRENCY_TABLE_SQL
.build(),
bigquery().buildDestinationTable("Currency")
.description("Generated table of currency information.")
.build()));
}
/**
* Generates a view of registrar data, used as an intermediate so that the invoice generation
* stage doesn't have to refer all the way back to the original managed backup dataset.
*/
private void makeRegistrarView() throws Exception {
handleTableCreation(
"registrar data view",
bigquery().query(
REGISTRAR_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.build(),
bigquery().buildDestinationTable("RegistrarData")
.description("Synthetic view of registrar information.")
.type(TableType.VIEW)
.build()));
}
/** Generates a view of registry data to feed into later views. */
private void makeRegistryView() throws Exception {
handleTableCreation(
"registry data view",
bigquery().query(
REGISTRY_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.build(),
bigquery().buildDestinationTable("RegistryData")
.description("Synthetic view of registry information.")
.type(TableType.VIEW)
.build()));
}
/**
* Generates a view of registrar credit entities that links in information from the owning
* Registrar (e.g. billing ID).
*/
private void makeCreditView() throws Exception {
handleTableCreation(
"credit data view",
bigquery().query(
CREDIT_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.put("DEST_DATASET", bigquery().getDatasetId())
.build(),
bigquery().buildDestinationTable("CreditData")
.description("Synthetic view of registrar credit information.")
.type(TableType.VIEW)
.build()));
}
/**
* Generates a view of registrar credit balance entities that collapses them down to the one
* 'true' credit balance for a given credit ID and effective time, eliminating any duplicates by
* choosing the most recently written balance entry of the set.
* <p>
* The result is a list of the historical balances of each credit (according to the most recent
* data written) that can be used to find the active balance of a credit at any point in time.
*/
private void makeCreditBalanceView() throws Exception {
handleTableCreation(
"credit balance data view",
bigquery().query(
CREDIT_BALANCE_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.put("DEST_DATASET", bigquery().getDatasetId())
.build(),
bigquery().buildDestinationTable("CreditBalanceData")
.description("Synthetic view of registrar credit balance information.")
.type(TableType.VIEW)
.build()));
}
/** Generates a view of premium list data for each TLD. */
private void makePremiumListView() throws Exception {
handleTableCreation(
"premium list data view",
bigquery().query(
PREMIUM_LIST_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.put("DEST_DATASET", bigquery().getDatasetId())
.build(),
bigquery().buildDestinationTable("PremiumListData")
.description("Synthetic view of premium list data.")
.type(TableType.VIEW)
.build()));
}
/** Generates a view of recurring billing events expanded into individual recurrences. */
private void makeRecurringEventView() throws Exception {
handleTableCreation(
"recurring event data view",
bigquery().query(
RECURRING_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.put("DEST_DATASET", bigquery().getDatasetId())
.build(),
bigquery().buildDestinationTable("RecurringEventData")
.description("Synthetic view of recurring billing event recurrence data.")
.type(TableType.VIEW)
.build()));
}
/**
* Generates a view of consolidated billing information that includes currency conversions,
* registrar details, and cancellation flags on top of the original BillingEvent.OneTime data.
*/
private void makeBillingView() throws Exception {
handleTableCreation(
"billing data view",
bigquery().query(
BILLING_DATA_SQL
.put("SOURCE_DATASET", sourceDatasetId)
.put("DEST_DATASET", bigquery().getDatasetId())
.put("TLDS", Joiner.on(",").join(tlds))
.build(),
bigquery().buildDestinationTable("BillingData")
.description("Synthetic view of consolidated billing information.")
.type(TableType.VIEW)
.build()));
}
private static SqlTemplate getSql(String filename) {
return SqlTemplate.create(
readResourceUtf8(MakeBillingTablesCommand.class, "sql/" + filename));
}
}

View file

@ -0,0 +1,237 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Functions.toStringFunction;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.DatastoreServiceUtils.getNameOrId;
import static com.google.domain.registry.util.DiffUtils.prettyPrintDeepDiff;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
/** A {@link ConfirmingCommand} that changes objects in the datastore. */
public abstract class MutatingCommand extends ConfirmingCommand implements RemoteApiCommand {
/**
* A mutation of a specific entity, represented by an old and a new version of the entity.
* Storing the old version is necessary to enable checking that the existing entity has not been
* modified when applying a mutation that was created outside the same transaction.
*/
private static class EntityChange {
/** The possible types of mutation that can be performed on an entity. */
public static enum ChangeType {
CREATE, DELETE, UPDATE;
/** Return the ChangeType corresponding to the given combination of version existences. */
public static ChangeType get(boolean hasOldVersion, boolean hasNewVersion) {
checkArgument(
hasOldVersion || hasNewVersion,
"An entity change must have an old version or a new version (or both)");
return !hasOldVersion ? CREATE : (!hasNewVersion ? DELETE : UPDATE);
}
}
/** The type of mutation being performed on the entity. */
final ChangeType type;
/** The old version of the entity, or null if this is a create. */
final ImmutableObject oldEntity;
/** The new version of the entity, or null if this is a delete. */
final ImmutableObject newEntity;
/** The key that points to the entity being changed. */
final Key<ImmutableObject> key;
public EntityChange(ImmutableObject oldEntity, ImmutableObject newEntity) {
type = ChangeType.get(oldEntity != null, newEntity != null);
checkArgument(
type != ChangeType.UPDATE || Key.create(oldEntity).equals(Key.create(newEntity)),
"Both entity versions in an update must have the same Key.");
this.oldEntity = oldEntity;
this.newEntity = newEntity;
key = Key.create(MoreObjects.firstNonNull(oldEntity, newEntity));
}
/** Returns a human-readable ID string for the entity being changed. */
public String getEntityId() {
return String.format(
"%s@%s",
key.getKind(),
// NB: try name before id, since name defaults to null, whereas id defaults to 0.
getNameOrId(key.getRaw()));
}
/** Returns a string representation of this entity change. */
@Override
public String toString() {
return String.format(
"%s %s\n%s",
UPPER_UNDERSCORE.to(UPPER_CAMEL, type.toString()),
getEntityId(),
type == ChangeType.UPDATE
? Optional
.fromNullable(emptyToNull(prettyPrintDeepDiff(
oldEntity.toDiffableFieldMap(), newEntity.toDiffableFieldMap())))
.or("[no changes]\n")
: (MoreObjects.firstNonNull(oldEntity, newEntity) + "\n"));
}
}
/** Map from entity keys to EntityChange objects representing changes to those entities. */
private final LinkedHashMap<Key<ImmutableObject>, EntityChange> changedEntitiesMap =
new LinkedHashMap<>();
/** A set of resource keys for which new transactions should be created after. */
private final Set<Key<ImmutableObject>> transactionBoundaries = new HashSet<>();
@Nullable
private Key<ImmutableObject> lastAddedKey;
/**
* Initializes the command.
* <p>
* Subclasses override this method to populate {@link #changedEntitiesMap} with updated
* entities. The old entity is the key and the new entity is the value; the key is null for
* newly created entities and the value is null for deleted entities.
*/
@Override
protected abstract void init() throws Exception;
/**
* Performs the command and returns a result description.
*
* <p>Subclasses can override this method if the command does something besides update entities,
* such as running a full flow.
*/
@Override
protected String execute() throws Exception {
for (final List<EntityChange> batch : getCollatedEntityChangeBatches()) {
ofy().transact(new VoidWork() {
@Override
public void vrun() {
for (EntityChange change : batch) {
// Load the key of the entity to mutate and double-check that it hasn't been
// modified from the version that existed when the change was prepared.
ImmutableObject existingEntity = ofy().load().key(change.key).now();
checkState(
Objects.equals(change.oldEntity, existingEntity),
"Entity changed since init() was called.\n%s",
prettyPrintDeepDiff(
change.oldEntity == null ? ImmutableMap.of()
: change.oldEntity.toDiffableFieldMap(),
existingEntity == null ? ImmutableMap.of()
: existingEntity.toDiffableFieldMap()));
switch (change.type) {
case CREATE: // Fall through.
case UPDATE:
ofy().save().entity(change.newEntity).now();
break;
case DELETE:
ofy().delete().key(change.key).now();
break;
default:
throw new UnsupportedOperationException(
"Unknown entity change type: " + change.type);
}
}
}});
}
return String.format("Updated %d entities.\n", changedEntitiesMap.size());
}
/**
* Returns a set of lists of EntityChange actions to commit. Each list should be executed in
* order inside a single transaction.
*/
private ImmutableSet<ImmutableList<EntityChange>> getCollatedEntityChangeBatches() {
ImmutableSet.Builder<ImmutableList<EntityChange>> batches = new ImmutableSet.Builder<>();
ArrayList<EntityChange> nextBatch = new ArrayList<>();
for (EntityChange change : changedEntitiesMap.values()) {
nextBatch.add(change);
if (transactionBoundaries.contains(change.key)) {
batches.add(ImmutableList.copyOf(nextBatch));
nextBatch.clear();
}
}
if (!nextBatch.isEmpty()) {
batches.add(ImmutableList.copyOf(nextBatch));
}
return batches.build();
}
/**
* Subclasses can call this to stage a mutation to an entity that will be applied by execute().
* Note that both objects passed must correspond to versions of the same entity with the same key.
*
* @param oldEntity the existing version of the entity, or null to create a new entity
* @param newEntity the new version of the entity to save, or null to delete the entity
*/
protected void stageEntityChange(
@Nullable ImmutableObject oldEntity, @Nullable ImmutableObject newEntity) {
EntityChange change = new EntityChange(oldEntity, newEntity);
checkArgument(
!changedEntitiesMap.containsKey(change.key),
"Cannot apply multiple changes for the same entity: %s",
change.getEntityId());
changedEntitiesMap.put(change.key, change);
lastAddedKey = change.key;
}
/**
* Subclasses can call this to write out all previously requested entity changes since the last
* transaction flush in a transaction.
*/
protected void flushTransaction() {
transactionBoundaries.add(checkNotNull(lastAddedKey));
}
/** Returns the changes that have been staged thus far. */
@Override
protected String prompt() {
return changedEntitiesMap.isEmpty()
? "No entity changes to apply."
: Joiner.on("\n").join(FluentIterable
.from(changedEntitiesMap.values())
.transform(toStringFunction()));
}
}

View file

@ -0,0 +1,41 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.beust.jcommander.Parameter;
/**
* A command to execute an epp command that intends to mutate objects
* (i.e. enables a dry run option).
*/
abstract class MutatingEppToolCommand extends EppToolCommand {
@Parameter(
names = {"-d", "--dry_run"},
description = "Do not actually commit any mutations")
boolean dryRun;
@Override
protected boolean isDryRun() {
return dryRun;
}
@Override
protected void initEppToolCommand() throws Exception {
initMutatingEppToolCommand();
}
abstract void initMutatingEppToolCommand() throws Exception;
}

View file

@ -0,0 +1,22 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
/** Password generator interface. */
interface PasswordGenerator {
/** Generates a password of a specified length. */
String createPassword(int length);
}

View file

@ -0,0 +1,55 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Ordering;
import com.google.domain.registry.rde.PendingDeposit;
import com.google.domain.registry.rde.PendingDepositChecker;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameters;
import javax.inject.Inject;
/** Command to show what escrow deposits are pending generation on the server. */
@Parameters(separators = " =", commandDescription = "List pending RDE/BRDA deposits.")
final class PendingEscrowCommand implements RemoteApiCommand {
private static final Ordering<PendingDeposit> SORTER =
new Ordering<PendingDeposit>() {
@Override
public int compare(PendingDeposit left, PendingDeposit right) {
return ComparisonChain.start()
.compare(left.tld(), right.tld())
.compare(left.mode(), right.mode())
.compare(left.watermark(), right.watermark())
.result();
}};
@Inject
PendingDepositChecker checker;
@Override
public void run() throws Exception {
System.out.println(FluentIterable
.from(SORTER.sortedCopy(checker.getTldsAndWatermarksPendingDepositForRdeAndBrda().values()))
.transform(Functions.toStringFunction())
.join(Joiner.on('\n')));
}
}

View file

@ -0,0 +1,104 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.export.PublishDetailReportAction;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.util.Map;
/**
* Command to publish a given billing detail report to a registrar's Drive folder.
*/
@Parameters(separators = " =", commandDescription = "Publish detail report for a registrar")
public class PublishDetailReportCommand extends ConfirmingCommand
implements ServerSideCommand, GtechCommand {
@Parameter(
names = "--registrar_id",
description = "Client identifier of the registrar to publish the report for",
required = true)
private String registrarId;
@Parameter(
names = "--report_name",
description = "Name of the detail report (without directory prefixes)",
required = true)
private String reportName;
@Parameter(
names = "--gcs_bucket",
description = "Name of the GCS bucket that holds billing output files.")
private String gcsBucket = "domain-registry-billing";
@Parameter(
names = "--gcs_folder",
description = "GCS folder under which report was exported.",
required = true)
private String gcsFolder;
private static final String DRIVE_FOLDER_URL_TEMPLATE =
"https://drive.google.com/corp/drive/#folders/%s";
private String gcsFolderPrefix;
private String driveFolderUrl;
private Connection connection;
@Override
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
protected void init() throws Exception {
// Append a trailing slash to the GCS folder (if there is one, and if it doesn't already end
// in a slash) to get the "folder prefix" version.
// TODO(b/18611424): Fix PublishDetailReportAction to take fewer error-prone parameters.
gcsFolderPrefix =
(gcsFolder.isEmpty() || gcsFolder.endsWith("/")) ? gcsFolder : gcsFolder + "/";
Registrar registrar = checkNotNull(
Registrar.loadByClientId(registrarId), "Registrar with ID %s not found", registrarId);
driveFolderUrl = String.format(DRIVE_FOLDER_URL_TEMPLATE, registrar.getDriveFolderId());
}
@Override
protected String prompt() {
String gcsFile = String.format("gs://%s/%s%s", gcsBucket, gcsFolderPrefix, reportName);
return "Publish detail report:\n"
+ " - Registrar: " + registrarId + "\n"
+ " - Drive folder: " + driveFolderUrl + "\n"
+ " - GCS file: " + gcsFile;
}
@Override
protected String execute() throws Exception {
final ImmutableMap<String, String> params = ImmutableMap.of(
PublishDetailReportAction.REGISTRAR_ID_PARAM, registrarId,
PublishDetailReportAction.DETAIL_REPORT_NAME_PARAM, reportName,
PublishDetailReportAction.GCS_FOLDER_PREFIX_PARAM, gcsFolderPrefix,
PublishDetailReportAction.GCS_BUCKET_PARAM, gcsBucket);
Map<String, Object> response =
connection.sendJson(PublishDetailReportAction.PATH, params);
return "Success! Published report with drive file ID: " + response.get("driveId");
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Random;
import javax.inject.Inject;
/** Password generator. */
class RandomPasswordGenerator implements PasswordGenerator {
private static final String SYMBOLS =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-=";
private final Random random;
@Inject
RandomPasswordGenerator(Random random) {
this.random = random;
}
/** Generates a password of a specified length. */
@Override
public String createPassword(int length) {
checkArgument(length > 0);
char[] password = new char[length];
for (int i = 0; i < length; ++i) {
password[i] = SYMBOLS.charAt(random.nextInt(SYMBOLS.length()));
}
return new String(password);
}
}

View file

@ -0,0 +1,116 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.ResourceUtils.readResourceUtf8;
import com.google.api.services.bigquery.model.Job;
import com.google.api.services.bigquery.model.JobConfiguration;
import com.google.api.services.bigquery.model.JobConfigurationExtract;
import com.google.api.services.bigquery.model.JobConfigurationQuery;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.bigquery.BigqueryConnection;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.util.SqlTemplate;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.nio.file.Path;
/** Command for creating a registrar activity report and saving it to cloud storage. */
@Parameters(separators = " =", commandDescription = "Generates a registrar activity report.")
final class RegistrarActivityReportCommand implements GtechCommand {
@ParametersDelegate
private final BigqueryParameters bigqueryParameters = new BigqueryParameters();
@Parameter(
names = {"-t", "--tld"},
description = "Name of TLD (in ASCII) on which to run report.",
required = true)
private String tld;
@Parameter(
names = {"-r", "--registrar"},
description = "Registrar clientId for which we're running this report.",
required = true)
private String registrarClientId;
@Parameter(
names = {"-s", "--start"},
description = "Start time for report.",
required = true)
private DateTime start;
@Parameter(
names = {"-e", "--end"},
description = "End time for report.",
required = true)
private DateTime end;
@Parameter(
names = {"-o", "--output"},
description = "GCS output filename, e.g. gs://bucket/blah.csv",
required = true)
private Path output;
@Override
public void run() throws Exception {
checkArgument(output.toUri().getScheme().equals("gs"),
"Not a valid cloud storage URI: %s", output);
checkArgument(end.isAfter(start), "End time must be after start time");
try (BigqueryConnection bq = bigqueryParameters.newConnection()) {
bq.ensureTable(bq.getTable("ReportingIdentifiers"),
getSql("ReportingIdentifiers.sql")
.build());
bq.ensureTable(bq.getTable("ReportingHistory"),
getSql("ReportingHistory.sql")
.build());
Job job = bq.runJob(new Job()
.setConfiguration(new JobConfiguration()
.setQuery(new JobConfigurationQuery()
.setDefaultDataset(bq.getDataset())
.setQuery(getSql("registrar_activity_report.sql")
.put("STARTTIME", BIGQUERY_TIMESTAMP_FORMATTER.print(start))
.put("ENDTIME", BIGQUERY_TIMESTAMP_FORMATTER.print(end))
.put("REGISTRAR", registrarClientId)
.put("TLD", tld)
.build()))));
bq.runJob(new Job()
.setConfiguration(new JobConfiguration()
.setExtract(new JobConfigurationExtract()
.setPrintHeader(true)
.setDestinationFormat("CSV")
.setDestinationUris(ImmutableList.of(output.toUri().toString()))
.setSourceTable(job.getConfiguration().getQuery().getDestinationTable()))));
}
}
private static SqlTemplate getSql(String filename) {
return SqlTemplate.create(
readResourceUtf8(RegistrarActivityReportCommand.class, "sql/" + filename));
}
/** Bigquery timestamps silently fail if you put the {@code T} in there. */
private static final DateTimeFormatter BIGQUERY_TIMESTAMP_FORMATTER =
DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss").withZoneUTC();
}

View file

@ -0,0 +1,264 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.common.GaeUserIdConverter;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.model.registrar.RegistrarContact.Builder;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.params.OptionalPhoneNumberParameter;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
/** Command for CRUD operations on {@link Registrar} contact list fields. */
@Parameters(
separators = " =",
commandDescription = "Create/read/update/delete the various contact lists for a Registrar.")
final class RegistrarContactCommand extends MutatingCommand implements GtechCommand {
private enum Mode { LIST, CREATE, UPDATE, DELETE }
@Parameter(
description = "Client identifier of the registrar account.",
required = true)
private List<String> mainParameters;
@Parameter(
names = "--mode",
description = "Type of operation you want to perform (LIST, CREATE, UPDATE, or DELETE).",
required = true)
private Mode mode;
@Nullable
@Parameter(
names = "--name",
description = "Contact name.")
private String name;
@Nullable
@Parameter(
names = "--contact_type",
description = "Type of communications for this contact; separate multiple with commas."
+ " Allowed values are ABUSE, ADMIN, BILLING, LEGAL, MARKETING, TECH, WHOIS.")
private List<String> contactTypeNames;
@Nullable
@Parameter(
names = "--email",
description = "Contact email address.")
private String email;
@Nullable
@Parameter(
names = "--phone",
description = "E.164 phone number, e.g. +1.2125650666",
converter = OptionalPhoneNumberParameter.class,
validateWith = OptionalPhoneNumberParameter.class)
private Optional<String> phone;
@Nullable
@Parameter(
names = "--fax",
description = "E.164 fax number, e.g. +1.2125650666",
converter = OptionalPhoneNumberParameter.class,
validateWith = OptionalPhoneNumberParameter.class)
private Optional<String> fax;
@Nullable
@Parameter(
names = "--allow_console_access",
description = "Enable or disable access to the registrar console for this contact.",
arity = 1)
private Boolean allowConsoleAccess;
@Nullable
@Parameter(
names = "--visible_in_whois_as_admin",
description = " Whether this contact is publically visible in WHOIS results as an "
+ "Admin contact.",
arity = 1)
private Boolean visibleInWhoisAsAdmin;
@Nullable
@Parameter(
names = "--visible_in_whois_as_tech",
description = " Whether this contact is publically visible in WHOIS results as a "
+ "Tech contact.",
arity = 1)
private Boolean visibleInWhoisAsTech;
@Parameter(
names = {"-o", "--output"},
description = "Output file when --mode=LIST",
validateWith = PathParameter.OutputFile.class)
private Path output = Paths.get("/dev/stdout");
@Nullable
private Set<RegistrarContact.Type> contactTypes;
@Override
protected void init() throws Exception {
checkArgument(mainParameters.size() == 1,
"Must specify exactly one client identifier: %s", ImmutableList.copyOf(mainParameters));
String clientIdentifier = mainParameters.get(0);
Registrar registrar = checkNotNull(
Registrar.loadByClientId(clientIdentifier), "Registrar %s not found", clientIdentifier);
contactTypes =
newHashSet(
transform(
nullToEmpty(contactTypeNames), Enums.stringConverter(RegistrarContact.Type.class)));
ImmutableSet<RegistrarContact> contacts = registrar.getContacts();
Map<String, RegistrarContact> contactsMap = new LinkedHashMap<>();
for (RegistrarContact rc : contacts) {
contactsMap.put(rc.getEmailAddress(), rc);
}
RegistrarContact oldContact;
switch (mode) {
case LIST:
listContacts(contacts);
break;
case CREATE:
stageEntityChange(null, createContact(registrar));
break;
case UPDATE:
oldContact =
checkNotNull(
contactsMap.get(checkNotNull(email, "--email is required when --mode=UPDATE")),
"No contact with the given email: %s",
email);
RegistrarContact newContact = updateContact(oldContact, registrar);
stageEntityChange(oldContact, newContact);
break;
case DELETE:
oldContact =
checkNotNull(
contactsMap.get(checkNotNull(email, "--email is required when --mode=DELETE")),
"No contact with the given email: %s",
email);
stageEntityChange(oldContact, null);
break;
default:
throw new AssertionError();
}
if (mode == Mode.CREATE || mode == Mode.UPDATE || mode == Mode.DELETE) {
stageEntityChange(registrar, registrar.asBuilder().setContactsRequireSyncing(true).build());
}
}
private void listContacts(Set<RegistrarContact> contacts) throws IOException {
List<String> result = new ArrayList<>();
for (RegistrarContact c : contacts) {
result.add(c.toStringMultilinePlainText());
}
Files.write(output, Joiner.on('\n').join(result).getBytes(UTF_8));
}
private RegistrarContact createContact(Registrar registrar) {
checkArgument(!isNullOrEmpty(name), "--name is required when --mode=CREATE");
checkArgument(!isNullOrEmpty(email), "--email is required when --mode=CREATE");
Builder builder = new RegistrarContact.Builder();
builder.setParent(registrar);
builder.setName(name);
builder.setEmailAddress(email);
if (phone != null) {
builder.setPhoneNumber(phone.orNull());
}
if (fax != null) {
builder.setFaxNumber(fax.orNull());
}
builder.setTypes(contactTypes);
if (Objects.equals(allowConsoleAccess, Boolean.TRUE)) {
builder.setGaeUserId(checkArgumentNotNull(
GaeUserIdConverter.convertEmailAddressToGaeUserId(email),
String.format("Email address %s is not associated with any GAE ID", email)));
}
if (visibleInWhoisAsAdmin != null) {
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
}
if (visibleInWhoisAsTech != null) {
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
}
return builder.build();
}
private RegistrarContact updateContact(RegistrarContact contact, Registrar registrar) {
checkNotNull(registrar);
checkNotNull(email, "--email is required when --mode=UPDATE");
Builder builder = contact.asBuilder();
builder.setParent(registrar);
if (!isNullOrEmpty(name)) {
builder.setName(name);
}
if (!isNullOrEmpty(email)) {
builder.setEmailAddress(email);
}
if (phone != null) {
builder.setPhoneNumber(phone.orNull());
}
if (fax != null) {
builder.setFaxNumber(fax.orNull());
}
if (contactTypes != null) {
builder.setTypes(contactTypes);
}
if (visibleInWhoisAsAdmin != null) {
builder.setVisibleInWhoisAsAdmin(visibleInWhoisAsAdmin);
}
if (visibleInWhoisAsTech != null) {
builder.setVisibleInWhoisAsTech(visibleInWhoisAsTech);
}
if (allowConsoleAccess != null) {
if (allowConsoleAccess.equals(Boolean.TRUE)) {
builder.setGaeUserId(checkArgumentNotNull(
GaeUserIdConverter.convertEmailAddressToGaeUserId(email),
String.format("Email address %s is not associated with any GAE ID", email)));
} else {
builder.setGaeUserId(null);
}
}
return builder.build();
}
}

View file

@ -0,0 +1,139 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.tools.Injector.injectReflectively;
import com.google.appengine.tools.remoteapi.RemoteApiInstaller;
import com.google.appengine.tools.remoteapi.RemoteApiOptions;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.model.ofy.ObjectifyService;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.ParameterFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
/** Container class to create and run remote commands against a datastore instance. */
@Parameters(separators = " =", commandDescription = "Command-line interface to the registry")
final class RegistryCli {
@Parameter(
names = {"-e", "--environment"},
description = "Sets the default environment to run the command.")
private RegistryToolEnvironment environment = RegistryToolEnvironment.PRODUCTION;
@ParametersDelegate
private AppEngineConnection connection = new AppEngineConnection();
@ParametersDelegate
private LoggingParameters loggingParams = new LoggingParameters();
// The <? extends Class<? extends Command>> wildcard looks a little funny, but is needed so that
// we can accept maps with value types that are subtypes of Class<? extends Command> rather than
// literally that type (e.g. Class<? extends GtechCommand>). For more explanation, see:
// http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ104
void run(
String programName,
String[] args,
ImmutableMap<String, ? extends Class<? extends Command>> commands) throws Exception {
Security.addProvider(new BouncyCastleProvider());
JCommander jcommander = new JCommander(this);
jcommander.addConverterFactory(new ParameterFactory());
jcommander.setProgramName(programName);
// Store the instances of each Command class here so we can retrieve the same one for the
// called command later on. JCommander could have done this for us, but it doesn't.
Map<String, Command> commandInstances = new HashMap<>();
HelpCommand helpCommand = new HelpCommand(jcommander);
jcommander.addCommand("help", helpCommand);
commandInstances.put("help", helpCommand);
for (Map.Entry<String, ? extends Class<? extends Command>> entry : commands.entrySet()) {
Command command = entry.getValue().newInstance();
jcommander.addCommand(entry.getKey(), command);
commandInstances.put(entry.getKey(), command);
}
try {
jcommander.parse(args);
} catch (ParameterException e) {
// If we failed to fully parse the command but at least found a valid command name, show only
// the usage for that command. Otherwise, show full usage. Either way, rethrow the error.
if (jcommander.getParsedCommand() == null) {
jcommander.usage();
} else {
jcommander.usage(jcommander.getParsedCommand());
}
// Don't rethrow if we said: registry_tool command --help
if ("Unknown option: --help".equals(e.getMessage())) {
return;
}
throw e;
}
checkState(RegistryToolEnvironment.get() == environment,
"RegistryToolEnvironment argument pre-processing kludge failed.");
Command command = commandInstances.get(jcommander.getParsedCommand());
if (command == null) {
jcommander.usage();
return;
}
loggingParams.configureLogging(); // Must be called after parameters are parsed.
injectReflectively(RegistryToolComponent.class, DaggerRegistryToolComponent.create(), command);
if (!(command instanceof RemoteApiCommand)) {
command.run();
return;
}
if (command instanceof ServerSideCommand) {
((ServerSideCommand) command).setConnection(connection);
}
// RemoteApiCommands need to have the remote api installed to work.
RemoteApiInstaller installer = new RemoteApiInstaller();
RemoteApiOptions options = new RemoteApiOptions();
options.server(connection.getServer().getHostText(), connection.getServer().getPort());
if (connection.isLocalhost()) {
// Use dev credentials for localhost.
options.useDevelopmentServerCredential();
} else {
options.useApplicationDefaultCredential();
}
installer.install(options);
// Ensure that all entity classes are loaded before command code runs.
ObjectifyService.initOfy();
try {
command.run();
} finally {
installer.uninstall();
}
}
}

View file

@ -0,0 +1,81 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.domain.registry.tools.javascrap.LoadAndResaveCommand;
/** Container class to create and run remote commands against a datastore instance. */
public final class RegistryTool {
/**
* Available commands.
*
* <p><b>Note:</b> If changing the command-line name of any commands below, remember to resolve
* any invocations in scripts (e.g. PDT, ICANN reporting).
*/
@VisibleForTesting
static final ImmutableMap<String, Class<? extends Command>> COMMAND_MAP =
ImmutableSortedMap.<String, Class<? extends Command>>naturalOrder()
.putAll(GtechTool.COMMAND_MAP)
.put("allocate_domain", AllocateDomainCommand.class)
.put("check_snapshot", CheckSnapshotCommand.class)
.put("create_auction_credits", CreateAuctionCreditsCommand.class)
.put("create_premium_list", CreatePremiumListCommand.class)
.put("create_reserved_list", CreateReservedListCommand.class)
.put("create_tld", CreateTldCommand.class)
.put("delete_credit", DeleteCreditCommand.class)
.put("delete_entity", DeleteEntityCommand.class)
.put("delete_epp_resource", DeleteEppResourceCommand.class)
.put("delete_premium_list", DeletePremiumListCommand.class)
.put("delete_reserved_list", DeleteReservedListCommand.class)
.put("encrypt_escrow_deposit", EncryptEscrowDepositCommand.class)
.put("execute_epp", ExecuteEppCommand.class)
.put("generate_zone_files", GenerateZoneFilesCommand.class)
.put("generate_escrow_deposit", GenerateEscrowDepositCommand.class)
.put("generate_lordn", GenerateLordnCommand.class)
.put("get_claims_list", GetClaimsListCommand.class)
.put("get_resource_by_key", GetResourceByKeyCommand.class)
.put("ghostryde", GhostrydeCommand.class)
.put("list_cursors", ListCursorsCommand.class)
.put("list_domains", ListDomainsCommand.class)
.put("list_hosts", ListHostsCommand.class)
.put("list_premium_lists", ListPremiumListsCommand.class)
.put("list_reserved_lists", ListReservedListsCommand.class)
.put("load_and_resave", LoadAndResaveCommand.class)
.put("load_snapshot", LoadSnapshotCommand.class)
.put("make_billing_tables", MakeBillingTablesCommand.class)
.put("pending_escrow", PendingEscrowCommand.class)
.put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class)
.put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class)
.put("update_application_status", UpdateApplicationStatusCommand.class)
.put("update_claims_notice", UpdateClaimsNoticeCommand.class)
.put("update_credits", UpdateCreditsCommand.class)
.put("update_cursors", UpdateCursorsCommand.class)
.put("update_premium_list", UpdatePremiumListCommand.class)
.put("update_reserved_list", UpdateReservedListCommand.class)
.put("update_smd", UpdateSmdCommand.class)
.put("update_tld", UpdateTldCommand.class)
.put("upload_claims_list", UploadClaimsListCommand.class)
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
.build();
public static void main(String[] args) throws Exception {
RegistryToolEnvironment.parseFromArgs(args).setup();
new RegistryCli().run("registry_tool", args, COMMAND_MAP);
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.config.ConfigModule;
import com.google.domain.registry.keyring.api.KeyModule;
import com.google.domain.registry.keyring.api.VoidKeyringModule;
import com.google.domain.registry.request.Modules.DatastoreServiceModule;
import com.google.domain.registry.request.Modules.Jackson2Module;
import com.google.domain.registry.request.Modules.URLFetchServiceModule;
import com.google.domain.registry.util.SystemClock.SystemClockModule;
import dagger.Component;
/**
* Dagger component for Registry Tool.
*
* <p>Any command class with {@code @Inject} fields <i>must</i> be listed as a method here.
* Otherwise {@link RegistryCli} will not be able to populate those fields after its instantiation.
*/
@Component(
modules = {
ConfigModule.class,
DatastoreServiceModule.class,
Jackson2Module.class,
KeyModule.class,
RegistryToolModule.class,
SystemClockModule.class,
URLFetchServiceModule.class,
VoidKeyringModule.class,
})
interface RegistryToolComponent {
void inject(CreateAnchorTenantCommand command);
void inject(CreateContactCommand command);
void inject(EncryptEscrowDepositCommand command);
void inject(GenerateApplicationsReportCommand command);
void inject(GenerateDnsReportCommand command);
void inject(GenerateEscrowDepositCommand command);
void inject(GhostrydeCommand command);
void inject(ListCursorsCommand command);
void inject(PendingEscrowCommand command);
void inject(SendEscrowReportToIcannCommand command);
void inject(SetupOteCommand command);
void inject(UpdateCursorsCommand command);
void inject(ValidateEscrowDepositCommand command);
void inject(WhoisQueryCommand command);
}

View file

@ -0,0 +1,116 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.config.RegistryEnvironment;
/** Enum of production environments, used for the {@code --environment} flag. */
enum RegistryToolEnvironment {
PRODUCTION(RegistryEnvironment.PRODUCTION),
ALPHA(RegistryEnvironment.ALPHA),
CRASH(RegistryEnvironment.CRASH),
QA(RegistryEnvironment.QA),
SANDBOX(RegistryEnvironment.SANDBOX),
LOCALHOST(RegistryEnvironment.LOCAL),
UNITTEST(RegistryEnvironment.UNITTEST),
PDT(RegistryEnvironment.PRODUCTION, ImmutableMap.of(
"com.google.domain.registry.rde.key.receiver",
"pdt-escrow-test@icann.org"));
private static final ImmutableList<String> FLAGS = ImmutableList.of("-e", "--environment");
private static RegistryToolEnvironment instance;
private final RegistryEnvironment actualEnvironment;
private final ImmutableMap<String, String> extraProperties;
private RegistryToolEnvironment(
RegistryEnvironment actualEnvironment,
ImmutableMap<String, String> extraProperties) {
this.actualEnvironment = actualEnvironment;
this.extraProperties = extraProperties;
}
private RegistryToolEnvironment(RegistryEnvironment actualEnvironment) {
this(actualEnvironment, ImmutableMap.<String, String>of());
}
/**
* Extracts environment from command-line arguments.
*
* <p>This operation can't be performed by JCommander because it needs to happen before any
* command classes get loaded. This is because this routine will set system properties that are
* referenced by static initializers for many classes. This approach also allows us to change the
* default values of JCommander parameters, based on the selected environment.
*
* @see #get()
*/
static RegistryToolEnvironment parseFromArgs(String[] args) {
return valueOf(getFlagValue(args, FLAGS).toUpperCase());
}
/**
* Returns the current environment.
*
* <p>This should be called after {@link #parseFromArgs(String[])}.
*/
static RegistryToolEnvironment get() {
checkState(instance != null, "No RegistryToolEnvironment has been set up");
return instance;
}
/** Setup execution environment. Call this method before any classes are loaded. */
void setup() {
instance = this;
System.setProperty(RegistryEnvironment.PROPERTY, actualEnvironment.name());
for (ImmutableMap.Entry<String, String> entry : extraProperties.entrySet()) {
System.setProperty(entry.getKey(), entry.getValue());
}
}
/** Extracts value from command-line arguments associated with any {@code flags}. */
private static String getFlagValue(String[] args, Iterable<String> flags) {
for (String flag : flags) {
boolean expecting = false;
for (int i = 0; i < args.length; i++) {
// XXX: Kludge to stop processing once we reach what is *probably* the command name.
// This is necessary in order to allow commands to accept arguments named '-e'.
// This code should probably be updated, should any zero arity flags be added to
// RegistryCli, LoggingParameters, or AppEngineConnection.
if (args[i].startsWith("-")) {
expecting = !args[i].contains("=");
} else {
if (expecting) {
expecting = false;
} else {
break; // This is the command name, unless zero arity flags were added.
}
}
if (args[i].equals(flag)) {
checkArgument(i + 1 < args.length, "%s flag missing value.", flag);
return args[i + 1];
}
if (args[i].startsWith(flag + "=")
|| args[i].startsWith(flag + " ")) {
return args[i].substring(flag.length() + 1);
}
}
}
throw new IllegalArgumentException("Please specify the environment flag, e.g. -e production.");
}
}

View file

@ -0,0 +1,47 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import dagger.Module;
import dagger.Provides;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.util.Random;
/** Dagger module for Registry Tool. */
@Module
final class RegistryToolModule {
@Provides
static RegistryToolEnvironment provideRegistryToolEnvironment() {
return RegistryToolEnvironment.get();
}
@Provides
static PasswordGenerator providePasswordGenerator(RandomPasswordGenerator passwordGenerator) {
return passwordGenerator;
}
@Provides
static Random provideRandom() {
try {
return SecureRandom.getInstance("NativePRNG");
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(e);
}
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.collect.Iterables.concat;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
/**
* Command to re-save all environment entities to ensure that they have valid commit logs.
*
* <p>The entities that are re-saved are those of type {@link Registry}, {@link Registrar}, and
* {@link RegistrarContact}.
*/
@Parameters(commandDescription = "Re-save all environment entities.")
final class ResaveEnvironmentEntitiesCommand implements RemoteApiCommand {
@Override
public void run() throws Exception {
Iterable<Key<? extends ImmutableObject>> keys = concat(
ofy().load().type(Registrar.class).ancestor(getCrossTldKey()).keys(),
ofy().load().type(Registry.class).ancestor(getCrossTldKey()).keys(),
ofy().load().type(RegistrarContact.class).ancestor(getCrossTldKey()).keys());
for (final Key<? extends ImmutableObject> key : keys) {
ofy().transact(new VoidWork() {
@Override
public void vrun() {
ofy().save().entity(ofy().load().key(key).now());
}});
System.out.printf("Re-saved entity %s\n", key);
}
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.domain.registry.rde.RdeReporter;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.inject.Inject;
/** Command to send ICANN notification that an escrow deposit was uploaded. */
@Parameters(separators = " =", commandDescription = "Send an ICANN report of an uploaded deposit.")
final class SendEscrowReportToIcannCommand implements RemoteApiCommand {
@Parameter(
description = "One or more foo-report.xml files.",
validateWith = PathParameter.InputFile.class,
required = true)
private List<Path> files;
@Inject
RdeReporter rdeReporter;
@Override
public void run() throws Exception {
for (Path file : files) {
rdeReporter.send(Files.readAllBytes(file));
System.out.printf("Uploaded: %s\n", file);
}
}
}

View file

@ -0,0 +1,40 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.net.MediaType;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import java.io.IOException;
import java.util.Map;
/** A command that executes on the server. */
interface ServerSideCommand extends RemoteApiCommand {
/** An http connection to AppEngine. */
interface Connection {
void prefetchXsrfToken() throws IOException;
String send(String endpoint, Map<String, ?> params, MediaType contentType, byte[] payload)
throws IOException;
Map<String, Object> sendJson(String endpoint, Map<String, ?> object) throws IOException;
String getServerUrl();
}
void setConnection(Connection connection);
}

View file

@ -0,0 +1,223 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.tools.CommandUtilities.promptForYes;
import static com.google.domain.registry.util.X509Utils.loadCertificate;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registry.Registry.TldState;
import com.google.domain.registry.tools.Command.GtechCommand;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.google.domain.registry.tools.params.PathParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.Duration;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.inject.Inject;
/** Composite command to set up OT&E TLDs and accounts. */
@Parameters(
separators = " =",
commandDescription = "Set up OT&E TLDs and registrars")
final class SetupOteCommand extends ConfirmingCommand implements RemoteApiCommand, GtechCommand {
// Regex: 3-14 alphanumeric characters or hyphens, the first of which must be a letter.
private static final Pattern REGISTRAR_PATTERN = Pattern.compile("^[a-z][-a-z0-9]{2,13}$");
private static final int PASSWORD_LENGTH = 16;
// Durations are short so that registrars can test with quick transfer (etc.) turnaround.
private static final Duration SHORT_ADD_GRACE_PERIOD = Duration.standardMinutes(60);
private static final Duration SHORT_REDEMPTION_GRACE_PERIOD = Duration.standardMinutes(10);
private static final Duration SHORT_PENDING_DELETE_LENGTH = Duration.standardMinutes(5);
private static final String DEFAULT_PREMIUM_LIST = "default_sandbox_list";
@Parameter(
names = {"-r", "--registrar"},
description = "must 1) consist of only lowercase letters, numbers, or hyphens, "
+ "2) start with a letter, and 3) be between 3 and 14 characters (inclusive). "
+ "We require 1 and 2 since the registrar name will be used to create TLDs,"
+ "and we require 3 since we append \"-[1234]\" to the name to create client"
+ "IDs which are required by the EPP XML schema to be between 3-16 chars.",
required = true)
private String registrar;
@Parameter(
names = {"-w", "--ip_whitelist"},
description = "comma separated list of IP addreses or CIDR ranges",
required = true)
private List<String> ipWhitelist = new ArrayList<>();
@Parameter(
names = {"-c", "--certfile"},
description = "full path to cert file in PEM format (best if on local storage)",
required = true,
validateWith = PathParameter.InputFile.class)
private Path certFile;
@Parameter(
names = {"--premium_list"},
description = "premium list to apply to all TLDs")
private String premiumList = DEFAULT_PREMIUM_LIST;
@Inject
PasswordGenerator passwordGenerator;
/**
* Long registrar names are truncated and then have an incrementing digit appended at the end so
* that unique ROID suffixes can be generated for all TLDs for the registrar.
*/
private int roidSuffixCounter = 0;
/** Constructs and runs a CreateTldCommand. */
private void createTld(
String tldName,
TldState initialTldState,
Duration addGracePeriod,
Duration redemptionGracePeriod,
Duration pendingDeleteLength) throws Exception {
CreateTldCommand command = new CreateTldCommand();
command.initialTldState = initialTldState;
command.mainParameters = ImmutableList.of(tldName);
command.roidSuffix = String.format(
"%S%X", tldName.replaceAll("[^a-z0-9]", "").substring(0, 7), roidSuffixCounter++);
command.addGracePeriod = addGracePeriod;
command.redemptionGracePeriod = redemptionGracePeriod;
command.pendingDeleteLength = pendingDeleteLength;
command.premiumListName = Optional.of(premiumList);
command.force = force;
command.run();
}
/** Constructs and runs a CreateRegistrarCommand */
private void createRegistrar(String registrarName, String password, String tld) throws Exception {
CreateRegistrarCommand command = new CreateRegistrarCommand();
command.mainParameters = ImmutableList.of(registrarName);
command.createGoogleGroups = false; // Don't create Google Groups for OT&E registrars.
command.allowedTlds = ImmutableList.of(tld);
command.registrarName = registrarName;
command.registrarType = Registrar.Type.OTE;
command.password = password;
command.clientCertificateFilename = certFile;
command.ipWhitelist = ipWhitelist;
command.street = ImmutableList.of("e-street");
command.city = "Neverland";
command.state = "ofmind";
command.countryCode = "US";
command.zip = "55555";
command.email = Optional.of("foo@neverland.com");
command.fax = Optional.of("+1.2125550100");
command.phone = Optional.of("+1.2125550100");
command.icannReferralEmail = "nightmare@registrar.test";
command.force = force;
command.run();
}
/** Run any pre-execute command checks */
@Override
protected boolean checkExecutionState() throws Exception {
checkArgument(REGISTRAR_PATTERN.matcher(registrar).matches(),
"Registrar name is invalid (see usage text for requirements).");
boolean warned = false;
if (RegistryEnvironment.get() != RegistryEnvironment.SANDBOX
&& RegistryEnvironment.get() != RegistryEnvironment.UNITTEST) {
System.err.printf(
"WARNING: Running against %s environment. Are "
+ "you sure you didn\'t mean to run this against sandbox (e.g. \"-e SANDBOX\")?%n",
RegistryEnvironment.get());
warned = true;
}
if (warned && !promptForYes("Proceed despite warnings?")) {
System.out.println("Command aborted.");
return false;
}
// Don't wait for create_registrar to fail if it's a bad certificate file.
loadCertificate(certFile.toAbsolutePath());
return true;
}
@Override
protected String prompt() throws Exception {
// Each underlying command will confirm its own operation as well, so just provide
// a summary of the steps in this command.
return "Creating TLDs:\n"
+ " " + registrar + "-sunrise\n"
+ " " + registrar + "-landrush\n"
+ " " + registrar + "-ga\n"
+ "Creating registrars:\n"
+ " " + registrar + "-1 (access to TLD " + registrar + "-sunrise)\n"
+ " " + registrar + "-2 (access to TLD " + registrar + "-landrush)\n"
+ " " + registrar + "-3 (access to TLD " + registrar + "-ga)\n"
+ " " + registrar + "-4 (access to TLD " + registrar + "-ga)";
}
@Override
public String execute() throws Exception {
createTld(registrar + "-sunrise", TldState.SUNRISE, null, null, null);
createTld(registrar + "-landrush", TldState.LANDRUSH, null, null, null);
createTld(
registrar + "-ga",
TldState.GENERAL_AVAILABILITY,
SHORT_ADD_GRACE_PERIOD,
SHORT_REDEMPTION_GRACE_PERIOD,
SHORT_PENDING_DELETE_LENGTH);
// Storing names and credentials in a list of tuples for later play-back.
List<List<String>> registrars = new ArrayList<>();
registrars.add(ImmutableList.<String>of(
registrar + "-1", passwordGenerator.createPassword(PASSWORD_LENGTH),
registrar + "-sunrise"));
registrars.add(ImmutableList.<String>of(
registrar + "-2", passwordGenerator.createPassword(PASSWORD_LENGTH),
registrar + "-landrush"));
registrars.add(ImmutableList.<String>of(
registrar + "-3", passwordGenerator.createPassword(PASSWORD_LENGTH),
registrar + "-ga"));
registrars.add(ImmutableList.<String>of(
registrar + "-4", passwordGenerator.createPassword(PASSWORD_LENGTH),
registrar + "-ga"));
for (List<String> r : registrars) {
createRegistrar(r.get(0), r.get(1), r.get(2));
}
StringBuilder output = new StringBuilder();
output.append("Copy these usernames/passwords back into the onboarding bug:\n\n");
for (List<String> r : registrars) {
output.append("Login: " + r.get(0) + "\n");
output.append("Password: " + r.get(1) + "\n");
output.append("TLD: " + r.get(2) + "\n\n");
}
return output.toString();
}
}

View file

@ -0,0 +1,183 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
import static com.google.domain.registry.model.domain.launch.ApplicationStatus.ALLOCATED;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.model.domain.launch.LaunchInfoResponseExtension;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.eppcommon.Trid;
import com.google.domain.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import com.google.domain.registry.model.poll.PollMessage;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.VoidWork;
import org.joda.time.DateTime;
import java.util.List;
/** Command to manually update the status of a domain application. */
@Parameters(separators = " =", commandDescription = "Manually update domain application status.")
final class UpdateApplicationStatusCommand extends MutatingCommand {
@Parameter(
names = "--ids",
description = "Comma-delimited list of application IDs to update the status.",
required = true)
private List<String> ids;
@Parameter(
names = "--msg",
description = "Message shown to registrars in the poll message.",
required = true)
private String message;
@Parameter(
names = "--status",
description = "The new application status.",
required = true)
private ApplicationStatus newStatus;
@Parameter(
names = "--history_client_id",
description = "Client id that made this change. Only recorded in the history entry.")
private String clientId = "CharlestonRoad";
@Override
protected void init() throws Exception {
checkArgumentNotNull(
Registrar.loadByClientId(clientId), "Registrar with client ID %s not found", clientId);
for (final String applicationId : ids) {
ofy().transact(new VoidWork() {
@Override
public void vrun() {
updateApplicationStatus(applicationId);
}
});
}
}
/**
* Stages changes to update the status of an application and also enqueue a poll message for the
* status change, which may contain a PendingActionNotificationResponse if this is a final status.
* <p>
* This method must be called from within a transaction.
*/
private void updateApplicationStatus(String applicationId) {
ofy().assertInTransaction();
DateTime now = ofy().getTransactionTime();
// Load the domain application.
DomainApplication domainApplication =
loadByUniqueId(DomainApplication.class, applicationId, now);
checkArgumentNotNull(domainApplication, "Domain application does not exist");
// It's not an error if the application already has the intended status. We want the method
// to be idempotent.
if (domainApplication.getApplicationStatus() == newStatus) {
System.err.printf("Domain application %s already has status %s\n", applicationId, newStatus);
return;
}
// Ensure domain does not already have a final status which it should not be updated from.
checkState(
!domainApplication.getApplicationStatus().isFinalStatus(),
"Domain application has final status %s",
domainApplication.getApplicationStatus());
// Update its status.
DomainApplication.Builder applicationBuilder = domainApplication.asBuilder()
.setApplicationStatus(newStatus)
.setLastEppUpdateTime(now)
// Use the current sponsor instead of the history client ID because the latter might be an
// internal client ID that we don't want to expose.
.setLastEppUpdateClientId(domainApplication.getCurrentSponsorClientId());
// Create a history entry (with no XML or Trid) to record that we are updating the status on
// this application.
HistoryEntry newHistoryEntry = new HistoryEntry.Builder()
.setType(HistoryEntry.Type.DOMAIN_APPLICATION_STATUS_UPDATE)
.setParent(domainApplication)
.setModificationTime(now)
.setClientId(clientId)
.setBySuperuser(true)
.build();
// Create a poll message informing the registrar that the application status was updated.
PollMessage.OneTime.Builder pollMessageBuilder = new PollMessage.OneTime.Builder()
.setClientId(domainApplication.getCurrentSponsorClientId())
.setEventTime(ofy().getTransactionTime())
.setMsg(message)
.setParent(newHistoryEntry)
.setResponseExtensions(ImmutableList.of(
new LaunchInfoResponseExtension.Builder()
.setApplicationId(domainApplication.getForeignKey())
.setPhase(domainApplication.getPhase())
.setApplicationStatus(newStatus)
.build()));
// If this is a final status (i.e. an end state), then we should remove pending create from the
// application and include a pending action notification in the poll message. Conversely, if
// this is not a final status, we should add pending create (which will already be there unless
// we're resurrecting an application).
if (newStatus.isFinalStatus()) {
applicationBuilder.removeStatusValue(StatusValue.PENDING_CREATE);
pollMessageBuilder.setResponseData(ImmutableList.of(
DomainPendingActionNotificationResponse.create(
domainApplication.getFullyQualifiedDomainName(),
ALLOCATED.equals(newStatus), // Whether the operation succeeded
getCreationTrid(domainApplication),
now)));
} else {
applicationBuilder.addStatusValue(StatusValue.PENDING_CREATE);
}
// Stage changes for all entities that need to be saved to datastore.
stageEntityChange(domainApplication, applicationBuilder.build());
stageEntityChange(null, pollMessageBuilder.build());
stageEntityChange(null, newHistoryEntry);
}
/** Retrieve the transaction id of the operation which created this application. */
static Trid getCreationTrid(DomainApplication domainApplication) {
Trid creationTrid = domainApplication.getCreationTrid();
if (creationTrid == null) {
// If the creation TRID is not present on the application (this can happen for older
// applications written before this field was added), then we must read the earliest history
// entry for the application to retrieve it.
return checkNotNull(checkNotNull(ofy()
.load()
.type(HistoryEntry.class)
.ancestor(domainApplication)
.order("modificationTime")
.first()
.now()).getTrid());
}
return creationTrid;
}
}

View file

@ -0,0 +1,120 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.net.InternetDomainName;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.launch.LaunchNotice;
import com.google.domain.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.tools.Command.RemoteApiCommand;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.VoidWork;
import org.joda.time.DateTime;
/** Command to update the claims notice on a domain application. */
@Parameters(separators = " =", commandDescription = "Update the claims notice on an application.")
final class UpdateClaimsNoticeCommand implements RemoteApiCommand {
@Parameter(
names = "--id",
description = "Application ID to update.",
required = true)
private String id;
@Parameter(
names = "--tcn_id",
description = "Trademark claims notice ID.",
required = true)
private String tcnId;
@Parameter(
names = "--validator_id",
description = "Trademark claims validator.")
private String validatorId = "tmch";
@Parameter(
names = "--expiration_time",
description = "Expiration time of claims notice.",
required = true)
private String expirationTime;
@Parameter(
names = "--accepted_time",
description = "Accepted time of claims notice.",
required = true)
private String acceptedTime;
@Override
public void run() throws Exception {
final LaunchNotice launchNotice = LaunchNotice.create(
tcnId, validatorId, DateTime.parse(expirationTime), DateTime.parse(acceptedTime));
ofy().transact(new VoidWork() {
@Override
public void vrun() {
try {
updateClaimsNotice(id, launchNotice);
} catch (InvalidChecksumException e) {
throw new RuntimeException(e);
}
}});
}
private void updateClaimsNotice(String applicationId, LaunchNotice launchNotice)
throws InvalidChecksumException {
ofy().assertInTransaction();
DateTime now = ofy().getTransactionTime();
// Load the domain application.
DomainApplication domainApplication =
loadByUniqueId(DomainApplication.class, applicationId, now);
checkArgument(domainApplication != null, "Domain application does not exist");
// Make sure this isn't a sunrise application.
checkArgument(domainApplication.getEncodedSignedMarks().isEmpty(),
"Can't update claims notice on sunrise applications.");
// Validate the new launch notice checksum.
String domainLabel = InternetDomainName.from(domainApplication.getFullyQualifiedDomainName())
.parts().get(0);
launchNotice.validate(domainLabel);
DomainApplication updatedApplication = domainApplication.asBuilder()
.setLaunchNotice(launchNotice)
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(domainApplication.getCurrentSponsorClientId())
.build();
// Create a history entry (with no XML or Trid) to record that we are updating the application.
HistoryEntry newHistoryEntry = new HistoryEntry.Builder()
.setType(HistoryEntry.Type.DOMAIN_APPLICATION_UPDATE)
.setParent(domainApplication)
.setModificationTime(now)
.setClientId(domainApplication.getCurrentSponsorClientId())
.setBySuperuser(true)
.build();
// Save entities to datastore.
ofy().save().<Object>entities(updatedApplication, newHistoryEntry);
}
}

View file

@ -0,0 +1,142 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.domain.registry.bigquery.BigqueryUtils.fromBigqueryTimestampString;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.domain.registry.model.billing.RegistrarCredit;
import com.google.domain.registry.model.billing.RegistrarCreditBalance;
import com.google.domain.registry.model.billing.RegistrarCreditBalance.BalanceMap;
import com.google.domain.registry.model.registrar.Registrar;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/** Command for writing out new balances of credits updated in an invoicing cycle. */
@Parameters(separators = " =", commandDescription = "Update a set of registrar credit balances")
final class UpdateCreditsCommand extends MutatingCommand {
@Parameter(
names = "--input_file",
description = "CSV file of updated credit balance information with this row format: "
+ " <registrar_id>,<credit_id>,<old_balance>,<new_balance>,<effective_time> "
+ "(typically produced by running the generate_invoice command)",
required = true)
private Path inputFile;
/** Struct-like container for an individual credit's balance update information. */
private static class CreditBalanceUpdate {
/** The id of the registrar to whom the credit being updated belongs. */
final String registrarId;
/** The id of the credit being updated (i.e. having a new balance written out). */
final Long creditId;
/** The old balance amount of the credit in minor units of the credit's currency. */
final Integer oldBalance;
/** The new balance amount of the credit in minor units of the credit's currency. */
final Integer newBalance;
/** The exact moment at which the new balance becomes active, replacing the old balance. */
final DateTime effectiveTime;
public CreditBalanceUpdate(List<String> fields)
throws IllegalArgumentException {
checkArgument(fields.size() == 5, "Wrong number of fields provided: %s", fields);
this.registrarId = fields.get(0);
this.creditId = Long.valueOf(fields.get(1));
this.oldBalance = Integer.valueOf(fields.get(2));
this.newBalance = Integer.valueOf(fields.get(3));
this.effectiveTime = fromBigqueryTimestampString(fields.get(4));
}
}
@Override
protected void init() throws Exception {
DateTime now = DateTime.now(UTC);
for (String line : Files.readAllLines(inputFile, StandardCharsets.UTF_8)) {
List<String> fields = Splitter.on(',').splitToList(line);
try {
stageCreditUpdate(new CreditBalanceUpdate(fields), now);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Cannot parse fields in line: " + line, e);
}
System.out.println();
}
}
/** Stage the actual new balance creation corresponding to the given credit balance update. */
private void stageCreditUpdate(CreditBalanceUpdate update, DateTime now) {
System.out.printf(
"Creating balance update for credit %s/%d at %s:\n",
update.registrarId,
update.creditId,
update.effectiveTime.toString());
// Load the registrar and the credit, checking for non-existence.
Registrar registrar = checkNotNull(Registrar.loadByClientId(update.registrarId),
"Registrar %s not found", update.registrarId);
RegistrarCredit credit = ofy().load()
.type(RegistrarCredit.class)
.parent(registrar)
.id(update.creditId)
.now();
checkNotNull(credit,
"Registrar credit for %s with ID %s not found",
update.registrarId, update.creditId.toString());
System.out.printf(" - Credit info: %s\n", credit.getSummary());
// Load the actual old balance at the moment before the update's effective time and ensure
// that it matches the expected old balance amount passed along in the update.
Optional<Money> oldBalance =
BalanceMap.createForCredit(credit).getActiveBalanceBeforeTime(update.effectiveTime);
checkState(oldBalance.isPresent(), "No balance found before effective time");
Money actualOldBalance = oldBalance.get();
Money expectedOldBalance = Money.ofMinor(credit.getCurrency(), update.oldBalance);
checkState(actualOldBalance.equals(expectedOldBalance),
"Real old balance does not match expected old balance (%s vs. %s)",
actualOldBalance,
expectedOldBalance);
// Create the new balance amount.
Money newBalance = Money.ofMinor(credit.getCurrency(), update.newBalance);
System.out.printf(" - %s -> %s\n", actualOldBalance, newBalance);
// Create and stage the new credit balance object for the new balance amount.
RegistrarCreditBalance newCreditBalance = new RegistrarCreditBalance.Builder()
.setParent(credit)
.setEffectiveTime(update.effectiveTime)
.setWrittenTime(now)
.setAmount(newBalance)
.build();
stageEntityChange(null, newCreditBalance);
}
}

View file

@ -0,0 +1,64 @@
// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools;
import com.google.common.base.Optional;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.RegistryCursor;
import com.google.domain.registry.model.registry.RegistryCursor.CursorType;
import com.google.domain.registry.tools.params.DateTimeParameter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.joda.time.DateTime;
import java.util.List;
/** Modifies {code RegistryCursor} timestamps used by locking rolling cursor tasks, like in RDE. */
@Parameters(separators = " =", commandDescription = "Modifies cursor timestamps used by LRC tasks")
final class UpdateCursorsCommand extends MutatingCommand {
@Parameter(
description = "TLDs on which to operate.",
required = true)
private List<String> tlds;
@Parameter(
names = "--type",
description = "Which cursor to update.",
required = true)
private CursorType cursorType;
@Parameter(
names = "--timestamp",
description = "The new timestamp to set.",
validateWith = DateTimeParameter.class,
required = true)
private DateTime newTimestamp;
@Override
protected void init() throws Exception {
for (String tld : tlds) {
Registry registry = Registry.get(tld);
Optional<DateTime> expectedTimestamp = RegistryCursor.load(registry, cursorType);
stageEntityChange(
expectedTimestamp.isPresent()
? RegistryCursor.create(registry, cursorType, expectedTimestamp.get())
: null,
RegistryCursor.create(registry, cursorType, newTimestamp));
}
}
}

Some files were not shown because too many files have changed in this diff Show more